#lang scheme/base

(define version-url "http://download.racket-lang.org/version.txt")
(define timeout 30)

(require "utils.rkt")

;; This file can be invoked from an installer, and in case it's
;; without zo files using `net/url' is extremely slow.  Instead, do
;; things directly.
;; (require net/url)
;; (define (url->port url) (get-pure-port (string->url url)))

(require scheme/tcp)
(define (url->port url)
  (define-values (host path)
    (apply values (cdr (regexp-match #rx"^http://([^/:@]+)(/.*)$" url))))
  (define-values (i o) (tcp-connect host 80))
  (fprintf o "GET ~a HTTP/1.0\r\nHost: ~a\r\n\r\n" path host)
  (flush-output o)
  (close-output-port o)
  (unless (regexp-match #rx"^HTTP/[0-9.]+ 200 OK\r\n.*?\r\n\r\n" i)
    (error 'url->port "bad reply from server: ~a" (read-line)))
  i)

(define error-value
  (case-lambda
    [(what) `(error ,what)]
    [(what more)
     `(error ,what
             ,(cond [(list? more) (format "~a" more)]
                    [(exn? more)  (format "(~a)" (exn-message more))]
                    [else         (format "(~a)" more)]))]))

(define (with-timeout timeout thunk)
  (define result #f)
  (let ([r (sync/timeout timeout
             (thread (lambda ()
                       (set! result
                             (with-handlers
                                 ([void (lambda (e)
                                          (error-value "internal error" e))])
                               (thunk))))))])
    (if r result (error-value "timeout"))))

(define (check-version-raw)
  (let/ec escape
    (define (err . args) (escape (apply error-value args)))
    (define-syntax try
      (syntax-rules ()
        [(_ expr error-message)
         (with-handlers ([void (lambda (e) (err error-message e))]) expr)]))
    ;; Get server information, carefully
    (define version-info
      (parameterize ([current-input-port
                      (try (url->port (format "~a?~a" version-url (version)))
                           "could not connect to website")])
        (try (read) "unexpected response from server")))
    (define (get key)
      (cond [(assq key version-info) => cadr]
            [else (err (format "no `~s' in response" key) version-info)]))
    (define (getver key)
      (let ([ver (get key)])
        (if (valid-version? ver)
          ver
          (err "bad version string from server" key))))
    (unless (and (list? version-info)
                 (andmap (lambda (x)
                           (and (list? x)
                                (= 2 (length x))
                                (symbol? (car x))
                                (string? (cadr x))))
                         version-info))
      (err "bad response from server" version-info))
    ;; Make a decision
    (let ([current (version)]
          [stable (getver 'stable)]
          [recent (getver 'recent)])
      (cond
        ;; we have the newest version (can be > if we have a build from git)
        [(version<=? recent current) 'ok]
        ;; we're stable, but there's a newer version
        [(version<=? stable current) `(ok-but ,recent)]
        ;; new version out -- no alphas or we have an alpha => show recent
        [(or (equal? recent stable)
             (and (alpha-version? current)
                  ;; but if we have an alpha that is older then the current
                  ;; stable then go to the next case
                  (version<=? stable current)))
         `(newer ,recent)]
        ;; new version out, we have an outdated stable, there is also an alpha
        ;; (alternatively, we have an alpha that is older than the current
        ;; stable)
        [else `(newer ,stable ,recent)]))))

;; Check the version on the server and compare to our version.  Possible return
;; values (message is always a string):
;; * `ok
;;   You're fine.
;; * `(ok-but ,version)
;;   You have a fine stable version, but note that there is a newer alpha
;; * `(newer ,version)
;;   You have an old version, please upgrade to `version'
;; * `(newer ,version ,alpha)
;;   You have an old version, please upgrade to `version' you may consider also
;;   the alpha version
;; * `(error ,message [,additional-info])
;;   An error occured, the third (optional) value can be shown as the system
;;   error that happened or the value that caused an error.
(provide check-version)
(define (check-version)
  (with-timeout timeout check-version-raw))
