/*!
    \file    DBMSrvBHist_History.cpp
    \author  TiloH
    \ingroup backup history handling by the DBMServer
    \brief   implementing a class for managing the information of the backup
             history files

\if EMIT_LICENCE

    ========== licence begin  GPL
    Copyright (c) 2002-2006 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end

\endif
*/


//-----------------------------------------------------------------------------
// includes
//-----------------------------------------------------------------------------

#include <stdlib.h>

#include "SAPDB/DBM/Srv/DBMSrv_Reply.hpp"
#include "SAPDB/DBM/Srv/BackupHistory/DBMSrvBHist_History.hpp"
#include "hcn31.h"
#include "hcn35.h"
#include "hcn35b.h"
#include "hcn42.h"
#include "hcn90.h"
#include "heo06.h"


//-----------------------------------------------------------------------------
// constants
//-----------------------------------------------------------------------------

const char * DBMSrvBHist_History_COL_ALL="*";
const char * DBMSrvBHist_History_KEY_ALL="*";
const char * DBMSrvBHist_History_LBL_ALL="*";
const char * DBMSrvBHist_History_ACT_ALL="*";

const char * DBMSrvBHist_History_RES_ALL="*";
const char * DBMSrvBHist_History_RES_LST="LAST";
const char * DBMSrvBHist_History_RES_CNT="CONTINUE";

const char * DBMSrvBHist_History_UNT_ALL="*";

const char * DBMSrvBHist_History_LISTPARAM_COLS      ="-C";
const char * DBMSrvBHist_History_LISTPARAM_KEY       ="-K";
const char * DBMSrvBHist_History_LISTPARAM_LABEL     ="-L";
const char * DBMSrvBHist_History_LISTPARAM_ACTION    ="-A";
const char * DBMSrvBHist_History_LISTPARAM_RESTORE   ="-R";
const char * DBMSrvBHist_History_LISTPARAM_MEDIA     ="-M";
const char * DBMSrvBHist_History_LISTPARAM_EBIDS     ="-E";
const char * DBMSrvBHist_History_LISTPARAM_UNTIL     ="-U";
const char * DBMSrvBHist_History_LISTPARAM_COMPRESSED="COMPRESSED";
const char * DBMSrvBHist_History_LISTPARAM_INVERTED  ="-INVERTED";
const char * DBMSrvBHist_History_LISTPARAM_INVERTED_S="-I";

const size_t DBMSrvBHist_History_EXPECTED_LINE_LENGTH=1024;


//-----------------------------------------------------------------------------
// members of class DBMSrvBHist_History
//-----------------------------------------------------------------------------

DBMSrvBHist_History::DBMSrvBHist_History()
{
    FirstBackup=History.end();
    CurrentBackup=OutputList.end();
    StartBackup=OutputList.end();
    EndBackup=OutputList.end();

    ColumnsAsString=0;
    Key    =0;
    Label  =0;
    Action =0;
    Restore=0;
    Until  =0;

    ForRestore  =false;
    RcOKOnly    =false;
    WithMedia   =false;
    WithEBIDs   =false;
    ListInverted=false;

    UsedLogPage =DBMSrvBHist_History_NOLOG;
    FirstLogPage=DBMSrvBHist_History_NOLOG;
    Restartable =false;

    VControl=0;
    ReplyData=0;
    ReplyLen=0;
    ReplyLenMax=0;
}

DBMSrvBHist_History::~DBMSrvBHist_History()
{
    Free();

    cn36_StrDealloc(ColumnsAsString);
    cn36_StrDealloc(Key);
    cn36_StrDealloc(Label);
    cn36_StrDealloc(Action);
    cn36_StrDealloc(Restore);
    cn36_StrDealloc(Until);
}

void DBMSrvBHist_History::Free()
{
    //delete the objects referenced by the elements of History
    for(Tools_List<DBMSrvBHist_Backup *>::iterator i=History.begin(); i!=History.end(); ++i)
        delete (*i);

    History.clear();
    OutputList.clear();
}

tcn00_Error DBMSrvBHist_History::BackupHistoryDate(VControlDataT * vcontrol,
                                                   CommandT      * command,
                                                   char          * replyData,
                                                   int           * replyLen,
                                                   int             replyLenMax)
{
    tcn00_Error rc;

    bool Exists;
    char Date[64];

    SetVControlAndReplyBuffer(vcontrol, replyData, replyLen, replyLenMax, 1);

    rc=CheckHistoryFile(Exists, Date);

    if(OK_CN00==rc)
    {
        if(!Exists)
            rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_NOHISTORY_CN00);
        else
        {
            char * CurrPos=ReplyData;

            sprintf(CurrPos, "%s%s", ANSWER_OK_CN00, LINE_SEPSTRING_CN00);
            CurrPos+=strlen(ReplyData);

            sprintf(CurrPos, "%s%s", Date, LINE_SEPSTRING_CN00);
        }
    }

    *replyLen=int(strlen(replyData));

    return rc;
}

tcn00_Error DBMSrvBHist_History::BackupHistoryOpen(VControlDataT * vcontrol,
                                                   CommandT      * command,
                                                   char          * replyData,
                                                   int           * replyLen,
                                                   int             replyLenMax)
{
    tcn00_Error rc=OK_CN00;
    char        Param[256];
    int         NParam=1;
    int         CheckEBIDs=false;

    SetVControlAndReplyBuffer(vcontrol, replyData, replyLen, replyLenMax, 1);

    // look for switch -e
    Param[0]='\0';

    while(cn90GetToken(command->args, Param, NParam, 256) && !WithEBIDs)
    {
        if(0==cn90Stricmp(Param, DBMSrvBHist_History_LISTPARAM_EBIDS))  //-e
            CheckEBIDs=true;

        NParam++;
    }

    return BackupHistoryOpen(vcontrol, CheckEBIDs, replyData, replyLen, replyLenMax);
}

tcn00_Error DBMSrvBHist_History::BackupHistoryOpen(VControlDataT * vcontrol,
                                                   int             CheckEBIDs,
                                                   char          * replyData,
                                                   int           * replyLen,
                                                   int             replyLenMax)
{
    tcn00_Error rc=OK_CN00;

    if(CheckEBIDs)
        WithEBIDs=true;
    else
        WithEBIDs=false;

    SetVControlAndReplyBuffer(vcontrol, replyData, replyLen, replyLenMax, 1);
    rc=ReadHistories();
    IfRCOKPrintOKIntoReply(rc);

    return rc;
}

struct BHistListParameter
{
  public:
    const char *  KeyWord;
    char       ** Value;
    int           ChangeToUpper;
    bool          Found;
};

tcn00_Error DBMSrvBHist_History::BackupHistoryList(VControlDataT * vcontrol,
                                                   CommandT      * command,
                                                   char          * replyData,
                                                   int           * replyLen,
                                                   int             replyLenMax)
{
    FUNCTION_DBG_MCN00_1("DBMSrvBHist_History::BackupHistoryList");

    tcn00_Error rc=OK_CN00;
    char        ParamString[256];
    char        ParamString2[256];
    int         CurParam=1;

    BHistListParameter ListPara[]={{DBMSrvBHist_History_LISTPARAM_COLS,       &ColumnsAsString, 1, 0},  // -c <col1,col2,...>
                                   {DBMSrvBHist_History_LISTPARAM_KEY,        &Key,             1, 0},  // -k <key>
                                   {DBMSrvBHist_History_LISTPARAM_LABEL,      &Label,           1, 0},  // -l <label>
                                   {DBMSrvBHist_History_LISTPARAM_ACTION,     &Action,          1, 0},  // -a <action>
                                   {DBMSrvBHist_History_LISTPARAM_RESTORE,    &Restore,         1, 0},  // -r <what>
                                   {DBMSrvBHist_History_LISTPARAM_MEDIA,      0,                0, 0},  // -m
                                   {DBMSrvBHist_History_LISTPARAM_EBIDS,      0,                0, 0},  // -e
                                   {DBMSrvBHist_History_LISTPARAM_UNTIL,      &Until,           0, 0},  // -u <when>
                                   {DBMSrvBHist_History_LISTPARAM_COMPRESSED, 0,                0, 0},  // compressed
                                   {DBMSrvBHist_History_LISTPARAM_INVERTED,   0,                0, 0},  // -inverted == -i
                                   {DBMSrvBHist_History_LISTPARAM_INVERTED_S, 0,                0, 0},  // -i == -inverted
                                   {0,                                        0,                0, 0}};

    SetVControlAndReplyBuffer(vcontrol, replyData, replyLen, replyLenMax, 1);

    rc=InitListParameters();

    if(OK_CN00==rc)
    {
        ParamString[0]='\0';

        // read the list parameters
        while(OK_CN00==rc && cn90GetToken(command->args, ParamString, CurParam, 256))
        {
            if('\0'!=ParamString[0])
            {
                int KeyWordFound=0;
                CurParam++;

                for(int i=0; !KeyWordFound && OK_CN00==rc && 0!=ListPara[i].KeyWord; i++)    //search in list of keywords until found or end of list
                {
                    if(0==cn90Stricmp(ParamString, ListPara[i].KeyWord))    //is the current parameter one of the keywords
                    {
                        ListPara[i].Found=1;
                        KeyWordFound=1;

                        if(0!=ListPara[i].Value)
                        {
                            if(cn90GetToken(command->args, ParamString2, CurParam, 255))
                            {
                                if(ListPara[i].ChangeToUpper)
                                    cn90StrUpperCopy(ParamString2, ParamString2, false);

                                CurParam++;
                                if(!cn36_StrAllocCpy(*(ListPara[i].Value), ParamString2))
                                    rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
                            }
                            else
                                rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_PARAM_CN00);
                        }
                    }
                }

                if(OK_CN00==rc && !KeyWordFound)
                    rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_PARAM_CN00);
            }
        }

        if(ListPara[4].Found)
        {
            ForRestore=true;
            RcOKOnly=true;
        }

        WithMedia=ListPara[5].Found;
        WithEBIDs=ListPara[6].Found;
        if(ListPara[9].Found || ListPara[10].Found)
            ListInverted=1;
        else
            ListInverted=0;

        // checking parameters (option mix)
        if(OK_CN00==rc)
        {
            if((ForRestore &&                                           //if ForRestore Key, Label and Action must be "*"
                (0!=strcmp(Key, DBMSrvBHist_History_KEY_ALL) ||
                 0!=strcmp(Label, DBMSrvBHist_History_LBL_ALL) || 
                 0!=strcmp(Action, DBMSrvBHist_History_ACT_ALL)  )) || 
               (!ForRestore &&                                          // until must only be used for restore
                0!=strcmp(Until, DBMSrvBHist_History_UNT_ALL))        )
            {
                rc=cn90AnswerIError(replyData, replyLen, ERR_PARAM_CN00);
            }
        }

        //translate columns argument
        if(OK_CN00==rc && 0!=strcmp(ColumnsAsString, DBMSrvBHist_History_COL_ALL)) //not just "*"
        {
            const char                 * CurPos=ColumnsAsString;
            DBMSrvBHist_Backup::Column   CurCol;

            Columns.clear(); //forget all previous columns

            while(OK_CN00==rc && 0!=CurPos && '\0'!=(*CurPos))
            {
                const char * Separator=strchr(CurPos, ',');

                if(0==Separator)
                    CurCol=DBMSrvBHist_Backup::ColumnFor(CurPos, strlen(CurPos));
                else
                    CurCol=DBMSrvBHist_Backup::ColumnFor(CurPos, Separator-CurPos);

                if(DBMSrvBHist_Backup::NoColumn!=CurCol && !Columns.push_back(CurCol))
                    rc=cn90AnswerIError(replyData, replyLen, ERR_MEM_CN00);

                if(0!=Separator)
                    CurPos=Separator+1;
                else
                    CurPos=0;
            }
        }

        if(OK_CN00==rc)
        {
            if(ListPara[8].Found)   //if parameter "compress" was set
            {
                ColumnWidthsBackup.SetColumnWidthsToZero(); //set minimal lengths to 0
                ColumnWidthsMedia.SetColumnWidthsToZero();  //so no additional spaces will be added to the output
            }
            else
                EnlargeColumnWidths(); //scan through the history and adjust column widths to the widest row for each column
        }

        if(OK_CN00==rc && ForRestore)
        {
            // read the restart info
            tcn003_BackupRestartInfo RestartInfo;

            if(STATE_ADMIN_CN00!=cn90DBState(VControl->dbname))
                rc=ERR_NOTADMIN_CN00;
            else
                rc=cn31BackupRestartInfo(VControl, ReplyData, ReplyLen, &RestartInfo);

            if(OK_CN00==rc)
            {
                if(!cn36_StrToUInt8(UsedLogPage, RestartInfo.FirstIoSequenceNeeded))
                    rc=cn90AnswerEvent(replyData, replyLen, teo200_EventList(FUNCTION_NAME_MCN00_1, ERR_UTLIOSEQNEED_CN00_1, RestartInfo.FirstIoSequenceNeeded));

                if(!cn36_StrToUInt8(FirstLogPage, RestartInfo.FirstIoSequenceOnLog))
                    rc=cn90AnswerEvent(replyData, replyLen, teo200_EventList(FUNCTION_NAME_MCN00_1, ERR_UTLIOSEQAVAIL_CN00_1, RestartInfo.FirstIoSequenceOnLog));

                Restartable =(RestartInfo.Restartable[0]=='1'); 
            }
            else
            {
                rc=OK_CN00;
                UsedLogPage =DBMSrvBHist_History_NOLOG;
                FirstLogPage=DBMSrvBHist_History_NOLOG;
                Restartable=false;
            }
        }

        // list the history
        if(OK_CN00==rc)
        {
            FirstBackup=History.begin();

            if(ForRestore)
            {
                rc=FindFirstRestoreItem();

                if(OK_CN00!=rc)
                    cn90AnswerIError(replyData, replyLen, rc);
            }

            if(OK_CN00==rc)
                rc=CreateOutputList();

            if(OK_CN00==rc)
                rc=BasicListHistory();

            if(OK_CN00==rc)
            {
                if(CurrentBackup!=OutputList.end())
                {
                    // prepare the next command 
                    VControl->szNextCommand.rawAssign("backup_history_listnext");
                    VControl->nNextCommandSkip=1;
                }
            }
            else
                CurrentBackup=StartBackup;
        }
    }

    *ReplyLen=int(strlen(replyData));

    return rc;
}

tcn00_Error DBMSrvBHist_History::BackupHistoryListNext(VControlDataT * vcontrol,
                                                       CommandT      * command,
                                                       char          * replyData,
                                                       int           * replyLen,
                                                       int             replyLenMax)
{
    tcn00_Error                               rc=OK_CN00;

    SetVControlAndReplyBuffer(vcontrol, replyData, replyLen, replyLenMax, 1);

    if(0==ColumnsAsString) //InitListParameters was never called before (after the constructor)
    {
        rc=InitListParameters();

        if(OK_CN00==rc) //enlarge minimal column widths if necessary
        {
            EnlargeColumnWidths();

            FirstBackup=History.begin();

            rc=CreateOutputList();
        }
    }

    if(CurrentBackup==EndBackup) //if at the end, start from the beginning
        CurrentBackup=StartBackup;

    if(OK_CN00==rc)
    {
        Tools_List<DBMSrvBHist_Backup*>::iterator HistoryEntry=CurrentBackup;

        rc=BasicListHistory();

        if(OK_CN00==rc)
        {
            if(CurrentBackup!=OutputList.end())
            {
                // prepare the next command 
                VControl->szNextCommand.rawAssign("backup_history_listnext");
                VControl->nNextCommandSkip = 1;
            }
        }
        else
            CurrentBackup=HistoryEntry;
    }

    *ReplyLen=int(strlen(replyData));

    return rc;
}

tcn00_Error DBMSrvBHist_History::BackupHistoryClose(VControlDataT * vcontrol,
                                                    CommandT      * command,
                                                    char          * replyData,
                                                    int           * replyLen,
                                                    int             replyLenMax)
{
    tcn00_Error rc=OK_CN00;

    SetVControlAndReplyBuffer(vcontrol, replyData, replyLen, replyLenMax, 1);

    Free();

    IfRCOKPrintOKIntoReply(rc);

    return rc;
}

tcn00_Error DBMSrvBHist_History::AnalyzeLogBackup(tcn003_BackupResult * Result)
{
    tcn00_Error rc=OK_CN00;

    char replyData[1000];
    int  replyLen;

    bool bHistoryOpened  = true;
    char szStamp[20];

    SetVControlAndReplyBuffer(cn00DBMServerData::vcontrol(), replyData, &replyLen, 999, 1);

    char        * Line=0;
    size_t        LineLength=DBMSrvBHist_History_EXPECTED_LINE_LENGTH;

    if(!cn36_StrAlloc(Line, LineLength))    //get memory for a line used with sqlfreadc() and expanded if needed
        rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);

    //read the history files (forward) into lists. if backwards reading is implemented, this
    //part has to be implemented into the rest of the function
    if(OK_CN00==rc)
        rc=ReadHistory(History, Line, LineLength);

    if(OK_CN00==rc)
    {
        // compute backup timestamp
        if(20>strlen(Result->szDBStamp1Date)+strlen(Result->szDBStamp1Time+2))
        {
            SAPDB_strcpy(szStamp, Result->szDBStamp1Date);
            strcat(szStamp, Result->szDBStamp1Time+2);
        }
        else
            rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_CN00);
    }

    if(OK_CN00==rc)
    {
        Tools_List<DBMSrvBHist_Backup*>::reverse_iterator Backup=History.rbegin();
        Tools_List<DBMSrvBHist_Backup*>::reverse_iterator Match=History.rend();

        for(; Backup!=History.rend() && Match==History.rend(); ++Backup)
        {
            if((*Backup)->HasTimeStamp(szStamp))
                Match=Backup; //last log backup matching Result 
        }

        if(Match!=History.rend())
        {
            while(OK_CN00==rc && Backup!=History.rend() && (*Backup)->HasSameKeyAs(**Match))
            {
                const char * StartDate=(*Backup)->GiveStartDate();
                const char * DBStamp1 =(*Backup)->GiveDBStamp1();

                if(strlen(StartDate)<19 || strlen(DBStamp1)<19)
                    rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_KNLDIRTY_CN00);
                else
                {
                    sprintf(Result->szDate, "%c%c%c%c%c%c%c%c", StartDate[0],
                                                                StartDate[1],
                                                                StartDate[2],
                                                                StartDate[3],
                                                                StartDate[5],
                                                                StartDate[6],
                                                                StartDate[8],
                                                                StartDate[9]);
                    sprintf(Result->szTime, "00%c%c%c%c%c%c",   StartDate[11],
                                                                StartDate[12],
                                                                StartDate[14],
                                                                StartDate[15],
                                                                StartDate[17],
                                                                StartDate[18]);

                    sprintf(Result->szPagesTransfered, "%d", atoi(Result->szPagesTransfered) + atoi((*Backup)->GivePagesTransfered()));

                    cn36_StrNCpy(Result->szFirstLogPageNo, (*Backup)->GiveStartLogPageAsString(), sizeof(Result->szFirstLogPageNo)-1);

                    sprintf(Result->szDBStamp1Date, "%c%c%c%c%c%c%c%c", DBStamp1[0],
                                                                        DBStamp1[1],
                                                                        DBStamp1[2],
                                                                        DBStamp1[3],
                                                                        DBStamp1[5],
                                                                        DBStamp1[6],
                                                                        DBStamp1[8],
                                                                        DBStamp1[9]);
                    sprintf(Result->szDBStamp1Time, "00%c%c%c%c%c%c",   DBStamp1[11],
                                                                        DBStamp1[12],
                                                                        DBStamp1[14],
                                                                        DBStamp1[15],
                                                                        DBStamp1[17],
                                                                        DBStamp1[18]);

                    sprintf(Result->szVolumes, "%d", atoi(Result->szVolumes) + atoi((*Backup)->GiveVolumes()));
                }

                ++Backup;
            }
        }
    }

    return rc;
}

tcn00_Error DBMSrvBHist_History::ReadBasicHistories(const char * DBName,
                                                    char       * replyData,
                                                    int        * replyLen)
{
    tcn00_Error rc=OK_CN00;

    WithEBIDs=false; //don't check availability of external backup

    SetVControlAndReplyBuffer(cn00DBMServerData::vcontrol(), replyData, replyLen, 100, 0);

    return ReadHistories();
}

void DBMSrvBHist_History::ReduceToFileLogBackupsWithName(const char * AbsoluteName,
                                                         const char * DefaultDirectory)
{
    Tools_List<DBMSrvBHist_Backup *>::iterator Backup=History.begin();

    while(Backup!=History.end())
    {
        if((*Backup)->IsALogBackup() && (*Backup)->MatchesFileNameElseReduce(AbsoluteName, DefaultDirectory))
            ++Backup;
        else
        {
            delete (*Backup);
            Backup=History.erase(Backup); //erase everything that is not a matching log backup
        }
    }
}

int DBMSrvBHist_History::ContainsFileBackup(const char                 *  BaseName,
                                            const char                 *  labelFromBackup,
                                            const tcn36_DateTimeString  & backupStartDateTime,
                                            const char                 *& BackupKey,
                                            const char                 *& BackupLabel)
{
    int rc=0;

    Tools_List<DBMSrvBHist_Backup *>::iterator Backup=History.begin();

    BackupKey=BackupLabel=0;

    while(Backup!=History.end())
    {
        if((*Backup)->ContainsFileBackup(BaseName, labelFromBackup, backupStartDateTime)) //label and time must match
        {
            rc=1;
            BackupKey=(*Backup)->GiveKey();
            BackupLabel=(*Backup)->GiveLabel();
            Backup=History.end();
        }
        else
            ++Backup;
    }

    return rc;
}

const Tools_List<DBMSrvBHist_Backup *> & DBMSrvBHist_History::GetHistoryList() const
{
    return History;
}

tcn00_Error DBMSrvBHist_History::InitListParameters()
{
    tcn00_Error rc=OK_CN00;

    EndBackup=StartBackup=CurrentBackup=OutputList.end();

    if(!cn36_StrAllocCpy(ColumnsAsString, DBMSrvBHist_History_COL_ALL) ||
       !cn36_StrAllocCpy(Key,             DBMSrvBHist_History_KEY_ALL) ||
       !cn36_StrAllocCpy(Label,           DBMSrvBHist_History_LBL_ALL) ||
       !cn36_StrAllocCpy(Action,          DBMSrvBHist_History_ACT_ALL) ||
       !cn36_StrAllocCpy(Restore,         DBMSrvBHist_History_RES_ALL) ||
       !cn36_StrAllocCpy(Until,           DBMSrvBHist_History_UNT_ALL)    )
    {
        rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
    }

    ForRestore=false;
    RcOKOnly  =false;
    WithMedia =false;
    WithEBIDs =false;

    UsedLogPage =DBMSrvBHist_History_NOLOG;
    FirstLogPage=DBMSrvBHist_History_NOLOG;
    Restartable =false;

    Columns.clear();                                                    //forget previous columns ...
    for(int i=0; OK_CN00==rc && i<DBMSrvBHist_Backup::NoColumn; ++i)    //...and place all columns in the list
        if(!Columns.push_back(DBMSrvBHist_Backup::Column(i)))
            rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);

    ColumnWidthsBackup.SetColumnWidthsToDefaults();
    ColumnWidthsMedia.SetColumnWidthsToDefaults();

    return rc;
}

tcn00_Error DBMSrvBHist_History::ReadHistories()
{
    tcn00_Error rc=OK_CN00;

    //read dbm.knl, dbm.mdf and also dbm.ebf (it contains medium information of stage backups) 
    Tools_List<DBMSrvBHist_Part *> TempMediaList;
    Tools_List<DBMSrvBHist_Part *> TempEBIDList;

    char        * Line=0;
    size_t        LineLength=DBMSrvBHist_History_EXPECTED_LINE_LENGTH;

    if(!cn36_StrAlloc(Line, LineLength))    //get memory for a line used with sqlfreadc() and expanded if needed
        rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);

    //read the history files (forward) into lists. if backwards reading is implemented, this
    //part has to be implemented into the join algorithm
    if(OK_CN00==rc)
        rc=ReadHistory(History, Line, LineLength);

    if(OK_CN00==rc)
        rc=ReadMediaHistory(TempMediaList, Line, LineLength);

    if(OK_CN00==rc)
        rc=ReadEBIDHistory(TempEBIDList, Line, LineLength);

    cn36_StrDealloc(Line);  //we don't read from the files any longer

    //now join the 3 lists into the final backup history structure
    if(OK_CN00==rc)
        rc=JoinHistoryWithMediaHistory(TempMediaList);

    if(OK_CN00==rc)
        rc=JoinHistoryWithEBIDHistory(TempEBIDList);

    //check availability of external backups
    if(OK_CN00==rc && WithEBIDs)
        rc=CheckAvailabilityOfExternalBackups(TempEBIDList);

    //free everything, that has not become a part of History and was not freed so far
    for(Tools_List<DBMSrvBHist_Part *>::iterator EBID=TempEBIDList.begin(); EBID!=TempEBIDList.end(); ++EBID)
        if(!(*EBID)->IsAdded())
           delete (*EBID);

    //manipulate return code for external backup tools
    for(Tools_List<DBMSrvBHist_Backup *>::iterator Backup=History.begin(); OK_CN00==rc && Backup!=History.end(); ++Backup)
    {
        rc=(*Backup)->AdjustReturnCode();

        if(OK_CN00!=rc)
            cn90AnswerIError(ReplyData, ReplyLen, rc);
    }

    return rc;
}

tcn00_Error DBMSrvBHist_History::OpenFile(const tsp00_Pathc & FileType, tsp00_Int4 & File)
{
    tcn00_Error        rc=OK_CN00;
    tsp05_RteFileError RTEFileError;
    tsp00_Pathc        FileName;
    tsp05_RteFileInfo  RTEFileInfo;

    // Does the file exist? If not, can it be restored ?
    cn42GetFileName(VControl->dbname, FileType, FileName);

    sqlfinfoc(FileName, &RTEFileInfo, &RTEFileError);

    if(vf_ok==RTEFileError.sp5fe_result && !RTEFileInfo.sp5fi_exists)
    {
        cn42RestoreFile(VControl->dbname, FileType);
        sqlfinfoc(FileName, &RTEFileInfo, &RTEFileError);
    }

    if(vf_ok==RTEFileError.sp5fe_result)
    {
        if(!RTEFileInfo.sp5fi_exists)
            rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_NOHISTORY_CN00);
    }
    else
        rc=cn90AnswerRTEError(ReplyData, ReplyLen, ERR_RTE_CN00, RTEFileError.sp5fe_text, RTEFileError.sp5fe_text.length(), RTEFileError.sp5fe_result);

    // open file
    if(OK_CN00==rc)
    {
        sqlfopenc(FileName, sp5vf_text, sp5vf_read, sp5bk_buffered, &File, &RTEFileError);

        if(vf_ok!=RTEFileError.sp5fe_result)
            rc=cn90AnswerRTEError (ReplyData, ReplyLen, ERR_RTE_CN00, RTEFileError.sp5fe_text, RTEFileError.sp5fe_text.length(), RTEFileError.sp5fe_result);
    }

    return rc;
}

tcn00_Error DBMSrvBHist_History::ReadNextTextLine(tsp00_Int4 & File, char *& Line, size_t & LineLength, int & ReachedEndOfFile)
{
    tcn00_Error        rc=OK_CN00;
    tsp00_Longint      BytesRead=0;
    tsp05_RteFileError FileError;

    int    LineIncomplete=1;
    size_t Used=0;

    while(OK_CN00==rc && LineIncomplete)
    {
        sqlfreadc(File, Line+Used, LineLength-Used, &BytesRead, &FileError);

        if(vf_ok!=FileError.sp5fe_result)
        {
            LineIncomplete=0;   //no more data to read, end while loop

            if(vf_eof==FileError.sp5fe_result)
                ReachedEndOfFile=1;
            else
                rc=cn90AnswerRTEError(ReplyData, ReplyLen, ERR_RTE_CN00, FileError.sp5fe_text, FileError.sp5fe_text.length(), FileError.sp5fe_result);
        }
        else
        {
            if(sp5vfw_no_warning==FileError.sp5fe_warning)
                LineIncomplete=0; // we could read a complete line into Line without errors or warnings, end the while loop
            else
                if(sp5vfw_no_eol_found==FileError.sp5fe_warning)   //line was to big for current buffer
                {
                    char * BiggerLine=0;

                    if(!cn36_StrAlloc(BiggerLine, 2*LineLength))    //allocate more memory
                        rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
                    else
                    {
                        strcpy(BiggerLine, Line);   //copy the part of the line read so far
                    
                        cn36_StrDealloc(Line);         //forget Line and us BiggerLine as Line
                        Line=BiggerLine;
                        LineLength*=2;

                        Used=strlen(BiggerLine);
                    }
                }
                else    //we don't know about other warnings, report them as errors
                {
                    LineIncomplete=0;   //no more data to read, end while loop
                    rc=cn90AnswerRTEError(ReplyData, ReplyLen, ERR_RTE_CN00, FileError.sp5fe_text, FileError.sp5fe_text.length(), FileError.sp5fe_result);
                }
        }
    }

    return rc;
}

tcn00_Error DBMSrvBHist_History::ReadHistory(Tools_List<DBMSrvBHist_Backup *> & BackupList, char *& Line, size_t & LineLength)
{
    tcn00_Error   rc=OK_CN00;
    tsp00_Int4    HistoryFile;
    int           ReachedEndOfFile=0;

    rc=OpenFile(FGET_BACKHIST_CN42, HistoryFile);

    while(OK_CN00==rc && !ReachedEndOfFile)
    {
        rc=ReadNextTextLine(HistoryFile, Line, LineLength, ReachedEndOfFile);

        if(OK_CN00==rc && !ReachedEndOfFile && '\0'!=Line[0])   //ignore empty lines
        {
            DBMSrvBHist_Backup * ABackup=new DBMSrvBHist_Backup;

            if(0!=ABackup)
            {
                rc=ABackup->SetTo(Line, ReplyData, ReplyLen, ReplyLenMax);

                if(OK_CN00==rc && !BackupList.push_back(ABackup))               //try to add the pointer to the list
                    rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);

                if(OK_CN00!=rc)
                    delete ABackup; //could not add to the list, last chance to free the memory
            }
            else
                rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
        }
    }

    if(OK_CN00==rc)
        rc=CloseFile(HistoryFile, ReportError);
    else
        CloseFile(HistoryFile, DoNotReportError);

    return rc;
}

tcn00_Error DBMSrvBHist_History::ReadMediaHistory(Tools_List<DBMSrvBHist_Part *> & MediaList, char *& Line, size_t & LineLength)
{
    tcn00_Error   rc=OK_CN00;
    tsp00_Int4    MediaHistoryFile;
    int           ReachedEndOfFile=0;

    rc=OpenFile(FGET_BACKMDF_CN42, MediaHistoryFile);

    if(OK_CN00==rc)
    {
        while(OK_CN00==rc && !ReachedEndOfFile)
        {
            rc=ReadNextTextLine(MediaHistoryFile, Line, LineLength, ReachedEndOfFile);

            if(OK_CN00==rc && !ReachedEndOfFile && '\0'!=Line[0])   //ignore empty lines
            {
                DBMSrvBHist_Part * APart=new DBMSrvBHist_Part(DBMSrvBHist_Part::PartFromMediaFile);

                if(0!=APart)
                {
                    rc=APart->SetToMedium(Line, ReplyData, ReplyLen, ReplyLenMax);
                
                    if(OK_CN00==rc && !MediaList.push_back(APart))               //try to add the pointer to the list
                        rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);

                    if(OK_CN00!=rc)
                        delete APart; //could not add to the list, last chance to free the memory
                }
                else
                    rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
            }
        }

        if(OK_CN00==rc)
            rc=CloseFile(MediaHistoryFile, ReportError);
        else
            CloseFile(MediaHistoryFile, DoNotReportError);
    }
    else
        rc=OK_CN00; // if backup media history and its copy are not present -> it's no error, all media informations are simply unknown

    return rc;
}

tcn00_Error DBMSrvBHist_History::ReadEBIDHistory(Tools_List<DBMSrvBHist_Part *> & EBIDList, char *& Line, size_t & LineLength)
{
    tcn00_Error   rc=OK_CN00;
    tsp00_Int4    ExternalHistoryFile;
    int           ReachedEndOfFile=0;

    rc=OpenFile(FGET_BACKEBF_CN42, ExternalHistoryFile);

    if(OK_CN00==rc)
    {
        while(OK_CN00==rc && !ReachedEndOfFile)
        {
            rc=ReadNextTextLine(ExternalHistoryFile, Line, LineLength, ReachedEndOfFile);

            if(OK_CN00==rc && !ReachedEndOfFile && '\0'!=Line[0])   //ignore empty lines
            {
                DBMSrvBHist_Part * APart=new DBMSrvBHist_Part(DBMSrvBHist_Part::PartFromEBIDFile);

                if(0!=APart)
                {
                    DBMSrvMsg_Error error;

                    if(!APart->SetToEBID(Line, error))
                    {
                        DBMSrv_Reply reply(ReplyData, ReplyLen, ReplyLenMax);

                        if(error==DBMSrvMsg_Error::MEMORY)
                            rc=reply.startWithMessageList(error);
                        else
                        {   
                            SAPDBErr_MessageList ebfDirty(DBMSrv_DBMError(EBFDIRTY), 0);

                            ebfDirty.AppendNewMessage(error);
                            rc=reply.startWithMessageList(ebfDirty);
                        }
                    }

                    if(OK_CN00==rc && !EBIDList.push_back(APart))               //try to add the pointer to the list
                        rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);

                    if(OK_CN00!=rc)
                        delete APart; //could not add to the list, last chance to free the memory
                }
                else
                    rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
            }
        }

        if(OK_CN00==rc)
            rc=CloseFile(ExternalHistoryFile, ReportError);
        else
            CloseFile(ExternalHistoryFile, DoNotReportError);
    }
    else
        rc=OK_CN00; // if external backup history and its copy are not present -> it's no error, all EBIDs are simply unknown

    return rc;
}

tcn00_Error DBMSrvBHist_History::CloseFile(tsp00_Int4 File, CloseFileFlag ReportErrorFlag)
{
    tcn00_Error        rc=OK_CN00;
    tsp05_RteFileError RTEFileError;

    sqlfclosec(File, sp5vf_close_normal, &RTEFileError);

    if(RTEFileError.sp5fe_result!=vf_ok && ReportError==ReportErrorFlag)
        rc=cn90AnswerRTEError(ReplyData, ReplyLen, ERR_RTE_CN00, RTEFileError.sp5fe_text, RTEFileError.sp5fe_text.length(), RTEFileError.sp5fe_result);

    return rc;
}

enum DBMSrvBHist_History_Type{Data=0, Log=1};
enum DBMSrvBHist_History_ListType{Backups, Media, EBIDs, EBIDsTool};

tcn00_Error DBMSrvBHist_History::JoinHistoryWithMediaHistory(Tools_List<DBMSrvBHist_Part *> & MediaList)
{
    tcn00_Error rc=OK_CN00;

    Tools_List<DBMSrvBHist_Backup *>::reverse_iterator Backup=History.rbegin();
    Tools_List<DBMSrvBHist_Part *>::reverse_iterator   Medium=MediaList.rbegin();
    Tools_List<DBMSrvBHist_Part *>::iterator           MediumHelp;

    Tools_List<DBMSrvBHist_Backup *> UnresolvedBackups[2];
    Tools_List<DBMSrvBHist_Part *>   UnresolvedMedia[2];

    DBMSrvBHist_History_ListType CurrentList=Backups;

    while(OK_CN00==rc && (Backup!=History.rend() || Medium!=MediaList.rend()))
    {
        int FoundAMatch=0;

        //during one loop check the last backup against the unresolved media
        if(Backups==CurrentList && Backup!=History.rend())
        {
            DBMSrvBHist_History_Type Type=(*Backup)->IsALog()?Log:Data; //handle log backups separate from other entries

            Tools_List<DBMSrvBHist_Part *>::iterator UnresolvedMedium=UnresolvedMedia[Type].begin();

            while(OK_CN00==rc && !FoundAMatch && UnresolvedMedium!=UnresolvedMedia[Type].end()) //check backup against all unresolved backups
            {
                if((*Backup)->HasKeyAndLabel((*UnresolvedMedium)->GiveKey(), (*UnresolvedMedium)->GiveLabel())) //we found a medium matching Backup
                {
                    FoundAMatch=1;
                    UnresolvedBackups[Type].clear();                                              //all backups of same type and newer than Backup can never match anything anymore (dbm.knl+dbm.mdf are written strictly ordered by time for Log and for the rest)
                    //all newer medium information of same type can also not match anything (dbm.knl+dbm.mdf are written strictly ordered by time for Log and for the rest)
                    for(MediumHelp=UnresolvedMedia[Type].begin(); MediumHelp!=UnresolvedMedium; ++MediumHelp) //so delete all those non-matching media parts
                        delete (*MediumHelp);
                    UnresolvedMedia[Type].erase(UnresolvedMedia[Type].begin(), UnresolvedMedium); //and then throw away the corresponding (now illegal pointing) list elements

                    do //append all matching media (parallel backups, backups with replace) to Backup
                    {
                        rc=(*Backup)->AddPart(*UnresolvedMedium);

                        if(OK_CN00==rc)
                            UnresolvedMedium=UnresolvedMedia[Type].erase(UnresolvedMedium); //don't delete corresponding media part, because (*Backup) is now pointing to it
                        else
                            cn90AnswerIError(ReplyData, ReplyLen, rc);
                    }
                    while(OK_CN00==rc &&                                    //append as long as nothing went wrong ...
                          UnresolvedMedium!=UnresolvedMedia[Type].end() &&  //... and as long as there are unresolved backups
                          (*Backup)->HasKeyAndLabel((*UnresolvedMedium)->GiveKey(), (*UnresolvedMedium)->GiveLabel())); //and as long key and label still match
                }
                else
                    ++UnresolvedMedium; //it just did not match, have a look onto the next
            }

            if(OK_CN00==rc && !UnresolvedBackups[Type].push_back(*Backup)) //consider backup as unresolved because it could be one with a replacement
                rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);

            ++Backup;
        }

        //during another loop check the last medium against the unresolved backups
        if(Media==CurrentList && Medium!=MediaList.rend())
        {
            DBMSrvBHist_History_Type Type=(*Medium)->IsALog()?Log:Data; //determine type of backup (handle log backups separate from other entries)

            Tools_List<DBMSrvBHist_Backup *>::iterator UnresolvedBackup=UnresolvedBackups[Type].begin();

            while(OK_CN00==rc && !FoundAMatch && UnresolvedBackup!=UnresolvedBackups[Type].end())
            {
                if((*UnresolvedBackup)->HasKeyAndLabel((*Medium)->GiveKey(), (*Medium)->GiveLabel()))
                {
                    FoundAMatch=1;
                    UnresolvedBackups[Type].erase(UnresolvedBackups[Type].begin(), UnresolvedBackup); //all backups of the same type and newer than UnresolvedBackup can never match anything anymore (dbm.knl+dbm.mdf are written strictly ordered by time for Log and for the rest)
                    //all newer medium information of same type can also not match anything (dbm.knl+dbm.mdf are written strictly ordered by time for Log and for the rest)
                    for(MediumHelp=UnresolvedMedia[Type].begin(); MediumHelp!=UnresolvedMedia[Type].end(); ++MediumHelp) //so delete all those non-matching media parts
                        delete (*MediumHelp);
                    UnresolvedMedia[Type].clear(); //and then throw away the corresponding (now illegal pointing) list elements

                    do
                    {
                        rc=(*UnresolvedBackup)->AddPart(*Medium);

                        if(OK_CN00==rc)
                            Medium=MediaList.erase(Medium); //don't delete corresponding media part, because (*Backup) is now pointing to it or it was already deleted by AddPart
                        else
                            cn90AnswerIError(ReplyData, ReplyLen, rc);
                    }
                    while(OK_CN00==rc &&                     //append as long as nothing went wrong ...
                          Medium!=MediaList.rend() &&        //... and as long as there are unresolved backups
                          (*UnresolvedBackup)->HasKeyAndLabel((*Medium)->GiveKey(), (*Medium)->GiveLabel())); //and as long key and label still match
                }
                else
                    ++UnresolvedBackup; //it just did not match, have a look onto the next medium
            }

            if(OK_CN00==rc && !FoundAMatch)
            {
                do
                {
                    if(UnresolvedMedia[Type].push_back(*Medium))
                        Medium=MediaList.erase(Medium);
                    else
                        rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
                }
                while(OK_CN00==rc &&
                      Medium!=MediaList.rend() &&
                      (*Medium)->HasSameKeyAndLabelAs(*UnresolvedMedia[Type].back()));
            }
        }

        if(Backups==CurrentList)    //work alternating on backup and media lists
            CurrentList=Media;
        else
            CurrentList=Backups;
    }

    //get rid of all parts still unresolved (they never had a chance to show up in the backup list :( )
    for(MediumHelp=MediaList.begin(); MediumHelp!=MediaList.end(); ++MediumHelp)
        delete (*MediumHelp);

    for(MediumHelp=UnresolvedMedia[Data].begin(); MediumHelp!=UnresolvedMedia[Data].end(); ++MediumHelp)
        delete (*MediumHelp);

    for(MediumHelp=UnresolvedMedia[Log].begin(); MediumHelp!=UnresolvedMedia[Log].end(); ++MediumHelp)
        delete (*MediumHelp);

    return rc;
}

tcn00_Error DBMSrvBHist_History::JoinHistoryWithEBIDHistory(Tools_List<DBMSrvBHist_Part *> & EBIDList)
{
    tcn00_Error rc=OK_CN00;

    Tools_List<DBMSrvBHist_Backup *>::reverse_iterator Backup=History.rbegin();
    Tools_List<DBMSrvBHist_Part *>::reverse_iterator   EBID=EBIDList.rbegin();

    Tools_List<DBMSrvBHist_Backup *> UnresolvedBackups;
    Tools_List<DBMSrvBHist_Part *>   UnresolvedEBIDs;

    Tools_List<DBMSrvBHist_Backup *>::iterator LastDataMatch=UnresolvedBackups.end();

    DBMSrvBHist_History_ListType CurrentList=Backups;

    while(OK_CN00==rc && (Backup!=History.rend() || EBID!=EBIDList.rend()))
    {
        int FoundAMatch=0;

        if(Backups==CurrentList && Backup!=History.rend())
        {
            Tools_List<DBMSrvBHist_Part *>::iterator UnresolvedEBID=UnresolvedEBIDs.begin();

            while(OK_CN00==rc && !FoundAMatch && UnresolvedEBID!=UnresolvedEBIDs.end())
            {
                if((*Backup)->HasKeyAndLabel((*UnresolvedEBID)->GiveKey(), (*UnresolvedEBID)->GiveLabel())) //we found a EBID matching a Backup
                {
                    FoundAMatch=1;

                    if(((*Backup)->IsAData() || (*Backup)->IsAPages()) && LastDataMatch!=UnresolvedBackups.end())
                        UnresolvedBackups.erase(UnresolvedBackups.begin(), ++LastDataMatch);  //we can forget about any backups upto (including) the last matched data/pages backup
                        //we can not forget about anything in UnresovedEBIDs, because of the archive_stage-commands

                    //add the first match
                    rc=(*Backup)->AddPart(*UnresolvedEBID);

                    if(OK_CN00==rc)
                        UnresolvedEBID=UnresolvedEBIDs.erase(UnresolvedEBID);
                    else
                        cn90AnswerIError(ReplyData, ReplyLen, rc);

                    //search for other matches for the same backup (e.g. from other copies)
                    while(OK_CN00==rc &&                            //search as long as nothing went wrong ...
                          UnresolvedEBID!=UnresolvedEBIDs.end())   //... and as long as there are any unresolved EBIDs
                    {
                        if((*Backup)->HasKeyAndLabel((*UnresolvedEBID)->GiveKey(), (*UnresolvedEBID)->GiveLabel()))//find all, where kay and label match
                        {
                            rc=(*Backup)->AddPart(*UnresolvedEBID);

                            if(OK_CN00==rc)
                                UnresolvedEBID=UnresolvedEBIDs.erase(UnresolvedEBID);
                            else
                                cn90AnswerIError(ReplyData, ReplyLen, rc);
                        }
                        else
                            ++UnresolvedEBID; //ignore all non-matching Unresolved EBIDs
                    }

                    if(OK_CN00==rc && !UnresolvedBackups.push_back(*Backup))  //consider backup as unresolved, as parts of other copies may follow
                        rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);

                    if(OK_CN00==rc && ((*Backup)->IsAData() || (*Backup)->IsAPages()))
                        LastDataMatch=(--UnresolvedBackups.end());  //the newly added data backup is the last match
                }
                else
                    ++UnresolvedEBID; //it just did not match, have a look onto the next
            }

            if(OK_CN00==rc && !FoundAMatch && !UnresolvedBackups.push_back(*Backup))
                rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);

            ++Backup;
        }

        if(EBIDs==CurrentList && EBID!=EBIDList.rend())
        {
            Tools_List<DBMSrvBHist_Backup *>::iterator UnresolvedBackup=UnresolvedBackups.begin();

            while(OK_CN00==rc && !FoundAMatch && UnresolvedBackup!=UnresolvedBackups.end())
            {
                if((*UnresolvedBackup)->HasKeyAndLabel((*EBID)->GiveKey(), (*EBID)->GiveLabel()))
                {
                    FoundAMatch=1;

                    if(((*UnresolvedBackup)->IsAData() || (*UnresolvedBackup)->IsAPages()) && LastDataMatch!=UnresolvedBackups.end())
                        UnresolvedBackups.erase(UnresolvedBackups.begin(), ++LastDataMatch);  //we can forget about any backups upto (including) the last matched data/pages backup

                    do
                    {
                        rc=(*UnresolvedBackup)->AddPart(*EBID);

                        if(OK_CN00==rc)
                            ++EBID;
                        else
                            cn90AnswerIError(ReplyData, ReplyLen, rc);
                    }
                    while(OK_CN00==rc &&                                                                  //append as long as nothing went wrong ...
                          EBID!=EBIDList.rend() &&                                                        //... and as long as there are unresolved backups
                          (*UnresolvedBackup)->HasKeyAndLabel((*EBID)->GiveKey(), (*EBID)->GiveLabel())); //and as long key and label still match

                    if(OK_CN00==rc && ((*UnresolvedBackup)->IsAData() || (*UnresolvedBackup)->IsAPages()))
                        LastDataMatch=UnresolvedBackup;  //UnresolvedBackup is now the last match
                }
                else
                    ++UnresolvedBackup; //it just did not match, have a look onto the next medium
            }

            if(!FoundAMatch)
            {
                do
                {
                    if(!UnresolvedEBIDs.push_back(*EBID))
                        rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
                    
                    if(OK_CN00==rc)
                        ++EBID;
                }
                while(OK_CN00==rc &&
                      EBID!=EBIDList.rend() &&
                      (*EBID)->HasSameKeyAndLabelAs(*UnresolvedEBIDs.back()));
            }
        }

        if(Backups==CurrentList)    //work alternating on backup list and EBID list
            CurrentList=EBIDs;
        else
            CurrentList=Backups;
    }

    return rc;
}

tcn00_Error DBMSrvBHist_History::JoinEBIDHistoryWithToolData(const tcn35_BackupToolConnectorEnum   ToolType,
                                                             Tools_List<DBMSrvBHist_Part *>      & EBIDList,
                                                             Tools_List<DBMSrvBHist_Part *>      & EBIDsFromTool)
{
    tcn00_Error rc=OK_CN00;

    Tools_List<DBMSrvBHist_Part *>::reverse_iterator EBID=EBIDList.rbegin();
    Tools_List<DBMSrvBHist_Part *>::reverse_iterator ToolEBID=EBIDsFromTool.rbegin();

    Tools_List<DBMSrvBHist_Part *> UnresolvedEBIDs;
    Tools_List<DBMSrvBHist_Part *> UnresolvedToolEBIDs;

    DBMSrvBHist_History_ListType CurrentList=EBIDs;

    while(OK_CN00==rc && (EBID!=EBIDList.rend() || ToolEBID!=EBIDsFromTool.rend()))
    {
        int FoundAMatch=0;

        if(EBIDs==CurrentList && EBID!=EBIDList.rend())
        {
            if((*EBID)->GiveUsedBackupTool()==ToolType)
            {
                Tools_List<DBMSrvBHist_Part *>::iterator UnresolvedToolEBID=UnresolvedToolEBIDs.begin();

                while(OK_CN00==rc && !FoundAMatch && UnresolvedToolEBID!=UnresolvedToolEBIDs.end())
                {
                    if((*EBID)->HasSameEBIDAs(**UnresolvedToolEBID))
                    {
                        FoundAMatch=1;
                        (*EBID)->SetEBIDStatus(**UnresolvedToolEBID); //take over the determined status
                    
                        UnresolvedToolEBID=UnresolvedToolEBIDs.erase(UnresolvedToolEBID);   //that one is now resolved
                    }
                    else
                        ++UnresolvedToolEBID;
                }

                if(!FoundAMatch && !UnresolvedEBIDs.push_back(*EBID))
                    rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
            }

            ++EBID;
        }

        if(EBIDsTool==CurrentList && ToolEBID!=EBIDsFromTool.rend())
        {
            Tools_List<DBMSrvBHist_Part *>::iterator UnresolvedEBID=UnresolvedEBIDs.begin();

            while(OK_CN00==rc && !FoundAMatch && UnresolvedEBID!=UnresolvedEBIDs.end())
            {
                if((*UnresolvedEBID)->HasSameEBIDAs(**ToolEBID))
                {
                    FoundAMatch=1;
                    (*UnresolvedEBID)->SetEBIDStatus(**ToolEBID); //take over the determined status

                    UnresolvedEBID=UnresolvedEBIDs.erase(UnresolvedEBID);   //that one is now resolved
                }
                else
                    ++UnresolvedEBID;
            }

            if(!FoundAMatch && !UnresolvedToolEBIDs.push_back(*ToolEBID))
                rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);

            ++ToolEBID;
        }

        if(EBIDs==CurrentList)    //work alternating on the EBID list from history and the EBID list from the backup tool
            CurrentList=EBIDsTool;
        else
            CurrentList=EBIDs;
    }

    return rc;
}

tcn00_Error DBMSrvBHist_History::CheckAvailabilityOfExternalBackups(Tools_List<DBMSrvBHist_Part *> & EBIDList)
{
    tcn00_Error                     rc=OK_CN00;
    tcn35_BackupToolConnectorEnum * BackupToolType=0;
    const int                       NumberOfBackupTools=tcn35_BackupToolConnectorEnum::NumberOfBackupToolConnectors();

    //as we are now asking the backup tools, all EBIDs must change status from UNKOWN to UNAVAILABLE (because from most tools we get just a list of available backups)
    for(Tools_List<DBMSrvBHist_Part *>::iterator h=EBIDList.begin(); h!=EBIDList.end(); ++h)
        (*h)->SetEBIDStatusToUnavailable();

    BackupToolType=new tcn35_BackupToolConnectorEnum[NumberOfBackupTools+1];

    if(0==BackupToolType)
        rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
    else
    {
        int i;
        Tools_List<DBMSrvBHist_Backup *>::iterator Backup;

        for(i=0; i<NumberOfBackupTools; ++i)
            BackupToolType[i]=tcn35_BackupToolConnectorEnum(tcn35_BackupToolConnectorEnum::None);

        //determine the used backup tools (from the already joined history) and init BackupToolType with that information
        for(Backup=History.begin(); Backup!=History.end(); ++Backup)
            (*Backup)->MarkUsedExternalBackupTools(BackupToolType);

        //determine the available backups for every used external backup tool
        //and check against all backups known through pHistoryEBIDs
        for(i=0; i<NumberOfBackupTools; ++i)
            if(BackupToolType[i]!=tcn35_BackupToolConnectorEnum::None)
            {
                tcn35b_DBMExternalBackupController * CurrentTool=0;

                rc=TheDBMExternalBackupControllerFactory_cn35b.ConstructADBMExternalBackupController(CurrentTool,
                                                                                                     ReplyData,
                                                                                                     ReplyLen,
                                                                                                     tcn35d_BackupInfo::GiveBackupInfo,
                                                                                                     BackupToolType[i],
                                                                                                     VControl->dbname);

                if(OK_CN00==rc)   //BackupController was constructed successfully
                {
                    DBMSrvBHist_EBIDList AvailableEBIDs;

                    if(!CurrentTool->ListEBIDsOfAvailableBackups(AvailableEBIDs))   // do not use CurrentTool->GiveLastError() if Tool is not installed anymore
                        rc=CurrentTool->GiveLastError();                            // do not ignore "real" errors

                    SetVControlAndReplyBuffer(VControl, ReplyData, ReplyLen, ReplyLenMax, 1); //reset the error handler changed by the backup controller object

                    if(OK_CN00==rc)                //all ok
                        rc=JoinEBIDHistoryWithToolData(BackupToolType[i], EBIDList, AvailableEBIDs.GiveList());
                }
                //else    //error was already reported

                if(0!=CurrentTool)
                    delete CurrentTool;

                SetVControlAndReplyBuffer(VControl, ReplyData, ReplyLen, ReplyLenMax, 1); //reset the error handler changed by the backup controller object
            }
            
        delete [] BackupToolType; // free the allocated memory
    }

    return rc;
}

tcn00_Error DBMSrvBHist_History::CheckHistoryFile(bool & Exists, char Date[64])
{
    tcn00_Error          rc=OK_CN00;

    tsp05_RteFileError   rteFileError;
    tsp00_Pathc          szFile;
    tsp05_RteFileInfo    rteFileInfo;

    char               * szPos=0;

    Exists=false;

    // exists history file ?
    cn42GetFileName(VControl->dbname, FGET_BACKHIST_CN42, szFile);
    sqlfinfoc(szFile, &rteFileInfo, &rteFileError);

    //if it does not exist, try to "restore" it from the copy <dbname>.knl
    if(vf_ok==rteFileError.sp5fe_result && !rteFileInfo.sp5fi_exists)
    {
        cn42RestoreFile(VControl->dbname, FGET_BACKHIST_CN42);
        sqlfinfoc(szFile, &rteFileInfo, &rteFileError);
    }

    if(vf_ok==rteFileError.sp5fe_result)
    {
        if(rteFileInfo.sp5fi_exists)
        {
            Exists=true;

            if(64>sizeof(rteFileInfo.sp5fi_date_modified)+sizeof(rteFileInfo.sp5fi_time_modified))
            {
                memcpy(Date, rteFileInfo.sp5fi_date_modified, sizeof(rteFileInfo.sp5fi_date_modified));
                Date+=sizeof(rteFileInfo.sp5fi_date_modified);

                memcpy(Date, rteFileInfo.sp5fi_time_modified, sizeof(rteFileInfo.sp5fi_time_modified));
                Date[sizeof(rteFileInfo.sp5fi_time_modified)]='\0';
            }
            else
                rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_CN00);
        }
        else
            rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_NOHISTORY_CN00);
    }
    else
        rc=cn90AnswerRTEError(ReplyData, ReplyLen, ERR_RTE_CN00, rteFileError.sp5fe_text, rteFileError.sp5fe_text.length(), rteFileError.sp5fe_result);

    return rc;
}

void DBMSrvBHist_History::SetVControlAndReplyBuffer(VControlDataT * NewVControl,
                                                    char          * NewReplyData,
                                                    int           * NewReplyLen,
                                                    int             NewReplyLenMax,
                                                    int             ResetErrorHandler)
{
    VControl   =NewVControl;
    ReplyData  =NewReplyData;
    ReplyLen   =NewReplyLen;
    ReplyLenMax=NewReplyLenMax;

    if(ResetErrorHandler)
    {
        tcn35e_DBMBackupControllerErrorHandler::BasicErrorHandler_cn35e.SetAsCurrentErrorHandler();
        tcn35e_DBMBackupControllerErrorHandler::BasicErrorHandler_cn35e.SetReplyArrayTo(0, 0);
    }
}

tcn00_Error DBMSrvBHist_History::FindFirstRestoreItem()
{
    tcn00_Error rc=OK_CN00;
    
    Tools_List<DBMSrvBHist_Backup*>::reverse_iterator PMatch;

    FirstBackup=History.end();

    if(0==strcmp(Restore, DBMSrvBHist_History_RES_ALL))    // list all possible data saves
    {
        // oldest possible data save depends from restart info
        //   - if there is a valid restart info, use only data saves after the last "HISTLOST"
        //     if there is no data save after that last "HISTLOST", use all data saves
        //   - if there is no valid restart info, use all data saves
        if(DBMSrvBHist_History_NOLOG!=FirstLogPage && UsedLogPage>=FirstLogPage)
        {
            // search the last "HISTLOST" (backward)
            for(PMatch=History.rbegin(); PMatch!=History.rend() && !((*PMatch)->IsAHistLost()); ++PMatch);

            // search the first data save after the last histlost (forward)
            if(PMatch!=History.rend())
                for(FirstBackup=PMatch; FirstBackup!=History.end() && !((*FirstBackup)->IsADataBackup()); ++FirstBackup);

            if(PMatch==History.rend() || FirstBackup==History.end())    //no "HISTLOST" or no data save after the last "HISTLOST"
                FirstBackup=History.begin();                            //shit! force usage of all data saves
        }
        else    //no valid restart info
            FirstBackup=History.begin();  //force usage of all data saves

        ForRestore=false; // disable restoremanager (this action is only a datasave listing with a specific start position)

        if(!cn36_StrAllocCpy(Label, DBMSrvBHist_Backup::DBMSrvBHist_Backup_LBLDAT) ||
           !cn36_StrAllocCpy(Action, DBMSrvBHist_Backup::DBMSrvBHist_Backup_ACTSAV)  )
        {
            rc=ERR_MEM_CN00;
        }
    }
    else
    {
        if(0==strcmp(Restore, DBMSrvBHist_History_RES_LST)) // search the last data save (backward)
        {
            for(PMatch=History.rbegin(); PMatch!=History.rend(); ++PMatch)
            {
                if((*PMatch)->IsAHistLost())
                    FirstLogPage=DBMSrvBHist_History_NOLOG; // HISTLOST passed -> maked restartinfo invalid

                if((*PMatch)->IsADataBackup() &&                            //we are looking for the newest complete data backup ...
                   (*PMatch)->WasSuccessful() &&                                    //...that was successful (TODO: change to successful and available?)...
                   (0==strcmp(Until, DBMSrvBHist_History_UNT_ALL) ||                //... and unless Until equals "*" ...
                    0<strncmp(Until, (*PMatch)->GiveTimeStamp(), strlen(Until))))   //... TimeStamp of the data backup must be a point in time before Until
                {
                    break;  //found the successful data backup we were looking for
                }
            }

            // view only save actions
            if(!cn36_StrAllocCpy(Action, DBMSrvBHist_Backup::DBMSrvBHist_Backup_ACTSAV))
                rc=ERR_MEM_CN00;
        }
        else
            if(0==strcmp(Restore, DBMSrvBHist_History_RES_CNT)) // search for continue restore
            {
                Tools_List<DBMSrvBHist_Backup*>::reverse_iterator PRestore;
                Tools_List<DBMSrvBHist_Backup*>::reverse_iterator PSave;
                Tools_List<DBMSrvBHist_Backup*>::reverse_iterator PTemp;

                if(FirstLogPage>UsedLogPage && !Restartable)    // is the first log higher than the used log?
                {
                    PMatch=History.rend();
                    PSave=History.rend();

                    // looking for the last successful restore pages or restore data
                    for(PRestore=History.rbegin(); PRestore!=History.rend(); ++PRestore)
                    {
                        if(((*PRestore)->IsADataRestore() || (*PRestore)->IsAPagesRestore()) &&
                           (*PRestore)->WasSuccessful())
                        {
                            break;
                        }
                    }

                    // find the matching save item to the restore
                    if(PRestore!=History.rend())
                    {
                        for(PSave=PRestore, ++PSave; PSave!=History.rend(); ++PSave)
                        {
                            if((*PSave)->EqualsInLabelWith(*PRestore) &&
                               (*PSave)->EqualsInDBStamp1With(*PRestore) &&
                               (*PSave)->EqualsInDBStamp2With(*PRestore) &&
                               (*PSave)->WasSuccessful())
                            {
                                break;
                            }
                        }
                    }

                    // look for logs before the save
                    if(PSave!=History.rend())
                    {
                        for(PTemp=PSave, ++PTemp; PTemp!=History.rend(); ++PTemp)
                        {
                            if((*PTemp)->IsALogBackup())
                            {
                                if((*PSave)->GiveStartLogPage() < (*PTemp)->GiveStopLogPage())
                                    PMatch=PTemp;
                                else
                                    break;
                            }
                            else
                                if((*PTemp)->IsAHistLost()) // break at HISTLOST
                                    break;
                        }

                        if(PMatch==History.rend())  //no log is in front of the restored save
                        {
                            PMatch=PSave;
                            --PMatch;        //position to one entry after the save (this is at least the restore PRestore)
                        }
                    }
                }
                else
                    PMatch=History.rend();
            }
            else
            {   // search a successful data save with the key specified in Restore (search backward)
                for(PMatch=History.rbegin(); PMatch!=History.rend(); ++PMatch)
                {
                    if((*PMatch)->IsAHistLost())
                        FirstLogPage=DBMSrvBHist_History_NOLOG; // HISTLOST passed -> maked restartinfo invalid

                    if((*PMatch)->HasKey(Restore) &&    // check key
                       (*PMatch)->WasSuccessful()   )   // use successful backups only
                    {
                        break;
                    }
                }

                if(PMatch!=History.rend() &&            //we found a match ...
                   !(*PMatch)->IsADataBackup()) //but it is not a complete data backup
                {
                    rc=ERR_PARAM_CN00;
                }

                if(!cn36_StrAllocCpy(Action, DBMSrvBHist_Backup::DBMSrvBHist_Backup_ACTSAV))    // view only save actions
                    rc=ERR_MEM_CN00;
            }

        if(PMatch!=History.rend())
            FirstBackup=PMatch;   //otherwise FirstBackup==History.end() which forces no output in BasicListHistory
    }

    return rc;
}

tcn00_Error DBMSrvBHist_History::CreateOutputList()
{
    tcn00_Error rc=OK_CN00;

    bool GetLine=false;
    bool FirstDataSavePassed=false;
    bool SecondDataSavePassed=false;

    Tools_List<DBMSrvBHist_Backup*>::iterator Log=History.end();
    Tools_List<DBMSrvBHist_Backup*>::iterator Data=History.end();
    Tools_List<DBMSrvBHist_Backup*>::iterator TheData=History.end();

    Tools_List<DBMSrvBHist_Backup*>::iterator Backup=FirstBackup;

    //empty any previous output list and adjust the iterators pointing into it
    OutputList.clear();
    EndBackup=StartBackup=CurrentBackup=OutputList.end();

    if(Backup!=History.end())
    {
        // prepare continue restore
        if(ForRestore &&
           !(*Backup)->IsAData())
        {
            FirstDataSavePassed = true;
        }

        do
        {
            GetLine=true;

            // check the key (full match)
            if(0!=strcmp(Key, DBMSrvBHist_History_KEY_ALL) &&   //if a key was specified
               !(*Backup)->HasKey(Key)                        ) //and current backup line has another key
            {
                GetLine=false;  //ignore the backup line, as key doesn't match
            }

            // check the label (pattern match <label>*) (execpt it is a history line)
            if(0!=strcmp(Label, DBMSrvBHist_History_LBL_ALL) &&
               !(*Backup)->MatchesLabel(Label) &&
               !(*Backup)->IsAHistLost()                        )
            {
                GetLine=false;  // label doesn't match
            }

            // check the action (pattern match <action>*) (execpt it is a history line)
            if(0!=strcmp(Action, DBMSrvBHist_History_ACT_ALL) &&
               !(*Backup)->MatchesAction(Action) &&
               !(*Backup)->IsAHistLost()                        )
            {
                GetLine=false;// action doesn't match
            }

            // check the until timestamp (pattern match <timestamp>*)
            if(0!=strcmp(Until, DBMSrvBHist_History_UNT_ALL) && 
               strncmp(Until, (*Backup)->GiveTimeStamp(), strlen(Until))<0)  //Until < (*Backup)->GiveTimeStamp()
            {
                GetLine=false;// timestamp doesn't match (backup created after Until time)
            }

            // check existing log against log save when restore manager is active
            if(ForRestore && 
               (*Backup)->IsALog() &&
               FirstLogPage!=DBMSrvBHist_History_NOLOG &&
               FirstLogPage <= (*Backup)->GiveStartLogPage())
            {
                GetLine=false;
            }

            // skip logsaves during restore manager not according to the first datasave 
            if(ForRestore && 
               TheData!=History.end() &&    //TheData is set and pointing to the first passed complete data backup
               (*Backup)->IsALog())
            {
                if((*TheData)->GiveStartLogPage() > (*Backup)->GiveStopLogPage())
                    GetLine=false;  //current log backup is not needed, as it contains only information from before the data backup

                if(FirstLogPage!=DBMSrvBHist_History_NOLOG &&       //if there is log on the log volume and
                   (*TheData)->GiveStartLogPage() >= FirstLogPage)  //start log page of the data backup is larger than first log page of log volume
                {
                    GetLine=false;  //none of the log backups, including the current one, is needed
                }
            }

            // check rc 
            if(RcOKOnly && 
               !(*Backup)->WasSuccessful())
            {
                GetLine=false;  //rc does not match
            }

            // skip datasaves during restore manager
            if(ForRestore &&                            //restore manager
               Data==History.end() &&                   //no complete data backup to take into account
               (*Backup)->IsAData())
            {
                if(FirstDataSavePassed)
                {
                    GetLine=false;
                    SecondDataSavePassed=true;
                }
                else
                {
                    Tools_List<DBMSrvBHist_Backup*>::reverse_iterator Temp;

                    FirstDataSavePassed=true;
          
                    Data   =Backup;
                    TheData=Backup;

                    // look for logs before
                    for(Temp=Backup, ++Temp; Temp!=History.rend(); Temp!=History.rend()?++Temp:Temp)
                    {
                        if((*Temp)->IsALog())
                        {
                            if((*Data)->GiveStartLogPage()<(*Temp)->GiveStopLogPage())
                                Log=Temp;
                            else
                                Temp=History.rend();    //end the loop because (*Temp) contains only log from "before" (*Data)
                        }
                        else
                            if((*Temp)->IsAHistLost())
                                Temp=History.rend();    //end the loop at a HISTLOST
                    }

                    if(Log==History.end())  //if no log is before the data backup...
                        Data=History.end(); //forget about the data backup
                }
            }

            // reaching the data save while using logs before data save in restore mode
            if(ForRestore &&
               Data!=History.end() &&
               Log==History.end() &&
               Data==Backup)
            {
                GetLine=false;
                Data=History.end();
            }

            // while using logs before data save in restore mode ignore all saves except logs
            if(ForRestore &&
               Data!=History.end() &&
               Log==History.end() &&
               !(*Backup)->IsALog())
            {
                GetLine=false;
            }

            // skip pagesaves (<-skiping any pages also restores) during restore manager not according to the first datasave
            if(ForRestore &&
               SecondDataSavePassed &&
               (*Backup)->IsAPages())
            {
                GetLine=false;
            }

            // skip everything, that is not a SAVE during restore manager
            if(ForRestore &&
               !(*Backup)->IsABackup())
            {
                GetLine=false;
            }

            //if the line is needed, add it to the OutputList
            if(GetLine &&
               !OutputList.push_back(*Backup))
            {
                rc=cn90AnswerIError(ReplyData, ReplyLen, ERR_MEM_CN00);
            }

            if(OK_CN00==rc)
            {
                // in restore mode use logs before data save
                if(ForRestore && Log!=History.end())
                {
                    Backup=Log;
                    Log=History.end();
                }
                else
                    Backup++;

                // restore manager aborts at next HISTLOST
                if(ForRestore && Backup!=History.end() && (*Backup)->IsAHistLost())
                    Backup=History.end();
            }
        }
        while(OK_CN00==rc && Backup!=History.end());
    }

    if(OK_CN00==rc)
    {
        if(ListInverted)
        {
            StartBackup=CurrentBackup=OutputList.rbegin();
            EndBackup=OutputList.rend();
        }
        else
        {
            StartBackup=CurrentBackup=OutputList.begin();
            EndBackup=OutputList.end();
        }
    }

    return rc;
}

tcn00_Error DBMSrvBHist_History::BasicListHistory()
{
    tcn00_Error rc=OK_CN00;

    char          * CurrPosInReplyData;
    char          * FlagPos;

    bool Full=false;
    Tools_List<DBMSrvBHist_Backup*>::iterator firstEntry=CurrentBackup;

    sprintf(ReplyData, "%s%s", ANSWER_OK_CN00, LINE_SEPSTRING_CN00);
    CurrPosInReplyData=ReplyData+strlen(ReplyData);

    sprintf(CurrPosInReplyData, "%s", CONT_FLAG_CONTINUE);
    FlagPos=CurrPosInReplyData;
    CurrPosInReplyData+=strlen(CurrPosInReplyData);

    while(CurrentBackup!=EndBackup && !Full)
    {
        //try to write information about (*CurrentBackup) into the reply buffer
        Full=PrintCurrentBackupInto(CurrPosInReplyData, ReplyLenMax-strlen(ReplyData)-1);

        if(!Full)
        {
            CurrPosInReplyData=ReplyData+strlen(ReplyData);

            if(ListInverted)
                CurrentBackup--;
            else
                CurrentBackup++;
        }
        else
            *CurrPosInReplyData='\0'; //ignore everything what was written for the current entry, because not all the information could be written
    }

    if(CurrentBackup==EndBackup)
        strncpy(FlagPos, CONT_FLAG_END, strlen(CONT_FLAG_END));
    else
    {
        if(firstEntry==CurrentBackup) //we have not moved forward a single backup :(, some clients could loop endless in this situation
        {
            DBMSrvMsg_Error err(DBMSrvMsg_Error(DBMSrvMsg_Error::COMM_PACKTOOSMALL, SAPDB_ToStringClass(ReplyLenMax)));

            err.AppendNewMessage(DBMSrvMsg_Error(
                                    DBMSrvMsg_Error::BHIST_TOOMUCHDATA,
                                    (*CurrentBackup)->GiveKey(),
                                    (*CurrentBackup)->GiveLabel()));
            
            rc=DBMSrv_Reply(ReplyData, ReplyLen, ReplyLenMax).startWithMessageList(err);
        }
        else
            strncpy(FlagPos, CONT_FLAG_CONTINUE, strlen(CONT_FLAG_CONTINUE));
    }

    return rc;
}

bool DBMSrvBHist_History::PrintCurrentBackupInto(char * Buffer, size_t MaxUsableBufferSize)
{
    bool Full;

    Full=(*CurrentBackup)->PrintBackupInto(Buffer, MaxUsableBufferSize, Columns, ColumnWidthsBackup);

    if(!Full)
        Full=(*CurrentBackup)->PrintMediaAndEBIDsInto(Buffer, MaxUsableBufferSize, WithMedia, WithEBIDs, ColumnWidthsMedia);

    return Full;
}

void DBMSrvBHist_History::EnlargeColumnWidths()
{
    for(Tools_List<DBMSrvBHist_Backup*>::iterator h=History.begin(); h!=History.end(); ++h)
        (*h)->EnlargeColumnWidths(Columns, ColumnWidthsBackup, ColumnWidthsMedia);
}

void DBMSrvBHist_History::IfRCOKPrintOKIntoReply(tcn00_Error Status)
{
    if(OK_CN00==Status)
        sprintf(ReplyData, "%s%s", ANSWER_OK_CN00, LINE_SEPSTRING_CN00);

    *ReplyLen=int(strlen(ReplyData));
}
