improving code, adding an identifier to log messages, closer to new unit code

svn: r5017
This commit is contained in:
Eli Barzilay 2006-12-04 07:10:25 +00:00
parent 675d3818d9
commit cdabc3a38d
3 changed files with 244 additions and 272 deletions

View File

@ -464,8 +464,9 @@ sub-directories:
(id time-str msg-str) (id time-str msg-str)
[<id>|<time>] <msg> [<id>|<time>] <msg>
where `<id>' is an integer representing the connection (numbered where `<id>' is an integer representing the connection (numbered
consecutively from 1 when the server starts) or "-" for a message consecutively from 1 when the server starts), "-" for a message
without a connection. without a connection, and "wN" for a message from the status
servlet.
* "[in]active/<assignment>/<user>/<filename>" (if submitted) --- the * "[in]active/<assignment>/<user>/<filename>" (if submitted) --- the
most recent submission for <assignment> by <user> where <filename> most recent submission for <assignment> by <user> where <filename>

View File

@ -3,298 +3,272 @@
(lib "list.ss") (lib "list.ss")
(lib "string.ss") (lib "string.ss")
(lib "date.ss") (lib "date.ss")
(lib "unitsig.ss") (lib "servlet.ss" "web-server")
(lib "servlet-sig.ss" "web-server")
(lib "response-structs.ss" "web-server") (lib "response-structs.ss" "web-server")
(lib "uri-codec.ss" "net")
(lib "md5.ss" "handin-server" "private")
(lib "logger.ss" "handin-server" "private") (lib "logger.ss" "handin-server" "private")
(lib "md5.ss" "handin-server" "private") (lib "config.ss" "handin-server" "private"))
(lib "uri-codec.ss" "net"))
(provide status-servlet) (define active-dir (build-path server-dir "active"))
(define inactive-dir (build-path server-dir "inactive"))
(define handin-dir (or (getenv "HANDIN_SERVER_DIR") (current-directory)))
(define active-dir (build-path handin-dir "active"))
(define inactive-dir (build-path handin-dir "inactive"))
(define active/inactive-dirs (list active-dir inactive-dir)) (define active/inactive-dirs (list active-dir inactive-dir))
(define master-password (define master-password (get-config 'master-password))
(with-handlers ([exn:fail? (lambda (x) #f)])
(cadr (assq 'master-password
(with-input-from-file (build-path handin-dir "config.ss")
read)))))
(define get-user-data (define get-user-data
(let ([users-file (build-path handin-dir "users.ss")]) (let ([users-file (build-path server-dir "users.ss")])
(lambda (user) (lambda (user)
(get-preference (string->symbol user) (lambda () #f) #f users-file)))) (get-preference (string->symbol user) (lambda () #f) #f users-file))))
(define (clean-str s) (define (clean-str s)
(regexp-replace #rx" *$" (regexp-replace #rx"^ *" s "") "")) (regexp-replace #rx" *$" (regexp-replace #rx"^ *" s "") ""))
(define (update-status status tag val) (define (aget alist key)
(let loop ([status status]) (cond [(assq key alist) => cdr] [else #f]))
(cond
[(null? status) (list (cons tag val))]
[(eq? (caar status) tag) (cons (cons tag val) (cdr status))]
[else (cons (car status) (loop (cdr status)))])))
(define (get-status status tag default)
(let ([a (assq tag status)])
(if a (cdr a) default)))
(define (make-page title . body) (define (make-page title . body)
`(html (head (title ,title)) `(html (head (title ,title))
(body ([bgcolor "white"]) (p ((align "center")) ,title) ,@body))) (body ([bgcolor "white"]) (h1 ((align "center")) ,title) ,@body)))
(define status-servlet (define handin-prefix-re
(unit/sig () ;; a regexp that turns a full path to a server-dir relative path
(import servlet^) (regexp
(string-append
"^" (regexp-quote
(regexp-replace
#rx"/?$"
(if (path? server-dir) (path->string server-dir) server-dir)
"/")))))
(define handin-prefix-re (define (make-k k tag)
;; a regexp that turns a full path to a handin-dir relative path (format "~a~atag=~a" k (if (regexp-match #rx"^[^#]*[?]" k) "&" "?")
(regexp (uri-encode (regexp-replace handin-prefix-re
(string-append (if (path? tag) (path->string tag) tag)
"^" (regexp-quote ""))))
(regexp-replace
#rx"/?$"
(if (path? handin-dir) (path->string handin-dir) handin-dir)
"/")))))
(define (make-k k tag)
(format "~a~atag=~a" k (if (regexp-match #rx"^[^#]*[?]" k) "&" "?")
(uri-encode (regexp-replace
handin-prefix-re
(if (path? tag) (path->string tag) tag)
""))))
(define (select-k request)
(let ([a (assq 'tag (request-bindings request))])
(and a (cdr a))))
;; `look-for' can be a username as a string (will find "bar+foo" for (define (select-k request)
;; "foo"), or a regexp that should match the whole directory name (used (aget (request-bindings request) 'tag))
;; with "^solution" below)
(define (find-hi-entry hi look-for)
(define (find-submission top)
(let ([dir (build-path top hi)])
(and (directory-exists? dir)
(ormap
(lambda (d)
(let ([d (path->string d)])
(and (cond [(string? look-for)
(member look-for
(regexp-split #rx" *[+] *" d))]
[(regexp? look-for) (regexp-match look-for d)]
[else (error 'find-hi-entry
"internal error: ~e" look-for)])
(build-path dir d))))
(directory-list dir)))))
(ormap find-submission active/inactive-dirs))
(define (handin-link k user hi) ;; `look-for' can be a username as a string (will find "bar+foo" for "foo"),
(let* ([dir (find-hi-entry hi user)] ;; or a regexp that should match the whole directory name (used with
[l (and dir (with-handlers ([exn:fail? (lambda (x) null)]) ;; "^solution" below)
(parameterize ([current-directory dir]) (define (find-hi-entry hi look-for)
(sort (define (find-submission top)
(filter (lambda (f) (let ([dir (build-path top hi)])
(and (not (equal? f "grade")) (and (directory-exists? dir)
(file-exists? f))) (ormap
(map path->string (directory-list))) (lambda (d)
string<?))))]) (let ([d (path->string d)])
(if (pair? l) (and (cond [(string? look-for)
(cdr (member look-for (regexp-split #rx" *[+] *" d))]
(apply [(regexp? look-for) (regexp-match look-for d)]
append [else (error 'find-hi-entry
(map "internal error: ~e" look-for)])
(lambda (i) `((br) ,i)) (build-path dir d))))
(map (lambda (f) (directory-list dir)))))
(let ([hi (build-path dir f)]) (ormap find-submission active/inactive-dirs))
`(font ()
(a ((href ,(make-k k hi))) ,f)
" ("
,(date->string
(seconds->date
(file-or-directory-modify-seconds hi))
#t)
")")))
l))))
(list (format "No handins accepted so far for user ~s, assignment ~s" user hi)))))
(define (solution-link k hi) (define (handin-link k user hi)
(let ([soln (find-hi-entry hi #rx"^solution")] (let* ([dir (find-hi-entry hi user)]
[none `((i "---"))]) [l (and dir (with-handlers ([exn:fail? (lambda (x) null)])
(cond [(not soln) none] (parameterize ([current-directory dir])
[(file-exists? soln) (sort (filter (lambda (f)
`((a ((href ,(make-k k soln))) "Solution"))] (and (not (equal? f "grade"))
[(directory-exists? soln) (file-exists? f)))
(parameterize ([current-directory soln]) (map path->string (directory-list)))
(let ([files (sort (map path->string string<?))))])
(filter file-exists? (if (pair? l)
(directory-list))) (cdr (apply append
string<?)]) (map (lambda (i) `((br) ,i))
(if (null? files) (map (lambda (f)
none (let ([hi (build-path dir f)])
(apply append `(font ()
(map (lambda (f) (a ([href ,(make-k k hi)]) ,f)
`((a ((href ,(make-k k (build-path soln f)))) " ("
(tt ,f)) ,(date->string
(br))) (seconds->date
files)))))] (file-or-directory-modify-seconds hi))
[else none]))) #t)
")")))
l))))
(list (format "No handins accepted so far for user ~s, assignment ~s"
user hi)))))
(define (handin-grade user hi) (define (solution-link k hi)
(let* ([dir (find-hi-entry hi user)] (let ([soln (find-hi-entry hi #rx"^solution")]
[grade (and dir [none `((i "---"))])
(let ([filename (build-path dir "grade")]) (cond [(not soln) none]
(and (file-exists? filename) [(file-exists? soln)
(with-input-from-file filename `((a ((href ,(make-k k soln))) "Solution"))]
(lambda () [(directory-exists? soln)
(read-string (file-size filename)))))))]) (parameterize ([current-directory soln])
(or grade "--"))) (let ([files (sort (map path->string
(filter file-exists? (directory-list)))
string<?)])
(if (null? files)
none
(apply append
(map (lambda (f)
`((a ([href ,(make-k k (build-path soln f))])
(tt ,f))
(br)))
files)))))]
[else none])))
(define (one-status-page status for-handin) (define (handin-grade user hi)
(let ([user (get-status status 'user (lambda () "???"))]) (let* ([dir (find-hi-entry hi user)]
(log-line "Status access: ~s" user) [grade (and dir
(let ([next (let ([filename (build-path dir "grade")])
(send/suspend (and (file-exists? filename)
(with-input-from-file filename
(lambda ()
(read-string (file-size filename)))))))])
(or grade "--")))
(define (one-status-page user for-handin)
(let* ([next (send/suspend
(lambda (k) (lambda (k)
(make-page (make-page (format "User: ~a, Handin: ~a" user for-handin)
(format "User: ~a, Handin: ~a" user for-handin) `(p ,@(handin-link k user for-handin))
`(p ,@(handin-link k user for-handin)) `(p "Grade: " ,(handin-grade user for-handin))
`(p "Grade: " ,(handin-grade user for-handin)) `(p ,@(solution-link k for-handin))
`(p ,@(solution-link k for-handin)) `(p (a ([href ,(make-k k "allofthem")])
`(p (a ((href ,(make-k k "allofthem"))) ,(format "All handins for ~a" user))))))]
,(format "All handins for ~a" user))))))]) [tag (select-k next)])
(let ([tag (select-k next)]) (if (string=? tag "allofthem")
(if (string=? tag "allofthem") (all-status-page user)
(all-status-page status) (download user tag))))
(download status tag))))))
(define re:base #rx"^([a-zA-Z]*)([0-9]+)") (define re:base #rx"^([a-zA-Z]*)([0-9]+)")
(define (all-status-page user)
(let* ([l (sort
(map path->string
(append (directory-list active-dir)
(with-handlers ([exn:fail? (lambda (x) null)])
(directory-list inactive-dir))))
(lambda (a b)
(let ([am (regexp-match re:base a)]
[bm (regexp-match re:base b)])
(if (and am bm
(string=? (cadr am) (cadr bm)))
(or (< (string->number (caddr am))
(string->number (caddr bm)))
(string<? a b))
(string<? a b)))))]
[next
(send/suspend
(lambda (k)
(define (header text)
`(td ((bgcolor "#f0f0f0")) (big (strong ,text))))
(make-page
(format "All Handins for ~a" user)
`(table ([bgcolor "#ddddff"] [cellpadding "6"] [align "center"])
(tr () ,@(map header '(nbsp "Files" "Grade" "Solution")))
,@(map (lambda (hi)
`(tr ([valign "top"])
,(header hi)
(td ([bgcolor "white"]) ,@(handin-link k user hi))
(td ([bgcolor "white"] (align "right")) ,(handin-grade user hi))
(td ([bgcolor "white"]) ,@(solution-link k hi))))
l)))))]
[tag (select-k next)])
(download user tag)))
(define (all-status-page status) (define (download who tag)
(let ([l (sort (define (check path elts)
(map path->string (let loop ([path path] [elts (reverse elts)])
(append (directory-list active-dir) (let*-values ([(base name dir?) (split-path path)]
(with-handlers ([exn:fail? (lambda (x) null)]) [(name) (path->string name)]
(directory-list inactive-dir)))) [(check) (and (pair? elts) (car elts))])
(lambda (a b) (if (null? elts)
(let ([am (regexp-match re:base a)] ;; must be rooted in active/inactive (why build-path instead of
[bm (regexp-match re:base b)]) ;; using `path'? -- because path will have a trailing slash)
(if (and am bm (member (build-path base name) active/inactive-dirs)
(string=? (cadr am) (cadr bm))) (and (cond [(eq? '* check) #t]
(or (< (string->number (caddr am)) (string->number (caddr bm))) [(regexp? check) (regexp-match check name)]
(string<? a b)) [(string? check)
(string<? a b)))))] (or (equal? name check)
[user (get-status status 'user (lambda () "???"))]) (member check (regexp-split #rx" *[+] *" name)))]
(log-line "Status access: ~s" user) [else #f])
(let ([next (loop base (cdr elts)))))))
(send/suspend (define file (build-path server-dir tag))
(lambda (k) (with-handlers ([exn:fail? (lambda (exn)
(define (header text) (make-page "Error" "Illegal file access"))])
`(td ((bgcolor "#f0f0f0")) (big (strong ,text)))) ;; Make sure the user is allowed to read the requested file:
(make-page (or (check file `(* ,who *))
(format "All Handins for ~a" user) (check file `(* #rx"^solution"))
`(table ([bgcolor "#ddddff"] [cellpadding "6"] [align "center"]) (check file `(* #rx"^solution" *))
(tr () ,@(map header '(nbsp "Files" "Grade" "Solution"))) (error "Boom!"))
,@(map (lambda (hi) (log-line "Status file-get: ~s ~a" who file)
`(tr ([valign "top"]) ;; Return the downloaded file
,(header hi) (let* ([data (with-input-from-file file
(td ([bgcolor "white"]) ,@(handin-link k user hi)) (lambda () (read-bytes (file-size file))))]
(td ([bgcolor "white"] (align "right")) ,(handin-grade user hi)) [html? (regexp-match #rx"[.]html?$" (string-foldcase tag))]
(td ([bgcolor "white"]) ,@(solution-link k hi)))) [wxme? (regexp-match #rx#"^WXME" data)])
l)))))]) (make-response/full 200 "Okay" (current-seconds)
(let ([tag (select-k next)]) (cond [html? #"text/html"]
(download status tag))))) [wxme? #"application/data"]
[else #"text/plain"])
`([Content-Length . ,(number->string (bytes-length data))]
[Content-Disposition
. ,(format "~a; filename=~s"
(if wxme? "attachment" "inline")
(let-values ([(base name dir?) (split-path file)])
(path->string name)))])
(list data)))))
(define (download status tag) (define (status-page user for-handin)
(define (check path elts) (log-line "Status access: ~s" user)
(let loop ([path path] [elts (reverse elts)]) (if for-handin
(let*-values ([(base name dir?) (split-path path)] (one-status-page user for-handin)
[(name) (path->string name)] (all-status-page user)))
[(check) (and (pair? elts) (car elts))])
(if (null? elts)
;; must be rooted in active/inactive (why build-path instead of
;; using `path'? -- because path will have a trailing slash)
(member (build-path base name) active/inactive-dirs)
(and (cond [(eq? '* check) #t]
[(regexp? check) (regexp-match check name)]
[(string? check)
(or (equal? name check)
(member check
(regexp-split #rx" *[+] *" name)))]
[else #f])
(loop base (cdr elts)))))))
(define file (build-path handin-dir tag))
(with-handlers ([exn:fail?
(lambda (exn)
(make-page "Error" "Illegal file access"))])
(let ([who (get-status status 'user (lambda () "???"))])
;; Make sure the user is allowed to read the requested file:
(or (check file `(* ,who *))
(check file `(* #rx"^solution"))
(check file `(* #rx"^solution" *))
(error "Boom!"))
(log-line "Status file-get: ~s ~a" who file))
;; Return the downloaded file
(let* ([data (with-input-from-file file
(lambda () (read-bytes (file-size file))))]
[html? (regexp-match #rx"[.]html?$" (string-foldcase tag))]
[wxme? (regexp-match #rx#"^WXME" data)])
(make-response/full 200 "Okay" (current-seconds)
(cond [html? #"text/html"]
[wxme? #"application/data"]
[else #"text/plain"])
`((Content-Length . ,(number->string (bytes-length data)))
(Content-Disposition
. ,(format "~a; filename=~s"
(if wxme? "attachment" "inline")
(let-values ([(base name dir?)
(split-path file)])
(path->string name)))))
(list data)))))
(define (status-page status for-handin) (define (login-page status for-handin errmsg)
(if for-handin (let* ([request
(one-status-page status for-handin) (send/suspend
(all-status-page status))) (lambda (k)
(make-page
"Handin Status Login"
`(form ([action ,k] [method "post"])
(table ([align "center"])
(tr (td ([colspan "2"] [align "center"])
(font ([color "red"]) ,(or errmsg 'nbsp))))
(tr (td "Username")
(td (input ([type "text"] [name "user"] [size "20"]
[value ""]))))
(tr (td nbsp))
(tr (td "Password")
(td (input ([type "password"] [name "passwd"]
[size "20"] [value ""]))))
(tr (td ([colspan "2"] [align "center"])
(input ([type "submit"] [name "post"]
[value "Login"])))))))))]
[user (clean-str (aget (request-bindings request) 'user))]
[passwd (aget (request-bindings request) 'passwd)]
[user-data (get-user-data user)])
(cond [(and user-data
(string? passwd)
(let ([pw (md5 passwd)])
(or (equal? pw (car user-data))
(equal? pw master-password))))
(status-page user for-handin)]
[else (login-page status for-handin "Bad username or password")])))
(define (login-page status for-handin errmsg) (define web-counter
(let ([request (let ([sema (make-semaphore 1)]
(send/suspend [count 0])
(lambda (k) (lambda ()
(make-page (dynamic-wind
"Handin Status Login" (lambda () (semaphore-wait sema))
`(form ([action ,k] [method "post"]) (lambda () (set! count (add1 count)) (format "w~a" count))
(table (lambda () (semaphore-post sema))))))
((align "center"))
(tr (td ((colspan "2") (align "center"))
(font ((color "red"))
,(if errmsg
errmsg
'nbsp))))
(tr (td "Username")
(td (input ([type "text"] [name "user"] [size "20"] [value ""]))))
(tr (td nbsp))
(tr (td "Password")
(td (input ([type "password"] [name "passwd"] [size "20"] [value ""]))))
(td ((colspan "2") (align "center"))
(input ([type "submit"] [name "post"] [value "Login"]))))))))])
(let ([user (clean-str (cdr (assq 'user (request-bindings request))))]
[passwd (cdr (assq 'passwd (request-bindings request)))])
(let ([user-data (get-user-data user)])
(cond
[(and user-data
(string? passwd)
(let ([pw (md5 passwd)])
(or (equal? pw (car user-data))
(equal? pw master-password))))
(status-page (update-status status 'user user) for-handin)]
[else
(login-page status for-handin "Bad username or password")])))))
(let ([a (assq 'handin (request-bindings initial-request))]) (define (start initial-request)
(login-page null (and a (cdr a)) #f)) (parameterize ([current-session (web-counter)])
(login-page null (aget (request-bindings initial-request) 'handin) #f)))
))) (provide interface-version timeout start)
(define interface-version 'v1)
(require status) (define timeout 180)
status-servlet )

View File

@ -54,18 +54,15 @@
(password-authentication ,(path->string (build-path (current-directory) "web-status-passwords")))))) (password-authentication ,(path->string (build-path (current-directory) "web-status-passwords"))))))
(virtual-host-table))) (virtual-host-table)))
#; ; old version
(define config@
(let ([file (make-temporary-file)])
(with-output-to-file file (lambda () (write config)) 'truncate)
(begin0 (load-configuration file) (delete-file file))))
(define config@ (define config@
(load-configuration-sexpr (load-configuration-sexpr
web-dir config web-dir config
#:make-servlet-namespace #:make-servlet-namespace
(make-make-servlet-namespace (make-make-servlet-namespace
#:to-be-copied-module-specs #:to-be-copied-module-specs
'((lib "logger.ss" "handin-server" "private"))))) '((lib "logger.ss" "handin-server" "private")
(lib "config.ss" "handin-server" "private")
(lib "md5.ss" "handin-server" "private")))))
(define-values/invoke-unit/sig web-server^ (define-values/invoke-unit/sig web-server^
(compound-unit/sig (compound-unit/sig