329 lines
14 KiB
Racket
329 lines
14 KiB
Racket
#lang scheme/unit
|
|
|
|
(require racket/class
|
|
racket/file
|
|
"sig.rkt"
|
|
"../gui-utils.rkt"
|
|
"../preferences.rkt"
|
|
mred/mred-sig
|
|
string-constants)
|
|
|
|
(import mred^
|
|
[prefix exit: framework:exit^]
|
|
[prefix frame: framework:frame^]
|
|
[prefix racket: framework:racket^]
|
|
[prefix editor: framework:editor^]
|
|
[prefix text: framework:text^]
|
|
[prefix finder: framework:finder^]
|
|
[prefix group: framework:group^])
|
|
|
|
(export framework:autosave^)
|
|
|
|
(define autosavable<%>
|
|
(interface ()
|
|
do-autosave))
|
|
|
|
(define objects null)
|
|
|
|
(define toc-path
|
|
(build-path (find-system-path 'pref-dir)
|
|
(case (system-type)
|
|
[(unix) ".plt-autosave-toc.rktd"]
|
|
[else "PLT-autosave-toc.rktd"])))
|
|
|
|
(define autosave-toc-save-filename
|
|
(build-path (find-system-path 'pref-dir)
|
|
(case (system-type)
|
|
[(unix) ".plt-autosave-toc-save.rktd"]
|
|
[else "PLT-autosave-toc-save.rktd"])))
|
|
|
|
(define autosave-timer%
|
|
(class timer%
|
|
(inherit start)
|
|
(field [last-name-mapping #f])
|
|
(define/override (notify)
|
|
(when (preferences:get 'framework:autosaving-on?)
|
|
(let-values ([(new-objects new-name-mapping) (rebuild-object-list)])
|
|
(set! objects new-objects)
|
|
(unless (equal? last-name-mapping new-name-mapping)
|
|
(set! last-name-mapping new-name-mapping)
|
|
(when (file-exists? autosave-toc-save-filename)
|
|
(delete-file autosave-toc-save-filename))
|
|
(when (file-exists? toc-path)
|
|
(copy-file toc-path autosave-toc-save-filename))
|
|
(call-with-output-file toc-path
|
|
(λ (port)
|
|
(write new-name-mapping port))
|
|
#:exists 'truncate
|
|
#:mode 'text))))
|
|
(let ([seconds (preferences:get 'framework:autosave-delay)])
|
|
(start (* 1000 seconds) #t)))
|
|
(super-new)
|
|
(let ([seconds (preferences:get 'framework:autosave-delay)])
|
|
(start (* 1000 seconds) #t))))
|
|
|
|
;; rebuild-object-list : -> (values (listof (weak-box (is-a?/c editor<%>)))
|
|
;; (listof (list (union #f string[filename]) string[filename]))
|
|
(define (rebuild-object-list)
|
|
(let loop ([orig-objects objects]
|
|
[name-mapping null]
|
|
[new-objects null])
|
|
(if (null? orig-objects)
|
|
(values new-objects name-mapping)
|
|
(let* ([object-wb (car orig-objects)]
|
|
[object (weak-box-value object-wb)])
|
|
(if object
|
|
(let* ([new-filename (send object do-autosave)]
|
|
[tmp-box (box #f)]
|
|
[filename (send object get-filename tmp-box)])
|
|
(loop (cdr orig-objects)
|
|
(if new-filename
|
|
(cons (list (and (not (unbox tmp-box))
|
|
filename
|
|
(path->bytes filename))
|
|
(and new-filename
|
|
(path->bytes new-filename)))
|
|
name-mapping)
|
|
name-mapping)
|
|
(cons object-wb new-objects)))
|
|
(loop (cdr orig-objects)
|
|
name-mapping
|
|
new-objects))))))
|
|
|
|
(define timer #f)
|
|
;; when the autosave delay is changed then we
|
|
;; trigger an autosave right away and let the
|
|
;; callback trigger the next one at the right interval
|
|
(preferences:add-callback
|
|
'framework:autosave-delay
|
|
(λ (k v)
|
|
(when timer
|
|
(send timer stop)
|
|
(send timer start 0 #t))))
|
|
|
|
|
|
|
|
(define (register b)
|
|
(unless timer
|
|
(set! timer (make-object autosave-timer%)))
|
|
(set! objects
|
|
(let loop ([objects objects])
|
|
(cond
|
|
[(null? objects) (list (make-weak-box b))]
|
|
[else (let ([weak-box (car objects)])
|
|
(if (weak-box-value weak-box)
|
|
(cons weak-box (loop (cdr objects)))
|
|
(loop (cdr objects))))]))))
|
|
|
|
;; restore-autosave-files/gui : -> (union #f (is-a?/c top-level-window<%>))
|
|
;; opens a frame that lists the autosave files that have changed.
|
|
(define (restore-autosave-files/gui)
|
|
|
|
;; main : -> void
|
|
;; start everything going
|
|
(define (main)
|
|
(when (file-exists? toc-path)
|
|
;; Load table from file, and check that the file was not corrupted
|
|
(let* ([table (let ([v (with-handlers ([exn:fail? (λ (x) null)])
|
|
(call-with-input-file toc-path read))])
|
|
(if (and (list? v)
|
|
(andmap (λ (i)
|
|
(and (list? i)
|
|
(= 2 (length i))
|
|
(or (not (car i))
|
|
(bytes? (car i)))
|
|
(bytes? (cadr i))))
|
|
v))
|
|
(map (λ (ent) (list (if (bytes? (list-ref ent 0))
|
|
(bytes->path (list-ref ent 0))
|
|
#f)
|
|
(bytes->path (list-ref ent 1))))
|
|
v)
|
|
null))]
|
|
;; assume that the autosave file was deleted due to the file being saved
|
|
[filtered-table
|
|
(filter (λ (x) (file-exists? (cadr x))) table)])
|
|
(unless (null? filtered-table)
|
|
(let* ([dlg (new (frame:focus-table-mixin dialog%)
|
|
(label (string-constant recover-autosave-files-frame-title)))]
|
|
[t (new text% (auto-wrap #t))]
|
|
[ec (new editor-canvas%
|
|
(parent dlg)
|
|
(editor t)
|
|
(line-count 2)
|
|
(stretchable-height #f)
|
|
(style '(no-hscroll)))]
|
|
[hp (new horizontal-panel%
|
|
[parent dlg]
|
|
[stretchable-height #f])]
|
|
[vp (new vertical-panel%
|
|
[parent hp]
|
|
[stretchable-height #f])]
|
|
[details-parent (new horizontal-panel% [parent dlg])])
|
|
(send vp set-alignment 'right 'center)
|
|
(make-object grow-box-spacer-pane% hp)
|
|
(send t insert (string-constant autosave-explanation))
|
|
(send t hide-caret #t)
|
|
(send t set-position 0 0)
|
|
(send t lock #t)
|
|
|
|
(for-each (add-table-line vp dlg details-parent) filtered-table)
|
|
(make-object button%
|
|
(string-constant autosave-done)
|
|
vp
|
|
(λ (x y)
|
|
(when (send dlg can-close?)
|
|
(send dlg on-close)
|
|
(send dlg show #f))))
|
|
(send dlg show #t)
|
|
(void))))))
|
|
|
|
;; add-table-line : (is-a? area-container<%>)
|
|
;; (or/c #f (is-a?/c top-level-window<%>))
|
|
;; (is-a? area-container<%>)
|
|
;; -> (list/c (or/c #f path?) path?)
|
|
;; -> void?
|
|
;; adds in a line to the overview table showing this pair of files.
|
|
(define (add-table-line area-container dlg show-details-parent)
|
|
(λ (table-entry)
|
|
(letrec ([orig-file (car table-entry)]
|
|
[backup-file (cadr table-entry)]
|
|
[hp (new horizontal-panel%
|
|
(parent area-container)
|
|
(style '(border))
|
|
(stretchable-height #f))]
|
|
[vp (new vertical-panel%
|
|
(parent hp))]
|
|
[msg1-panel (new horizontal-panel%
|
|
(parent vp))]
|
|
[msg1-label (new message%
|
|
(parent msg1-panel)
|
|
(label (string-constant autosave-original-label:)))]
|
|
[msg1 (new message%
|
|
(label (if orig-file (path->string orig-file) (string-constant autosave-unknown-filename)))
|
|
(stretchable-width #t)
|
|
(parent msg1-panel))]
|
|
[msg2-panel (new horizontal-panel%
|
|
(parent vp))]
|
|
[msg2-label (new message%
|
|
(parent msg2-panel)
|
|
(label (string-constant autosave-autosave-label:)))]
|
|
[msg2 (new message%
|
|
(label (path->string backup-file))
|
|
(stretchable-width #t)
|
|
(parent msg2-panel))]
|
|
[details
|
|
(make-object button% (string-constant autosave-details) hp
|
|
(λ (x y)
|
|
(show-files table-entry show-details-parent dlg)))]
|
|
[delete
|
|
(make-object button%
|
|
(string-constant autosave-delete-button)
|
|
hp
|
|
(λ (delete y)
|
|
(when (delete-autosave table-entry)
|
|
(disable-line)
|
|
(send msg2 set-label (string-constant autosave-deleted)))))]
|
|
[recover
|
|
(make-object button%
|
|
(string-constant autosave-recover)
|
|
hp
|
|
(λ (recover y)
|
|
(let ([filename-result (recover-file dlg table-entry)])
|
|
(when filename-result
|
|
(disable-line)
|
|
(send msg2 set-label (string-constant autosave-recovered!))
|
|
(send msg1 set-label (gui-utils:quote-literal-label
|
|
(path->string filename-result)
|
|
#:quote-amp? #f))))))]
|
|
[disable-line
|
|
(λ ()
|
|
(send recover enable #f)
|
|
(send details enable #f)
|
|
(send delete enable #f))])
|
|
(let ([w (max (send msg1-label get-width) (send msg2-label get-width))])
|
|
(send msg1-label min-width w)
|
|
(send msg2-label min-width w))
|
|
(void))))
|
|
|
|
;; delete-autosave : (list (union #f string[filename]) string[filename]) -> boolean
|
|
;; result indicates if delete occurred
|
|
(define (delete-autosave table-entry)
|
|
(let ([autosave-file (cadr table-entry)])
|
|
(and (gui-utils:get-choice
|
|
(format (string-constant are-you-sure-delete?)
|
|
autosave-file)
|
|
(string-constant autosave-delete-title)
|
|
(string-constant cancel)
|
|
(string-constant warning)
|
|
#f)
|
|
(with-handlers ([exn:fail?
|
|
(λ (exn)
|
|
(message-box
|
|
(string-constant warning)
|
|
(format (string-constant autosave-error-deleting)
|
|
autosave-file
|
|
(if (exn? exn)
|
|
(format "~a" (exn-message exn))
|
|
(format "~s" exn))))
|
|
#f)])
|
|
(delete-file autosave-file)
|
|
#t))))
|
|
|
|
;; show-files : (list (or/c #f path?) path?) (is-a?/c area-container<%>) (is-a?/c dialog%) -> void
|
|
(define (show-files table-entry show-details-parent dlg)
|
|
(let ([file1 (list-ref table-entry 0)]
|
|
[file2 (list-ref table-entry 1)])
|
|
(send dlg begin-container-sequence)
|
|
(define had-children? #f)
|
|
(send show-details-parent change-children (λ (x)
|
|
(set! had-children? (not (null? x)))
|
|
'()))
|
|
(when file1
|
|
(add-file-viewer file1 show-details-parent (string-constant autosave-original-label)))
|
|
(add-file-viewer file2 show-details-parent (string-constant autosave-autosave-label))
|
|
(send dlg end-container-sequence)
|
|
(unless had-children?
|
|
(send dlg center))))
|
|
|
|
;; add-file-viewer : path? -> void
|
|
(define (add-file-viewer filename parent label)
|
|
(define vp (make-object vertical-panel% parent))
|
|
(define t (make-object show-files-text%))
|
|
(define msg1 (make-object message% label vp))
|
|
(define msg2 (new message%
|
|
[label (gui-utils:quote-literal-label
|
|
(path->string filename)
|
|
#:quote-amp? #f)]
|
|
[parent vp]))
|
|
(define ec (make-object editor-canvas% vp t))
|
|
(send ec min-height 400)
|
|
(send t load-file filename)
|
|
(send t hide-caret #t)
|
|
(send t lock #t))
|
|
|
|
(define show-files-frame% frame:basic%)
|
|
(define show-files-text% text:keymap%)
|
|
|
|
(main))
|
|
|
|
;; recover-file : (union #f (is-a?/c toplevel-window<%>))
|
|
;; (list (union #f string[filename]) string)
|
|
;; -> (union #f string)
|
|
(define (recover-file parent table-entry)
|
|
(let ([orig-name (or (car table-entry)
|
|
(parameterize ([finder:dialog-parent-parameter parent])
|
|
(finder:put-file #f #f #f
|
|
(string-constant autosave-restore-to-where?))))])
|
|
(and orig-name
|
|
(let ([autosave-name (cadr table-entry)])
|
|
(let ([tmp-name (and (file-exists? orig-name)
|
|
(make-temporary-file "autosave-repair~a" orig-name))])
|
|
(when (file-exists? orig-name)
|
|
(delete-file orig-name))
|
|
(copy-file autosave-name orig-name)
|
|
(delete-file autosave-name)
|
|
(when tmp-name
|
|
(delete-file tmp-name))
|
|
orig-name)))))
|