Newer
Older
(defun purge-backup-files (pathname &optional (keep 0))
"Delete old versions of files matching the given Pathname,
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
optionally keeping some of the most recent old versions."
(declare (type (or pathname string stream) pathname)
(type (integer 0 *) keep))
(let ((hash (make-hash-table :test 'equal)))
(enumerate-search-list
(path (make-pathname :version :wild :defaults pathname))
(clrhash hash)
(enumerate-matches (name path nil :follow-links nil)
(let ((dot (position #\. name :from-end t))
(len (length name)))
(when (and dot
(> len (+ dot 3))
(char= (char name (1+ dot)) #\~)
(char= (char name (1- len)) #\~)
(eq (unix:unix-file-kind name) :file))
(multiple-value-bind (version next)
(parse-integer name :start (+ dot 2) :end (1- len)
:junk-allowed t)
(when (and version (= next (1- len)))
(push (cons version name)
(gethash (subseq name 0 dot) hash '())))))))
(maphash (lambda (key value)
(declare (ignore key))
(mapc #'unix:unix-unlink
(mapcar #'cdr (nthcdr keep
(sort value #'> :key #'car)))))
hash))))
;;; Return Home:, which is set up for us at initialization time.
"Returns the home directory of the logged in user as a pathname.
This is obtained from the logical name \"home:\"."
;;; File-Write-Date -- Public
;;;
(defun file-write-date (file)
"Return file's creation date, or NIL if it doesn't exist.
An error of type file-error is signalled if file is a wild pathname"
(if (wild-pathname-p file)
(error 'simple-file-error
:pathname file
:format-control (intl:gettext "Bad place for a wild pathname."))
(when name
(multiple-value-bind
(res dev ino mode nlink uid gid rdev size atime mtime)
(unix:unix-stat name)
(declare (ignore dev ino mode nlink uid gid rdev size atime))
(when res
(+ unix-to-universal-time mtime)))))))
;;; File-Author -- Public
;;;
(defun file-author (file)
"Returns the file author as a string, or nil if the author cannot be
determined. Signals an error of type file-error if file doesn't exist,
or file is a wild pathname."
(if (wild-pathname-p file)
(error 'simple-file-error
:pathname file
:format-control (intl:gettext "Bad place for a wild pathname."))
(unless name
(error 'simple-file-error
:pathname file
:format-control (intl:gettext "~S doesn't exist.")
:format-arguments (list file)))
(multiple-value-bind (winp dev ino mode nlink uid)
(unix:unix-stat name)
(declare (ignore dev ino mode nlink))
(when winp
(let ((user-info (unix:unix-getpwuid uid)))
(when user-info
(unix:user-info-name user-info))))))))
;;; DIRECTORY -- public.
(defun directory (pathname &key (all t) (check-for-subdirs t)
(truenamep t) (follow-links t))
"Returns a list of pathnames, one for each file that matches the given
pathname. Supplying :ALL as nil causes this to ignore Unix dot files. This
never includes Unix dot and dot-dot in the result. If :TRUENAMEP is NIL,
then symbolic links in the result are not expanded, which is not the
default because TRUENAME does follow links and the result pathnames are
defined to be the TRUENAME of the pathname (the truename of a link may well
be in another directory). If FOLLOW-LINKS is NIL then symbolic links are
not followed."
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
(flet ((ordered-strings-remove-duplicates (list)
(let ((results '())
(prev nil))
(dolist (elem list)
(when (or (null prev)
(not (string= elem prev)))
(push elem results))
(setf prev elem))
(nreverse results))))
(let ((results nil))
(enumerate-search-list
(pathname (merge-pathnames pathname
(make-pathname :name :wild
:type :wild
:version :wild
:defaults *default-pathname-defaults*)
:wild))
(enumerate-matches (name pathname nil :follow-links follow-links)
(when (or all
(let ((slash (position #\/ name :from-end t)))
(or (null slash)
(= (1+ slash) (length name))
(char/= (schar name (1+ slash)) #\.))))
(push name results))))
(let ((*ignore-wildcards* t))
(mapcar #'(lambda (name)
(let ((name (if (and check-for-subdirs
(eq (unix:unix-file-kind name)
:directory))
(concatenate 'string name "/")
name)))
(if truenamep (truename name) (pathname name))))
(ordered-strings-remove-duplicates (sort results #'string<)))))))
;;;; Printing directories.
;;; PRINT-DIRECTORY is exported from the EXTENSIONS package.
;;;
(defun print-directory (pathname &optional stream &key all verbose return-list)
"Like Directory, but prints a terse, multi-column directory listing
instead of returning a list of pathnames. When :all is supplied and
non-nil, then Unix dot files are included too (as ls -a). When :verbose
is supplied and non-nil, then a long listing of miscellaneous
information is output one file per line."
(let ((*standard-output* (out-synonym-of stream))
(pathname pathname))
(if verbose
(print-directory-verbose pathname all return-list)
(print-directory-formatted pathname all return-list))))
(defun print-directory-verbose (pathname all return-list)
(let ((contents (directory pathname :all all :check-for-subdirs nil
:truenamep nil))
(format t (intl:gettext "Directory of ~A:~%") (namestring pathname))
(dolist (file contents)
(let* ((namestring (unix-namestring file))
(tail (subseq namestring
(1+ (or (position #\/ namestring
:from-end t
:test #'char=)
-1)))))
(multiple-value-bind
(reslt dev-or-err ino mode nlink uid gid rdev size atime mtime)
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
(declare (ignore ino gid rdev atime)
(fixnum uid mode))
(cond (reslt
;;
;; Print characters for file modes.
(macrolet ((frob (bit name &optional sbit sname negate)
`(if ,(if negate
`(not (logbitp ,bit mode))
`(logbitp ,bit mode))
,(if sbit
`(if (logbitp ,sbit mode)
(write-char ,sname)
(write-char ,name))
`(write-char ,name))
(write-char #\-))))
(frob 15 #\d nil nil t)
(frob 8 #\r)
(frob 7 #\w)
(frob 6 #\x 11 #\s)
(frob 5 #\r)
(frob 4 #\w)
(frob 3 #\x 10 #\s)
(frob 2 #\r)
(frob 1 #\w)
(frob 0 #\x))
;;
;; Print the rest.
(multiple-value-bind (sec min hour date month year)
(get-decoded-time)
(declare (ignore sec min hour date month))
(format t "~2D ~8A ~8D ~12A ~A~@[/~]~%"
nlink
(let ((user-info (unix:unix-getpwuid uid)))
(if user-info (unix:user-info-name user-info) uid))
size
(decode-universal-time-for-files mtime year)
tail
(= (logand mode unix:s-ifmt) unix:s-ifdir))))
(t (format t (intl:gettext "Couldn't stat ~A -- ~A.~%")
(push (if (= (logand mode unix:s-ifmt) unix:s-ifdir)
(pathname (concatenate 'string namestring "/"))
file)
result)))))
(nreverse result)))
(defun decode-universal-time-for-files (time current-year)
(multiple-value-bind (sec min hour day month year)
(decode-universal-time (+ time unix-to-universal-time))
(declare (ignore sec))
(format nil "~A ~2,' D ~:[ ~D~;~*~2,'0D:~2,'0D~]"
(svref '#("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug"
"Sep" "Oct" "Nov" "Dec")
(1- month))
day (= current-year year) year hour min)))
(defun print-directory-formatted (pathname all return-list)
(let ((width (or (line-length *standard-output*) 80))
(names ())
(cnt 0)
(max-len 0)
(result (directory pathname :all all :truenamep nil)))
(declare (list names) (fixnum max-len cnt))
;;
;; Get the data.
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
(dolist (file result)
(let* ((name (unix-namestring file))
(length (length name))
(end (if (and (plusp length)
(char= (schar name (1- length)) #\/))
(1- length)
length))
(slash-name (subseq name
(1+ (or (position #\/ name
:from-end t
:end end
:test #'char=)
-1))))
(len (length slash-name)))
(declare (simple-string slash-name)
(fixnum len))
(if (> len max-len) (setq max-len len))
(incf cnt)
(push slash-name names)))
(setq names (nreverse names))
;;
;; Do the output.
(let* ((col-width (1+ max-len))
(cols (max (truncate width col-width) 1))
(lines (ceiling cnt cols)))
(declare (fixnum cols lines))
(format t (intl:gettext "Directory of ~A:~%") (namestring pathname))
(dotimes (i lines)
(declare (fixnum i))
(dotimes (j cols)
(declare (fixnum j))
(let ((name (nth (+ i (the fixnum (* j lines))) names)))
(when name
(write-string name)
(unless (eql j (1- cols))
(dotimes (i (- col-width (length (the simple-string name))))
(write-char #\space))))))
(terpri)))
(when return-list
result)))
;;;; File completion.
(defun complete-file (pathname &key (defaults *default-pathname-defaults*)
ignore-types)
(let ((files (directory (complete-file-directory-arg pathname defaults)
:check-for-subdirs nil
:truenamep nil)))
(cond ((null files)
(values nil nil))
((null (cdr files))
(values (merge-pathnames (file-namestring (car files))
pathname)
t))
(t
(let ((good-files
(delete-if #'(lambda (pathname)
(and (simple-string-p
(pathname-type pathname))
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
(member (pathname-type pathname)
ignore-types
:test #'string=)))
files)))
(cond ((null good-files))
((null (cdr good-files))
(return-from complete-file
(values (merge-pathnames (file-namestring
(car good-files))
pathname)
t)))
(t
(setf files good-files)))
(let ((common (file-namestring (car files))))
(dolist (file (cdr files))
(let ((name (file-namestring file)))
(dotimes (i (min (length common) (length name))
(when (< (length name) (length common))
(setf common name)))
(unless (char= (schar common i) (schar name i))
(setf common (subseq common 0 i))
(return)))))
(values (merge-pathnames common pathname)
nil)))))))
;;; COMPLETE-FILE-DIRECTORY-ARG -- Internal.
;;;
(defun complete-file-directory-arg (pathname defaults)
(let* ((pathname (merge-pathnames pathname (directory-namestring defaults)))
(type (pathname-type pathname)))
(flet ((append-multi-char-wild (thing)
(etypecase thing
(null :wild)
(pattern
(make-pattern (append (pattern-pieces thing)
(list :multi-char-wild))))
(simple-string
(make-pattern (list thing :multi-char-wild))))))
(if (or (null type) (eq type :unspecific))
;; There is no type.
(make-pathname :defaults pathname
:name (append-multi-char-wild (pathname-name pathname))
:type :wild)
;; There already is a type, so just extend it.
(make-pathname :defaults pathname
:name (pathname-name pathname)
:type (append-multi-char-wild (pathname-type pathname)))))))
;;; Ambiguous-Files -- Public
(defun ambiguous-files (pathname
&optional (defaults *default-pathname-defaults*))
"Return a list of all files which are possible completions of Pathname.
We look in the directory specified by Defaults as well as looking down
the search list."
(directory (complete-file-directory-arg pathname defaults)
:truenamep nil
:check-for-subdirs nil))
;;; File-writable -- exported from extensions.
;;;
;;; Determines whether the single argument (which should be a pathname)
"File-writable accepts a pathname and returns T if the current
(let ((name (unix-namestring name nil)))
(cond ((null name)
nil)
((unix:unix-file-kind name)
(values (unix:unix-access name unix:w_ok)))
(t
(values
0
(or (position #\/ name :from-end t)
0))
;;; Pathname-Order -- Internal
;;;
;;; Predicate to order pathnames by. Goes by name.
;;;
(defun pathname-order (x y)
(let ((xn (%pathname-name x))
(yn (%pathname-name y)))
(if (and xn yn)
(let ((res (string-lessp xn yn)))
(cond ((not res) nil)
((= res (length (the simple-string xn))) t)
((= res (length (the simple-string yn))) nil)
(t t)))
xn)))
;;; Default-Directory -- Public
;;;
(defun default-directory ()
"Returns the pathname for the default directory. This is the place where
a file will be written if no directory is specified. This may be changed
with setf."
(multiple-value-bind (gr dir-or-error)
(if gr
(let ((*ignore-wildcards* t))
(values
(parse-namestring (concatenate 'simple-string dir-or-error "/")
*unix-host*)))
(error dir-or-error))))
;;; %Set-Default-Directory -- Internal
;;;
(defun %set-default-directory (new-val)
(let ((namestring (unix-namestring new-val t)))
(unless namestring
:format-control (intl:gettext "~S doesn't exist.")
(multiple-value-bind (gr error)
(if gr
(setf (search-list "default:") (default-directory))
;;;
(defsetf default-directory %set-default-directory)
(defun filesys-init ()
;; Use :unspecific so we don't create file versions whenever merging
;; happens. If the user wants that, let him change
;; *default-pathname-defaults* appropriately.
(setf *default-pathname-defaults*
(%make-pathname *unix-host* nil nil nil nil :unspecific))
(setf (search-list "default:") (default-directory))
nil)
;;; Ensure-Directories-Exist -- Public
;;;
(defun ensure-directories-exist (pathspec &key verbose (mode #o777))
"Tests whether the directories containing the specified file
actually exist, and attempts to create them if they do not.
Portable programs should avoid using the :MODE keyword argument."
(pathname (if (logical-pathname-p pathname)
(translate-logical-pathname pathname)
pathname))
(created-p nil))
(when (wild-pathname-p pathname)
(error 'simple-file-error
:format-control (intl:gettext "Bad place for a wild pathname.")
:pathname pathspec))
(enumerate-search-list (pathname pathname)
(let ((dir (pathname-directory pathname)))
(loop for i from 1 upto (length dir)
do (let ((newpath (make-pathname
:host (pathname-host pathname)
:device (pathname-device pathname)
:directory (subseq dir 0 i))))
(tagbody
retry
(restart-case
(unless (probe-file newpath)
(let ((namestring (namestring newpath)))
(when verbose
(format *standard-output* (intl:gettext "~&Creating directory: ~A~%")
namestring))
(unix:unix-mkdir namestring mode)
(unless (probe-file namestring)
(error 'simple-file-error
:pathname pathspec
:format-control (intl:gettext "Can't create directory ~A.")
:format-arguments (list namestring)))
(setf created-p t)))
(retry () :report "Try to create the directory again"
(go retry))))))
;; Only the first path in a search-list is considered.
(return (values pathname created-p))))))