|
4 | 4 | racket/unit |
5 | 5 | racket/file |
6 | 6 | racket/match |
| 7 | + racket/list |
7 | 8 | "sig.rkt" |
8 | 9 | "text-sig.rkt" |
9 | 10 | "../gui-utils.rkt" |
|
24 | 25 | [prefix group: framework:group^] |
25 | 26 | [prefix canvas: framework:canvas^]) |
26 | 27 |
|
27 | | - (export framework:autosave^) |
| 28 | + (export framework:autosave/int^) |
28 | 29 |
|
29 | 30 | (define autosavable<%> |
30 | 31 | (interface () |
31 | 32 | do-autosave)) |
32 | 33 |
|
33 | | - (define objects null) |
34 | | - |
35 | 34 | (define current-toc-path |
36 | 35 | (make-parameter |
37 | 36 | (build-path (find-system-path 'pref-dir) |
|
40 | 39 | [else "PLT-autosave-toc.rktd"])))) |
41 | 40 | (define toc-path (current-toc-path)) |
42 | 41 |
|
43 | | - (define (get-autosave-toc-save-filename) |
44 | | - (define toc-path (current-toc-path)) |
45 | | - (define-values (base name dir) (split-path toc-path)) |
46 | | - (define save-filename-path |
47 | | - (case (system-type) |
48 | | - [(unix) ".plt-autosave-toc-save.rktd"] |
49 | | - [else "PLT-autosave-toc-save.rktd"])) |
50 | | - (make-directory* base) |
51 | | - (build-path base save-filename-path)) |
52 | | - |
| 42 | + #| |
| 43 | +
|
| 44 | + The toc files contain `autosave-toc` entries |
| 45 | +
|
| 46 | + autosave-toc ::= (listof autosave-to-entry) |
| 47 | + autosave-toc-entry ::= |
| 48 | + (list/c (or/c #f bytes[filename]) -- filename of the edited file (#f if not yet saved) |
| 49 | + bytes[filename])) -- filename of the autosave file |
| 50 | +
|
| 51 | + |# |
| 52 | + |
| 53 | + (define (with-autosave-filesystem-lock thunk) |
| 54 | + (define autosave-toc-save-filename (current-toc-path)) |
| 55 | + (call-with-file-lock/timeout |
| 56 | + autosave-toc-save-filename |
| 57 | + 'exclusive |
| 58 | + thunk |
| 59 | + (λ () |
| 60 | + ((autosave-lockfile-failure) autosave-toc-save-filename)))) |
| 61 | + |
| 62 | + ;; -> autosave-toc |
| 63 | + ;; assumes in the dynamic-extent of the thunk passed to with-autosave-filesystem-lock |
| 64 | + (define (get-autosave-toc-content) |
| 65 | + (call-with-input-file (current-toc-path) |
| 66 | + (λ (port) |
| 67 | + (read port)))) |
| 68 | + |
| 69 | + ;; autosave-toc -> void |
| 70 | + ;; assumes in the dynamic-extent of the thunk passed to with-autosave-filesystem-lock |
| 71 | + (define (put-autosave-toc-content toc-content) |
| 72 | + (call-with-output-file (current-toc-path) |
| 73 | + (λ (port) |
| 74 | + (write toc-content port) |
| 75 | + (newline port)) |
| 76 | + #:exists 'truncate |
| 77 | + #:mode 'text)) |
| 78 | + |
| 79 | + (define-local-member-name add-object) |
53 | 80 | (define autosave-timer% |
54 | 81 | (class timer% |
55 | 82 | (inherit start) |
56 | | - (field [last-name-mapping #f]) |
| 83 | + (define objects '()) |
| 84 | + (define last-name-mapping '()) |
57 | 85 | (define/override (notify) |
58 | 86 | (when (preferences:get 'framework:autosaving-on?) |
59 | | - (let-values ([(new-objects new-name-mapping) (rebuild-object-list)]) |
60 | | - (define toc-path (current-toc-path)) |
61 | | - (define autosave-toc-save-filename (get-autosave-toc-save-filename)) |
62 | | - (set! objects new-objects) |
63 | | - (unless (equal? last-name-mapping new-name-mapping) |
64 | | - (set! last-name-mapping new-name-mapping) |
65 | | - (when (file-exists? autosave-toc-save-filename) |
66 | | - (delete-file autosave-toc-save-filename)) |
67 | | - (when (file-exists? toc-path) |
68 | | - (copy-file toc-path autosave-toc-save-filename)) |
69 | | - (call-with-output-file toc-path |
70 | | - (λ (port) |
71 | | - (write new-name-mapping port)) |
72 | | - #:exists 'truncate |
73 | | - #:mode 'text)))) |
| 87 | + (define-values (new-objects new-name-mapping) (rebuild-object-list)) |
| 88 | + (define toc-path (current-toc-path)) |
| 89 | + (set! objects new-objects) |
| 90 | + (unless (equal? last-name-mapping new-name-mapping) |
| 91 | + (define added (remove* last-name-mapping new-name-mapping)) |
| 92 | + (define removed (remove* new-name-mapping last-name-mapping)) |
| 93 | + (set! last-name-mapping new-name-mapping) |
| 94 | + (with-autosave-filesystem-lock |
| 95 | + (λ () |
| 96 | + (define old (get-autosave-toc-content)) |
| 97 | + (define new (remove-duplicates (append (remove* removed old) added))) |
| 98 | + (put-autosave-toc-content new))))) |
74 | 99 | (cond |
75 | | - [(null? objects) (set! timer #f)] |
| 100 | + [(null? objects) (hash-set! timers (current-eventspace) #f)] |
76 | 101 | [else |
77 | 102 | (let ([seconds (preferences:get 'framework:autosave-delay)]) |
78 | 103 | (start (* 1000 seconds) #t))])) |
| 104 | + |
| 105 | + ;; rebuild-object-list : -> (values (listof (weak-box (is-a?/c editor<%>))) |
| 106 | + ;; autosave-toc) |
| 107 | + (define/private (rebuild-object-list) |
| 108 | + (let loop ([orig-objects objects] |
| 109 | + [name-mapping null] |
| 110 | + [new-objects null]) |
| 111 | + (if (null? orig-objects) |
| 112 | + (values new-objects name-mapping) |
| 113 | + (let* ([object-wb (car orig-objects)] |
| 114 | + [object (weak-box-value object-wb)]) |
| 115 | + (if object |
| 116 | + (let* ([new-filename (send object do-autosave)] |
| 117 | + [tmp-box (box #f)] |
| 118 | + [filename (send object get-filename tmp-box)]) |
| 119 | + (loop (cdr orig-objects) |
| 120 | + (if new-filename |
| 121 | + (cons (list (and (not (unbox tmp-box)) |
| 122 | + filename |
| 123 | + (path->bytes filename)) |
| 124 | + (and new-filename |
| 125 | + (path->bytes new-filename))) |
| 126 | + name-mapping) |
| 127 | + name-mapping) |
| 128 | + (cons object-wb new-objects))) |
| 129 | + (loop (cdr orig-objects) |
| 130 | + name-mapping |
| 131 | + new-objects)))))) |
| 132 | + |
| 133 | + (define/public (add-object b) |
| 134 | + (set! objects |
| 135 | + (let loop ([objects objects]) |
| 136 | + (cond |
| 137 | + [(null? objects) (list (make-weak-box b))] |
| 138 | + [else (let ([weak-box (car objects)]) |
| 139 | + (if (weak-box-value weak-box) |
| 140 | + (cons weak-box (loop (cdr objects))) |
| 141 | + (loop (cdr objects))))])))) |
| 142 | + |
79 | 143 | (super-new) |
80 | 144 | (let ([seconds (preferences:get 'framework:autosave-delay)]) |
81 | 145 | (start (* 1000 seconds) #t)))) |
| 146 | + |
| 147 | + (define (autosave-lockfile-failure path) |
| 148 | + (message-box "" (format "Unable to claim lockfile ~a" path))) |
82 | 149 |
|
83 | | - ;; rebuild-object-list : -> (values (listof (weak-box (is-a?/c editor<%>))) |
84 | | - ;; (listof (list (union #f string[filename]) string[filename])) |
85 | | - (define (rebuild-object-list) |
86 | | - (let loop ([orig-objects objects] |
87 | | - [name-mapping null] |
88 | | - [new-objects null]) |
89 | | - (if (null? orig-objects) |
90 | | - (values new-objects name-mapping) |
91 | | - (let* ([object-wb (car orig-objects)] |
92 | | - [object (weak-box-value object-wb)]) |
93 | | - (if object |
94 | | - (let* ([new-filename (send object do-autosave)] |
95 | | - [tmp-box (box #f)] |
96 | | - [filename (send object get-filename tmp-box)]) |
97 | | - (loop (cdr orig-objects) |
98 | | - (if new-filename |
99 | | - (cons (list (and (not (unbox tmp-box)) |
100 | | - filename |
101 | | - (path->bytes filename)) |
102 | | - (and new-filename |
103 | | - (path->bytes new-filename))) |
104 | | - name-mapping) |
105 | | - name-mapping) |
106 | | - (cons object-wb new-objects))) |
107 | | - (loop (cdr orig-objects) |
108 | | - name-mapping |
109 | | - new-objects)))))) |
110 | | - |
111 | | - (define timer #f) |
| 150 | + (define timers (make-weak-hash)) |
112 | 151 | ;; when the autosave delay is changed then we |
113 | 152 | ;; trigger an autosave right away and let the |
114 | 153 | ;; callback trigger the next one at the right interval |
115 | 154 | (preferences:add-callback |
116 | 155 | 'framework:autosave-delay |
117 | 156 | (λ (k v) |
118 | | - (when timer |
119 | | - (send timer stop) |
120 | | - (send timer start 0 #t)))) |
121 | | - |
122 | | - |
| 157 | + (for ([(_ timer) (in-hash timers)]) |
| 158 | + (when timer |
| 159 | + (send timer stop) |
| 160 | + (send timer start 0 #t))))) |
123 | 161 |
|
124 | 162 | (define (register b) |
| 163 | + (define timer (hash-ref timers (current-eventspace) #f)) |
125 | 164 | (unless timer |
126 | | - (set! timer (make-object autosave-timer%))) |
127 | | - (set! objects |
128 | | - (let loop ([objects objects]) |
129 | | - (cond |
130 | | - [(null? objects) (list (make-weak-box b))] |
131 | | - [else (let ([weak-box (car objects)]) |
132 | | - (if (weak-box-value weak-box) |
133 | | - (cons weak-box (loop (cdr objects))) |
134 | | - (loop (cdr objects))))])))) |
| 165 | + (set! timer (make-object autosave-timer%)) |
| 166 | + (hash-set! timers (current-eventspace) timer)) |
| 167 | + (send timer add-object b)) |
135 | 168 |
|
136 | 169 | ;; restore-autosave-files/gui : -> void? |
137 | 170 | ;; opens a frame that lists the autosave files that have changed. |
|
0 commit comments