#!/bin/sh
: ; exec klone $0 "$@"
; The above line finds the klone executable in the $PATH
;;Skeleton of a typical klone script
;;(stack-dump-on-error t)
;;(kdb t)

(setq args (getopts "USAGE: vcat [file]
like cat, but outputs on stderr a dot per k read (or per chunk)
cats file or stdin if no file specified or file is -
"
    ("-m" () meg "output a dot per megabyte read (i.e: -c 1024)")
    ("-g" () bargraph "bargraph output. needs -b option")
    ("-b" bytes bytes "expected size in bytes for bargraph. will only copy
this number of bytes
defaults to the size of file if not stdin.")
    ("-o" file outfilename "output to file. default to stdout")
    ("-n" () null "output file to /dev/null")
    ("-q" () quiet "quiet, no output to stderr. maybe useful with -r")
    ("-skip" bytes skip "skip N first bytes")
    ("-c" bytes chunk-size "chunk (bytes per dot) size in K. default to 1")
    ("-r" N max-retries "in case of read errors, retry N times. default 0")
    ("-i" () ignore_errors "in case of read errors, ignore a sector")
    ("-ss" sector_size sector_size "sector size to ignore in bytes (def. 1)")
    ("-slow" N slow "sets chuncksize to 1K after N bytes")
    ("-s" () showspeed "display instant transfer speed under the bargraph")
))

(setq chuncksize (if meg (* 1024 1024) 1024))
(if (not sector_size) (setq sector_size 1))
(setq sector_size (Int sector_size))
(if chunk-size (setq chuncksize (* 1024 (Int chunk-size))))
(setq fdout (if outfilename 
    (open outfilename :direction :output :if-exists :supersede)
    *standard-output*
))
(setq bytes (if bytes (Int bytes) 0))
(setq skip (if skip (Int skip) 0))
(setq slow (if slow (Int slow) *maxint*))
(if (and bargraph (<= bytes 0) (not (and args (/= "-" (0 args))))) (progn
    (print-format *standard-error* "vcat error: -g requires -b\n")
    (exit 1)
))
(setq max-retries (if max-retries (Int max-retries) 0))

(if quiet
  (defmacro P (&rest args) ())
  (defmacro P (&rest args) `(print-format  *standard-error* ,@args))
)

(defun main (&aux
    (size skip)
    elapsed-time
    (retries max-retries)
    curtime
    (start-time (mod (get-internal-run-time) *maxint*))
    (toread chuncksize)
  )
  (if (and args (/= "-" (0 args)))
    (progn
      (setq fdin (open (0 args)))
      (file-position fdin skip)
      (if (<= bytes 0)
	(setq bytes (get (file-stats (0 args)) 'size))
      )
    )
    (progn
      (setq fdin *standard-input*)
      (if (/= 0 skip) 
	(dotimes (i (/ skip chuncksize)) (read-chars chuncksize fdin))
	(read-chars (- skip (* chuncksize (/ skip chuncksize))) fdin)
  )))
  (init-display)
  (catch 'EOF
    (while t
      (if (>= size slow)
	(setq toread 1024)		;sets buffer to 1k before end
      )
      (setq buffer (read-chars toread fdin))
      (if (> (length buffer) 0) (progn
	  (incf size (length buffer))
	  (show-progress size)
	  (if (/= retries max-retries) (progn	;there was an error
	      (P "Read error at byte %0, but OK after %1 retries!\n\e[A"
		(- max-retries retries)
	      )
	      (setq retries max-retries)
	  ))
	)
	(if (> retries 0) (progn
	    (P "Read error at byte %0, retrying %1 times...     !\n\e[A"
	      size retries
	    )
	    (incf retries -1)
	  )
	  (if ignore_errors
	    (with (n (- sector_size (mod size sector_size)))
	      (P "vcat: read error at byte %0! chunk filled with %1 0s... \n" 
		size n)
	      (setq buffer (make-string n 0))
	      (incf size (length buffer))
	      (file-position fdin size)
	    )
	    (progn
	      (P "vcat: read error at byte %0!                         \n"
		size)
	      (end start-time size 2)
      ))))
      (if (not null) 
	(if (/= (length buffer)
	    (write-chars buffer () fdout)
	  )
	  (progn 
	    (P "vcat: write error at byte %0!                        \n"
	      size)
	    (end start-time size 2)
      )))
      (if (and (> bytes 0) (>= size bytes))
	(end start-time size 0)
      )
  ))
  (end start-time size 0)
)

(defun show-progress-dot (size)
  (write-char #\. *standard-error*)
)

(defun init-display ()
  (setq show-progress 
    (if 
      quiet ()
      bargraph 
      (progn (ascii-progress-bar 0 bytes)
	(if showspeed
	  ascii-progress-bar-with-speed
	  ascii-progress-bar
	)
      )
      show-progress-dot))
)

(defun end (start-time size code &aux elapsed-time)
  (if (not quiet) (progn
      (setq elapsed-time
	(+ 1 (- (mod (get-internal-run-time) *maxint*) start-time)))
      (print-format  *standard-error* 
	"%5%0 chars (%1k, %2M) read in %3ms (%4 bytes per second)\n" 
	size (/ size 1024) (/ size (* 1024 1024))
	elapsed-time 
	(with (bps (Int (/ size (/ elapsed-time 1000.))))
	  (if (< bps 64000) bps (+ (String (/ bps 1024)) " kilo"))
	)
	(if bargraph "" "\n")
  )))
  (flush ())
  (exit code)
)

(main)

;;; EMACS MODES
;;; Local Variables: ***
;;; mode:lisp ***
;;; End: ***

