open-insight/SYSPROG/STPROC/BASE_MFS.txt
2024-03-25 15:17:34 -07:00

450 lines
16 KiB
Plaintext

Subroutine Base_MFS(Code, FSList, Handle, Name, FMC, Record, Status)
/***********************************************************************************************************************
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 : Base_MFS
Description : Base MFS (Modified File System) shell for general use.
Notes : Used to track activity in a given database table, regardless of how the table is accessed.
Generally the MFS remains as generic as possible and makes a call to another table-specific stored
procedure to handle all of the main functionality.
MFS procedures should normally be stored in the SYSPROG application for optimum accessibility.
The table-specific stored procedures should be stored in the local application.
Some methods might need the regular name of the database table. Since the MFS routine does not
normally provide this information we need to track it ourselves. The OPEN.FILE method gives us
an opportunity to retrieve the regular name as well as the table handle. This information is then
stored in the /Tables/ global common for convenient reference.
Record based actions (e.g. READ.RECORD, WRITE.RECORD, DELETE.RECORD) will be routed to table
specific and promoted (i.e. generic) action handlers befoe the BFS is called (Call_Next_FS internal
method.) The MFS argument Status can be set accordingly to determine how the rest of the action
chain should be executed (see the ACTION_SETUP insert for more information.)
Parameters :
Code [in] -- An integer value indicating the operation to be performed (1 = read a record, 4 = delete a
record, 11 = open a file, etc.)
FSList [in] -- The list of MFSs and the BFS name for the current file or volume. This is an @SVM delimited
array, with the current MFS name as the first value in the array, and the BFS name as the
last value.
Handle [in] -- The file handle of the file or media map being accessed. Note, this does contain the entire
handle structure that the Basic+ Open statement would provide.
Name [in] -- The name (key) of the record or file being accessed.
FMC [in] -- Various functions.
Record [in] -- The entire record (for record-oriented functions) or a newly-created handle (for "get
handle" functions).
Status [out/in] -- A return code indicating the success or failure of an operation.
History : (Date, Initials, Notes)
07/27/10 dmb Original programmer
03/26/11 dmb Save and restore @FILE.ERROR to prevent incorrect error messages being passed down the line.
05/03/16 dmb [SRPFW-124] Revise the Get_Original_Record logic to call the remaing MFS chain rather
than just try to call the BFS directly.
06/09/16 dmb [SRPFW-282] Update the CLEARFILE action to gosub to Action_Chain rather than Call_Next_FS
so the promoted action can be invoked.
09/18/19 dmb [SRPFW-282] Update OPEN.FILE to set the volume based on the path in the handle
(Record argument).
06/25/20 dmb [SRPFW-282] Update OPEN.FILE to also remove the Table*Database prefix in the Record
argument if it exists.
09/10/20 dmb [SRPFW-282] Update OPEN.FILE to default Volume to REVBOOT.
***********************************************************************************************************************/
#pragma precomp SRP_PreCompiler
$insert LOGICAL
$insert FSERRORS_HDR
$insert FILE.SYSTEM.EQUATES
$insert ACTION_SETUP
Declare subroutine SRP_Stopwatch
Actions = 'READ_RECORD,READONLY_RECORD,WRITE_RECORD,DELETE_RECORD,LOCK_RECORD,UNLOCK_RECORD,SELECT,READNEXT,'
Actions := 'CLEARSELECT,CLEARFILE,OPEN_FILE,CREATE_FILE,RENAME_FILE,MOVE_FILE,DELETE_FILE,OPEN_MEDIA,CREATE_MEDIA,'
Actions := 'READ_MEDIA,WRITE_MEDIA,UNLOCK_ALL,FLUSH,INSTALL,RESERVED,RESERVED,RESERVED,OMNI_SCRIPT,CLOSE_MEDIA,'
Actions := 'RECORD_COUNT,REMAKE_FILE,CREATE_INDEX,DELETE_INDEX,UPDATE_INDEX,SELECT_INDEX,READNEXT_INDEX'
BaseAction = Field(Actions, ',', Code)
// Initialize the ActionFlow variable. Assume the action will chain forward.
ActionFlow = ACTION_CONTINUE$
// Initialize the OrigRecord variable. The WRITE.RECORD and DELETE.RECORD actions will populate this.
OrigRecord = ''
// FILE.SYSTEM.ONGOSUB has the On Code GoSub... command
$insert FILE.SYSTEM.ONGOSUB
Return
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MFS Actions
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
READ.RECORD:
GoSub Action_Chain
return
READO.RECORD:
GoSub Action_Chain
return
WRITE.RECORD:
// Get the original (static) record from the database table.
GoSub Get_Original_Record
GoSub Action_Chain
return
DELETE.RECORD:
// Get the original (static) record from the database table.
GoSub Get_Original_Record
GoSub Action_Chain
return
LOCK.RECORD:
GoSub Call_Next_FS
return
UNLOCK.RECORD:
GoSub Call_Next_FS
return
SELECT:
GoSub Call_Next_FS
return
READNEXT:
GoSub Call_Next_FS
return
CLEARSELECT:
GoSub Call_Next_FS
return
CLEARFILE:
GoSub Action_Chain
return
OPEN.FILE:
// Call BFS in order to get the table handle. The regular name of the table will be returned in the Name argument
// and the handle will be returned in the Record argument.
GoSub Call_Next_FS
// Load the handle and table name into the labelled common.
If Status then
TableName = Name[1, '*']
Accountname = Name[Col2() + 1, '999']
Volume = Record[-1, 'B' : @TM]
Volume = Volume[14, 9999]
Volume[-12, 12] = ''
If Volume EQ '' then Volume = 'REVBOOT'
Locate TableName in TableNames@ using @FM Setting fPos then
If TableHandles@<fPos> EQ Record else
// There is a new handle for the indicated table. This could be the same table name from a different
// volume or an updated handle for the same table. Either way, just append a new handle/table pair
// to the lookup arrays.
TableNames@ := TableName : @FM
TableAccounts@ := AccountName : @FM
TableHandles@ := Record : @FM
TableVolumes@ := Volume : @FM
end
end else
TableNames@ := TableName : @FM
TableAccounts@ := AccountName : @FM
TableHandles@ := Record : @FM
TableVolumes@ := Volume : @FM
end
end
return
CREATE.FILE:
GoSub Call_Next_FS
return
RENAME.FILE:
GoSub Call_Next_FS
return
MOVE.FILE:
GoSub Call_Next_FS
return
DELETE.FILE:
GoSub Call_Next_FS
return
OPEN.MEDIA:
GoSub Call_Next_FS
return
CREATE.MEDIA:
GoSub Call_Next_FS
return
READ.MEDIA:
GoSub Call_Next_FS
return
WRITE.MEDIA:
GoSub Call_Next_FS
return
UNLOCK.ALL:
Record = ''
Status = ACTION_CONTINUE$
return
FLUSH:
Record = ''
Status = ACTION_CONTINUE$
return
INSTALL:
Status = ACTION_CONTINUE$
return
RESERVED:
// There is a critical error if this has been reached.
Status = ACTION_STOP$
return
OMNI.SCRIPT:
GoSub Call_Next_FS
return
CLOSE.MEDIA:
GoSub Call_Next_FS
return
RECORD.COUNT:
GoSub Call_Next_FS
return
REMAKE.FILE:
GoSub Call_Next_FS
return
CREATE.INDEX:
GoSub Call_Next_FS
return
DELETE.INDEX:
GoSub Call_Next_FS
return
UPDATE.INDEX:
GoSub Call_Next_FS
return
SELECT.INDEX:
GoSub Call_Next_FS
return
READNEXT.INDEX:
GoSub Call_Next_FS
return
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Internal GoSubs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Get_Original_Record:
// To get the original record from the database table a direct call to this table's remaing chain must be made.
@FILE.ERROR = ''
NewFSList = Delete(FSList, 1, 1, 1)
NextFS = NewFSList<1, 1, 1>
Call @NextFS(READO.RECORD, NewFSList, Handle, Name, FMC, OrigRecord, ActionStatus)
// If ActionStatus is Null then it is a new record or an error reading.
return
Call_Next_FS:
// Since this MFS is being executed it is responsible for moving the chain forward. The next MFS/BFS item is in the
// BFS array. Pull it from the top and pass the remaining items.
NewFSList = Delete(FSList, 1, 1, 1)
NextFS = NewFSList<1, 1, 1>
If Len(NextFS) then
Call @NextFS(Code, NewFSList, Handle, Name, FMC, Record, Status)
end
return
Action_Chain:
// This internal method provides the developer with a complete chain of actions. Prior to the BFS call the developer
// can execute logic in a table specific action handler and then a promoted (i.e. generic) action handler. The
// action will be suffixed with '_PRE' to identify the action logic before the BFS. After the BFS the table specific
// action handler and promoted action handler will be called again. This is very analogous to the way event handling
// in OpenInsight is managed (i.e. pre-system event handler, system event handler, post-system event handler.)
Action = BaseAction : '_PRE'
GoSub Call_Table_Actions
If ActionFlow EQ ACTION_CONTINUE$ OR ActionFlow EQ ACTION_CONTINUE_NO_SYSTEM$ then
GoSub Call_Promoted_Actions
end
If ActionFlow EQ ACTION_CONTINUE$ OR ActionFlow EQ ACTION_CONTINUE_NO_PROMOTED$ OR ActionFlow EQ ACTION_SYSTEM_ONLY$ then
GoSub Call_Next_FS
end
Action = BaseAction
If ActionFlow EQ ACTION_CONTINUE$ OR ActionFlow EQ ACTION_CONTINUE_NO_PROMOTED$ OR ActionFlow EQ ACTION_CONTINUE_NO_SYSTEM$ then
GoSub Call_Table_Actions
end
If ActionFlow EQ ACTION_CONTINUE$ OR ActionFlow EQ ACTION_CONTINUE_NO_SYSTEM$ then
GoSub Call_Promoted_Actions
end
return
Call_Table_Actions:
// Pass activity to the datatable table's action handler if it exists.
// Note: It is critical that handler routine be named in this format: TableName_ACTIONS
// Check to see if this table has already been determine to have an action handler. Once it has already been
// checked, whether or not a handler exists, it will not be checked again during this session. This will optimize
// performance.
InActionList = False$ ; // Assume False for now.
InNoActionList = False$ ; // Assume False for now.
InActionList = SRP_List_Locate(ActionListHandle@, TableName) NE 0
If Not(InActionList) then
InNoActionList = SRP_List_Locate(NoActionListHandle@, TableName)
end
If Not(InActionList) AND Not(InNoActionList) then
// This table has not yet been added to either list, so a table action handler might exist.
NumApps = Count(@APPID, @FM) + (@APPID NE '')
// Starting with the current application, search for an action routine and go through the list of inherited
// applications until SYSPROG has been checked.
For AppCnt = 1 to NumApps
AppID = @APPID<AppCnt>
If AppID _EQC 'SYSPROG' then
SysObjKey = '$' : TableName : '_ACTIONS'
end else
SysObjKey = '$' : TableName : '_ACTIONS' : '*' : @APPID<AppCnt>
end
If Len(SysObjHandle@) then
OrigFileError = @FILE.ERROR
@FILE.ERROR = ''
BFS = 'RTP57'
Call @BFS(READO.RECORD, BFS, SysObjHandle@, SysObjKey, FMC, SysObjRecord, ActionStatus)
@FILE.ERROR = OrigFileError
If ActionStatus then InActionList = True$
end
Until InActionList
Next AppCnt
If (InActionList) then
SRP_List_Add(ActionListHandle@, TableName)
end else
SRP_List_Add(NoActionListHandle@, TableName)
end
end
If InActionList then
ActionRoutine = TableName : '_ACTIONS'
Transfer ActionFlow to OrigActionFlow ; // Save the current action flow.
ActionFlow = Function(@ActionRoutine(Action, '', FSList, Handle, Name, FMC, Record, Status, OrigRecord))
// If the table action returned ACTION_CONTINUE, then this means no special action flow was returned.
// Therefore, restore the action flow that existed before the table action call.
If ActionFlow EQ ACTION_CONTINUE$ then Transfer OrigActionFlow to ActionFlow
end
return
Call_Promoted_Actions:
// Pass activity to the application's promoted action handler if it exists.
// Note: It is critical that handler routine be named in this format: PROMOTED_BaseAction_ACTION
// Check to see if this action has already been determine to have a promoted handler. Once it has already been
// checked, whether or not a handler exists, it will not be checked again during this session. This will optimize
// performance.
InNoPromotedList = False$ ; // Assume False for now.
InPromotedList = SRP_List_Locate(PromotedListHandle@, BaseAction) NE 0
If Not(InPromotedList) then
InNoPromotedList = SRP_List_Locate(NoPromotedListHandle@, BaseAction)
end
If Not(InPromotedList) AND Not(InNoPromotedList) then
// This action has not yet been added to either list, so a promoted action handler might exist.
NumApps = Count(@APPID, @FM) + (@APPID NE '')
// Starting with the current application, search for an action routine and go through the list of inherited
// applications until SYSPROG has been checked.
For AppCnt = 1 to NumApps
AppID = @APPID<AppCnt>
If AppID _EQC 'SYSPROG' then
SysObjKey = '$PROMOTED_' : BaseAction : '_ACTION'
end else
SysObjKey = '$PROMOTED_' : BaseAction : '_ACTION' : '*' : @APPID<AppCnt>
end
If Len(SysObjHandle@) then
OrigFileError = @FILE.ERROR
@FILE.ERROR = ''
BFS = 'RTP57'
Call @BFS(READO.RECORD, BFS, SysObjHandle@, SysObjKey, FMC, SysObjRecord, ActionStatus)
@FILE.ERROR = OrigFileError
If ActionStatus then InPromotedList = True$
end
Until InPromotedList
Next AppCnt
If (InPromotedList) then
SRP_List_Add(PromotedListHandle@, BaseAction)
end else
SRP_List_Add(NoPromotedListHandle@, BaseAction)
end
end
If InPromotedList then
ActionRoutine = 'PROMOTED_' : BaseAction : '_ACTION'
Transfer ActionFlow to OrigActionFlow ; // Save the current action flow.
ActionFlow = Function(@ActionRoutine(Action, '', FSList, Handle, Name, FMC, Record, Status, OrigRecord))
// If the promoted action returned ACTION_CONTINUE, then this means no special action flow was returned.
// Therefore, restore the action flow that existed before the promoted action call.
If ActionFlow EQ ACTION_CONTINUE$ then Transfer OrigActionFlow to ActionFlow
end
return