#lang scheme/base (provide delete-directory/files copy-directory/files make-directory* make-temporary-file get-preference put-preferences fold-files find-files pathlist-closure) ;; utility: sorted dirlist so functions are deterministic (define (sorted-dirlist [dir (current-directory)]) (let* ([ps (directory-list dir)] [ps (map (lambda (p) (cons (path->string p) p)) ps)] [ps (sort ps (lambda (p1 p2) (stringkey p) (string->symbol (bytes->string/latin-1 (path->bytes p)))) (define (pref-cache-install! fn-key fn-date f) (let ([table (or (weak-box-value pref-cache) (make-hash-table))]) (hash-table-put! table (path->key fn-key) (cons (file-or-directory-modify-seconds fn-date #f (lambda () -inf.0)) f)) (unless (eq? table (weak-box-value pref-cache)) (set! pref-cache (make-weak-box table))))) (define (get-prefs flush-mode filename) (define (read-prefs default-pref-file) (with-handlers ([exn:fail:filesystem? (lambda (x) null)]) (let* ([pref-file (or filename (let ([f default-pref-file]) (if (file-exists? f) ;; Using `file-exists?' means there's technically a ;; race condition, but something has gone really wrong ;; if the file disappears. f ;; Error here bails out through above `with-handlers' (build-path (collection-path "defaults") "plt-prefs.ss"))))] [prefs (with-pref-params (lambda () (with-input-from-file pref-file read)))]) ;; Make sure file content had the right shape: (if (and (list? prefs) (andmap (lambda (x) (and (pair? x) (pair? (cdr x)) (null? (cddr x)))) prefs)) prefs null)))) (let* ([fn (path->complete-path (or filename (find-system-path 'pref-file)))] [cache (let ([table (weak-box-value pref-cache)]) (and table (hash-table-get table (path->key fn) #f)))]) (if (and cache (or (not flush-mode) (and (eq? flush-mode 'timestamp) (= (car cache) (file-or-directory-modify-seconds fn #f (lambda () -inf.0)))))) (cdr cache) (let ([ts (file-or-directory-modify-seconds fn #f (lambda () -inf.0))] [f (read-prefs fn)]) (pref-cache-install! fn fn f) f)))) (define (get-preference name [fail-thunk (lambda () #f)] [refresh-cache? 'timestamp] [filename #f]) (unless (symbol? name) (raise-type-error 'get-preference "symbol" name)) (unless (and (procedure? fail-thunk) (procedure-arity-includes? fail-thunk 0)) (raise-type-error 'get-preference "procedure (arity 0)" fail-thunk)) (let ([f (get-prefs refresh-cache? filename)]) (let ([m (assq name f)]) (if m (cadr m) (fail-thunk))))) (define (put-preferences names vals [lock-there #f] [filename #f]) (unless (and (list? names) (andmap symbol? names)) (raise-type-error 'put-preferences "list of symbols" names)) (unless (list? vals) (raise-type-error 'put-preferences "list" vals)) (unless (= (length names) (length vals)) (raise-mismatch-error 'put-preferences (format "the size of the name list (~a) does not match the size of the value list (~a): " (length names) (length vals)) vals)) (let-values ([(pref-file lock-file pref-dir) (let ([filename (or filename (find-system-path 'pref-file))]) (let-values ([(base name dir?) (split-path filename)]) (let ([dir (if (symbol? base) (current-directory) base)]) (unless (directory-exists? dir) (make-directory* dir)) (values filename (build-path dir (bytes->path-element (bytes-append (if (eq? 'windows (system-type)) #"_" #".") #"LOCK" (path-element->bytes name)))) dir))))]) (with-handlers ([exn:fail:filesystem:exists? (lambda (x) (if lock-there (lock-there lock-file) (error 'put-preferences "some other process has the preference-file lock, as indicated by the existence of the lock file: ~e" lock-file)))]) ;; Grab lock: (close-output-port (open-output-file lock-file #:exists 'error)) (dynamic-wind void (lambda () (let ([f (get-prefs #t filename)]) (set! f (let loop ([f f][a null]) (cond [(null? f) (reverse (append (map list names vals) a))] [else (if (memq (caar f) names) (loop (cdr f) a) (loop (cdr f) (cons (car f) a)))]))) ;; To write the file, copy the old one to a temporary name ;; (preserves permissions, etc), write to the temp file, ;; then move (atomicly) the temp file to the normal name. (let ([tmp-file (make-temporary-file "TMPPREF~a" (and (file-exists? pref-file) pref-file) pref-dir)]) ;; If something goes wrong, try to delete the temp file. (with-handlers ([exn:fail? (lambda (exn) (with-handlers ([exn:fail:filesystem? void]) (delete-file tmp-file)) (raise exn))]) ;; Write to temp file... (with-output-to-file tmp-file #:exists 'truncate/replace (lambda () (with-pref-params (lambda () ;; If a pref value turns out to be unreadable, raise ;; an exception instead of creating a bad pref file. (parameterize ([print-unreadable #f]) ;; Poor man's pretty-print: one line per entry. (printf "(\n") (for-each (lambda (a) (if (and (list? (cadr a)) (< 4 (length (cadr a)))) (begin (printf " (~s\n (\n" (car a)) (for-each (lambda (i) (printf " ~s\n" i)) (cadr a)) (printf " ))\n")) (printf " ~s\n" a))) f) (printf ")\n")))))) ;; Install the new table in the cache. It's possible that this ;; cache entry will be replaced by a reading thread before we ;; move the file, but that's ok. It just means that a future ;; reading thread will have to read again. (pref-cache-install! (path->complete-path (or filename (find-system-path 'pref-file))) tmp-file f) (rename-file-or-directory tmp-file pref-file #t))))) (lambda () ;; Release lock: (delete-file lock-file)))))) ;; fold-files : (pathname sym alpha -> alpha) alpha pathname/#f -> alpha (define (fold-files f init [path #f] [follow-links? #t]) (define (do-path path acc) (cond [(and (not follow-links?) (link-exists? path)) (f path 'link acc)] [(directory-exists? path) (call-with-values (lambda () (f path 'dir acc)) (letrec ([descend (case-lambda [(acc) (do-paths (map (lambda (p) (build-path path p)) (sorted-dirlist path)) acc)] [(acc descend?) (if descend? (descend acc) acc)])]) descend))] [(file-exists? path) (f path 'file acc)] [(link-exists? path) (f path 'link acc)] ; dangling links [else (error 'fold-files "path disappeared: ~e" path)])) (define (do-paths paths acc) (cond [(null? paths) acc] [else (do-paths (cdr paths) (do-path (car paths) acc))])) (if path (do-path path init) (do-paths (sorted-dirlist) init))) (define (find-files f [path #f]) (reverse (fold-files (lambda (path kind acc) (if (f path) (cons path acc) acc)) null path))) (define (pathlist-closure paths) (let loop ([paths (map (lambda (p) (let ([p2 (if (link-exists? p) (let ([p2 (resolve-path p)]) (if (relative-path? p2) (let-values ([(base name dir?) (split-path p)]) (build-path base p2)) p2)) p)]) (simplify-path p2 #f))) paths)] [r '()]) (if (null? paths) (reverse r) (let loop2 ([path (car paths)] [new (cond [(file-exists? (car paths)) (list (car paths))] [(directory-exists? (car paths)) (find-files void (car paths))] [else (error 'pathlist-closure "file/directory not found: ~a" (car paths))])]) (let-values ([(base name dir?) (split-path path)]) (if (path? base) (loop2 base (if (or (member base r) (member base paths)) new (cons base new))) (loop (cdr paths) (append (reverse new) r))))))))