//   DDTapeServerDOs.bcpl   Tape Server Do* command procedures
//   Last modified by Tim Diebert, June 22, 1981  4:01 PM
//
get   "DDTapes.d"
get   "PUP.decl"
get   "DDTapeServer.decl"

//---------------------------------------------------------------
external
//---------------------------------------------------------------
[
//Imports

//From DDTapeServerDispUtil
   InitDisplay; DWs; DWo; MonitorKeys

//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 = OpenDDTape(dr,  DoErrorProc,  DoErrorProc)
            test tape
            ifnot   ReplyNo(ser, badDrive)
            ifso
               [   //All went well!!
               ser>>Service.tape = tape
               ser>>Service.drive = dr
               useddrives!dr = true
               ser>>Service.density = PE
               if tape>>VDDTCB.opDensity eq NRZI
                  then ser>>Service.density = NRZI
               ReplyYes(ser, cmdOpenDrive)
               ]
            ]
   ]
]

//---------------------------------------------------------------
and DoCloseDrive(ser) be
//---------------------------------------------------------------
[
   if VerifyOpen(ser) then   //drive is open,  close it
      [
      let dr = ser>>Service.drive
      useddrives!dr = false
      CloseDDTape(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
      ActOnDDTape(ser>>Service.tape, ReadFwd, lv blk>>HereIsRecord.record, rwBlockLength, 0, 0, ser>>Service.retries)
      let count = (ser>>Service.tape)>>DDTCB.ByteCount
      if count gr rwBlockLength 
         then count = rwBlockLength   //truncate to fit
      blk>>HereIsRecord.recordLength = (ser>>Service.tape)>>DDTCB.ByteCount
      blk>>HereIsRecord.endingStatus = (ser>>Service.tape)>>DDTCB.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 
         then blk>>WriteRecord.recordLength = rwBlockLength
      ActOnDDTape(ser>>Service.tape, Write, 0, 0, lv blk>>WriteRecord.record, blk>>WriteRecord.recordLength, ser>>Service.retries)
      ReplyStatus(ser, cmdWriteRecord)
      ]
]

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

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

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

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

//---------------------------------------------------------------
and DoWriteEOF(ser) be
//---------------------------------------------------------------
[
   if VerifyOpen(ser) then
      if OkToWrite(ser) then
         [
         ActOnDDTape(ser>>Service.tape, WriteEOF)   //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
         let vtape = ser>>Service.tape   //get ptr to TCB
         let gapsize = 960   //number of bytes in PE gap.
         if vtape>>VDDTCB.opDensity eq NRZI then gapsize = 480   //number of bytes in NRZI gap.
         let bytes = (inches * vtape>>VDDTCB.opDensity) - gapsize   //get gap size
         if bytes le 0 then bytes = 1   
         ActOnDDTape(vtape, EraseVar, bytes)   //Write inches of blank tape
         ReplyStatus(ser, cmdWriteBlankTape)   //reply with ending status
      ]
]

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

//---------------------------------------------------------------
and DoUnload(ser) be
//---------------------------------------------------------------
[
   if VerifyOpen(ser) then
      [
      PerformVDDTCB(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.density = ser>>Service.density
   if ser>>Service.drive ge 0 then   //open tape
      [
      blk>>HereIsStatus.tstatus = PerformVDDTCB(tape, NoOp)
      ]
   //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 setDensity:
         [
         test (set eq PE % set eq NRZI)  
            ifso
               [
               ser>>Service.density = set
               let ok = SetDensity(ser>>Service.tape, set)
               test ok
                  ifso
                     [
                     ReplyYes(ser, cmdSetStatus)
                     ser>>Service.density = set
                     ]
                  ifnot
                     ReplyNo(ser, DensitySetNotatBOT)
               ]
            ifnot
               ReplyNo(ser, badDensitySetting)
        endcase 
         ]
   default:   ReplyNo(ser, badStatusSelector); endcase
      ]
]

//---------------------------------------------------------------
and DoSendText(ser) be
//---------------------------------------------------------------
[   //display text string to operator
   let blk = ser>>Service.blk
   DWs(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
   DWs("*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 = $\
            DWs(echo)
            echo>>String.char↑1 = str>>String.char↑i
            DWs(echo)
            ]
            loop
         ]
      if i ls (cmdBlockLength-2)/2 then 
         [
         str>>String.char↑i = chr
         i = i+1
         echo>>String.char↑1 = chr
         DWs(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)
   DWs("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 = PerformVDDTCB(ser>>Service.tape, NoOp)
   unless stat<<DDStatus.FPT then resultis true
   (ser>>Service.tape)>>VDDTCB.WFP = 1 // fake write error
   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)>>DDTCB.Flags   //get tape status
   blk>>YesNo.stat = 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 badDensitySetting:   str = "Illegal Density Setting"; endcase
      case DensitySetNotatBOT:   str = "Density set at place other than BOT"; 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)>>DDTCB.Flags   //get tape status
   blk>>YesNo.stat = 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)>>DDTCB.Flags   //get tape status
   blk>>YesNo.cause = doneOperation
   blk>>YesNo.stat = stat      //return status bits
   blk>>YesNo.type = cmdYes   //assume Yes
   //   always no if not online or not ready or error
   unless stat<<DDStatus.RDY then blk>>YesNo.type = cmdNo
   unless stat<<DDStatus.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<<DDStatus.FPT  then blk>>YesNo.type = cmdNo
         if stat<<DDStatus.HE % stat<<DDStatus.SE % stat<<DDStatus.HDWERR %
           stat<<DDStatus.RDP % stat<<DDStatus.DL 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<<DDStatus.FMK 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
//---------------------------------------------------------------
[
   DWs(str)
]