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 \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 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: // // = Table name // = Database ID // = Volume // = Boolean flag to indicate if the table has BASE_MFS installed // = Boolean flag to indicate if the table is not-suspended from being queued // = Boolean flag to indicate Is the table is not-suspended from being replicated // = 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[1, 1] EQ '!') OR (VolumeTables[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 = 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 = 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 = 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 = 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 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 = 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 = 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 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 = 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 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 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 = 'Replication Manager - Emergency Alert' Message = SMTPSettings<7> Message = NotificationEmails Message = '' Message = '' Message = '' Message = 'TEXT' Message = MessageBody Message = '' Message = SRPMail_Importance_High$ ConfigFile = '' ConfigFile = SendUsing_Port$ ConfigFile = '' ConfigFile = SMTPSettings<2> ConfigFile = SMTPSettings<1> ConfigFile = SMTPSettings<3> ConfigFile = SMTPSettings<4> ConfigFile = SMTPSettings<5> ConfigFile = 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 = 'Replication Manager - Emergency Alert' Message = SMTPSettings<7> Message = NotificationEmails Message = '' Message = '' Message = '' Message = 'TEXT' Message = MessageBody Message = '' Message = SRPMail_Importance_High$ ConfigFile = '' ConfigFile = SendUsing_Port$ ConfigFile = '' ConfigFile = SMTPSettings<2> ConfigFile = SMTPSettings<1> ConfigFile = SMTPSettings<3> ConfigFile = SMTPSettings<4> ConfigFile = SMTPSettings<5> ConfigFile = 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