///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoArchiveThread.cc  
// --------------------                                                     
// Cego archive thread class implementation
//                                                         
// Design and Implementation by Bjoern Lemke
//               
// (C)opyright 2000-2026 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoArchiveThread
// 
// Description: Archive thread implementation for redo log file shifting
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// base includes
#include <lfcbase/Exception.h>
#include <lfcbase/Net.h>
#include <lfcbase/Sleeper.h>

// cego includes
#include "CegoArchiveThread.h"
#include "CegoDefs.h"
#include "CegoXMLdef.h"
#include "CegoAdminHandler.h"

CegoArchiveThread::CegoArchiveThread(CegoDatabaseManager *pDBMng) : Thread()
{
    _pDBMng = pDBMng;
    _terminated = false;
    _modId = _pDBMng->getModId("CegoArchiveThread");
}

CegoArchiveThread::~CegoArchiveThread()  
{
    _terminated=true;
    _joined=false;
    unsigned count=0;
    while ( _joined == false && count < 10 )
    {
	Sleeper s;
	s.milliSleep(100);
	count++;
    }
    
    if ( _joined )
    {
	_pDBMng->log(_modId, Logger::NOTICE, Chain("Archive thread terminated"));
	join(getTid());
    }
    else
    {
	_pDBMng->log(_modId, Logger::NOTICE, Chain("Canceling hanging archive thread ..."));
	cancel();
    }
}

void* CegoArchiveThread::job(void* arg)
{
    _pDBMng->log(_modId, Logger::NOTICE, Chain("Archive thread started"));
    
    while ( _terminated == false )
    {
	try
	{		
	    shiftRedoLogs();
	}
	catch ( Exception e)
	{
	    Chain msg;
	    e.pop(msg);
	    _pDBMng->log(_modId, Logger::LOGERR, Chain("Archive thread error : ") + msg); 	   
	}

	unsigned i=0;
	Sleeper s;
	
	while( i < LOGMNG_ARCHIVE_DELAY * 1000 && _terminated == false )
	{
	    s.milliSleep(100);
	    i+=100;
	}
    }
    _joined = true;
    return 0;
}


// wrapper method to trigger shiftRedoLogs method
// this is needed to rescue active online logfile after database crash
void CegoArchiveThread::shiftActive(CegoDatabaseManager* pDBMng, const Chain& tableSet)
{	
    ListT<Chain> lfList;
    ListT<unsigned> sizeList;
    ListT<Chain> statusList;
    
    pDBMng->getLogFileInfo(tableSet, lfList, sizeList, statusList);	
    
    Chain *pLogName = lfList.First();
    unsigned *pSize= sizeList.First();
    Chain *pStatus = statusList.First();
    
    while ( pLogName && pSize && pStatus )
    {
	Element *pLE = new Element(XML_LOGFILE_ELEMENT);
	pLE->setAttribute(XML_NAME_ATTR, *pLogName);
	pLE->setAttribute(XML_SIZE_ATTR, Chain(*pSize));
	
	pLE->setAttribute(XML_STATUS_ATTR, *pStatus);

	if ( *pStatus == Chain(XML_ACTIVE_VALUE) )
	{	    
	    unsigned tabSetId = pDBMng->getTabSetId(tableSet);

	    ListT<Chain> archPathList;
	    ListT<Chain> archIdList;
	    
	    pDBMng->getArchLogInfo(tabSetId, archIdList, archPathList);
    	
	    Chain *pArchPath = archPathList.First();
	    
	    while ( pArchPath )
	    {
		copyLog(pDBMng, tableSet, *pLogName, *pArchPath);
		pArchPath = archPathList.Next();
	    }    
	}
	
	pLogName = lfList.Next();
	pSize = sizeList.Next();
	pStatus = statusList.Next();
    }
}

void CegoArchiveThread::shiftRedoLogs()
{        
    ListT<Chain> tsList;

    _pDBMng->getActiveTableSet(tsList, true, true); 

    Chain *pActiveTS = tsList.First();

    while ( pActiveTS )
    {	
	unsigned tabSetId = _pDBMng->getTabSetId(*pActiveTS);
	
	ListT<Chain> archPathList;
	ListT<Chain> archIdList;
	ListT<Chain> occupiedLogList;
	
	_pDBMng->getArchLogInfo(tabSetId, archIdList, archPathList);
	_pDBMng->getOccupiedLogList(tabSetId, occupiedLogList);
    
	Chain *pOccupiedLog = occupiedLogList.First();
    
	while ( pOccupiedLog )
	{

	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Shifting logfile ") + *pOccupiedLog + Chain(" to archive destinations") );
	    
	    Chain *pArchPath = archPathList.First();
	    
	    while ( pArchPath )
	    {		    
		copyLog(_pDBMng, *pActiveTS, *pOccupiedLog, *pArchPath);
		pArchPath = archPathList.Next();
	    }
	    
	    _pDBMng->setLogFileStatus(tabSetId, *pOccupiedLog, XML_FREE_VALUE);			
	
	    pOccupiedLog = occupiedLogList.Next();
	}
	
	pActiveTS = tsList.Next();
    }
}

void CegoArchiveThread::copyLog(CegoDatabaseManager* pDBMng, const Chain& tableSet, const Chain& logFileName, const Chain& archLogPath)
{
    File logFile(logFileName);
    
    logFile.open(File::READ);

    unsigned offset;
    logFile.readByte((char*)&offset, sizeof(unsigned));

    unsigned pos = sizeof(unsigned);
    unsigned long long lsn = 0;

    while ( pos < offset && lsn == 0 )
    {
	unsigned len;
	logFile.readByte((char*)&len, sizeof(unsigned));	
	
	// cout << "Read len : " << len << endl;
	
	pos += len + sizeof(unsigned);
	
	char* buf = new char[len];
	
	logFile.readByte(buf, len);
	CegoLogRecord lr;
	
	lr.decode(buf);
	
	// if ( lr.getAction() != CegoLogRecord::LOGREC_BUPAGE )
	lsn = lr.getLSN();
	
	delete[] buf;
    }
    
    logFile.close();

    // in case of no valid lsn entries, we skip this file 
    if ( lsn > 0 )
    {    	
	Chain archLogFile = pDBMng->getArchiveLogName(tableSet, lsn);	
	Chain archFileName = archLogPath + Chain("/") + archLogFile;

	// for static method, we need to get modId 
	unsigned long modId = pDBMng->getModId("CegoArchiveThread");
	pDBMng->log(modId, Logger::NOTICE, Chain("Shift ") + logFileName + Chain(" to ") + archFileName);
	
	// we first write to a temp file to indicate, that the file is currently written
	// if finished, we move the file to the original name, so the file can be further processed ( e.g. for tape archiving )
	Chain tmpName = archLogPath + Chain("/.") + archLogFile;
	
	File archFile(tmpName);
		
	archFile = logFile;
	
	archFile.open(File::APPEND);
	archFile.trunc(offset);
	archFile.close();
	
	archFile.rename(archFileName);	
    }
    return;
}
