client/util.ss
#lang scheme
(require
 (planet murphy/packed-io:1:0)
 "../main.ss"
 "handle.ss")

(define (file-handle->input-port handle [name (string->some-system-path
                                               (stat-name (send handle read-stat))
                                               'unix)])
  (let ([offset (send handle offset)])
    (if offset
        (make-input-port
         name
         (λ (buffer)
           (let ([data (send handle read (bytes-length buffer))])
             (if (not (eof-object? data))
                 (begin
                   (bytes-copy! buffer 0 data)
                   (bytes-length data))
                 eof)))
         #f
         (λ ()
           (send handle clunk))
         #f #f #f
         void
         (add1 offset))
        (raise-type-error 'file-handle->input-port "open file handle" handle))))

(define (file-handle->output-port handle [name (string->some-system-path
                                                (stat-name (send handle read-stat))
                                                'unix)])
  (let ([offset (send handle offset)])
    (if offset
        (make-output-port
         name
         always-evt
         (λ (data start end dont-block? enable-breaks?)
           (send handle write (subbytes data start end)))
         (λ ()
           (send handle clunk))
         #f #f #f #f
         void
         (add1 offset))
        (raise-type-error 'file-handle->output-port "open file handle" handle))))

(define (explode-path path)
  (let next ([names null] [path (simplify-path path #f)])
    (case path
      [(relative #f)
       names]
      [else
       (let-values ([(base name must-be-dir?) (split-path path)])
         (next
          (cons (case name
                  [(same) "."]
                  [(up) ".."]
                  [else (some-system-path->string name)])
                names)
          base))])))

(define (9p-open-input-file anchor path)
  (let ([file
         (foldl
          (λ (name anchor)
            (send anchor walk name))
          anchor (explode-path path))])
    (send file open (open-mode r))
    (file-handle->input-port file)))

(define (9p-open-output-file anchor path
                             #:exists [exists-flag 'error]
                             #:permissions [perm (file-mode (type file)
                                                            (user r w) (group r) (others r))])
  (let*-values ([(steps names)
                 (split-at-right (explode-path path) 1)]
                [(name)
                 (car names)]
                [(parent)
                 (foldl
                  (λ (name anchor)
                    (send anchor walk name))
                  anchor steps)]
                [(file i/o-unit)
                 (cond
                   [(with-handlers ([exn:fail:filesystem:9p? (λ (exn) #f)])
                      (send parent walk name))
                    => (λ (file)
                         (case exists-flag
                           [(error)
                            (raise
                             (make-exn:fail:filesystem:exists
                              (format "~a: file ~e already exists below ~e"
                                      '9p-open-output-file path anchor)
                              (current-continuation-marks)))]
                           [(replace)
                            (send file remove)
                            (send parent create name perm (open-mode w))]
                           [(truncate must-truncate)
                            (values file
                                    (send file open (open-mode w trunc)))]
                           [(truncate/replace)
                            (or (with-handlers ([exn:fail:filesystem:9p? (λ (exn) #f)])
                                  (values file
                                          (send file open (open-mode w trunc))))
                                (begin
                                  (send file remove)
                                  (send parent create name perm (open-mode w))))]
                           [(update can-update)
                            (values file
                                    (send file open (open-mode w)))]
                           [(append)
                            (values file
                                    (begin0
                                      (send file open (open-mode w))
                                      (send file offset (stat-length (send file read-stat)))))]))]
                   [else
                    (case exists-flag
                      [(update must-truncate)
                       (raise
                        (make-exn:fail:filesystem
                         (format "~a: file ~e does not exist below ~e"
                                 '9p-open-output-file path anchor)
                         (current-continuation-marks)))]
                      [else
                       (send parent create name perm (open-mode w))])])])
    (file-handle->output-port file)))

(define (call-with-9p-input-file anchor path proc)
  (proc (9p-open-input-file anchor path)))

(define (call-with-9p-input-file* anchor path proc)
  (let ([port (9p-open-input-file anchor path)])
    (dynamic-wind
     void
     (λ ()
       (proc port))
     (λ ()
       (close-input-port port)))))

(define (with-input-from-9p-file anchor path thunk)
  (call-with-9p-input-file*
   anchor path
   (λ (port)
     (parameterize ([current-input-port port])
       (thunk)))))

(define (call-with-9p-output-file anchor path proc
                                  #:exists [exists-flag 'error]
                                  #:permissions [perm (file-mode (type file)
                                                                 (user r w) (group r) (others w))])
  (proc (9p-open-output-file anchor path #:exists exists-flag #:permissions perm)))

(define (call-with-9p-output-file* anchor path proc
                                   #:exists [exists-flag 'error]
                                   #:permissions [perm (file-mode (type file)
                                                                  (user r w) (group r) (others w))])
  (let ([port (9p-open-output-file anchor path #:exists exists-flag #:permissions perm)])
    (dynamic-wind
     void
     (λ ()
       (proc port))
     (λ ()
       (close-output-port port)))))

(define (with-output-to-9p-file anchor path thunk
                                #:exists [exists-flag 'error]
                                #:permissions [perm (file-mode (type file)
                                                               (user r w) (group r) (others w))])
  (call-with-9p-output-file*
   anchor path
   (λ (port)
     (parameterize ([current-output-port port])
       (thunk)))
   #:exists exists-flag #:permissions perm))

(provide/contract
 [file-handle->input-port (->* ((is-a?/c file-handle<%>)) (any/c) input-port?)]
 [file-handle->output-port (->* ((is-a?/c file-handle<%>)) (any/c) output-port?)]
 [9p-open-input-file (-> (is-a?/c directory-handle<%>)
                         (and/c path-string? relative-path?)
                         input-port?)]
 [call-with-9p-input-file (-> (is-a?/c directory-handle<%>)
                              (and/c path-string? relative-path?)
                              (-> input-port? any)
                              any)]
 [call-with-9p-input-file* (-> (is-a?/c directory-handle<%>)
                               (and/c path-string? relative-path?)
                               (-> input-port? any)
                               any)]
 [with-input-from-9p-file (-> (is-a?/c directory-handle<%>)
                              (and/c path-string? relative-path?)
                              (-> any)
                              any)]
 [9p-open-output-file (->* ((is-a?/c directory-handle<%>)
                            (and/c path-string? relative-path?))
                           (#:exists
                            (symbols 'error 'append 'update 'replace 'truncate 'truncate/replace)
                            #:permissions
                            (packing-contract uint/p))
                           output-port?)]
 [call-with-9p-output-file (->* ((is-a?/c directory-handle<%>)
                                 (and/c path-string? relative-path?)
                                 (-> output-port? any))
                                (#:exists
                                 (symbols 'error 'append 'update 'replace 'truncate 'truncate/replace)
                                 #:permissions
                                 (packing-contract uint/p))
                                any)]
 [call-with-9p-output-file* (->* ((is-a?/c directory-handle<%>)
                                  (and/c path-string? relative-path?)
                                  (-> output-port? any))
                                 (#:exists
                                  (symbols 'error 'append 'update 'replace 'truncate 'truncate/replace)
                                  #:permissions
                                  (packing-contract uint/p))
                                 any)]
 [with-output-to-9p-file (->* ((is-a?/c directory-handle<%>)
                               (and/c path-string? relative-path?)
                               (-> any))
                              (#:exists
                               (symbols 'error 'append 'update 'replace 'truncate 'truncate/replace)
                               #:permissions
                               (packing-contract uint/p))
                              any)])