aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/connection.lisp20
-rw-r--r--src/package.lisp1
-rw-r--r--src/property/file.lisp21
3 files changed, 34 insertions, 8 deletions
diff --git a/src/connection.lisp b/src/connection.lisp
index 75617cf..d7caadf 100644
--- a/src/connection.lisp
+++ b/src/connection.lisp
@@ -390,6 +390,26 @@ PATH may be any kind of file, including directories."
nconc (list "-e" (car path))
when (cdr path) collect "-a")))
+(defun remote-file-mode-and-size (path)
+ "Get the numeric mode and size in bytes of PATH, or NIL if it does not exist."
+ (flet ((sum (chars order)
+ (+ (if (char= (elt chars 0) #\r) (* order 4) 0)
+ (if (char= (elt chars 1) #\w) (* order 2) 0)
+ (eswitch ((elt chars 2) :test #'char=)
+ (#\S (if (= order #o100) #o4000 #o2000))
+ (#\s (if (= order #o100) #o4100 #o2010))
+ (#\T #o1000)
+ (#\t (+ order #o1000))
+ (#\x order)
+ (#\- 0)))))
+ (and (remote-exists-p path)
+ (let* ((ls (split-string (run "ls" "-ld" path)))
+ (lscar (car ls)))
+ (values (+ (sum (subseq lscar 1 4) #o100)
+ (sum (subseq lscar 4 7) #o10)
+ (sum (subseq lscar 7 10) 1))
+ (parse-integer (nth 4 ls)))))))
+
(defun readfile (path)
(connection-readfile
*connection*
diff --git a/src/package.lisp b/src/package.lisp
index 97312e8..211b653 100644
--- a/src/package.lisp
+++ b/src/package.lisp
@@ -109,6 +109,7 @@
#:runlines
#:test
#:remote-exists-p
+ #:remote-file-mode-and-size
#:delete-remote-trees
#:readfile
#:writefile
diff --git a/src/property/file.lisp b/src/property/file.lisp
index dd4ae89..9575499 100644
--- a/src/property/file.lisp
+++ b/src/property/file.lisp
@@ -35,14 +35,19 @@ CONTENT can be a list of lines or a single string."
(declare (indent 1))
(:desc (declare (ignore content mode mode-supplied-p))
#?"${path} has defined content")
- (:apply (with-change-if-changes-file-content-or-mode (path)
- (let ((args (list path
- (etypecase content
- (cons (unlines content))
- (string (format nil "~A~&" content))))))
- (when mode-supplied-p
- (nconcf args (list :mode mode)))
- (apply #'writefile args)))))
+ (:apply (let ((content (etypecase content
+ (cons (unlines content))
+ (string (format nil "~A~&" content)))))
+ (if (and (remote-exists-p path)
+ (multiple-value-bind (existing-mode existing-size)
+ (remote-file-mode-and-size path)
+ (and (or (not mode-supplied-p) (= existing-mode mode))
+ ;; Avoid downloading arbitrarily large files.
+ (>= (* 4 (length content)) existing-size)
+ (string= (readfile path) content))))
+ :no-change
+ (apply #'writefile
+ path content (and mode-supplied-p `(:mode ,mode)))))))
(defprop contains-lines :posix (path &rest lines)
"Ensure there is a file at PATH containing each of LINES once."