;;;-*- Mode:LISP; Package:SERIAL; Fonts:(CPTFONT TR12I); Base:8 -*-

;1;; LMODEM driver for Lisp Machines*

;1;; This file contains all stuff specific to the LMODEM protocol.*
;1;; This conforms to the LMODEM protocol as used on MIT-ITS and*
;1;; as described in MC:AR60:CPM;MODEM2 PROTOC*

(defun morrow-send-file (file)
  (lmodem-send-file
    file
    (si:make-serial-stream ':baud 2000.
			   ':number-of-data-bits 8.
			   ':number-of-stop-bits 1
			   ':parity nil
			   ':ascii-characters nil)))

(defun morrow-receive-file (file)
  (lmodem-receive-file
    file
    (si:make-serial-stream ':baud 1200.
			   ':number-of-data-bits 8.
			   ':number-of-stop-bits 1
			   ':parity nil
			   ':ascii-characters nil)))

(defun lmodem-send-file (file stream)
  (send (make-instance 'lmodem-connection ':modem-stream stream)
	':send-file file))

(defun lmodem-receive-file (file stream)
  (send (make-instance 'lmodem-connection ':modem-stream stream)
	':get-file file))

(defflavor lmodem-connection ()
	   (modem-connection)
  :initable-instance-variables)

(defmethod (lmodem-connection :get-file) (file remote-file)
  (condition-case ()
      (with-open-file (file-stream file ':direction ':output)
	(*catch ':close-transfer
	  (send modem-stream ':STRING-OUT
		(string-append tmodem-pathname " -st " remote-file))
	  (send modem-stream ':tyo #Return)
	  (send self ':receive-file file-stream))
	(tv:notify nil "Transfer of file ~A complete." file))
    (sys:abort (tv:notify nil "File transfer aborted."))))

(defmethod (lmodem-connection :send-file) (file remote-file)
  (condition-case ()
      (with-open-file (file-stream file ':direction ':input)
	(send modem-stream ':STRING-OUT
	      (string-append tmodem-pathname " -rt " remote-file))
	(send modem-stream ':TYO #Return)
	(send self ':transmit-file file-stream)
	(tv:notify nil "Transfer of file ~A complete." file))
    (sys:abort (tv:notify nil "File transfer aborted."))))

(defmethod (lmodem-connection :read-packet) ()
  (or
    (dotimes (count 10)				;Master recount
      (*catch ':master-retry
	(let ((soh-maybe))
	  (loop with time = (time:get-universal-time)
		for current-time = (time:get-universal-time)
		until (or (> (- current-time time) receive-timeout)
			  (memq soh-maybe '(#soh #can #eot)))
		do (setq soh-maybe (send self ':tyi-with-timeout receive-timeout)))
	  (when (equal soh-maybe #eot)
	    (send modem-stream ':tyo #ack)
	    (*throw ':EOF ':SUCCESS))
	  (unless (equal soh-maybe #soh)
	    (send self ':nak "Bad Char for SOH, ~A~%" soh-maybe))
	  (when (equal soh-maybe #can)
	    (tv:notify nil "CAN received]%")
	    (ferror eh:abort-object))
	  (cond
	    ((not soh-maybe) nil)
	    (t (let (block not-block
		     (packet (allocate-resource 'SERIAL-PACKET))
		     cksum temp (real-check 0))
		 (unless (setq block (send self ':tyi-with-timeout char-timeout))
		   (send self ':nak "Timeout on BLOCK")
		   (*throw ':master-retry nil))
		 (unless (setq not-block (send self ':tyi-with-timeout char-timeout))
		   (send self ':nak "Timeout on NOT-BLOCK")
		   (*throw ':master-retry nil))
		 (dotimes (count1 128.)
		   (unless (setq temp (send self ':tyi-with-timeout char-timeout))
		     (send self ':NAK "Character timeout on character ~A" count1)
		     (*throw ':MASTER-RETRY nil))
		   (array-push packet temp)
		   (setq real-check (+ temp real-check)))
		 (setq real-check (logand real-check 377))
		 (unless (setq cksum (send self ':tyi-with-timeout char-timeout))
		   (send self ':nak "Timeout on checksum")
		   (*throw ':master-retry nil))
		 (cond ((and (equal not-block (- 255. block))
			     (equal block block-number)
			     (equal cksum real-check))
			(send modem-stream ':TYO #ACK)
			(setf (modem-block packet) block)
			(return packet))
		       (t (send self ':nak
				"Check ~A, Real Check ~A, Block ~A, Not-Block ~A, RB ~A"
				cksum real-check block not-block block-number)
			  nil))))))))
    (progn
      (send modem-stream ':tyo #can)
      (*Throw ':CLOSE-TRANSFER NIL)
      (ferror eh:abort-object))))

(defmethod (lmodem-connection :send-packet-from-file-and-wait) (file-stream)
  (let ((packet (allocate-resource 'SERIAL-PACKET)) (check 0))
    (unwind-protect
	(progn
	  (dotimes (count 128.)
	    (cond (carryover
		   (aset carryover packet count)
		   (setq check (+ check carryover)
			 carryover nil))
		  (t (cond ((not eof-flag)
			    (aset #Control-Z packet count)
			    (setq check (+ check #Control-Z)))
			   (t (let* ((char (send file-stream ':TYI))
				     (char1 (and char (translate-lispm-to-ascii char))))
				(when (equal char #\Return)
				  (setq char1 #Return)
				  (setq carryover #Linefeed))
				(when char1
				  (setq check (+ check char1))
				  (aset char1 packet count))
				(unless char
				  (setq eof-flag nil)
				  (setq check (+ check #Control-Z))
				  (aset #Control-Z packet count))))))))
	  (dotimes (count 10.)
	    (send modem-stream ':TYO #SOH)
	    (send modem-stream ':TYO (logand block-number #o377))
	    (send modem-stream ':TYO (logxor #o377 (logand block-number #o377)))
	    (send modem-stream ':STRING-OUT packet)
	    (send modem-stream ':CLEAR-INPUT)
	    (send modem-stream ':TYO (logand #o377 check))
	    (let ((char (send self ':TYI-WITH-TIMEOUT receive-timeout)))
	      (cond ((and char (= char #ack))
		     (*throw ':GOOD-SEND T))
		    (t (when (equal char #can) (ferror eh:abort-object)))))))
      (deallocate-resource 'SERIAL-PACKET packet)))
  (ferror eh:abort-object))

(compile-flavor-methods lmodem-connection)
