#lang scheme/unit (require mzlib/class scheme/file "sig.ss" "../gui-utils.ss" "../preferences.ss" mred/mred-sig mzlib/list string-constants) (import mred^ [prefix exit: framework:exit^] [prefix frame: framework:frame^] [prefix scheme: framework:scheme^] [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 autosave-toc-filename (build-path (find-system-path 'pref-dir) (case (system-type) [(unix) ".plt-autosave-toc"] [else "PLT-autosave-toc"]))) (define autosave-toc-save-filename (build-path (find-system-path 'pref-dir) (case (system-type) [(unix) ".plt-autosave-toc-save"] [else "PLT-autosave-toc-save"]))) (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? autosave-toc-filename) (copy-file autosave-toc-filename autosave-toc-save-filename)) (call-with-output-file autosave-toc-filename (λ (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) new-filename) name-mapping) name-mapping) (cons object-wb new-objects))) (loop (cdr orig-objects) name-mapping new-objects)))))) (define timer #f) (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? autosave-toc-filename) ;; 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 autosave-toc-filename read))] [path? (λ (x) (and (string? x) (absolute-path? x)))]) (if (and (list? v) (andmap (λ (i) (and (list? i) (= 2 (length i)) (or (not (car i)) (path? (car i))) (path? (cadr i)))) v)) 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* ([f (new final-frame% (label (string-constant recover-autosave-files-frame-title)))] [t (new text% (auto-wrap #t))] [ec (new editor-canvas% (parent (send f get-area-container)) (editor t) (line-count 2) (style '(no-hscroll)))] [hp (make-object horizontal-panel% (send f get-area-container))] [vp (make-object vertical-panel% hp)]) (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 f) filtered-table) (make-object button% (string-constant autosave-done) vp (λ (x y) (when (send f can-close?) (send f on-close) (send f show #f)))) (send f show #t) (yield done-semaphore) (void)))))) (define done-semaphore (make-semaphore 0)) (define final-frame% (class frame:basic% (define/augment (can-close?) #t) (define/augment (on-close) (inner (void) on-close) (send (group:get-the-frame-group) remove-frame this) (semaphore-post done-semaphore)) (super-new))) ;; add-table-line : (is-a? area-container<%>) (union #f (is-a?/c top-level-window<%>)) ;; -> (list (union #f string[filename]) string[filename-file-exists?]) ;; -> void ;; adds in a line to the overview table showing this pair of files. (define (add-table-line area-container 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 (or 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 backup-file) (stretchable-width #t) (parent msg2-panel))] [details (make-object button% (string-constant autosave-details) hp (λ (x y) (show-files table-entry)))] [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 parent table-entry)]) (when filename-result (disable-line) (send msg2 set-label (string-constant autosave-recovered!)) (send msg1 set-label filename-result)))))] [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 (union #f string[filename]) string) -> void (define (show-files table-entry) (let ([file1 (car table-entry)] [file2 (cadr table-entry)]) (define frame (make-object show-files-frame% (if file1 (string-constant autosave-compare-files) (string-constant autosave-show-autosave)) #f (if file1 600 300) 600)) (define hp (new horizontal-panel% (parent (send frame get-area-container)))) (when file1 (add-file-viewer file1 hp (string-constant autosave-original-label))) (add-file-viewer file2 hp (string-constant autosave-autosave-label)) (send frame show #t))) ;; add-file-viewer : string[filename] -> 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 (make-object message% filename vp)) (define ec (make-object editor-canvas% vp t)) (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)))))