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@ 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 If AppID _EQC 'SYSPROG' then SysObjKey = '$' : TableName : '_ACTIONS' end else SysObjKey = '$' : TableName : '_ACTIONS' : '*' : @APPID 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 If AppID _EQC 'SYSPROG' then SysObjKey = '$PROMOTED_' : BaseAction : '_ACTION' end else SysObjKey = '$PROMOTED_' : BaseAction : '_ACTION' : '*' : @APPID 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