;;; -*- Mode:Zetalisp; Package:USER; Base:10; readtable:zl -*-
;;;
;;; Minor support for Symbolics' CARRY-TAPE format
;;;
;;; -dg 5/13/85
;;; -chuck kollar @CGI 02/20/86
;;;
;;;
;;;


(defun keyword-read (stream)
  "Read symbols into the package keyword.
"
  (let ((package (find-package "")))
    (read stream)))


(defun make-carry-tape-stream (&rest ignored)
  "Return a carry tape stream.
"
  #+TI
  (mt:make-mt-stream :characters t :byte-size 8 :error nil :density 0 :direction :input
		     :record-size 1024)
  #+LMI
  (fs:make-mt-stream :characters t :byte-size 8 :error nil :density 0 :direction :input
		     :record-size 1024))


(defun read-carry-tape-header ()
  (with-open-stream (stream (make-carry-tape-stream))
    (let ((plist (list (intern (string-trim '(#/space) (readline stream))))))
      (loop
	(let ((prop (keyword-read stream)))
	  (when (eq prop :END)
	    (when (get plist :DUMP-LIST-FOLLOWS)
	      (putprop plist
		       (if (and (string-equal (readline stream) "RECORD-TYPE DUMP-LIST")
				(string-equal (readline stream) "END"))
			   (do* ((line (readline stream) (readline stream))
				 (dump-list nil))
				((zerop (length line)) dump-list)
			     (setq dump-list (append dump-list (list line))))
			   (format t "~&Error in header: Bad dump list.~%"))
		       :dump-list))
	    (return plist))
	  (putprop plist
		   (case prop
			 ((:VERSION :TAPE-SYSTEM-VERSION :DUMP-LIST-FOLLOWS)	;symbols or numbers
			  (read stream))
			 ((:TIME :MACHINE :USER-ID :TAPE-HOST :TAPE-DRIVE)	;strings
			  (readline stream))
			 (t (ferror nil "unhandled prop - ~A" prop)))
		   prop))))))


(defun describe-carry-tape-header (h)
  (print h))


(defresource random-string-resource (size)
  "A resource for copying data from a raw quart-stream to another stream."
  :constructor (make-string size))


(defun decode-block-mark (thing)
  (if (string-equal "BLOK" thing :end2 4)
      (do ((num 0)
	   (word (string thing))
	   (count 15 (1- count)))
	  ((= count 3) num)
	(setq num (+ num
		     (* (- (aref word count) 48)
			(^ 10. (- 15 count))))))
      (ferror nil "~&Bad block marker - ~a" thing)))


(defun preservable-properties (plist)
  (let ((return-list (list "Preservable Properties")))
    (when (get plist :CREATION-DATE)
      (setf (get return-list :CREATION-DATE)
	    (typecase (get plist :CREATION-DATE)
	      (:STRING (time:parse-universal-time (get plist :CREATION-DATE)))
	      (:NUMBER (get plist :CREATION-DATE))
	      (ELSE (time:get-universal-time)))))
    (when (get plist :AUTHOR)
      (setf (get return-list :AUTHOR)
	    (string (get plist :AUTHOR))))
    (cdr return-list)))


(defun read-carry-record-plist (instream &aux (plist (list 'carry-tape-file-plist)))
  (do ((prop (keyword-read instream) (keyword-read instream)))
      ((eq prop :END) plist)
    (setf (get plist prop)
	  (case prop
		((:CREATION-DATE :AUTHOR :TYPE :RAW-TYPE :NAME
				 :RAW-NAME :PATHNAME :DIRECTORY :HOST :DUMP-GROUP)
		 (readline instream))
		((:VERSION :CHARACTERS :SYSTEM-TYPE :CANONICAL-TYPE :TAPE-SYSTEM-VERSION
			   :RECORD-TYPE :BYTE-SIZE)
		 (read instream))))))


(defun copy-carry-tape-record (host &key
			       (create-directory t)
			       overwrite
			       query
			       from-stream)
  (setq host (si:parse-host host))
  (with-open-stream (instream (if from-stream
				  (open from-stream)
				  (make-carry-tape-stream)))
    (let* ((plist (read-carry-record-plist instream))
	   (new-pathname (parse-pathname-on-carry-tape (get plist :PATHNAME) host))
	   (preservable-properties nil))
      (unless new-pathname
	(return-from copy-carry-tape-record nil))
      
      (condition-case ()
	  (directory new-pathname)		; error if dir not there
	(fs:directory-not-found
	 (when (and create-directory
		    (if query
			(y-or-n-p "~&Create directory ~A?" new-pathname)
			t))
	   (format t "~&Creating directory for ~A~%" new-pathname)
	   (fs:create-directory new-pathname)
	   (setq overwrite t)))
	(:no-error (if (probe-file new-pathname)
		       (when (eq overwrite :ASK)
			 (setq overwrite (yes-or-no-p "~&Overwrite file [~A]? ")))
		       (setq overwrite t))))
      
      (cond ((not overwrite)
	     (format t "~&File Already exists - ~A - Not Overwriting.~%" new-pathname))
	    ((if query (y-or-n-p "~&Restore ~A?" new-pathname) t)
	     (format t "~&Copying file ~A to ~A - " (get plist :PATHNAME) new-pathname)
	     (setq preservable-properties (preservable-properties plist))
	     (with-open-stream (outstream
				 (if overwrite
				     (open new-pathname
					   :DIRECTION :OUTPUT
					   :CHARACTERS (get plist :CHARACTERS)
					   :BYTE-SIZE (or (get plist :BYTE-SIZE)
							  (if (get plist :CHARACTERS) 8. 16.)))
				     'si:null-stream))
	       (when (and preservable-properties (neq outstream 'si:null-stream))
		 (lexpr-send outstream :CHANGE-PROPERTIES t preservable-properties))
	       (using-resource (block-string random-string-resource 16)
		 (do ((block-mark (progn (send instream :STRING-IN t block-string 0 3)
					 (if (string-equal block-string "EOF" :END1 3)
					     :EOF
					     (send instream :STRING-IN t block-string 3)
					     block-string))
				  (progn (send instream :STRING-IN t block-string 0 3)
					 (if (string-equal block-string "EOF" :END1 3)
					     :EOF
					     (send instream :STRING-IN t block-string 3)
					     block-string))))
		     ((eq block-mark :EOF))
		   (using-resource (string random-string-resource (decode-block-mark block-mark))
		     (send instream :STRING-IN t string)
		     (send outstream :STRING-OUT string)))))
	     (when overwrite (format t "done.~%")))))))


(defun restore-carry-tape (&key
			   (host si:local-host)
			   (overwrite nil)
			   (query nil)
			   (rewind t))
  (setq host (si:parse-host host))
  (when rewind (mt:rewind))
  (describe-carry-tape-header (read-carry-tape-header))
  (loop
    (condition-case ()
	(copy-carry-tape-record host :overwrite overwrite :query query)
      (fs:read-end-of-file))))


;; you may want to modify this function to serve your own purposes.

(defvar *pathname-string-replacements*
	'((#/[ #/<) (#/] #/>) (#/; #/#)))


(defun parse-pathname-on-carry-tape (string host)
  (let ((n (string-search ":" string)))
    (do ((s (substring string (if n (1+ n) 0)))
	 (j 0 (1+ j)))
	((= j (length s))
	 (fs:parse-pathname s host))
      (setf (aref s j)
	    (or (cadr (assq (aref s j) *pathname-string-replacements*)) 
		(aref s j))))))

