|
1 | 1 | #lang scheme/unit |
2 | | - (require "sig.rkt") |
| 2 | + (require "sig.rkt" |
| 3 | + racket/list |
| 4 | + "../preferences.rkt") |
3 | 5 |
|
4 | 6 | (import) |
5 | 7 | (export framework:path-utils^) |
6 | | - |
7 | | - (define (generate-autosave-name name) |
8 | | - (let-values ([(base name dir?) |
9 | | - (if name |
10 | | - (split-path name) |
11 | | - (values (find-system-path 'doc-dir) |
12 | | - (bytes->path-element #"mredauto") |
13 | | - #f))]) |
14 | | - (let* ([base (if (path? base) |
15 | | - base |
16 | | - (current-directory))] |
17 | | - [path (if (relative-path? base) |
18 | | - (build-path (current-directory) base) |
19 | | - base)]) |
| 8 | + |
| 9 | +;; preferences initialized in main.rkt |
| 10 | + |
| 11 | +(define (make-getter/ensure-exists pref-sym) |
| 12 | + (λ () |
| 13 | + (let ([maybe-dir (preferences:get pref-sym)]) |
| 14 | + (and maybe-dir |
| 15 | + (directory-exists? maybe-dir) |
| 16 | + (memq 'write (file-or-directory-permissions maybe-dir)) |
| 17 | + maybe-dir)))) |
| 18 | + |
| 19 | +(define current-backup-dir |
| 20 | + (make-getter/ensure-exists 'path-utils:backup-dir)) |
| 21 | + |
| 22 | +(define current-autosave-dir |
| 23 | + (make-getter/ensure-exists 'path-utils:autosave-dir)) |
| 24 | + |
| 25 | + ; generate-autosave-name : (or/c #f path-string? path-for-some-system?) -> path? |
| 26 | + (define (generate-autosave-name maybe-old-path) |
| 27 | + (cond |
| 28 | + [maybe-old-path |
| 29 | + (let*-values ([(base name dir?) (split-path maybe-old-path)] |
| 30 | + [(base) (cond |
| 31 | + [(not (path? base)) |
| 32 | + (current-directory)] |
| 33 | + [(relative-path? base) |
| 34 | + (build-path (current-directory) base)] |
| 35 | + [else |
| 36 | + base])]) |
| 37 | + (cond |
| 38 | + [(current-autosave-dir) |
| 39 | + => |
| 40 | + (λ (dir) |
| 41 | + (make-unique-autosave-name dir (encode-as-path-element base name)))] |
| 42 | + [else |
| 43 | + (make-unique-autosave-name base name)]))] |
| 44 | + [else |
| 45 | + (make-unique-autosave-name (or (current-autosave-dir) |
| 46 | + (find-system-path 'doc-dir)) |
| 47 | + (bytes->path-element #"mredauto"))])) |
| 48 | + |
| 49 | + |
| 50 | + ; make-unique-autosave-name : dir-path path-element -> path? |
| 51 | + (define (make-unique-autosave-name dir name) |
20 | 52 | (let loop ([n 1]) |
21 | 53 | (let* ([numb (string->bytes/utf-8 (number->string n))] |
22 | 54 | [new-name |
23 | | - (build-path path |
| 55 | + (build-path dir |
24 | 56 | (if (eq? (system-type) 'windows) |
25 | 57 | (bytes->path-element |
26 | 58 | (bytes-append (regexp-replace #rx#"\\..*$" |
|
36 | 68 | #"#"))))]) |
37 | 69 | (if (file-exists? new-name) |
38 | 70 | (loop (add1 n)) |
39 | | - new-name)))))) |
40 | | - |
| 71 | + new-name)))) |
| 72 | + |
| 73 | + ;; generate-backup-name : path? -> path? |
41 | 74 | (define (generate-backup-name full-name) |
42 | 75 | (let-values ([(pre-base name dir?) (split-path full-name)]) |
43 | 76 | (let ([base (if (path? pre-base) |
44 | 77 | pre-base |
45 | 78 | (current-directory))]) |
46 | | - (let ([name-bytes (path-element->bytes name)]) |
47 | | - (cond |
48 | | - [(and (eq? (system-type) 'windows) |
49 | | - (regexp-match #rx#"(.*)\\.[^.]*" name-bytes)) |
50 | | - => |
51 | | - (λ (m) |
52 | | - (build-path base (bytes->path-element (bytes-append (cadr m) #".bak"))))] |
53 | | - [(eq? (system-type) 'windows) |
54 | | - (build-path base (bytes->path-element (bytes-append name-bytes #".bak")))] |
55 | | - [else |
56 | | - (build-path base (bytes->path-element (bytes-append name-bytes #"~")))]))))) |
| 79 | + (define name-element |
| 80 | + (let ([name-bytes (path-element->bytes name)]) |
| 81 | + (bytes->path-element |
| 82 | + (cond |
| 83 | + [(and (eq? (system-type) 'windows) |
| 84 | + (regexp-match #rx#"(.*)\\.[^.]*" name-bytes)) |
| 85 | + => |
| 86 | + (λ (m) |
| 87 | + (bytes-append (cadr m) #".bak"))] |
| 88 | + [(eq? (system-type) 'windows) |
| 89 | + (bytes-append name-bytes #".bak")] |
| 90 | + [else |
| 91 | + (bytes-append name-bytes #"~")])))) |
| 92 | + (cond |
| 93 | + [(current-backup-dir) |
| 94 | + => |
| 95 | + (λ (dir) |
| 96 | + (build-path dir (encode-as-path-element base name-element)))] |
| 97 | + [else |
| 98 | + (build-path base name-element)])))) |
| 99 | + |
| 100 | + |
| 101 | + |
| 102 | +(define candidate-separators |
| 103 | + `(#"!" #"%" #"_" #"|" #":" #">" #"^" #"$" #"@" #"*" #"?")) |
| 104 | + |
| 105 | +(define separator-regexps |
| 106 | + (map (compose1 byte-regexp regexp-quote) candidate-separators)) |
| 107 | + |
| 108 | +; encode-as-path-element : dir-path path-element -> path-element |
| 109 | +; N.B. generate-backup-name may supply a relative directory, but |
| 110 | +; we should always use a complete one. |
| 111 | +; That is handled by simplify+explode-path->bytes. |
| 112 | +; Windows has limitations on path lengths. Racket handles MAX_PATH |
| 113 | +; by using "\\?\" paths when necessary, but individual elements must |
| 114 | +; be shorter than lpMaximumComponentLength. |
| 115 | +; We respect this limit (on all platforms, for consistency) |
| 116 | +; by replacing some bytes from the middle if necessary. |
| 117 | +(define (encode-as-path-element base-maybe-relative name) |
| 118 | + (define illegal-rx |
| 119 | + (case (system-path-convention-type) |
| 120 | + [(windows) #rx#"\\\\"] |
| 121 | + [else #rx#"/"])) |
| 122 | + (define l-bytes |
| 123 | + (simplify+explode-path->bytes (build-path base-maybe-relative name))) |
| 124 | + (define separator-byte |
| 125 | + (or (let ([all-components (apply bytes-append l-bytes)]) |
| 126 | + (for/first ([sep (in-list candidate-separators)] |
| 127 | + [rx (in-list separator-regexps)] |
| 128 | + #:unless (regexp-match? rx all-components)) |
| 129 | + sep)) |
| 130 | + #"!")) |
| 131 | + (define legible-name-bytes |
| 132 | + (apply |
| 133 | + bytes-append |
| 134 | + separator-byte |
| 135 | + (add-between |
| 136 | + (for/list ([elem (in-list l-bytes)]) |
| 137 | + (regexp-replace* illegal-rx elem separator-byte)) |
| 138 | + separator-byte))) |
| 139 | + (define num-legible-bytes |
| 140 | + (bytes-length legible-name-bytes)) |
| 141 | + (bytes->path-element |
| 142 | + (cond |
| 143 | + [(< num-legible-bytes |
| 144 | + (lpMaximumComponentLength)) |
| 145 | + legible-name-bytes] |
| 146 | + [else |
| 147 | + (define replacement |
| 148 | + (bytes-append separator-byte #"..." separator-byte)) |
| 149 | + (define num-excess-bytes |
| 150 | + (+ (- num-legible-bytes |
| 151 | + (lpMaximumComponentLength)) |
| 152 | + 5 ; extra margin of safety |
| 153 | + (bytes-length replacement))) |
| 154 | + (define num-bytes-to-keep-per-side |
| 155 | + (floor (/ (- num-legible-bytes num-excess-bytes) |
| 156 | + 2))) |
| 157 | + (bytes-append |
| 158 | + (subbytes legible-name-bytes 0 num-bytes-to-keep-per-side) |
| 159 | + replacement |
| 160 | + (subbytes legible-name-bytes (- num-legible-bytes |
| 161 | + num-bytes-to-keep-per-side)))]))) |
| 162 | + |
| 163 | + |
| 164 | +;; simplify+explode-path->bytes : path? -> (listof bytes?) |
| 165 | +;; Useful because path-element->bytes doesn't work on root paths. |
| 166 | +;; Using simplify-path ensures no 'up or 'same. |
| 167 | +(define (simplify+explode-path->bytes pth) |
| 168 | + (define elems |
| 169 | + (explode-path (simplify-path pth))) |
| 170 | + (cons (path->bytes (car elems)) |
| 171 | + (map path-element->bytes (cdr elems)))) |
| 172 | + |
| 173 | +;; lpMaximumComponentLength : -> real? |
| 174 | +;; Returns the maximum length of an element of a "\\?\" path on Windows. |
| 175 | +;; For now, assuming 255, but really this should be |
| 176 | +;; "the value returned in the lpMaximumComponentLength parameter |
| 177 | +;; of the GetVolumeInformation function". |
| 178 | +;; See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath |
| 179 | +(define (lpMaximumComponentLength) |
| 180 | + 255) |
| 181 | + |
57 | 182 |
|
0 commit comments