BBjClientFile Example

REM This sample implements a file explorer to demonstrate the client
REM filesystem methods

REM USE declarations for Java classes

USE java.io.ByteArrayInputStream
USE java.io.ByteArrayOutputStream
USE java.io.FileInputStream
USE java.io.FileOutputStream
USE java.io.InputStream
USE java.io.OutputStream

USE java.lang.Object
USE java.lang.String
USE java.lang.System

USE java.util.Date
USE java.util.HashMap
USE java.util.Iterator
USE java.util.Properties
USE java.util.TreeMap

REM DECLARE statements for type checking

DECLARE BBjAPI myAPI!
DECLARE BBjSysGui mySysGui!
DECLARE BBjWindow myWindow!
DECLARE BBjTree myExplorer!
DECLARE BBjButton myHomeButton!
DECLARE BBjButton myCreateButton!
DECLARE BBjButton myDeleteButton!
DECLARE BBjButton myRenameButton!
DECLARE BBjButton myMkdirButton!
DECLARE BBjButton myReadOnlyButton!
DECLARE BBjButton myLoadConfigButton!
DECLARE BBjButton mySaveConfigButton!
DECLARE BBjStandardGrid myProperties!
DECLARE FileUtil myFileUtil!
DECLARE BBjClientFileSystem myClientFS!
DECLARE BBjClientFile myFile!
DECLARE BBjVector roots!
DECLARE BBjTreeNodeExpandedEvent myExpandEv!
DECLARE BBjTreeMouseDownEvent mySelectEv!
DECLARE BBjTreeNodeEditStoppedEvent myEditStoppedEv!
DECLARE BBjInt ROOT_NODE%

REM Open SYSGUI channel for display
sysgui = UNT
OPEN(sysgui)"X0"

REM Get BBjAPI and BBjSysGui objects
myAPI! = BBJAPI()
mySysGui! = myAPI!.getSysGui()

REM Create explorer window
myWindow! = mySysGui!.addWindow(100, 100, 400, 500, "Explorer", $00010092$)

REM Create explorer tree
myExplorer! = myWindow!.addTree(101, 0, 0, 200, 500)
ROOT_NODE% = 1
myExplorer!.setRoot(ROOT_NODE%, $$)
myExplorer!.setRootVisible(0)

REM Label for grid
myWindow!.addStaticText(102, 200, 0, 200, 25, "Properties", $4000$)

REM Grid for properties display
rows = 6
cols = 2
myProperties! = myWindow!.addGrid(103, 200, 25, 200, 225, $0010$, rows, cols)
myProperties!.setColumnWidth(0, 100)
myProperties!.setColumnWidth(1, 100)
myProperties!.setVisible(1)
myProperties!.setColumnAlignment(0, myProperties!.LEFT_ALIGNMENT)
myProperties!.setColumnAlignment(1, myProperties!.LEFT_ALIGNMENT)

REM Buttons for different example actions

myHomeButton! = myWindow!.addButton(104, 200, 250, 100, 25, "Home")
myCreateButton! = myWindow!.addButton(105, 300, 250, 100, 25, "Create File")
myDeleteButton! = myWindow!.addButton(106, 200, 275, 100, 25, "Delete")
myRenameButton! = myWindow!.addButton(107, 300, 275, 100, 25, "Rename")
myMkdirButton! = myWindow!.addButton(108, 200, 300, 100, 25, "Mkdir")
myReadOnlyButton! = myWindow!.addButton(109, 300, 300, 100, 25, "Set Read Only")
myLoadConfigButton! = myWindow!.addButton(110, 200, 325, 100, 25, "Load Config")
mySaveConfigButton! = myWindow!.addButton(111, 300, 325, 100, 25, "Save Config")

REM The BBjClientFilesystem object used to obtain client side file references
myClientFS! = myAPI!.getThinClient().getClientFileSystem()

REM A Custom Object to hold the data
myFileUtil! = new FileUtil(myClientFS!, myExplorer!)

REM Add filesystem roots to tree
roots! = myClientFS!.getRoots()
FOR i = 0 TO roots!.size() - 1
    myFile! = CAST(BBjClientFile, roots!.get(i))
    myFileUtil!.addFile(myFile!, ROOT_NODE%)
NEXT i

REM Setup callbacks
myWindow!.setCallback(myWindow!.ON_CLOSE, "DO_CLOSE")
myExplorer!.setCallback(myExplorer!.ON_TREE_EXPAND, "EXPAND")
myExplorer!.setCallback(myExplorer!.ON_TREE_MOUSE_DOWN, "SELECTED")
myExplorer!.setCallback(myExplorer!.ON_TREE_EDIT_STOP, "EDIT_STOPPED")
myHomeButton!.setCallback(myHomeButton!.ON_BUTTON_PUSH, "HOME_ACTION")
myCreateButton!.setCallback(myCreateButton!.ON_BUTTON_PUSH, "CREATE_ACTION")
myDeleteButton!.setCallback(myDeleteButton!.ON_BUTTON_PUSH, "DELETE_ACTION")
myRenameButton!.setCallback(myRenameButton!.ON_BUTTON_PUSH, "RENAME_ACTION")
myMkdirButton!.setCallback(myMkdirButton!.ON_BUTTON_PUSH, "MKDIR_ACTION")
myReadOnlyButton!.setCallback(myReadOnlyButton!.ON_BUTTON_PUSH, "READONLY_ACTION")
myLoadConfigButton!.setCallback(myLoadConfigButton!.ON_BUTTON_PUSH, "LOADCONFIG_ACTION")
mySaveConfigButton!.setCallback(mySaveConfigButton!.ON_BUTTON_PUSH, "SAVECONFIG_ACTION")

REM Once everything is done, set window visible
myWindow!.setVisible(1)

PROCESS_EVENTS

REM Callback for ON_TREE_EXPAND
EXPAND:
    myExpandEv! = CAST(BBjTreeNodeExpandedEvent, myAPI!.getLastEvent())
    currentID% = myExpandEv!.getNodeID()
    myFileUtil!.populateChildren(currentID%)
    RETURN

REM Callback for ON_TREE_MOUSE_DOWN
SELECTED:
    mySelectEv! = CAST(BBjTreeMouseDownEvent, myAPI!.getLastEvent())
    currentID% = mySelectEv!.getNodeID()
    myExplorer!.selectNode(currentID%)
    myFile! = myFileUtil!.getFile(currentID%)
    IF (myFile! = NULL())
        PRINT "Selected node " + STR(currentID%) + " was NULL()"
        PRINT "Text: " + myExplorer!.getNodeText(currentID%)
    ELSE
        myFileUtil!.setProperties(myFile!, myProperties!)
    ENDIF
    RETURN

REM Callback when a previously started edit is finished
EDIT_STOPPED:
    myEditStoppedEv! = CAST(BBjTreeNodeEditStoppedEvent, myAPI!.getLastEvent())
    currentID% = myEditStoppedEv!.getNodeID()
    myExplorer!.setNodeEditable(currentID%, 0)
    myExplorer!.setTreeEditable(0)
    name$ = myEditStoppedEv!.getNewText()
    myFileUtil!.createOrRenameFile(currentID%, name$)
    RETURN

REM Callback for window close box
DO_CLOSE:
    RELEASE

REM Callback for "Home" button
HOME_ACTION:
    myFileUtil!.showHomeDirectory()
    RETURN

REM Callback for "Create File" button
CREATE_ACTION:
    myFileUtil!.createFile()
    RETURN

REM Callback for "Delete" button
DELETE_ACTION:
    myFileUtil!.deleteFile()
    RETURN

REM Callback for "Rename" button
RENAME_ACTION:
    node% = myExplorer!.getSelectedNode()
    IF (node% > 0)
        myExplorer!.setNodeText(node%, myFileUtil!.getFile(node%).getName())
        myExplorer!.setTreeEditable(1)
        myExplorer!.setNodeEditable(node%, 1)
        myExplorer!.editNode(node%)
    ENDIF
    RETURN

REM Callback for "Mkdir" button
MKDIR_ACTION:
    myFileUtil!.mkdir()
    RETURN

REM Callback for "Set Readonly " button
READONLY_ACTION:
    node% = myExplorer!.getSelectedNode()
    IF (node% > 0)
        myFile! = myFileUtil!.getFile(node%)
        myFile!.setReadOnly()
        myFileUtil!.setProperties(myFile!, myProperties!)
    ENDIF
    RETURN

REM Callback for "Load Config" button
LOADCONFIG_ACTION:
    myFileUtil!.loadConfig()
    RETURN

REM Callback for "Save Config" button
SAVECONFIG_ACTION:
    myFileUtil!.saveConfig()
    RETURN

REM Utility class to store all the associated state for the tree and the files
CLASS PUBLIC FileUtil
    REM Map from tree node numbers to files
    FIELD PRIVATE HashMap NodeToFileMap! = new HashMap()

    REM Map from files to tree node numbers
    FIELD PRIVATE HashMap FileToNodeMap! = new HashMap()

    REM explorer tree control
    FIELD PRIVATE BBjTree Explorer!

    REM Client filesystem
    FIELD PRIVATE BBjClientFileSystem ClientFS!

    REM Node number used for new tree nodes
    FIELD PRIVATE BBjInt NodeNum% = 2

    REM Configured directory color
    FIELD PRIVATE BBjString DirColor$ = "#0000ff"

    REM Configured color for non-files
    FIELD PRIVATE BBjString NonFileColor$ = "#ff0000"

    REM Configuration whether config is stored in memory or on disk
    FIELD PRIVATE BBjInt MemoryConfig%

    REM Local configuration file, if used
    FIELD PRIVATE BBjString LocalConfig$

    REM Last modification time of remote config file
    FIELD PRIVATE BBjNumber LastModified

    REM Constants for the configuration properties file
    FIELD PUBLIC STATIC BBjString DIR_COLOR$ = "configfs.dirColor"
    FIELD PUBLIC STATIC BBjString NON_FILE_COLOR$ = "configfs.nonFileColor"
    FIELD PUBLIC STATIC BBjString MEMORY_CONFIG$ = "configfs.memoryConfig"

    METHOD PUBLIC FileUtil(BBjClientFileSystem p_clientFS!, BBjTree p_explorer!)
        REM Set the values for the fields
        #Explorer! = p_explorer!
        #ClientFS! = p_clientFS!

        REM Load the config from the client, if it exists
        #loadConfig()
    METHODEND

    REM Adds a file to the tree below the node specified by p_parent%
    METHOD PUBLIC VOID addFile(BBjClientFile p_clientFile!, BBjInt p_parent%)
        DECLARE BBjString text$
        DECLARE BBjInt node%

        REM If we already have the file, reuse it.
        IF (#FileToNodeMap!.containsKey(p_clientFile!))
            node% = #getNode(p_clientFile!)
        REM Otherwise, make a new node
        ELSE
            node% = #NodeNum%
            #NodeNum% = INT(#NodeNum% + 1)
        ENDIF

        REM Get the html formatted text for the file
        text$ = #getFileNameText(p_clientFile!)

        REM Add the file, using the folder icons for directories, and normal icons for others.
        IF (p_clientFile!.isDirectory())
            #Explorer!.addExpandableNode(node%, p_parent%, text$)
        ELSE
            #Explorer!.addNode(node%, p_parent%, text$)
        ENDIF

        REM Add the tree node number and file to the maps for other operations
        #NodeToFileMap!.put(node%, p_clientFile!)
        #FileToNodeMap!.put(p_clientFile!, node%)
    METHODEND

    REM Returns an HTML-formatted string for the given client file based on
    REM its properties
    METHOD PUBLIC BBjString getFileNameText(BBjClientFile p_clientFile!)
        DECLARE BBjString text$
        text$ = p_clientFile!.getName()
        styled = 0

        REM If it's a directory, use the saved directory color.
        IF (p_clientFile!.isDirectory())
            text$ = "<font color=""" + #DirColor$ + """>" + text$ + "/</font>"
            styled = 1
        ELSE
            REM otherwise, if it's not a regular file, use the non-file color
            IF (! p_clientFile!.isFile())
                text$ = "<font color=""" + #NonFileColor$ + """>" + text$ + "</font>"
                styled = 1
            ENDIF
        ENDIF

        REM If it's a hidden file, make it italicized
        IF (p_clientFile!.isHidden())
            text$ = "<i>" + text$ + "</i>"
            styled = 1
        ENDIF

        REM If it's been styled in anyway, add html tags
        IF styled
            text$ = "<html>" + text$ + "</html>"
        ENDIF

        REM Return the text
        METHODRET text$
    METHODEND

    REM Return the file associated with the provided node number
    METHOD PUBLIC BBjClientFile getFile(BBjInt p_nodeNum%)
        DECLARE BBjClientFile ret!
        ret! = CAST(BBjClientFile, #NodeToFileMap!.get(p_nodeNum%))
        METHODRET ret!
    METHODEND


    REM Return the node number associated with the provided file
    METHOD PUBLIC BBjInt getNode(BBjClientFile p_file!)
        DECLARE BBjInt ret%

       IF (#FileToNodeMap!.get(p_file!) <> NULL()) THEN
          ret% = CAST(BBjInt, #FileToNodeMap!.get(p_file!))
          METHODRET ret%
       else
          METHODRET 2
       ENDIF

    METHODEND


    METHOD PUBLIC VOID populateChildren(BBjInt p_nodeNum%)
        DECLARE BBjClientFile myCurrentFile!
        DECLARE BBjClientFile child!
        DECLARE BBjVector children!
        DECLARE TreeMap sorter!
        DECLARE Iterator iter!
        DECLARE BBjString name$
        DECLARE Object key!

        REM Get the file associated with p_nodeNum%
        myCurrentFile! = #getFile(p_nodeNum%)

        REM If it exists, then populate the children of the directory
        IF (myCurrentFile! <> NULL())
            REM If it has children, remove all the descendents, and repopulate the list
            IF (#Explorer!.getNumChildren(p_nodeNum%))
                #removeDescendents(p_nodeNum%)
            ENDIF

            REM If it's a directory, add all the files below it.
            IF (myCurrentFile!.isDirectory())
                children! = myCurrentFile!.listFiles()
                IF (children!.size() > 0)
                    REM TreeMap used for sorting the file names
                    sorter! = new TreeMap()
                    FOR i = 0 TO children!.size() - 1
                        child! = CAST(BBjClientFile, children!.get(i))
                        name$ = child!.getName()

                        REM Sort directories before files
                        IF (child!.isDirectory())
                            name$ = $00$ + name$
                        ENDIF
                        sorter!.put(name$, child!)
                    NEXT i

                    REM Iterating through the TreeMap will add the files in sorted order
                    iter! = sorter!.keySet().iterator()
                    WHILE iter!.hasNext()
                        key! = iter!.next()
                        child! = CAST(BBjClientFile, sorter!.get(key!))
                        #addFile(child!, p_nodeNum%)
                    WEND
                ENDIF
            ENDIF
        ENDIF
    METHODEND

    REM Sets the properties of the given file in the grid
    METHOD PUBLIC VOID setProperties(BBjClientFile p_file!, BBjStandardGrid p_properties!)
        DECLARE BBjVector data!
        data! = BBJAPI().makeVector()


        REM Populate each line of the grid with the appropriate data.
        data!.addItem("Name"); data!.addItem(p_file!.getName())
        data!.addItem("Canonical"); data!.addItem(p_file!.getCanonicalFile().getPath())
        data!.addItem("Contained in"); IF (p_file!.getParent() = NULL()) THEN data!.addItem("<root>") ELSE data!.addItem(p_file!.getParent().getPath()) FI
        data!.addItem("Readable"); data!.addItem(String.valueOf(p_file!.canRead()))
        data!.addItem("Writeable"); data!.addItem(String.valueOf(p_file!.canWrite()))
        data!.addItem("Modified Date"); data!.addItem(new Date(p_file!.lastModified()).toString())


        REM Set the data in the grid
        p_properties!.setCellText(0, 0, data!)
    METHODEND


    METHOD PUBLIC VOID showHomeDirectory()
        DECLARE BBjVector path!
        DECLARE BBjVector files!
        DECLARE BBjClientFile file!
        DECLARE BBjClientFile parent!
        DECLARE Iterator iter!
        DECLARE BBjInt node%
        DECLARE Object node!

        REM We have to populate the tree down to the home directory,
        REM if it's never been done

        file! = #ClientFS!.getHomeDirectory()
        path! = BBJAPI().makeVector()
        parent! = file!
        WHILE (parent! <> NULL() AND (! #FileToNodeMap!.containsKey(parent!)))
            path!.add(0, parent!)
            parent! = parent!.getParent()
        WEND

        REM The vector has all the directories, from closest to the root to
        REM furthest from the root.  Iterate and populate until we're home
 
        IF (parent! <> NULL())
            node% = #getNode(parent!)

            #populateChildren(node%)
            iter! = path!.iterator()
            WHILE (iter!.hasNext())
                file! = CAST(BBjClientFile, iter!.next())
                node% = #getNode(file!)
                #populateChildren(node%)
            WEND

            REM Once we're home, show that node
            #Explorer!.expandNode(node%)
            #Explorer!.setNodeVisible(node%)
            #Explorer!.selectNode(node%)
        ELSE
            REM Should never happen
            PRINT "parent = NULL()"
        ENDIF

    METHODEND

    REM Remove a specific node and all its children from the tree and the maps
    METHOD PRIVATE VOID remove(BBjInt p_node%)
        DECLARE BBjClientFile file!
        DECLARE BBjInt children%

        REM First iterate through any children and recursively call remove()
        children% = #Explorer!.getNumChildren(p_node%)
        IF (children% > 0)
            REM Go backwards, so the index remains consistent
            FOR i = children% - 1 TO 0 STEP -1
                REM recursive call
                #remove(#Explorer!.getChildAt(p_node%, i))
            NEXT i
        ENDIF

        REM Finally, remove the node itself and the associated file
        file! = #getFile(p_node%)
        #NodeToFileMap!.remove(p_node%)
        #FileToNodeMap!.remove(file!)
        #Explorer!.removeNode(p_node%)
    METHODEND

    REM Remove all the children of a specific node from the tree and the maps
    METHOD PRIVATE VOID removeDescendents(BBjInt p_node%)
        DECLARE BBjInt children%
        DECLARE BBjInt child%
        DECLARE BBjClientFile file!

        REM call remove() on all the children
        children% = #Explorer!.getNumChildren(p_node%)
        IF (children% > 0)
            FOR i = children% - 1 TO 0 STEP -1
                child% = #Explorer!.getChildAt(p_node%, i)
                #remove(child%)
            NEXT i
        ENDIF
    METHODEND

    REM Get ready to create a new file
    METHOD PUBLIC VOID createFile()
        DECLARE BBjInt node%
        DECLARE BBjInt newNode%
        DECLARE BBjNumber dummy
        DECLARE BBjClientFile file!

        REM Only do it if there's a selected node.
        node% = #Explorer!.getSelectedNode()
        IF (node% <> -1)
            file! = #getFile(node%)
            REM If the selected node is a directory, create the file in the directory
            IF (file!.isDirectory())
                #populateChildren(node%)
            REM Otherwise, get the parent of the specified file and use that.
            ELSE
                file! = file!.getParent()
                node% = #getNode(file!)
            ENDIF

            REM If we can write to the directory, add a new node, and
            REM immediately begin editing the node.
            IF (file!.canWrite())
                newNode% = #NodeNum%
                #NodeNum% = INT(#NodeNum% + 1)
                #Explorer!.addNode(newNode%, node%, "New File")
                #Explorer!.setTreeEditable(1)
                #Explorer!.setNodeEditable(newNode%, 1)
                #Explorer!.setNodeVisible(newNode%)
                #Explorer!.editNode(newNode%)
                REM When the edit finishes, we'll get an event and create the file then
            REM If we can't, tell the user
            ELSE
                dummy = MSGBOX("Cannot write to this directory")
            ENDIF
        ENDIF
    METHODEND

    REM delete the selected node
    METHOD PUBLIC VOID deleteFile()
        DECLARE BBjInt node%
        DECLARE BBjClientFile file!
        DECLARE BBjNumber dummy

        REM Determine what file is selected
        node% = #Explorer!.getSelectedNode()
        file! = CAST(BBjClientFile, #NodeToFileMap!.get(node%))
        REM If the file may not be deleted, tell the user
        IF (! file!.delete())
            dummy = MSGBOX("Delete failed")
        REM Otherwise, delete it from all the maps.
        ELSE
            #FileToNodeMap!.remove(file!)
            #NodeToFileMap!.remove(node%)
            #Explorer!.removeNode(node%)
        ENDIF
    METHODEND

    REM Make a directory
    METHOD PUBLIC VOID mkdir()
        DECLARE BBjInt parentNode%
        DECLARE BBjInt newNode%
        DECLARE BBjNumber dummy
        DECLARE BBjClientFile parent!
        DECLARE BBjClientFile newDir!

        REM Get the selected node to create a directory in
        parentNode% = #Explorer!.getSelectedNode()
        IF (parentNode% <> -1)
            REM Get the directory containing the file if it's not a directory
            parent! = #getFile(parentNode%)
            IF (parent!.isDirectory())
                #populateChildren(parentNode%)
            ELSE
                parent! = parent!.getParent()
                parentNode% = #getNode(parent!)
            ENDIF

            REM If we can write to the directory, create a new folder
            IF (parent!.canWrite())
                newDir! = #ClientFS!.getClientFile(parent!, "New Folder")
                REM If we can create the folder, start editing the name.
                IF (newDir!.mkdir())
                    #addFile(newDir!, parentNode%)
                    newNode% = #getNode(newDir!)
                    #Explorer!.setNodeText(newNode%, newDir!.getName())
                    #Explorer!.setTreeEditable(1)
                    #Explorer!.setNodeEditable(newNode%, 1)
                    #Explorer!.setNodeVisible(newNode%)
                    #Explorer!.editNode(newNode%)
                    REM Respond to the event as though it were a rename event
                REM Otherwise, tell the user
                ELSE
                    dummy = MSGBOX("mkdir failed")
                ENDIF
            REM Otherwise, tell the user
            ELSE
                dummy = MSGBOX("Cannot write to this directory")
            ENDIF
        ENDIF
    METHODEND

    REM This method is called in response to an editstopped event.
    METHOD PUBLIC VOID createOrRenameFile(BBjInt p_node%, BBjString p_string$)
        DECLARE BBjClientFile file!
        DECLARE BBjClientFile newFile!
        DECLARE BBjClientFile parentFile!
        DECLARE BBjInt parent%
        DECLARE BBjInt node%

        file! = CAST(BBjClientFile, #NodeToFileMap!.get(p_node%))

        REM If the file does not exist, we're creating a new file
        IF (file! = NULL())
            REM Remove the existing node
            parent% = #Explorer!.getParentNode(p_node%)          
            parentFile! = CAST(BBjClientFile, #NodeToFileMap!.get(parent%))
            #Explorer!.removeNode(p_node%)

            REM Use the new text p_string$ as the new file name
            newFile! = #ClientFS!.getClientFile(parentFile!, p_string$)
            newFile!.createNewFile()

        REM Otherwise, the file does exist, and we're renaming it
        ELSE
            REM Get the file representing the new name
            parentFile! = file!.getParent()
            parent% = #getNode(parentFile!)
            newFile! = #ClientFS!.getClientFile(parentFile!, p_string$)

            REM Rename the old file to the new file name.
            file!.renameTo(newFile!)

            REM Remove the old file, we'll add the new one afterwards
            #NodeToFileMap!.remove(p_node%)
            #FileToNodeMap!.remove(file!)
            #Explorer!.removeNode(p_node%)
        ENDIF

        REM Add the new file, and reselect it
        #addFile(newFile!, parent%)
        node% = #getNode(newFile!)
        #Explorer!.selectNode(node%)
        #Explorer!.setNodeVisible(node%)
    METHODEND

    REM Load the configuration from the client
    METHOD PUBLIC VOID loadConfig()
        DECLARE BBjClientFile userConfigFile!
        DECLARE BBjClientFile userHome!
        DECLARE BBjString localFile$
        DECLARE Properties props!
        DECLARE InputStream input!
        DECLARE BBjInt tmpMemoryConfig%

        REM Get the home directory
        userHome! = #ClientFS!.getHomeDirectory()

        REM This is the file name used for the config file
        userConfigFile! = #ClientFS!.getClientFile(userHome!.getPath() + "/.clientfs/config/config.properties")

        REM If it exists and we can read it, get the file contents
        IF (userConfigFile!.exists() AND userConfigFile!.canRead())
            props! = new Properties()
            REM If we're using an in-memory config, read the bytes directly
            IF (#MemoryConfig%)
                contents$ = userConfigFile!.getContents()
                input! = new ByteArrayInputStream(contents$)
            REM Otherwise, write to a local file and read it that way
            ELSE
                #LocalConfig$ = userConfigFile!.copyFromClient()
                input! = new FileInputStream(#LocalConfig$)
            ENDIF

            REM Load the properties from the InputStream
            props!.load(input!)

            REM Set the fields based on each property.
            IF (props!.getProperty(#DIR_COLOR$) <> NULL())
                #DirColor$ = props!.getProperty(#DIR_COLOR$)
            ENDIF
            IF (props!.getProperty(#NON_FILE_COLOR$) <> NULL())
                #NonFileColor$ = props!.getProperty(#NON_FILE_COLOR$)
            ENDIF
            IF (props!.getProperty(#MEMORY_CONFIG$) <> NULL())
                tmpMemoryConfig% = INT(NUM(props!.getProperty(#MEMORY_CONFIG$), ERR=*NEXT))
                #MemoryConfig% = tmpMemoryConfig%
            ENDIF

            REM Remember the last time we read the file by setting the last modified time
            #LastModified = System.currentTimeMillis()
            userConfigFile!.setLastModified(#LastModified)
        ENDIF
    METHODEND

    REM Save the configuration properties file
    METHOD PUBLIC VOID saveConfig()
        DECLARE BBjClientFile userConfigFile!
        DECLARE BBjClientFile userConfigDir!
        DECLARE BBjClientFile userHome!
        DECLARE ByteArrayOutputStream byteOutput!
        DECLARE FileOutputStream fileOutput!
        DECLARE Properties props!
        DECLARE BBjString contents$
        DECLARE BBjInt msgResult%

        REM Get the home directory and the subdirectory containing the configuration.
        userHome! = #ClientFS!.getHomeDirectory()
        userConfigDir! = #ClientFS!.getClientFile(userHome!.getPath() + "/.clientfs/config")

        REM Get the file object representing the directory.
        userConfigFile! = #ClientFS!.getClientFile(userConfigDir!, "config.properties")

        REM Create the directory if it's never existed.
        IF (! userConfigDir!.exists())
            IF (! userConfigDir!.mkdirs())
                dummy = MSGBOX("Cannot create config dir")
                METHODRET
            ENDIF
        ENDIF

        REM IF the file exists, check to make sure we're not overwriting changes
        IF (userConfigFile!.exists())
            IF (userConfigFile!.lastModified() > #LastModified)
                msgResult% = MSGBOX("Config file modified since last load.  Overwrite changes?", 1)
                IF (msgResult% <> 1)
                    METHODRET
                ENDIF
            ENDIF
        ENDIF

        REM Set the properties from this object

        props! = new Properties()
        props!.setProperty(#DIR_COLOR$, #DirColor$)
        props!.setProperty(#NON_FILE_COLOR$, #NonFileColor$)
        props!.setProperty(#MEMORY_CONFIG$, "1")

        REM If using an in-memory config, store the contents to a string.
        IF (#MemoryConfig%)
            byteOutput! = new ByteArrayOutputStream()
            props!.store(byteOutput!, "Client FS Sample Config File")
            byteOutput!.close()

            REM Assign the bytes to a string variable
            bytes$ = byteOutput!.toByteArray()

            REM Set those contents across the wire
            userConfigFile!.setContents(bytes$)

        REM Otherwise, use a local file and then copy contents over.
        ELSE
            REM If we don't have a local config file make one
            IF (#LocalConfig$ = $$)
                IF (! userConfigFile!.exists())
                    userConfigFile!.createNewFile()
                ENDIF
                #LocalConfig$ = userConfigFile!.copyFromClient()
            ENDIF

            REM Write the properties to a local file.
            fileOutput! = new FileOutputStream(#LocalConfig$)
            props!.store(fileOutput!, "Client FS Sample Config File")
            fileOutput!.close()

            REM Copy the file to the client
            userConfigFile!.copyToClient(#LocalConfig$)
        ENDIF

        REM Update the last modified time
        #LastModified = userConfigFile!.lastModified()
    METHODEND
CLASSEND