about summary refs log tree commit diff
diff options
context:
space:
mode:
authorArun Isaac2023-09-25 23:43:23 +0100
committerArun Isaac2023-09-25 23:49:38 +0100
commit4b0ce31c5f139d371af1b063f83cf980e06fabaf (patch)
tree91166071aecdf4ccc38b7edba4c12ef68ccde554
parentcc386c2765e0fd705fee8f26fcb13aa9907f84db (diff)
downloadrent-in-london-4b0ce31c5f139d371af1b063f83cf980e06fabaf.tar.gz
rent-in-london-4b0ce31c5f139d371af1b063f83cf980e06fabaf.tar.lz
rent-in-london-4b0ce31c5f139d371af1b063f83cf980e06fabaf.zip
Ensure atomic writes to cache.
* rent.scm (call-with-atomic-output-file): New function.
(http-get*): Use call-with-atomic-output-file instead of
call-with-output-file.
-rw-r--r--rent.scm20
1 files changed, 18 insertions, 2 deletions
diff --git a/rent.scm b/rent.scm
index 3fe8d94..7258c5e 100644
--- a/rent.scm
+++ b/rent.scm
@@ -1,5 +1,5 @@
 ;;; rent-in-london --- Shortlist houses to rent in London
-;;; Copyright © 2022 Arun Isaac <arunisaac@systemreboot.net>
+;;; Copyright © 2022, 2023 Arun Isaac <arunisaac@systemreboot.net>
 ;;;
 ;;; This program is free software: you can redistribute it and/or
 ;;; modify it under the terms of the GNU General Public License as
@@ -61,6 +61,22 @@
 (define %tfl-base-url
   "https://api.tfl.gov.uk")
 
+(define (call-with-atomic-output-file filename proc)
+  "Open @var{filename} for output and pass the open port to
+@var{proc}. Writes to @var{filename} are guaranteed to be atomic."
+  (let* ((temporary-port (mkstemp "rent-XXXXXX"))
+         (temporary-filename (port-filename temporary-port)))
+    (dynamic-wind
+      (const #t)
+      (lambda ()
+        (call-with-port temporary-port
+          (lambda _
+            (proc temporary-port)
+            (fsync temporary-port)))
+        (rename-file temporary-filename filename))
+      (lambda ()
+        (false-if-exception (delete-file temporary-filename))))))
+
 (define (json-ref scm . keys)
   (match keys
     ((key other-keys ...)
@@ -207,7 +223,7 @@
                    (> (current-time)
                       (+ (stat:mtime (stat cache-file))
                          cache-live-time))))
-      (call-with-output-file cache-file
+      (call-with-atomic-output-file cache-file
         (cut put-bytevector <> (http-get-follow uri))))
     ;; Return contents of cache.
     (call-with-input-file cache-file