450 lines
16 KiB
Plaintext
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
|