//tspdos.bcpl   Tape Server Do* command procedures
//   g. krasner  November 19, 1979
//   Last modified by Tim Diebert, October 10, 1980  11:00 AM
//
get   "Tapes.d"
get   "PUP.decl"
get   "TSP.decl"

//---------------------------------------------------------------
external
//---------------------------------------------------------------
[
//Imports
   //From Tapes   
    OpenTape; CloseTape; TapeRewind; TapeUnload; TapeEOF
    TapeStatus; ActOnTape; PerformVTCB
    FileSkipFwd; FileSkipRev; RecSkipFwd; RecSkipRev; EraseInches
   //From 'System'
    Ws; Wo; Endofs; keys; Block; Gets; CallSwat
   //From BSP
    BSPWriteBlock; BSPForceOutput
//Imported Globals
   rwBlock; rwKey; kbdKey; usedidnos; useddrives; ctxQ
   currentVersionString
//Exports (to TSPServer)
   ReplyNo; ReplyYes
   DoOpenDrive; DoCloseDrive; DoReadRecord; DoWriteRecord
   DoFwdSpaceRecord; DoBackSpaceRecord; DoFwdSpaceFile
   DoBackSpaceFile; DoWriteEOF; DoWriteBlankTape
   DoRewind; DoUnload; DoGetStatus; DoSetStatus
   DoSendText; DoGetText
]

//   Command Processors: Do*()
//These routines handle the processing for a received command.  Most
//verify that there is a drive open (Service.drives = -1).
//Error recovery and Replying are all done at this level.

//---------------------------------------------------------------
let DoOpenDrive(ser) be
//---------------------------------------------------------------
[
   let blk = ser>>Service.blk
// process the OpenDrive command, opening the given drive if allowed.
   test ser>>Service.drive ge 0
   ifso   //this Server has a drive open already
      ReplyNo(ser,openAlready)
   ifnot
   [
      let dr = blk>>OpenDrive.driveNumber
      test dr ls maxDrives
      ifnot   ReplyNo(ser,badDriveNo) 
      ifso   test useddrives!dr
         ifso   ReplyNo(ser,driveInUse)
         ifnot
         [
            let tape = OpenTape(dr,ser>>Service.speed,DoErrorProc)
            test tape
            ifnot   ReplyNo(ser,badDrive)
            ifso
            [   //All went well!!
               ser>>Service.tape = tape
               ser>>Service.drive = dr
               useddrives!dr = true
               ReplyYes(ser,cmdOpenDrive)
            ]
         ]
   ]
]

//---------------------------------------------------------------
and DoCloseDrive(ser) be
//---------------------------------------------------------------
[   if VerifyOpen(ser) then   //drive is open, close it
   [   let dr = ser>>Service.drive
      useddrives!dr = false
      CloseTape(ser>>Service.tape)
      ser>>Service.tape = false
      ser>>Service.drive = -1
      ReplyYes(ser,cmdCloseDrive)
   ]
]

//---------------------------------------------------------------
and DoReadRecord(ser) be
//---------------------------------------------------------------
[   //Read a record from tape
   if VerifyOpen(ser) then
   [   //Do read, and return status as well
      let blk = ser>>Service.blk   //get command block
      let count = ActOnTape(ser>>Service.tape,ReadTape,lv blk>>HereIsRecord.record,rwBlockLength,0,0,ser>>Service.retries)
      if count le 0 then count = 0
      if count gr rwBlockLength-24 
         then count = rwBlockLength-24   //truncate to fit
      blk>>HereIsRecord.recordLength = count
      blk>>HereIsRecord.endingStatus = (ser>>Service.tape)>>TCB.Flags
      blk>>HereIsRecord.type = cmdHereIsRecord
      blk>>HereIsRecord.length = 4+(count+1)/2
      count = 2 * blk>>HereIsRecord.length
      BSPWriteBlock(ser>>Service.bspStr,blk,0,count)
      BSPForceOutput(ser>>Service.bspSoc)
   ]
]

//---------------------------------------------------------------
and DoWriteRecord(ser) be
//---------------------------------------------------------------
[   //Write a record to tape
   if VerifyOpen(ser) then
   if OkToWrite(ser) then
   [   let blk = ser>>Service.blk
      if blk>>WriteRecord.recordLength gr rwBlockLength-24 
       then blk>>WriteRecord.recordLength = rwBlockLength-24
      ActOnTape(ser>>Service.tape,WriteTape,0,0,lv blk>>WriteRecord.record,blk>>WriteRecord.recordLength,ser>>Service.retries)
      ReplyStatus(ser,cmdWriteRecord)
   ]
]

//---------------------------------------------------------------
and DoFwdSpaceRecord(ser) be
//---------------------------------------------------------------
[   if VerifyOpen(ser) then
   [   RecSkipFwd(ser>>Service.tape)   //Space Forward
      ReplyStatus(ser,cmdFwdSpaceRecord)   //reply with ending status
   ]
]

//---------------------------------------------------------------
and DoBackSpaceRecord(ser) be
//---------------------------------------------------------------
[   if VerifyOpen(ser) then
   [   RecSkipRev(ser>>Service.tape)   //Space Back
      ReplyStatus(ser,cmdBackSpaceRecord)   //reply with ending status
   ]
]

//---------------------------------------------------------------
and DoFwdSpaceFile(ser) be
//---------------------------------------------------------------
[   if VerifyOpen(ser) then
   [   FileSkipFwd(ser>>Service.tape)   //Skip File Forward
      ReplyStatus(ser,cmdFwdSpaceFile)   //reply with ending status
   ]
]

//---------------------------------------------------------------
and DoBackSpaceFile(ser) be
//---------------------------------------------------------------
[   if VerifyOpen(ser) then
   [   FileSkipRev(ser>>Service.tape)   //Skip File Backward
      ReplyStatus(ser,cmdBackSpaceFile)   //reply with ending status
   ]
]

//---------------------------------------------------------------
and DoWriteEOF(ser) be
//---------------------------------------------------------------
[   if VerifyOpen(ser) then
   if OkToWrite(ser) then
   [   TapeEOF(ser>>Service.tape)   //Write an EOF
      ReplyStatus(ser,cmdWriteEOF)   //reply with ending status
   ]
]

//---------------------------------------------------------------
and DoWriteBlankTape(ser) be
//---------------------------------------------------------------
[   if VerifyOpen(ser) then
   if OkToWrite(ser) then
   [   let blk = ser>>Service.blk
      let inches = blk>>WriteBlankTape.gap   //get gap size
      EraseInches(ser>>Service.tape,inches)   //Write inches of blank tape
      ReplyStatus(ser,cmdWriteBlankTape)   //reply with ending status
   ]
]

//---------------------------------------------------------------
and DoRewind(ser) be
//---------------------------------------------------------------
[   if VerifyOpen(ser) then
   [  PerformVTCB(ser>>Service.tape, Rewind)   //initiate rewind
       ReplyYes(ser,cmdRewind)   //reply before rewind complete
   ]
]

//---------------------------------------------------------------
and DoUnload(ser) be
//---------------------------------------------------------------
[   if VerifyOpen(ser) then
   [  PerformVTCB(ser>>Service.tape, Unload)   //initiate Unload
       ReplyYes(ser,cmdUnload)   //reply before rewind complete
   ]
]

//---------------------------------------------------------------
and DoGetStatus(ser) be
//---------------------------------------------------------------
[   let blk = ser>>Service.blk
   let tape = ser>>Service.tape
   blk>>HereIsStatus.length = 6
   blk>>HereIsStatus.type = cmdHereIsStatus
   blk>>HereIsStatus.drive = ser>>Service.drive
   blk>>HereIsStatus.retries = ser>>Service.retries
   blk>>HereIsStatus.speed = ser>>Service.speed
   if ser>>Service.drive ge 0 then   //open tape
   [   blk>>HereIsStatus.tstatus = TapeStatus(tape)
   ]
   //Send message
   BSPWriteBlock(ser>>Service.bspStr,blk,0,12)
   BSPForceOutput(ser>>Service.bspSoc)
]

//---------------------------------------------------------------
and DoSetStatus(ser) be
//---------------------------------------------------------------
[   let blk = ser>>Service.blk
   let which = blk>>SetStatus.selector   //get selector
   let set = blk>>SetStatus.newsetting
   switchon which into
   [
   case setRetries:
      [ test (set ge 0 & set le 8)  
        ifso   [ ser>>Service.retries = set
           ReplyYes(ser,cmdSetStatus) ]
        ifnot   ReplyNo(ser,badRetrySetting)
        endcase 
      ]
   case setSpeed:
      [ test ser>>Service.drive ls 0    //only set speed if not open
         ifso
         [ switchon set into
            [
            case 0:   ser>>Service.speed = IPS45; ReplyYes(ser,cmdSetStatus); endcase
            case 1:   ser>>Service.speed = IPS125; ReplyYes(ser,cmdSetStatus); endcase
            default:   ReplyNo(ser,badSpeedSetting); endcase
            ]
         ]
         ifnot ReplyNo(ser,openAlready)
        endcase 
      ]
   default:   ReplyNo(ser,badStatusSelector); endcase
   ]
]

//---------------------------------------------------------------
and DoSendText(ser) be
//---------------------------------------------------------------
[   //display text string to operator
   let blk = ser>>Service.blk
   Ws(lv blk>>SendText.text)
   ReplyYes(ser,cmdSendText)
]

//---------------------------------------------------------------
and DoGetText(ser) be
//---------------------------------------------------------------
[   //Get a line of Text from operator (terminated by cr)
   while kbdKey ge 0 do Block()   //wait for keyboard
   kbdKey = ser>>Service.idnumber   //take keyboard
   Ws("*nReply? ")   //prompt
   let blk = ser>>Service.blk
   blk>>HereIsText.type = cmdHereIsText
   let str = lv blk>>HereIsText.text
   let echo = "x"; let i = 1; let chr = 0
   until chr eq 13 do   //until cr
   [   while Endofs(keys) do Block()   //wait for key
      chr = Gets(keys)
      if chr eq 8 then
      [ if i gr 1 then
         [   //backspace, echo \<lastchar> and backup i
         i = i-1
         echo>>String.char↑1 = $\;Ws(echo)
         echo>>String.char↑1 = str>>String.char↑i;Ws(echo)
         ]
         loop
      ]
      if i ls (cmdBlockLength-2)/2 then 
         [ str>>String.char↑i = chr; i = i+1
           echo>>String.char↑1 = chr;Ws(echo) ]
   ]
   str>>String.length = i-1
   blk>>HereIsText.length = 2+(i+1)/2   //full words plus header
   BSPWriteBlock(ser>>Service.bspStr,blk,0,blk>>HereIsText.length*2)
   BSPForceOutput(ser>>Service.bspSoc)
   Ws("sent")
   kbdKey = -1      //release beyboard
]

//---------------------------------------------------------------
and VerifyOpen(ser) = valof
//---------------------------------------------------------------
[   if ser>>Service.drive ge 0 then resultis true
   ReplyNo(ser,driveNotOpened)
   resultis false
]

//---------------------------------------------------------------
and OkToWrite(ser) = valof
//---------------------------------------------------------------
[   let stat = TapeStatus(ser>>Service.tape)   //tape must be open!!
   unless stat<<Status.FPT then resultis true
   ReplyNo(ser,writeProtected)
   resultis false
]

//---------------------------------------------------------------
and ReplyYes(ser,code) be
//---------------------------------------------------------------
[   //Sends back Yes reply, not switchon code for string yet
   let blk = ser>>Service.blk   //get command block
   blk>>YesNo.type = cmdYes
   blk>>YesNo.cause = doneOperation
   let stat = (ser>>Service.tape)>>TCB.Flags   //get tape status
   blk>>YesNo.code = stat
   let str = "Good Command"
   YesNoMessage(blk,str,ser)   //set up string and send out
]

//---------------------------------------------------------------
and ReplyNo(ser,code) be
//---------------------------------------------------------------
[   //Sends back No block, with correct string
   let str = " "
   switchon code into
   [
   case noVersion:      str = "Version Check Unmade"; endcase
   case noGoodMessage:   str = "Illegal Command"; endcase
   case openAlready:   str = "Drive Already Open"; endcase
   case driveInUse:      str = "Drive In Use"; endcase
   case badDrive:      str = "Drive Bad"; endcase
   case badDriveNo:   str = "Bad Drive Number"; endcase
   case driveNotOpened:      str = "Drive Not Open"; endcase
   case badStatusSelector:   str = "Cannot set that Status"; endcase
   case badRetrySetting:   str = "Bad Setting for Retries"; endcase
   case writeProtected:   str = "Tape Write Protected"; endcase
   case badSpeedSetting:   str = "Illegal Speed Setting"; endcase
   default:         str = "Illegal Command"; endcase
   ]
   let blk = ser>>Service.blk   //get command block
   blk>>YesNo.type = cmdNo
   blk>>YesNo.cause = code
   let stat = (ser>>Service.tape)>>TCB.Flags   //get tape status
   blk>>YesNo.code = stat
   YesNoMessage(blk,str,ser)   //set up string and send out
]

//---------------------------------------------------------------
and ReplyStatus(ser,cmd) be
//---------------------------------------------------------------
[   //Send back the ending status of the tape drive 
   // used by most action commands
   let blk = ser>>Service.blk
   let stat = (ser>>Service.tape)>>TCB.Flags   //get tape status
   blk>>YesNo.cause = doneOperation
   blk>>YesNo.code = stat      //return status bits
   blk>>YesNo.type = cmdYes   //assume Yes
   //   always no if not online or not ready or error
   unless stat<<Status.RDY then blk>>YesNo.type = cmdNo
   unless stat<<Status.ONL then blk>>YesNo.type = cmdNo
   let error = stat & TapeErr
   if error then blk>>YesNo.type = cmdNo
   switchon cmd into   //other changes to No depend on command
   [
   case cmdWriteRecord:
      [   //No if write protected 
         if stat<<Status.FPT  then blk>>YesNo.type = cmdNo
         endcase ]
   case cmdFwdSpaceRecord:
      [   //No special no
         endcase ]
   case cmdBackSpaceRecord:
      [   //No special no
         endcase ]
   case cmdFwdSpaceFile:
      [   //No special no
         endcase ]
   case cmdBackSpaceFile:
      [   //No special no
         endcase ]
   case cmdWriteEOF:
      [   //No if not EOF 
         unless stat<<Status.EOF then blk>>YesNo.type = cmdNo
         endcase ]
   case cmdWriteBlankTape:
      [   //No special no
         endcase ]
   ]
   //put in string of good or bad operation
   let str = "Good Operation"
   if blk>>YesNo.type eq cmdNo then str = "Bad Ending Status"
   YesNoMessage(blk,str,ser)   //set up string and send out
]

//---------------------------------------------------------------
and YesNoMessage(blk,str,ser) be
//---------------------------------------------------------------
[   let i = 0
   blk>>YesNo.length = 5 + ((str>>String.length + 2) / 2)
   for i = 0 to blk>>YesNo.length - 4 do
   [   (lv blk>>YesNo.str)!i = str!i   //copy string
   ]
//   and send block out
   BSPWriteBlock(ser>>Service.bspStr,blk,0,2*blk>>YesNo.length)
   BSPForceOutput(ser>>Service.bspSoc)
]


//---------------------------------------------------------------
and DoErrorProc(str) be
//---------------------------------------------------------------
[
   Ws(str)
]