Function Replication_Services(@Service, @Params)
/***********************************************************************************************************************

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   Replication_Services

    Description :   Handler program for all module related services.

    Notes       :   The generic parameters should contain all the necessary information to process the services. Often
                    this will be information like the data Record and Key ID.

    Parameters  :
        Service         [in] -- Name of the service being requested
        Param1-10   [in/out] -- Additional request parameter holders
        Response       [out] -- Response to be sent back to the Controller (MCP) or requesting procedure

    History     :   (Date, Initials, Notes)
        01/11/20    dmb     Original programmer.
        01/11/20    dmb     Update AddToReplicationQueueTable to bypass suspended tables.
        01/11/20    dmb     Update ReplicateTransactions to detect when new tables are being processed within the same
                            queue and end the process. This maintains the principle of one queue being processed at any
                            time by an engine.
        01/11/20    dmb     Add GetSRPEngineServerINI and SetSRPEngineServerINI services.
        01/11/20    dmb     Add CreateMasterReplicationQueue service.
        01/12/16    dmb     Add SetSourceREVBOOTPath, GetSourceREVBOOTPath, SetTargetREVBOOTPath, and
                            GetTargetREVBOOTPath services.
        01/12/20    dmb     Add SetDailyNotificationEmails, GetDailyNotificationEmails, SetEmergencyNotificationEmails,
                            GetEmergencyNotificationEmails, SetSMTPSettings, and GetSMTPSettings services.
        01/12/20    dmb     Obsolete SendAlert service. Add SendEmergencyAlert service.
        01/12/20    dmb     Update ReplicateTransactions service to replicate object code along with source code if
                            it exists.
        01/12/20    dmb     Update ReplicateTransactions service to properly alias the target SYSREPOS table and update
                            the Repository pointers as needed.
        02/24/20    dmb     Add Push.Select and Pop.Select calls to the GetReplicationQueueTableHandle service to
                            avoid unintentional clearing of @LIST_ACTIVE.
        02/24/20    dmb     If Attach_Table fails in CreatePrivateReplicationQueue, check to see if it is not an SSP280
                            error before assuming this means the table doesn't exist.
        02/24/20    dmb     If Attach_Table fails in CreateMasterReplicationQueue, check to see if it is not an SSP280
                            error before assuming this means the table doesn't exist.
        02/24/20    dmb     Swap @VM with CRLF$ in the FileError variable for the SendEmergencyAlert service.
        02/24/20    dmb     Add additional flag in GetReplicationQueueTableHandle to track whether a specific queue is
                            already known to exist or not exist. This prevents the service from attempting to attach
                            queues unnecessarily and slow down performance.
        02/24/20    dmb     Refactor entire service module using Enhanced BASIC+ syntax.
        02/26/20    dmb     Update AddToReplicationQueueTable to ignore %RECORDS% Key IDs.
        02/26/20    dmb     Add GetReplicationQueueTableStatus and GetAllReplicationQueueTableStatus services.
        02/26/20    dmb     Add ResetReplicationQueueCounters service.
        02/27/20    dmb     Update ReplicateTransactions service to allow for the possibility that a pending transaction
                            in a queue might not be available. Insstead of issuing a warning and aborting the process,
                            just flag this as an IGNORE transaction.
        02/27/20    dmb     Update ReplicateTransactions service to support the calling of another stored procedure
                            to handle the replication process. Used for alternative replication processes, such as when
                            the target is a SQL server.
        02/27/20    dmb     Update ReplicateTransactions service to detach the source volume as strored in the transaction
                            ID before attempting to attach the resolved source volume. This will help to avoid FS404
                            errors in cases where the volume was already attached by the application.
        02/28/20    dmb     Update ReplicateTransactions service to track source volume changes and set the NewTable
                            flag accordingly. This addresses the issue where a table of the same name and same
                            Database ID exists (albeit one is aliased). They will both write to the same private queue
                            so the source volume must be used to detect a change in the actual source table.
        03/01/20    dmb     Update the CreateMasterReplicationQueue service so the HaveQueueTable variable defaults
                            to false.
        03/08/20    dmb     Update the GetReplicationQueueTables service so Set_Status(0) is called to avoid hearing
                            engine dings if there are no tables in the QueuePath passed into the List_Volume_Sub
                            subroutine.
        03/12/20    dmb     Do not set an error if paths are missing from these services: SetSourceServerPath,
                            SetSourceREVBOOTPath, SetSourceQueuePath, SetTargetServerPath, SetTargetREVBOOTPath, and
                            SetTargetQueuePath.
        03/12/20    dmb     Do not set an error in the SetMaxTransactionsCount if the MaxTransactionsCount argument
                            is empty. Only set an error if the value is non-numeric.
        03/12/20    dmb     Allow the SetDailyNotificationEmails and SetEmergencyNotificationEmails services to pass in
                            an empty argument. This will clear out emails.
        03/28/20    dmb     Update the SetSRPEngineServerINI service to support JSON content rather than @FM/@VM content
                            for the INI data.
        03/28/20    dmb     Update the GetSRPEngineServerINI service to return JSON content rather than @FM/@VM content.
        03/28/20    dmb     Add the GetSRPEngineServerTitle service.
        03/29/20    dmb     Add the GetSRPEngineServerExeName service. Update the SetSRPEngineServerINI,
                            GetSRPEngineServerINI, and GetSRPEngineServerTitle services to use this service.
        08/02/20    dmb     Update the ReplicateTransactions service so that if the called Replication Procedure has an
                            error the SendEmergencyAlert service will get called.
        08/02/20    dmb     Update the SendEmergencyAlert service to use the new SMTP Sent From parameter.
        08/31/20    dmb     Update the GetReplicationQueueTableHandle service to check Get_Status after the Fix_LH
                            subroutine is called.
        09/09/20    dmb     Deprecate and remove the ProcessTransactions service.
        09/09/20    dmb     Update the AddToReplicationQueueTable service to not call the GetDisabledTableFlag service.
                            It is now the responsibility of the caller (e.g., PROMOTED_WRITE_RECORD_ACTION) to verify
                            that the table is allowed to replicate.
        09/09/20    dmb     Deprecate and remove the GetDisabledTableFlag service. This has been superceded by the
                            IsTableAllowedToQueue and IsTableAllowedToReplicate services.
        09/09/20    dmb     Deprecate and remove the AppendToPendingQueue service. This was an artifact of the logic
                            to manage OS file based queues.
        09/09/20    dmb     Deprecate and remove the SetReplicationVolumes and GetReplicationVolumes services.
        09/09/20    dmb     Deprecate and remove the GetInProcessQueueFiles and GetInProcessQueueFile services.
        09/09/20    dmb     Update the GetReplicationQueueTableStatus to include the %NumProcess% counter.
        09/12/20    dmb     Deprecate the IsTableIncludedForReplication service and replace it with the
                            IsReplicationTable service.
        09/12/20    dmb     Added the GetVolumeTables and HasBaseMFSInstalled services.
        09/12/20    dmb     Deprecate the CreateMasterReplicationQueue service and replcate it with the
                            CreateReplicationQueue service.
        09/12/20    dmb     Deprecate and remove the GetPendingQueueFiles and GetPendingQueueFile services.
        09/12/20    dmb     Update the GetReplicationQueueTable service to use the GetTableQueueType service.
        09/12/20    dmb     Deprecate the CreatePrivateReplicationQueue service and replace it with the
                            CreateReplicationQueue service.
        09/12/20    dmb     Deprecate the DeletePrivateReplicationQueue service and replace it with the
                            DeleteReplicationQueue service.
        09/13/20    dmb     Add QueueTable argument to the GetReplicationQueueTableHandle service.
        09/13/20    dmb     Update the ReplicateTransactions service to handle tables that are suspended from being
                            replicated. Introduce a new transaction ID action of SKIP. This is similar to IGNORE, but
                            SKIP IDs won't be deleted since they are still valid.
        09/14/20    dmb     Update the AddBaseMFSToTable, RemoveBaseMFSFromTable, and HasBaseMFSInstalled services
                            to always use SYSPROG as the database when calling Alias_Table for REVMEDIA.
        09/14/20    dmb     Add the SetErrorLogPath, GetErrorLogPath, and IsLoggingEnabled services.
        09/14/20    dmb     Add the LogError service. Update the ReplicateTransactions service to call the LogError
                            service (if logging is enabled) whenever the SendEmergencyAlert service is called.
        09/23/20    dmb     Update the ResetReplicationQueueCounters service to use the same updates that were already
                            added to the GetReplicationQueueTableStatus service.
        09/23/20    dmb     Update the ReplicateTransactions service to verify if the private queue is for a table
                            that is allowed to replicate before processing the transactions in the queue. This will
                            avoid unnecessary checks against all transactions.
        10/17/20    dmb     Remove all hardcoded references to the SYSENV table and use the RepConfigTable$ equate
                            instead. This will allow users to define the table that should be used.
        10/18/20    dmb     Update the LogError and SendEmergencyAlert services to retreive the current Queue Table and
                            Engine Name and include this in the information being submitted.
		02/23/21	dmb		Update the ReplicateTransactions service to attach all tables rather than just the table
							being replicated so symbolics will function properly. Set a memory flag to avoid constant
							attach processes.

***********************************************************************************************************************/

#pragma precomp SRP_PreCompiler

$insert LOGICAL
$insert SERVICE_SETUP
$insert SRPMAIL_INSERTS

Equ     CRLF$ to \0D0A\
Equ     Tab$  to \09\


// The default table to store configuration information is SYSENV, but this equate can be changed to point to another
// table.
*Equ RepConfigTable$ to 'SYSENV'
Equ RepConfigTable$ to 'REP_MAN_CONFIG'

// Equates that define the <RepConfigTable>\REPLICATION_TABLES structure.
Equ RepTabTableName$  to 1
Equ RepTabDatabaseID$ to 2
Equ RepTabVolume$     to 3
Equ RepTabHasBaseMFS$ to 4
Equ RepTabCanQueue$   to 5
Equ RepTabCanRep$     to 6
Equ RepTabQueueType$  to 7

Common /ReplicationServices/ QueueTableList@, QueueHandleList@, QueueTableExists@, Unused4@, Unused5@, Unused6@, Unused7@, Unused8@

Declare function    Replication_Services, Memory_Services, GetTickCount, RTI_OS_Directory, SRP_Path, Get_LH_Info
Declare function    SRP_Encode, SRP_Decode, List_Volume_Sub, SRP_Sort_Array, GetCommandLine, UCase, SRP_Stopwatch
Declare function    Environment_Services
Declare function    SRP_Rotate_Array, SRP_Send_Mail, Get.RecCount, SRP_JSON, Database_Services, SRP_Array, Logging_Services
Declare subroutine  Replication_Services, Memory_Services, Fix_LH, Set_Status, Alias_Table, Attach_Table, Detach_Volume
Declare subroutine  SRP_Stopwatch, Clear_Table, Detach_Table, Create_Table, Delete_Table, Send_Info, Repos_Attach_Trans
Declare subroutine  Repos_Detach_Trans, Push.Select, Pop.Select, SRP_JSON, Database_Services, Logging_Services

Declare subroutine  WritePrivateProfileString, obj_Notes
Declare subroutine  GetPrivateProfileSection, GetPrivateProfileString

// SRP HashTable declarations.
Declare function    SRP_HashTable_Create, SRP_HashTable_Contains, SRP_HashTable_Count, SRP_HashTable_Get
Declare function    SRP_HashTable_GetKeys, SRP_HashTable_GetValues, SRP_HashTable_GetKeyValuePairs
Declare subroutine  SRP_HashTable_Set, SRP_HashTable_Release, SRP_HashTable_Remove

// SRP List declarations.
Declare function    SRP_List
Declare subroutine  SRP_List

LogPath     = Environment_Services('GetApplicationRootPath') : '\LogFiles\Replication'
LogDate     = Oconv(Date(), 'D4/')
LogTime     = Oconv(Time(), 'MTS')
LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' Delete Error Log.csv'
Headers     = 'Logging DTM' : @FM : 'Lookup Key ID' : @FM : 'Queue Table Handle'
objLog      = Logging_Services('NewLog', LogPath, LogFileName, CRLF$, Comma$, Headers, '', False$, False$)
LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM

GoToService else
    Error_Services('Set', Service : ' is not a valid service request within the ' : ServiceModule : ' services module.')
end

Return Response else ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN     = True$, False$


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//----------------------------------------------------------------------------------------------------------------------
// AddToReplicationQueueTable
//
// TransactionID - The transaction ID that needs to be added to the queue. Note, this is not a Key ID to a database row.
// Rather, it is a meaningful identifier. See the notes below. - [Required]
//
// Adds the specified transaction ID to the replication queue table for eventual processing. The transaction ID must
// contain all necessary components for a replication action to succeed and follow this format:
//
//  <1> - Action (i.e., WRITE, DELETE, CLEARFILE)
//  <2> - Application (OpenInsight application that owns the database table.)
//  <3> - Volume (Volume path, mapped drive or UNC, where the database table live.)
//  <4> - Table (Database table that received the action.)
//  <5> - KeyID (If application, the database row Key ID related to the action.)
//----------------------------------------------------------------------------------------------------------------------
Service AddToReplicationQueueTable(TransactionID)

    // Verify that this is a source (i.e., production) server.
    ServerType          = Replication_Services('GetServerType')
    If ServerType _EQC 'Source' then
        Action              = TransactionID[1, @FM]
        Application         = TransactionID[Col2() + 1, @FM]
        Volume              = TransactionID[Col2() + 1, @FM]
        Table               = TransactionID[Col2() + 1, @FM]
        KeyID               = TransactionID[Col2() + 1, 999]        
        LookupKeyID         = Action:'*':Table:'*':KeyID
        
        QueueTableName      = Replication_Services('GetReplicationQueueTable', Application, Table)

        AddTransaction      = True$ ; // Assume this is a valid transaction to add to the queue for now.

        Begin Case
            Case (Table _EQC 'SYSPROCS' AND (KeyID[1, 15] _EQC 'SRP_EDITOR_TEMP' OR KeyID _EQC 'VARDECL_'))
                // Data row is either a temp record created by the SRP Editor needed to support inherited stored
                // procedures or it is the special VARDECL_ record created by the compiler.
                AddTransaction  = False$

            Case IndexC(Volume, Replication_Services('GetTargetServerPath'), 1)
                // Volume for this table is located on the target server. This occurs with the ReplicateTransactions
                // service is writing a row to the target table.
                AddTransaction  = False$

            Case IndexC(Volume, Replication_Services('GetTargetREVBOOTPath'), 1)
                // Volume for this table is located on the target server. This occurs with the ReplicateTransactions
                // service is writing a row to the target table.
                AddTransaction  = False$

            Case KeyID _EQC '%RECORDS%'
                // Do not replicate QuickDex records.
                AddTransaction  = False$

            Case TransactionID EQ ''
                // This is an invalid TransactionID.
                AddTransaction  = False$
                
            Case RowExists(QueueTableName, LookupKeyID)
                // This is a duplicate transaction.
                AddTransaction  = False$
                
        End Case

        If AddTransaction then
            hQueueTable = Replication_Services('GetReplicationQueueTableHandle', Application, Table)

            If hQueueTable NE '' then
                CounterKeyID    = '%NextPendingSK%'
                If Replication_Services('LockReplicationQueueCounter', hQueueTable, CounterKeyID) then
                    // The counter value represents the next available sequence. Do not increase it until it is successfully
                    // used.
                    Read Counter from hQueueTable, CounterKeyID then
                        If Num(Counter) else
                            Counter     = 1
                        end
                    end else
                        Counter = 1
                    end

                    DummyLookupRec = ''
                    Write DummyLookupRec to hQueueTable, LookupKeyID then
                        Write TransactionID to hQueueTable, Counter then
                            Counter    += 1
                            Write Counter to hQueueTable, CounterKeyID else
                                StatusFlag  = Get_Status(StatusCode)
                                Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, Volume, 'ReplicationQueue for ' : Table, CounterKeyID, StatusCode, @File_Error)
                            end
                        end else
                            StatusFlag  = Get_Status(StatusCode)
                            Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, Volume, 'ReplicationQueue for ' : Table, Counter, StatusCode, @File_Error)
                        end
                    end else
                        StatusFlag  = Get_Status(StatusCode)
                        Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, Volume, 'ReplicationQueue for ' : Table, CounterKeyID, StatusCode, @File_Error)
                    end
                    
                    Replication_Services('UnlockReplicationQueueCounter', hQueueTable, CounterKeyID)
                end else
                    // Unable to lock pending queue. Send an alert.
                    StatusFlag  = Get_Status(StatusCode)
                    Replication_Services('SendEmergencyAlert', Service, 'LOCK', Application, Volume, 'ReplicationQueue for ' : Table, CounterKeyID, StatusCode, @File_Error)
                end
            end
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// LockReplicationQueueCounter
//
// QueueTableHandle - Handle to the database table holding the replication queue transactions. - [Required]
// CounterKeyID - Key ID to the counter for the indicated queue table. - [Required]
//
// Returns a True if the pending queue counter is properly locked. Since locks on the queue file should be very short,
// this routine will only attempt to lock a queue file for a couple of seconds before sending out an alert that
// something unexpected has occured.
//----------------------------------------------------------------------------------------------------------------------
Service LockReplicationQueueCounter(QueueTableHandle, CounterKeyID)

    ReplicationQueueCounterLocked   = False$    ; // Assume no lock for now.

    If QueueTableHandle NE '' AND CounterKeyID NE '' then
        // Use GetTickCount to track the duration of the lock attempt in milliseconds. After 2 seconds then the
        // lock attempt should be aborted.
        StartAttempt    = GetTickCount()
        Loop
            Lock QueueTableHandle, CounterKeyID then ReplicationQueueCounterLocked = True$
            CurrentAttempt      = GetTickCount()
            AttemptDuration     = CurrentAttempt - StartAttempt
        Until ReplicationQueueCounterLocked OR (AttemptDuration GT 2000)
        Repeat
        If Not(ReplicationQueueCounterLocked) then Error_Services('Add', 'Unable to lock ' : CounterKeyID : ' in the ' : Service : ' service.')
    end else
        Error_Services('Add', 'QueueTableHandle or CounterKeyID arguments were missing from the ' : Service : ' service.')
    end

    Response    = ReplicationQueueCounterLocked

end service


//----------------------------------------------------------------------------------------------------------------------
// LockPendingQueue
//
// Application - Name of the database application for the table. - [Required]
// Table - Name of the database table whose queue file needs to be locked. - [Required]
//
// Returns a True if the pending queue file is properly locked. Since locks on the queue file should be very short,
// this routine will only attempt to lock a queue file for a couple of seconds before sending out an alert that
// something unexpected has occured.
//----------------------------------------------------------------------------------------------------------------------
Service LockPendingQueue(Application, Table)

    Convert @Lower_Case to @Upper_Case in Application
    Convert @Lower_Case to @Upper_Case in Table

    PendingQueueLocked  = False$    ; // Assume no lock for now.

    If Len(Application) AND Len(Table) then
        hRepConfigTable = Memory_Services('GetValue', 'hRepConfigTable')
        If Len(hRepConfigTable) else
            Open RepConfigTable$ to hRepConfigTable then
                Memory_Services('SetValue', 'hRepConfigTable', hRepConfigTable)
            end else
                Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
            end
        end

        If Error_Services('NoError') then
            // Use GetTickCount to track the duration of the lock attempt in milliseconds. After 2 seconds then the
            // lock attempt should be aborted.
            StartAttempt    = GetTickCount()
            LockKeyID       = Application : '*' : Table : '*' : 'Pending'
            Loop
                Lock hRepConfigTable, LockKeyID then PendingQueueLocked = True$
                CurrentAttempt      = GetTickCount()
                AttemptDuration     = CurrentAttempt - StartAttempt
            Until PendingQueueLocked OR (AttemptDuration GT 2000)
            Repeat
            If Not(PendingQueueLocked) then Error_Services('Add', 'Unable to lock the pending queue for ' : Application : '*' : Table : ' in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'Application or Table arguments were missing from the ' : Service : ' service.')
    end

    Response    = PendingQueueLocked

end service


//----------------------------------------------------------------------------------------------------------------------
// UnlockReplicationQueueCounter
//
// QueueTableHandle - Handle to the database table holding the replication queue transactions. - [Required]
// CounterKeyID - Key ID to the counter for the indicated queue table. - [Required]
//
// Returns a True if the pending queue counter is properly unlocked.
//----------------------------------------------------------------------------------------------------------------------
Service UnlockReplicationQueueCounter(QueueTableHandle, CounterKeyID)

    ReplicationQueueCounterUnlocked = False$    ; // Assume not unlocked for now.

    If QueueTableHandle NE '' AND CounterKeyID NE '' then
        Unlock QueueTableHandle, CounterKeyID then
            ReplicationQueueCounterUnlocked = True$
        end else
            Error_Services('Add', 'Unable to unlock ' : CounterKeyID : ' in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'QueueTableHandle or CounterKeyID arguments were missing from the ' : Service : ' service.')
    end

    Response    = ReplicationQueueCounterUnlocked

end service


//----------------------------------------------------------------------------------------------------------------------
// UnlockPendingQueue
//
// Application - Name of the database application for the table. - [Required]
// Table - Name of the database table whose queue file needs to be unlocked. - [Required]
//
// Returns a True if the pending queue file is properly unlocked.
//----------------------------------------------------------------------------------------------------------------------
Service UnlockPendingQueue(Application, Table)

    Convert @Lower_Case to @Upper_Case in Application
    Convert @Lower_Case to @Upper_Case in Table

    PendingQueueUnlocked = False$   ; // Assume not unlocked for now.

    If Len(Application) AND Len(Table) then
        hRepConfigTable = Memory_Services('GetValue', 'hRepConfigTable')
        If Len(hRepConfigTable) else
            Open RepConfigTable$ to hRepConfigTable then
                Memory_Services('SetValue', 'hRepConfigTable', hRepConfigTable)
            end else
                Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
            end
        end

        If Error_Services('NoError') then
            LockKeyID   = Application : '*' : Table : '*' : 'Pending'
            Unlock hRepConfigTable, LockKeyID then
                PendingQueueUnlocked    = True$
            end else
                Error_Services('Add', 'Unable to unlock the pending queue for ' : Application : '*' : Table : ' in the ' : Service : ' service.')
            end
        end
    end else
        Error_Services('Add', 'Application or Table arguments were missing from the ' : Service : ' service.')
    end

    Response    = PendingQueueUnlocked

end service


//----------------------------------------------------------------------------------------------------------------------
// LockInProcessQueue
//
// Application - Name of the database application for the table. - [Required]
// Table - Name of the database table whose queue file needs to be locked. - [Required]
//
// Returns a True if the in-process queue file is properly locked. Since locks on the queue file should be very short,
// this routine will only attempt to lock a queue file for a couple of seconds before sending out an alert that
// something unexpected has occured.
//----------------------------------------------------------------------------------------------------------------------
Service LockInProcessQueue(Application, Table)

    Convert @Lower_Case to @Upper_Case in Application
    Convert @Lower_Case to @Upper_Case in Table

    InProcessQueueLocked    = False$    ; // Assume no lock for now.

    If Len(Application) AND Len(Table) then
        hRepConfigTable = Memory_Services('GetValue', 'hRepConfigTable')
        If Len(hRepConfigTable) else
            Open RepConfigTable$ to hRepConfigTable then
                Memory_Services('SetValue', 'hRepConfigTable', hRepConfigTable)
            end else
                Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
            end
        end

        If Error_Services('NoError') then
            // Use GetTickCount to track the duration of the lock attempt in milliseconds. After 2 seconds then the
            // lock attempt should be aborted.
            StartAttempt    = GetTickCount()
            LockKeyID       = Application : '*' : Table : '*' : 'InProcess'
            Loop
                Lock hRepConfigTable, LockKeyID then InProcessQueueLocked = True$
                CurrentAttempt      = GetTickCount()
                AttemptDuration     = CurrentAttempt - StartAttempt
            Until InProcessQueueLocked OR (AttemptDuration GT 2000)
            Repeat
            If Not(InProcessQueueLocked) then Error_Services('Add', 'Unable to lock the in-process queue for ' : Application : '*' : Table : ' in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'Application or Table arguments were missing from the ' : Service : ' service.')
    end

    Response    = InProcessQueueLocked

end service


//----------------------------------------------------------------------------------------------------------------------
// UnlockInProcessQueue
//
// Application - Name of the database application for the table. - [Required]
// Table - Name of the database table whose queue file needs to be unlocked. - [Required]
//
// Returns a True if the in-process queue file is properly unlocked.
//----------------------------------------------------------------------------------------------------------------------
Service UnlockInProcessQueue(Application, Table)

    Convert @Lower_Case to @Upper_Case in Application
    Convert @Lower_Case to @Upper_Case in Table

    InProcessQueueUnlocked  = False$   ; // Assume not unlocked for now.

    If Len(Application) AND Len(Table) then
        hRepConfigTable = Memory_Services('GetValue', 'hRepConfigTable')
        If Len(hRepConfigTable) else
            Open RepConfigTable$ to hRepConfigTable then
                Memory_Services('SetValue', 'hRepConfigTable', hRepConfigTable)
            end else
                Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
            end
        end

        If Error_Services('NoError') then
            LockKeyID   = Application : '*' : Table : '*' : 'InProcess'
            Unlock hRepConfigTable, LockKeyID then
                InProcessQueueUnlocked    = True$
            end else
                Error_Services('Add', 'Unable to unlock the in-process queue for ' : Application : '*' : Table : ' in the ' : Service : ' service.')
            end
        end
    end else
        Error_Services('Add', 'Application or Table arguments were missing from the ' : Service : ' service.')
    end

    Response    = InProcessQueueUnlocked

end service


//----------------------------------------------------------------------------------------------------------------------
// SetMaxTransactionsCount
//
// MaxTransactionsCount - The maximum number of transactions that will be processed in one sesion. - [Required]
//
// Sets the maximum number of transactions that should be processed at one time.
//----------------------------------------------------------------------------------------------------------------------
Service SetMaxTransactionsCount(MaxTransactionsCount)

    If MaxTransactionsCount GT 0 AND Num(MaxTransactionsCount) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_MAX_TRANSACTION_COUNT'
            Lock hRepConfigTable, RepConfigKeyID then
                Write MaxTransactionsCount to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing max transaction count in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking max transaction count in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end else
        If Num(MaxTransactionsCount) else Error_Services('Add', 'MaxTransactionsCount argument is not a valid number in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// SetReplicationProcedure
//
// ReplicationProcedure - the name of the custom stored procedure that will handle replications. - [Optional]
//
// Sets the the name of the custom stored procedure that will handle replications.
//----------------------------------------------------------------------------------------------------------------------
Service SetReplicationProcedure(ReplicationProcedure)

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_REPLICATION_PROCEDURE'
        Lock hRepConfigTable, RepConfigKeyID then
            Write ReplicationProcedure to hRepConfigTable, RepConfigKeyID else
                Error_Services('Add', 'Error writing replication procedure in the ' : Service : ' service.')
            end
            Unlock hRepConfigTable, RepConfigKeyID
        end else
            Error_Services('Add', 'Error locking replication procedure in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// ResetReplicationQueueCounters
//
// QueueTable - Name of the replication queue table. - [Required]
//
// Resets the replication queue counters based on the transaction rows in the queue table. Returns the same information
// in the same format as the GetReplicationQueueTableStatus service (%EngineName% value, %NextInProcessSK% value,
// %NextPendingSK% value, and total estimated number of rows in the indicated queue table).
//----------------------------------------------------------------------------------------------------------------------
Service ResetReplicationQueueCounters(QueueTable)

    Convert @Lower_Case to @Upper_Case in QueueTable

    QueueTableStatus    = ''

    If QueueTable NE '' then
        QueuePath   = Replication_Services('GetSourceQueuePath')
        If Error_Services('NoError') then
            Set_Status(0)
            ShouldDetach    = False$
            // First attempt to open the private replication queue table.
            Open QueueTable to hQueueTable else
                // Since the Open statement didn't work, attempt to attach the private replication queue table.
                Set_Status(0)
                Attach_Table(QueuePath : '', QueueTable, 'GLOBAL', '')
                If Get_Status(StatusCode) then
                    Error_Services('Add', 'Unable to attach ' : QueueTable : ' in the ' : Service : ' service.')
                end else
                    // Make sure table is detached since it was not attached already.
                    ShouldDetach    = True$
                    Set_Status(0)
                    Open QueueTable to hQueueTable else
                        Set_Status(0)
                        Error_Services('Add', 'Unable to open ' : QueueTable : ' in the ' : Service : ' service.')
                    end
                end
            end
            If Error_Services('NoError') then
                Select hQueueTable
                EndOfCursor     = False$
                NextInProcessSK = 9999999
                NextPendingSK   = 1
                Loop
                    Readnext TransactionID then
                        If Num(TransactionID) then
                            If TransactionID GT NextPendingSK then NextPendingSK = TransactionID
                            If TransactionID LT NextInProcessSK then NextInProcessSK = TransactionID
                        end
                    end else
                        EndOfCursor = True$
                    end
                Until EndOfCursor EQ True$
                Repeat
                
                If NextInProcessSK EQ 9999999 then NextInProcessSK = 1

                If NextPendingSK GT 1 then NextPendingSK += 1

                TotalRows = Get.RecCount(hQueueTable, Status, False$)
                // If any of the queue counters have values, adjust the TotalRows count accordingly.
                Read OldNextInProcessSK from hQueueTable, '%NextInProcessSK%' then
                    TotalRows -= 1
                end
                Write NextInProcessSK to hQueueTable, '%NextInProcessSK%' else
                    Error_Services('Add', 'Error updating the %NextInProcessSK% counter in ' : QueueTable : ' in the ' : Service : ' service.')
                end
                Read OldNextPendingSK from hQueueTable, '%NextPendingSK%' then
                    TotalRows -= 1
                end
                Write NextPendingSK to hQueueTable, '%NextPendingSK%' else
                    Error_Services('Add', 'Error updating the %NextPendingSK% counter in ' : QueueTable : ' in the ' : Service : ' service.')
                end
            	Read EngineName from hQueueTable, '%EngineName%' then
            	    TotalRows -= 1
            	end else
            	    EngineName = ''
            	end
                Read NumProcesses from hQueueTable, '%NumProcesses%' then
                    TotalRows -= 1
                end else
                    NumProcesses = 0
                end
                QueueTableStatus = EngineName : @VM : NextInProcessSK : @VM : NextPendingSK : @VM : TotalRows : @VM : NumProcesses
                Set_Status(0)
                If ShouldDetach = True$ then Detach_Table(QueueTable)
            end
        end
    end else
        Error_Services('Add', 'QueueTable argument was missing from the ' : Service : ' service.')
    end

    Response    = QueueTableStatus

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMaxTransactionsCount
//
// Returns the maximum number of transactions that should be processed at one time.
//----------------------------------------------------------------------------------------------------------------------
Service GetMaxTransactionsCount()

    MaxTransactionsCount    = 0

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_MAX_TRANSACTION_COUNT'
        Read MaxTransactionsCount from hRepConfigTable, RepConfigKeyID else MaxTransactionsCount = 0
        If MaxTransactionsCount EQ 0 then
            Error_Services('Add', 'The max transaction count has not been setup in the ' : RepConfigTable$ : ' table.')
        end
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = MaxTransactionsCount

end service


//----------------------------------------------------------------------------------------------------------------------
// GetReplicationProcedure
//
// Returns the name of the custom stored procedure that will handle replications.
//----------------------------------------------------------------------------------------------------------------------
Service GetReplicationProcedure()

    ReplicationProcedure    = ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_REPLICATION_PROCEDURE'
        Read ReplicationProcedure from hRepConfigTable, RepConfigKeyID else ReplicationProcedure = ''
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = ReplicationProcedure

end service


//----------------------------------------------------------------------------------------------------------------------
// ReplicateTransactions
//
// Primary service to take transactions from the replication queue tables and process them. This service will be called
// multiple times by the SRP Engine Server. Therefore, it does not need to run in a loop. See the comments inline below
// to get the basic work flow.
//----------------------------------------------------------------------------------------------------------------------
Service ReplicateTransactions()
    
    // Verify if error logging is enabled.
    LoggingEnabled    = Replication_Services('IsLoggingEnabled')

    // Verify that this is a source (i.e., production) server.
    ServerType        = Replication_Services('GetServerType')

    // Verify that the system is enabled and not stopped before replication any transactions.
    SystemEnabledFlag = Replication_Services('GetSystemEnabledFlag')
    SystemStopFlag    = Replication_Services('GetSystemStopFlag')

    If SystemEnabledFlag AND Not(SystemStopFlag) then

        Begin Case
            Case ServerType _EQC 'Source'
                //--------------------------------------------------------------------------------------------------------------
                //
                //  1. Check for replication queue tables that need to be processed. This will be determined based on the
                //     NextInProcess counter value and the NextPending counter value. If the NextInProcess counter is less
                //     than the NextPending counter then there are transactions in this replication queue table that are ready
                //     to be process.
                //
                //--------------------------------------------------------------------------------------------------------------
                CommandLine        = GetCommandLine()
                Swap '/S=' with @FM in CommandLine
                EngineName         = CommandLine<2>    ; // This is the NamedPipe identifier for the engine processing this service.
                HaveQueueTable     = False$            ; // Assume there is no queue table that is ready to be processed for now.
                QueueTableStatuses = Replication_Services('GetAllReplicationQueueTableStatus')
                If Len(QueueTableStatuses) then
                    // There are replication queue tables. Sort by number of processes to prioritize and then attempt
                    // to processs.
                    QueueTableStatuses     = SRP_Array('SortRows', QueueTableStatuses, 'AN6', 'LIST', @FM, @VM)
                    ReplicationQueueTables = SRP_Array('Rotate', QueueTableStatuses, @FM, @VM)<1>
                    Convert @VM to @FM in ReplicationQueueTables
                    QueueTableCnt          = 0
                    NumQueueTables         = DCount(ReplicationQueueTables, @FM)
                    Loop
                        QueueTableCnt  += 1
                        QueueTable      = ReplicationQueueTables<QueueTableCnt>
                        hQueueTable     = Replication_Services('GetReplicationQueueTableHandle', '', '', QueueTable)
                        PendingSKID     = '%NextPendingSK%'
                        InProcessSKID   = '%NextInProcessSK%'
                        HaveQueueTable  = Replication_Services('LockReplicationQueueCounter', hQueueTable, InProcessSKID)
                        // Increment the process count for this queue table. This will help prioritize queue tables that
                        // haven't been processed.
                        Read NumProcesses from hQueueTable, '%NumProcesses%' then
                            If NumProcesses GT 10000 then NumProcesses = 0
                        end else
                            NumProcesses = 0
                        end
                        NumProcesses += 1
                        Write NumProcesses to hQueueTable, '%NumProcesses%' else Null
                        If HaveQueueTable then
                            // Verify that this table has a Next In Process counter with a lower value than the Next Pending
                            // counter.
                            Read NextPendingSK from hQueueTable, PendingSKID else NextPendingSK = 1
                            Read FirstInProcessSK from hQueueTable, InProcessSKID else FirstInProcessSK = 1
                            If FirstInProcessSK EQ NextPendingSK then
                                HaveQueueTable  = False$
                                Replication_Services('UnlockReplicationQueueCounter', hQueueTable, InProcessSKID)
                            end else
                                If (QueueTable NE 'REPLICATION_QUEUE_GLOBAL_PUBLIC') AND (QueueTable NE 'REPLICATION_QUEUE_GLOBAL_MASTER') then
                                    // This is a private queue. Verify the table name using this private queue and then
                                    // check to see if it is permitted to replicate.
                                    Read TransactionID from hQueueTable, FirstInProcessSK then
                                        Action             = TransactionID[1, @FM]
                                        Application        = TransactionID[Col2() + 1, @FM]
                                        SourceVolume       = TransactionID[Col2() + 1, @FM]
                                        Table              = TransactionID[Col2() + 1, @FM]
                                        KeyID              = TransactionID[Col2() + 1, 999]
                                        AllowedToReplicate = Replication_Services('IsTableAllowedToReplicate', Table, Application)
                                        If Not(AllowedToReplicate) then
                                            HaveQueueTable = False$
                                            Replication_Services('UnlockReplicationQueueCounter', hQueueTable, InProcessSKID)
                                        end
                                    end
                                end
                            end
                        end
                    Until (QueueTableCnt GE NumQueueTables) OR (HaveQueueTable)
                    Repeat
                end

                //--------------------------------------------------------------------------------------------------------------
                //
                //  2. Loop through the next group of un-processed transactions and replicate them.
                //
                //--------------------------------------------------------------------------------------------------------------
                If HaveQueueTable then
                    // Get the number of pending transactions.
                    NumPendingTransactions  = NextPendingSK - FirstInProcessSK
                    If NumPendingTransactions GT 0 then
                        // Note the engine that is managing this queue.
                        Write EngineName to hQueueTable, '%EngineName%' else Null
                        // Get the maximum number of transactions to process.
                        TransactionCount     = Replication_Services('GetMaxTransactionsCount')
                        If NumPendingTransactions GT TransactionCount then NumPendingTransactions = TransactionCount
                        LastInProcessSK      = FirstInProcessSK + (NumPendingTransactions - 1)
                        ReplicationProcedure = Replication_Services('GetReplicationProcedure')

                        hHashTable = SRP_HashTable_Create(False$, NumPendingTransactions)
                        If hHashTable NE '' then
                            SourceServerPath       = ''     ; // Initialize the variable. Only call the GetSourceServerPath if this is empty.
                            NewTable               = False$ ; // Flag indicating if a new table in the queue is encountered.
                                                              // This will cause the current replication process to abort since
                                                              // only one table can be processed at a time.
                            FirstTable             = ''     ; // Records the name of the first table in the queue.
                            FirstVolume            = ''     ; // Records the name of the first volume in the queue.
                            FirstSkippedSK         = ''     ; // The first transaction ID that was skipped. This will be used to reset the next in-process SK.

                            // Run through the list of tranactions twice. The first pass is to read any tranasctions that need
                            // to be written to the target table and store them in a hash table. This is so only one
                            // attach/detach needs to be done against the source and target servers for optimum performance.
                            For InProcessSK = FirstInProcessSK to LastInProcessSK
                                // Update the counter with the current value. If the process aborts, the counter will be set to
                                // the last value that has not yet been processed. This allows any future processing to
                                // correctly pick up where this left off.
                                Write InProcessSK to hQueueTable, InProcessSKID then
                                    Read TransactionID from hQueueTable, InProcessSK then
                                        Action             = TransactionID[1, @FM]
                                        Application        = TransactionID[Col2() + 1, @FM]
                                        SourceVolume       = TransactionID[Col2() + 1, @FM]
                                        Table              = TransactionID[Col2() + 1, @FM]
                                        KeyID              = TransactionID[Col2() + 1, 999]
                                       
                                        AllowedToReplicate = Replication_Services('IsTableAllowedToReplicate', Table, Application)
                                        If AllowedToReplicate EQ True$ then
                                            SRP_HashTable_Set(hHashTable, 'TransactionID*' : InProcessSK, TransactionID)

                                            // Copy the SourceVolume variable since it can be updated by the following
                                            // processes.
                                            OriginalSourceVolume    = SourceVolume

                                            // Check to see if the queue is presenting a new table. If so, then end the loop so
                                            // only transactions connected to the same table are processed. A new table is
                                            // evident if the table name changes or the volume changes (i.e., the table name
                                            // might be the same but it is from a different volume).
                                            If (FirstTable NE '') AND (Table NE FirstTable) then NewTable = True$
                                            If (FirstVolume NE '') AND (SourceVolume NE FirstVolume) then NewTable = True$

                                            If NewTable then
                                                // Set the end of the counter to the previous value so the current loop will
                                                // end. The next loop sequence will pick up the new table.
                                                LastInProcessSK = InProcessSK - 1
                                            end else
                                                If SourceServerPath EQ '' then
                                                    // This must be the first iteration in the loop. Attempt to open the table that the
                                                    // source row resides in. However, make sure that any paths are resolved to the
                                                    // official absolute path (which should normally be in UNC format). The SourceVolume
                                                    // variable will likely contain a mapped drive and this might not be available to
                                                    // the replication service.
                                                    SourceServerPath    = UCase(Replication_Services('GetSourceServerPath'))
                                                    FirstTable          = Table         ; // Record the first table in the queue for future reference.
                                                    FirstVolume         = SourceVolume  ; // Record the first volume in the queue for future reference.
                                                    SourceVolumeType    = ''
                                                    Begin Case
                                                        Case SRP_Path('IsUNC', SourceVolume)
                                                            // This path is already in UNC format so nothing needs to be
                                                            // done.
                                                            Transfer SourceVolume to ResolvedSourceVolume
                                                            SourceVolumeType    = 'UNC'

                                                        Case SourceVolume _EQC 'REVBOOT'
                                                            // This is the REVBOOT volume which probably means this is a
                                                            // system table. Use REVBOOT rather than a resolved to the source
                                                            // REVBOOT path. Otherwise, an SSP280 error will result.
                                                            ResolvedSourceVolume    = SourceVolume
                                                            SourceVolumeType        = 'REVBOOT'

                                                        Case SRP_Path('IsRelative', SourceVolume)
                                                            // This is a relative path so it needs to be converted to a full
                                                            // UNC path. These paths are relative to REVBOOT, so the source
                                                            // REVBOOT path needs to be retrieved.
                                                            SourceREVBOOTPath       = Replication_Services('GetSourceREVBOOTPath')
                                                            ResolvedSourceVolume    = SRP_Path('Combine', SourceREVBOOTPath, SourceVolume)
                                                            SourceVolumeType        = 'Relative'

                                                        Case Otherwise$
                                                            // This must be a local or remote drive. If this is remote then convert
                                                            // to UNC using the source server path.
                                                            If SRP_Path('IsNetworkPath', SourceVolume) then
                                                                SourceVolume[1, 3]      = ''    ; // Remove the mapped drive
                                                                ResolvedSourceVolume    = SRP_Path('Combine', SourceServerPath, SourceVolume)
                                                                SourceVolumeType        = 'UNC'
                                                            end else
                                                                Transfer SourceVolume to ResolvedSourceVolume
                                                                SourceVolumeType        = 'Local'
                                                            end
                                                    End Case
                                                    // Attempt to detach the original source volume as a precaution against potential FS404 errors.
                                                    // Even if it is unsuccessful don't do anything as it might be that the volume was never attach.
													IsAttached = Memory_Services('GetValue', Service : '*TABLES_ATTACHED')
													If Not(IsAttached) then
														Set_Status(0)
														Detach_Volume(OriginalSourceVolume : '', Success)
														Set_Status(0)
														Attach_Table(ResolvedSourceVolume : '', '', Application, '')
														If Get_Status(StatusCode) then
															// Error handling goes here.
														end else
															Memory_Services('SetValue', Service : '*TABLES_ATTACHED', True$)
														end
													end
                                                    If Get_Status(StatusCode) then
                                                        If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'ATTACH', Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error)
                                                        Replication_Services('SendEmergencyAlert', Service, 'ATTACH', Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error)
                                                    end else
                                                        Open Table to hSourceTable then
                                                            If Table _EQC 'SYSPROCS' then
                                                                // The data row is coming from SYSPROCS, so it is likely to be
                                                                // source code. The object code should also be pulled and replicated
                                                                // with the source.
                                                                Open 'SYSOBJ' to hSourceSysObj else
                                                                    StatusFlag  = Get_Status(StatusCode)
                                                                    If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'OPEN', Application, ResolvedSourceVolume, 'SYSOBJ', KeyID, StatusCode, @File_Error)
                                                                    Replication_Services('SendEmergencyAlert', Service, 'OPEN', Application, ResolvedSourceVolume, 'SYSOBJ', KeyID, StatusCode, @File_Error)
                                                                end
                                                                Open 'SYSREPOS' to hSourceSysRepos else
                                                                    StatusFlag  = Get_Status(StatusCode)
                                                                    If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'OPEN', Application, ResolvedSourceVolume, 'SYSREPOS', KeyID, StatusCode, @File_Error)
                                                                    Replication_Services('SendEmergencyAlert', Service, 'OPEN', Application, ResolvedSourceVolume, 'SYSREPOS', KeyID, StatusCode, @File_Error)
                                                                end
                                                            end
                                                        end else
                                                            StatusFlag  = Get_Status(StatusCode)
                                                            If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'OPEN', Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error)
                                                            Replication_Services('SendEmergencyAlert', Service, 'OPEN', Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error)
                                                        end
                                                    end
                                                end

                                                If Action _EQC 'WRITE' then
                                                    Read SourceRow from hSourceTable, KeyID then
                                                        SRP_HashTable_Set(hHashTable, KeyID, SourceRow)
                                                        If Table _EQC 'SYSPROCS' then
                                                            Read SourceRowObj from hSourceSysObj, '$' : KeyID then
                                                                SourceRowObj64  = SRP_Encode(SourceRowObj, 'BASE64')
                                                                SRP_HashTable_Set(hHashTable, '$' : KeyID, SourceRowObj64)
                                                            end
                                                            SourceApplication   = Field(KeyID, '*', 2, 1)
                                                            If SourceApplication EQ '' then SourceApplication = 'SYSPROG'
                                                            SysReposSourceKeyID = SourceApplication : '*STPROC**' : KeyID[1, '*']
                                                            Read SourceReposStprocRow from hSourceSysRepos, SysReposSourceKeyID then
                                                                SRP_HashTable_Set(hHashTable, SysReposSourceKeyID, SourceReposStprocRow)
                                                            end
                                                            SysReposObjKeyID    = SourceApplication : '*STPROCEXE**' : KeyID[1, '*']
                                                            Read SourceReposStprocExeRow from hSourceSysRepos, SysReposObjKeyID then
                                                                SRP_HashTable_Set(hHashTable, SysReposObjKeyID, SourceReposStprocExeRow)
                                                            end
                                                        end
                                                    end else
                                                        If @File_Error<1> EQ 100 then
                                                            // This means the row did not exist in the table. However, this is
                                                            // quite possible if there has been more activity with this row
                                                            // that deleted it prior to this transaction being processed.
                                                            SRP_HashTable_Set(hHashTable, 'TransactionID*' : InProcessSK, 'IGNORE')
                                                        end else
                                                            StatusFlag  = Get_Status(StatusCode)
                                                            If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'READ', Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error)
                                                            Replication_Services('SendEmergencyAlert', Service, 'READ', Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error)
                                                        end
                                                    end
                                                end
                                            end
                                        end else
                                            // SKIP is different than IGNORE in that it should be kept in the queue for future
                                            // processing. This is likely when the table has been temporarily set so that
                                            // it shouldn't be replicated even though queue transactions exist.
                                            SRP_HashTable_Set(hHashTable, 'TransactionID*' : InProcessSK, 'SKIP')
                                            If FirstSkippedSK EQ '' then FirstSkippedSK = InProcessSK
                                        end
                                    end else
                                        // On some occassions it is possible for gaps to occur in the transaction ID sequence within a queue table.
                                        // Since the transaction ID cannot be read, flag it as a SKIP item but do not set the FirstSkippedSK variable
                                        // since this is not a real transaction ID.
                                        Set_Status(0)
                                        SRP_HashTable_Set(hHashTable, 'TransactionID*' : InProcessSK, 'SKIP')
                                    end
                                end else
                                    StatusFlag  = Get_Status(StatusCode)
                                    If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'WRITE', Application, ResolvedSourceVolume, 'ReplicationQueue for ' : Table, InProcessSKID, StatusCode, @File_Error)
                                    Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, ResolvedSourceVolume, 'ReplicationQueue for ' : Table, InProcessSKID, StatusCode, @File_Error)
                                end
                            Until NewTable
                            Next InProcessSK
                            TargetServerPath        = ''    ; // Initialize the variable. Only call the GetTargetServerPath if this is empty.
                            ResolvedTargetVolume    = ''    ; // Initialize the variable.

                            // Make a second pass through the list of transactions. This time to apply the actions against the
                            // target table.
                            For InProcessSK = FirstInProcessSK to LastInProcessSK
                                // Update the counter with the current value. If the process aborts, the counter will be set to the last
                                // value that has not yet been processed. This allows any future processing to correctly pick up where
                                // this left off.
                                ActionCompleted = False$
                                TransactionID   = SRP_HashTable_Get(hHashTable, 'TransactionID*' : InProcessSK)
                                Action          = TransactionID[1, @FM]
                                Application     = TransactionID[Col2() + 1, @FM]
                                SourceVolume    = TransactionID[Col2() + 1, @FM]
                                Table           = TransactionID[Col2() + 1, @FM]
                                KeyID           = TransactionID[Col2() + 1, 999]

                                If Action _NEC 'SKIP' then
                                    If ReplicationProcedure then
                                        // There is a replication procedure. Call it with the TransactionID. Check Error_Services
                                        // to see if there were any issue. If not, assume the action was completed.
                                        If Action _EQC 'IGNORE' then
                                            // If a normal action (e.g., WRITE) cannot be processed due to normal reasons, the
                                            // action is dynamically changed to IGNORE so that the service can continue without
                                            // erroring out. The transaction ID will also be deleted.
                                            ActionCompleted = True$
                                        end else
                                            Call @ReplicationProcedure(TransactionID)
                                            If Error_Services('NoError') then
                                                ActionCompleted = True$
                                            end else
                                                Message = Error_Services('GetMessage')
                                                If IndexC(Message, 'The OITableName, OIKeyID, or OIRow argument was missing', 1) then
                                                    Error_Services('Clear')
                                                    // Remove the transaction from the queue
                                                    Delete hQueueTable, InProcessSK else
                                                        // The transaction ID might not exist due to SKIPing. Just clear the error and move on.
                                                        Set_Status(0)
                                                    end
                                                    // Remove the lookup key from the queue
                                                    LookupKeyID = Action:'*':Table:'*':KeyID
                                                    Delete hQueueTable, LookupKeyID else
                                                        // The lookup key might not exist due to SKIPing. Just clear the error and move on.
                                                        Set_Status(0)
                                                        // Log failure
                                                        LogData    = ''
                                                        LogData<1> = LoggingDTM
                                                        LogData<2> = LookupKeyID
                                                        LogData<3> = hQueueTable
                                                        Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
                                                    end
                                                    // Send email alert
                                                    Message      := ' Table: ':Table:' Key: ':KeyID
                                                    Replication_Services('SendAlert', Service, ReplicationProcedure, Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error, '', Message)
                                                    // Send an internal OI message to OI admins
                                                    Recipients    = Xlate('SEC_GROUPS', 'OI_ADMIN', 'USER', 'X')
                                                    SentFrom      = 'SYSTEM'
                                                    Subject       = 'Replication Manager Error'
                                                    Message       = 'The OITableName, OIKeyID, or OIRow argument was missing in the ' : Service : ' service. Table: ':Table:' Key: ':KeyID
                                                    AttachWindow  = ''
                                                    AttachKey     = ''
                                                    SendToGroup   = ''
                                                    Parms         = Recipients:@RM:SentFrom:@RM:Subject:@RM:Message:@RM:AttachWindow:@RM:AttachKey:@RM:SendToGroup
                                                    obj_Notes('Create',Parms)
                                                end else
                                                    If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, ReplicationProcedure, Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error, '', Message)
                                                    Replication_Services('SendEmergencyAlert', Service, ReplicationProcedure, Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error, '', Message)
                                                end
                                            end
                                        end
                                    end else
                                        // There is no replication procedure specified. Perform the replication procedure using
                                        // normal OpenInsight to OpenInsight logic.
                                        If TargetServerPath EQ '' then
                                            // This must be the first iteration in the loop. Attempt to open the target table. However,
                                            // make sure that any paths are resolved to the official source server path (which should be
                                            // in UNC format).
                                            Set_Status(0)
                                            If SourceVolumeType _EQC 'REVBOOT' then
                                                Success = True$
                                            end else
                                                Detach_Volume(ResolvedSourceVolume : '', Success)
                                            end
                                            If Success EQ True$ then
                                                Set_Status(0)
                                                TargetServerPath    = UCase(Replication_Services('GetTargetServerPath'))
                                                Begin Case
                                                    Case SourceVolumeType _EQC 'UNC'
                                                        ResolvedTargetVolume    = ResolvedSourceVolume
                                                        Swap SourceServerPath with TargetServerPath in ResolvedTargetVolume

                                                    Case SourceVolumeType _EQC 'REVBOOT'
                                                        ResolvedTargetVolume    = SRP_Path('RemoveBackslash', Replication_Services('GetTargetREVBOOTPath'))

                                                    Case SourceVolumeType _EQC 'Relative'
                                                        TargetREVBOOTPath       = Replication_Services('GetTargetREVBOOTPath')
                                                        ResolvedTargetVolume    = ResolvedSourceVolume
                                                        Swap SourceREVBOOTPath with TargetREVBOOTPath in ResolvedTargetVolume

                                                    Case SourceVolumeType _EQC 'Local'
                                                        // Local tables have no distintion between source and target. Just
                                                        // use the ResolvedSourceVolume.
                                                        ResolvedTargetVolume    = ResolvedSourceVolume

                                                    Case Otherwise$
                                                        StatusFlag          = Get_Status(StatusCode)
                                                        AdditionalMessage   = 'An unknown SourceVolumeType, ' : SourceVolumeType : ', was encountered.'
                                                        If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, '', Application, '', '', '', StatusCode, @File_Error, '', AdditionalMessageMessage)
                                                        Replication_Services('SendEmergencyAlert', Service, '', Application, '', '', '', StatusCode, @File_Error, '', AdditionalMessage)

                                                End Case

                                                // If the table is SYSPROCS, alias the target table rather than attach. Otherwise, the target's
                                                // copy of the source code will appear in the debugger.
                                                If Table _EQC 'SYSPROCS' then
                                                    Alias_Table(ResolvedTargetVolume : '', Application, 'SYSPROCS', 'TARGET_SYSPROCS')
                                                end else
                                                    Attach_Table(ResolvedTargetVolume : '', Table, Application, '')
                                                end

                                                If Get_Status(StatusCode) then
                                                    StatusFlag  = Get_Status(StatusCode)
                                                    If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'ATTACH', Application, ResolvedTargetVolume, Table, KeyID, StatusCode, @File_Error)
                                                    Replication_Services('SendEmergencyAlert', Service, 'ATTACH', Application, ResolvedTargetVolume, Table, KeyID, StatusCode, @File_Error)
                                                end else
                                                    OpenSuccessful  = True$ ; // Assume the Open statement will be successful for now.
                                                    If Table _EQC 'SYSPROCS' then
                                                        Open 'TARGET_SYSPROCS' to hTargetTable else OpenSuccessful = False$
                                                    end else
                                                        Open Table to hTargetTable else OpenSuccessful = False$
                                                    end
                                                    If OpenSuccessful EQ True$ then
                                                        If Table _EQC 'SYSPROCS' then
                                                            // Since source code is being replicated, alias the target SYSOBJ table to
                                                            // replicate object code as well.
                                                            Set_Status(0)
                                                            Alias_Table(ResolvedTargetVolume : '', Application, 'SYSOBJ', 'TARGET_SYSOBJ')
                                                            If Get_Status(StatusCode) then
                                                                StatusFlag  = Get_Status(StatusCode)
                                                                If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'ALIAS', Application, ResolvedTargetVolume, 'SYSOBJ', '', StatusCode, @File_Error)
                                                                Replication_Services('SendEmergencyAlert', Service, 'ALIAS', Application, ResolvedTargetVolume, 'SYSOBJ', '', StatusCode, @File_Error)
                                                            end else
                                                                Open 'TARGET_SYSOBJ' to hTargetSysObj else
                                                                    StatusFlag  = Get_Status(StatusCode)
                                                                    If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'OPEN', Application, ResolvedTargetVolume, 'SYSOBJ', KeyID, StatusCode, @File_Error)
                                                                    Replication_Services('SendEmergencyAlert', Service, 'OPEN', Application, ResolvedTargetVolume, 'SYSOBJ', KeyID, StatusCode, @File_Error)
                                                                end
                                                            end

                                                            // Use Repos_Attach_Trans to alias the target SYSREPOS table to replicate
                                                            // entity pointers.
                                                            Set_Status(0)
                                                            Repos_Attach_Trans(ResolvedTargetVolume : '', 1)
                                                            If Get_Status(StatusCode) then
                                                                StatusFlag  = Get_Status(StatusCode)
                                                                If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'ALIAS', Application, ResolvedTargetVolume, 'SYSREPOS', '', StatusCode, @File_Error)
                                                                Replication_Services('SendEmergencyAlert', Service, 'ALIAS', Application, ResolvedTargetVolume, 'SYSREPOS', '', StatusCode, @File_Error)
                                                            end else
                                                                Open 'SYSREPOS_TEMP' to hTargetSysRepos else
                                                                    StatusFlag  = Get_Status(StatusCode)
                                                                    If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'OPEN', Application, ResolvedTargetVolume, 'SYSREPOS_TEMP', KeyID, StatusCode, @File_Error)
                                                                    Replication_Services('SendEmergencyAlert', Service, 'OPEN', Application, ResolvedTargetVolume, 'SYSREPOS_TEMP', KeyID, StatusCode, @File_Error)
                                                                end
                                                            end
                                                        end
                                                    end else
                                                        StatusFlag  = Get_Status(StatusCode)
                                                        If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'OPEN', Application, ResolvedTargetVolume, Table, KeyID, StatusCode, @File_Error)
                                                        Replication_Services('SendEmergencyAlert', Service, 'OPEN', Application, ResolvedTargetVolume, Table, KeyID, StatusCode, @File_Error)
                                                    end
                                                end
                                            end else
                                                StatusFlag  = Get_Status(StatusCode)
                                                If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'DETACH', Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error)
                                                Replication_Services('SendEmergencyAlert', Service, 'DETACH', Application, ResolvedSourceVolume, Table, KeyID, StatusCode, @File_Error)
                                            end
                                        end

                                        // Perform the action as needed.
                                        Begin Case
                                            Case Action _EQC 'WRITE'
                                                SourceRow   = SRP_HashTable_Get(hHashTable, KeyID)
                                                Write SourceRow to hTargetTable, KeyID then
                                                    If Table _EQC 'SYSPROCS' then
                                                        // Check to see if object was found that needs to be replicated.
                                                        If SRP_HashTable_Contains(hHashTable, '$' : KeyID) then
                                                            SourceRowObj64  = SRP_HashTable_Get(hHashTable, '$' : KeyID)
                                                            SourceRowObj    = SRP_Decode(SourceRowObj64, 'BASE64')
                                                            Write SourceRowObj to hTargetSysObj, '$' : KeyID else
                                                                StatusFlag  = Get_Status(StatusCode)
                                                                If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'WRITE', Application, ResolvedTargetVolume, 'SYSOBJ', '$' : KeyID, StatusCode, @File_Error)
                                                                Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, ResolvedTargetVolume, 'SYSOBJ', '$' : KeyID, StatusCode, @File_Error)
                                                            end
                                                        end
                                                        // Check to see if repository entity pointers were found that needs to be replicated.
                                                        SourceApplication   = Field(KeyID, '*', 2, 1)
                                                        If SourceApplication EQ '' then SourceApplication = 'SYSPROG'
                                                        SysReposSourceKeyID = SourceApplication : '*STPROC**' : KeyID[1, '*']
                                                        If SRP_HashTable_Contains(hHashTable, SysReposSourceKeyID) then
                                                            SourceReposStprocRow    = SRP_HashTable_Get(hHashTable, SysReposSourceKeyID)
                                                            Write SourceReposStprocRow to hTargetSysRepos, SysReposSourceKeyID else
                                                                StatusFlag  = Get_Status(StatusCode)
                                                                If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'WRITE', Application, ResolvedTargetVolume, 'SYSREPOS', SysReposSourceKeyID, StatusCode, @File_Error)
                                                                Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, ResolvedTargetVolume, 'SYSREPOS', SysReposSourceKeyID, StatusCode, @File_Error)
                                                            end
                                                        end
                                                        SysReposObjKeyID    = SourceApplication : '*STPROCEXE**' : KeyID[1, '*']
                                                        If SRP_HashTable_Contains(hHashTable, SysReposObjKeyID) then
                                                            SourceReposStprocExeRow = SRP_HashTable_Get(hHashTable, SysReposObjKeyID)
                                                            Write SourceReposStprocExeRow to hTargetSysRepos, SysReposObjKeyID else
                                                                StatusFlag  = Get_Status(StatusCode)
                                                                If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'WRITE', Application, ResolvedTargetVolume, 'SYSREPOS', SysReposObjKeyID, StatusCode, @File_Error)
                                                                Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, ResolvedTargetVolume, 'SYSREPOS', SysReposObjKeyID, StatusCode, @File_Error)
                                                            end
                                                        end
                                                    end
                                                    ActionCompleted = True$
                                                end else
                                                    StatusFlag  = Get_Status(StatusCode)
                                                    If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'WRITE', Application, ResolvedTargetVolume, Table, KeyID, StatusCode, @File_Error)
                                                    Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, ResolvedTargetVolume, Table, KeyID, StatusCode, @File_Error)
                                                end

                                            Case Action _EQC 'DELETE'
                                                Delete hTargetTable, KeyID then
                                                    ActionCompleted = True$
                                                end else
                                                    Read TargetRow from hTargetTable, KeyID then
                                                        StatusFlag  = Get_Status(StatusCode)
                                                        If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'DELETE', Application, ResolvedTargetVolume, Table, KeyID, StatusCode, @File_Error)
                                                        Replication_Services('SendEmergencyAlert', Service, 'DELETE', Application, ResolvedTargetVolume, Table, KeyID, StatusCode, @File_Error)
                                                    end else
                                                        // This means the row did not exist in the table. However, this is
                                                        // quite possible if there has been more activity with this row
                                                        // that deleted it prior to this transaction being processed.
                                                        ActionCompleted = True$
                                                    end
                                                end

                                            Case Action _EQC 'CLEARFILE'
                                                // Performing a ClearFile statement can have disastorous consequences if performed
                                                // against a non-intended table, such as a system table. Make sure only valid tables
                                                // are being cleared.
                                                PerformClearFile    = True$ ; // Assume the table will be cleared for now.
                                                Begin Case
                                                    Case SourceVolume _EQC 'REVBOOT'
                                                        PerformClearFile    = False$
                                                        AdditionalMessage   = 'An attempt was made to try and clear a table in the ' : SourceVolume : ' volume.'
                                                        If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'CLEARFILE', Application, ResolvedTargetVolume, Table, '', '', '', '', AdditionalMessage)
                                                        Replication_Services('SendEmergencyAlert', Service, 'CLEARFILE', Application, ResolvedTargetVolume, Table, '', '', '', '', AdditionalMessage)

                                                    Case Table _EQC 'SYSPROCS'
                                                        PerformClearFile    = False$
                                                        AdditionalMessage   = 'An attempt was made to try and clear the ' : Table : ' table.'
                                                        If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'CLEARFILE', Application, ResolvedTargetVolume, Table, '', '', '', '', AdditionalMessage)
                                                        Replication_Services('SendEmergencyAlert', Service, 'CLEARFILE', Application, ResolvedTargetVolume, Table, '', '', '', '', AdditionalMessage)

                                                    Case Table[1, 8] _EQC 'SYSREPOS'
                                                        PerformClearFile    = False$
                                                        AdditionalMessage   = 'An attempt was made to try and clear the ' : Table : ' table.'
                                                        If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'CLEARFILE', Application, ResolvedTargetVolume, Table, '', '', '', '', AdditionalMessage)
                                                        Replication_Services('SendEmergencyAlert', Service, 'CLEARFILE', Application, ResolvedTargetVolume, Table, '', '', '', '', AdditionalMessage)
                                                End Case

                                                If PerformClearFile EQ True$ then
                                                    ClearFile hTargetTable then
                                                        ActionCompleted = True$
                                                    end else
                                                        StatusFlag  = Get_Status(StatusCode)
                                                        If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'CLEARFILE', Application, ResolvedTargetVolume, Table, '', StatusCode, @File_Error)
                                                        Replication_Services('SendEmergencyAlert', Service, 'CLEARFILE', Application, ResolvedTargetVolume, Table, '', StatusCode, @File_Error)
                                                    end
                                                end

                                            Case Action _EQC 'IGNORE'
                                                // If a normal action (e.g., WRITE) cannot be processed due to normal reasons, the
                                                // action is dynamically changed to IGNORE so that the service can continue without
                                                // erroring out. The transaction ID will also be deleted.
                                                ActionCompleted = True$

                                        End Case
                                    end

                                    // If the action completed successfully (and the action is not SKIP) then delete the transaction
                                    // from the queue table so it will not be processed again.
                                    If ActionCompleted EQ True$ then
*                                       Replication_Services('PostToDailyLog')
                                        Delete hQueueTable, InProcessSK else
                                            // The transaction ID might not exist due to SKIPing. Just clear the error and move on.
                                            Set_Status(0)
                                        end
                                        LookupKeyID = Action:'*':Table:'*':KeyID
                                        Delete hQueueTable, LookupKeyID else
                                            // The lookup key might not exist due to SKIPing. Just clear the error and move on.
                                            Set_Status(0)
                                            // Log failure
                                            LogData    = ''
                                            LogData<1> = LoggingDTM
                                            LogData<2> = LookupKeyID
                                            LogData<3> = hQueueTable
                                            Logging_Services('AppendLog', objLog, LogData, @RM, @FM)
                                        end
                                    end
                                end else
                                    // SKIP actions are always completed.
                                    ActionCompleted = True$
                                end
                            // Only continue to process transactions if there are no unexpected problems that would cause
                            // an action to not complete.
                            While ActionCompleted EQ True$
                            Next InProcessSK

                            If ResolvedTargetVolume NE '' then
                                // Check to see if the target SYSREPOS table was opened. If so, run Repos_Detach_Trans to
                                // properly detach the REPOS_BFS volume.
                                Open 'SYSREPOS_TEMP' to hTargetSysRepos then
                                    Repos_Detach_Trans(ResolvedTargetVolume : '', 1)
                                end

                                Set_Status(0)
                                Detach_Volume(ResolvedTargetVolume : '', Success)
                            end else
                                // There was no resolved target volume (probably due to a custom replication procedure
                                // being called instead. So just set the Success flag to true.
                                Success = True$
                            end

                            If Success EQ True$ then
                                // InProcessSK is now 1 greater. Save this as the NextInProcessSK value.
                                If FirstSkippedSK NE '' then Transfer FirstSkippedSK to InProcessSK
                                Write InProcessSK to hQueueTable, InProcessSKID then
                                    If Replication_Services('LockReplicationQueueCounter', hQueueTable, PendingSKID) then
                                        // Read the Next Pending counter again since it might have changed. But now it is locked so this
                                        // value won't change until it is unlocked.
                                        Read NextPendingSK from hQueueTable, PendingSKID else NextPendingSK = 1
                                        If InProcessSK EQ NextPendingSK then
                                            Write 1 to hQueueTable, PendingSKID else
                                                StatusFlag  = Get_Status(StatusCode)
                                                If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'WRITE', Application, ResolvedSourceVolume, 'ReplicationQueue for ' : Table, PendingSKID, StatusCode, @File_Error)
                                                Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, ResolvedSourceVolume, 'ReplicationQueue for ' : Table, PendingSKID, StatusCode, @File_Error)
                                            end

                                            Write 1 to hQueueTable, InProcessSKID else
                                                StatusFlag  = Get_Status(StatusCode)
                                                If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'WRITE', Application, ResolvedSourceVolume, 'ReplicationQueue for ' : Table, InProcessSKID, StatusCode, @File_Error)
                                                Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, ResolvedSourceVolume, 'ReplicationQueue for ' : Table, InProcessSKID, StatusCode, @File_Error)
                                            end
                                        end
                                        Replication_Services('UnlockReplicationQueueCounter', hQueueTable, PendingSKID)
                                    end
                                end else
                                    StatusFlag  = Get_Status(StatusCode)
                                    If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'WRITE', Application, ResolvedSourceVolume, 'ReplicationQueue for ' : Table, InProcessSKID, StatusCode, @File_Error)
                                    Replication_Services('SendEmergencyAlert', Service, 'WRITE', Application, ResolvedSourceVolume, 'ReplicationQueue for ' : Table, InProcessSKID, StatusCode, @File_Error)
                                end
                            end else
                                StatusFlag  = Get_Status(StatusCode)
                                If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, 'DETACH', Application, ResolvedTargetVolume, Table, '', StatusCode, @File_Error)
                                Replication_Services('SendEmergencyAlert', Service, 'DETACH', Application, ResolvedTargetVolume, Table, '', StatusCode, @File_Error)
                            end
                        end
                        SRP_HashTable_Release(hHashTable)
                    end
                    Replication_Services('UnlockReplicationQueueCounter', hQueueTable, InProcessSKID)
                    Write '' to hQueueTable, '%EngineName%' else Null
                end

            Case ServerType _EQC 'Target'
                // Reserved for future use.

            Case Otherwise$
                // This is an invalid Server Type. Send an alert and stop the service.
                AdditionalMessage   = Quote(ServerType) : ' is an invalid server type.'
                If LoggingEnabled EQ True$ then Replication_Services('LogError', Service, '', '', '', '', '', '', '', '', AdditionalMessage)
                Replication_Services('SendEmergencyAlert', Service, '', '', '', '', '', '', '', '', AdditionalMessage)

        End Case

    end else
        // Either the replication system has been disabled or stopped, or this is a replication server. Abort this
        // service and tell the SRP Engine Server to quit. If the system has been stopped, reset the SystemStopFlag
        // to False since this should only be a temporary.
        If SystemStopFlag EQ True$ then
            Replication_Services('SetSystemStopFlag', False$)
        end
        
        Send_Info('SRPENGINESERVERQUIT')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// WriteDatabaseRow
//
// TransactionID - The transaction ID that contains the relevant information for the replication action.
//
// Writes the database row into the target server's database table.
//----------------------------------------------------------------------------------------------------------------------
Service WriteDatabaseRow(TransactionID)

    ActionCompleted = False$    ; // Assume the action did not complete for now.

    If TransactionID NE '' then
        Action              = TransactionID[1, @FM]
        Application         = TransactionID[Col2() + 1, @FM]
        SourceVolume        = TransactionID[Col2() + 1, @FM]
        Table               = TransactionID[Col2() + 1, @FM]
        KeyID               = TransactionID[Col2() + 1, 999]

        // Attempt to open the table that the source row resides in. However, make sure that any paths are resolved to
        // the official source server path (which should be in UNC format). The SourceVolume variable will likely
        // contain a mapped drive and this might not be available to the replication service.
        SourceServerPath    = UCase(Replication_Services('GetSourceServerPath'))
        If Index(SourceVolume, ':\', 1) then
            // Source volume has a mapped drive. Replace with the source server path.
            MappedDrive = SourceVolume[1, 3]
            Swap MappedDrive with SourceServerPath in SourceVolume
        end
        Set_Status(0)
        Attach_Table(SourceVolume, Table, Application, '')
        If Get_Status(StatusCode) then
            Set_Status(0)
        end else
            Open Table to hSourceTable then
                // The table did not open. It could be because it is not attached.
                Read SourceRow from hSourceTable, KeyID then
                    Set_Status(0)
                    Detach_Volume(SourceVolume, Success)
                    If Success EQ True$ then
                        Set_Status(0)
                        TargetServerPath    = UCase(Replication_Services('GetTargetServerPath'))
                        TargetVolume        = UCase(SourceVolume)
                        Swap SourceServerPath with TargetServerPath in TargetVolume
                        Attach_Table(TargetVolume, Table, Application, '')
                        If Get_Status(StatusCode) then
                            Set_Status(0)
                        end else
                            Open Table to hTargetTable then
                                Write SourceRow to hTargetTable, KeyID then
                                    ActionCompleted = True$
                                end
                            end
                        end
                        Set_Status(0)
                        Detach_Volume(TargetVolume, Success)
                        Set_Status(0)
                        Attach_Table(SourceVolume, Table, Application, '')
                        If Get_Status(StatusCode) then
                            Set_Status(0)
                        end
                    end
                end
            end else
                Error_Services('Add', 'Unable to open the ' : Table : ' table in the ' : Service : ' service.')
            end
        end
    end else
        Error_Services('Add', 'TransactionID argument was missing from the ' : Service : ' service.')
    end

    Response    = ActionCompleted

end service


//----------------------------------------------------------------------------------------------------------------------
// DeleteDatabaseRow
//
// TransactionID - The transaction ID that contains the relevant information for the replication action.
//
// Deletes the database row from the target server's database table.
//----------------------------------------------------------------------------------------------------------------------
Service DeleteDatabaseRow(TransactionID)

    ActionCompleted = False$    ; // Assume the action did not complete for now.

    If TransactionID NE '' then
        Action              = TransactionID[1, @FM]
        Application         = TransactionID[Col2() + 1, @FM]
        SourceVolume        = TransactionID[Col2() + 1, @FM]
        Table               = TransactionID[Col2() + 1, @FM]
        KeyID               = TransactionID[Col2() + 1, 999]

        // Attempt to open the table that the source row resides in. However, make sure that any paths are resolved to
        // the official source server path (which should be in UNC format). The SourceVolume variable will likely
        // contain a mapped drive and this might not be available to the replication service.
        SourceServerPath    = UCase(Replication_Services('GetSourceServerPath'))
        If Index(SourceVolume, ':\', 1) then
            // Source volume has a mapped drive. Replace with the source server path.
            MappedDrive = SourceVolume[1, 3]
            Swap MappedDrive with SourceServerPath in SourceVolume
        end
        Set_Status(0)
        Attach_Table(SourceVolume, Table, Application, '')
        If Get_Status(StatusCode) then
            Set_Status(0)
        end else
            Open Table to hSourceTable then
                // The table did not open. It could be because it is not attached.
                Read SourceRow from hSourceTable, KeyID then
                    Set_Status(0)
                    Detach_Volume(SourceVolume, Success)
                    If Success EQ True$ then
                        Set_Status(0)
                        TargetServerPath    = UCase(Replication_Services('GetTargetServerPath'))
                        TargetVolume        = UCase(SourceVolume)
                        Swap SourceServerPath with TargetServerPath in TargetVolume
                        Attach_Table(TargetVolume, Table, Application, '')
                        If Get_Status(StatusCode) then
                            Set_Status(0)
                        end else
                            Open Table to hTargetTable then
                                Write SourceRow to hTargetTable, KeyID then
                                    ActionCompleted = True$
                                end
                            end
                        end
                        Set_Status(0)
                        Detach_Volume(TargetVolume, Success)
                        Set_Status(0)
                        Attach_Table(SourceVolume, Table, Application, '')
                        If Get_Status(StatusCode) then
                            Set_Status(0)
                        end
                    end
                end
            end else
                Error_Services('Add', 'Unable to open the ' : Table : ' table in the ' : Service : ' service.')
            end
        end
    end else
        Error_Services('Add', 'TransactionID argument was missing from the ' : Service : ' service.')
    end

    Response    = ActionCompleted

end service


//----------------------------------------------------------------------------------------------------------------------
// SetReplicationTables
//
// ReplicationTables    - An @FM/@VM formatted array of database tables and related attributes. - [Optional]
//
// If the ReplicaationTables argument is empty, it will clear the current settings. The format of the ReplicationTables
// array is as follows:
//
// <x, 1> = Table name
// <x, 2> = Database ID
// <x, 3> = Volume
// <x, 4> = Boolean flag to indicate if the table has BASE_MFS installed
// <x, 5> = Boolean flag to indicate if the table is not-suspended from being queued
// <x, 6> = Boolean flag to indicate Is the table is not-suspended from being replicated
// <x, 7> = Type of queue for this table
//----------------------------------------------------------------------------------------------------------------------
Service SetReplicationTables(ReplicationTables)

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_TABLES'
        Lock hRepConfigTable, RepConfigKeyID then
            ReplicationTables = SRP_Array('SortRows', ReplicationTables, 'AL2' : @FM : 'AL1', 'LIST', @FM, @VM)
            Write ReplicationTables to hRepConfigTable, RepConfigKeyID else
                Error_Services('Add', 'Error writing the replication tables list in the ' : Service : ' service.')
            end
            Unlock hRepConfigTable, RepConfigKeyID
        end else
            Error_Services('Add', 'Error locking the replication tables list in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetReplicationTables
//
// Returns an @FM/@VM array of database tables that the Replication Manager is aware of.
//----------------------------------------------------------------------------------------------------------------------
Service GetReplicationTables()

    ReplicationTables = ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_TABLES'
        Read ReplicationTables from hRepConfigTable, RepConfigKeyID else ReplicationTables = ''
    end

    Response = ReplicationTables

end service


//----------------------------------------------------------------------------------------------------------------------
// GetVolumeTables
//
// Volume   - The volume to search for tables. - [Required]
//
// Returns an @FM/@VM array of table information for the indicated volume. Indexes and Dictionaries are excluded.
//----------------------------------------------------------------------------------------------------------------------
Service GetVolumeTables(Volume)

    VolumeTables = ''

    If Volume NE '' then
        rv           = Set_Status(0)
        VolumeTables = List_Volume_Sub(Volume, '', 'TABLE_NAME' : @FM : 'TABLE_DATABASE_ID', '')
        If Get_Status(StatusCode) then
            Error_Services('Add', 'Error calling the List_Volume_Sub function in the ' : Service : ' service. Status Code : ' : StatusCode)
        end else
            // Tables were found. Remove dictionaries and indexes.
            NumTables    = DCount(VolumeTables, @FM)
            For TableCnt = NumTables to 1 Step -1
                If (VolumeTables<TableCnt>[1, 1] EQ '!') OR (VolumeTables<TableCnt>[1, 5] EQ 'DICT.') then
                    VolumeTables = Delete(VolumeTables, TableCnt, 0, 0)
                end
            Next TableCnt
            // Sort the list by database ID and then by table name.
            VolumeTables = SRP_Array('SortRows', VolumeTables, 'AL2' : @FM : 'AL1', 'LIST', @FM, @VM)
        end
    end else
        Error_Services('Add', 'Volume argument was missing in the ' : Service : ' service.')
    end

    Response = VolumeTables

end service


//----------------------------------------------------------------------------------------------------------------------
// AddBaseMFSToTable
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
// Volume       - Volume where the table resides. - [Required]
//
// Adds (or installs) the BASE_MFS modified filing system routine to the indicated table.
//----------------------------------------------------------------------------------------------------------------------
Service AddBaseMFSToTable(TableName, DatabaseID, Volume)

    If (TableName NE '') AND (DatabaseID NE '') AND (Volume NE '') then
        Set_Status(0)
        Alias_Table(Volume : '', 'SYSPROG', 'REVMEDIA', 'REVMEDIA_TEMP')
        If Get_Status(StatusCode) then
            StatusFlag  = Get_Status(StatusCode)
            Error_Services('Add', 'Error aliasing REVMEDIA from ' : Volume : ' in the ' : Service : ' service: ' : StatusCode)
        end else
            TableRow = Database_Services('ReadDataRow', 'REVMEDIA_TEMP', TableName : '*' : DatabaseID)
            If Error_Services('NoError') then
                MFSList = TableRow<2>
                Locate 'BASE_MFS' in MFSList using @VM setting Pos else
                    MFSList<0, -1>  = 'BASE_MFS'
                    TableRow<2>     = MFSList
                    Database_Services('WriteDataRow', 'REVMEDIA_TEMP', TableName : '*' : DatabaseID, TableRow)
                end
                ReplicationTables = Replication_Services('GetReplicationTables')
                If Error_Services('NoError') then
                    TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
                    (TableNames, DatabaseIDs) using @FM = TableArray
                    TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
                    ThisTableItemKey = TableName : @VM : DatabaseID
                    Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                        ReplicationTables<Pos, RepTabHasBaseMFS$> = True$
                        Replication_Services('SetReplicationTables', ReplicationTables)
                    end
                end
            end
            Error     = Error_Services('GetMessage')
            Set_Status(0)
            Detach_Table('REVMEDIA_TEMP')
            Database_Services('ClearTableHandle', 'REVMEDIA_TEMP')
            Set_Status(0)
            // Using the absolute path to the REVBOOT folder will confuse Attach_Table, so check for a match and then
            // replace with REVBOOT if needed.
            If Volume _EQC Drive() then Volume = 'REVBOOT'
            Attach_Table(Volume : '', TableName : '', DatabaseID : '', '')
            Error_Services('Add', Error)
        end
    end else
        Error_Services('Add', 'TableName, DatabaseID, or Volume argument was missing from the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// RemoveBaseMFSFromTable
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
// Volume       - Volume where the table resides. - [Required]
//
// Removes the BASE_MFS modified filing system routine from the indicated table.
//----------------------------------------------------------------------------------------------------------------------
Service RemoveBaseMFSFromTable(TableName, DatabaseID, Volume)

    If (TableName NE '') AND (DatabaseID NE '') AND (Volume NE '') then
        Set_Status(0)
        Alias_Table(Volume : '', 'SYSPROG', 'REVMEDIA', 'REVMEDIA_TEMP')
        If Get_Status(StatusCode) then
            StatusFlag  = Get_Status(StatusCode)
            Error_Services('Add', 'Error aliasing REVMEDIA from ' : Volume : ' in the ' : Service : ' service: ' : StatusCode)
        end else
            TableRow = Database_Services('ReadDataRow', 'REVMEDIA_TEMP', TableName : '*' : DatabaseID)
            If Error_Services('NoError') then
                MFSList = TableRow<2>
                Locate 'BASE_MFS' in MFSList using @VM setting Pos then
                    MFSList           = Delete(MFSList, 0, Pos, 0)
                    TableRow<2>       = MFSList
                    Database_Services('WriteDataRow', 'REVMEDIA_TEMP', TableName : '*' : DatabaseID, TableRow)
                end
                ReplicationTables = Replication_Services('GetReplicationTables')
                If Error_Services('NoError') then
                    TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
                    (TableNames, DatabaseIDs) using @FM = TableArray
                    TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
                    ThisTableItemKey = TableName : @VM : DatabaseID
                    Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                        ReplicationTables<Pos, RepTabHasBaseMFS$> = False$
                        Replication_Services('SetReplicationTables', ReplicationTables)
                    end
                end
            end
            Error = Error_Services('GetMessage')
            Set_Status(0)
            Detach_Table('REVMEDIA_TEMP')
            Database_Services('ClearTableHandle', 'REVMEDIA_TEMP')
            Set_Status(0)
            // Using the absolute path to the REVBOOT folder will confuse Attach_Table, so check for a match and then
            // replace with REVBOOT if needed.
            If Volume _EQC Drive() then Volume = 'REVBOOT'
            Attach_Table(Volume : '', TableName : '', DatabaseID : '', '')
            Error_Services('Add', Error)
        end
    end else
        Error_Services('Add', 'TableName, DatabaseID, or Volume argument was missing from the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// HasBaseMFSInstalled
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
//
// Returns a Boolean flag if the indicated table has BASE_MFS already installed.
//----------------------------------------------------------------------------------------------------------------------
Service HasBaseMFSInstalled(TableName, DatabaseID, Volume)

    BaseMFSInstalled = False$ ; // Assume false for now.

    If (TableName NE '') AND (DatabaseID NE '') AND (Volume NE '') then
        Set_Status(0)
        Alias_Table(Volume : '', 'SYSPROG', 'REVMEDIA', 'REVMEDIA_TEMP')
        If Get_Status(StatusCode) then
            StatusFlag  = Get_Status(StatusCode)
            Error_Services('Add', 'Error aliasing REVMEDIA from ' : Volume : ' in the ' : Service : ' service: ' : StatusCode)
        end else
            TableRow = Database_Services('ReadDataRow', 'REVMEDIA_TEMP', TableName : '*' : DatabaseID)
            If Error_Services('NoError') then
                MFSList = TableRow<2>
                Locate 'BASE_MFS' in MFSList using @VM setting Pos then
                    BaseMFSInstalled = True$
                end
            end
            Set_Status(0)
            Detach_Table('REVMEDIA_TEMP')
            Database_Services('ClearTableHandle', 'REVMEDIA_TEMP')
        end
    end else
        Error_Services('Add', 'TableName, DatabaseID, or Volume argument was missing from the ' : Service : ' service.')
    end

    Response = BaseMFSInstalled

end service


//----------------------------------------------------------------------------------------------------------------------
// IsReplicationTable
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
//
// Returns a Boolean flag if the indicated table should be considered for replication processing.
//----------------------------------------------------------------------------------------------------------------------
Service IsReplicationTable(TableName, DatabaseID)

    ReplicationTable = False$ ; // Assume false for now.

    If (TableName NE '') AND (DatabaseID NE '') then
        ReplicationTables = Replication_Services('GetReplicationTables')
        If Error_Services('NoError') then
            TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
            (TableNames, DatabaseIDs) using @FM = TableArray
            TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
            ThisTableItemKey = TableName : @VM : DatabaseID
            Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                ReplicationTable = True$
            end
        end
    end else
        Error_Services('Add', 'TableName or DatabaseID argument was missing from the ' : Service : ' service.')
    end

    Response = ReplicationTable

end service


//----------------------------------------------------------------------------------------------------------------------
// SuspendTableQueue
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
//
// Turns off the flag for the indicated table so that queue processes will be suspended (disabled). The table will still
// be considered for replication and any queued transactions will still be processed, but no new transactions will be
// queued.
//----------------------------------------------------------------------------------------------------------------------
Service SuspendTableQueue(TableName, DatabaseID)

    If (TableName NE '') AND (DatabaseID NE '') then
        ReplicationTables = Replication_Services('GetReplicationTables')
        If Error_Services('NoError') then
            TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
            (TableNames, DatabaseIDs) using @FM = TableArray
            TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
            ThisTableItemKey = TableName : @VM : DatabaseID
            Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                ReplicationTables<Pos, RepTabCanQueue$> = False$
                Replication_Services('SetReplicationTables', ReplicationTables)
            end
        end
    end else
        Error_Services('Add', 'TableName or DatabaseID argument was missing from the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// AllowTableQueue
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
//
// Turns on the flag for the indicated table so that queue processes will be allowed (enabled).
//----------------------------------------------------------------------------------------------------------------------
Service AllowTableQueue(TableName, DatabaseID)

    If (TableName NE '') AND (DatabaseID NE '') then
        ReplicationTables = Replication_Services('GetReplicationTables')
        If Error_Services('NoError') then
            TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
            (TableNames, DatabaseIDs) using @FM = TableArray
            TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
            ThisTableItemKey = TableName : @VM : DatabaseID
            Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                ReplicationTables<Pos, RepTabCanQueue$> = True$
                Replication_Services('SetReplicationTables', ReplicationTables)
            end
        end
    end else
        Error_Services('Add', 'TableName or DatabaseID argument was missing from the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// IsTableAllowedToQueue
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
//
// Returns a Boolean flag if the indicated table should be allowed to queue.
//----------------------------------------------------------------------------------------------------------------------
Service IsTableAllowedToQueue(TableName, DatabaseID)

    CanQueue = False$ ; // Assume false for now.

    If (TableName NE '') AND (DatabaseID NE '') then
        ReplicationTables = Replication_Services('GetReplicationTables')
        If Error_Services('NoError') then
            TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
            (TableNames, DatabaseIDs) using @FM = TableArray
            TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
            ThisTableItemKey = TableName : @VM : DatabaseID
            Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                CanQueue = ReplicationTables<Pos, RepTabCanQueue$>
            end
        end
    end else
        Error_Services('Add', 'TableName or DatabaseID argument was missing from the ' : Service : ' service.')
    end

    Response = CanQueue

end service


//----------------------------------------------------------------------------------------------------------------------
// SuspendTableReplication
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
//
// Turns off the flag for the indicated table so that replication processes will be suspended (disabled). The table will
// still be considered for replication and transactions will still be queued, but no queued transactions will be
// processed.
//----------------------------------------------------------------------------------------------------------------------
Service SuspendTableReplication(TableName, DatabaseID)

    If (TableName NE '') AND (DatabaseID NE '') then
        ReplicationTables = Replication_Services('GetReplicationTables')
        If Error_Services('NoError') then
            TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
            (TableNames, DatabaseIDs) using @FM = TableArray
            TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
            ThisTableItemKey = TableName : @VM : DatabaseID
            Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                ReplicationTables<Pos, RepTabCanRep$> = False$
                Replication_Services('SetReplicationTables', ReplicationTables)
            end
        end
    end else
        Error_Services('Add', 'TableName or DatabaseID argument was missing from the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// AllowTableReplication
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
//
// Turns on the flag for the indicated table so that replication processes will be allowed (enabled).
//----------------------------------------------------------------------------------------------------------------------
Service AllowTableReplication(TableName, DatabaseID)

    If (TableName NE '') AND (DatabaseID NE '') then
        ReplicationTables = Replication_Services('GetReplicationTables')
        If Error_Services('NoError') then
            TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
            (TableNames, DatabaseIDs) using @FM = TableArray
            TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
            ThisTableItemKey = TableName : @VM : DatabaseID
            Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                ReplicationTables<Pos, RepTabCanRep$> = True$
                Replication_Services('SetReplicationTables', ReplicationTables)
            end
        end
    end else
        Error_Services('Add', 'TableName or DatabaseID argument was missing from the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// IsTableAllowedToReplicate
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
//
// Returns a Boolean flag if the indicated table should be allowed to replicate.
//----------------------------------------------------------------------------------------------------------------------
Service IsTableAllowedToReplicate(TableName, DatabaseID)

    CanReplicate = False$ ; // Assume false for now.

    If (TableName NE '') AND (DatabaseID NE '') then
        ReplicationTables = Replication_Services('GetReplicationTables')
        If Error_Services('NoError') then
            TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
            (TableNames, DatabaseIDs) using @FM = TableArray
            TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
            ThisTableItemKey = TableName : @VM : DatabaseID
            Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                CanReplicate = ReplicationTables<Pos, RepTabCanRep$>
            end
        end
    end else
        Error_Services('Add', 'TableName or DatabaseID argument was missing from the ' : Service : ' service.')
    end

    Response = CanReplicate

end service


//----------------------------------------------------------------------------------------------------------------------
// SetTableQueueType
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
// QueueType    - Queue type to set. - [Required] 
//
// Sets a queue type to be associated to the indicated table.
//----------------------------------------------------------------------------------------------------------------------
Service SetTableQueueType(TableName, DatabaseID, QueueType)

    If (TableName NE '') AND (DatabaseID NE '') AND (QueueType NE '') then
        Locate QueueType in 'Public,Private' using ',' setting Pos then
            ReplicationTables = Replication_Services('GetReplicationTables')
            If Error_Services('NoError') then
                TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
                (TableNames, DatabaseIDs) using @FM = TableArray
                TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
                ThisTableItemKey = TableName : @VM : DatabaseID
                Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                    ReplicationTables<Pos, RepTabQueueType$> = QueueType
                    Replication_Services('SetReplicationTables', ReplicationTables)
                end
            end
        end else
            Error_Services('Add', 'In invalid queue type (' : QueueType : ') was pass into the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'TableName, DatabaseID, or QueueType argument was missing from the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTableQueueType
//
// TableName    - Name of the table. - [Required]
// DatabaseID   - Database the table belongs to. - [Required]
//
// Returns the queue type associated with the indicated table. If no queue type has been assigned, it will default to
// the Public queue type.
//----------------------------------------------------------------------------------------------------------------------
Service GetTableQueueType(TableName, DatabaseID)

    QueueType = 'Public'    ; // Assume Public for now

    If (TableName NE '') AND (DatabaseID NE '') then
        ReplicationTables = Replication_Services('GetReplicationTables')
        If Error_Services('NoError') then
            TableArray       = SRP_Array('Rotate', ReplicationTables, @FM, @VM)
            (TableNames, DatabaseIDs) using @FM = TableArray
            TableItemKeys    = SRP_Array('Rotate', TableNames : @FM : DatabaseIDs, @FM, @VM)
            ThisTableItemKey = TableName : @VM : DatabaseID
            Locate ThisTableItemKey in TableItemKeys using @FM setting Pos then
                QueueType = ReplicationTables<Pos, RepTabQueueType$>
            end
        end
    end else
        Error_Services('Add', 'TableName or DatabaseID argument was missing from the ' : Service : ' service.')
    end

    Response = QueueType

end service


//----------------------------------------------------------------------------------------------------------------------
// SetSystemStopFlag
//
// System Enabled Flag - Boolean flag to determine if the system should be stopped.
//
// Sets a Boolean flag indicating that the system should be stopped.
//----------------------------------------------------------------------------------------------------------------------
Service SetSystemStopFlag(SystemStopFlag)

    If Len(SystemStopFlag) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_SYSTEM_STOPPED'
            Lock hRepConfigTable, RepConfigKeyID then
                Write SystemStopFlag to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing system stopped flag in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking system stopped flag in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'SystemStopFlag arguement was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSystemStopFlag
//
// Returns a Boolean flag indicating if the system should be stopped. The default is False. If the system is stopped,
// then transactions should continue to be added to the pending queue files but not processed.
//----------------------------------------------------------------------------------------------------------------------
Service GetSystemStopFlag()

    SystemStopFlag  = True$ ; // Assume True for now. Only an explicit True will stop the system.

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_SYSTEM_STOPPED'
        Read SystemStopFlag from hRepConfigTable, RepConfigKeyID then
            If SystemStopFlag NE True$ then SystemEnabledFlag = False$
        end
    end

    Response    = SystemStopFlag

end service


//----------------------------------------------------------------------------------------------------------------------
// SetSystemEnabledFlag
//
// System Enabled Flag. Boolean flag to determine if the system is enabled.
//
// Sets a Boolean flag indicating if the system is currently enabled.
//----------------------------------------------------------------------------------------------------------------------
Service SetSystemEnabledFlag(SystemEnabledFlag)

    If Len(SystemEnabledFlag) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_SYSTEM_ENABLED'
            Lock hRepConfigTable, RepConfigKeyID then
                Write SystemEnabledFlag to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing system enabled flag in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking system enabled flag in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'SystemEnabledFlag arguement was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSystemEnabledFlag
//
// Returns a Boolean flag indicating if the system is currently enabled. The default is True. If the system is not
// enabled, then transactions should continue to be added to the pending queue files but not processed.
//----------------------------------------------------------------------------------------------------------------------
Service GetSystemEnabledFlag()

    SystemEnabledFlag   = True$ ; // Assume True for now. Only an explicit False will disable the system.

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_SYSTEM_ENABLED'
        Read SystemEnabledFlag from hRepConfigTable, RepConfigKeyID then
            If SystemEnabledFlag NE False$ then SystemEnabledFlag = True$
        end
    end

    Response    = SystemEnabledFlag

end service


//----------------------------------------------------------------------------------------------------------------------
// SetServerType
//
// Server Type - The server type. Valid options are 'Source' and 'Target'.
//
// Sets the server type.
//----------------------------------------------------------------------------------------------------------------------
Service SetServerType(ServerType)

    If Len(ServerType) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_SERVER_TYPE'
            Lock hRepConfigTable, RepConfigKeyID then
                Write ServerType to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing server type in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking server type in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'ServerType arguement was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetServerType
//
// Returns the type of server. Valid optons are 'Source' and 'Target'. Default is 'Source'.
//----------------------------------------------------------------------------------------------------------------------
Service GetServerType()

    ServerType  = 'Source'  ; // Assume Source unless otherwise noted.

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_SERVER_TYPE'
        Read ServerType from hRepConfigTable, RepConfigKeyID then
            If ServerType NE 'Source' AND  ServerType NE 'Target' then ServerType = 'Source'
        end
    end

    Response    = ServerType

end service


//----------------------------------------------------------------------------------------------------------------------
// SetSourceQueuePath
//
// QueuePath - Path to where the transaction queue files are maintained.
//
// Sets the OS path to where the transaction queue files are maintained.
//----------------------------------------------------------------------------------------------------------------------
Service SetSourceQueuePath(QueuePath)

    If Len(QueuePath) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_SOURCE_QUEUE_PATH'
            Lock hRepConfigTable, RepConfigKeyID then
                Write QueuePath to hRepConfigTable, RepConfigKeyID then
                    ServerType  = Replication_Services('GetServerType')
                    If ServerType _EQC 'Source' then
                        Replication_Services('CreateReplicationQueue', 'GLOBAL', 'PUBLIC')
                    end
                end else
                    Error_Services('Add', 'Error writing queue path in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking queue path in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSourceQueuePath
//
// Returns the OS path to where the transaction queues are maintained. This will always append a backslash to make it
// easier for calling services to work with.
//----------------------------------------------------------------------------------------------------------------------
Service GetSourceQueuePath()

    QueuePath = ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_SOURCE_QUEUE_PATH'
        Read QueuePath from hRepConfigTable, RepConfigKeyID else QueuePath = ''
        If SRP_Path('IsRelative', QueuePath) then
            QueuePath   = SRP_Path('Expand', QueuePath)
        end
        QueuePath   = SRP_Path('AddBackslash', QueuePath)
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = QueuePath

end service


//----------------------------------------------------------------------------------------------------------------------
// SetSourceServerPath
//
// SourceServerPath - Path to the root where the source tables live.
//
// Sets the OS path to where the source tables live.
//----------------------------------------------------------------------------------------------------------------------
Service SetSourceServerPath(SourceServerPath)

    If Len(SourceServerPath) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_SOURCE_SERVER_PATH'
            Lock hRepConfigTable, RepConfigKeyID then
                Write SourceServerPath to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing source server path in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking source server path in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSourceServerPath
//
// Returns the OS path to where the source tables live. This will always append a backslash to make it easier for
// calling services to work with.
//----------------------------------------------------------------------------------------------------------------------
Service GetSourceServerPath()

    SourceServerPath = ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_SOURCE_SERVER_PATH'
        Read SourceServerPath from hRepConfigTable, RepConfigKeyID then
            SourceServerPath    = SRP_Path('AddBackslash', SourceServerPath)
        end else
            SourceServerPath    = ''
        end
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = SourceServerPath

end service


//----------------------------------------------------------------------------------------------------------------------
// SetSourceREVBOOTPath
//
// SetSourceREVBOOTPath - Path to the source REVBOOT volume.
//
// Sets the OS path to where the source REVBOOT volume exists.
//----------------------------------------------------------------------------------------------------------------------
Service SetSourceREVBOOTPath(SourceREVBOOTPath)

    If Len(SourceREVBOOTPath) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_SOURCE_REVBOOT_PATH'
            Lock hRepConfigTable, RepConfigKeyID then
                Write SourceREVBOOTPath to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing source REVBOOT path in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking source REVBOOT path in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSourceREVBOOTPath
//
// Returns the OS path to where the source REVBOOT volume exists. This will always append a backslash to make it easier
// for calling services to work with.
//----------------------------------------------------------------------------------------------------------------------
Service GetSourceREVBOOTPath()

    SourceREVBOOTPath = ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_SOURCE_REVBOOT_PATH'
        Read SourceREVBOOTPath from hRepConfigTable, RepConfigKeyID then
            SourceREVBOOTPath   = SRP_Path('AddBackslash', SourceREVBOOTPath)
        end else
            SourceREVBOOTPath   = ''
        end
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = SourceREVBOOTPath

end service


//----------------------------------------------------------------------------------------------------------------------
// SetTargetQueuePath
//
// QueuePath - Path to where the transaction queue files are maintained.
//
// Sets the OS path to where the transaction queue files are maintained.
//----------------------------------------------------------------------------------------------------------------------
Service SetTargetQueuePath(QueuePath)

    If Len(QueuePath) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_TARGET_QUEUE_PATH'
            Lock hRepConfigTable, RepConfigKeyID then
                Write QueuePath to hRepConfigTable, RepConfigKeyID then
                    ServerType  = Replication_Services('GetServerType')
                    If ServerType _EQC 'Target' then
                        Replication_Services('CreateReplicationQueue', 'GLOBAL', 'PUBLIC')
                    end
                end else
                    Error_Services('Add', 'Error writing queue path in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking queue path in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTargetQueuePath
//
// Returns the OS path to where the transaction queues are maintained. This will always append a backslash to make it
// easier for calling services to work with.
//----------------------------------------------------------------------------------------------------------------------
Service GetTargetQueuePath()

    QueuePath = ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_TARGET_QUEUE_PATH'
        Read QueuePath from hRepConfigTable, RepConfigKeyID else QueuePath = ''
        If SRP_Path('IsRelative', QueuePath) then
            QueuePath   = SRP_Path('Expand', QueuePath)
        end
        QueuePath   = SRP_Path('AddBackslash', QueuePath)
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = QueuePath

end service


//----------------------------------------------------------------------------------------------------------------------
// SetTargetServerPath
//
// TargetServerPath - Path to the root where the target tables live.
//
// Sets the OS path to where the target tables live.
//----------------------------------------------------------------------------------------------------------------------
Service SetTargetServerPath(TargetServerPath)

    If Len(TargetServerPath) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_TARGET_SERVER_PATH'
            Lock hRepConfigTable, RepConfigKeyID then
                Write TargetServerPath to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing target server path in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking target server path in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTargetServerPath
//
// Returns the OS path to where the target tables live. This will always append a backslash to make it easier for
// calling services to work with.
//----------------------------------------------------------------------------------------------------------------------
Service GetTargetServerPath()

    TargetServerPath = ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_TARGET_SERVER_PATH'
        Read TargetServerPath from hRepConfigTable, RepConfigKeyID else TargetServerPath = ''
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = TargetServerPath

end service


//----------------------------------------------------------------------------------------------------------------------
// SetTargetREVBOOTPath
//
// SetTargetREVBOOTPath - Path to the Target REVBOOT volume.
//
// Sets the OS path to where the target REVBOOT volume exists.
//----------------------------------------------------------------------------------------------------------------------
Service SetTargetREVBOOTPath(TargetREVBOOTPath)

    If Len(TargetREVBOOTPath) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_TARGET_REVBOOT_PATH'
            Lock hRepConfigTable, RepConfigKeyID then
                Write TargetREVBOOTPath to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing target REVBOOT path in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking target REVBOOT path in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTargetREVBOOTPath
//
// Returns the OS path to where the target REVBOOT volume exists. This will always append a backslash to make it easier
// for calling services to work with.
//----------------------------------------------------------------------------------------------------------------------
Service GetTargetREVBOOTPath()

    TargetREVBOOTPath = ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_TARGET_REVBOOT_PATH'
        Read TargetREVBOOTPath from hRepConfigTable, RepConfigKeyID then
            TargetREVBOOTPath   = SRP_Path('AddBackslash', TargetREVBOOTPath)
        end else
            TargetREVBOOTPath   = ''
        end
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = TargetREVBOOTPath

end service


//----------------------------------------------------------------------------------------------------------------------
// SetErrorLogPath
//
// ErrorLogPath - Path to the where the error logs should be stored.
//
// Sets the OS path to where the error logs are stored.
//----------------------------------------------------------------------------------------------------------------------
Service SetErrorLogPath(ErrorLogPath)

    If Len(ErrorLogPath) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_ERROR_LOG_PATH'
            Lock hRepConfigTable, RepConfigKeyID then
                Write ErrorLogPath to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing error log path in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking error log path in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetErrorLogPath
//
// Returns the OS path to where error logs are stored. This will always append a backslash to make it easier
// for calling services to work with.
//----------------------------------------------------------------------------------------------------------------------
Service GetErrorLogPath()

    ErrorLogPath = ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_ERROR_LOG_PATH'
        Read ErrorLogPath from hRepConfigTable, RepConfigKeyID then
            ErrorLogPath = SRP_Path('AddBackslash', ErrorLogPath)
        end else
            ErrorLogPath = ''
        end
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = ErrorLogPath

end service


//----------------------------------------------------------------------------------------------------------------------
// IsLoggingEnabled
//
// Returns the OS path to where error logs are stored. This will always append a backslash to make it easier
// for calling services to work with.
//----------------------------------------------------------------------------------------------------------------------
Service IsLoggingEnabled()

    LoggingEnabled = Replication_Services('GetErrorLogPath') NE ''

    Response       = LoggingEnabled

end service


//----------------------------------------------------------------------------------------------------------------------
// GetReplicationQueueTableHandle
//
// Application - Name of the database application for the table. - [Optional if QueueTable is populated]
// Table       - Name of the database table for the queue file. - [Optional if QueueTable is populated]
// QueueTable  - Name of the actual queue table. This is ignored if Application and Table are populated. - [Optional]
//
// Returns the handle to the database table where the pending transactions are maintained for the indicated database
// table. This service also checks the Sizelock of the table to make sure it is not less than 2. If it is, it will
// automatically set the Sizelock to 4.
//----------------------------------------------------------------------------------------------------------------------
Service GetReplicationQueueTableHandle(Application, Table, QueueTable)

    Convert @Lower_Case to @Upper_Case in Application
    Convert @Lower_Case to @Upper_Case in Table

    If QueueTableList@      EQ ''   then QueueTableList@    = SRP_List('Create')
    If QueueHandleList@     EQ ''   then QueueHandleList@   = SRP_List('Create')
    If QueueTableExists@    EQ ''   then QueueTableExists@  = SRP_List('Create')

    hQueueTable = ''

    If (Len(Application) AND Len(Table)) OR (Len(QueueTable)) then
        If QueueTable EQ '' then QueueTable = Replication_Services('GetReplicationQueueTable', Application, Table)
        Index      = SRP_List('Locate', QueueTableList@, QueueTable)
        If Index GT 0 then
            HaveQueueTable  = SRP_List('GetAt', QueueTableExists@, Index)
            If HaveQueueTable then
                // The handle has already been created and stored. Pull from the queue handle list.
                hQueueTable = SRP_List('GetAt', QueueHandleList@, Index)
            end
        end else
            HaveQueueTable  = False$
            // Attempt to open the table.
            Set_Status(0)
            Open QueueTable to hQueueTable then
                HaveQueueTable  = True$
            end else
                // The table did not open. Attempt to attach and then open again.
                QueuePath       = Replication_Services('GetSourceQueuePath')
                Set_Status(0)
                Push.Select(F1, F2, F3, F4)
                Attach_Table(QueuePath : '', QueueTable, 'GLOBAL', '')
                Pop.Select(F1, F2, F3, F4)
                If Get_Status(StatusCode) then
                    Set_Status(0)
                    Error_Services('Add', 'Unable to get a table handle for the ' : QueueTable : ' table in the ' : Service : ' service.')
                end else
                    Set_Status(0)
                    Open QueueTable to hQueueTable then
                        HaveQueueTable  = True$
                    end else
                        Set_Status(0)
                    end
                end
            end
            SRP_List('Add', QueueTableList@, QueueTable)
            If HaveQueueTable then
                TableInfo   = Get_LH_Info(QueueTable)
                If TableInfo<5> LT 2 then
                    Fix_LH(QueueTable, 5, True$, 4)
                    TableInfo   = Get_LH_Info(QueueTable)
                    If Get_Status(StatusCode) then
                        Set_Status(0)
                        Error_Services('Add', 'Error attempting to set the sizelock for the ' : QueueTable : ' table in the ' : Service : ' service.')
                    end
                end
                SRP_List('Add', QueueHandleList@, hQueueTable)
                SRP_List('Add', QueueTableExists@, True$)
            end else
                SRP_List('Add', QueueHandleList@, '')
                SRP_List('Add', QueueTableExists@, False$)
            end
        end
    end else
        Error_Services('Add', 'One or more required arguments were missing from the ' : Service : ' service.')
    end

    Response    = hQueueTable

end service


//----------------------------------------------------------------------------------------------------------------------
// GetReplicationQueueTables
//
// Returns an @FM list of replication queue tables.
//----------------------------------------------------------------------------------------------------------------------
Service GetReplicationQueueTables()

    QueuePath       = Replication_Services('GetSourceQueuePath')

    QueueTables     = List_Volume_Sub(QueuePath, '', 'TABLE_NAME', '')
    rv              = Set_Status(0)
    NumQueueTables  = DCount(QueueTables, @FM)
    For ItemCnt     = NumQueueTables to 1 Step -1
        QueueTable  = QueueTables<ItemCnt>
        If QueueTable[1, 18] _EQC 'REPLICATION_QUEUE_' else
            QueueTables = Delete(QueueTables, ItemCnt, 0, 0)
        end
    Next ItemCount
    QueueTables = SRP_Array('SortRows', QueueTables, 'AL1', 'LIST', @FM, @VM)

    Response = QueueTables

end service


//----------------------------------------------------------------------------------------------------------------------
// GetReplicationQueueTable
//
// Application - Name of the database application for the table. - [Required]
// Table - Name of the database table for the queue file. - [Required]
//
// Returns the database table where the pending transactions are maintained for the indicated database table.
//----------------------------------------------------------------------------------------------------------------------
Service GetReplicationQueueTable(Application, Table)

    Convert @Lower_Case to @Upper_Case in Application
    Convert @Lower_Case to @Upper_Case in Table

    If Len(Application) AND Len(Table) then
        QueueType = Replication_Services('GetTableQueueType', Table, Application)
        If QueueType EQ 'Private' then
            QueueTable  = 'REPLICATION_QUEUE_' : Application : '_' : Table
        end else
            QueueTable  = 'REPLICATION_QUEUE_GLOBAL_PUBLIC'
        end
    end else
        Error_Services('Add', 'Application or Table arguments were missing from the ' : Service : ' service.')
    end

    Response    = QueueTable

end service


//----------------------------------------------------------------------------------------------------------------------
// GetAllReplicationQueueTableStatus
//
// Returns the %NextInProcessSK% value, %NextPendingSK% value, and total estimated number of rows for all replication
// queue tables. This will be a @FM/@VM list (i.e., QueueTable1 : @VM : Status1 : @FM : QueueTable2 : @VM : Status2).
//----------------------------------------------------------------------------------------------------------------------
Service GetAllReplicationQueueTableStatus()

    AllReplicationQueueTableStatus  = ''

    AllQueueTables  = Replication_Services('GetReplicationQueueTables')
    For Each QueueTable in AllQueueTables using @FM
        AllReplicationQueueTableStatus  := QueueTable : @VM
        AllReplicationQueueTableStatus  := Replication_Services('GetReplicationQueueTableStatus', QueueTable) : @FM
    Next QueueTable

    AllReplicationQueueTableStatus[-1, 1]   = ''    ; // Strip off the final @FM

    Response    = AllReplicationQueueTableStatus

end service


//----------------------------------------------------------------------------------------------------------------------
// GetReplicationQueueTableStatus
//
// QueueTable - Name of the replication queue table. - [Required]
//
// Returns the %EngineName% value, %NextInProcessSK% value, %NextPendingSK% value, and total estimated number of rows in
// the indicated queue table.
//----------------------------------------------------------------------------------------------------------------------
Service GetReplicationQueueTableStatus(QueueTable)

    Convert @Lower_Case to @Upper_Case in QueueTable

    QueueTableStatus    = ''

    If QueueTable NE '' then
        QueuePath   = Replication_Services('GetSourceQueuePath')
        If Error_Services('NoError') then
            Set_Status(0)
            ShouldDetach    = False$
            // First attempt to open the private replication queue table.
            Open QueueTable to hQueueTable else
                // Since the Open statement didn't work, attempt to attach the private replication queue table.
                Set_Status(0)
                Attach_Table(QueuePath : '', QueueTable, 'GLOBAL', '')
                If Get_Status(StatusCode) then
                    Error_Services('Add', 'Unable to attach ' : QueueTable : ' in the ' : Service : ' service.')
                end else
                    // Make sure table is detached since it was not attached already.
                    ShouldDetach    = True$
                    Set_Status(0)
                    Open QueueTable to hQueueTable else
                        Set_Status(0)
                        Error_Services('Add', 'Unable to open ' : QueueTable : ' in the ' : Service : ' service.')
                    end
                end
            end
            If Error_Services('NoError') then
                TotalRows       = Get.RecCount(hQueueTable, Status, False$)
                // If any of the queue counters have values, adjust the TotalRows count accordingly.
                Read NextInProcessSK from hQueueTable, '%NextInProcessSK%' then
                    TotalRows -= 1
                end else
                    NextInProcessSK = ''
                end
                Read NextPendingSK from hQueueTable, '%NextPendingSK%' then
                    TotalRows -= 1
                end else
                    NextPendingSK = ''
                end
            	Read EngineName from hQueueTable, '%EngineName%' then
            	    TotalRows -= 1
            	end else
            	    EngineName = ''
            	end
                Read NumProcesses from hQueueTable, '%NumProcesses%' then
                    TotalRows -= 1
                end else
                    NumProcesses = 0
                end
                QueueTableStatus = EngineName : @VM : NextInProcessSK : @VM : NextPendingSK : @VM : TotalRows : @VM : NumProcesses
                Set_Status(0)
                If ShouldDetach = True$ then Detach_Table(QueueTable)
            end
        end
    end else
        Error_Services('Add', 'QueueTable argument was missing from the ' : Service : ' service.')
    end

    Response    = QueueTableStatus

end service


//----------------------------------------------------------------------------------------------------------------------
// CreateReplicationQueue
//
// Application  - Name of the database application for the table. - [Required]
// Table        - Name of the database table. - [Required]
//
// Creates a replication queue table for the indicated database table. This is created in the designated queue table
// volume.
//----------------------------------------------------------------------------------------------------------------------
Service CreateReplicationQueue(Application, Table)

    Convert @Lower_Case to @Upper_Case in Application
    Convert @Lower_Case to @Upper_Case in Table

    ReplicationQueueCreated = False$    ; // Assume this will not be created for now.

    If Len(Application) AND Len(Table) then
        QueuePath   = Replication_Services('GetSourceQueuePath')
        If Error_Services('NoError') then
            QueueTable     = Replication_Services('GetReplicationQueueTable', Application, Table)
            HaveQueueTable = False$
            // First attempt to open the private replication queue table.
            Set_Status(0)
            Open QueueTable to hQueueTable then
                HaveQueueTable  = True$
            end else
                // Since the Open statement didn't work, attempt to attach the private replication queue table.
                Set_Status(0)
                Attach_Table(QueuePath : '', QueueTable, 'GLOBAL', '')
                If Get_Status(StatusCode) then
                    If StatusCode[1, @VM] NE 'SSP280' then
                        Set_Status(0)
                        Convert @VM to @TM in StatusCode
                        Error_Services('Add', 'Error creating the ' : QueueTable : ' table in the ' : Service : ' service.' : @TM : @TM : 'StatusCode' : @TM : '=========' : @TM : StatusCode)
                    end
                end else
                    HaveQueueTable  = True$
                end
            end

            If Error_Services('NoError') AND Not(HaveQueueTable) then
                // The private replication queue table is not available, create the table using default attributes.
                Attributes      = ''
                Attributes<1>   = 1500000   ; // Estimated number of rows.
                Attributes<2>   = 100       ; // Average row size.
                Attributes<3>   = 3         ; // Estimated number of dictionary rows.
                Attributes<4>   = 4096      ; // Framesize.
                Attributes<5>   = 80        ; // Resize threshold.
                Set_Status(0)
                Create_Table(QueuePath, QueueTable, False$, 'GLOBAL', Attributes, False$)
                If Get_Status(StatusCode) then
                    Set_Status(0)
                    Convert @VM to @TM in StatusCode
                    Error_Services('Add', 'Error creating the ' : QueueTable : ' table in the ' : Service : ' service.' : @TM : @TM : 'StatusCode' : @TM : '=========' : @TM : StatusCode)
                end else
                    // The table has been created. Set the resize lock to 4.
                    Fix_LH(QueueTable, 5, True$, 4)
                    ReplicationQueueCreated = True$
                end
            end
        end
    end else
        Error_Services('Add', 'Application or Table arguments were missing from the ' : Service : ' service.')
    end

    // Always release the cached list for the queue tables if this service is called.
    If QueueTableList@      NE ''   then SRP_List('Release', QueueTableList@)   ; QueueTableList@   = ''
    If QueueHandleList@     NE ''   then SRP_List('Release', QueueHandleList@)  ; QueueHandleList@  = ''
    If QueueTableExists@    NE ''   then SRP_List('Release', QueueTableExists@) ; QueueTableExists@ = ''

    Response    = ReplicationQueueCreated

end service


//----------------------------------------------------------------------------------------------------------------------
// DeleteReplicationQueue
//
// Application  - Name of the database application for the table. - [Required]
// Table        - Name of the database table. - [Required]
//
// Deletes the replication queue table for the indicated database table.
//----------------------------------------------------------------------------------------------------------------------
Service DeleteReplicationQueue(Application, Table)

    Convert @Lower_Case to @Upper_Case in Application
    Convert @Lower_Case to @Upper_Case in Table

    ReplicationQueueDeleted = False$    ; // Assume this will not be deleted for now.

    If Len(Application) AND Len(Table) then
        QueuePath   = Replication_Services('GetSourceQueuePath')
        If Error_Services('NoError') then
            QueueTable     = Replication_Services('GetReplicationQueueTable', Application, Table)
            HaveQueueTable  = False$
            // First attempt to open the private replication queue table.
            Set_Status(0)
            Open QueueTable to hQueueTable then
                HaveQueueTable  = True$
            end else
                // Since the Open statement didn't work, attempt to attach the private replication queue table.
                Set_Status(0)
                Attach_Table(QueuePath : '', QueueTable, 'GLOBAL', '')
                If Get_Status(StatusCode) else
                    HaveQueueTable  = True$
                end
            end

            If HaveQueueTable then
                // The replication queue table is available, delete using the DATA prefix as there should be
                // no dictionary.
                Set_Status(0)
                Delete_Table('DATA.' : QueueTable, True$, Status)
                If Get_Status(StatusCode) then
                    Set_Status(0)
                    Error_Services('Add', 'Error deleting the ' : QueueTable : ' in the ' : Service : ' service.')
                end else
                    ReplicationQueueDeleted = True$
                end
            end
        end
    end else
        Error_Services('Add', 'Application or Table arguments were missing from the ' : Service : ' service.')
    end

    // Always release the cached list for the queue tables if this service is called.
    If QueueTableList@      NE ''   then SRP_List('Release', QueueTableList@)   ; QueueTableList@   = ''
    If QueueHandleList@     NE ''   then SRP_List('Release', QueueHandleList@)  ; QueueHandleList@  = ''
    If QueueTableExists@    NE ''   then SRP_List('Release', QueueTableExists@) ; QueueTableExists@ = ''

    Response    = ReplicationQueueDeleted

end service


//----------------------------------------------------------------------------------------------------------------------
// SetSRPEngineServerINI
//
// EnginePath           - Path to where the SRP Engine Server is located. - [Required]
// SRPEngineServerINI   - JSON object containing the INI sections and key/values. - [Required]
//
// Creates or updates the SRP Engine Server INI file.
//----------------------------------------------------------------------------------------------------------------------
Service SetSRPEngineServerINI(EnginePath, SRPEngineServerINI)

    If EnginePath NE '' OR SRPEngineServerINI NE '' then
        If SRP_JSON(objINI, 'Parse', SRPEngineServerINI) EQ '' then
            Title           = SRP_JSON(objINI, 'GetValue', 'General.Title', '')
            If Title EQ '' then Title = 'SRP Engine Server'
            Location        = SRP_JSON(objINI, 'GetValue', 'Directories.Location', '')
            If Location EQ '' then Location = Drive()
            WorkingDir      = SRP_JSON(objINI, 'GetValue', 'Directories.WorkingDir', '')
            If WorkingDir EQ '' then WorkingDir = Drive()
            Port            = SRP_JSON(objINI, 'GetValue', 'EnginePool.Port', '')
            EngineCount     = SRP_JSON(objINI, 'GetValue', 'EnginePool.EngineCount', '')
            If EngineCount EQ '' then EngineCount = 1
            BasePipeName    = SRP_JSON(objINI, 'GetValue', 'EnginePool.BasePipeName', '')
            If BasePipeName EQ '' then BasePipeName = 'EN'
            ShowEngines     = SRP_JSON(objINI, 'GetValue', 'EnginePool.ShowEngines', 'False')
            Database        = SRP_JSON(objINI, 'GetValue', 'OInsight.Database', '')
            Username        = SRP_JSON(objINI, 'GetValue', 'OInsight.Username', '')
            Password        = SRP_JSON(objINI, 'GetValue', 'OInsight.Password', '')
            Command         = SRP_JSON(objINI, 'GetValue', 'Autocommand.Command', '')
            Interval        = SRP_JSON(objINI, 'GetValue', 'Autocommand.Interval', '')
            InitCommand     = SRP_JSON(objINI, 'GetValue', 'Autocommand.InitCommand', '')
            SRP_JSON(objINI, 'Release')
            EnginePath      = SRP_Path('AddBackslash', EnginePath)
            ExeName         = Replication_Services('GetSRPEngineServerExeName', EnginePath)
            BaseName        = ExeName[1, '.']
            INIFile         = EnginePath  : BaseName : '.ini' : \00\
            WritePrivateProfileString('General' : \00\, 'Title' : \00\, Title : \00\, INIFile)
            WritePrivateProfileString('Directories' : \00\, 'Location' : \00\, Location : \00\, INIFile)
            WritePrivateProfileString('Directories' : \00\, 'WorkingDir' : \00\, WorkingDir : \00\, INIFile)
            WritePrivateProfileString('EnginePool' : \00\, 'Port' : \00\, Port : \00\, INIFile)
            WritePrivateProfileString('EnginePool' : \00\, 'EngineCount' : \00\, EngineCount : \00\, INIFile)
            WritePrivateProfileString('EnginePool' : \00\, 'BasePipeName' : \00\, BasePipeName : \00\, INIFile)
            WritePrivateProfileString('EnginePool' : \00\, 'ShowEngines' : \00\, ShowEngines : \00\, INIFile)
            WritePrivateProfileString('OInsight' : \00\, 'Database' : \00\, Database : \00\, INIFile)
            WritePrivateProfileString('OInsight' : \00\, 'Username' : \00\, Username : \00\, INIFile)
            WritePrivateProfileString('OInsight' : \00\, 'Password' : \00\, Password : \00\, INIFile)
            WritePrivateProfileString('Autocommand' : \00\, 'Command' : \00\, Command : \00\, INIFile)
            WritePrivateProfileString('Autocommand' : \00\, 'Interval' : \00\, Interval : \00\, INIFile)
            WritePrivateProfileString('Autocommand' : \00\, 'InitCommand' : \00\, InitCommand : \00\, INIFile)
        end else
            Error_Services('Add', 'Error parsing JSON object in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'EnginePath or SRPEngineServerINI argument was missing or is not a valid number in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSRPEngineServerINI
//
// EnginePath - Path to where the SRP Engine Server is located. - [Required]
//
// Returns an @FM/@VM array of SRPEngineServer.INI names and values.
//----------------------------------------------------------------------------------------------------------------------
Service GetSRPEngineServerINI(EnginePath)

    SRPEngineServerINI  = ''

    If EnginePath NE '' then
        If SRP_JSON(objINI, 'New') then
            EnginePath      = SRP_Path('AddBackslash', EnginePath)
            ExeName         = Replication_Services('GetSRPEngineServerExeName', EnginePath)
            BaseName        = ExeName[1, '.']
            INIFile         = EnginePath  : BaseName : '.ini' : \00\

            Title           = Str(\00\, 1024)
            GetPrivateProfileString('General' : \00\, 'Title' : \00\, \00\, Title, Len(Title), INIFile)
            Title           = Title[1, \00\]
            Location        = Str(\00\, 1024)
            GetPrivateProfileString('Directories' : \00\, 'Location' : \00\, \00\, Location, Len(Location), INIFile)
            Location        = Location[1, \00\]
            WorkingDir      = Str(\00\, 1024)
            GetPrivateProfileString('Directories' : \00\, 'WorkingDir' : \00\, \00\, WorkingDir, Len(WorkingDir), INIFile)
            WorkingDir      = WorkingDir[1, \00\]
            Port            = Str(\00\, 1024)
            GetPrivateProfileString('EnginePool' : \00\, 'Port' : \00\, \00\, Port, Len(Port), INIFile)
            Port            = Port[1, \00\]
            EngineCount     = Str(\00\, 1024)
            GetPrivateProfileString('EnginePool' : \00\, 'EngineCount' : \00\, \00\, EngineCount, Len(EngineCount), INIFile)
            EngineCount     = EngineCount[1, \00\]
            BasePipeName    = Str(\00\, 1024)
            GetPrivateProfileString('EnginePool' : \00\, 'BasePipeName' : \00\, \00\, BasePipeName, Len(BasePipeName), INIFile)
            BasePipeName    = BasePipeName[1, \00\]
            ShowEngines     = Str(\00\, 1024)
            GetPrivateProfileString('EnginePool' : \00\, 'ShowEngines' : \00\, \00\, ShowEngines, Len(ShowEngines), INIFile)
            ShowEngines     = ShowEngines[1, \00\]
            Database        = Str(\00\, 1024)
            GetPrivateProfileString('OInsight' : \00\, 'Database' : \00\, \00\, Database, Len(Database), INIFile)
            Database        = Database[1, \00\]
            Username        = Str(\00\, 1024)
            GetPrivateProfileString('OInsight' : \00\, 'Username' : \00\, \00\, Username, Len(Username), INIFile)
            Username        = Username[1, \00\]
            Password        = Str(\00\, 1024)
            GetPrivateProfileString('OInsight' : \00\, 'Password' : \00\, \00\, Password, Len(Password), INIFile)
            Password        = Password[1, \00\]
            Command         = Str(\00\, 1024)
            GetPrivateProfileString('Autocommand' : \00\, 'Command' : \00\, \00\, Command, Len(Command), INIFile)
            Command         = Command[1, \00\]
            Interval        = Str(\00\, 1024)
            GetPrivateProfileString('Autocommand' : \00\, 'Interval' : \00\, \00\, Interval, Len(Interval), INIFile)
            Interval        = Interval[1, \00\]
            InitCommand     = Str(\00\, 1024)
            GetPrivateProfileString('Autocommand' : \00\, 'InitCommand' : \00\, \00\, InitCommand, Len(InitCommand), INIFile)
            InitCommand     = InitCommand[1, \00\]

            // General settings.
            If SRP_JSON(objGeneral, 'New') then
                SRP_JSON(objGeneral, 'SetValue', 'Title', Title, 'String')
                SRP_JSON(objINI, 'Set', 'General', objGeneral)
                SRP_JSON(objGeneral, 'Release')
            end
            // Directories settings.
            If SRP_JSON(objDirectories, 'New') then
                SRP_JSON(objDirectories, 'SetValue', 'Location', Location, 'String')
                SRP_JSON(objDirectories, 'SetValue', 'WorkingDir', WorkingDir, 'String')
                SRP_JSON(objINI, 'Set', 'Directories', objDirectories)
                SRP_JSON(objDirectories, 'Release')
            end
            // EnginePool settings.
            If SRP_JSON(objEnginePool, 'New') then
                SRP_JSON(objEnginePool, 'SetValue', 'Port', Port, 'String')
                SRP_JSON(objEnginePool, 'SetValue', 'EngineCount', EngineCount, 'String')
                SRP_JSON(objEnginePool, 'SetValue', 'BasePipeName', BasePipeName, 'String')
                SRP_JSON(objEnginePool, 'SetValue', 'ShowEngines', ShowEngines)
                SRP_JSON(objINI, 'Set', 'EnginePool', objEnginePool)
                SRP_JSON(objEnginePool, 'Release')
            end
            // OInsight settings.
            If SRP_JSON(objOInsight, 'New') then
                SRP_JSON(objOInsight, 'SetValue', 'Database', Database, 'String')
                SRP_JSON(objOInsight, 'SetValue', 'Username', Username, 'String')
                SRP_JSON(objOInsight, 'SetValue', 'Password', Password, 'String')
                SRP_JSON(objINI, 'Set', 'OInsight', objOInsight)
                SRP_JSON(objOInsight, 'Release')
            end
            // Autocommand settings.
            If SRP_JSON(objAutocommand, 'New') then
                SRP_JSON(objAutocommand, 'SetValue', 'Command', Command, 'String')
                SRP_JSON(objAutocommand, 'SetValue', 'Interval', Interval, 'String')
                SRP_JSON(objAutocommand, 'SetValue', 'InitCommand', InitCommand, 'String')
                SRP_JSON(objINI, 'Set', 'Autocommand', objAutocommand)
                SRP_JSON(objAutocommand, 'Release')
            end
            SRPEngineServerINI  = SRP_JSON(objINI, 'Stringify', 'Fast')
            SRP_JSON(objINI, 'Release')
        end else
            Error_Services('Add', 'Error creating a JSON object in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'EnginePath argument was missing or is not a valid number in the ' : Service : ' service.')
    end

    Response    = SRPEngineServerINI

end service


//----------------------------------------------------------------------------------------------------------------------
// SetSRPEngineServerExeName
//
// ExeName  - Name of the SRP Engine Server executable file.
//
// Sets the name of the SRP Engine Server executable that the SRP Replication Manager will use.
//----------------------------------------------------------------------------------------------------------------------
Service SetSRPEngineServerExeName(ExeName)

    If Len(ExeName) then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_EXE_NAME'
            Lock hRepConfigTable, RepConfigKeyID then
                Write ExeName to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing source server path in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking source server path in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSRPEngineServerExeName
//
// Returns the name of the SRP Engine Server EXE file used by the SRP Replication Manager.
//----------------------------------------------------------------------------------------------------------------------
Service GetSRPEngineServerExeName()

    ExeName       = ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_EXE_NAME'
        Read ExeName from hRepConfigTable, RepConfigKeyID else Null
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = ExeName

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSRPEngineServerTitle
//
// Returns title used to uniquely name the SRP Engine Server.
//----------------------------------------------------------------------------------------------------------------------
Service GetSRPEngineServerTitle(EnginePath)

    Title       = ''

    If EnginePath NE '' then
        EnginePath  = SRP_Path('AddBackslash', EnginePath)
        ExeName     = Replication_Services('GetSRPEngineServerExeName', EnginePath)
        BaseName    = ExeName[1, '.']
        INIFile     = EnginePath  : BaseName : '.ini' : \00\

        Title   = Str(\00\, 1024)
        GetPrivateProfileString('General' : \00\, 'Title' : \00\, \00\, Title, Len(Title), INIFile)
        Title   = Title[1, \00\]
    end else
        Error_Services('Add', 'EnginePath argument was missing or is not a valid number in the ' : Service : ' service.')
    end

    Response    = Title

end service


//----------------------------------------------------------------------------------------------------------------------
// SetDailyNotificationEmails
//
// NotificationEmails - @VM list of email addresses. - [Required]
//
// Sets the list of emails used for daily notifications.
//----------------------------------------------------------------------------------------------------------------------
Service SetDailyNotificationEmails(NotificationEmails)

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_DAILY_NOTIFICATION_EMAILS'
        Lock hRepConfigTable, RepConfigKeyID then
            Write NotificationEmails to hRepConfigTable, RepConfigKeyID else
                Error_Services('Add', 'Error writing daily notification emails in the ' : Service : ' service.')
            end
            Unlock hRepConfigTable, RepConfigKeyID
        end else
            Error_Services('Add', 'Error locking daily notification emails in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetDailyNotificationEmails
//
// Returns an @VM list of emails used for daily notifications.
//----------------------------------------------------------------------------------------------------------------------
Service GetDailyNotificationEmails()

    NotificationEmails  =   ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_DAILY_NOTIFICATION_EMAILS'
        Read NotificationEmails from hRepConfigTable, RepConfigKeyID else NotificationEmails = ''
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = NotificationEmails

end service


//----------------------------------------------------------------------------------------------------------------------
// SetEmergencyNotificationEmails
//
// NotificationEmails - @VM list of email addresses. - [Required]
//
// Sets the list of emails used for emergency notifications.
//----------------------------------------------------------------------------------------------------------------------
Service SetEmergencyNotificationEmails(NotificationEmails)

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_EMERGENCY_NOTIFICATION_EMAILS'
        Lock hRepConfigTable, RepConfigKeyID then
            Write NotificationEmails to hRepConfigTable, RepConfigKeyID else
                Error_Services('Add', 'Error writing emergency notification emails in the ' : Service : ' service.')
            end
            Unlock hRepConfigTable, RepConfigKeyID
        end else
            Error_Services('Add', 'Error locking emergency notification emails in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetEmergencyNotificationEmails
//
// Returns an @VM list of emails used for emergency notifications.
//----------------------------------------------------------------------------------------------------------------------
Service GetEmergencyNotificationEmails()

    NotificationEmails  =   ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_EMERGENCY_NOTIFICATION_EMAILS'
        Read NotificationEmails from hRepConfigTable, RepConfigKeyID else NotificationEmails = ''
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = NotificationEmails

end service


//----------------------------------------------------------------------------------------------------------------------
// SetSMTPSettings
//
// SMTPSettings - @FM array of SMTP settings. - [Required]
//
// Sets the SMTP settings.
//----------------------------------------------------------------------------------------------------------------------
Service SetSMTPSettings(SMTPSettings)

    If SMTPSettings NE '' then
        Open RepConfigTable$ to hRepConfigTable then
            RepConfigKeyID = 'REPLICATION_SMTP_SETTINGS'
            Lock hRepConfigTable, RepConfigKeyID then
                Write SMTPSettings to hRepConfigTable, RepConfigKeyID else
                    Error_Services('Add', 'Error writing SMTP settings in the ' : Service : ' service.')
                end
                Unlock hRepConfigTable, RepConfigKeyID
            end else
                Error_Services('Add', 'Error locking SMTP settings in the ' : Service : ' service.')
            end
        end else
            Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'SMTPSettings argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSMTPSettings
//
// Returns an @FM array of SMTP settings.
//----------------------------------------------------------------------------------------------------------------------
Service GetSMTPSettings()

    SMTPSettings    =   ''

    Open RepConfigTable$ to hRepConfigTable then
        RepConfigKeyID = 'REPLICATION_SMTP_SETTINGS'
        Read SMTPSettings from hRepConfigTable, RepConfigKeyID else SMTPSettings = ''
    end else
        Error_Services('Add', 'Error opening the ' : RepConfigTable$ : ' table in the ' : Service : ' service.')
    end

    Response    = SMTPSettings

end service


Service PostToDailyLog:

end service


Service SendDailyLog:

end service


//----------------------------------------------------------------------------------------------------------------------
// LogError
//
// OriginalService - The original service that triggered the alert condition.
// Action - 
// Application - 
// Volume - 
// Table - 
// KeyID - 
// StatusCode - This is the result of Get_Status at the time the alert condition was triggered.
// FileError - This is the content of @FILE_ERROR at the time the alert condition was triggered.
// Position - Line number or other position identifier in the code that called the LogError service.
// AdditionalMessage - Additional message to include in the log.
//
// Logs the error.
//----------------------------------------------------------------------------------------------------------------------
Service LogError(OriginalService, Action, Application, Volume, Table, KeyID, StatusCode, FileError, Position, AdditionalMessage)

    ErrorLogPath       = Replication_Services('GetErrorLogPath')

    If ErrorLogPath NE '' then
        LogDate     = Oconv(Date(), 'D4/')
        LogTime     = Oconv(Time(), 'MTS')
        LogFileName = LogDate[7, 4] : '-' : LogDate[1, 2] : '-' : LogDate[4, 2] : ' ReplicateTransactionsError.log'
        Headers     = 'Logging DTM' : @FM : 'Service' : @FM : 'Action' : @FM : 'Application' : @FM : 'Volume' : @FM : 'Table' : @FM : 'KeyID' : @FM : 'QueueTable' : @FM : 'EngineName' : @FM : 'StatusCode' : @FM : 'FileError' : @FM : 'Position' : @FM : 'Additional Message'
        objLog      = Logging_Services('NewLog', ErrorLogPath, LogFileName, CRLF$, Tab$, Headers, '', False$, False$)
        LoggingDTM  = LogDate : ' ' : LogTime   ; // Logging DTM

        QueueTable  = Replication_Services('GetReplicationQueueTable', Application, Table)
        CommandLine = GetCommandLine()
        Swap '/S=' with @FM in CommandLine
        EngineName  = CommandLine<2>    ; // This is the NamedPipe identifier for the engine processing this service.

        Convert @FM to ' ' in StatusCode
        Convert @FM to ' ' in FileError
        Convert @FM to ' ' in AdditionalMessage

        LogData     = ''
        LogData<1>  = LoggingDTM
        LogData<2>  = OriginalService
        LogData<3>  = Action
        LogData<4>  = Application
        LogData<5>  = Volume
        LogData<6>  = Table
        LogData<7>  = KeyID
        LogData<8>  = QueueTable
        LogData<9>  = EngineName
        LogData<10> = StatusCode
        LogData<11> = FileError
        LogData<12> = Position
        LogData<13> = AdditionalMessage
        Logging_Services('AppendLog', objLog, LogData, @RM, @FM, True$)
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// SendEmergencyAlert
//
// OriginalService - The original service that triggered the alert condition.
// Action - 
// Application - 
// Volume - 
// Table - 
// KeyID - 
// StatusCode - This is the result of Get_Status at the time the alert condition was triggered.
// FileError - This is the content of @FILE_ERROR at the time the alert condition was triggered.
// Position - Line number or other position identifier in the code that called the SendEmergencyAlert service.
// AdditionalMessage - Additional message to include in the emergency alert.
//
// Sends out an emergency alert message to the designated emergency notification email accounts.
//----------------------------------------------------------------------------------------------------------------------
Service SendEmergencyAlert(OriginalService, Action, Application, Volume, Table, KeyID, StatusCode, FileError, Position, AdditionalMessage)

    SMTPSettings        = Replication_Services('GetSMTPSettings')
    NotificationEmails  = Replication_Services('GetEmergencyNotificationEmails')
    Convert @VM to ',' in NotificationEmails

    If (SMTPSettings NE '') AND (NotificationEmails NE '') then

        MessageBody = ''

        If OriginalService NE '' then
            MessageBody := 'Service:     ' : OriginalService : CRLF$
        end

        If Action NE '' then
            MessageBody := 'Action:      ' : Action : CRLF$
        end

        If Application NE '' then
            MessageBody := 'Application: ' : Application : CRLF$
        end

        If Volume NE '' then
            MessageBody := 'Volume:      ' : Volume : CRLF$
        end

        If Table NE '' then
            MessageBody := 'Table:       ' : Table : CRLF$
        end

        If (Application NE '') AND (Table NE '') then
            QueueTable = Replication_Services('GetReplicationQueueTable', Application, Table)
            MessageBody := 'QueueTable:  ' : QueueTable : CRLF$
        end

        CommandLine = GetCommandLine()
        Swap '/S=' with @FM in CommandLine
        EngineName  = CommandLine<2>    ; // This is the NamedPipe identifier for the engine processing this service.
        If EngineName NE '' then
            MessageBody := 'EngineName:  ' : EngineName : CRLF$
        end

        If KeyID NE '' then
            MessageBody := 'KeyID:       ' : KeyID : CRLF$
        end

        If Position NE '' then
            MessageBody := 'Position:    ' : Position : CRLF$
        end

        If StatusCode NE '' then
            Swap @VM with CRLF$ in StatusCode
            MessageBody := CRLF$
            MessageBody := 'Get_Status() Results:' : CRLF$
            MessageBody := '---------------------' : CRLF$
            MessageBody := StatusCode : CRLF$
        end

        If FileError NE '' then
            Swap @FM with CRLF$ in FileError
            Swap @VM with CRLF$ in FileError
            MessageBody := CRLF$
            MessageBody := '@FILE_ERROR Results:' : CRLF$
            MessageBody := '--------------------' : CRLF$
            MessageBody := FileError : CRLF$
        end

        If AdditionalMessage NE '' then
            Swap @FM with CRLF$ in AdditionalMessage
            MessageBody := CRLF$
            MessageBody := 'Additional Message:' : CRLF$
            MessageBody := '-------------------' : CRLF$
            MessageBody := AdditionalMessage : CRLF$
        end

        Message                       = ''
        Message<SRPMail_Subject$>     = 'Replication Manager - Emergency Alert'
        Message<SRPMail_From$>        = SMTPSettings<7>
        Message<SRPMail_To$>          = NotificationEmails
        Message<SRPMail_Cc$>          = '' 
        Message<SRPMail_Bcc$>         = ''
        Message<SRPMail_ReplyTo$>     = ''
        Message<SRPMail_BodyType$>    = 'TEXT'
        Message<SRPMail_Body$>        = MessageBody
        Message<SRPMail_Attachments$> = ''
        Message<SRPMail_Importance$>  = SRPMail_Importance_High$

        ConfigFile                       = ''
        ConfigFile<SendUsing$>           = SendUsing_Port$
        ConfigFile<SMTPServerPickupDir$> = ''
        ConfigFile<SMTPServerPort$>      = SMTPSettings<2>
        ConfigFile<SMTPServer$>          = SMTPSettings<1>
        ConfigFile<SMTPAuthenticate$>    = SMTPSettings<3>
        ConfigFile<SMTPSendUsername$>    = SMTPSettings<4>
        ConfigFile<SMTPSendPassword$>    = SMTPSettings<5>
        ConfigFile<SMTPUseSSL$>          = SMTPSettings<6>

        If Xlate('SYSOBJ', '$SRP_SEND_MAIL', '', 'X') NE '' then
            Success = SRP_Send_Mail(Message, ConfigFile)

            If Success NE True$ then
                Error_Services('Add', 'Error sending email in the ' : Service : ' service. Error string: ' : Success)
            end
        end else
            AdditionalMessage = 'The SRP Mail utility does not appear to be installed.'
            Replication_Services('LogError', Service, Action, Application, Volume, Table, KeyID, StatusCode, FileError, Position, AdditionalMessage)
        end
    end else
        Error_Services('Add', 'SMTP Settings or Notification Emails were missing from the ' : Service : ' service.')
    end
 
    // Set the system enabled flag to false to stop the replication. Tell the SRP Engine Server to quit.
    Replication_Services('SetSystemEnabledFlag', False$)
    Send_Info('SRPENGINESERVERQUIT')
    Stop

end service


Service SendAlert(OriginalService, Action, Application, Volume, Table, KeyID, StatusCode, FileError, Position, AdditionalMessage)

    SMTPSettings        = Replication_Services('GetSMTPSettings')
    NotificationEmails  = Replication_Services('GetEmergencyNotificationEmails')
    Convert @VM to ',' in NotificationEmails

    If (SMTPSettings NE '') AND (NotificationEmails NE '') then

        MessageBody = ''

        If OriginalService NE '' then
            MessageBody := 'Service:     ' : OriginalService : CRLF$
        end

        If Action NE '' then
            MessageBody := 'Action:      ' : Action : CRLF$
        end

        If Application NE '' then
            MessageBody := 'Application: ' : Application : CRLF$
        end

        If Volume NE '' then
            MessageBody := 'Volume:      ' : Volume : CRLF$
        end

        If Table NE '' then
            MessageBody := 'Table:       ' : Table : CRLF$
        end

        If (Application NE '') AND (Table NE '') then
            QueueTable = Replication_Services('GetReplicationQueueTable', Application, Table)
            MessageBody := 'QueueTable:  ' : QueueTable : CRLF$
        end

        CommandLine = GetCommandLine()
        Swap '/S=' with @FM in CommandLine
        EngineName  = CommandLine<2>    ; // This is the NamedPipe identifier for the engine processing this service.
        If EngineName NE '' then
            MessageBody := 'EngineName:  ' : EngineName : CRLF$
        end

        If KeyID NE '' then
            MessageBody := 'KeyID:       ' : KeyID : CRLF$
        end

        If Position NE '' then
            MessageBody := 'Position:    ' : Position : CRLF$
        end

        If StatusCode NE '' then
            Swap @VM with CRLF$ in StatusCode
            MessageBody := CRLF$
            MessageBody := 'Get_Status() Results:' : CRLF$
            MessageBody := '---------------------' : CRLF$
            MessageBody := StatusCode : CRLF$
        end

        If FileError NE '' then
            Swap @FM with CRLF$ in FileError
            Swap @VM with CRLF$ in FileError
            MessageBody := CRLF$
            MessageBody := '@FILE_ERROR Results:' : CRLF$
            MessageBody := '--------------------' : CRLF$
            MessageBody := FileError : CRLF$
        end

        If AdditionalMessage NE '' then
            Swap @FM with CRLF$ in AdditionalMessage
            MessageBody := CRLF$
            MessageBody := 'Additional Message:' : CRLF$
            MessageBody := '-------------------' : CRLF$
            MessageBody := AdditionalMessage : CRLF$
        end

        Message                       = ''
        Message<SRPMail_Subject$>     = 'Replication Manager - Emergency Alert'
        Message<SRPMail_From$>        = SMTPSettings<7>
        Message<SRPMail_To$>          = NotificationEmails
        Message<SRPMail_Cc$>          = '' 
        Message<SRPMail_Bcc$>         = ''
        Message<SRPMail_ReplyTo$>     = ''
        Message<SRPMail_BodyType$>    = 'TEXT'
        Message<SRPMail_Body$>        = MessageBody
        Message<SRPMail_Attachments$> = ''
        Message<SRPMail_Importance$>  = SRPMail_Importance_High$

        ConfigFile                       = ''
        ConfigFile<SendUsing$>           = SendUsing_Port$
        ConfigFile<SMTPServerPickupDir$> = ''
        ConfigFile<SMTPServerPort$>      = SMTPSettings<2>
        ConfigFile<SMTPServer$>          = SMTPSettings<1>
        ConfigFile<SMTPAuthenticate$>    = SMTPSettings<3>
        ConfigFile<SMTPSendUsername$>    = SMTPSettings<4>
        ConfigFile<SMTPSendPassword$>    = SMTPSettings<5>
        ConfigFile<SMTPUseSSL$>          = SMTPSettings<6>

        If Xlate('SYSOBJ', '$SRP_SEND_MAIL', '', 'X') NE '' then
            Success = SRP_Send_Mail(Message, ConfigFile)

            If Success NE True$ then
                Error_Services('Add', 'Error sending email in the ' : Service : ' service. Error string: ' : Success)
            end
        end else
            AdditionalMessage = 'The SRP Mail utility does not appear to be installed.'
            Replication_Services('LogError', Service, Action, Application, Volume, Table, KeyID, StatusCode, FileError, Position, AdditionalMessage)
        end
    end else
        Error_Services('Add', 'SMTP Settings or Notification Emails were missing from the ' : Service : ' service.')
    end

end service


Service RecompileEntity()

end service


//----------------------------------------------------------------------------------------------------------------------
// GetVersion
//
// Returns the version of the SRP Replication Manager being used. The response will be in the following format:
//
// x.x.x [RCx]
// mm.dd.yyyy hh:mmA/P
//
// A carriage-return/line-feed character will be used to separate the two pieces of information.
//----------------------------------------------------------------------------------------------------------------------
Service GetVersion()

    Version     = Database_Services('ReadDataRow', RepConfigTable$, 'SRP_REPLICATION_MANAGER_VERSION')

    Swap @FM with CRLF$ in Version

    Response    = Version

end service
