/* xbmemo4.cpp XBase64 Software Library Copyright (c) 1997,2003,2014,2022,2023 Gary A Kunkel The xb64 software library is covered under the terms of the GPL Version 3, 2007 license. Email Contact: XDB-devel@lists.sourceforge.net XDB-users@lists.sourceforge.net This class is used for support dBASE V4 memo files */ #include "xbase.h" #ifdef XB_MEMO_SUPPORT #ifdef XB_DBF4_SUPPORT namespace xb{ /***********************************************************************/ //! @brief Class Constructor. /*! \param dbf Pointer to dbf instance. \param sFileName Memo file name. */ xbMemoDbt4::xbMemoDbt4( xbDbf * dbf, xbString const & sFileName ) : xbMemo( dbf, sFileName ){ iMemoFileType = 4; SetBlockSize( dbf->GetCreateMemoBlockSize() ); } /***********************************************************************/ //! @brief Class Deconstructor. xbMemoDbt4::~xbMemoDbt4(){} /***********************************************************************/ //! @brief Abort. /*! Abort any pending updates to the memo file. \returns XB_NO_ERROR */ xbInt16 xbMemoDbt4::Abort(){ xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; xbUInt32 ulBlockNo; try{ xbUInt32 ulNodeCnt = llNewBlocks.GetNodeCnt(); for( xbUInt32 l = 0; l < ulNodeCnt; l++ ){ if(( rc = llNewBlocks.RemoveFromFront( ulBlockNo )) != XB_NO_ERROR ){ iErrorStop = 100; throw rc; } if(( rc = FreeMemoBlockChain( ulBlockNo )) != XB_NO_ERROR ){ iErrorStop = 110; throw rc; } } llOldBlocks.Clear(); } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::Abort() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ //! @brief Commit changes to memo file. /*! Commit any pending updates to the memo file. \returns XB_NO_ERROR. */ xbInt16 xbMemoDbt4::Commit(){ xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; xbUInt32 ulBlockNo; try{ xbUInt32 ulNodeCnt = llOldBlocks.GetNodeCnt(); for( xbUInt32 l = 0; l < ulNodeCnt; l++ ){ if(( rc = llOldBlocks.RemoveFromFront( ulBlockNo )) != XB_NO_ERROR ){ iErrorStop = 100; throw rc; } if(( rc = FreeMemoBlockChain( ulBlockNo )) != XB_NO_ERROR ){ iErrorStop = 110; throw rc; } } llNewBlocks.Clear(); } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::Commit() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ //! @brief Create memo file. /*! \returns Return Codes */ xbInt16 xbMemoDbt4::CreateMemoFile(){ xbInt16 iErrorStop = 0; xbInt16 rc = XB_NO_ERROR; char cBuf[4]; try{ if(( rc = xbFopen( "w+b", dbf->GetShareMode() )) != XB_NO_ERROR ){ iErrorStop = 100; throw rc; } ulHdrNextBlock = 1L; ePutUInt32( cBuf, ulHdrNextBlock ); if(( rc = xbFwrite( cBuf, 4, 1 ))!= XB_NO_ERROR ){ iErrorStop = 110; xbFclose(); throw rc; } for(int i = 0; i < 4; i++ ) xbFputc( 0x00 ); GetFileNamePart( sDbfFileNameWoExt ); sDbfFileNameWoExt.PadRight( ' ', 8 ); // need at least eight bytes of name sDbfFileNameWoExt = sDbfFileNameWoExt.Mid( 1, 8 ); // need no more than eight bytes of name for( int i = 1; i < 9; i++ ) xbFputc( sDbfFileNameWoExt[i] ); for(int i = 0; i < 4; i++ ) xbFputc( 0x00 ); ePutInt16( cBuf, GetBlockSize()); if(( rc = xbFwrite( cBuf, 2, 1 ))!= XB_NO_ERROR ){ iErrorStop = 120; xbFclose(); throw rc; } for( xbUInt32 i = 0; i < (GetBlockSize() - 22); i++ ) xbFputc( 0x00 ); if(( mbb = (void *) malloc( GetBlockSize())) == NULL ){ rc = XB_NO_MEMORY; iErrorStop = 130; return XB_NO_MEMORY; } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::CreateMemoFile() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); xbFclose(); } return rc; } /***********************************************************************/ #ifdef XB_DEBUG_SUPPORT //! @brief Dump memo file header. /*! \returns XB_NO_ERROR */ xbInt16 xbMemoDbt4::DumpMemoFreeChain() { xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; xbUInt32 ulCurBlock, ulLastDataBlock; try{ if(( rc = ReadDbtHeader(1)) != XB_NO_ERROR ){ iErrorStop = 100; throw rc; } if(( rc = CalcLastDataBlock( ulLastDataBlock )) != XB_NO_ERROR ){ iErrorStop = 110; throw rc; } ulCurBlock = ulHdrNextBlock; std::cout << "**********************************" << std::endl; std::cout << "Head Node Next Block = " << ulCurBlock << std::endl;; std::cout << "Total blocks in file = " << ulLastDataBlock << std::endl; while( ulCurBlock < ulLastDataBlock ){ if(( rc = ReadBlockHeader( ulCurBlock, 2 )) != XB_NO_ERROR ){ iErrorStop = 120; throw rc; } std::cout << "**********************************" << std::endl; std::cout << "This Free Block = [" << ulCurBlock << "] contains [" << ulFreeBlockCnt << "] block(s)" << std::endl; std::cout << "Next Free Block = [" << ulNextFreeBlock << "]" << std::endl; ulCurBlock = ulNextFreeBlock; } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::UpdateMemoField() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return XB_NO_ERROR; } //! @brief Dump memo internals. /*! \returns XB_NO_ERROR */ xbInt16 xbMemoDbt4::DumpMemoInternals() { xbLinkListNode *llPtr; xbInt16 iNodeCnt; llPtr = llOldBlocks.GetHeadNode(); iNodeCnt = llOldBlocks.GetNodeCnt(); std::cout << "Link List Old Blocks - " << iNodeCnt << " nodes" << std::endl; for( xbInt16 i = 0; i < iNodeCnt; i++ ){ std::cout << llPtr->GetKey() << ","; llPtr = llPtr->GetNextNode(); } std::cout << std::endl; llPtr = llNewBlocks.GetHeadNode(); iNodeCnt = llNewBlocks.GetNodeCnt(); std::cout << "Link List New Blocks - " << iNodeCnt << " nodes" << std::endl; for( xbInt16 i = 0; i < iNodeCnt; i++ ){ std::cout << llPtr->GetKey() << ","; llPtr = llPtr->GetNextNode(); } std::cout << std::endl; return XB_NO_ERROR; } #endif // XB_DEBUG_SUPPORT /***********************************************************************/ //! @brief Dump memo file header. /*! \returns XB_NO_ERROR */ xbInt16 xbMemoDbt4::DumpMemoHeader(){ xbInt16 rc = XB_NO_ERROR; xbUInt32 ulLastDataBlock; CalcLastDataBlock( ulLastDataBlock ); if(( rc = ReadDbtHeader( 1 )) != XB_NO_ERROR ) return rc; std::cout << "Version 4 Memo Header Info" << std::endl; std::cout << "Memo File Name = " << GetFqFileName() << std::endl; std::cout << "Hdr Next Avail Block = " << ulHdrNextBlock << std::endl; std::cout << "Block Size = " << GetBlockSize() << std::endl; std::cout << "Dbf File Name wo Ext = " << sDbfFileNameWoExt.Str() << std::endl; std::cout << "Last Data Block = " << ulLastDataBlock << std::endl; return rc; } /************************************************************************/ //! @brief Find an empty set of blocks in the free block chain /*! This routine searches thruugh the free node chain in a dbase IV type memo file searching for a place to grab some free blocks for reuse \param ulBlocksNeeded The size to look in the chain for. \param ulLastDataBlock is the last data block in the file, enter 0 for the routine to calculate it. \param ulLocation The location it finds. \param ulPreviousNode Block number of the node immediately previous to this node in the chain.
0 if header node \param bFound Output xbFalse - Spot not found in chain.
xbTrue - Spot found in chain. \returns Return Codes */ xbInt16 xbMemoDbt4::FindBlockSetInChain( xbUInt32 ulBlocksNeeded, xbUInt32 &ulLastDataBlock, xbUInt32 &ulLocation, xbUInt32 &ulPrevNode, xbBool &bFound ){ xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; xbUInt32 ulCurNode; try{ ulPrevNode = 0L; if( ulLastDataBlock == 0 ){ /* Determine last good data block */ if(( rc = CalcLastDataBlock( ulLastDataBlock )) != XB_NO_ERROR ){ iErrorStop = 100; throw rc; } } if( ulHdrNextBlock < ulLastDataBlock ){ ulCurNode = ulHdrNextBlock; if(( rc = ReadBlockHeader( ulHdrNextBlock, 2 )) != XB_NO_ERROR ){ iErrorStop = 110; throw rc; } while( ulBlocksNeeded > ulFreeBlockCnt && ulNextFreeBlock < ulLastDataBlock ){ ulPrevNode = ulCurNode; ulCurNode = ulNextFreeBlock; if(( rc = ReadBlockHeader( ulNextFreeBlock, 2 )) != XB_NO_ERROR ){ iErrorStop = 120; throw rc; } } if( ulBlocksNeeded <= ulFreeBlockCnt ){ ulLocation = ulCurNode; // PreviousNode = lPrevNode; bFound = xbTrue; } else { // no data found and at end of chain ulPrevNode = ulCurNode; bFound = xbFalse; } } else { bFound = xbFalse; } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::FindBlockSetInChain() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ //! @brief Free a block. /*! \returns Return Codes */ xbInt16 xbMemoDbt4::FreeMemoBlockChain( xbUInt32 ulBlockNo ){ xbUInt32 ulLastDataBlock; return FreeMemoBlockChain( ulBlockNo, ulLastDataBlock ); } /***********************************************************************/ //! @brief Free a block. /*! \param ulBlockNo The block number being deleted. \param ulLastDataBlock Output - Last free block number,prior to this block. \returns Return Codes */ xbInt16 xbMemoDbt4::FreeMemoBlockChain( xbUInt32 ulBlockNo, xbUInt32 &ulLastDataBlock ) { xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; xbUInt32 ulNoOfFreedBlocks; xbUInt32 ulLastFreeBlock = 0; xbUInt32 ulLastFreeBlockCnt = 0; xbUInt32 ulSaveNextFreeBlock; // iFieldNo - The field no m\bing deleted // iBlockNo - The block number being deleted // iNoOfFreeBlocks - The number of blocks being freed with this delete // iLastDataBlock - The next block number to allocate if more blocks needed // iHdrNextBlock - The head pointer in the main header block // iNextFreeBlock - The block that is immediately following the current free block to be added // iLastFreeBlock - Last free block number,prior to this block // iLastFreeBlockCnt - Last free block number of blocks try{ if( ulBlockNo <= 0 ){ iErrorStop = 100; rc =XB_INVALID_BLOCK_NO; throw rc; } /* Load the first block */ if(( rc = ReadBlockHeader( ulBlockNo, 1 )) != XB_NO_ERROR ){ iErrorStop = 110; throw rc; } if( (ulFieldLen) % GetBlockSize() ) ulNoOfFreedBlocks = ((ulFieldLen) / GetBlockSize()) + 1L; else ulNoOfFreedBlocks = (ulFieldLen) / GetBlockSize(); /* Determine last good data block */ if(( rc = CalcLastDataBlock( ulLastDataBlock )) != XB_NO_ERROR ){ iErrorStop = 120; throw rc; } if(( rc = ReadDbtHeader( 0 )) != XB_NO_ERROR ){ iErrorStop = 130; throw rc; } // Not an empty node chain, position to correct location in chain ulNextFreeBlock = ulHdrNextBlock; while( ulBlockNo > ulNextFreeBlock && ulBlockNo < ulLastDataBlock ){ ulLastFreeBlock = ulNextFreeBlock; if(( rc = ReadBlockHeader( ulNextFreeBlock, 2 )) != XB_NO_ERROR ){ iErrorStop = 140; return rc; } ulLastFreeBlockCnt = ulFreeBlockCnt; } // One of two outcomes at this point // A) This block is combined with the next free block chain, and points to the free chain after the next free block // B) This block is not combined with the next free block chain, and points to the next block // (which could be the last block // should next block should be concatonated onto the end of this set? ulSaveNextFreeBlock = ulNextFreeBlock; if(( ulBlockNo + ulNoOfFreedBlocks ) == ulNextFreeBlock && ulNextFreeBlock < ulLastDataBlock ){ if(( rc = ReadBlockHeader( ulNextFreeBlock, 2 )) != XB_NO_ERROR ){ iErrorStop = 150; throw rc; } ulNoOfFreedBlocks += ulFreeBlockCnt; ulSaveNextFreeBlock = ulNextFreeBlock; } // if this is the first set of free blocks if( ulLastFreeBlock == 0 ){ // 1 - write out the current block // 2 - update header block // 3 - write header block // 4 - update data field ePutUInt32( (char *) mbb, ulSaveNextFreeBlock ); ePutUInt32( (char *) mbb+4, ulNoOfFreedBlocks ); if(( rc = WriteBlock( ulBlockNo, 8, mbb )) != XB_NO_ERROR ){ iErrorStop = 160; throw rc; } ulHdrNextBlock = ulBlockNo; if(( rc = UpdateHeadNextNode()) != XB_NO_ERROR ){ iErrorStop = 170; throw rc; } return XB_NO_ERROR; } /* determine if this block set should be added to the previous set */ if(( ulLastFreeBlockCnt + ulLastFreeBlock ) == ulBlockNo ){ if(( rc = ReadBlockHeader( ulLastFreeBlock, 2 )) != XB_NO_ERROR ){ iErrorStop = 180; throw rc; } ulFreeBlockCnt += ulNoOfFreedBlocks; ePutUInt32( (char *) mbb, ulSaveNextFreeBlock ); ePutUInt32( (char *) mbb+4, ulFreeBlockCnt ); if(( rc = WriteBlock( ulLastFreeBlock, 8, mbb )) != XB_NO_ERROR ){ iErrorStop = 190; throw rc; } return XB_NO_ERROR; } /* insert into the chain */ /* 1 - set the next bucket on the current node */ /* 2 - write this node */ /* 3 - go to the previous node */ /* 4 - insert this nodes id into the previous node set */ /* 5 - write previous node */ ePutUInt32( (char *) mbb, ulSaveNextFreeBlock ); ePutUInt32( (char *) mbb+4, ulNoOfFreedBlocks ); if(( rc = WriteBlock( ulBlockNo, 8, mbb )) != XB_NO_ERROR ){ iErrorStop = 200; throw rc; } if(( rc = ReadBlockHeader( ulLastFreeBlock, 2 )) != XB_NO_ERROR ){ iErrorStop = 210; throw rc; } ePutUInt32( (char *) mbb, ulBlockNo ); if(( rc = WriteBlock( ulLastFreeBlock, 8, mbb )) != XB_NO_ERROR ){ iErrorStop = 220; throw rc; } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::DeleteMemoField() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /************************************************************************/ //! @brief Get a set of blocks from the free block chain. /*! This routine grabs a set of blocks out of the free block chain. \param ulBlocksNeeded The number of blocks requested. \param ulLocation \param ulPrevNode \returns Return Codes */ xbInt16 xbMemoDbt4::GetBlockSetFromChain( xbUInt32 ulBlocksNeeded, xbUInt32 ulLocation, xbUInt32 ulPrevNode ) { xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; xbUInt32 ulNextFreeBlock2; xbUInt32 ulNewFreeBlocks; xbUInt32 ulSaveNextFreeBlock; try{ if(( rc = ReadBlockHeader( ulLocation, 2 )) != XB_NO_ERROR ){ iErrorStop = 100; throw rc; } if( ulBlocksNeeded == ulFreeBlockCnt ){ // grab this whole set of blocks if( ulPrevNode == 0 ){ // first in the chain ulHdrNextBlock = ulNextFreeBlock; if(( rc = UpdateHeadNextNode()) != XB_NO_ERROR ){ iErrorStop = 110; throw rc; } } else // remove out of the middle or end { ulNextFreeBlock2 = ulNextFreeBlock; if(( rc = ReadBlockHeader( ulPrevNode, 2 )) != XB_NO_ERROR ){ iErrorStop = 120; throw rc; } ulNextFreeBlock = ulNextFreeBlock2; if(( rc = WriteBlockHeader( ulPrevNode, 2 )) != XB_NO_ERROR ){ iErrorStop = 130; throw rc; } } } else { // only take a portion of this set if( ulPrevNode == 0 ){ // first in the set ulHdrNextBlock = ulLocation + ulBlocksNeeded; if(( rc = UpdateHeadNextNode()) != XB_NO_ERROR ){ iErrorStop = 140; throw rc; } ulFreeBlockCnt -= ulBlocksNeeded; if(( rc = WriteBlockHeader( ulHdrNextBlock, 2 )) != XB_NO_ERROR ){ iErrorStop = 150; throw rc; } } else { // remove out of the middle or end ulNewFreeBlocks = ulFreeBlockCnt - ulBlocksNeeded; ulSaveNextFreeBlock = ulNextFreeBlock; ulNextFreeBlock2 = ulLocation + ulBlocksNeeded; if(( rc = ReadBlockHeader( ulPrevNode, 2 )) != XB_NO_ERROR ){ iErrorStop = 160; throw rc; } ulNextFreeBlock = ulNextFreeBlock2; if(( rc = WriteBlockHeader( ulPrevNode, 2 )) != XB_NO_ERROR ){ iErrorStop = 170; throw rc; } ulFreeBlockCnt = ulNewFreeBlocks; ulNextFreeBlock = ulSaveNextFreeBlock; if(( rc = WriteBlockHeader( ulNextFreeBlock2, 2 )) != XB_NO_ERROR ){ iErrorStop = 180; throw rc; } } } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::GetBlockSetFromChain() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ //! @brief Get a memo field for a given field number. /*! \param iFieldNo Field number to retrieve data for. \param sMemoData Output - string containing memo field data. \returns Return Codes */ xbInt16 xbMemoDbt4::GetMemoField( xbInt16 iFieldNo, xbString & sMemoData ){ xbUInt32 ulBlockNo; xbUInt32 ulMemoFieldLen; xbUInt32 ulMemoFieldDataLen; xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; char *p = NULL; try{ if(( rc = GetMemoFieldLen( iFieldNo, ulMemoFieldLen, ulBlockNo )) != XB_NO_ERROR ){ iErrorStop = 100; throw rc; } if( ulBlockNo == 0L || ulMemoFieldLen == 0L ) sMemoData = ""; else{ ulMemoFieldDataLen = ulMemoFieldLen - 8; if(( p = (char *)calloc(1, ulMemoFieldDataLen+1)) == NULL ){ iErrorStop = 110; rc = XB_NO_MEMORY; xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::GetMemoField() lBlockNo = %ld Data Len = [%ld]", ulBlockNo, ulMemoFieldDataLen + 1 ); xbase->WriteLogMessage( sMsg.Str() ); throw rc; } // go to the first block of the memo field, skip past the first 8 bytes if(( xbFseek( ( ulBlockNo * GetBlockSize() + 8 ), SEEK_SET )) != XB_NO_ERROR ){ iErrorStop = 120; rc = XB_SEEK_ERROR; throw rc; } // read the memo file data into buffer pointed to by "p" if(( rc = xbFread( p, ulMemoFieldDataLen, 1 )) != XB_NO_ERROR ){ iErrorStop = 130; rc = XB_READ_ERROR; throw rc; } // null terminate the string char *p2; p2 = p + ulMemoFieldDataLen; *p2 = 0x00; // save it to the string sMemoData.Set( p, ulMemoFieldDataLen + 1 ); free( p ); } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::GetMemoField() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); if( p ) free( p ); } return rc; } /***********************************************************************/ //! @brief Get a memo field length for a given field number. /*! \param iFieldNo Field number to retrieve data for. \param ulMemoFieldLen Output - length of memo field data. \returns Return Codes */ xbInt16 xbMemoDbt4::GetMemoFieldLen( xbInt16 iFieldNo, xbUInt32 &ulMemoFieldLen ){ xbUInt32 ulBlockNo; return GetMemoFieldLen( iFieldNo, ulMemoFieldLen, ulBlockNo ); } /***********************************************************************/ //! @brief Get a memo field length for a given field number. /*! \param iFieldNo Field number to retrieve data for. \param ulMemoFieldLen Output - length of memo field data. \param ulBlockNo Output - Starting block number. \returns Return Codes */ xbInt16 xbMemoDbt4::GetMemoFieldLen( xbInt16 iFieldNo, xbUInt32 &ulMemoFieldLen, xbUInt32 &ulBlockNo ){ xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; char cFieldType; try{ if(( rc = dbf->GetFieldType( iFieldNo, cFieldType )) != XB_NO_ERROR ){ iErrorStop = 100; throw rc; } if( cFieldType != 'M' ){ iErrorStop = 110; rc = XB_INVALID_MEMO_FIELD; throw rc; } if(( rc = dbf->GetULongField( iFieldNo, ulBlockNo )) < XB_NO_ERROR ){ iErrorStop = 120; throw rc; } if( ulBlockNo < 1 ){ ulMemoFieldLen = 0; return XB_NO_ERROR; } if(( rc = ReadBlockHeader( ulBlockNo, 1 )) != XB_NO_ERROR ){ iErrorStop = 130; throw rc; } ulMemoFieldLen = ulFieldLen; } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::GetMemoFieldLen() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ //! @brief Open memo file. /*! \returns Return Codes */ xbInt16 xbMemoDbt4::OpenMemoFile() { xbInt16 iErrorStop = 0; xbInt16 rc = XB_NO_ERROR; try{ if(( rc = xbFopen( dbf->GetOpenMode(), dbf->GetShareMode())) != XB_NO_ERROR ){ iErrorStop = 100; throw rc; } if(( rc = ReadDbtHeader( 1 )) != XB_NO_ERROR ){ iErrorStop = 110; throw rc; } if(( mbb = (void *) malloc( GetBlockSize())) == NULL ){ xbFclose(); iErrorStop = 120; rc = XB_NO_MEMORY; throw rc; } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::OpenMemoFile() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ //! @brief Pack memo file. /*! This routine frees up any unused blocks in the file resulting from field updates. Version 3 memo files do not reclaim unused space (Version 4 files do). This routine cleans up the unused space. \returns Return Codes */ xbInt16 xbMemoDbt4::PackMemo( void (*memoStatusFunc ) ( xbUInt32 ulItemNum, xbUInt32 ulNumItems )){ xbInt16 iRc = 0; xbInt16 iErrorStop = 0; char * cBlock = NULL; #ifdef XB_LOCKING_SUPPORT xbBool bTableLocked = xbFalse; xbBool bMemoLocked = xbFalse; #endif try{ #ifdef XB_LOCKING_SUPPORT if( dbf->GetAutoLock() && !dbf->GetTableLocked() ){ if(( iRc = dbf->LockTable( XB_LOCK )) != XB_NO_ERROR ){ iErrorStop = 100; throw iRc; } else { bTableLocked = xbTrue; } if(( iRc = LockMemo( XB_LOCK )) != XB_NO_ERROR ){ iErrorStop = 110; throw iRc; } else { bMemoLocked = xbTrue; } } #endif // create temp file xbString sTempMemoName; //if(( iRc = CreateUniqueFileName( GetDirectory(), "dbt", sTempMemoName )) != XB_NO_ERROR ){ if(( iRc = CreateUniqueFileName( GetTempDirectory(), "DBT", sTempMemoName )) != XB_NO_ERROR ){ iErrorStop = 120; throw iRc; } xbMemoDbt4 *pMemo = new xbMemoDbt4( dbf, sTempMemoName ); if(( iRc = pMemo->CreateMemoFile()) != XB_NO_ERROR ){ iErrorStop = 130; throw iRc; } // for dbase III, block size is always 512, don't need to reset it // for each record in dbf xbUInt32 ulRecCnt; if(( iRc = dbf->GetRecordCnt( ulRecCnt)) != XB_NO_ERROR ){ iErrorStop = 140; throw iRc; } xbInt32 lFldCnt = dbf->GetFieldCnt(); char cFldType; xbString sMemoFldData; for( xbUInt32 ulI = 1; ulI <= ulRecCnt; ulI++ ){ if(( iRc = dbf->GetRecord( ulI )) != XB_NO_ERROR ){ iErrorStop = 150; throw iRc; } if( (void *) memoStatusFunc) (*memoStatusFunc)(ulI, ulRecCnt ); // for each memo field for( xbInt32 lFc = 0; lFc < lFldCnt; lFc++ ){ if(( iRc = dbf->GetFieldType( lFc, cFldType )) != XB_NO_ERROR ){ iErrorStop = 160; throw iRc; } if( cFldType == 'M' ){ // copy it to work field if(( iRc = dbf->GetMemoField( lFc, sMemoFldData )) != XB_NO_ERROR ){ iErrorStop = 170; throw iRc; } // write it to new field if(( iRc = pMemo->UpdateMemoField( lFc, sMemoFldData )) != XB_NO_ERROR ){ iErrorStop = 180; throw iRc; } } } } //copy target back to source xbUInt32 ulBlkSize = GetBlockSize(); xbUInt64 ullFileSize; if(( iRc = pMemo->GetFileSize( ullFileSize )) != XB_NO_ERROR ){ iErrorStop = 190; throw iRc; } // file size should be evenly divisible by block size xbUInt32 ulBlkCnt; if( ullFileSize % ulBlkSize ){ iErrorStop = 200; throw iRc; } else { ulBlkCnt = (xbUInt32) (ullFileSize / ulBlkSize); } if(( iRc = xbTruncate( 0 )) != XB_NO_ERROR ){ iErrorStop = 210; throw iRc; } if(( cBlock = (char *) malloc( ulBlkSize )) == NULL ){ iErrorStop = 220; throw iRc; } // can't rename files in a multiuser, cross platform environment, causes issues // copy work table back to source table for( xbUInt32 ulBc = 0; ulBc < ulBlkCnt; ulBc++ ){ if(( iRc = pMemo->ReadBlock( ulBc, ulBlkSize, cBlock )) != XB_NO_ERROR ){ iErrorStop = 230; throw iRc; } if(( iRc = WriteBlock( ulBc, ulBlkSize, cBlock )) != XB_NO_ERROR ){ iErrorStop = 240; throw iRc; } } if(( xbFseek( 8, SEEK_SET )) != XB_NO_ERROR ){ iErrorStop = 250; iRc = XB_SEEK_ERROR; throw iRc; } for( int i = 1; i < 9; i++ ) xbFputc( sDbfFileNameWoExt[i] ); //close and delete target if(( iRc = pMemo->xbFclose()) != XB_NO_ERROR ){ iErrorStop = 260; throw iRc; } if(( iRc = pMemo->xbRemove()) != XB_NO_ERROR ){ iErrorStop = 270; throw iRc; } free( cBlock ); delete pMemo; } catch (xbInt16 iRc ){ free( cBlock ); xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::PackMemo() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, iRc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( iRc )); } #ifdef XB_LOCKING_SUPPORT if( bTableLocked ) dbf->LockTable( XB_UNLOCK ); if( bMemoLocked ) LockMemo( XB_UNLOCK ); #endif return iRc; } /***********************************************************************/ //! @brief Read block header. /*! \param ulBlockNo Block to read \param iOption 1 - Read fields option 1 2 - Read fields option 2 \returns Return Codes */ xbInt16 xbMemoDbt4::ReadBlockHeader( xbUInt32 ulBlockNo, xbInt16 iOption ) { xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; try{ if(( rc = ReadBlock( ulBlockNo, 8, mbb )) != XB_NO_ERROR ){ iErrorStop = 100; rc = XB_READ_ERROR; } if( iOption == 1 ){ iField1 = eGetInt16((char *) mbb ); iStartPos = eGetInt16((char *) mbb+2); ulFieldLen = eGetUInt32((char *) mbb+4); } else if( iOption == 2 ){ ulNextFreeBlock = eGetUInt32((char *) mbb ); ulFreeBlockCnt = eGetUInt32((char *) mbb+4 ); } else{ iErrorStop = 110; rc = XB_INVALID_OPTION; throw rc; } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::ReadBlockHeader() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ //! @brief Read dbt header file. /*! \param iOption 0 --> read only first four bytes
1 --> read the entire thing \returns Return Codes */ xbInt16 xbMemoDbt4::ReadDbtHeader( xbInt16 iOption ) { xbInt16 iErrorStop = 0; xbInt16 rc = XB_NO_ERROR; xbInt16 iReadLen = 0; char *p; char MemoBlock[22]; try{ if( !FileIsOpen() ){ iErrorStop = 100; rc = XB_NOT_OPEN; throw rc; } if( xbFseek( 0, SEEK_SET )){ iErrorStop = 110; rc = XB_SEEK_ERROR; throw rc; } if( iOption ) iReadLen = 22; else iReadLen = 4; if(( xbFread( &MemoBlock, (size_t) iReadLen, 1 )) != XB_NO_ERROR ){ iErrorStop = 120; rc = XB_READ_ERROR; throw rc; } p = MemoBlock; ulHdrNextBlock = eGetUInt32( p ); if( iOption == 0) return XB_NO_ERROR; p += 8; sDbfFileNameWoExt = ""; for( int i = 0; i < 8; i++ ) sDbfFileNameWoExt += *p++; p += 4; SetBlockSize( (xbUInt32) eGetInt16( p )); cVersion = MemoBlock[16]; } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::ReadDbtHeader() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ #ifdef XB_DEBUG_SUPPORT //! @brief Read free block information from header. /*! This routing pulls any reusable block information for file header. Not used with version 3 memo files - stub. \param ulBlockNo \param ulNextBlock \param ulFreeBlockCnt \returns XB_NO_ERROR */ xbInt16 xbMemoDbt4::ReadFreeBlockHeader( xbUInt32 ulBlockNo, xbUInt32 &ulNextBlock, xbUInt32 &ulFreeBlockCount ){ xbInt16 rc = XB_NO_ERROR; rc = ReadBlockHeader( ulBlockNo, 2 ); ulNextBlock = ulNextFreeBlock; ulFreeBlockCount = ulFreeBlockCnt; return rc; } #endif /***********************************************************************/ //! @brief Update header name. /*! \returns XB_NO_ERROR */ xbInt16 xbMemoDbt4::UpdateHeaderName() { xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; GetFileNamePart( sDbfFileNameWoExt ); sDbfFileNameWoExt.PadRight( ' ', 8 ); // need at least eight bytes of name sDbfFileNameWoExt = sDbfFileNameWoExt.Mid( 1, 8 ); // need no more than eight bytes of name try{ if(( rc = xbFseek( 8, SEEK_SET )) != XB_NO_ERROR ){ iErrorStop = 100; throw rc; } for( int i = 1; i < 9; i++ ){ if(( rc = xbFputc( sDbfFileNameWoExt[i] )) != XB_NO_ERROR ){ iErrorStop = 110; throw rc; } } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::UpdateHeaderName() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ //! @brief Update a memo field length for a given field number. /*! \param iFieldNo Field number to update data for. \param sMemoData Data to update memo field data with. \returns Return Codes */ xbInt16 xbMemoDbt4::UpdateMemoField( xbInt16 iFieldNo, const xbString & sMemoData ) { xbInt16 iErrorStop = 0; xbInt16 rc = XB_NO_ERROR; xbUInt32 ulBlockNo; try{ if(( rc = dbf->GetULongField( iFieldNo, ulBlockNo )) < XB_NO_ERROR ){ iErrorStop = 100; throw rc; } if( sMemoData == "" ){ if( ulBlockNo == 0 ){ /* if nothing to do, return */ return XB_NO_ERROR; } else { // if this is in the new blocks link list already, then this is not the first update for this memo field // this would be second or third update on the field since the original change and not commited // Since it won't be needed in either a Commmit() or Abort(), can be freed immediately if( llNewBlocks.SearchFor( ulBlockNo ) > 0 ){ if(( rc = FreeMemoBlockChain( ulBlockNo )) != XB_NO_ERROR ){ iErrorStop = 110; throw rc; } if(( llNewBlocks.RemoveByVal( ulBlockNo )) < XB_NO_ERROR ){ iErrorStop = 120; throw rc; } } else { // first revision, save what it was in case of Abort() command if(( llOldBlocks.InsertAtFront( ulBlockNo )) != XB_NO_ERROR ){ iErrorStop = 130; throw rc; } } if(( rc = dbf->PutField( iFieldNo, "" )) != XB_NO_ERROR ){ iErrorStop = 140; throw rc; } } } else { // free up the old space xbUInt32 ulLastDataBlock = 0L; if( ulBlockNo > 0 ){ if( llNewBlocks.SearchFor( ulBlockNo ) > 0 ){ if(( rc = FreeMemoBlockChain( ulBlockNo, ulLastDataBlock )) != XB_NO_ERROR ){ iErrorStop = 150; throw rc; } } else { // first revision, save what it was in case of Abort() command if(( rc = llOldBlocks.InsertAtFront( ulBlockNo )) != XB_NO_ERROR ){ iErrorStop = 160; throw rc; } } } // should next line be unsigned 32 bit int? xbUInt32 ulTotalLen = 8 + sMemoData.Len(); xbUInt32 ulBlocksNeeded; if( ulTotalLen % GetBlockSize()) ulBlocksNeeded = ulTotalLen / GetBlockSize() + 1; else ulBlocksNeeded = ulTotalLen / GetBlockSize(); xbBool bUsedBlockFound; xbUInt32 ulHeadBlock; xbUInt32 ulPrevNode; if(( rc = FindBlockSetInChain( ulBlocksNeeded, ulLastDataBlock, ulHeadBlock, ulPrevNode, bUsedBlockFound )) != XB_NO_ERROR ){ iErrorStop = 170; throw rc; } iField1 = -1; iStartPos = 8; ulFieldLen = sMemoData.Len() + 8; if( bUsedBlockFound ){ if(( rc = GetBlockSetFromChain( ulBlocksNeeded, ulHeadBlock, ulPrevNode )) != XB_NO_ERROR ){ iErrorStop = 180; throw rc; } if(( rc = WriteBlockHeader( ulHeadBlock, 1 )) != XB_NO_ERROR ){ iErrorStop = 190; throw rc; } if(( rc = xbFwrite( sMemoData.Str(), sMemoData.Len(), 1 )) != XB_NO_ERROR ){ iErrorStop = 200; throw rc; } } else { // append to the end if(( rc = WriteBlockHeader( ulLastDataBlock, 1 )) != XB_NO_ERROR ){ iErrorStop = 220; throw rc; } if(( rc = xbFwrite( sMemoData.Str(), sMemoData.Len(), 1 )) != XB_NO_ERROR ){ iErrorStop = 230; throw rc; } if(( rc = xbFputc( 0x00, (xbInt32)((ulBlocksNeeded * GetBlockSize()) - (sMemoData.Len() + 8)))) != XB_NO_ERROR ){ iErrorStop = 240; throw rc; } if( ulLastDataBlock == ulHdrNextBlock ){ // this is first node to be added to the node chain ulHdrNextBlock += ulBlocksNeeded; ulHeadBlock = ulLastDataBlock; if(( rc = UpdateHeadNextNode()) != XB_NO_ERROR ){ iErrorStop = 250; throw rc; } } else { // adding memo data to the end of the file, but chain exists ulNextFreeBlock = ulLastDataBlock + ulBlocksNeeded; ulHeadBlock = ulLastDataBlock; if(( rc = WriteBlockHeader( ulPrevNode, 2 )) != XB_NO_ERROR ){ iErrorStop = 260; throw rc; } } } if(( rc = llNewBlocks.InsertAtFront( ulHeadBlock )) != XB_NO_ERROR ){ // In case of Abort(), this block needs to be freed iErrorStop = 270; throw rc; } if(( rc = dbf->PutLongField( iFieldNo, (xbInt32) ulHeadBlock )) != XB_NO_ERROR ){ iErrorStop = 280; throw rc; } } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::UpdateMemoField() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ //! @brief Write block header. /*! \param ulBlockNo Block to read \param iOption 1 - Read fields option 1 2 - Read fields option 2 \returns Return Codes */ xbInt16 xbMemoDbt4::WriteBlockHeader( xbUInt32 ulBlockNo, xbInt16 iOption ) { xbInt16 rc = XB_NO_ERROR; xbInt16 iErrorStop = 0; try{ if( iOption == 1 ){ ePutInt16 ((char *) mbb, iField1 ); ePutInt16 ((char *) mbb+2, iStartPos ); ePutUInt32((char *) mbb+4, ulFieldLen ); } else if( iOption == 2 ){ ePutUInt32((char *) mbb, ulNextFreeBlock ); ePutUInt32((char *) mbb+4, ulFreeBlockCnt ); } else{ iErrorStop = 100; rc = XB_INVALID_OPTION; throw rc; } if(( rc = WriteBlock( ulBlockNo, 8, mbb )) != XB_NO_ERROR ){ iErrorStop = 110; rc = XB_READ_ERROR; } } catch (xbInt16 rc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::WriteHeader() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, rc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( rc )); } return rc; } /***********************************************************************/ //! @brief Empty the memo file. /*! \returns Return Codes */ xbInt16 xbMemoDbt4::Zap(){ xbInt16 iRc = 0; xbInt16 iErrorStop = 0; char cBuf[4]; try{ ulHdrNextBlock = 1L; ePutUInt32( cBuf, ulHdrNextBlock ); if(( iRc != xbFseek( 0, SEEK_SET )) != XB_NO_ERROR ){ iErrorStop = 100; throw iRc; } if(( iRc != xbFwrite( cBuf, 4, 1 ))!= XB_NO_ERROR ){ iErrorStop = 110; throw iRc; } if(( iRc != xbTruncate( GetBlockSize())) != XB_NO_ERROR ){ iErrorStop = 120; throw iRc; } } catch (xbInt16 iRc ){ xbString sMsg; sMsg.Sprintf( "xbMemoDbt4::Zap() Exception Caught. Error Stop = [%d] rc = [%d]", iErrorStop, iRc ); xbase->WriteLogMessage( sMsg.Str() ); xbase->WriteLogMessage( GetErrorMessage( iRc )); } return iRc; }/***********************************************************************/ } /* namespace */ #endif /* XB_DBF4_SUPPORT */ #endif /* XB_MEMO_SUPPORT */