528 lines
16 KiB
Scheme
528 lines
16 KiB
Scheme
(module tool mzscheme
|
|
(require (lib "mred.ss" "mred")
|
|
(lib "class.ss")
|
|
(lib "unitsig.ss")
|
|
(lib "tool.ss" "drscheme")
|
|
(lib "etc.ss")
|
|
(lib "file.ss")
|
|
(lib "framework.ss" "framework")
|
|
(lib "sendurl.ss" "net")
|
|
(lib "bitmap-label.ss" "mrlib")
|
|
"client.ss"
|
|
"info.ss"
|
|
;; Temporary hack for test suite in separate window:
|
|
(lib "extension.ss" "test-suite"))
|
|
|
|
(provide tool@)
|
|
|
|
(define uninstalled? #f)
|
|
|
|
(define server:port
|
|
(#%info-lookup 'server:port (lambda () (getenv "PLT_HANDIN_SERVER_PORT"))))
|
|
(define-values (server port-no)
|
|
(if server:port
|
|
(let ([m (regexp-match #rx"^(.+):([0-9]+)$" server:port)])
|
|
(unless m
|
|
(error 'handin-client
|
|
"Bad configuration ~s, expecting \"server:port\""
|
|
server:port))
|
|
(values (cadr m) (string->number (caddr m))))
|
|
(values #f #f)))
|
|
|
|
(define handin-name (#%info-lookup 'name))
|
|
(define this-collection (#%info-lookup 'collection))
|
|
(define web-menu-name (#%info-lookup 'web-menu-name (lambda () #f)))
|
|
(define web-address (#%info-lookup 'web-address (lambda () #f)))
|
|
|
|
(preferences:set-default 'submit:username "" string?)
|
|
(define (remembered-user)
|
|
(preferences:get 'submit:username))
|
|
(define (remember-user user)
|
|
(preferences:set 'submit:username user))
|
|
|
|
(define (connect)
|
|
(handin-connect server
|
|
port-no
|
|
(build-path
|
|
(collection-path this-collection)
|
|
"server-cert.pem")))
|
|
|
|
(define handin-frame%
|
|
(class dialog%
|
|
(inherit show is-shown?)
|
|
(super-new [label "Handin"])
|
|
|
|
(init-field content)
|
|
|
|
(define status (new message%
|
|
[label (format "Making secure connection to ~a..." server)]
|
|
[parent this]
|
|
[stretchable-width #t]))
|
|
(define username (new text-field%
|
|
[label "Username:"]
|
|
[init-value (remembered-user)]
|
|
[parent this]
|
|
[callback (lambda (t e) (activate-ok))]
|
|
[stretchable-width #t]))
|
|
(define passwd (new text-field%
|
|
[label "Password:"]
|
|
[parent this]
|
|
[callback (lambda (t e) (activate-ok))]
|
|
[style '(single password)]
|
|
[stretchable-width #t]))
|
|
(define assignment (new choice%
|
|
[label "Assignment:"]
|
|
[choices null]
|
|
[parent this]
|
|
[callback void]
|
|
[stretchable-width #t]))
|
|
|
|
(define button-panel (new horizontal-pane%
|
|
[parent this]
|
|
[stretchable-height #f]))
|
|
(make-object vertical-pane% button-panel) ; spacer
|
|
(define ok (new button%
|
|
[label "Handin"]
|
|
[parent button-panel]
|
|
[callback (lambda (b e)
|
|
(disable-interface)
|
|
(send status set-label "Handing in...")
|
|
(parameterize ([current-custodian
|
|
comm-cust])
|
|
(thread
|
|
(lambda ()
|
|
(with-handlers ([void
|
|
(lambda (exn)
|
|
(report-error
|
|
"Handin failed."
|
|
exn))])
|
|
(remember-user (send username get-value))
|
|
(submit-assignment
|
|
connection
|
|
(send username get-value)
|
|
(send passwd get-value)
|
|
(send assignment
|
|
get-string
|
|
(send assignment get-selection))
|
|
content
|
|
(lambda ()
|
|
(semaphore-wait commit-lock)
|
|
(send status set-label "Comitting...")
|
|
(set! committing? #t)
|
|
(semaphore-post commit-lock)))
|
|
(queue-callback
|
|
(lambda ()
|
|
(when abort-commit-dialog
|
|
(send abort-commit-dialog show #f))
|
|
(send status set-label "Handin successful.")
|
|
(set! committing? #f)
|
|
(done-interface))))))))]
|
|
[style '(border)]))
|
|
|
|
(define ok-can-enable? #f)
|
|
(define (activate-ok)
|
|
(send ok enable (and ok-can-enable?
|
|
(not (string=? "" (send username get-value)))
|
|
(not (string=? "" (send passwd get-value))))))
|
|
|
|
(define cancel (new button%
|
|
[label "Cancel"]
|
|
[parent button-panel]
|
|
[callback (lambda (b e)
|
|
(let ([go? (begin
|
|
(semaphore-wait commit-lock)
|
|
(if committing?
|
|
(begin
|
|
(semaphore-post commit-lock)
|
|
(send abort-commit-dialog show #t)
|
|
continue-abort?)
|
|
#t))])
|
|
(when go?
|
|
(custodian-shutdown-all comm-cust)
|
|
(show #f))))]))
|
|
|
|
(define continue-abort? #f)
|
|
(define abort-commit-dialog
|
|
(let ([d (make-object dialog% "Commit in Progress")])
|
|
(make-object message% "The commit action is in progress." d)
|
|
(make-object message% "Cancelling now may or may not work." d)
|
|
(make-object message% "Cancel anyway?" d)
|
|
(let ([b (new horizontal-panel%
|
|
[parent d]
|
|
[stretchable-height #f]
|
|
[alignment '(center center)])])
|
|
(make-object button% "Continue Commit" d (lambda (b e) (send d show #f)))
|
|
(make-object button% "Try to Cancel" d (lambda (b e)
|
|
(set! continue-abort? #t)
|
|
(send d show #f))))))
|
|
|
|
(define (disable-interface)
|
|
(send ok enable #f)
|
|
(send username enable #f)
|
|
(send passwd enable #f)
|
|
(send assignment enable #f))
|
|
(define (enable-interface)
|
|
(send ok enable #t)
|
|
(send username enable #t)
|
|
(send passwd enable #t)
|
|
(send assignment enable #t)
|
|
(send passwd focus))
|
|
(define (done-interface)
|
|
(send cancel set-label "Close")
|
|
(send cancel focus))
|
|
|
|
(define (report-error tag exn)
|
|
(queue-callback
|
|
(lambda ()
|
|
(let* ([msg (if (exn? exn)
|
|
(let ([s (exn-message exn)])
|
|
(if (string? s)
|
|
s
|
|
(format "~e" s)))
|
|
(format "~e" exn))]
|
|
[retry? (regexp-match #rx"bad username or password for" msg)])
|
|
(custodian-shutdown-all comm-cust)
|
|
(disable-interface)
|
|
(send status set-label tag)
|
|
(when (is-shown?)
|
|
(message-box "Server Error" msg this)
|
|
(if retry?
|
|
(begin (init-comm) (semaphore-post go-sema) (enable-interface))
|
|
(done-interface)))))))
|
|
|
|
(define go-sema #f)
|
|
(define commit-lock #f)
|
|
(define committing? #f)
|
|
|
|
(define connection #f)
|
|
|
|
(define comm-cust #f)
|
|
(define (init-comm)
|
|
(set! go-sema (make-semaphore 1))
|
|
(set! commit-lock (make-semaphore 1))
|
|
(set! comm-cust (make-custodian))
|
|
(parameterize ([current-custodian comm-cust])
|
|
(thread (lambda ()
|
|
(let/ec escape
|
|
(with-handlers ([void
|
|
(lambda (exn)
|
|
(report-error
|
|
"Connection failed."
|
|
exn)
|
|
(escape))])
|
|
(semaphore-wait go-sema)
|
|
(let-values ([(h l) (connect)])
|
|
(when (null? l)
|
|
(error 'handin "there are no active assignments"))
|
|
(set! connection h)
|
|
(for-each (lambda (assign)
|
|
(send assignment append assign))
|
|
l)
|
|
(send assignment enable #t)
|
|
(set! ok-can-enable? #t)
|
|
(activate-ok)
|
|
(send status set-label (format "Connected securely for ~a." handin-name)))))))))
|
|
|
|
(define/augment (on-close)
|
|
(inner (void) on-close)
|
|
(custodian-shutdown-all comm-cust))
|
|
|
|
(send ok enable #f)
|
|
(send assignment enable #f)
|
|
|
|
(init-comm)
|
|
(send passwd focus)
|
|
(show #t)))
|
|
|
|
(define (manage-handin-account)
|
|
(new
|
|
(class dialog%
|
|
(inherit show is-shown?)
|
|
(super-new [label "Handin Account"]
|
|
[alignment '(left center)])
|
|
|
|
(define status (new message%
|
|
[label (format "Manage ~a account at ~a." handin-name server)]
|
|
[parent this]
|
|
[stretchable-width #t]))
|
|
|
|
(define tabs (new tab-panel%
|
|
[parent this]
|
|
[choices '("Change Password"
|
|
"New User"
|
|
"Uninstall")]
|
|
[callback
|
|
(lambda (tp e)
|
|
(send single active-child
|
|
(list-ref
|
|
(list old-user-box
|
|
new-user-box
|
|
uninstall-box)
|
|
(send tabs get-selection))))]))
|
|
|
|
(define single (new panel:single%
|
|
[parent tabs]))
|
|
|
|
(define (mk-txt label parent activate-ok)
|
|
(new text-field%
|
|
[label label]
|
|
[parent parent]
|
|
[callback (lambda (t e) (activate-ok))]
|
|
[stretchable-width #t]))
|
|
|
|
(define (mk-passwd label parent activate-ok)
|
|
(new text-field%
|
|
[label label]
|
|
[parent parent]
|
|
[callback (lambda (t e) (activate-ok))]
|
|
[style '(single password)]
|
|
[stretchable-width #t]))
|
|
|
|
(define (non-empty? t)
|
|
(not (string=? "" (send t get-value))))
|
|
|
|
(define (activate-change)
|
|
(send change-button enable
|
|
(and (non-empty? old-username)
|
|
(non-empty? old-passwd)
|
|
(non-empty? new-passwd)
|
|
(non-empty? confirm-passwd))))
|
|
(define old-user-box (new vertical-panel%
|
|
[parent single]
|
|
[alignment '(center center)]))
|
|
(define old-username (mk-txt "Username:" old-user-box activate-change))
|
|
(send old-username set-value (remembered-user))
|
|
|
|
(define old-passwd (mk-passwd "Old:" old-user-box activate-change))
|
|
(define new-passwd (mk-passwd "New:" old-user-box activate-change))
|
|
(define confirm-passwd (mk-passwd "New again:" old-user-box activate-change))
|
|
(define change-button (new button%
|
|
[label "Set Password"]
|
|
[parent old-user-box]
|
|
[callback
|
|
(lambda (b e)
|
|
(do-change/add #f old-username b e))]
|
|
[style '(border)]))
|
|
|
|
(define (activate-new)
|
|
(send new-button enable
|
|
(and (non-empty? new-username)
|
|
(non-empty? full-name)
|
|
(non-empty? student-id)
|
|
(non-empty? add-passwd))))
|
|
(define new-user-box (new vertical-panel%
|
|
[parent single]
|
|
[alignment '(center center)]))
|
|
(define new-username (mk-txt "Username:" new-user-box activate-new))
|
|
(send new-username set-value (remembered-user))
|
|
(define full-name (mk-txt "Full Name:" new-user-box activate-new))
|
|
(define student-id (mk-txt "ID:" new-user-box activate-new))
|
|
(define add-passwd (mk-passwd "Password:" new-user-box activate-new))
|
|
(define new-button (new button%
|
|
[label "Add User"]
|
|
[parent new-user-box]
|
|
[callback
|
|
(lambda (b e)
|
|
(do-change/add #t new-username b e))]
|
|
[style '(border)]))
|
|
|
|
(define uninstall-box (new vertical-panel%
|
|
[parent single]
|
|
[alignment '(center center)]))
|
|
(define uninstall-button (new button%
|
|
[label (format "Uninstall ~a" handin-name)]
|
|
[parent uninstall-box]
|
|
[callback
|
|
(lambda (b e)
|
|
(let ([dir (collection-path this-collection)])
|
|
(with-handlers ([void (lambda (exn)
|
|
(report-error
|
|
"Uninstall failed."
|
|
exn))])
|
|
(delete-directory/files dir)
|
|
(set! uninstalled? #t)
|
|
(send uninstall-button enable #f)
|
|
(message-box
|
|
"Uninstall"
|
|
(format
|
|
"The ~a tool has been uninstalled. ~a~a"
|
|
handin-name
|
|
"The Handin button and associated menu items"
|
|
" will not appear after you restart DrScheme.")))))]))
|
|
(send uninstall-button enable (not uninstalled?))
|
|
|
|
(define (report-error tag exn)
|
|
(queue-callback
|
|
(lambda ()
|
|
(custodian-shutdown-all comm-cust)
|
|
(send status set-label tag)
|
|
(when (is-shown?)
|
|
(message-box
|
|
"Server Error"
|
|
(if (exn? exn)
|
|
(let ([s (exn-message exn)])
|
|
(if (string? s)
|
|
s
|
|
(format "~e" s))))
|
|
this)
|
|
(set! comm-cust (make-custodian))))))
|
|
|
|
(define comm-cust (make-custodian))
|
|
(define/augment (on-close)
|
|
(inner (void) on-close)
|
|
(custodian-shutdown-all comm-cust))
|
|
|
|
(define button-panel (new horizontal-pane%
|
|
[parent this]
|
|
[stretchable-height #f]))
|
|
(make-object vertical-pane% button-panel) ; spacer
|
|
(define cancel (new button%
|
|
[label "Cancel"]
|
|
[parent button-panel]
|
|
[callback (lambda (b e)
|
|
(custodian-shutdown-all comm-cust)
|
|
(show #f))]))
|
|
|
|
;; Too-long fields can't damage the server, but they might
|
|
;; result in confusing errors due to safety cut-offs on
|
|
;; the server side.
|
|
(define (check-length field size name k)
|
|
(when ((string-length (send field get-value)) . > . size)
|
|
(message-box "Error"
|
|
(format "The ~a must be no longer than ~a characters."
|
|
name size))
|
|
(k (void))))
|
|
|
|
(define (do-change/add new? username b e)
|
|
(let/ec k
|
|
(unless new?
|
|
(check-length new-passwd 50 "New password" k)
|
|
(when (not (string=? (send new-passwd get-value)
|
|
(send confirm-passwd get-value)))
|
|
(message-box "Password Error"
|
|
"The \"New\" and \"New again\" passwords are not the same.")
|
|
(k (void))))
|
|
(when new?
|
|
(check-length username 50 "Username" k)
|
|
(check-length full-name 100 "Full Name" k)
|
|
(check-length student-id 100 "ID" k)
|
|
(check-length add-passwd 50 "Password" k))
|
|
(send tabs enable #f)
|
|
(parameterize ([current-custodian comm-cust])
|
|
(thread
|
|
(lambda ()
|
|
(with-handlers ([void (lambda (exn)
|
|
(send tabs enable #t)
|
|
(report-error
|
|
"Update failed."
|
|
exn))])
|
|
(remember-user (send username get-value))
|
|
(send status set-label "Making secure connection...")
|
|
(let-values ([(h l) (connect)])
|
|
(send status set-label "Updating server...")
|
|
(if new?
|
|
(submit-addition
|
|
h
|
|
(send username get-value)
|
|
(send full-name get-value)
|
|
(send student-id get-value)
|
|
(send add-passwd get-value))
|
|
(submit-password-change
|
|
h
|
|
(send username get-value)
|
|
(send old-passwd get-value)
|
|
(send new-passwd get-value))))
|
|
(send status set-label "Success.")
|
|
(send cancel set-label "Close")))))))
|
|
|
|
(send new-user-box show #f)
|
|
(send uninstall-box show #f)
|
|
(activate-new)
|
|
(activate-change)
|
|
(show #t))))
|
|
|
|
(define (scale-by-half file)
|
|
(let* ([bm (make-object bitmap% file)]
|
|
[w (send bm get-width)]
|
|
[h (send bm get-height)]
|
|
[bm2 (make-object bitmap% (quotient w 2) (quotient h 2))]
|
|
[mdc (make-object bitmap-dc% bm2)])
|
|
(send mdc set-scale 0.5 0.5)
|
|
(send mdc draw-bitmap bm 0 0)
|
|
(send mdc set-bitmap #f)
|
|
bm2))
|
|
|
|
(define handin-icon
|
|
(scale-by-half
|
|
(build-path (collection-path this-collection) "icon.png")))
|
|
|
|
(define (editors->string editors)
|
|
(let* ([base (make-object editor-stream-out-bytes-base%)]
|
|
[stream (make-object editor-stream-out% base)])
|
|
(write-editor-version stream base)
|
|
(write-editor-global-header stream)
|
|
(for-each (lambda (ed)
|
|
(send ed write-to-file stream)
|
|
(send ed write-to-file stream))
|
|
editors)
|
|
(write-editor-global-footer stream)
|
|
(send base get-string)))
|
|
|
|
(add-test-suite-extension
|
|
"Handin"
|
|
handin-icon
|
|
(lambda (parent editor)
|
|
(let ([content (editors->string (list editor))])
|
|
(new handin-frame% [parent parent] [content content]))))
|
|
|
|
(define tool@
|
|
(unit/sig drscheme:tool-exports^
|
|
(import drscheme:tool^)
|
|
|
|
(define phase1 void)
|
|
(define phase2 void)
|
|
|
|
(define tool-button-label
|
|
(bitmap-label-maker
|
|
"Handin"
|
|
handin-icon))
|
|
|
|
(define (make-new-unit-frame% super%)
|
|
(class super%
|
|
(inherit get-button-panel
|
|
get-definitions-text
|
|
get-interactions-text)
|
|
(super-instantiate ())
|
|
|
|
(define/override (file-menu:between-open-and-revert file-menu)
|
|
(new menu-item%
|
|
(label (format "Manage ~a..." handin-name))
|
|
(parent file-menu)
|
|
(callback (lambda (m e) (manage-handin-account))))
|
|
(super file-menu:between-open-and-revert file-menu))
|
|
|
|
(define/override (help-menu:after-about menu)
|
|
(when web-menu-name
|
|
(new menu-item%
|
|
(label web-menu-name)
|
|
(parent menu)
|
|
(callback (lambda (item evt)
|
|
(send-url web-address)))))
|
|
(super help-menu:after-about menu))
|
|
|
|
(define button
|
|
(new button%
|
|
[label (tool-button-label this)]
|
|
[parent (get-button-panel)]
|
|
[callback (lambda (button evt)
|
|
(let ([content (editors->string
|
|
(list (get-definitions-text)
|
|
(get-interactions-text)))])
|
|
(new handin-frame% [parent this] [content content])))]
|
|
[style '(deleted)]))
|
|
(send (get-button-panel) change-children
|
|
(lambda (l) (cons button l)))))
|
|
|
|
(when (and server port-no)
|
|
(drscheme:get/extend:extend-unit-frame make-new-unit-frame% #f)))))
|