;;; -*-lisp-*-
;;;
-;;; $Id$
-;;;
;;; Safely modify collections of files
;;;
;;; (c) 2005 Straylight/Edgeware
;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
(defpackage #:safely
- (:use #:common-lisp #:mdw.base)
- (:export #:safely #:safely-close #:safely-delete-file
- #:safely-open-output-stream #:safely-bail #:safely-commit
- #:safely-writing))
+ (:use #:common-lisp #:mdw.base))
(in-package #:safely)
#+(or cmu sbcl)
(declare (ignore as-file))
(unix-namestring pathname nil)))
-(defstruct (safely (:predicate safelyp))
+(export '(safely safelyp make-safely))
+(defstruct (safely (:predicate safelyp) (:constructor make-safely ()))
"Stores information about how to commit or undo safe writes."
(streams nil)
(trail nil))
+(export 'safely-close)
(defun safely-close (safe stream)
"Make sure that STREAM is closed when SAFE is finished."
(push stream (safely-streams safe)))
+(export 'safely-delete-file)
(defun safely-delete-file (safe file)
"Delete FILE when SAFE is committed."
(push `(:delete ,file) (safely-trail safe)))
(when ret
(return (values new ret)))))))
+(export 'safely-open-output-stream)
(defun safely-open-output-stream (safe file &rest open-args)
"Create an output stream which will be named FILE when SAFE is committed.
Other OPEN-ARGS are passed to open."
(safely-trail safe))
stream))
+#+clisp
+(progn
+ (ffi:def-call-out %rename (:name "rename")
+ (:language :stdc)
+ (:library "libc.so.6")
+ (:arguments (from ffi:c-string)
+ (to ffi:c-string))
+ (:return-type ffi:int)))
+
(declaim (inline rename))
(defun rename (old new)
+ #-clisp
(let ((target (make-pathname :directory '(:relative)
:defaults new)))
- #-clisp (rename-file old target)
- #+clisp (rename-file old target :if-exists :overwrite)))
+ (rename-file old target))
+
+ #+clisp
+ (let ((rc (%rename (namestring old) (namestring new))))
+ (when (= rc -1)
+ (error "Failed to rename ~S to ~S: ~A" old new (posix:strerror)))))
(defun delete-file-without-moaning (file)
"Delete the FILE, ignoring errors."
(setf (safely-streams safe) nil)
(setf (safely-trail safe) nil))
+(export 'safely-bail)
(defun safely-bail (safe)
"Abort the operations in SAFE, unwinding all the things that have been
done. Streams are closed, new files are removed."
(return copy))))))
(close output)))))
+(export 'safely-commit)
(defun safely-commit (safe)
"Commit SAFE. The files deleted by safely-delete-file are deleted; the
files created by safely-open-output-stream are renamed over the old
(safely-unwind cleanup)
(safely-reset safe))))
+;; The symbol `safely' is already exported.
(defmacro safely ((safe &key) &body body)
"Do stuff within the BODY safely. If BODY completes without errors, the
SAFE is committed; otherwise it's bailed."
(when ,safe
(safely-bail ,safe)))))
+(export 'safely-writing)
(defmacro safely-writing ((stream file &rest open-args) &body body)
"Simple macro for writing a single file safely. STREAM is opened onto a
temporary file, and if BODY completes, it is renamed to FILE."