(Arne Babenhauserheide)
2016-02-14: merge guile-fcp and wotdump merge guile-fcp and wotdump
diff --git a/fcp.rkt b/fcp.rkt new file mode 100644 --- /dev/null +++ b/fcp.rkt @@ -0,0 +1,202 @@ +#lang racket/base + +;; Copyright (c) 2015 dinky's evil twin sone://EWtk1limedjBM2LnGE3~z98tC8bLTu9ryLIMcFgg8PI +;; License: LGPL + +(require + racket/pretty + racket/tcp + racket/string) + +;; note: data/skip-list takes hella long to load, so use alists +;; for better performance ordered dicts! + +(displayln 'OK) +(define (startup-time) + (displayln (exact->inexact (current-process-milliseconds))) + (flush-output) + (exit)) +;; (startup-time) + +;; racket/port takes 400ms to load! +(define (copy-port in out) + (let ((buf (make-bytes #x1000))) + (let loop () + (let ((amt (read-bytes! buf in))) + (when (not (eof-object? amt)) + (write-bytes buf out) + (loop)))))) + +(define (find-identifier name opts) + (let ((identifier (assq 'Identifier opts))) + (if identifier + (string->symbol (car identifier)) + name))) + +(define (fcp-loop app) + (define waiters (make-immutable-hash)) + (define aliases (make-immutable-hash)) + (define in (current-input-port)) + (define out (current-output-port)) + + (define data-buf (make-bytes #x1000)) + + (define (write-line s) + (write-string s out) + (newline out)) + + (define (send name opts (data #f) (data-length 0)) + (write-line (symbol->string name)) + (for-each + (λ (pair) + (let ((name (car pair)) + (value (cdr pair))) + (write-line (string-append (symbol->string name) + "=" (cond + ((symbol? value) (symbol->string value)) + ((string? value) value) + ((bytes? value) (bytes->string/utf-8 value)) + ((number? value) (number->string value)) + ((eq? value #f) "false") + ((eq? value #t) "true") + (else + (error "wat is ~s" value))))))) + opts) + (if data + (begin + (write-line (string-append "Data-Length=" (number->string data-length))) + (write-line "Data") + (cond + ((procedure? data) + (data (λ (chunk) (write-bytes chunk out)))) + ((input-port? data) + (copy-port data out)) + ((bytes? data) + (write-bytes data out)) + ((string? data) + (write-bytes (string->bytes/utf-8 data) out)) + (else + (error "How to write this data?" data)))) + (begin + (write-line "EndMessage") + (newline out)))) + + (define expect + (case-lambda + ((identifier newaliases waiter) + (set! aliases + (apply hash-set* aliases + (let loop ((result '()) (newaliases newaliases)) + (if (null? newaliases) + (reverse result) + (let ((alias (car newaliases))) + (when (hash-ref aliases alias #f) + (error "Already waiting on alias" alias identifier)) + (loop (cons identifier (cons (car newaliases) result)) (cdr newaliases))))))) + (expect identifier waiter)) + ((identifier waiter) + (if (list? identifier) + (expect (car identifier) (cdr identifier) waiter) + (begin + (set! waiters (hash-set waiters identifier waiter))))))) + + (define (doit shutdown) + (app send expect shutdown) + + (let read-a-message () + (define name (string->symbol + (let ((line (read-line in 'linefeed))) + (when (eof-object? line) + (error "Fffail")) + line))) + (let properties ((opts '())) + (define line (read-line in 'linefeed)) + (case line + (("Data" "EndMessage") + (define identifier (find-identifier + (hash-ref aliases name name) + opts)) + (define waiter (hash-ref waiters identifier)) + (if (equal? line "Data") + (let-values (((feed finished) (waiter name identifier opts)) + ((total) (string->number (cdr (assoc "DataLength" opts))))) + (let reading-data ((left total)) + (if (<= left 0) + (finished total) + (let* ((max-to-read (min left (bytes-length data-buf))) + (amount (read-bytes! data-buf in 0 max-to-read))) + (when (eof-object? amount) + (error "FCP server closed connection")) + (cond + ((procedure? feed) + (feed data-buf amount left total)) + ((output-port? feed) + (write-bytes data-buf amount feed)) + (else + (error "How the heay ~s" feed))) + (reading-data (- left amount)))))) + (waiter name identifier opts)) + (read-a-message)) + (else + + (define-values (name value) (apply values + (string-split + line + "=" + #:repeat? #f))) + (properties (cons (cons name value) opts))))) + (read-a-message))) + + (dynamic-wind + (λ () + (set!-values (in out) (tcp-connect/enable-break "127.0.0.1" 9481)) + (file-stream-buffer-mode out 'none)) + (λ () + (call/cc doit)) + (λ () + (close-input-port in) + (close-output-port out) + (set! in #f)))) + +(define make-identifier (let ((counter 0)) + + (λ (sym) + (begin0 + (string-append (symbol->string sym) "-" (number->string counter)) + (set! counter (+ counter 1)))))) + +(define uri (let ((uri (getenv "URI"))) + (if (or (not uri) (= 0 (string-length uri))) + "KSK@gpl.txt" + uri))) + +(fcp-loop + (λ (send expect shutdown) + (expect 'NodeHello + (λ (name identifier opts) + (pretty-print (list 'got name opts)) + (expect '(SimpleProgress ProtocolError) + (λ (name identifier opts) + (pretty-print (list 'progress name opts)))) + (expect '(DataFound) + (λ (name identifier opts) + (displayln "Found it!"))); + (expect 'AllData + (λ (name identifier opts) + (pretty-print (list 'receiving-data name opts)) + (values + (λ (buf amount left total) + (println (list 'got-data amount left total))) + (λ (total) + (println 'all-done) + (shutdown))))) + (expect 'GetFailed + (λ (name identifier opts) + (pretty-print (list "Aww! It didn't come" uri opts)) + (shutdown))) + (send 'ClientGet `((Identifier . ,(make-identifier 'get)) + (URI . ,uri) + (Verbosity . 1) + (ReturnType . direct))))) + (send 'ClientHello '((Name . "Racket FCP") + (ExpectedVersion . 2.0))))) diff --git a/fcp.scm b/fcp.scm new file mode 100644 --- /dev/null +++ b/fcp.scm @@ -0,0 +1,231 @@ +;; Copyright (c) 2015 dinky's evil twin sone://EWtk1limedjBM2LnGE3~z98tC8bLTu9ryLIMcFgg8PI +;; License: LGPL + +(use-modules + (rnrs bytevectors) + (rnrs io ports) ;; get/put-bytevector bytevector->string + (ice-9 rw) ;; write-string + (ice-9 rdelim) + (ice-9 pretty-print) + (ice-9 vlist) + (srfi srfi-11)) ; let-values + +(define (println s) + (pretty-print s)) + +(define (copy-port in out) + (let ((buf (make-bytevector #x1000))) + (let loop () + (let ((amt (get-bytevector-n! in buf 0 #x1000))) + (when amt + (put-bytevector out buf 0 amt) + (loop)))))) + +(define (find-identifier name opts) + (let ((ret + (let ((identifier (assq 'Identifier opts))) + (if identifier + (string->symbol (cdr identifier)) + name)))) + (println (list 'find-id name opts ret)) + ret)) + +(define (string-splitonce s delim) + (let ((where (string-index s delim))) + (if where + (values (substring/shared s 0 where) (substring/shared s (+ where 1) (string-length s))) + (values s "")))) + +(define (vhash-keys v) + (vhash-fold (lambda (name value l) (cons name l)) '() v)) + +(define (fcp-loop app) + (define waiters vlist-null) + (define aliases vlist-null) + (define sock #f) + + (define data-buf (make-bytevector #x1000)) + + (define (write-line s) + (write-string/partial s sock) + (newline sock)) + + (letrec* ((send + (lambda* (name opts #:optional (data #f) (data-length 0)) + (println (symbol->string name)) + (write-line (symbol->string name)) + (for-each + (λ (pair) + (let ((name (car pair)) + (value (cdr pair))) + (let ((line (string-append (symbol->string name) + "=" (cond + ((symbol? value) (symbol->string value)) + ((string? value) value) + ((bytevector? value) (bytevector->string + value "utf-8")) + ((number? value) (number->string value)) + ((eq? value #f) "false") + ((eq? value #t) "true") + (else + (error "wat is ~s" value)))))) + (println line) + (write-line line)))) + opts) + (if data + (begin + (write-line (string-append "Data-Length=" (number->string data-length))) + (write-line "Data") + (cond + ((procedure? data) + (data (λ (chunk) (put-bytevector sock chunk)))) + ((input-port? data) + (copy-port data sock)) + ((bytevector? data) + (put-bytevector sock data)) + ((string? data) + (put-bytevector sock (string->bytevector data "utf-8"))) + (else + (error "How to write this data?" data)))) + (begin + (println "EndMessage") + (write-line "EndMessage") + (newline sock))))) + (expect + (case-lambda + ((identifier newaliases waiter) + (set! aliases + (let loop ((result aliases) (newaliases newaliases)) + (if (null? newaliases) + result + (let ((alias (car newaliases))) + (when (vhash-assq alias aliases) + (error "Already waiting on alias" alias identifier)) + (loop (vhash-consq identifier (car newaliases) result) (cdr newaliases)))))) + (expect identifier waiter)) + ((identifier waiter) + (if (list? identifier) + (expect (car identifier) (cdr identifier) waiter) + (begin + (println (list 'consq identifier waiter (vhash-keys waiters))) + (set! waiters (vhash-consq identifier waiter waiters)) + (println (list 'consq identifier waiter (vhash-keys waiters)))))))) + + (doit (lambda (shutdown) + (app send expect shutdown) + (let read-a-message () + (define name (string->symbol + (let ((line (read-line sock 'trim))) + (println (list 'line line)) + (when (eof-object? line) + (error "Fffail")) + line))) + (let properties ((opts '())) + (define line (read-line sock 'trim)) + (println (list 'line line)) + (if (or (equal? line "Data") + (equal? line "EndMessage")) + (begin + (println 'woo) + (let* ((name + (let ((derp (vhash-assq name aliases))) + (if derp + (cdr derp) + name))) + (identifier (find-identifier name opts)) + (waiter (let ((waiter + (or + (vhash-assq identifier waiters) + (vhash-assq name waiters)))) + (when (not waiter) + (println (list identifier name 'not-iny (vhash-keys waiters))) + (error "waugh")) + (cdr waiter)))) + (println (list 'waiteruh waiter)) + (if (equal? line "Data") + (let-values (((feed finished) (waiter name identifier opts)) + ((total) (string->number (cdr (assoc 'DataLength opts))))) + (let reading-data ((left total)) + (if (<= left 0) + (finished total) + (let* ((max-to-read (min left (bytevector-length data-buf))) + (amount (get-bytevector-n! sock data-buf 0 max-to-read))) + (when (eof-object? amount) + (error "FCP server closed connection")) + (cond + ((procedure? feed) + (feed data-buf amount left total)) + ((output-port? feed) + (put-bytevector feed data-buf amount)) + (else + (error "How the heay ~s" feed))) + (reading-data (- left amount)))))) + (waiter name identifier opts))) + (read-a-message)) + (call-with-values + (lambda () + (string-splitonce line #\=)) + (lambda (name value) + (println (list 'pair name value)) + (properties (cons (cons (string->symbol name) value) opts)))))) + (read-a-message))))) + (dynamic-wind + (λ () + (set! sock (let* ((addrs (getaddrinfo "127.0.0.1" "9481")) + (addr (car addrs)) + (s (socket (addrinfo:fam addr) + (addrinfo:socktype addr) + (addrinfo:protocol addr)))) + (connect s (addrinfo:addr addr)) + s))) + (λ () + (call/cc doit)) + (λ () + (close-port sock) + (set! sock #f))))) + +(define make-identifier (let ((counter 0)) + + (λ (sym) + (let ((result + (string-append (symbol->string sym) + "-" + (number->string counter)))) + (set! counter (+ counter 1)) + result)))) + +(define uri (let ((uri (getenv "URI"))) + (if (or (not uri) (= 0 (string-length uri))) + "KSK@gpl.txt" + uri))) + +(fcp-loop + (λ (send expect shutdown) + (expect 'NodeHello + (λ (name identifier opts) + (pretty-print (list 'got name opts)) + (expect '(SimpleProgress) ; ProtocolError) + (λ (name identifier opts) + (pretty-print (list 'progress name opts)))) + (expect '(DataFound) + (λ (name identifier opts) + (println "Found it!"))); + (expect 'AllData + (λ (name identifier opts) + (pretty-print (list 'receiving-data name opts)) + (values + (λ (buf amount left total) + (println (list 'got-data amount left total))) + (λ (total) + (println 'all-done) + (shutdown))))) + (expect 'GetFailed + (λ (name identifier opts) + (pretty-print (list "Aww! It didn't come" uri opts)) + (shutdown))) + (send 'ClientGet `((Identifier . ,(make-identifier 'get)) + (URI . ,uri) + (Verbosity . 1) + (ReturnType . direct))))) + (send 'ClientHello '((Name . "Racket FCP") + (ExpectedVersion . 2.0)))))