//
//  File:   hdbIsamfile.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.
//
#include <iostream>
#include <fstream>
#include <cstdio>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "hzTextproc.h"
#include "hzDirectory.h"
#include "hzDatabase.h"
using namespace std ;
/*
**  Variables
*/
/*
**  SECTION 1: hdbIsamfile Functions
*/
hdbIsamfile::hdbIsamfile    (void)
{
    m_nElements = 0 ;
    m_nBlocks = 0 ;
    m_nKeyLimit = 256 ;
    m_nObjLimit = 256 ;
    m_nBlkSize = HZ_BLOCKSIZE ;
    m_nInitState = 0 ;
    //_hzGlobal_Memstats.m_numIsamfile++ ;
}
hdbIsamfile::~hdbIsamfile   (void)
{
    if (m_nInitState == 2)
        Close() ;
    //_hzGlobal_Memstats.m_numIsamfile-- ;
}
/*
**  hdbIsamfile Init and Halt Functions
*/
hzEcode hdbIsamfile::Init   (hdbADP& adp, const hzString& name, const hzString& opdir, uint32_t keySize, uint32_t objSize, uint32_t blkSize)
{
    //  Initialize the hdbIsamfile instance.
    //
    //  This is a matter of asserting the working directory, the index and the data file.  exist and are open for reading and writing.
    //
    //  This is a matter of opening an input stream for the data file, an output stream for the data file and an output stream for the index file. Note both the
    //  output streams are opened with ios::app as they will only ever append and that there is no input stream for the index file as during normal operation of
    //  a binary datacron, the index is never read.
    //
    //  nd index file as append only files. The first step is to open the index file for reading and load the index into the memory. This file is then closed but
    //  opened again in write mode. If it does not exists then it is created and left open in write mode. The second step is to create or open the data file for
    //  both reading and writing. The hdbIsamfile is then ready to store and retrieve binary objects.
    //
    //  Arguments:  1) adp      The Application Delta Profile.
    //              2) name     Name of ISAM-file (will be base name of the delta and data store files)
    //              3) opdir    Full pathname of working directory
    //
    //  Returns:    E_OPENFAIL  The files could not be opened or created (no space or wrong permissions)
    //              E_OK        Operation was successful.
    //
    //  Errors:     Any false return is due to an irrecoverable error. The calling function
    //              must check the return value.
    _hzfunc("hdbIsamfile::Init") ;
    FSTAT       fs ;        //  File status
    ifstream    is ;        //  Input stream for reading index file
    int32_t     fd ;        //  File descriptor
    hzEcode     rc ;        //  Return code
    if (m_nInitState)
        return hzerr(E_INITDUP, "Resource already initialized") ;
    //  Check we have a name and working directory
    if (!name)  return hzerr(E_ARGUMENT, "No name") ;
    if (!opdir) return hzerr(E_ARGUMENT, "Repository %s: No working directory", *name) ;
    //  Check we do not already have a datacron of the name
    if (adp.GetBinRepos(name))
        return hzerr(E_DUPLICATE, "Binary datum cron %s already exists", *name) ;
    //  Assign internal structure, name and working directory
    m_Name = name ;
    m_Workdir = opdir ;
    m_Error.Printf("%s called with cron name %s and workdir %s\n", *_fn, *m_Name, *m_Workdir) ;
    //  Assert working directory
    if (lstat(*m_Workdir, &fs) < 0)
    {
        rc = AssertDir(*m_Workdir, 0777) ;
        if (rc != E_OK)
            return hzerr(rc, "Could not assert working dir of %s", *m_Workdir) ;
    }
    //  Set the pathnames for the index and data file
    m_fileDelta = m_Workdir + "/" + m_Name + ".idx" ;
    m_fileStore = m_Workdir + "/" + m_Name + ".dat" ;
    m_Error.Printf("%s Have index file of %s and data file of %s\n", *_fn, *m_fileDelta, *m_fileStore) ;
    //  memset(m_Buf, 0, HZ_BLOCKSIZE) ;
    //  m_Buf[0] = m_Buf[1] = m_Buf[2] = m_Buf[3] = m_Buf[4] = m_Buf[5] = m_Buf[6] = m_Buf[7] = '0' ;
    //  m_Buf[8] = CHAR_EOT ;
    //  m_Buf[9] = CHAR_NL ;
    if (lstat(*m_fileDelta, &fs) < 0)
    {
        fd = open(*m_fileDelta, O_RDWR|O_CREAT|O_TRUNC, 0600) ;
        if (fd < -1)
            return hzerr(E_OPENFAIL, "Cannot create %s, error %s", *m_fileDelta, Err2Txt(errno)) ;
        close(fd) ;
    }
    if (lstat(*m_fileStore, &fs) < 0)
    {
        fd = open(*m_fileStore, O_RDWR|O_CREAT|O_TRUNC, 0600) ;
        if (fd < -1)
            return hzerr(E_OPENFAIL, "Cannot create %s, error %s", *m_fileStore, Err2Txt(errno)) ;
        close(fd) ;
    }
    //  Set init state etc
    m_nInitState = 1 ;
    return E_OK ;
}
hzEcode hdbIsamfile::Open   (void)
{
    //  Open the hdbIsamfile instance.
    //
    //  This is a matter of opening an input stream for the data file, an output stream for the data file and an output stream for the index file. Note both the
    //  output streams are opened with ios::app as they will only ever append and that there is no input stream for the index file as during normal operation of
    //  a hdbIsamfile, the index is never read.
    //
    //  Arguments:  None
    //
    //  Returns:    E_NOINIT    If the repository has not been initialized
    //              E_SEQUENCE  If the repository is already open
    //              E_OPENFAIL  If either the index or data file cannot be opened for either reading or writing
    //              E_OK        If the operation was successful
    _hzfunc("hdbIsamfile::Open") ;
    hzMapS<uint32_t,hzString>   tmp ;   //  Temporary map
    ifstream    is ;            //  Index input stream
    hzString    key ;           //  Key
    uint32_t    addr ;          //  Block address
    uint32_t    nLines = 0 ;    //  Line count
    uint32_t    n ;             //  Temp index iterator
    if (m_nInitState == 0)  hzexit(E_NOINIT, "Cannot open an uninitialized datacron") ;
    if (m_nInitState == 2)  hzexit(E_SEQUENCE, "Datacron %s is already open", *m_Name) ;
    //m_WrI.open(*m_fileDelta, std::ios::app) ;
    m_WrI.open(*m_fileDelta) ;
    if (m_WrI.fail())
        return hzerr(E_OPENFAIL, "Cannot open index file for writing: Repos %s", *m_fileDelta) ;
    //m_WrD.open(*m_fileStore, std::ios::app) ;
    m_WrD.open(*m_fileStore) ;
    if (m_WrD.fail())
        return hzerr(E_OPENFAIL, "Could not open file (%s) for writing", *m_fileStore) ;
    is.open(*m_fileDelta) ;
    if (is.fail())
        return hzerr(E_OPENFAIL, "Could not open index file (%s) for reading", *m_fileStore) ;
    m_nBlocks = 0 ;
    for (;;)
    {
        is.getline(m_Buf, 500) ;
        if (!is.gcount())
            break ;
        nLines++ ;
        //  Block address is first part of line, key is remainder
        m_Buf[8] = 0 ;
        IsHexnum(addr, m_Buf) ;
        key = m_Buf + 9 ;
        tmp.Insert(addr, key) ;
        m_nBlocks++ ;
    }
    is.close() ;
    //  Transpose tmp index to operational index
    for (n = 0 ; n < tmp.Count() ; n++)
    {
        addr = tmp.GetKey(n) ;
        key = tmp.GetObj(n) ;
        m_Index.Insert(key, addr) ;
    }
    tmp.Clear() ;
    //  m_RdI.open(*m_fileDelta) ;
    //  if (m_RdI.fail())
    //      return hzerr(E_OPENFAIL, "Could not open index file (%s) for reading", *m_fileStore) ;
    m_RdD.open(*m_fileStore) ;
    if (m_RdD.fail())
        return hzerr(E_OPENFAIL, "Could not open data file (%s) for reading", *m_fileStore) ;
    m_nInitState = 2 ;
    return E_OK ;
}
hzEcode hdbIsamfile::Close  (void)
{
    //  Close the datacron.
    //
    //  Arguments:  None
    //
    //  Returns:    E_NOINIT    The datacron is not initialized
    //              E_SEQUENCE  The datacron is not open
    //              E_OK        The operation was successful
    _hzfunc("hdbIsamfile::Close") ;
    if (m_nInitState < 1)   return E_NOINIT ;
    if (m_nInitState < 2)   return E_NOTOPEN ;
    //if (m_RdI.is_open())  m_RdI.close() ;
    if (m_RdD.is_open())    m_RdD.close() ;
    if (m_WrI.is_open())    m_WrI.close() ;
    if (m_WrD.is_open())    m_WrD.close() ;
    return E_OK ;
}
hzEcode hdbIsamfile::Insert (const hzString& newKey, const hzString& newObj)
{
    //  Insert a key/object pair into the ISAM file. Note it is permissable for the object to be blank but not the key.
    //
    //  This involves the following steps:-
    //
    //      - Identify the target data block for the new key from the index. We then have both the target block address and the available space.
    //      - Read in target data block, assemble new version of it including the new key.
    //      - If the target has enough space
    //          - Write out target block.
    //          - Advice the index map of a new higher key, if applicable.
    //      - Else
    //          - Write out half the target to the original location
    //          - Write out upper half to a new location
    //          - Advice the index map.
    //
    //  Note that no attempt is made to move elements into adjacent data blocks. Disk space is cheap but disk operations are expensive.
    //
    //  Arguments:  1) key  The key
    //              2) obj  The object
    //
    //  Returns:    E_NOINIT    If this hdbIsamfile instance has not been initialized
    //              E_NODATA    If the supplied datum is of zero size
    //              E_WRITEFAIL If the data could not be written to disk.
    //              E_OK        If operation successful
    _hzfunc("hdbIsamfile::Insert") ;
    hzMapM<hzString,hzString>   tmp ;   //  Temp map of key/object pairs found in target data area
    hzChain     Zout ;          //  For writing out the temp map to a block
    hzChain     Xind ;          //  For writing out index entries
    chIter      zi ;            //  Chain iterator
    char*       i ;             //  Buffer iterator
    char*       j ;             //  Buffer iterator
    const char* k ;             //  Key iterator
    hzString    strA ;          //  Key string
    hzString    strB ;          //  Object string
    uint32_t    addr = 0 ;      //  Address of target data block
    uint32_t    nSpace = 0 ;    //  Space consumed
    uint32_t    nC ;            //  Counter
    int32_t     nLo ;           //  Position of target within m_Index map
    hzEcode     rc = E_OK ;     //  Return code
    //  Check init state
    if (m_nInitState < 1)   return E_NOINIT ;
    if (m_nInitState < 2)   return E_NOTOPEN ;
    //  Check validity of supplied key (must contain no newlines)
    if (!newKey)
        return E_NODATA ;
    m_Error.Clear() ;
    nSpace = newKey.Length() ;
    k = *newKey ;
    for (nC = 0 ; nC < nSpace ; nC++)
    {
        if (k[nC] == 0 || k[nC] == CHAR_NL)
            { m_Error.Printf("Illegal value %c (%d) found in key [%s]\n", k[nC], k[nC], *newKey) ; return E_BADVALUE ; }
    }
    //  If virgin ISAM
    if (!m_Index.Count())
    {
        nC = newKey.Length() ;
        memset(m_Buf, 0, HZ_BLOCKSIZE) ;
        sprintf(m_Buf, "%s\n%s\n", *newKey, *newObj) ;
        m_WrD.write(m_Buf, HZ_BLOCKSIZE) ;
        if (m_WrD.fail())
            { m_Error.Printf("Could not write to virgin data file\n") ; return E_WRITEFAIL ; }
        m_WrD.flush() ;
        m_WrI << "00000000," << "\n" ;
        if (m_WrI.fail())
            { m_Error.Printf("Could not write to virgin index file\n") ; return E_WRITEFAIL ; }
        m_Index.Insert(_hzGlobal_nullString, 0) ;
        m_nBlocks = 1 ;
        m_WrI.flush() ;
        return E_OK ;
    }
    //  Identify target data block
    nLo = m_Index.Target(newKey) ;
    strA = m_Index.GetKey(nLo) ;
    if (nLo >= (int32_t) m_Index.Count())
        { nLo = m_Index.Count() -1 ; m_Error.Printf("%s: Case 0: identified index posn %d\n", *_fn, nLo) ; }
    else if (strA > newKey)
        { nLo-- ; m_Error.Printf("%s: Case 1: identified index posn %d\n", *_fn, nLo) ; }
    else
        m_Error.Printf("%s: Case 2: identified index posn %d\n", *_fn, nLo) ;
    strA = (char*) 0 ;
    
    addr = m_Index.GetObj(nLo) ;
        m_Error.Printf("%s: Case 2: identified block %d\n", *_fn, nLo) ;
    //  Read in target data block
    m_RdD.seekg(addr * m_nBlkSize) ;
    if (m_RdD.fail())
        { m_Error.Printf("%s: Failed to seek to block %d (position %d) in data block\n", *_fn, addr, addr * m_nBlkSize) ; return E_READFAIL ; }
    m_RdD.read(m_Buf, HZ_BLOCKSIZE) ;
    if (m_RdD.fail())
        { m_Error.Printf("%s: Failed to read block %d (position %d) in data block\n", *_fn, addr, addr * m_nBlkSize) ; return E_READFAIL ; }
    for (i = j = m_Buf, nC = 0 ; nC < HZ_BLOCKSIZE ; i++, nC++)
    {
        if (m_Buf[nC] == CHAR_NL)
        {
            *i = 0 ;
            if (!strA)
                { strA = j ; j = i + 1 ; continue ; }
            if (*j)
                strB = j ;
            j = i + 1 ;
            if (!strA)
                break ;
            tmp.Insert(strA, strB) ;
            strA = strB = (char*) 0 ;
        }
    }
    //  Insert the new key
    tmp.Insert(newKey, newObj) ;
    //  Seek to the original block
    m_WrD.seekp(addr * m_nBlkSize) ;
    if (m_WrD.fail())
        { m_Error.Printf("Could not seek to posn %u in data file\n", addr * m_nBlkSize) ; return E_WRITEFAIL ; }
    m_Error.Printf("Seeked to posn %u in data file\n", m_WrD.tellp()) ;
    //  Write out map, add block if necessary
    Zout.Clear() ;
    for (nC = 0 ; nC < tmp.Count() ; nC++)
    {
        strA = tmp.GetKey(nC) ;
        strB = tmp.GetObj(nC) ;
        nSpace = Zout.Size() + strA.Length() + strB.Length() + 2 ;
        if (nSpace > m_nBlkSize)
        {
            memset(m_Buf, 0, HZ_BLOCKSIZE) ;
            for (i = m_Buf, zi = Zout ; !zi.eof() ; i++, zi++)
                *i = *zi ;
            m_WrD.write(m_Buf, HZ_BLOCKSIZE) ;
            if (m_WrD.fail())
                { m_Error.Printf("Could not write to data file\n") ; return E_WRITEFAIL ; }
            Zout.Clear() ;
            m_WrD.flush() ;
            //  Create new block
            Xind.Printf("%08x,%s\n", m_nBlocks, *strA) ; m_WrI << Xind ; m_WrI.flush() ; Xind.Clear() ;
            m_Index.Insert(strA, m_nBlocks) ;
            m_nBlocks++ ;
            //  Allocate new block
            m_WrD.seekp(0, ios_base::end) ;
            if (m_WrD.fail())
                { m_Error.Printf("Alloc - Could not seek to end in data file\n") ; return E_WRITEFAIL ; }
        }
        Zout << strA ;
        Zout.AddByte(CHAR_NL) ;
        if (strB)
            Zout << strB ;
        Zout.AddByte(CHAR_NL) ;
    }
    if (Zout.Size())
    {
        memset(m_Buf, 0, HZ_BLOCKSIZE) ;
        for (i = m_Buf, zi = Zout ; !zi.eof() ; i++, zi++)
            *i = *zi ;
        m_WrD.write(m_Buf, HZ_BLOCKSIZE) ;
        if (m_WrD.fail())
            { m_Error.Printf("Could not write to posn %u in data file\n", m_WrD.tellp()) ; return E_WRITEFAIL ; }
        m_WrD.flush() ;
    }
    return rc ;
}
bool    hdbIsamfile::Exists (const hzString& key)
{
    //  Determine if the supplied key matches any found in the ISAM
    //
    //  Argument:   key     The search key
    //
    //  Returns:    True    If the key is found
    //              False   Otherwise
    _hzfunc("hdbIsamfile::Exists") ;
    hzMapM<hzString,hzString>   tmp ;   //  Temp map of key/object pairs found in target data area
    char*       i ;             //  Buffer iterator
    char*       j ;             //  Buffer iterator
    hzString    strA ;          //  Key string
    hzString    strB ;          //  Object string
    uint32_t    nPos ;          //  Position of target within m_Index map
    uint32_t    addr ;          //  Address of target data block
    uint32_t    nC ;            //  Counter
    //  Check init state
    m_Error.Clear() ;
    m_Cond = E_OK ;
    m_Error.Printf("%s called\n", *_fn) ;
    if (m_nInitState < 1)   { hzwarn(E_NOINIT, "ISAM file not initialized") ; return false ; }
    if (m_nInitState < 2)   { hzwarn(E_NOTOPEN, "ISAM file not open") ; return false ; }
    if (!key)
        return false ;
    if (!m_Index.Count())
        return false ;
    //  Identify target data block
    nPos = m_Index.Target(key) ;
    if (nPos >= m_Index.Count())
        nPos = m_Index.Count() -1 ;
    strA = m_Index.GetKey(nPos) ;
    if (strA > key)
        nPos-- ;
    for (; nPos < m_Index.Count() ; nPos++)
    {
        if (m_Index.GetKey(nPos) > key)
            break ;
        //  Read in target data block
        addr = m_Index.GetObj(nPos) ;
        m_RdD.seekg(addr * m_nBlkSize) ;
        if (m_RdD.fail())
            { m_Cond = E_READFAIL ; m_Error.Printf("%s: Failed to seek to block %d (position %d) in data block\n", *_fn, addr, addr * m_nBlkSize) ; return false ; }
        m_RdD.read(m_Buf, HZ_BLOCKSIZE) ;
        if (m_RdD.fail())
            { m_Cond = E_READFAIL ; m_Error.Printf("%s: Failed to read block %d (position %d) in data block\n", *_fn, addr, addr * m_nBlkSize) ; return false ; }
        for (i = j = m_Buf, nC = 0 ; nC < HZ_BLOCKSIZE ; i++, nC++)
        {
            if (m_Buf[nC] == CHAR_NL)
            {
                *i = 0 ;
                if (!strA)
                    { strA = j ; j = i + 1 ; continue ; }
                if (*j)
                    strB = j ;
                j = i + 1 ;
                if (!strA)
                    break ;
                tmp.Insert(strA, strB) ;
                strA = strB = (char*) 0 ;
            }
        }
    }
    return tmp.Exists(key) ;
}
hzEcode hdbIsamfile::Fetch  (hzArray<hzPair>& result, const hzString& keyLo, const hzString& keyHi)
{
    //  Fetches objects matching the key or falling within a range of two keys.
    //
    //  Arguments:  1)  obj     The object to be populated
    //              2)  datumId The object id
    //
    //  Returns:    E_NOTOPEN   If the reader stream is not open for reading
    //              E_READFAIL  The reader stream is already in a fialed state
    //              E_CORRUPT   Row metadata not as expected. Possible overwrites
    //              E_FORMAT    Data format error
    //              E_OK        The operation was successful
    _hzfunc("hdbIsamfile::Fetch") ;
    hzMapM<hzString,hzString>   tmp ;   //  Temp map of key/object pairs found in target data area
    char*       i ;             //  Buffer iterator
    char*       j ;             //  Buffer iterator
    hzString    keyA ;          //  Key string
    hzString    keyB ;          //  Key string
    hzString    strA ;          //  Key string
    hzString    strB ;          //  Object string
    hzPair      pair ;          //  For output
    uint32_t    nPos ;          //  Position of target within m_Index map
    uint32_t    addr ;          //  Address of target data block
    uint32_t    nC ;            //  Counter
    int32_t     nLo ;           //  Position of first target within tmp map
    int32_t     nHi ;           //  Position of last target within tmp map
    hzEcode     rc = E_OK ;     //  Return code
    //  Check init state
    if (m_nInitState < 1)   return E_NOINIT ;
    if (m_nInitState < 2)   return E_NOTOPEN ;
    result.Clear() ;
    m_Error.Clear() ;
    //  Sort the keys
    if (!keyLo && !keyHi)
        { m_Error.Printf("No keys supplied\n") ; return E_ARGUMENT ; }
    if (!m_Index.Count())
        return E_NOTFOUND ;
    if (!keyHi)
        keyA = keyB = keyLo ;
    else if (!keyLo)
        keyA = keyB = keyHi ;
    else
    {
        if (keyHi < keyLo)
            { keyA = keyHi ; keyB = keyLo ; }
        else
            { keyA = keyLo ; keyB = keyHi ; }
    }
    //  Identify target data block
    nPos = m_Index.Target(keyA) ;
    if (nPos >= m_Index.Count())
        nPos = m_Index.Count() -1 ;
    strA = m_Index.GetKey(nPos) ;
    if (strA > keyA)
        nPos-- ;
    for (; nPos < m_Index.Count() ; nPos++)
    {
        if (m_Index.GetKey(nPos) > keyB)
            break ;
        //  Read in target data block
        addr = m_Index.GetObj(nPos) ;
        m_RdD.seekg(addr * m_nBlkSize) ;
        if (m_RdD.fail())
            { m_Error.Printf("%s: Failed to seek to block %d (position %d) in data block\n", *_fn, addr, addr * m_nBlkSize) ; return E_READFAIL ; }
        m_RdD.read(m_Buf, HZ_BLOCKSIZE) ;
        if (m_RdD.fail())
            { m_Error.Printf("%s: Failed to read block %d (position %d) in data block\n", *_fn, addr, addr * m_nBlkSize) ; return E_READFAIL ; }
        for (i = j = m_Buf, nC = 0 ; nC < HZ_BLOCKSIZE ; i++, nC++)
        {
            if (m_Buf[nC] == CHAR_NL)
            {
                *i = 0 ;
                if (!strA)
                    { strA = j ; j = i + 1 ; continue ; }
                if (*j)
                    strB = j ;
                j = i + 1 ;
                if (!strA)
                    break ;
                tmp.Insert(strA, strB) ;
                strA = strB = (char*) 0 ;
            }
        }
    }
    //  If object id is zero return blank object
    nLo = tmp.First(keyA) ;
    if (nLo < 0)
        return E_NOTFOUND ;
    nHi = tmp.Last(keyB) ;
    for (; nLo <= nHi ; nLo++)
    {
        pair.name = tmp.GetKey(nLo) ;
        pair.value = tmp.GetObj(nLo) ;
        result.Add(pair) ;
    }
    return rc ;
}
hzEcode hdbIsamfile::Delete (const hzString& key)
{
    //  Delete an object from the datacron.
    //
    //  This function appears only for consistency with the base class and does nothing.
    //
    //  Arguments:  1)  datumId The object id to be deleted
    //
    //  Returns:    E_NOINIT    The datacron is not initialized
    //              E_SEQUENCE  The datacron is not open for writing
    //              E_RANGE     The requested item does not exist
    //              E_OK        Operation successful
    _hzfunc("hdbIsamfile::Delete") ;
    //  Check init state
    if (m_nInitState < 1)   return E_NOINIT ;
    if (m_nInitState < 2)   return E_NOTOPEN ;
    return E_OK ;
}
#if 0
hzEcode hdbIsamfile::Update (uint32_t& datumId, const hzChain& datum)   //, uint32_t an1, uint32_t an2)
{
    //  Replace the object named by the supplied id (arg2) with the contents of the supplied hzChain (arg1). This will:-
    //
    //      1) Locate the object and in so doing, obtain the most recent (current) address and version of the existing object
    //      2) Determine the new location of the object
    //      3) Assemble the object header which will be the new size and version number, the current date and the most recent address.
    //      4) Append the data file
    //      5) Update the index
    //
    //  Arguments:  1) datumId  Datum ID - Set first to the original, set by this function to the new version of the object
    //              2) datum    Binary datum value
    //
    //  Returns:    E_NOINIT    The datacron is not initialized
    //              E_NOTOPEN   Attempt to modify object zero
    //              E_NOTFOUND  Attempt to modify non existant object
    //              E_WRITEFAIL The datacron is not open for writing
    //              E_RANGE     The requested item does not exist
    //              E_OK        The operation was successful
    _hzfunc("hdbIsamfile::Update") ;
    _datum_hd   hdr ;           //  Datum header
    //  uint32_t    rem ;           //  Remainder (to align to 32 byte chunks)
    //  uint32_t    tot ;           //  Total size of datum and padding
    hzEcode     rc = E_OK ;     //  Return code
    //char      buf [20] ;      //  Buffer for padding
    if (m_nInitState < 1)   return E_NOINIT ;
    if (m_nInitState < 2)   return hzerr(E_SEQUENCE, "Binary datum cron (%s) is not open", *m_Name) ;
    if (datumId == 0)       return E_NOTOPEN ;
    if (datumId > m_nDatum) return E_NOTFOUND ;
    if (!datum.Size())      return E_NODATA ;
    hdr.m_DTStamp.SysDateTime() ;
    hdr.m_Size = datum.Size() ;
    hdr.m_Prev = datumId ;
    //  rem = ((16 - datum.Size()) % 16) % 16 ;
    //  tot = datum.Size() + rem ;
    //  Write to the index
    m_LockIwr.Lock() ;
        hdr.m_Addr = m_nSize ;
        datumId = ++m_nDatum ;
        m_nSize += datum.Size() ;
        m_WrI.write((char*) &hdr, sizeof(_datum_hd)) ;
        if (m_WrI.fail())
            { m_Error.Printf("Could not update index delta for datum %d\n", datumId) ; rc = E_WRITEFAIL ; }
    m_LockIwr.Unlock() ;
    m_LockDwr.Lock() ;
        if (rc == E_OK)
        {
            //  Write to the data file
            m_WrD << datum ;
            if (m_WrD.fail())
                { m_Error.Printf("Could not update data file for datum %d\n", datumId) ; rc = E_WRITEFAIL ; }
        }
    m_LockDwr.Unlock() ;
    if (rc == E_OK)
    {
        m_WrI.flush() ;
        m_WrD.flush() ;
    }
    return rc ;
}
#endif
#if 0
hzEcode hdbIsamfile::Integ  (hzLogger& log)
{
    //  Performs an integrity check on the binary datum repository. The test expects to find in the index file, the time/date stamp and addresses of datum to be
    //  in ascending order. It further expects to find the sizes total exactly matches the data file size.
    //
    //  Argument:   log     The log-channel for the integrity report
    //
    //  Returns:    E_NOTOPEN   If the reader stream is not open for reading
    //              E_READFAIL  The reader stream is already in a fialed state
    //              E_CORRUPT   Row metadata not as expected. Possible overwrites
    //              E_FORMAT    Data format error
    //              E_OK        The operation was successful
    _hzfunc("hdbIsamfile::Integ") ;
    ifstream    is ;            //  Input stream for index file (separate from the in-built)
    _datum_hd*  pHd ;           //  Datum header pointer
    _datum_hd   arrHd[128] ;    //  Datum header array (128 headers)
    uint32_t    nBytes ;        //  Bytes read in from index file
    uint32_t    nItems ;        //  Number of headers read in from index file
    uint32_t    nC ;            //  Header iterator
    uint32_t    totalItems ;    //  Bytes to get in the next read operation
    uint32_t    totalSize ;     //  Bytes to get in the next read operation
    hzEcode     rc = E_OK ;     //  Return code
    //  Check we have an internal structure (have initialized)
    if (m_nInitState < 1)
        return E_NOINIT ;
    //  if (m_nInitState < 2)   return hzerr(E_SEQUENCE, "Binary datum cron (%s) is not open", *m_Name) ;
    is.open(*m_fileDelta) ;
    if (is.fail())
        return hzerr(E_OPENFAIL, "Could not open index file %s\n", *m_fileDelta) ;
    for (;;)
    {
        is.read((char*) arrHd, 4096) ;
        nBytes = is.gcount() ;
        if (nBytes % sizeof(_datum_hd))
        {
            log.Out("%s Unexpected read size %d\n", *_fn, nBytes) ;
            break ;
        }
        nItems = nBytes/sizeof(_datum_hd) ;
        for (pHd = arrHd, nC = 0 ; nC < nItems ; nC++, pHd++)
        {
            log.Out("Datum %u (%u) %s %u\n", pHd->m_Addr, nC, *pHd->m_DTStamp.Str(), pHd->m_Size) ;
            totalItems++ ;
            totalSize += pHd->m_Size ;
        }
        if (nItems < 128)
            break ;
    }
    is.close() ;
    log.Out("%s Total items %u\n", *_fn, totalItems) ;
    log.Out("%s Total size %u\n", *_fn, totalSize) ;
}
#endif