(defclass rsync-translation ()
((base :initarg :base :initform (error "base must be populated") :accessor base-of)
(map-from :initarg :map-from :initform (error "map-from must be populated") :accessor map-from-of)
(map-to :initarg :map-to :initform (error "map-to must be populated") :accessor map-to-of)
(password-file :initarg :password-file :initform nil :accessor password-file-of)
(port :initarg :port :initform nil :accessor port-of)
(user :initarg :user :initform nil :accessor user-of)))
(defvar *rsync-translations* nil)
(defun add-rsync-translation (base map-from map-to &key password-file port user)
(let ((new-translation (make-instance 'rsync-translation
:base base
:map-from map-from :map-to map-to
:password-file password-file :port port :user user)))
(let ((member-head (member (pathname-directory base) *rsync-translations*
:key (lambda (item) (pathname-directory (base-of item)))
:test (lambda (a b) (equal a b)))))
(if member-head
(setf (car member-head) new-translation)
(push new-translation *rsync-translations*)))))
(defmacro with-rsync-translations (xlat &body body)
`(let ((base (base-of ,xlat))
(map-from (map-from-of ,xlat))
(map-to (map-to-of ,xlat))
(password-file (password-file-of ,xlat))
(port (port-of ,xlat))
(user (user-of ,xlat)))
,@body))
(defun cp (source dest &key password-file port user)
(sys:call-system-showing-output
(format nil
"rsync ~A ~A rsync://~A~A~A/~A~A"
(if password-file
(format nil "--password-file=~A" (namestring password-file))
"")
(namestring source)
(if user (format nil "~A@" user) "")
(pathname-host dest)
(if port (format nil ":~A" port) "")
(pathname-device dest)
(directory-namestring dest))
:output-stream nil
:kill-process-on-abort t
:show-cmd nil))
(defun subtree-of (path base)
(let ((path-dir (pathname-directory path))
(base-dir (pathname-directory base)))
(loop for base-item on base-dir
for path-item on path-dir
unless (and (equal (car base-item) (car path-item))
(or (not (cdr base-item))
(cdr path-item)))
do (return nil)
finally (return t))))
(defun lookup-translation (pathname)
(find-if (lambda (item) (subtree-of pathname (base-of item)))
*rsync-translations*))
(defun timestamp ()
(multiple-value-bind (s m h d mm y) (get-decoded-time)
(format nil "~4,'0D-~2,'0D-~2,'0D ~2,'0D:~2,'0D:~2,'0D" y mm d h m s)))
(defun rsync-file (buffer)
(unless buffer (setf buffer (editor:current-buffer)))
(let* ((buffer-pathname (editor:buffer-pathname buffer)))
(when-let (translation (lookup-translation buffer-pathname))
(with-rsync-translations translation
(format t "~&~A: Updating ~A on ~A ... "
(timestamp)
(file-namestring buffer-pathname)
(car (last (pathname-directory base))))
(cp buffer-pathname
(merge-pathnames
(make-pathname :directory (append '(:relative)
(subseq (pathname-directory buffer-pathname)
(length (pathname-directory map-from)))))
map-to)
:password-file password-file :port port :user user
)
(format t "done~%")
))))
(editor::add-global-hook editor::write-file-hook 'rsync-file)
; ///file => rsync://////file
; (host, user, pw-file, port are not encoded in the source or destination path)
; so for a given base, need a list of translations:
; host/prefix => host/device/target
(let ((base (make-pathname :directory '(:absolute "big2" "home" "lmc" "work" "employer" "client" "server"))))
(add-rsync-translation base base
(make-pathname :host "localhost" :device "root" :directory '(:absolute))
:password-file (merge-pathnames (make-pathname :directory '(:relative :back)
:name ".rsync-pw")
base)
:port 1873
:user "lmc"))