;;; -*- Mode:LISP; Package:USER; Base:10; readtable:zl -*-
;;; TI machines want to see MODE:ZETALISP,
;;; damn, MODE:LISP gives nothing!
;;; -*- Mode:ZETALISP; Package:USER; Base:10; readtable:zl -*-
;;;
;;; Minor support for Symbolics' CARRY-TAPE format
;;;
;;; -dg 5/13/85
;;; -gjc 11-Aug-86 13:09:07

(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
	;; this is actually a max record size. if too small then there
	;; will be an error during read.
				     :record-size 16384))


(defun read-prop-line (stream)
  (let ((line (readline stream nil)))
    (cond ((null line) nil)
	  ('else
	   (let ((n (string-search " " line)))
	     (cond ((null n)
		    (intern line ""))
		   ('else
		    (let ((key (intern (substring line 0 n) "")))
		      (values key
			      (funcall (or (get key 'prop-line-value)
					   #'(lambda (line n)
					       (string-trim '(#\space)
							    (substring line n))))
				       line
				       n))))))))))

(defprop :creation-date ut-prop-line prop-line-value)
(defprop :time ut-prop-line prop-line-value)

(defun ut-prop-line (line start)
  (or (catch-error (parse-integer line :start start) nil)
      (time:parse-universal-time line start)))

(defun lisp-prop-line-value (line start)
  (let ((base 10.)
	(ibase 10.)
	(*package* (find-package 'user))
	(*readtable* (si:find-readtable-named "CL")))
    (read-from-string line nil start)))

(defprop :tape-system-version lisp-prop-line-value prop-line-value)
(defprop :dump-list-follows lisp-prop-line-value prop-line-value)

(defun read-plist-lines (stream name)
  (do ((key)(value)(plist (ncons name)))
      ((not (multiple-value-setq (key value) (read-prop-line stream)))
       plist)
    (and (eq :end key) (return plist))
    (putprop plist value key)))

(defun read-carry-tape-header ()
  (let ((stream (make-carry-tape-stream)))
    (let ((plist (read-plist-lines stream 'header)))
      (flush-tape-stream stream)
      plist)))


(defun flush-tape-stream (stream)
  (stream-copy-until-eof stream 'si:null-stream)
  (send stream :close :raw))


(defun describe-carry-tape-header (h)
  (format t "~&       ~A~%" (car h))
  (do ((l (cdr h) (cddr l)))
      ((null l))
    (format t "~S ~S~%" (car l) (cadr l))))

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

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

(defun preservable-properties (plist)
  (do ((return-list (ncons 'preservable-properties))
       (l '(:creation-date :author) (cdr l))
       (value))
      ((null l) (cdr return-list))
    (if (setq value (get plist (car l)))
	(putprop return-list value (car l)))))

(defprop :version lisp-prop-line-value prop-line-value)
(defprop :characters lisp-prop-line-value prop-line-value)
(defprop :byte-size lisp-prop-line-value prop-line-value)

(defun copy-carry-tape-file (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))
  (let ((instream (if from-stream
		      (open from-stream)
		    (make-carry-tape-stream))))
    (let ((plist (read-plist-lines instream 'carry-tape-file-plist))
	  (new-pathname)
	  (preservable-properties)
	  (byte-size))
      (when (not (setq new-pathname (funcall parse-pathname-on-carry-tape
					     (get plist :pathname) host plist)))
	(format t "~&Skipping tape file ~A~%" (get plist :pathname))
	(return-from copy-carry-tape-file (close instream)))
      (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)
	     (close instream))
	    ((and query (not (y-or-n-p "~&Restore ~A?" new-pathname)))
	     (close instream))
	    ('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-file (outstream new-pathname
					:direction :output
					:characters (get plist :characters)
					:byte-size byte-size)
	       (when preservable-properties
		 (lexpr-send outstream :change-properties t preservable-properties))
	       (copy-carry-tape-blocks instream
				       outstream
				       byte-size)))))))



(defun copy-carry-tape-blocks (instream outstream byte-size)
  (do-forever
    (ecase (send instream :tyi)
      (#\B
       (do ((l '(#\L #\O #\K) (cdr l)))
	   ((null l))
	 (or (eq (car l) (send instream :tyi))
	     (ferror nil "bad carry tape file record")))
       (let ((record-size 0))
	 (dotimes (j 12)
	   (setq record-size (+ (or (digit-char-p (send instream :tyi))
				    (ferror nil "bad carry tape file record"))
				(* record-size 10))))
	 (using-resource (string-r random-string-resource record-size)
	   (let ((string (car string-r)))
	     (send instream :string-in t string 0 record-size)
	     (cond ((= byte-size 8.)
		    (send outstream :string-out string))
		   ((= byte-size 16.)
		    (setf (fill-pointer (cadr string-r)) (ceiling record-size 2))
		    (send outstream :string-out (cadr string-r))))))))
      (#\E
       (do ((l '(#\O #\F) (cdr l)))
	   ((null l))
	 (or (eq (car l) (send instream :tyi))
	     (ferror nil "bad carry tape file record")))
       (flush-tape-stream instream)
       (return (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-file
	 host :overwrite overwrite :query query
	 :parse-pathname-on-carry-tape (or transform
					   'default-parse-pathname-on-carry-tape))
      (fs:read-end-of-file))))

(defvar *dummy-lispm-host* (si:make-dummy-host :lispm))

(defvar *dummy-symbolics-host* (si:make-dummy-host :lispm))
(putprop *dummy-symbolics-host* :lmfs 'fs:file-system-type)

(defvar *dummy-unix-host* (si:make-dummy-host :unix))
(defvar *dummy-vms-host* (si:make-dummy-host :vms))
(defvar *dummy-tops20-host* (si:make-dummy-host :tops-20))

(defun strip-host (string)
  (let ((n (string-search ":" string)))
    (cond (n
	   (substring string n))
	  ('else
	   string))))

(defun default-parse-pathname-on-carry-tape (string host plist)
  (default-transform-pathname
    (cond ((string-equal (get plist :system-type) "LISPM")
	   (cond ((string-search ">" (get plist :directory))
		  (fs:parse-pathname (strip-host string)
				     *dummy-symbolics-host*))
		 ('else
		  (fs:parse-pathname (strip-host string) *dummy-lispm-host*))))
	  ((string-equal (get plist :system-type) "UNIX")
	   (fs:parse-pathname (strip-host string)
			      *dummy-unix-host*))
	  ((string-equal (get plist :system-type) "VMS")
	   (fs:parse-pathname (strip-host string)
			      *dummy-VMS-host*))
	  ((string-equal (get plist :system-type) "TOPS-20")
	   (fs:parse-pathname (strip-host string)
			      *dummy-tops20-host*))
	  
	  ('else
	   (ferror nil "unknown system type: ~S" (get plist :system-type))))
    host))

(defvar *default-ignore-types* '("BIN" "QFASL" "XFASL" "PRESS"))

(defvar *default-select-directories* nil)

(defun default-transform-pathname (x h)
  (and (default-select-pathname x)
       (send x :new-pathname :host h)))

(defun default-select-pathname (x)
  (cond ((mem #'(lambda (a b) (si:string-matchp b a))
	      (send x :type)
	      *default-ignore-types*)
	 ())
	(*default-select-directories*
	 (mem #'directory-matchp (if (atom (send x :directory))
				     (list (send x :directory))
				   (send x :directory))
	      *default-select-directories*))
	('else
	 t)))


(defun directory-matchp (dir pattern)
  (do ((ldir dir (cdr ldir))
       (lp pattern (cdr lp)))
      ((or (null ldir) (null lp))
       (or (and (null ldir) (null lp))
	   (eq (cadr lp) '**)))
    (cond ((eq (car lp) '**)
	   (return t))
	  ((eq (car lp) '*))
	  ((si:string-matchp (car lp) (car ldir)))
	  ('else
	   (return nil)))))
		  
