BBjClientFile Example

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

REM USE declarations for Java classes


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 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!

REM Open SYSGUI channel for display
sysgui = UNT

REM Get BBjAPI and BBjSysGui objects
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)
myExplorer!.setRoot(ROOT_NODE%, $$)

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!.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%)

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


    myExpandEv! = CAST(BBjTreeNodeExpandedEvent, myAPI!.getLastEvent())
    currentID% = myExpandEv!.getNodeID()

    mySelectEv! = CAST(BBjTreeMouseDownEvent, myAPI!.getLastEvent())
    currentID% = mySelectEv!.getNodeID()
    myFile! = myFileUtil!.getFile(currentID%)
    IF (myFile! = NULL())
        PRINT "Selected node " + STR(currentID%) + " was NULL()"
        PRINT "Text: " + myExplorer!.getNodeText(currentID%)
        myFileUtil!.setProperties(myFile!, myProperties!)

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

REM Callback for window close box

REM Callback for "Home" button

REM Callback for "Create File" button

REM Callback for "Delete" button

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

REM Callback for "Mkdir" button

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

REM Callback for "Load Config" button

REM Callback for "Save Config" button

REM Utility class to store all the associated state for the tree and the files
    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

    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
            node% = #NodeNum%
            #NodeNum% = INT(#NodeNum% + 1)

        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$)
            #Explorer!.addNode(node%, p_parent%, text$)

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

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

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

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

        REM Return the text
        METHODRET text$

    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!

    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%
          METHODRET 2


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

            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$
                        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%)

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

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

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

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

            REM Once we're home, show that node
            REM Should never happen
            PRINT "parent = NULL()"


    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

        REM Finally, remove the node itself and the associated file
        file! = #getFile(p_node%)

    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)
            NEXT i

    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())
            REM Otherwise, get the parent of the specified file and use that.
                file! = file!.getParent()
                node% = #getNode(file!)

            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!.setNodeEditable(newNode%, 1)
                REM When the edit finishes, we'll get an event and create the file then
            REM If we can't, tell the user
                dummy = MSGBOX("Cannot write to this directory")

    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.

    REM Make a directory
        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())
                parent! = parent!.getParent()
                parentNode% = #getNode(parent!)

            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!.setNodeEditable(newNode%, 1)
                    REM Respond to the event as though it were a rename event
                REM Otherwise, tell the user
                    dummy = MSGBOX("mkdir failed")
            REM Otherwise, tell the user
                dummy = MSGBOX("Cannot write to this directory")

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

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

        REM Otherwise, the file does exist, and we're renaming it
            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.

            REM Remove the old file, we'll add the new one afterwards

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

    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/")

        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
                #LocalConfig$ = userConfigFile!.copyFromClient()
                input! = new FileInputStream(#LocalConfig$)

            REM Load the properties from the InputStream

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

            REM Remember the last time we read the file by setting the last modified time
            #LastModified = System.currentTimeMillis()

    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!, "")

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

        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)

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

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

            REM Set those contents across the wire

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

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

            REM Copy the file to the client

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


Copyright BASIS International Ltd. BBj®, Visual PRO/5®, PRO/5®, and BBx® are registered trademarks.