//
//  File:   hdsExec.cpp
//
//  Legal Notice:   This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
//  The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free
//  Software Foundation, either version 3 of the License, or any later version.
//
//  The HadronZoo C++ Class Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
//  A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
//  Description:
//
//  Implementation of Dissemino forms. All the important executive actions occur as a result of data submitted in forms so functions relating to Dissemino forms
//  have been groupd in this file. Included are the hdsApp member functions responsible for reading form definitons and commands from the configs.
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdarg.h>
#include <dirent.h>
#include <netdb.h>
#include <signal.h>
#include "hzErrcode.h"
//#include "hzFsTbl.h"
#include "hzTokens.h"
#include "hzDissemino.h"
using namespace std ;
/*
**  SECTION 1:  Exec/Form Config Functions
*/
const char* Exec2Txt    (Exectype eType)
{
    //  Category:   Diagnostics
    static const char*  _exec_types [] =
    {
        "EXEC_NULL",
        "EXEC_SENDEMAIL",
        "EXEC_SETVAR",
        "EXEC_ADDUSER",
        "EXEC_LOGON",
        "EXEC_TEST",
        "EXEC_EXTRACT",
        "EXEC_OBJ_TEMP",
        "EXEC_OBJ_START",
        "EXEC_OBJ_FETCH",
        "EXEC_OBJ_IMPORT",
        "EXEC_OBJ_EXPORT",
        "EXEC_OBJ_SETMBR",
        "EXEC_OBJ_COMMIT",
        "EXEC_OBJ_CLOSE",
        "EXEC_TREE_DCL",
        "EXEC_TREE_HEAD",
        "EXEC_TREE_ITEM",
        "EXEC_TREE_FORM",
        "EXEC_TREE_SYNC",
        "EXEC_TREE_DEL",
        "EXEC_TREE_EXP",
        "EXEC_TREE_CLR",
        "EXEC_SRCH_PAGES",
        "EXEC_SRCH_REPOS",
        "EXEC_FILESYS",
        "EXEC_INVALID"
    } ;
    switch  (eType)
    {
    case EXEC_NULL:         return _exec_types[0] ;
    case EXEC_SENDEMAIL:    return _exec_types[1] ;
    case EXEC_SETVAR:       return _exec_types[2] ;
    case EXEC_ADDUSER:      return _exec_types[3] ;
    case EXEC_LOGON:        return _exec_types[4] ;
    case EXEC_TEST:         return _exec_types[5] ;
    case EXEC_EXTRACT:      return _exec_types[6] ;
    case EXEC_OBJ_TEMP:     return _exec_types[7] ;
    case EXEC_OBJ_START:    return _exec_types[8] ;
    case EXEC_OBJ_FETCH:    return _exec_types[9] ;
    case EXEC_OBJ_IMPORT:   return _exec_types[10] ;
    case EXEC_OBJ_EXPORT:   return _exec_types[11] ;
    case EXEC_OBJ_SETMBR:   return _exec_types[12] ;
    case EXEC_OBJ_COMMIT:   return _exec_types[13] ;
    case EXEC_OBJ_CLOSE:    return _exec_types[14] ;
    case EXEC_TREE_DCL:     return _exec_types[15] ;
    case EXEC_TREE_HEAD:    return _exec_types[16] ;
    case EXEC_TREE_ITEM:    return _exec_types[17] ;
    case EXEC_TREE_FORM:    return _exec_types[18] ;
    case EXEC_TREE_SYNC:    return _exec_types[19] ;
    case EXEC_TREE_DEL:     return _exec_types[20] ;
    case EXEC_TREE_EXP:     return _exec_types[21] ;
    case EXEC_TREE_CLR:     return _exec_types[22] ;
    case EXEC_SRCH_PAGES:   return _exec_types[23] ;
    case EXEC_SRCH_REPOS:   return _exec_types[24] ;
    case EXEC_FILESYS:      return _exec_types[25] ;
    }
    return _exec_types[26] ;
}
hdsFormhdl::hdsFormhdl  (void)
{
    m_pFormdef = 0 ;
    m_pCompletePage = 0 ;
    m_pFailDfltPage = 0 ;
    m_Access = 0 ;
    m_flgFH = 0 ;
}
hdsFormhdl::~hdsFormhdl (void)
{
    hzList<hdsExec*>::Iter  ei ;    //  Command iterator
    hdsExec*    pExec ;             //  Current instruction
    for (ei = m_Exec ; ei.Valid() ; ei++)
    {
        pExec = ei.Element() ;
        delete pExec ;
    }
}
#if 0
hzEcode hdsApp::_readExec   (hzXmlNode* pN, hzList<hdsExec*>& execList, hdsPage* pPage, hdsFormhdl* pFhdl)
hzEcode hdsApp::_readFormHdl    (hzXmlNode* pN)
hzEcode hdsApp::_readFormDef    (hzXmlNode* pN)
hdsFormref* hdsApp::_readFormDef    (hzXmlNode* pN, hdsResource* pLR)
hdsFormref* hdsApp::_readFormRef    (hzXmlNode* pN, hdsResource* pPage)
hdsVE*  hdsApp::_readField  (hzXmlNode* pN, hdsFormdef* pForm)
hdsVE*  hdsApp::_readXhide  (hzXmlNode* pN, hdsFormdef* pForm)
hzEcode hdsApp::_readResponse   (hzXmlNode* pN, hdsFormhdl* pFhdl, hzString& pageGoto, hdsResource** pPageGoto)
#endif
/*
**  SECTION 2:  Exec/Form Execution Functions
*/
hzEcode hdsExec::SendEmail  (hzChain& errorReport, hzHttpEvent* pE)
{
    _hzfunc("hdsExec::SendEmail") ;
    hzEmail     msg ;       //  Email instance
    hzEmaddr    emaFrom ;   //  Formal email address
    hzEmaddr    emaTo ;     //  Formal email address
    hzString    param1 ;    //  Used for sender address
    hzString    param2 ;    //  Used for recipient address
    hzString    param3 ;    //  Used for message subject
    hzString    param4 ;    //  Used for message content
    hzString    S ;         //  Intermediate string
    hzEcode     rc ;        //  Return code
    param1 = m_pApp->m_ExecParams[m_FstParam] ;
    param2 = m_pApp->m_ExecParams[m_FstParam+1] ;
    param3 = m_pApp->m_ExecParams[m_FstParam+2] ;
    param4 = m_pApp->m_ExecParams[m_FstParam+3] ;
    S = m_pApp->ConvertText(param1, pE) ;
    if (!S)
        { errorReport.Printf("No sender supplied (%s)\n", *param1) ; return E_NODATA ; }
    emaFrom = S ;
    if (!emaFrom)
        { errorReport.Printf("Bad sender address %s (%s)\n", *S) ; return E_BADADDRESS ; }
    S = m_pApp->ConvertText(param2, pE) ;
    if (!S)
        { errorReport.Printf("No recipient supplied (%s)\n", *param2) ; return E_NODATA ; }
    emaTo = S ;
    if (!emaTo)
        { errorReport.Printf("Bad recipient address %s (%s)\n", *S) ; return E_BADADDRESS ; }
    rc = msg.SetSender(emaFrom) ;
    if (rc != E_OK)
        { errorReport.Printf("Could not set sender to %s, err=%s\n", *emaFrom, Err2Txt(rc)) ; return rc ; }
    errorReport.Printf("Set sender to %s\n", *emaFrom) ;
    //  Establish recipient
    rc = msg.AddRecipient(emaTo) ;
    if (rc != E_OK)
        { errorReport.Printf("Could not set recipient to %s, err=%s\n", *emaTo, Err2Txt(rc)) ; return rc ; }
    errorReport.Printf("Set rcpt to %s\n", *emaTo) ;
    //  Establish subject
    S = m_pApp->ConvertText(param3, pE) ;
    rc = msg.SetSubject(S) ;
    if (rc != E_OK)
        { errorReport.Printf("Could not set subject to %s, err=%s\n", *S, Err2Txt(rc)) ; return rc ; }
    errorReport.Printf("Set subject to %s (%s)\n", *S, *param3) ;
    //  Establish message body
    msg.m_Text = m_pApp->ConvertText(param4, pE) ;
    errorReport.Printf("Set message body (%d bytes)\n", msg.m_Text.Size()) ;
    rc = msg.Compose() ;
    if (rc != E_OK)
        { errorReport.Printf("Could not compose message, err=%s\n", Err2Txt(rc)) ; return rc ; }
    errorReport.Printf("Composed message\n") ;
    rc = msg.SendSmtp("127.0.0.1", *m_pApp->m_SmtpUser, *m_pApp->m_SmtpPass) ;
    return rc ;
}
hzEcode hdsExec::Adduser    (hzChain& errorReport, hzHttpEvent* pE)
{
    //  Explicitly create a new user as an executive step.
    //
    //  This is done as part of the response to submission of a user registration form. In the form submission exec blocks, an <addUser> tag gives rise to an execAddUser instance,
    //  which in turn results in a call to this function. The <addUser> tag should not be used in any other context. Likewise, adding new users should not be attempted by a series
    //  of execCommit directives.
    //
    //  NOTE: This function ONLY adds the new user to the standard subscriber repository
    
    //  In the common case where the webapp has only one type of user, 
    //.will insert information for a new user into both the specified user repository and the inbult subscriber repository. The sequence is as follows:-
    //
    //  1)  The proposed username must be unique across all users in all user classes. The username must not already exist in the subscriber class.
    //  2)  The address of the subsriber entry is established in order to be inserted in the user class
    //  3)  The user info that belongs in the user class repository is added
    //  4)  The user info that belongs in the subscriber repository is added
    //
    //  If this action fails on the grounds of poor setup, this is a fatal error.
    //  If this action fails on the grounds of insufficient user information, this is a config error as it should not be possible to enter an incomplete user
    //  registration form.
    //  If this action fails on the grounds of username already in existance, send the error page.
    //
    //
    //  Arguments:  1)  error   The error report chain
    //              2)  pE      The HTTP event pointer
    //
    //  Returns
    //
    //  Note: Errors are exclusively reported to the 
    _hzfunc("hdsExec::Adduser") ;
    hzList  <hzPair>::Iter  pi ;    //  member name/value pairs for user-class object
    //const hdbClass*       pSubsClass ;    //  User class
    const hdbClass*     pUserClass ;    //  User class
    hdbObject           objSubscriber ;     //  Subscriber object
    hdbObject           userObj ;       //  User class object
    hzAtom              atom ;          //  Atom for loading objects
    hzChain             err ;           //  Error report
    hzLogger*           pLog ;          //  Thread logger
    //hdbObjRepos*      pSubsRepos ;    //  User class repository
    hdbObjRepos*        pUserRepos ;    //  User class repository
    hzPair              pair ;          //  Member name/value pair for user-class object
    hzEmaddr            ema ;           //  Email address
    hzString            userclass ;     //  Name of user class
    hzString            username ;      //  Extracted/converted username
    hzString            password ;      //  Extracted/converted password
    hzString            emaStr ;        //  Email address
    hzString            S ;             //  Value string
    uint32_t            subsObjId ;     //  New user object id in subscriber cache
    //uint32_t          userObjId ;     //  New user object id in user-class cache
    hzEcode             rc = E_OK ;     //  Return code
    /*
    **  Check basics
    */
    if (!m_pApp)
        hzexit(E_NOINIT, "No Application") ;
    if (!m_pApp->m_ADP.m_pClassSubscriber)  return hzerr(E_NOINIT, "Subscriber Class not initialized") ;
    if (!m_pApp->m_ADP.m_pReposSubscriber)  return hzerr(E_NOINIT, "Subscriber Repos not initialized") ;
    userclass   = m_pApp->m_ExecParams[m_FstParam] ;
    username    = m_pApp->m_ExecParams[m_FstParam+1] ;
    password    = m_pApp->m_ExecParams[m_FstParam+2] ;
    emaStr      = m_pApp->m_ExecParams[m_FstParam+3] ;
    if (!userclass) return hzerr(E_NOINIT, "No user class specified") ;
    if (!username)  return hzerr(E_NOINIT, "No user name specified") ;
    if (!password)  return hzerr(E_NOINIT, "No user password specified") ;
    if (!emaStr)    return hzerr(E_NOINIT, "No user email specified") ;
    pUserClass = m_pApp->m_ADP.GetPureClass(userclass) ;
    if (!pUserClass)
        return hzerr(E_NOTFOUND, "No user class: Class %s not known as a class\n", *userclass) ;
    pUserRepos = m_pApp->m_ADP.GetObjRepos(userclass) ;
    if (!pUserRepos)
        return hzerr(E_NOTFOUND, "No user repository for class %s\n", *userclass) ;
    objSubscriber.Init(m_pApp->m_ADP.m_pClassSubscriber) ;
    userObj.Init(pUserClass) ;
    /*
    **  Set up SUBSCRIBER object
    */
    //  Step 1: Establish username
    rc = m_pApp->PcEntConv(atom, username, pE) ;
    if (rc != E_OK)
        return hzwarn(rc, "Error during conversion of username %s", *username) ;
    username = atom.Str() ;
    //  Check it does not already exist
    //pMbr = pSubsClass->GetMember("username") ;
    rc = m_pApp->m_ADP.m_pReposSubscriber->Exists(subsObjId, m_pApp->m_ADP.m_pMbr_Subscriber_username, atom) ;
    if (rc != E_OK)
        return hzwarn(rc, "Error during lookup of user %s", *username) ;
    if (subsObjId)
        return hzwarn(E_DUPLICATE, "User %s already exists", *username) ;
    pLog = GetThreadLogger() ;
    //  Set the username in the subscriber object
    objSubscriber.SetMbrValue(m_pApp->m_ADP.m_pMbr_Subscriber_username, username) ;
    pLog->Log("Set subscriber username to %s\n", *username) ;
    //  Step 2: Establish email address
    rc = m_pApp->PcEntConv(atom, emaStr, pE) ;
    if (rc != E_OK)
        return hzwarn(rc, "Error during conversion of password %s", *ema) ;
    ema = atom.Str() ;
    objSubscriber.SetMbrValue(m_pApp->m_ADP.m_pMbr_Subscriber_email, ema) ;
    pLog->Log("Set subscriber email %s to %s\n", *ema, *atom.Str()) ;
    //  Step 3: Establish password
    rc = m_pApp->PcEntConv(atom, password, pE) ;
    if (rc != E_OK)
        return hzwarn(rc, "Error during conversion of password %s", *password) ;
    objSubscriber.SetMbrValue(m_pApp->m_ADP.m_pMbr_Subscriber_userpass, atom.Str()) ;
    pLog->Log("Set subscriber password %s to %s\n", *password, *atom.Str()) ;
    //  Step 4: Set userUID. This is always the current population of susbsriber cache
    atom = m_pApp->m_ADP.m_pReposSubscriber->Count() + 1 ;
    objSubscriber.SetValue(m_pApp->m_ADP.m_pMbr_Subscriber_UID, atom) ;
    pLog->Log("subscriber UID = %s\n", *atom.Str()) ;
#if 0
    //  Step 5: Set user adderess - This is always the current population of the user cache
    userObjId = pUserRepos->Count() + 1 ;
    atom = userObjId ;
    objSubscriber.SetValue(pSubsClass->GetMember(SUBSCRIBER_USERADDR), atom) ;
    pLog->Log("user object id = %s\n", *atom.Str()) ;
    //  Step 6: Set user type (user class id)
    atom.SetValue(BASETYPE_STRING, userclass) ;
    objSubscriber.SetValue(pSubsClass->GetMember(SUBSCRIBER_USERTYPE), atom) ;
    pLog->Log("user class %s\n", *atom.Str()) ;
    /*
    **  Now create USER object
    */
    atom = subsObjId ;
    userObj.SetValue(0, atom) ;
    /*
    MUST FIX
    for (pi = m_Steps ; pi.Valid() ; pi++)
    {
        pair = pi.Element() ;
        //  Establish member value
        rc = m_pApp->PcEntConv(atom, pair.value, pE) ;
        if (rc != E_OK)
            return hzwarn(rc, "Error during conversion of member value %s %s", *pair.name, *pair.value) ;
        //  Set member value
        pLog->Log("Set user object member %s to %s\n", *pair.name, *atom.Str()) ;
        userObj.SetValue(pair.name, atom.Str()) ;
    }
    */
    /*
    **  Now do the inserts
    */
    rc = pUserRepos->Insert(userObjId, userObj) ;
    if (rc != E_OK)
        return hzwarn(rc, "Error during insert of user %s", *username) ;
    pLog->Log("Inserted user object member objid %d\n", userObjId) ;
    rc = pSubsRepos->Insert(subsObjId, subsObj) ;
    if (rc != E_OK)
        return hzwarn(rc, "Error during insert of subscriber %s", *username) ;
    pLog->Log("Inserted subs object member objid %d\n", subsObjId) ;
#endif
    return E_OK ;
}
hzEcode hdsExec::Logon  (hzChain& errorReport, hzHttpEvent* pE)
{
    //  Log a user in as admin. This is done without password checking. As the supplied m_Uname parameter is always a percent entity, this must be evaluated
    //  before use.
    //
    //  Arguments:  1)  error   The error report chain
    //              2)  pE      The HTTP event pointer
    //
    //  Returns:    E_NOTFOUND  If there is no session or if the username is not established
    //              E_OK        If the username is established
    _hzfunc("hdsExec::Logon") ;
    hdbObject       uObj ;          //  User object
    hzAtom          atom ;          //  Atom for fetching values
    hdsInfo*        pInfo ;         //  Session
    hdbObjRepos*    pRepos ;        //  User class repos
    //hdbMember*        pMbr ;          //  Member (of subscriber class)
    hzString        tmpStr ;        //  Temp string
    hzString        m_Uname ;       //  Username resolved form percent entity
    hzString        unam ;          //  Username resolved form percent entity
    uint32_t        bAccess ;       //  Object id in the base user cache
    uint32_t        objId ;         //  Object id in the base user cache
    hzEcode         rc ;            //  Return code
    m_Uname = m_pApp->m_ExecParams[m_FstParam] ;
    pInfo = (hdsInfo*) pE->Session() ;
    if (pInfo)
        { errorReport.Printf("Cannot logon user implied by %s as there is already a session in place\n", *m_Uname) ; return E_NOTFOUND ; }
    if (m_Uname[0] != CHAR_PERCENT)
        unam = m_Uname ;
    else
    {
        rc = m_pApp->PcEntConv(atom, m_Uname, pE) ;
        if (rc != E_OK)
        {
            errorReport.Printf("Cannot logon user as %s does not evaluate\n", *m_Uname) ;
            return E_NOTFOUND ;
        }
        unam = atom.Str() ;
    }
    errorReport.Printf("Trying username %s\n", *unam) ;
    rc = m_pApp->m_ADP.m_pReposSubscriber->Exists(objId, m_pApp->m_ADP.m_pMbr_Subscriber_username, atom) ;
    if (rc != E_OK)
        { errorReport.Printf("User %s not found\n", *unam) ; return E_NOTFOUND ; }
    if (!objId)
        { errorReport.Printf("User %s not found case 2\n", *unam) ; return E_NOTFOUND ; }
    //  Log user in as a user of the appropriate class
    pInfo->m_SubId = objId ;
    m_pApp->m_SessCookie.Insert(pE->Cookie(), pInfo) ;
    rc = m_pApp->m_ADP.m_pReposSubscriber->Fetch(uObj, objId) ;
    if (rc != E_OK)
    {
        errorReport.Printf("Could not fetch subscriber user type with obj id %d\n", objId) ;
        return rc ;
    }
    tmpStr = atom.Str() ;
    bAccess = m_pApp->m_UserTypes[tmpStr] ;
    pRepos = m_pApp->m_ADP.GetObjRepos(tmpStr) ;
    if (!pRepos)
        { errorReport.Printf("User type [%s] not located\n", *tmpStr) ; return E_NOTFOUND ; }
    pInfo->m_UserRepos = pRepos->DeltaId() ;
    //  Get user id from the subscriber object
    uObj.GetValue(atom, m_pApp->m_ADP.m_pMbr_Subscriber_UID) ;
    pInfo->m_UserId = atom.Int32() ;
    if (rc == E_OK)
    {
        pInfo->m_Access &= ACCESS_ADMIN ;
        pInfo->m_Access |= bAccess ;
    }
    return rc ;
}
hzEcode hdsExec::Extract    (hzChain& errorReport, hzHttpEvent* pE)
{
    //  Perform a text extraction. The scenario will be that the input will be a file bound to the event (an uploaded file in a form submission). The
    //  m_Input member will name the field carrying the file. ...
    //
    //  Arguments:  1)  error   The error report chain
    //              2)  pE      The HTTP event pointer
    //
    //  Returns:    E_NOTFOUND  If there is no session or if the username is not established
    //              E_OK        If the username is established
    _hzfunc("hdsExec::Extract") ;
    hzVect<hzXmlNode*>          vx ;    //  Nodes containing text
    hzList<hzHttpFile>::Iter    fx ;    //  Iterator of submitted files
    hzDocXml        xdoc ;      //  Unzipped docx file loader
    hzHttpFile      hf ;        //  File meta data from event
    hzChain         Z ;         //  Unzipped docx file
    hzChain         D ;         //  Part of docx within and including <w:document> tags
    hzChain         E ;         //  For export of XML doc
    hzChain         T ;         //  Extracted Text
    chIter          zi ;        //  For iteration (aim to ignore larges parts of MicroSoft formats)
    hzXmlNode*      pN ;        //  Node pointer
    const char*     i ;         //  For derivation
    hzString        m_Input ;   //  Name of field carrying the submitted file
    hzString        m_Target ;  //  Name of field carrying the target file
    hzString        fldname ;   //  Name of field carrying the submitted file
    uint32_t        n ;         //  XML Node counter
    hzEcode         rc ;        //  Return code
    m_Input = m_pApp->m_ExecParams[m_FstParam] ;
    m_Target = m_pApp->m_ExecParams[m_FstParam+1] ;
    errorReport.Printf("Extracting from %s to %s\n", *m_Input, *m_Target) ;
    i = *m_Input ;
    if (memcmp(i, "%e:", 3))
    {
        errorReport.Printf("Not an identifiable input\n") ;
        return E_NOTFOUND ;
    }
    fldname = i + 3 ;
    if (pE->m_Uploads.Exists(fldname))
    {
        hf = pE->m_Uploads[fldname] ;
        errorReport.Printf("Found input (fld %s file %s of %d bytes) mime=%d\n", *hf.m_fldname, *hf.m_filename, hf.m_file.Size(), hf.m_mime) ;
        if (hf.m_mime == HMTYPE_APP_OPEN_DOCX)
        {
            Gunzip(Z, hf.m_file) ;
            for (zi = Z ; !zi.eof() ; zi++)
            {
                if (*zi != CHAR_LESS)
                    continue ;
                if (zi == "<w:document")
                {
                    D << "<w:document" ;
                    for (zi += 11 ; !zi.eof() ; zi++)
                    {
                        if (*zi == CHAR_LESS)
                        {
                            D.AddByte(CHAR_NL) ;
    
                            if (zi == "</w:document>")
                            {
                                D << "</w:document>\n" ;
                                break ;
                            }
                        }
                        D.AddByte(*zi) ;
                        if (*zi == CHAR_MORE)
                            D.AddByte(CHAR_NL) ;
                    }
                    break ;
                }
            }
            rc = xdoc.Load(D) ;
            errorReport.Printf("%s. XML load status err=%s\n", __func__, Err2Txt(rc)) ;
            if (rc == E_OK)
            {
                xdoc.FindNodes(vx, "w:t") ;
                for (n = 0 ; n < vx.Count() ; n++)
                {
                    pN = vx[n] ;
                    E += "<p>\n" ;
                    //E << pN->m_tmpContent ;
                    E << pN->m_fixContent ;
                    E += "\n</p>\n" ;
                }
            }
            errorReport.Printf("Setting var %s with chain of %d bytes\n", *m_Target, E.Size()) ;
            pE->SetVarChain(m_Target, E) ;
            //pE->m_mapChains.Insert(m_Target, E) ;
        }
    }
    return rc ;
}
hzEcode hdsExec::Filesys    (hzChain& errorReport, hzHttpEvent* pE)
{
    //  Execute a filesystem command to create, delete or list a directory or to write out or read in a binary object to/from a file - as opposed to storing it
    //  in a repository.
    _hzfunc("hdsExec::Filesys") ;
    FSTAT       fs ;            //  File/directory status
    hzAtom      atom ;          //  For obtaing file content
    hzChain     content ;       //  The file content (usually file upload)
    hzString    resource ;      //  The directory/file to operate on
    hzString    m_Action ;      //  The command specifier (mkdir|rmdir|save|delete)
    hzString    m_Resource ;    //  Directory resource to operate on
    hzString    m_Content ;     //  Save command only - specifies data to be saved
    hzEcode     rc = E_OK ;     //  Return value
    errorReport.Printf("Filesys action %s and resource %s\n", *m_Action, *m_Resource) ;
    m_Action = m_pApp->m_ExecParams[m_FstParam] ;
    m_Resource = m_pApp->m_ExecParams[m_FstParam] ;
    m_Content = m_pApp->m_ExecParams[m_FstParam] ;
    if (m_Action == "mkdir")
    {
        resource = m_pApp->ConvertText(m_Resource, pE) ;
        rc = AssertDir(resource, 0777) ;
        if (rc != E_OK)
            errorReport.Printf("Could not assert dir %s error is %s\n", *resource, Err2Txt(rc)) ;
        return rc ;
    }
    if (m_Action == "rmdir")
    {
        //  Will work even if directory not empy
        resource = m_pApp->ConvertText(m_Resource, pE) ;
        rc = BlattDir(resource) ;
        if (rc != E_OK)
            errorReport.Printf("Could not blatt dir %s error is %s\n", *resource, Err2Txt(rc)) ;
        return rc ;
    }
    if (m_Action == "save")
    {
        //  Write out data to a file.
        //  Obtain file name
        resource = m_pApp->ConvertText(m_Resource, pE) ;
        //  Obtain file content
        rc = m_pApp->PcEntConv(atom, m_Content, pE) ;
        content = atom.Chain() ;
        if (!content.Size())
        {
            errorReport.Printf("No data to write to file %s\n", *resource) ;
            return E_NODATA ;
        }
        ofstream    os ;    //  Output stream
        os.open(*resource) ;
        if (os.fail())
            { errorReport.Printf("Could not open for write, file %s\n", *resource) ; return E_WRITEFAIL ; }
        os << content ;
        if (os.fail())
            { errorReport.Printf("Could not write, file %s\n", *resource) ; return E_WRITEFAIL ; }
        os.close() ;
        return E_OK ;
    }
    if (m_Action == "delete")
    {
        //  Delete a file
        resource = m_pApp->ConvertText(m_Resource, pE) ;
        if (lstat(*resource, &fs) < 0)
            { errorReport.Printf("Action DELETE Failed: File %s not found\n", *resource) ; return E_NOTFOUND ; }
        if (ISDIR(fs.st_mode))
            { errorReport.Printf("Action DELETE Failed: %s is a directory\n", *resource) ; return E_TYPE ; }
        if (unlink(*resource) < 0)
            { errorReport.Printf("Action DELETE Failed: File %s could not be deleted\n", *resource) ; return E_WRITEFAIL ; }
        return E_OK ;
    }
    //  List entries in a directory
    errorReport.Printf("Filesys command Failed: Action %s illegal\n", *m_Action) ;
    return E_SYNTAX ;
}
hzEcode hdsExec::SrchPages  (hzChain& errorReport, hzHttpEvent* pE)
{
    //  Conduct a document search and provide a list of document ids found. In all cases the search will either be of an inbuilt resource such as the
    //  site's 'indigionous' pages (those defined as <xpage> in the config plus any passive pages), or it will be of a specified class.
    //
    //  Arguments:  1)  error   The error report chain
    //              2)  pE      The HTTP event pointer
    //
    //  Returns:    E_NOTFOUND  If there is no session or if the username is not established
    //              E_OK        If the username is established
    _hzfunc("hdsExec::SrchPages") ;
    hzVect<hdsPage*>    Result ;    //  Pages found
    hzVect<uint32_t>    res ;       //  Results
    hzChain         Z ;             //  For formulating response
    hzToken         T ;             //  Tokenizer for search criteria
    hdbIdset        R ;             //  Final search result
    hdsResource*    pRes ;          //  Resource pointer
    hdsPage*        pPage ;         //  Page pointer
    hzString        m_Action ;      //  The value of event variable x-action must have (if specified)
    hzString        m_Source ;      //  Source to search from (eg indiginous web pages)
    hzString        m_Criteria ;    //  The field name containng the search criteria
    hzString        m_Count ;       //  Name of the variable used to display the count
    hzString        m_Found ;       //  Name of the variable used to display the result (chain of HTML of table defined in format)
    hzString        V ;             //  Temp string
    hzString        S ;             //  Temp string
    uint32_t        nCount ;        //  Loop counter
    uint32_t        nStart ;        //  Fetch counter
    hzEcode         bSelect ;       //  True if we already have one per token result
    hzEcode         rc = E_OK ;     //  Return code
    char            buf [20] ;      //  For spelling out numbers
    if (!pE)
        Fatal("No HTTP event supplied\n") ;
    errorReport.Printf("SrchPages ...\n") ;
    //  First tokenize the search criteria
    Result.Clear() ;
    m_Criteria = m_pApp->m_ExecParams[m_FstParam] ;
    V = "srch_pages_criteria" ;
    if (!pE->m_mapStrings.Exists(V))
    {
        errorReport.Printf("%s. No such field as srch_pages_criteria\n", __func__) ;
        pE->m_appError = "Due to an Internal Error (Field name mismatch) we could not process your request" ;
        return E_NOTFOUND ;
    }
    S = pE->m_mapStrings[V] ;
    //rc = pE->SetVarString(m_Criteria, S) ;
    errorReport.Printf("%s. Search page index for [%s]\n", __func__, *S) ;
    rc = m_pApp->m_PageIndex.Eval(R, S) ;
    if (rc != E_OK)
    {
        //pE->SetVar(g_Errmsg, "Due to an Internal Error (Evaluation of page index) we could not process your request\n") ;
        pE->m_appError = "Due to an Internal Error (Evaluation of page index) we could not process your request" ;
        return E_NOTFOUND ;
    }
    if (!R.Count())
    {
        Z.Printf("Your search for %s found 0 results. Please try again", *S) ;
        pE->m_appError = Z ;
        return E_NOTFOUND ;
    }
    errorReport.Printf("%s. Results are %d pages, error=%s\n", __func__, R.Count(), Err2Txt(rc)) ;
    //  Fetch records from R
    sprintf(buf, "%d", R.Count()) ;
    S = buf ;
    rc = pE->SetVarString(m_Count, S) ;
    if (rc != E_OK)
        errorReport.Printf("%s. Could not set var %s to %s\n", __func__, *m_Count, buf) ;
    Z << "<div id=\"stdlist\">\n" ;
    Z << "<table align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n" ;
    for (nStart = 0 ;;)
    {
        R.Fetch(res, nStart, 20) ;
        errorReport.Printf("%s: Fetched %d records (total)\n", __func__, res.Count()) ;
        //  Lookup titles of pages using numbers
        for (nCount = 0 ; nCount < res.Count() ; nCount++)
        {
            //pRes = m_pApp->m_PagesName.GetObj(res[nCount]) ;
            pRes = m_pApp->m_ResourcesName.GetObj(res[nCount]) ;
            pPage = dynamic_cast<hdsPage*>(pRes) ;
            if (!pPage)
                continue ;
            errorReport.Printf("%s: Page No %d is page %p\n", __func__, res[nCount], pPage) ;
            Result.Add(pPage) ;
            Z.Printf("<tr><td><a href=\"%s\">%s</a></td><td> </td><td><a href=\"%s\">%s</a></td></tr>\n",
                *pPage->m_Url, *pPage->m_Title, *pPage->m_Url, *pPage->m_Desc) ;
        }
        if (res.Count() < 20)
            break ;
        nStart += 20 ;
    }
    Z << "</table>\n" ;
    Z << "</div>\n" ;
    V = "srch_pages_result" ;
    rc = pE->SetVarChain(m_Found, Z) ;
    if (rc != E_OK)
        errorReport.Printf("%s. Could not set var %s to chain of %d bytes\n", __func__, *m_Found, Z.Size()) ;
    errorReport.Printf("%s: Site scanned. %d pages\n", __func__, Result.Count()) ;
    return rc ;
}
hzEcode hdsExec::SrchRepos  (hzChain& errorReport, hzHttpEvent* pE)
{
    //  Conduct a document search and provide a list of document ids found. In all cases the search will either be of an inbuilt resource such as the
    //  site's 'indigionous' pages (those defined as <xpage> in the config plus any passive pages), or it will be of a specified class.
    //
    //  Arguments:  1)  error   The error report chain
    //              2)  pE      The HTTP event pointer
    //
    //  Returns:    E_NOTFOUND  If there is no session or if the username is not established
    //              E_OK        If the username is established
    _hzfunc("hdsExec::DocSearch") ;
    hzVect<hdsPage*>    Result ;    //  Pages found
    hzVect<uint32_t>    res ;       //  Results
    hzChain         Z ;             //  For formulating response
    hzToken         T ;             //  Tokenizer for search criteria
    hdbIdset        R ;             //  Final search result
    hdsResource*    pRes ;          //  Resource
    hdsPage*        pPage ;         //  Page pointer
    hdbObjRepos*    pRepos ;        //  Repos to look for records if we are not using pages
    hzString        m_Action ;      //  The value of event variable x-action must have (if specified)
    hzString        m_Source ;      //  Source to search from (eg indiginous web pages)
    hzString        m_Criteria ;    //  The field name containng the search criteria
    hzString        m_Count ;       //  Name of the variable used to display the count
    hzString        m_Found ;       //  Name of the variable used to display the result (chain of HTML of table defined in format)
    hzString        S ;             //  Temp string
    uint32_t        nCount ;        //  Loop counter
    uint32_t        nStart ;        //  Fetch counter
    hzEcode         bSelect ;       //  True if we already have one per token result
    hzEcode         rc = E_OK ;     //  Return code
    char            buf [20] ;      //  For spelling out numbers
    if (!pE)
        Fatal("No HTTP event supplied\n") ;
    errorReport.Printf("DocSearch ...\n") ;
    //  First tokenize the search criteria
    Result.Clear() ;
    m_Criteria = m_pApp->m_ExecParams[m_FstParam] ;
    if (!pE->m_mapStrings.Exists(m_Criteria))
    {
        errorReport.Printf("%s. No such field as %s\n", __func__, *m_Criteria) ;
        pE->m_appError = "Due to an Internal Error (Field name mismatch) we could not process your request" ;
        return E_NOTFOUND ;
    }
    S = pE->m_mapStrings[m_Criteria] ;
    //rc = pE->SetVarString(m_Criteria, S) ;
    if (m_Source == "pages")
    {
        errorReport.Printf("%s. Search page index for [%s]\n", __func__, *S) ;
        rc = m_pApp->m_PageIndex.Eval(R, S) ;
        if (rc != E_OK)
        {
            //pE->SetVar(g_Errmsg, "Due to an Internal Error (Evaluation of page index) we could not process your request\n") ;
            pE->m_appError = "Due to an Internal Error (Evaluation of page index) we could not process your request" ;
            return E_NOTFOUND ;
        }
    }
    else
    {
        //  The source must be a user defined class
        pRepos = m_pApp->m_ADP.GetObjRepos(m_Source) ;
        if (!pRepos)
        {
            //pE->SetVar(g_Errmsg, "Due to an Internal Error (Repos not located) we could not process your request\n") ;
            pE->m_appError = "Due to an Internal Error (Repos not located) we could not process your request" ;
            return E_NOTFOUND ;
        }
        rc = pRepos->Select(R, *S) ;
        if (rc != E_OK)
        {
            //pE->SetVar(g_Errmsg, "Due to an Internal Error (Evaluation of select on cache) we could not process your request\n") ;
            pE->m_appError = "Due to an Internal Error (Evaluation of select on cache) we could not process your request" ;
            return E_NOTFOUND ;
        }
    }
    if (!R.Count())
    {
        Z.Printf("Your search for %s found 0 results. Please try again", *S) ;
        pE->m_appError = Z ;
        return E_NOTFOUND ;
    }
    errorReport.Printf("%s. Results are %d pages, error=%s\n", __func__, R.Count(), Err2Txt(rc)) ;
    //  Fetch records from R
    sprintf(buf, "%d", R.Count()) ;
    S = buf ;
    rc = pE->SetVarString(m_Count, S) ;
    if (rc != E_OK)
        errorReport.Printf("%s. Could not set var %s to %s\n", __func__, *m_Count, buf) ;
    Z << "<div id=\"stdlist\">\n" ;
    Z << "<table align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n" ;
    for (nStart = 0 ;;)
    {
        R.Fetch(res, nStart, 20) ;
        errorReport.Printf("%s: Fetched %d records (total)\n", __func__, res.Count()) ;
        //  Lookup titles of pages using numbers
        for (nCount = 0 ; nCount < res.Count() ; nCount++)
        {
            //pRes = m_pApp->m_PagesName.GetObj(res[nCount]) ;
            pRes = m_pApp->m_ResourcesName.GetObj(res[nCount]) ;
            pPage = dynamic_cast<hdsPage*>(pRes) ;
            if (!pPage)
                continue ;
            errorReport.Printf("%s: Page No %d is page %p\n", __func__, res[nCount], pPage) ;
            Result.Add(pPage) ;
            Z.Printf("<tr><td><a href=\"%s\">%s</a></td><td> </td><td><a href=\"%s\">%s</a></td></tr>\n",
                *pPage->m_Url, *pPage->m_Title, *pPage->m_Url, *pPage->m_Desc) ;
        }
        if (res.Count() < 20)
            break ;
        nStart += 20 ;
    }
    Z << "</table>\n" ;
    Z << "</div>\n" ;
    rc = pE->SetVarChain(m_Found, Z) ;
    if (rc != E_OK)
        errorReport.Printf("%s. Could not set var %s to chain of %d bytes\n", __func__, *m_Found, Z.Size()) ;
    errorReport.Printf("%s: Site scanned. %d pages\n", __func__, Result.Count()) ;
    return rc ;
}
hzEcode hdsExec::Exec   (hzChain& errorReport, hzHttpEvent* pE)
{
    //  Run a single Exec command. Commands are placed within <procdata> tags, which can appear in <xform> and <page>
    //
    //  Arguments:  1)  error   The error report chain
    //              2)  pE      The HTTP event pointer
    _hzfunc("hdsExec::Exec") ;
    const hdbClass*     pClass ;    //  Data class
    const hdbMember*    pMbr ;      //  Data class member
    ifstream        is ;            //  For data import
    hdbObject*      pCurObj ;       //  Single data class instance
    hzAtom          atom ;          //  Atom for fetching values
    hzChain         bval ;          //  Document or binary (if applicable)
    hdbObjRepos*    pRepos ;        //  Target repository
    hdsInfo*        pInfo ;         //  Session
    hdbObject*      pObj ;          //  Standalone object pointer (either that vested with the HTTP event or the user session)
    hzString        param1 ;        //  1st parameter
    hzString        param2 ;        //  2nd parameter
    hzString        param3 ;        //  3rd parameter
    hzString        param4 ;        //  4th parameter
    hzString        param5 ;        //  5th parameter
    hzString        param6 ;        //  6th parameter
    hzString        strVal ;        //  Used to derive real value of the step's input when specified as a variable name instead of a literal
    hzString        S ;             //  Temp string
    hzString        a ;             //  Content of parameter 1
    hzString        b ;             //  Content of parameter 2
    //hdsUsertype       utype ;         //  User type (for loging in)
    //uint32_t      n ;             //  Object value iterator
    uint32_t        objId = 0 ;     //  Object id
    hzEcode         rc = E_OK ;     //  Return code
    if (!pE)
        Fatal("%s. No HTTP event supplied\n", __func__) ;
    //  Obtain user session if any
    pInfo = (hdsInfo*) pE->Session() ;
    threadLog("%s Commencing with info %p -> ", Exec2Txt(m_Command), pInfo) ;
    errorReport.Printf("%s Commencing with info %p -> ", Exec2Txt(m_Command), pInfo) ;
    switch  (m_Command)
    {
    case EXEC_SENDEMAIL:    //  Send an email. Params are sender, recipient, subject and msg.
                            rc = SendEmail(errorReport, pE) ;
                            break ;
    case EXEC_SETVAR:       if (!pInfo)
                                { errorReport.Printf("EXEC_SETVAR No session in place\n") ; return E_CORRUPT ; }
                            //  Get the value
                            param2 = m_pApp->m_ExecParams[m_FstParam+1] ;
                            if (param2[0] == CHAR_PERCENT)
                                rc = m_pApp->PcEntConv(atom, param2, pE) ;
                            else
                                rc = atom.SetValue(m_type, param2) ;
                            //  Get the name
                            param1 = m_pApp->m_ExecParams[m_FstParam] ;
                            if (!pInfo->m_Sessvals.Exists(param1))
                                pInfo->m_Sessvals.Insert(param1, atom) ;
                            else
                                pInfo->m_Sessvals[param1] = atom ;
                            break ;
    case EXEC_ADDUSER:      //  Explicitly create a new user as an executive step. See hdsExec::Adduser()
                            rc = Adduser(errorReport, pE) ;
                            break ;
    case EXEC_LOGON:        //  Log a user in as admin. This is done without password checking. As the supplied m_Uname parameter is always a percent entity,
                            //  this must be evaluated before use.
                            rc = Logon(errorReport, pE) ;
                            break ;
    case EXEC_TEST:         //  Tests if a condition is true. Currently limited to testing if two parameters are equal
                            param1 = m_pApp->m_ExecParams[m_FstParam] ;
                            param2 = m_pApp->m_ExecParams[m_FstParam+1] ;
                            a = m_pApp->ConvertText(param1, pE) ;
                            b = m_pApp->ConvertText(param2, pE) ;
                            if (a == b)
                                errorReport.Printf("exec test OK: a = %s, b = %s\n", *a, *b) ;
                            else
                            {
                                errorReport.Printf("exec test FAIL: a = %s, b = %s\n", *a, *b) ;
                                rc = E_NOTFOUND ;
                            }
                            break ;
    case EXEC_EXTRACT:      //  Perform a text extraction. The scenario will be that the input will be a file bound to the event (e.g. a file upload in a form
                            //  submission). The m_Input member will name the field carrying the file.
                            rc = Extract(errorReport, pE) ;
                            break ;
    case EXEC_OBJ_TEMP:     //  Create a temporary hdbObject and vest with the HTTP event (m_pContextObj)
        param1 = m_pApp->m_ExecParams[m_FstParam] ;
        param2 = m_pApp->m_ExecParams[m_FstParam+1] ;
        threadLog("Got exec params of %s and %s\n", *param1, *param2) ;
        pClass = m_pApp->m_ADP.GetPureClass(param2) ;
        if (!pClass)
            { errorReport.Printf("EXEC_OBJ_TEMP FAIL: No such class as %s\n", *param2) ; rc = E_NOTFOUND ; break ; }
        if (pE->m_pContextObj)
            { errorReport.Printf("EXEC_OBJ_TEMP FAIL: Already a temp object\n") ; rc = E_NOTFOUND ; break ; }
        pE->m_pContextObj = pObj = new hdbObject() ;
        pObj->Init(pClass) ;
        pObj->SetName(param1) ;
        errorReport.Printf("EXEC_OBJ_TEMP: Allocated a temp object of %s\n", *param1) ;
        break ;
    case EXEC_OBJ_START:    //  Assert the current user session object. This either finds the current object as null and sets it, or finds the right object in
                            //  place. If the current object is not the right one, this is an error.
        if (!pInfo)
            { errorReport.Printf("EXEC_OBJ_START FAIL: No user session\n") ; rc = E_NOINIT ; break ; }
            
        threadLog("Got exec param of %d\n", m_FstParam) ;
        param1 = m_pApp->m_ExecParams[m_FstParam] ;
        threadLog("Got exec param of %d\n", m_FstParam) ;
        threadLog("Got exec param of %s\n", *param1) ;
        param2 = m_pApp->m_ExecParams[m_FstParam+1] ;
        threadLog("Got exec param of %s\n", *param2) ;
        pClass = m_pApp->m_ADP.GetPureClass(param2) ;
        pInfo->ObjectAssert(param1, pClass) ;   //m_pApp->m_SObj2Class[param1]) ;
        break ;
    case EXEC_OBJ_FETCH:    //  Load the current user session object from a repository
    case EXEC_OBJ_IMPORT:   //  Load the current user session object from a JSON file
    case EXEC_OBJ_EXPORT:   //  Save the current user session object to a JSON file
    case EXEC_OBJ_SETMBR:   //  Set a data class member within the current object. The params are repository name, class name, member name and the data source.
    case EXEC_OBJ_COMMIT:   //  Commit data in the named hdbObject instance (param1) to the named repository (param2). An object id of 0 effects an INSERT while
    case EXEC_OBJ_CLOSE:    //  Closes the current user session object.
        //  First obtain existing user session or temp object
        param1 = m_pApp->m_ExecParams[m_FstParam] ;     //  Object key
        if (pE->m_pContextObj)
            pCurObj = (hdbObject*) pE->m_pContextObj ;
        else
        {
            if (!pInfo)
                { errorReport.Printf("%s FAIL: No user session\n", Exec2Txt(m_Command)) ; rc = E_NOINIT ; break ; }
            pCurObj = pInfo->ObjectSelect(param1) ;
            pCurObj = pInfo->m_pObj ;
            if (!pInfo->m_pObj)
                { errorReport.Printf("%s FAIL: No current object\n", Exec2Txt(m_Command)) ; rc = E_NOINIT ; break ; }
        }
        //  Then switch again on these object commands
        switch  (m_Command)
        {
        case EXEC_OBJ_FETCH:    //  Load the current user session object from a repository
                                break ;
        case EXEC_OBJ_IMPORT:   //  Load the current user session object from a JSON file
            param2 = m_pApp->m_ExecParams[m_FstParam+1] ;   //  Source file name
            strVal = m_pApp->ConvertText(param2, pE) ;
            if (rc != E_OK)
                { errorReport.Printf("%s FAIL: String %s could not be converted\n", Exec2Txt(m_Command), *param2) ; rc = E_OPENFAIL ; break ; }
            is.open(*strVal) ;
            if (is.fail())
                { errorReport.Printf("%s FAIL: File %s could not be opened\n", Exec2Txt(m_Command), *strVal) ; ; rc = E_OPENFAIL ; break ; }
            bval << is ;
            is.close() ;
            pCurObj->ImportJSON(bval) ;
            break ;
        case EXEC_OBJ_EXPORT:   //  Save the current user session object to a JSON file
            param2 = m_pApp->m_ExecParams[m_FstParam+1] ;   //  Traget file name
            break ;
        case EXEC_OBJ_SETMBR:   //  Set a data class member within the current object. The params are repository name, class name, member name and the data source.
            param2 = m_pApp->m_ExecParams[m_FstParam+1] ;   //  Class name
            param3 = m_pApp->m_ExecParams[m_FstParam+2] ;   //  Member name
            param4 = m_pApp->m_ExecParams[m_FstParam+3] ;   //  Data source
            pClass = m_pApp->m_ADP.GetPureClass(param2) ;
            if (!pClass)
                { errorReport.Printf("EXEC_OBJ_SETMBR FAIL: No such class as %s\n", *param2) ; rc = E_NOTFOUND ; break ; }
            pMbr = pClass->GetMember(param3) ;
            if (!pMbr)
                { errorReport.Printf("EXEC_OBJ_SETMBR FAIL: Class %s has no member of %s\n", *param2, *param3) ; rc = E_NOTFOUND ; break ; }
            switch  (pMbr->Basetype())
            {
            case BASETYPE_TXTDOC:
            case BASETYPE_BINARY:
                //  The step is to set the object's member to the file content. The filename is got from the step input (naming the event variable) and
                //  from there, we lookup the actual file content in the event
                if (pE->m_Uploads.Exists(param4))
                {
                    hzHttpFile  hf ;    //  External document/binary file
                    hf = pE->m_Uploads[param4] ;
                    strVal = m_pApp->ConvertText(param4, pE) ;
                    errorReport.Printf("Step %s=%s (fld %s file %s of %d bytes) mime=%d\n", *param3, *param4, *hf.m_fldname, *hf.m_filename, hf.m_file.Size(), hf.m_mime) ;
                    //rc = pCurObj->SetBinary(param3, hf.m_file) ;
                    rc = pCurObj->SetBinary(pMbr, hf.m_file) ;
                }
                if (pE->m_mapChains.Exists(param4))
                {
                    bval = pE->m_mapChains[param4] ;
                    strVal.Clear() ;
                    errorReport.Printf("Step %s=%s (chain %d bytes)\n", *param3, *param4, bval.Size()) ;
                    //rc = pCurObj->SetBinary(param3, bval) ;
                    rc = pCurObj->SetBinary(pMbr, bval) ;
                }
                break ;
            case BASETYPE_EMADDR:
            case BASETYPE_URL:
            case BASETYPE_STRING:
                strVal = m_pApp->ConvertText(param4, pE) ;
                errorReport.Printf("Strlike Step %s = %s (%s)\n", *param3, *param4, *strVal) ;
                //rc = pCurObj->SetValue(pMbr->Posn(), strVal) ;
                atom = strVal ;
                rc = pCurObj->SetValue(pMbr, atom) ;
                break ;
            default:    //  Deal with num-like or numeric values
                rc = m_pApp->PcEntConv(atom, param4, pE) ;
                errorReport.Printf("Numlike Step %s = %s (%s)\n", *param3, *param4, atom.Show()) ;
                //rc = pCurObj->SetValue(pMbr->Posn(), atom) ;
                rc = pCurObj->SetValue(pMbr, atom) ;
                break ;
            }
            break ;
        case EXEC_OBJ_COMMIT:   //  Commit data in the named hdbObject instance (param1) to the named repository (param2). An object id of 0 effects an INSERT while
                                //  a non-zero object id effects an UPDATE. The object id is derived from @resarg and is foud at this juncture by ....
            if (!pCurObj)
                { errorReport.Printf("EXEC_OBJ_COMMIT FAIL: No current object\n") ; rc = E_NOINIT ; break ; }
            pRepos = m_pApp->m_ADP.GetObjRepos(pCurObj->ReposId()) ;
            if (!pRepos)
                { errorReport.Printf("EXEC_OBJ_COMMIT FAIL: Object %s No repository\n", *param1) ; rc = E_NOINIT ; break ; }
            if (pCurObj->GetObjId())
                rc = pRepos->Update(*pCurObj, pCurObj->GetObjId()) ;
            else
                rc = pRepos->Insert(objId, *pCurObj) ;
            //  Error?
            if (rc != E_OK)
                errorReport.Printf("EXEC_OBJ_COMMIT FAIL: Insert/Modify on repository of %s failed with error %s\n", *param2, Err2Txt(rc)) ;
            break ;
        case EXEC_OBJ_CLOSE:    //  Closes the current user session object.
                                break ;
        }
        break ;
    //  Tree Operations
    case EXEC_TREE_DCL:     //  Declare a private tree and place it in the user session. This will have no effect if the user session already has the tree which
                            //  is identified by name. If there is a different tree in the session, it will be deleted and replaced with the requested tree.
        if (!pInfo)
            { errorReport.Printf("EXEC_TREE_DCL FAIL. No user session\n") ; return E_NOTFOUND ; }
        param1 = m_pApp->m_ExecParams[m_FstParam] ;     //  Tree id
        //param2 = m_pApp->m_ExecParams[m_FstParam+1] ; //  Tree to copy (if applicable)
        //  Session has pvt tree?
        if (pInfo->m_pTree)
        {
            if (pInfo->m_pTree->m_Groupname == param1)
                break ;
            delete pInfo->m_pTree ;
        }
        pInfo->m_pTree = new hdsNavtree() ;
        pInfo->m_pTree->m_Groupname = param1 ;
        threadLog("SET tree %s in session %p\n", *pInfo->m_pTree->m_Groupname, pInfo) ;
        break ;
    /*
    case EXEC_TREE_CPY:     //  If the current user session tree is empty, copy headings from the named public tree (if supplied) and copy entries from a source
                            //  object (if supplied). This command can only apply to the current user session tree. If this is not present or does not have the
                            //  supplied id, this is an error. The command will do nothing if the current user session tree is not empty. It is for initializing
                            //  values only.
        if (!pInfo)
            { errorReport.Printf("EXEC_TREE_CPY FAIL. No user session\n") ; return E_NOTFOUND ; }
        param1 = m_pApp->m_ExecParams[m_FstParam] ;     //  Tree id
        param2 = m_pApp->m_ExecParams[m_FstParam+1] ;   //  Tree to copy (if applicable)
        param3 = m_pApp->m_ExecParams[m_FstParam+2] ;   //  Object to copy (if applicable)
        //  Check source tree if supplied
        if (param2)
        {
            pTree = m_pApp->m_ArticleGroups[param2] ;
            if (!pTree)
                { errorReport.Printf("EXEC_TREE_CPY FAIL. Named source tree %s not found\n", *param2) ; return E_NOTFOUND ; }
        }
        //  Check source object if supplied
        if (param3)
        {
        }
        if (pInfo->m_pTree->Count())
            break ;
    */
    case EXEC_TREE_HEAD:        //  Add a heading to the tree if it does not already exist. A heading is an empty article with no link.
        if (!pE)
            Fatal("No HTTP event supplied\n") ;
        pInfo = (hdsInfo*) pE->Session() ;
        if (!pInfo)
            { errorReport.Printf("EXEC_TREEOP FAIL. No user session\n") ; return E_NOTFOUND ; }
        if (!pInfo->m_pTree)
            { errorReport.Printf("EXEC_TREEOP FAIL. No user tree\n") ; return E_NOTFOUND ; }
        param1 = m_pApp->m_ExecParams[m_FstParam] ;     //  Tree id
        param2 = m_pApp->m_ExecParams[m_FstParam+1] ;   //  Parent item id
        param3 = m_pApp->m_ExecParams[m_FstParam+2] ;   //  Refname
        param4 = m_pApp->m_ExecParams[m_FstParam+3] ;   //  Headline
        if (pInfo->m_pTree->m_Groupname != param1)
            { errorReport.Printf("EXEC_TREEOP FAIL. Wrong tree\n") ; return E_NOTFOUND ; }
        //  If a parent tree item is stated, check it exists
        if (param2 && !pInfo->m_pTree->Exists(param2))
            { errorReport.Printf("EXEC_TREEOP FAIL. Stated parent (%s) does not exist\n", *param2) ; return E_NOTFOUND ; }
        //  Then add/delete the new item(s)
        pInfo->m_pTree->AddHead(param2, param3, param4, false) ;
        break ;
    case EXEC_TREE_ITEM:    //  Add an article to the tree if it does not already exist.
        if (!pE)
            Fatal("No HTTP event supplied\n") ;
        pInfo = (hdsInfo*) pE->Session() ;
        if (!pInfo)
            { errorReport.Printf("EXEC_TREEOP FAIL. No user session\n") ; return E_NOTFOUND ; }
        if (!pInfo->m_pTree)
            { errorReport.Printf("EXEC_TREEOP FAIL. No user tree\n") ; return E_NOTFOUND ; }
        param1 = m_pApp->m_ExecParams[m_FstParam] ;     //  Tree id
        param2 = m_pApp->m_ExecParams[m_FstParam+1] ;   //  Parent item id
        param3 = m_pApp->m_ExecParams[m_FstParam+2] ;   //  Refname
        param4 = m_pApp->m_ExecParams[m_FstParam+3] ;   //  Headline
        if (pInfo->m_pTree->m_Groupname != param1)
            { errorReport.Printf("EXEC_TREEOP FAIL. Wrong tree\n") ; return E_NOTFOUND ; }
        //  If a parent tree item is stated, check it exists
        if (param2 && !pInfo->m_pTree->Exists(param2))
            { errorReport.Printf("EXEC_TREEOP FAIL. Stated parent (%s) does not exist\n", *param2) ; return E_NOTFOUND ; }
        //  Then add/delete the new item(s)
        //pArt = new hdsArticleStd() ;
        pInfo->m_pTree->AddItem(param2, param3, param4, 0, false) ;
        break ;
    case EXEC_TREE_FORM:    //  Add a form to the tree as an article.
        if (!pInfo)             { errorReport.Printf("EXEC_TREEOP FAIL. No user session\n") ; return E_NOTFOUND ; }
        if (!pInfo->m_pTree)    { errorReport.Printf("EXEC_TREEOP FAIL. No user tree\n") ; return E_NOTFOUND ; }
        param1 = m_pApp->m_ExecParams[m_FstParam] ;     //  Tree id
        param2 = m_pApp->m_ExecParams[m_FstParam+1] ;   //  Parent item id
        param3 = m_pApp->m_ExecParams[m_FstParam+2] ;   //  Refname
        param4 = m_pApp->m_ExecParams[m_FstParam+3] ;   //  Headline
        param5 = m_pApp->m_ExecParams[m_FstParam+4] ;   //  Form reference
        param6 = m_pApp->m_ExecParams[m_FstParam+5] ;   //  Data class context
        if (pInfo->m_pTree->m_Groupname != param1)
            { errorReport.Printf("EXEC_TREEOP FAIL. Wrong tree\n") ; return E_NOTFOUND ; }
        hdsArticleStd*  pArt ;      //  Article
        hdsFormref*     pFormRef ;  //  Form reference
        //  Create article
        pArt = new hdsArticleStd() ;
        pArt->m_Refname = param3 ;
        pArt->m_Title = param4 ;
        pArt->m_bFlags |= HZ_TREEITEM_FORM ;
        pArt->m_bFlags |= HZ_TREEITEM_LINK ;
        //  Add form reference to the article
        pFormRef = new hdsFormref(0) ;
        pFormRef->m_Formname = param5 ;
        pArt->AddFormref(pFormRef) ;
        pArt->AddVisent(pFormRef) ;
        //  Add article to the tree
        pInfo->m_pTree->AddItem(param2, param3, param4, pArt, false) ;
        break ;
    case EXEC_TREE_SYNC:    //  Sync the tree to a standalone object.
        /*
        if (!pE)
            Fatal("No HTTP event supplied\n") ;
        pInfo = (hdsInfo*) pE->Session() ;
        if (!pInfo)
            { errorReport.Printf("EXEC_TREEOP FAIL. No user session\n") ; return E_NOTFOUND ; }
        if (!pInfo->m_pTree)
            { errorReport.Printf("EXEC_TREEOP FAIL. No user tree\n") ; return E_NOTFOUND ; }
        param1 = m_pApp->m_ExecParams[m_FstParam] ;     //  Tree id
        param2 = m_pApp->m_ExecParams[m_FstParam+1] ;   //  Object id
        if (pInfo->m_pTree->m_Groupname != param1)
            { errorReport.Printf("EXEC_TREEOP FAIL. Wrong tree\n") ; return E_NOTFOUND ; }
        //  Then obtain existing user session or temp object
        if (pE->m_pContextObj)
            pCurObj = (hdbObject*) pE->m_pContextObj ;
        else
        {
            if (!pInfo)
                { errorReport.Printf("%s FAIL: No user session\n", Exec2Txt(m_Command)) ; rc = E_NOINIT ; break ; }
            pCurObj = pInfo->ObjectSelect(param2) ;
            pCurObj = pInfo->m_pObj ;
            if (!pInfo->m_pObj)
                { errorReport.Printf("%s FAIL: No current object\n", Exec2Txt(m_Command)) ; rc = E_NOINIT ; break ; }
        }
        pInfo->m_pTree->Sync(*pCurObj) ;
        */
        break ;
    case EXEC_TREE_DEL: break ;
    case EXEC_TREE_EXP: break ;
    case EXEC_TREE_CLR: break ;
    case EXEC_SRCH_PAGES:   //  Conduct a document search and provide a list of document ids found. The search will either be of an inbuilt resource such as the
                            //  site's 'indigionous' pages (those defined as <xpage> in the config plus any passive pages), or it will be of a specified class.
        rc = SrchPages(errorReport, pE) ;
        break ;
    case EXEC_SRCH_REPOS:   //  Conduct a search on a repository and provide a list of object ids found. The search will either be of an inbuilt resource such as the
                            //  site's 'indigionous' pages (those defined as <xpage> in the config plus any passive pages), or it will be of a specified class.
        rc = SrchRepos(errorReport, pE) ;
        break ;
    case EXEC_FILESYS:      //  Execute a filesystem command to create, delete or list a directory or to write out or read in a binary object to/from a file (as
                            //  opposed to storing it in a repository.
        rc = Filesys(errorReport, pE) ;
        break ;
    }
    return rc ;
}