;;; -*- Mode:zetaLISP; Package:USER; Base:10; readtable:zl -*-
;;;
;;; Minor support for Symbolics' CARRY-TAPE format
;;;
;;; -dg 5/13/85
;;;

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

(defun make-carry-tape-stream (&rest ignored)
  #+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))
    (do ((plist (list (intern (string-trim '(#/space) (readline stream))))))
	(())
      (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 ((dump-list)
			      (line (readline stream) (readline stream)))
			     ((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-random-string size))

(defun make-random-string (size)
  (let ((string (make-string size)))
    (list string (make-array (floor size 2) :type 'art-16b :displaced-to string))))

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


(defun preservable-properties (plist)
  (let ((return-list (list "Preservable Properties")))
    (cond-every ((get plist :creation-date)
		 (putprop return-list
			  (typecase (get plist :creation-date)
			    (string (time:parse-universal-time
				       (get plist :creation-date)))
			    (number (get plist :creation-date))
			    (t (time:get-universal-time)))
			  :creation-date))
		((get plist :author)
		 (putprop return-list
			  (string (get plist :author))
			  :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)
    (putprop plist
	     (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)))
	     prop)))

(defun copy-carry-tape-record (host &key
			       (create-directory t)
			       overwrite
			       query
			       from-stream
			       (parse-pathname-on-carry-tape 'default-parse-pathname-on-carry-tape))
  (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)
	  (preservable-properties)
	  (byte-size))
      (or (setq new-pathname (funcall parse-pathname-on-carry-tape (get plist :pathname) host plist))
	  (return-from copy-carry-tape-record nil))
      (condition-case ()
	  (probef new-pathname)
	(fs:directory-not-found
	 (when (and create-directory (or (not query)
					 (y-or-n-p "~&Create directory ~A?" new-pathname)))
	   (format t "~&Creating directory for ~A~%" new-pathname)
	   (fs:create-directory new-pathname)
	   (setq overwrite t)))
	(:no-error (if (probef 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))
	    ((and query (not (y-or-n-p "~&Restore ~A?" new-pathname)))
	     ())
	    ('else
	     (format t "~&Copying file ~A to ~A - " (get plist :pathname) new-pathname)
	     (fs:create-directory new-pathname)
	     (setq preservable-properties (preservable-properties plist))
	     (setq byte-size (or (get plist :byte-size)
				 (if (get plist :characters) 8. 16.)))
	     (with-open-stream (outstream
				 (if overwrite
				     (open new-pathname
					   :direction :output
					   :characters (get plist :characters)
					   :byte-size byte-size)
				     'si:null-stream))
	       (when (and preservable-properties (neq outstream 'si:null-stream))
		 (lexpr-send outstream :change-properties t preservable-properties))
	       (using-resource (block-string-r random-string-resource 16)
		 (let ((block-string (car block-string-r)))
		   (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-r random-string-resource (decode-block-mark block-mark))
		       (let ((string (car string-r)))
			 (send instream :string-in t string)
			 (cond ((= byte-size 8.)
				(send outstream :string-out string))
			       ((= byte-size 16.)
				(send outstream :string-out (cadr string-r))))))))))
	     (when overwrite (format t "done.~%")))))))

(defun restore-carry-tape (&key
			   (host si:local-host)
			   (overwrite nil)
			   (query nil)
			   transform)
  (setq host (si:parse-host host))
  (fs:mt-rewind)
  (describe-carry-tape-header (read-carry-tape-header))
  (do-forever 
    (condition-case ()
	(copy-carry-tape-record host :overwrite overwrite :query query
				:parse-pathname-on-carry-tape (or transform 'default-parse-pathname-on-carry-tape))
      (fs:read-end-of-file))))


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

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

(defun default-parse-pathname-on-carry-tape (string host plist)
  plist
  (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))))))


(defun symbolics-selective-parse-pathname (pathname host plist)
  (cond ((string-equal (get plist :type) "BFD")
	 (let ((p (fs:parse-pathname pathname)))
	   (send p :new-pathname :host host
		 :directory (append '("GRUMMAN")
				    (if (atom (send p :directory))
					(list (send p :directory))
					(send p :directory))))))
	('else
	 (format t "~&Skipping ~S~%" pathname))))
