<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package http

// HTTP status codes as registered with IANA.
// See: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
const (
	StatusContinue           = 100 // RFC 7231, 6.2.1
	StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
	StatusProcessing         = 102 // RFC 2518, 10.1

	StatusOK                   = 200 // RFC 7231, 6.3.1
	StatusCreated              = 201 // RFC 7231, 6.3.2
	StatusAccepted             = 202 // RFC 7231, 6.3.3
	StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
	StatusNoContent            = 204 // RFC 7231, 6.3.5
	StatusResetContent         = 205 // RFC 7231, 6.3.6
	StatusPartialContent       = 206 // RFC 7233, 4.1
	StatusMultiStatus          = 207 // RFC 4918, 11.1
	StatusAlreadyReported      = 208 // RFC 5842, 7.1
	StatusIMUsed               = 226 // RFC 3229, 10.4.1

	StatusMultipleChoices   = 300 // RFC 7231, 6.4.1
	StatusMovedPermanently  = 301 // RFC 7231, 6.4.2
	StatusFound             = 302 // RFC 7231, 6.4.3
	StatusSeeOther          = 303 // RFC 7231, 6.4.4
	StatusNotModified       = 304 // RFC 7232, 4.1
	StatusUseProxy          = 305 // RFC 7231, 6.4.5
	_                       = 306 // RFC 7231, 6.4.6 (Unused)
	StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
	StatusPermanentRedirect = 308 // RFC 7538, 3

	StatusBadRequest                   = 400 // RFC 7231, 6.5.1
	StatusUnauthorized                 = 401 // RFC 7235, 3.1
	StatusPaymentRequired              = 402 // RFC 7231, 6.5.2
	StatusForbidden                    = 403 // RFC 7231, 6.5.3
	StatusNotFound                     = 404 // RFC 7231, 6.5.4
	StatusMethodNotAllowed             = 405 // RFC 7231, 6.5.5
	StatusNotAcceptable                = 406 // RFC 7231, 6.5.6
	StatusProxyAuthRequired            = 407 // RFC 7235, 3.2
	StatusRequestTimeout               = 408 // RFC 7231, 6.5.7
	StatusConflict                     = 409 // RFC 7231, 6.5.8
	StatusGone                         = 410 // RFC 7231, 6.5.9
	StatusLengthRequired               = 411 // RFC 7231, 6.5.10
	StatusPreconditionFailed           = 412 // RFC 7232, 4.2
	StatusRequestEntityTooLarge        = 413 // RFC 7231, 6.5.11
	StatusRequestURITooLong            = 414 // RFC 7231, 6.5.12
	StatusUnsupportedMediaType         = 415 // RFC 7231, 6.5.13
	StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
	StatusExpectationFailed            = 417 // RFC 7231, 6.5.14
	StatusTeapot                       = 418 // RFC 7168, 2.3.3
	StatusUnprocessableEntity          = 422 // RFC 4918, 11.2
	StatusLocked                       = 423 // RFC 4918, 11.3
	StatusFailedDependency             = 424 // RFC 4918, 11.4
	StatusUpgradeRequired              = 426 // RFC 7231, 6.5.15
	StatusPreconditionRequired         = 428 // RFC 6585, 3
	StatusTooManyRequests              = 429 // RFC 6585, 4
	StatusRequestHeaderFieldsTooLarge  = 431 // RFC 6585, 5
	StatusUnavailableForLegalReasons   = 451 // RFC 7725, 3

	StatusInternalServerError           = 500 // RFC 7231, 6.6.1
	StatusNotImplemented                = 501 // RFC 7231, 6.6.2
	StatusBadGateway                    = 502 // RFC 7231, 6.6.3
	StatusServiceUnavailable            = 503 // RFC 7231, 6.6.4
	StatusGatewayTimeout                = 504 // RFC 7231, 6.6.5
	StatusHTTPVersionNotSupported       = 505 // RFC 7231, 6.6.6
	StatusVariantAlsoNegotiates         = 506 // RFC 2295, 8.1
	StatusInsufficientStorage           = 507 // RFC 4918, 11.5
	StatusLoopDetected                  = 508 // RFC 5842, 7.2
	StatusNotExtended                   = 510 // RFC 2774, 7
	StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)

var statusText = map[int]string{
	StatusContinue:           "Continue",
	StatusSwitchingProtocols: "Switching Protocols",
	StatusProcessing:         "Processing",

	StatusOK:                   "OK",
	StatusCreated:              "Created",
	StatusAccepted:             "Accepted",
	StatusNonAuthoritativeInfo: "Non-Authoritative Information",
	StatusNoContent:            "No Content",
	StatusResetContent:         "Reset Content",
	StatusPartialContent:       "Partial Content",
	StatusMultiStatus:          "Multi-Status",
	StatusAlreadyReported:      "Already Reported",
	StatusIMUsed:               "IM Used",

	StatusMultipleChoices:   "Multiple Choices",
	StatusMovedPermanently:  "Moved Permanently",
	StatusFound:             "Found",
	StatusSeeOther:          "See Other",
	StatusNotModified:       "Not Modified",
	StatusUseProxy:          "Use Proxy",
	StatusTemporaryRedirect: "Temporary Redirect",
	StatusPermanentRedirect: "Permanent Redirect",

	StatusBadRequest:                   "Bad Request",
	StatusUnauthorized:                 "Unauthorized",
	StatusPaymentRequired:              "Payment Required",
	StatusForbidden:                    "Forbidden",
	StatusNotFound:                     "Not Found",
	StatusMethodNotAllowed:             "Method Not Allowed",
	StatusNotAcceptable:                "Not Acceptable",
	StatusProxyAuthRequired:            "Proxy Authentication Required",
	StatusRequestTimeout:               "Request Timeout",
	StatusConflict:                     "Conflict",
	StatusGone:                         "Gone",
	StatusLengthRequired:               "Length Required",
	StatusPreconditionFailed:           "Precondition Failed",
	StatusRequestEntityTooLarge:        "Request Entity Too Large",
	StatusRequestURITooLong:            "Request URI Too Long",
	StatusUnsupportedMediaType:         "Unsupported Media Type",
	StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
	StatusExpectationFailed:            "Expectation Failed",
	StatusTeapot:                       "I'm a teapot",
	StatusUnprocessableEntity:          "Unprocessable Entity",
	StatusLocked:                       "Locked",
	StatusFailedDependency:             "Failed Dependency",
	StatusUpgradeRequired:              "Upgrade Required",
	StatusPreconditionRequired:         "Precondition Required",
	StatusTooManyRequests:              "Too Many Requests",
	StatusRequestHeaderFieldsTooLarge:  "Request Header Fields Too Large",
	StatusUnavailableForLegalReasons:   "Unavailable For Legal Reasons",

	StatusInternalServerError:           "Internal Server Error",
	StatusNotImplemented:                "Not Implemented",
	StatusBadGateway:                    "Bad Gateway",
	StatusServiceUnavailable:            "Service Unavailable",
	StatusGatewayTimeout:                "Gateway Timeout",
	StatusHTTPVersionNotSupported:       "HTTP Version Not Supported",
	StatusVariantAlsoNegotiates:         "Variant Also Negotiates",
	StatusInsufficientStorage:           "Insufficient Storage",
	StatusLoopDetected:                  "Loop Detected",
	StatusNotExtended:                   "Not Extended",
	StatusNetworkAuthenticationRequired: "Network Authentication Required",
}

// StatusText returns a text for the HTTP status code. It returns the empty
// string if the code is unknown.
func StatusText(code int) string {
	return statusText[code]
}
<commit_msg>net/http: add StatusMisdirectedRequest (421)<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package http

// HTTP status codes as registered with IANA.
// See: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
const (
	StatusContinue           = 100 // RFC 7231, 6.2.1
	StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
	StatusProcessing         = 102 // RFC 2518, 10.1

	StatusOK                   = 200 // RFC 7231, 6.3.1
	StatusCreated              = 201 // RFC 7231, 6.3.2
	StatusAccepted             = 202 // RFC 7231, 6.3.3
	StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
	StatusNoContent            = 204 // RFC 7231, 6.3.5
	StatusResetContent         = 205 // RFC 7231, 6.3.6
	StatusPartialContent       = 206 // RFC 7233, 4.1
	StatusMultiStatus          = 207 // RFC 4918, 11.1
	StatusAlreadyReported      = 208 // RFC 5842, 7.1
	StatusIMUsed               = 226 // RFC 3229, 10.4.1

	StatusMultipleChoices   = 300 // RFC 7231, 6.4.1
	StatusMovedPermanently  = 301 // RFC 7231, 6.4.2
	StatusFound             = 302 // RFC 7231, 6.4.3
	StatusSeeOther          = 303 // RFC 7231, 6.4.4
	StatusNotModified       = 304 // RFC 7232, 4.1
	StatusUseProxy          = 305 // RFC 7231, 6.4.5
	_                       = 306 // RFC 7231, 6.4.6 (Unused)
	StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
	StatusPermanentRedirect = 308 // RFC 7538, 3

	StatusBadRequest                   = 400 // RFC 7231, 6.5.1
	StatusUnauthorized                 = 401 // RFC 7235, 3.1
	StatusPaymentRequired              = 402 // RFC 7231, 6.5.2
	StatusForbidden                    = 403 // RFC 7231, 6.5.3
	StatusNotFound                     = 404 // RFC 7231, 6.5.4
	StatusMethodNotAllowed             = 405 // RFC 7231, 6.5.5
	StatusNotAcceptable                = 406 // RFC 7231, 6.5.6
	StatusProxyAuthRequired            = 407 // RFC 7235, 3.2
	StatusRequestTimeout               = 408 // RFC 7231, 6.5.7
	StatusConflict                     = 409 // RFC 7231, 6.5.8
	StatusGone                         = 410 // RFC 7231, 6.5.9
	StatusLengthRequired               = 411 // RFC 7231, 6.5.10
	StatusPreconditionFailed           = 412 // RFC 7232, 4.2
	StatusRequestEntityTooLarge        = 413 // RFC 7231, 6.5.11
	StatusRequestURITooLong            = 414 // RFC 7231, 6.5.12
	StatusUnsupportedMediaType         = 415 // RFC 7231, 6.5.13
	StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
	StatusExpectationFailed            = 417 // RFC 7231, 6.5.14
	StatusTeapot                       = 418 // RFC 7168, 2.3.3
	StatusMisdirectedRequest           = 421 // RFC 7540, 9.1.2
	StatusUnprocessableEntity          = 422 // RFC 4918, 11.2
	StatusLocked                       = 423 // RFC 4918, 11.3
	StatusFailedDependency             = 424 // RFC 4918, 11.4
	StatusUpgradeRequired              = 426 // RFC 7231, 6.5.15
	StatusPreconditionRequired         = 428 // RFC 6585, 3
	StatusTooManyRequests              = 429 // RFC 6585, 4
	StatusRequestHeaderFieldsTooLarge  = 431 // RFC 6585, 5
	StatusUnavailableForLegalReasons   = 451 // RFC 7725, 3

	StatusInternalServerError           = 500 // RFC 7231, 6.6.1
	StatusNotImplemented                = 501 // RFC 7231, 6.6.2
	StatusBadGateway                    = 502 // RFC 7231, 6.6.3
	StatusServiceUnavailable            = 503 // RFC 7231, 6.6.4
	StatusGatewayTimeout                = 504 // RFC 7231, 6.6.5
	StatusHTTPVersionNotSupported       = 505 // RFC 7231, 6.6.6
	StatusVariantAlsoNegotiates         = 506 // RFC 2295, 8.1
	StatusInsufficientStorage           = 507 // RFC 4918, 11.5
	StatusLoopDetected                  = 508 // RFC 5842, 7.2
	StatusNotExtended                   = 510 // RFC 2774, 7
	StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)

var statusText = map[int]string{
	StatusContinue:           "Continue",
	StatusSwitchingProtocols: "Switching Protocols",
	StatusProcessing:         "Processing",

	StatusOK:                   "OK",
	StatusCreated:              "Created",
	StatusAccepted:             "Accepted",
	StatusNonAuthoritativeInfo: "Non-Authoritative Information",
	StatusNoContent:            "No Content",
	StatusResetContent:         "Reset Content",
	StatusPartialContent:       "Partial Content",
	StatusMultiStatus:          "Multi-Status",
	StatusAlreadyReported:      "Already Reported",
	StatusIMUsed:               "IM Used",

	StatusMultipleChoices:   "Multiple Choices",
	StatusMovedPermanently:  "Moved Permanently",
	StatusFound:             "Found",
	StatusSeeOther:          "See Other",
	StatusNotModified:       "Not Modified",
	StatusUseProxy:          "Use Proxy",
	StatusTemporaryRedirect: "Temporary Redirect",
	StatusPermanentRedirect: "Permanent Redirect",

	StatusBadRequest:                   "Bad Request",
	StatusUnauthorized:                 "Unauthorized",
	StatusPaymentRequired:              "Payment Required",
	StatusForbidden:                    "Forbidden",
	StatusNotFound:                     "Not Found",
	StatusMethodNotAllowed:             "Method Not Allowed",
	StatusNotAcceptable:                "Not Acceptable",
	StatusProxyAuthRequired:            "Proxy Authentication Required",
	StatusRequestTimeout:               "Request Timeout",
	StatusConflict:                     "Conflict",
	StatusGone:                         "Gone",
	StatusLengthRequired:               "Length Required",
	StatusPreconditionFailed:           "Precondition Failed",
	StatusRequestEntityTooLarge:        "Request Entity Too Large",
	StatusRequestURITooLong:            "Request URI Too Long",
	StatusUnsupportedMediaType:         "Unsupported Media Type",
	StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
	StatusExpectationFailed:            "Expectation Failed",
	StatusTeapot:                       "I'm a teapot",
	StatusMisdirectedRequest:           "Misdirected Request",
	StatusUnprocessableEntity:          "Unprocessable Entity",
	StatusLocked:                       "Locked",
	StatusFailedDependency:             "Failed Dependency",
	StatusUpgradeRequired:              "Upgrade Required",
	StatusPreconditionRequired:         "Precondition Required",
	StatusTooManyRequests:              "Too Many Requests",
	StatusRequestHeaderFieldsTooLarge:  "Request Header Fields Too Large",
	StatusUnavailableForLegalReasons:   "Unavailable For Legal Reasons",

	StatusInternalServerError:           "Internal Server Error",
	StatusNotImplemented:                "Not Implemented",
	StatusBadGateway:                    "Bad Gateway",
	StatusServiceUnavailable:            "Service Unavailable",
	StatusGatewayTimeout:                "Gateway Timeout",
	StatusHTTPVersionNotSupported:       "HTTP Version Not Supported",
	StatusVariantAlsoNegotiates:         "Variant Also Negotiates",
	StatusInsufficientStorage:           "Insufficient Storage",
	StatusLoopDetected:                  "Loop Detected",
	StatusNotExtended:                   "Not Extended",
	StatusNetworkAuthenticationRequired: "Network Authentication Required",
}

// StatusText returns a text for the HTTP status code. It returns the empty
// string if the code is unknown.
func StatusText(code int) string {
	return statusText[code]
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Primitive HTTP client. See RFC 2616.

package http

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"io"
	"net"
	"os"
	"strconv"
	"strings"
)

// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
// return true if the string includes a port.
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }

// Used in Send to implement io.ReadCloser by bundling together the
// bufio.Reader through which we read the response, and the underlying
// network connection.
type readClose struct {
	io.Reader
	io.Closer
}

// Send issues an HTTP request.  Caller should close resp.Body when done reading it.
//
// TODO: support persistent connections (multiple requests on a single connection).
// send() method is nonpublic because, when we refactor the code for persistent
// connections, it may no longer make sense to have a method with this signature.
func send(req *Request) (resp *Response, err os.Error) {
	if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
		return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme}
	}

	addr := req.URL.Host
	if !hasPort(addr) {
		addr += ":" + req.URL.Scheme
	}
	info := req.URL.RawUserinfo
	if len(info) > 0 {
		enc := base64.URLEncoding
		encoded := make([]byte, enc.EncodedLen(len(info)))
		enc.Encode(encoded, []byte(info))
		if req.Header == nil {
			req.Header = make(map[string]string)
		}
		req.Header["Authorization"] = "Basic " + string(encoded)
	}

	var conn io.ReadWriteCloser
	if req.URL.Scheme == "http" {
		conn, err = net.Dial("tcp", "", addr)
		if err != nil {
			return nil, err
		}
	} else { // https
		conn, err = tls.Dial("tcp", "", addr, nil)
		if err != nil {
			return nil, err
		}
		h := req.URL.Host
		if hasPort(h) {
			h = h[0:strings.LastIndex(h, ":")]
		}
		if err := conn.(*tls.Conn).VerifyHostname(h); err != nil {
			return nil, err
		}
	}

	err = req.Write(conn)
	if err != nil {
		conn.Close()
		return nil, err
	}

	reader := bufio.NewReader(conn)
	resp, err = ReadResponse(reader, req.Method)
	if err != nil {
		conn.Close()
		return nil, err
	}

	resp.Body = readClose{resp.Body, conn}

	return
}

// True if the specified HTTP status code is one for which the Get utility should
// automatically redirect.
func shouldRedirect(statusCode int) bool {
	switch statusCode {
	case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect:
		return true
	}
	return false
}

// Get issues a GET to the specified URL.  If the response is one of the following
// redirect codes, it follows the redirect, up to a maximum of 10 redirects:
//
//    301 (Moved Permanently)
//    302 (Found)
//    303 (See Other)
//    307 (Temporary Redirect)
//
// finalURL is the URL from which the response was fetched -- identical to the
// input URL unless redirects were followed.
//
// Caller should close r.Body when done reading it.
func Get(url string) (r *Response, finalURL string, err os.Error) {
	// TODO: if/when we add cookie support, the redirected request shouldn't
	// necessarily supply the same cookies as the original.
	// TODO: set referrer header on redirects.
	for redirect := 0; ; redirect++ {
		if redirect >= 10 {
			err = os.ErrorString("stopped after 10 redirects")
			break
		}

		var req Request
		if req.URL, err = ParseURL(url); err != nil {
			break
		}
		url = req.URL.String()
		if r, err = send(&req); err != nil {
			break
		}
		if shouldRedirect(r.StatusCode) {
			r.Body.Close()
			if url = r.GetHeader("Location"); url == "" {
				err = os.ErrorString(fmt.Sprintf("%d response missing Location header", r.StatusCode))
				break
			}
			continue
		}
		finalURL = url
		return
	}

	err = &URLError{"Get", url, err}
	return
}

// Post issues a POST to the specified URL.
//
// Caller should close r.Body when done reading it.
func Post(url string, bodyType string, body io.Reader) (r *Response, err os.Error) {
	var req Request
	req.Method = "POST"
	req.ProtoMajor = 1
	req.ProtoMinor = 1
	req.Close = true
	req.Body = nopCloser{body}
	req.Header = map[string]string{
		"Content-Type": bodyType,
	}
	req.TransferEncoding = []string{"chunked"}

	req.URL, err = ParseURL(url)
	if err != nil {
		return nil, err
	}

	return send(&req)
}

// PostForm issues a POST to the specified URL,
// with data's keys and values urlencoded as the request body.
//
// Caller should close r.Body when done reading it.
func PostForm(url string, data map[string]string) (r *Response, err os.Error) {
	var req Request
	req.Method = "POST"
	req.ProtoMajor = 1
	req.ProtoMinor = 1
	req.Close = true
	body := urlencode(data)
	req.Body = nopCloser{body}
	req.Header = map[string]string{
		"Content-Type":   "application/x-www-form-urlencoded",
		"Content-Length": strconv.Itoa(body.Len()),
	}
	req.ContentLength = int64(body.Len())

	req.URL, err = ParseURL(url)
	if err != nil {
		return nil, err
	}

	return send(&req)
}

// TODO: remove this function when PostForm takes a multimap.
func urlencode(data map[string]string) (b *bytes.Buffer) {
	m := make(map[string][]string, len(data))
	for k, v := range data {
		m[k] = []string{v}
	}
	return bytes.NewBuffer([]byte(EncodeQuery(m)))
}

// Head issues a HEAD to the specified URL.
func Head(url string) (r *Response, err os.Error) {
	var req Request
	req.Method = "HEAD"
	if req.URL, err = ParseURL(url); err != nil {
		return
	}
	url = req.URL.String()
	if r, err = send(&req); err != nil {
		return
	}
	return
}

type nopCloser struct {
	io.Reader
}

func (nopCloser) Close() os.Error { return nil }
<commit_msg>http: follow relative redirect in Get<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Primitive HTTP client. See RFC 2616.

package http

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"io"
	"net"
	"os"
	"strconv"
	"strings"
)

// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
// return true if the string includes a port.
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }

// Used in Send to implement io.ReadCloser by bundling together the
// bufio.Reader through which we read the response, and the underlying
// network connection.
type readClose struct {
	io.Reader
	io.Closer
}

// Send issues an HTTP request.  Caller should close resp.Body when done reading it.
//
// TODO: support persistent connections (multiple requests on a single connection).
// send() method is nonpublic because, when we refactor the code for persistent
// connections, it may no longer make sense to have a method with this signature.
func send(req *Request) (resp *Response, err os.Error) {
	if req.URL.Scheme != "http" && req.URL.Scheme != "https" {
		return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme}
	}

	addr := req.URL.Host
	if !hasPort(addr) {
		addr += ":" + req.URL.Scheme
	}
	info := req.URL.RawUserinfo
	if len(info) > 0 {
		enc := base64.URLEncoding
		encoded := make([]byte, enc.EncodedLen(len(info)))
		enc.Encode(encoded, []byte(info))
		if req.Header == nil {
			req.Header = make(map[string]string)
		}
		req.Header["Authorization"] = "Basic " + string(encoded)
	}

	var conn io.ReadWriteCloser
	if req.URL.Scheme == "http" {
		conn, err = net.Dial("tcp", "", addr)
		if err != nil {
			return nil, err
		}
	} else { // https
		conn, err = tls.Dial("tcp", "", addr, nil)
		if err != nil {
			return nil, err
		}
		h := req.URL.Host
		if hasPort(h) {
			h = h[0:strings.LastIndex(h, ":")]
		}
		if err := conn.(*tls.Conn).VerifyHostname(h); err != nil {
			return nil, err
		}
	}

	err = req.Write(conn)
	if err != nil {
		conn.Close()
		return nil, err
	}

	reader := bufio.NewReader(conn)
	resp, err = ReadResponse(reader, req.Method)
	if err != nil {
		conn.Close()
		return nil, err
	}

	resp.Body = readClose{resp.Body, conn}

	return
}

// True if the specified HTTP status code is one for which the Get utility should
// automatically redirect.
func shouldRedirect(statusCode int) bool {
	switch statusCode {
	case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect:
		return true
	}
	return false
}

// Get issues a GET to the specified URL.  If the response is one of the following
// redirect codes, it follows the redirect, up to a maximum of 10 redirects:
//
//    301 (Moved Permanently)
//    302 (Found)
//    303 (See Other)
//    307 (Temporary Redirect)
//
// finalURL is the URL from which the response was fetched -- identical to the
// input URL unless redirects were followed.
//
// Caller should close r.Body when done reading it.
func Get(url string) (r *Response, finalURL string, err os.Error) {
	// TODO: if/when we add cookie support, the redirected request shouldn't
	// necessarily supply the same cookies as the original.
	// TODO: set referrer header on redirects.
	var base *URL
	for redirect := 0; ; redirect++ {
		if redirect >= 10 {
			err = os.ErrorString("stopped after 10 redirects")
			break
		}

		var req Request
		if base == nil {
			req.URL, err = ParseURL(url)
		} else {
			req.URL, err = base.ParseURL(url)
		}
		if err != nil {
			break
		}
		url = req.URL.String()
		if r, err = send(&req); err != nil {
			break
		}
		if shouldRedirect(r.StatusCode) {
			r.Body.Close()
			if url = r.GetHeader("Location"); url == "" {
				err = os.ErrorString(fmt.Sprintf("%d response missing Location header", r.StatusCode))
				break
			}
			base = req.URL
			continue
		}
		finalURL = url
		return
	}

	err = &URLError{"Get", url, err}
	return
}

// Post issues a POST to the specified URL.
//
// Caller should close r.Body when done reading it.
func Post(url string, bodyType string, body io.Reader) (r *Response, err os.Error) {
	var req Request
	req.Method = "POST"
	req.ProtoMajor = 1
	req.ProtoMinor = 1
	req.Close = true
	req.Body = nopCloser{body}
	req.Header = map[string]string{
		"Content-Type": bodyType,
	}
	req.TransferEncoding = []string{"chunked"}

	req.URL, err = ParseURL(url)
	if err != nil {
		return nil, err
	}

	return send(&req)
}

// PostForm issues a POST to the specified URL,
// with data's keys and values urlencoded as the request body.
//
// Caller should close r.Body when done reading it.
func PostForm(url string, data map[string]string) (r *Response, err os.Error) {
	var req Request
	req.Method = "POST"
	req.ProtoMajor = 1
	req.ProtoMinor = 1
	req.Close = true
	body := urlencode(data)
	req.Body = nopCloser{body}
	req.Header = map[string]string{
		"Content-Type":   "application/x-www-form-urlencoded",
		"Content-Length": strconv.Itoa(body.Len()),
	}
	req.ContentLength = int64(body.Len())

	req.URL, err = ParseURL(url)
	if err != nil {
		return nil, err
	}

	return send(&req)
}

// TODO: remove this function when PostForm takes a multimap.
func urlencode(data map[string]string) (b *bytes.Buffer) {
	m := make(map[string][]string, len(data))
	for k, v := range data {
		m[k] = []string{v}
	}
	return bytes.NewBuffer([]byte(EncodeQuery(m)))
}

// Head issues a HEAD to the specified URL.
func Head(url string) (r *Response, err os.Error) {
	var req Request
	req.Method = "HEAD"
	if req.URL, err = ParseURL(url); err != nil {
		return
	}
	url = req.URL.String()
	if r, err = send(&req); err != nil {
		return
	}
	return
}

type nopCloser struct {
	io.Reader
}

func (nopCloser) Close() os.Error { return nil }
<|endoftext|>


<commit_before>package lazyquicktime

import "fmt"
import "image"
import "errors"

import "github.com/amarburg/go-lazyfs"
import "github.com/amarburg/go-quicktime"
import "github.com/amarburg/go-prores-ffmpeg"

type LazyQuicktime struct {
	file lazyfs.FileSource
	Tree quicktime.AtomArray
	Trak quicktime.TRAKAtom
	Stbl *quicktime.STBLAtom
	Mvhd quicktime.MVHDAtom
}

func LoadMovMetadata(file lazyfs.FileSource) (*LazyQuicktime, error) {

	mov := &LazyQuicktime{file: file}

	sz, err := file.FileSize()
	if sz < 0 || err != nil {
		return mov, fmt.Errorf("Unable to retrieve file size.")
	}

	set_eagerload := func(conf *quicktime.BuildTreeConfig) {
		conf.EagerloadTypes = []string{"moov"}
	}

	tree, err := quicktime.BuildTree(file, sz, set_eagerload)

	if err != nil {
		return mov, err
	}
	mov.Tree = tree

	moov := mov.Tree.FindAtom("moov")
	if moov == nil {
		return mov, errors.New("Can't find MOOV atom")
	}

	mvhd := moov.FindAtom("mvhd")
	if mvhd == nil {
		return mov, errors.New("Couldn't find MVHD in the moov atom")
	}
	mov.Mvhd, _ = quicktime.ParseMVHD(mvhd)

	tracks := moov.FindAtoms("trak")
	if tracks == nil || len(tracks) == 0 {
		return mov, errors.New("Couldn't find any TRAKs in the MOOV")
	}

	var track *quicktime.Atom = nil
	for i, t := range tracks {
		mdia := t.FindAtom("mdia")
		if mdia == nil {
			fmt.Println("No mdia track", i)
			continue
		}

		minf := mdia.FindAtom("minf")
		if minf == nil {
			fmt.Println("No minf track", i)
			continue
		}

		if minf.FindAtom("vmhd") != nil {
			track = t
			break
		}
	}

	if track == nil {
		return mov, errors.New("Couldn't identify the Video track")
	}

	mov.Trak, err = quicktime.ParseTRAK(track)
	if err != nil {
		return mov, errors.New(fmt.Sprintf("Unable to parse TRAK atom: %s", err.Error()))
	}

	mov.Stbl = &mov.Trak.Mdia.Minf.Stbl // Just an alias

	return mov, nil
}

func (mov *LazyQuicktime) NumFrames() int {
	return mov.Stbl.NumFrames()
}

func (mov *LazyQuicktime) Duration() float32 {
	return mov.Mvhd.Duration()
}

func (mov *LazyQuicktime) ExtractFrame(frame int) (image.Image, error) {

	frame_offset, frame_size, _ := mov.Stbl.SampleOffsetSize(frame)

	//fmt.Printf("Extracting frame %d at offset %d size %d\n", frame, frame_offset, frame_size)

	buf := make([]byte, frame_size)

	if buf == nil {
		return nil, fmt.Errorf("Couldn't make buffer of size %d", frame_size)
	}

	n, _ := mov.file.ReadAt(buf, frame_offset)

	if n != frame_size {
		return nil, fmt.Errorf("Tried to read %d bytes but got %d instead", frame_size, n)
	}

	width, height := int(mov.Trak.Tkhd.Width), int(mov.Trak.Tkhd.Height)

	img, err := prores.DecodeProRes(buf, width, height)

	return img, err

}
<commit_msg>Switch to use of uint64 for file size.<commit_after>package lazyquicktime

import "fmt"
import "image"
import "errors"

import "github.com/amarburg/go-lazyfs"
import "github.com/amarburg/go-quicktime"
import "github.com/amarburg/go-prores-ffmpeg"

type LazyQuicktime struct {
	file lazyfs.FileSource
	Tree quicktime.AtomArray
	Trak quicktime.TRAKAtom
	Stbl *quicktime.STBLAtom
	Mvhd quicktime.MVHDAtom
}

func LoadMovMetadata(file lazyfs.FileSource) (*LazyQuicktime, error) {

	mov := &LazyQuicktime{file: file}

	sz, err := file.FileSize()
	if sz < 0 || err != nil {
		return mov, fmt.Errorf("Unable to retrieve file size.")
	}

	set_eagerload := func(conf *quicktime.BuildTreeConfig) {
		conf.EagerloadTypes = []string{"moov"}
	}

	fmt.Println("Reading Mov of size ", sz)
	tree, err := quicktime.BuildTree(file, uint64(sz), set_eagerload)

	if err != nil {
		return mov, err
	}
	mov.Tree = tree

	moov := mov.Tree.FindAtom("moov")
	if moov == nil {
		return mov, errors.New("Can't find MOOV atom")
	}

	mvhd := moov.FindAtom("mvhd")
	if mvhd == nil {
		return mov, errors.New("Couldn't find MVHD in the moov atom")
	}
	mov.Mvhd, _ = quicktime.ParseMVHD(mvhd)

	tracks := moov.FindAtoms("trak")
	if tracks == nil || len(tracks) == 0 {
		return mov, errors.New("Couldn't find any TRAKs in the MOOV")
	}

	var track *quicktime.Atom = nil
	for i, t := range tracks {
		mdia := t.FindAtom("mdia")
		if mdia == nil {
			fmt.Println("No mdia track", i)
			continue
		}

		minf := mdia.FindAtom("minf")
		if minf == nil {
			fmt.Println("No minf track", i)
			continue
		}

		if minf.FindAtom("vmhd") != nil {
			track = t
			break
		}
	}

	if track == nil {
		return mov, errors.New("Couldn't identify the Video track")
	}

	mov.Trak, err = quicktime.ParseTRAK(track)
	if err != nil {
		return mov, errors.New(fmt.Sprintf("Unable to parse TRAK atom: %s", err.Error()))
	}

	mov.Stbl = &mov.Trak.Mdia.Minf.Stbl // Just an alias

	return mov, nil
}

func (mov *LazyQuicktime) FileSize() int64 {
	sz,_ := mov.file.FileSize()
	return sz
}

func (mov *LazyQuicktime) NumFrames() int {
	return mov.Stbl.NumFrames()
}

func (mov *LazyQuicktime) Duration() float32 {
	return mov.Mvhd.Duration()
}

func (mov *LazyQuicktime) ExtractFrame(frame int) (image.Image, error) {

	frame_offset, frame_size, _ := mov.Stbl.SampleOffsetSize(frame)

	//fmt.Printf("Extracting frame %d at offset %d size %d\n", frame, frame_offset, frame_size)

	buf := make([]byte, frame_size)

	if buf == nil {
		return nil, fmt.Errorf("Couldn't make buffer of size %d", frame_size)
	}

	n, _ := mov.file.ReadAt(buf, frame_offset)

	if n != frame_size {
		return nil, fmt.Errorf("Tried to read %d bytes but got %d instead", frame_size, n)
	}

	width, height := int(mov.Trak.Tkhd.Width), int(mov.Trak.Tkhd.Height)

	img, err := prores.DecodeProRes(buf, width, height)

	return img, err

}
<|endoftext|>


<commit_before>// The term package implements rendering HTML content for the terminal.
package term

import (
	"bytes"
	"errors"
	"io"
	"runtime"
	"unicode"

	"github.com/PuerkitoBio/goquery"
	"golang.org/x/net/html"
)

// Renderer implements a rendering HTML content in a stripped down format
// suitable for the terminal.
type Renderer struct {
	// Width sets the assumed width of the terminal, which aids in wrapping
	// text. If == 0, then the width is treated as 80. If < 0, then the width is
	// treated as unbounded.
	Width int
	// TabSize indicates the number of characters that the terminal uses to
	// render a tab. If <= 0, then tabs are assumed to have a size of 8.
	TabSize int
}

var (
	stop         = errors.New("stop")          // Stop walking without erroring.
	skipChildren = errors.New("skip children") // Skip all child nodes.
	skipText     = errors.New("skip text")     // Skip child text nodes.
)

type walker func(node *html.Node, entering bool) error

func walk(node *html.Node, cb walker) error {
	err := cb(node, true)
	if err != nil && err != skipChildren && err != skipText {
		return err
	}
	if err != skipChildren {
		for c := node.FirstChild; c != nil; c = c.NextSibling {
			if c.Type == html.TextNode && err == skipText {
				continue
			}
			if err := walk(c, cb); err != nil {
				return err
			}
		}
	}
	if err = cb(node, false); err != nil && err != skipChildren && err != skipText {
		return err
	}
	return err
}

// Facilitates wrapped text.
type writer struct {
	bytes.Buffer
	w io.Writer

	// If wrapping, wrap to this width.
	width int

	// Size of tab character.
	tabSize int
}

func (w *writer) Flush() error {
	b := w.Buffer.Bytes()
	defer w.Buffer.Reset()

	b = bytes.TrimSpace(b)
	var paragraphs [][]rune
	for i := 0; i < len(b); {
		if b[i] == '\n' {
			// Bounds check unneeded; if b[i], being a \n, was at the end, it
			// would have been trimmed.
			if b[i+1] == '\n' {
				// New paragraph.
				paragraphs = append(paragraphs, []rune(string(b[:i])))
				i += 2
				for b[i] == '\n' {
					// Collapse extra newlines.
					i++
				}
				b = b[i:]
				i = 0
				continue
			} else {
				// Unwrap.
				b[i] = ' '
			}
		}
		i++
	}
	paragraphs = append(paragraphs, []rune(string(b)))
	if w.width > 0 {
		for j, p := range paragraphs {
			for i := 0; i < len(p); {
				n := i + w.width
				if n+1 >= len(p) {
					break
				}
				for n > i && !unicode.IsSpace(p[n]) {
					n--
				}
				if n <= i {
					// Long word.
					n = i + w.width
					p = append(p, 0)
					copy(p[n+1:], p[n:])
					p[n] = '\n'
					i = n + 1
				} else {
					p[n] = '\n'
					i = n + 1
				}
			}
			paragraphs[j] = p
		}
		if runtime.GOOS == "windows" {
			// Remove newlines that are inserted just after the edge of the
			// width. This prevents the terminal from incorrectly producing an
			// extra gap when wrapping. This removes the newline entirely, so it
			// is assumed that the wrapping will separate the previous word from
			// the next word.
			for i, p := range paragraphs {
				for h, i := 0, 0; i < len(p); i++ {
					if p[i] != '\n' {
						continue
					}
					if i-h >= w.width {
						copy(p[i:], p[i+1:])
						p = p[:len(p)-1]
						h = i
						i--
					} else {
						h = i + 1
					}
				}
				paragraphs[i] = p
			}
		}
	}
	for i, p := range paragraphs {
		if i > 0 {
			w.w.Write([]byte{'\n', '\n'})
		}
		if _, err := w.w.Write([]byte(string(p))); err != nil {
			return err
		}
	}
	return nil
}

func (r Renderer) Render(w io.Writer, s *goquery.Selection) error {
	buf := &writer{
		w:       w,
		width:   r.Width,
		tabSize: r.TabSize,
	}
	for _, node := range s.Nodes {
		if node.Type == html.TextNode {
			continue
		}
		err := walk(node, func(node *html.Node, entering bool) error {
			switch node.Type {
			case html.ErrorNode:
			case html.TextNode:
				if entering {
					buf.WriteString(node.Data)
				}
			case html.DocumentNode:
			case html.ElementNode:
				h := handlers[elementMatcher{node.Data, entering}]
				if h != nil {
					return h(buf, node)
				}
			case html.CommentNode:
			case html.DoctypeNode:
			case html.RawNode:
			}
			return nil
		})
		if err != nil && err != stop {
			return err
		}
	}
	return buf.Flush()
}

type elementMatcher struct {
	data     string
	entering bool
}

type nodeHandlers map[elementMatcher]func(w *writer, node *html.Node) error

func isElement(node *html.Node, tag string) bool {
	return node.Type == html.ElementNode && node.Data == tag
}

var handlers = nodeHandlers{
	{"code", true}: func(w *writer, node *html.Node) error {
		if isElement(node.Parent, "pre") { // May have syntax: `class="language-*"`
			// Block
			w.WriteString(node.FirstChild.Data)
			return skipChildren
		} else {
			// Inline
			return w.WriteByte('`')
		}
	},
	{"code", false}: func(w *writer, node *html.Node) error {
		if isElement(node.Parent, "pre") {
			// Block
		} else {
			// Inline
			return w.WriteByte('`')
		}
		return nil
	},
	{"section", true}: func(w *writer, node *html.Node) error {
		return skipText
	},
	{"p", true}: func(w *writer, node *html.Node) error {
		_, err := w.WriteString("\n\n")
		return err
	},
	{"p", false}: func(w *writer, node *html.Node) error {
		_, err := w.WriteString("\n\n")
		return err
	},
}
<commit_msg>Render sections.<commit_after>// The term package implements rendering HTML content for the terminal.
package term

import (
	"bytes"
	"errors"
	"io"
	"runtime"
	"strings"
	"unicode"

	"github.com/PuerkitoBio/goquery"
	"golang.org/x/net/html"
)

// Renderer implements a rendering HTML content in a stripped down format
// suitable for the terminal.
type Renderer struct {
	// Width sets the assumed width of the terminal, which aids in wrapping
	// text. If == 0, then the width is treated as 80. If < 0, then the width is
	// treated as unbounded.
	Width int
	// TabSize indicates the number of characters that the terminal uses to
	// render a tab. If <= 0, then tabs are assumed to have a size of 8.
	TabSize int
}

var (
	stop         = errors.New("stop")          // Stop walking without erroring.
	skipChildren = errors.New("skip children") // Skip all child nodes.
	skipText     = errors.New("skip text")     // Skip child text nodes.
)

type walker func(node *html.Node, entering bool) error

func walk(node *html.Node, cb walker) error {
	err := cb(node, true)
	if err != nil && err != skipChildren && err != skipText {
		return err
	}
	if err != skipChildren {
		for c := node.FirstChild; c != nil; c = c.NextSibling {
			if c.Type == html.TextNode && err == skipText {
				continue
			}
			if err := walk(c, cb); err != nil {
				return err
			}
		}
	}
	if err = cb(node, false); err != nil && err != skipChildren && err != skipText {
		return err
	}
	return err
}

// Facilitates wrapped text.
type writer struct {
	bytes.Buffer
	w io.Writer

	// If wrapping, wrap to this width.
	width int

	// Size of tab character.
	tabSize int
}

func (w *writer) Flush() error {
	b := w.Buffer.Bytes()
	defer w.Buffer.Reset()

	b = bytes.TrimSpace(b)
	var paragraphs [][]rune
	for i := 0; i < len(b); {
		if b[i] == '\n' {
			// Bounds check unneeded; if b[i], being a \n, was at the end, it
			// would have been trimmed.
			if b[i+1] == '\n' {
				// New paragraph.
				paragraphs = append(paragraphs, []rune(string(b[:i])))
				i += 2
				for b[i] == '\n' {
					// Collapse extra newlines.
					i++
				}
				b = b[i:]
				i = 0
				continue
			} else {
				// Unwrap.
				b[i] = ' '
			}
		}
		i++
	}
	paragraphs = append(paragraphs, []rune(string(b)))
	if w.width > 0 {
		for j, p := range paragraphs {
			for i := 0; i < len(p); {
				n := i + w.width
				if n+1 >= len(p) {
					break
				}
				for n > i && !unicode.IsSpace(p[n]) {
					n--
				}
				if n <= i {
					// Long word.
					n = i + w.width
					p = append(p, 0)
					copy(p[n+1:], p[n:])
					p[n] = '\n'
					i = n + 1
				} else {
					p[n] = '\n'
					i = n + 1
				}
			}
			paragraphs[j] = p
		}
		if runtime.GOOS == "windows" {
			// Remove newlines that are inserted just after the edge of the
			// width. This prevents the terminal from incorrectly producing an
			// extra gap when wrapping. This removes the newline entirely, so it
			// is assumed that the wrapping will separate the previous word from
			// the next word.
			for i, p := range paragraphs {
				for h, i := 0, 0; i < len(p); i++ {
					if p[i] != '\n' {
						continue
					}
					if i-h >= w.width {
						copy(p[i:], p[i+1:])
						p = p[:len(p)-1]
						h = i
						i--
					} else {
						h = i + 1
					}
				}
				paragraphs[i] = p
			}
		}
	}
	for i, p := range paragraphs {
		if i > 0 {
			w.w.Write([]byte{'\n', '\n'})
		}
		if _, err := w.w.Write([]byte(string(p))); err != nil {
			return err
		}
	}
	return nil
}

func (r Renderer) Render(w io.Writer, s *goquery.Selection) error {
	buf := &writer{
		w:       w,
		width:   r.Width,
		tabSize: r.TabSize,
	}
	var state walkState
	for _, node := range s.Nodes {
		if node.Type == html.TextNode {
			continue
		}
		err := walk(node, func(node *html.Node, entering bool) error {
			switch node.Type {
			case html.ErrorNode:
			case html.TextNode:
				if entering {
					buf.WriteString(node.Data)
				}
			case html.DocumentNode:
			case html.ElementNode:
				h := handlers[elementMatcher{node.Data, entering}]
				if h != nil {
					return h(buf, node, &state)
				}
			case html.CommentNode:
			case html.DoctypeNode:
			case html.RawNode:
			}
			return nil
		})
		if err != nil && err != stop {
			return err
		}
	}
	return buf.Flush()
}

type elementMatcher struct {
	data     string
	entering bool
}

type walkState struct {
	depth int
}

type nodeHandlers map[elementMatcher]func(w *writer, node *html.Node, s *walkState) error

func isElement(node *html.Node, tag string) bool {
	return node.Type == html.ElementNode && node.Data == tag
}

func sectionName(node *html.Node) string {
	for _, attr := range node.Attr {
		if attr.Key == "data-name" {
			return attr.Val
		}
	}
	return ""
}

var handlers = nodeHandlers{
	{"code", true}: func(w *writer, node *html.Node, s *walkState) error {
		if isElement(node.Parent, "pre") { // May have syntax: `class="language-*"`
			// Block
			w.WriteString(node.FirstChild.Data)
			return skipChildren
		} else {
			// Inline
			return w.WriteByte('`')
		}
	},
	{"code", false}: func(w *writer, node *html.Node, s *walkState) error {
		if isElement(node.Parent, "pre") {
			// Block
		} else {
			// Inline
			return w.WriteByte('`')
		}
		return nil
	},
	{"section", true}: func(w *writer, node *html.Node, s *walkState) error {
		if s.depth > 0 {
			if name := sectionName(node); name != "" {
				w.WriteString(strings.Repeat("#", s.depth))
				w.WriteString(" ")
				w.WriteString(name)
				w.WriteString("\n\n")
			}
		}
		s.depth++
		return skipText
	},
	{"section", false}: func(w *writer, node *html.Node, s *walkState) error {
		s.depth--
		return nil
	},
	{"body", true}: func(w *writer, node *html.Node, s *walkState) error {
		s.depth++
		return nil
	},
	{"body", false}: func(w *writer, node *html.Node, s *walkState) error {
		s.depth--
		return nil
	},
	{"p", true}: func(w *writer, node *html.Node, s *walkState) error {
		_, err := w.WriteString("\n\n")
		return err
	},
	{"p", false}: func(w *writer, node *html.Node, s *walkState) error {
		_, err := w.WriteString("\n\n")
		return err
	},
}
<|endoftext|>


<commit_before>// Copyright 2015 Yahoo Inc.
// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.

package main

import (
	"bufio"
	"github.com/ardielle/ardielle-go/rdl"
	"log"
	"strings"
	"text/template"
)

type javaClientGenerator struct {
	registry rdl.TypeRegistry
	schema   *rdl.Schema
	name     string
	writer   *bufio.Writer
	err      error
	banner   string
	ns       string
	base     string
}

// GenerateJavaClient generates the client code to talk to the server
func GenerateJavaClient(banner string, schema *rdl.Schema, outdir string, ns string, base string, options []string) error {
	reg := rdl.NewTypeRegistry(schema)
	packageDir, err := javaGenerationDir(outdir, schema, ns)
	if err != nil {
		return err
	}

	cName := javaGenerationStringOptionSet(options, "clientclass")
	if cName == "" {
		cName = capitalize(string(schema.Name))
	}

	out, file, _, err := outputWriter(packageDir, cName, "Client.java")
	if err != nil {
		return err
	}
	gen := &javaClientGenerator{reg, schema, cName, out, nil, banner, ns, base}
	gen.processTemplate(javaClientTemplate)
	out.Flush()
	file.Close()
	if gen.err != nil {
		return gen.err
	}

	//ResourceException - the throawable wrapper for alternate return types
	out, file, _, err = outputWriter(packageDir, "ResourceException", ".java")
	if err != nil {
		return err
	}
	err = javaGenerateResourceException(schema, out, ns)
	out.Flush()
	file.Close()
	if err != nil {
		return err
	}

	//ResourceError - the default data object for an error
	out, file, _, err = outputWriter(packageDir, "ResourceError", ".java")
	if err != nil {
		return err
	}
	err = javaGenerateResourceError(schema, out, ns)
	out.Flush()
	file.Close()
	return err
}

func (gen *javaClientGenerator) processTemplate(templateSource string) error {
	commentFun := func(s string) string {
		return formatComment(s, 0, 80)
	}
	funcMap := template.FuncMap{
		"header":     func() string { return javaGenerationHeader(gen.banner) },
		"package":    func() string { return javaGenerationPackage(gen.schema, gen.ns) },
		"comment":    commentFun,
		"methodSig":  func(r *rdl.Resource) string { return gen.clientMethodSignature(r) },
		"methodBody": func(r *rdl.Resource) string { return gen.clientMethodBody(r) },
		"name":       func() string { return gen.name },
		"cName":      func() string { return capitalize(gen.name) },
		"lName":      func() string { return uncapitalize(gen.name) },
	}
	t := template.Must(template.New(gen.name).Funcs(funcMap).Parse(templateSource))
	return t.Execute(gen.writer, gen.schema)
}

func (gen *javaClientGenerator) resourcePath(r *rdl.Resource) string {
	path := r.Path
	i := strings.Index(path, "?")
	if i >= 0 {
		path = path[0:i]
	}
	return path
}

const javaClientTemplate = `{{header}}
package {{package}};
import com.yahoo.rdl.*;
import javax.ws.rs.client.*;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.net.ssl.HostnameVerifier;

public class {{cName}}Client {
    Client client;
    WebTarget base;
    String credsHeader;
    String credsToken;

    public {{cName}}Client(String url) {
        client = ClientBuilder.newClient();
        base = client.target(url);
    }

    public {{cName}}Client(String url, HostnameVerifier hostnameVerifier) {
        client = ClientBuilder.newBuilder()
            .hostnameVerifier(hostnameVerifier)
            .build();
        base = client.target(url);
    }

    public void close() {
        client.close();
    }

    public {{cName}}Client setProperty(String name, Object value) {
        client = client.property(name, value);
        return this;
    }

    public {{cName}}Client addCredentials(String header, String token) {
        credsHeader = header;
        credsToken = token;
        return this;
    }
{{range .Resources}}
    {{methodSig .}} {
        {{methodBody .}}
    }
{{end}}
}
`

func (gen *javaClientGenerator) clientMethodSignature(r *rdl.Resource) string {
	reg := gen.registry
	returnType := javaType(reg, r.Type, false, "", "")
	methName, params := javaMethodName(reg, r)
	sparams := ""
	if len(params) > 0 {
		sparams = strings.Join(params, ", ")
	}
	return "public " + returnType + " " + methName + "(" + sparams + ")"
}

func (gen *javaClientGenerator) clientMethodBody(r *rdl.Resource) string {
	reg := gen.registry
	returnType := javaType(reg, r.Type, false, "", "")
	path := r.Path
	s := "WebTarget target = base.path(\"" + path + "\")"
	entityName := ""
	q := ""
	h := ""
	for _, in := range r.Inputs {
		iname := javaName(in.Name)
		if in.PathParam {
			s += "\n            .resolveTemplate(\"" + iname + "\", " + iname + ")"
		} else if in.QueryParam != "" {
			q += "\n        if (" + iname + " != null) {"
			q += "\n            target = target.queryParam(\"" + in.QueryParam + "\", " + iname + ");"
			q += "\n        }"
		} else if in.Header != "" {
			h += "\n        if (" + iname + " != null) {"
			h += "\n            invocationBuilder = invocationBuilder.header(\"" + in.Header + "\", " + iname + ");"
			h += "\n        }"
		} else { //the entity
			entityName = iname
		}
	}
	s += ";"
	if q != "" {
		s += q
	}
	s += "\n        Invocation.Builder invocationBuilder = target.request(\"application/json\");"
	if r.Auth != nil {
		if r.Auth.Authenticate || (r.Auth.Action != "" && r.Auth.Resource != "") {
			s += "\n        if (credsHeader != null) {"
			s += "\n            invocationBuilder = invocationBuilder.header(credsHeader, credsToken);"
			s += "\n        }"
		} else {
			log.Println("*** Badly formed auth spec in resource input:", r)
		}
	}
	if h != "" {
		s += h
	}
	s += "\n"
	switch r.Method {
	case "PUT", "POST":
		s += "        Response response = invocationBuilder." + strings.ToLower(r.Method) + "(javax.ws.rs.client.Entity.entity(" + entityName + ", \"application/json\"));\n"
	default:
		s += "        Response response = invocationBuilder." + strings.ToLower(r.Method) + "();\n"
	}
	s += "        int code = response.getStatus();\n"
	s += "        String status = Response.Status.fromStatusCode(code).toString();\n"
	expected := "status.equals(\"" + r.Expected + "\")"
	for _, alt := range r.Alternatives {
		expected += "|| status.equals(\"" + alt + "\")"
	}
	s += "        if (" + expected + ") {\n"
	s += "            return response.readEntity(" + returnType + ".class);\n"
	if r.Exceptions != nil {
		for k, v := range r.Exceptions {
			s += "        } else if (status.equals(\"" + k + "\")) {\n"
			s += "            throw new ResourceException(code, response.readEntity(" + v.Type + ".class));\n"
		}
	}
	s += "        } else {\n"
	s += "            throw new ResourceException(code, response.readEntity(Object.class));\n"
	s += "        }\n"
	return s
}
<commit_msg>One possible solution to issue #40<commit_after>// Copyright 2015 Yahoo Inc.
// Licensed under the terms of the Apache version 2.0 license. See LICENSE file for terms.

package main

import (
	"bufio"
	"github.com/ardielle/ardielle-go/rdl"
	"log"
	"strings"
	"text/template"
)

type javaClientGenerator struct {
	registry rdl.TypeRegistry
	schema   *rdl.Schema
	name     string
	writer   *bufio.Writer
	err      error
	banner   string
	ns       string
	base     string
}

// GenerateJavaClient generates the client code to talk to the server
func GenerateJavaClient(banner string, schema *rdl.Schema, outdir string, ns string, base string, options []string) error {
	reg := rdl.NewTypeRegistry(schema)
	packageDir, err := javaGenerationDir(outdir, schema, ns)
	if err != nil {
		return err
	}

	cName := javaGenerationStringOptionSet(options, "clientclass")
	if cName == "" {
		cName = capitalize(string(schema.Name))
	}

	out, file, _, err := outputWriter(packageDir, cName, "Client.java")
	if err != nil {
		return err
	}
	gen := &javaClientGenerator{reg, schema, cName, out, nil, banner, ns, base}
	gen.processTemplate(javaClientTemplate)
	out.Flush()
	file.Close()
	if gen.err != nil {
		return gen.err
	}

	//ResourceException - the throawable wrapper for alternate return types
	out, file, _, err = outputWriter(packageDir, "ResourceException", ".java")
	if err != nil {
		return err
	}
	err = javaGenerateResourceException(schema, out, ns)
	out.Flush()
	file.Close()
	if err != nil {
		return err
	}

	//ResourceError - the default data object for an error
	out, file, _, err = outputWriter(packageDir, "ResourceError", ".java")
	if err != nil {
		return err
	}
	err = javaGenerateResourceError(schema, out, ns)
	out.Flush()
	file.Close()
	return err
}

func (gen *javaClientGenerator) processTemplate(templateSource string) error {
	commentFun := func(s string) string {
		return formatComment(s, 0, 80)
	}
	funcMap := template.FuncMap{
		"header":     func() string { return javaGenerationHeader(gen.banner) },
		"package":    func() string { return javaGenerationPackage(gen.schema, gen.ns) },
		"comment":    commentFun,
		"methodSig":  func(r *rdl.Resource) string { return gen.clientMethodSignature(r) },
		"methodBody": func(r *rdl.Resource) string { return gen.clientMethodBody(r) },
		"name":       func() string { return gen.name },
		"cName":      func() string { return capitalize(gen.name) },
		"lName":      func() string { return uncapitalize(gen.name) },
	}
	t := template.Must(template.New(gen.name).Funcs(funcMap).Parse(templateSource))
	return t.Execute(gen.writer, gen.schema)
}

func (gen *javaClientGenerator) resourcePath(r *rdl.Resource) string {
	path := r.Path
	i := strings.Index(path, "?")
	if i >= 0 {
		path = path[0:i]
	}
	return path
}

const javaClientTemplate = `{{header}}
package {{package}};
import com.yahoo.rdl.*;
import javax.ws.rs.client.*;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.net.ssl.HostnameVerifier;
import java.util.Arrays;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;

public class {{cName}}Client {
    Client client;
    WebTarget base;
    String credsHeader;
    String credsToken;

    public {{cName}}Client(String url) {
        client = ClientBuilder.newClient();
        base = client.target(url);
    }

    public {{cName}}Client(String url, HostnameVerifier hostnameVerifier) {
        client = ClientBuilder.newBuilder()
            .hostnameVerifier(hostnameVerifier)
            .build();
        base = client.target(url);
    }

    public void close() {
        client.close();
    }

    public {{cName}}Client setProperty(String name, Object value) {
        client = client.property(name, value);
        return this;
    }

    public {{cName}}Client addCredentials(String header, String token) {
        credsHeader = header;
        credsToken = token;
        return this;
    }
{{range .Resources}}
    {{methodSig .}} {
        {{methodBody .}}
    }
{{end}}
}
`

func (gen *javaClientGenerator) clientMethodSignature(r *rdl.Resource) string {
	reg := gen.registry
	returnType := javaType(reg, r.Type, false, "", "")
	methName, params := javaMethodName(reg, r)
	sparams := ""
	if len(params) > 0 {
		sparams = strings.Join(params, ", ")
	}
	if len(r.Outputs) > 0 {
		if sparams == "" {
			sparams = "Map<String,List<String>> headers"
		} else {
			sparams = sparams + ", Map<String,List<String>> headers"
		}
	}
	return "public " + returnType + " " + methName + "(" + sparams + ")"
}

func (gen *javaClientGenerator) clientMethodBody(r *rdl.Resource) string {
	reg := gen.registry
	returnType := javaType(reg, r.Type, false, "", "")
	path := r.Path
	s := "WebTarget target = base.path(\"" + path + "\")"
	entityName := ""
	q := ""
	h := ""
	for _, in := range r.Inputs {
		iname := javaName(in.Name)
		if in.PathParam {
			s += "\n            .resolveTemplate(\"" + iname + "\", " + iname + ")"
		} else if in.QueryParam != "" {
			q += "\n        if (" + iname + " != null) {"
			q += "\n            target = target.queryParam(\"" + in.QueryParam + "\", " + iname + ");"
			q += "\n        }"
		} else if in.Header != "" {
			h += "\n        if (" + iname + " != null) {"
			h += "\n            invocationBuilder = invocationBuilder.header(\"" + in.Header + "\", " + iname + ");"
			h += "\n        }"
		} else { //the entity
			entityName = iname
		}
	}
	s += ";"
	if q != "" {
		s += q
	}
	s += "\n        Invocation.Builder invocationBuilder = target.request(\"application/json\");"
	if r.Auth != nil {
		if r.Auth.Authenticate || (r.Auth.Action != "" && r.Auth.Resource != "") {
			s += "\n        if (credsHeader != null) {"
			s += "\n            invocationBuilder = invocationBuilder.header(credsHeader, credsToken);"
			s += "\n        }"
		} else {
			log.Println("*** Badly formed auth spec in resource input:", r)
		}
	}
	if h != "" {
		s += h
	}
	s += "\n"
	switch r.Method {
	case "PUT", "POST":
		s += "        Response response = invocationBuilder." + strings.ToLower(r.Method) + "(javax.ws.rs.client.Entity.entity(" + entityName + ", \"application/json\"));\n"
	default:
		s += "        Response response = invocationBuilder." + strings.ToLower(r.Method) + "();\n"
	}
	s += "        int code = response.getStatus();\n"
	s += "        String status = Response.Status.fromStatusCode(code).toString();\n"
	expected := "status.equals(\"" + r.Expected + "\")"
	for _, alt := range r.Alternatives {
		expected += "|| status.equals(\"" + alt + "\")"
	}
	s += "        if (" + expected + ") {\n"
	if len(r.Outputs) > 0 {
		s += "            if (headers != null) {\n"
		for _, out := range r.Outputs {
			s += "                headers.put(\"" + string(out.Name) + "\", Arrays.asList((String)response.getHeaders().getFirst(\"" + out.Header + "\")));\n"
		}
		s += "            }\n"
	}

	s += "            return response.readEntity(" + returnType + ".class);\n"
	if r.Exceptions != nil {
		for k, v := range r.Exceptions {
			s += "        } else if (status.equals(\"" + k + "\")) {\n"
			s += "            throw new ResourceException(code, response.readEntity(" + v.Type + ".class));\n"
		}
	}
	s += "        } else {\n"
	s += "            throw new ResourceException(code, response.readEntity(Object.class));\n"
	s += "        }\n"
	return s
}
<|endoftext|>


<commit_before>// Copyright 2016 The prometheus-operator Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e

import (
	"encoding/json"
	"fmt"
	"k8s.io/client-go/pkg/api/v1"
	"net/http"
	"testing"
	"time"
)

func TestAlertmanagerCreateDeleteCluster(t *testing.T) {
	name := "test"

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(name); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.CreateAlertmanagerAndWaitUntilReady(framework.MakeBasicAlertmanager(name, 3)); err != nil {
		t.Fatal(err)
	}
}

func TestAlertmanagerScaling(t *testing.T) {
	name := "test"

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(name); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.CreateAlertmanagerAndWaitUntilReady(framework.MakeBasicAlertmanager(name, 3)); err != nil {
		t.Fatal(err)
	}

	if err := framework.UpdateAlertmanagerAndWaitUntilReady(framework.MakeBasicAlertmanager(name, 5)); err != nil {
		t.Fatal(err)
	}

	if err := framework.UpdateAlertmanagerAndWaitUntilReady(framework.MakeBasicAlertmanager(name, 3)); err != nil {
		t.Fatal(err)
	}
}

func TestAlertmanagerVersionMigration(t *testing.T) {
	name := "test"

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(name); err != nil {
			t.Fatal(err)
		}
	}()

	am := framework.MakeBasicAlertmanager(name, 3)
	am.Spec.Version = "v0.5.0"
	if err := framework.CreateAlertmanagerAndWaitUntilReady(am); err != nil {
		t.Fatal(err)
	}

	am.Spec.Version = "v0.5.1"
	if err := framework.UpdateAlertmanagerAndWaitUntilReady(am); err != nil {
		t.Fatal(err)
	}

	am.Spec.Version = "v0.5.0"
	if err := framework.UpdateAlertmanagerAndWaitUntilReady(am); err != nil {
		t.Fatal(err)
	}
}

func TestExposingAlertmanagerWithNodePort(t *testing.T) {
	alertmanager := framework.MakeBasicAlertmanager("test-alertmanager", 1)
	alertmanagerService := framework.MakeAlertmanagerNodePortService(alertmanager.Name, "nodeport-service", 30903)

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(alertmanager.Name); err != nil {
			t.Fatal(err)
		}
		if err := framework.DeleteService(alertmanagerService.Name); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.CreateAlertmanagerAndWaitUntilReady(alertmanager); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateServiceAndWaitUntilReady(alertmanagerService); err != nil {
		t.Fatal(err)
	}

	resp, err := http.Get(fmt.Sprintf("http://%s:30903/", framework.ClusterIP))
	if err != nil {
		t.Fatal("Retrieving alertmanager landing page failed with error: ", err)
	} else if resp.StatusCode != 200 {
		t.Fatal("Retrieving alertmanager landing page failed with http status code: ", resp.StatusCode)
	}
}

func TestExposingAlertmanagerWithKubernetesAPI(t *testing.T) {
	alertmanager := framework.MakeBasicAlertmanager("test-alertmanager", 1)
	alertmanagerService := framework.MakeAlertmanagerService(alertmanager.Name, "alertmanager-service", v1.ServiceTypeClusterIP)

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(alertmanager.Name); err != nil {
			t.Fatal(err)
		}
		if err := framework.DeleteService(alertmanagerService.Name); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.CreateAlertmanagerAndWaitUntilReady(alertmanager); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateServiceAndWaitUntilReady(alertmanagerService); err != nil {
		t.Fatal(err)
	}

	proxyGet := framework.KubeClient.CoreV1().Services(framework.Namespace.Name).ProxyGet
	request := proxyGet("", alertmanagerService.Name, "web", "/", make(map[string]string))
	_, err := request.DoRaw()
	if err != nil {
		t.Fatal(err)
	}
}

func TestExposingAlertmanagerWithIngress(t *testing.T) {
	alertmanager := framework.MakeBasicAlertmanager("main", 1)
	alertmanagerService := framework.MakeAlertmanagerService(alertmanager.Name, "test-group", v1.ServiceTypeClusterIP)
	ingress := framework.MakeBasicIngress(alertmanagerService.Name, 9093)

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(alertmanager.Name); err != nil {
			t.Fatal(err)
		}
		if err := framework.DeleteService(alertmanagerService.Name); err != nil {
			t.Fatal(err)
		}
		if err := framework.KubeClient.Extensions().Ingresses(framework.Namespace.Name).Delete(ingress.Name, nil); err != nil {
			t.Fatal(err)
		}
		if err := framework.DeleteNginxIngressControllerIncDefaultBackend(); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.SetupNginxIngressControllerIncDefaultBackend(); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateAlertmanagerAndWaitUntilReady(alertmanager); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateServiceAndWaitUntilReady(alertmanagerService); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateIngress(ingress); err != nil {
		t.Fatal(err)
	}

	ip, err := framework.GetIngressIP(ingress.Name)
	if err != nil {
		t.Fatal(err)
	}

	err = framework.WaitForHTTPSuccessStatusCode(time.Second*30, fmt.Sprintf("http://%s/metrics", *ip))
	if err != nil {
		t.Fatal(err)
	}
}

func TestMeshInitialization(t *testing.T) {
	var amountAlertmanagers int32 = 3
	alertmanager := framework.MakeBasicAlertmanager("test-alertmanager", amountAlertmanagers)
	alertmanagerService := framework.MakeAlertmanagerService(alertmanager.Name, "alertmanager-service", v1.ServiceTypeClusterIP)

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(alertmanager.Name); err != nil {
			t.Fatal(err)
		}
		if err := framework.DeleteService(alertmanagerService.Name); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.CreateAlertmanagerAndWaitUntilReady(alertmanager); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateServiceAndWaitUntilReady(alertmanagerService); err != nil {
		t.Fatal(err)
	}

	proxyGet := framework.KubeClient.CoreV1().Services(framework.Namespace.Name).ProxyGet
	err := framework.Poll(time.Second*20, time.Second, func() (bool, error) {
		request := proxyGet("", alertmanagerService.Name, "web", "/api/v1/status", make(map[string]string))
		resp, err := request.DoRaw()
		if err != nil {
			return false, err
		}

		var amStatus alertmanagerStatus
		if err := json.Unmarshal(resp, &amStatus); err != nil {
			return false, err
		}

		if len(amStatus.Data.MeshStatus.Peers) == int(amountAlertmanagers) {
			return true, nil
		}

		return false, nil
	})

	if err != nil {
		t.Fatal(err)
	}
}

type alertmanagerStatus struct {
	Data alertmanagerStatusData `json:"data"`
}

type alertmanagerStatusData struct {
	MeshStatus meshStatus `json:"meshStatus"`
}

type meshStatus struct {
	Peers []interface{} `json:"peers"`
}
<commit_msg>test: Use alertmanager:master with new /meshStatus api endpoint<commit_after>// Copyright 2016 The prometheus-operator Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package e2e

import (
	"encoding/json"
	"fmt"
	"github.com/coreos/prometheus-operator/pkg/client/monitoring/v1alpha1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/pkg/api/v1"
	"net/http"
	"testing"
	"time"
)

func TestAlertmanagerCreateDeleteCluster(t *testing.T) {
	name := "test"

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(name); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.CreateAlertmanagerAndWaitUntilReady(framework.MakeBasicAlertmanager(name, 3)); err != nil {
		t.Fatal(err)
	}
}

func TestAlertmanagerScaling(t *testing.T) {
	name := "test"

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(name); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.CreateAlertmanagerAndWaitUntilReady(framework.MakeBasicAlertmanager(name, 3)); err != nil {
		t.Fatal(err)
	}

	if err := framework.UpdateAlertmanagerAndWaitUntilReady(framework.MakeBasicAlertmanager(name, 5)); err != nil {
		t.Fatal(err)
	}

	if err := framework.UpdateAlertmanagerAndWaitUntilReady(framework.MakeBasicAlertmanager(name, 3)); err != nil {
		t.Fatal(err)
	}
}

func TestAlertmanagerVersionMigration(t *testing.T) {
	name := "test"

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(name); err != nil {
			t.Fatal(err)
		}
	}()

	am := framework.MakeBasicAlertmanager(name, 3)
	am.Spec.Version = "v0.5.0"
	if err := framework.CreateAlertmanagerAndWaitUntilReady(am); err != nil {
		t.Fatal(err)
	}

	am.Spec.Version = "v0.5.1"
	if err := framework.UpdateAlertmanagerAndWaitUntilReady(am); err != nil {
		t.Fatal(err)
	}

	am.Spec.Version = "v0.5.0"
	if err := framework.UpdateAlertmanagerAndWaitUntilReady(am); err != nil {
		t.Fatal(err)
	}
}

func TestExposingAlertmanagerWithNodePort(t *testing.T) {
	alertmanager := framework.MakeBasicAlertmanager("test-alertmanager", 1)
	alertmanagerService := framework.MakeAlertmanagerNodePortService(alertmanager.Name, "nodeport-service", 30903)

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(alertmanager.Name); err != nil {
			t.Fatal(err)
		}
		if err := framework.DeleteService(alertmanagerService.Name); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.CreateAlertmanagerAndWaitUntilReady(alertmanager); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateServiceAndWaitUntilReady(alertmanagerService); err != nil {
		t.Fatal(err)
	}

	resp, err := http.Get(fmt.Sprintf("http://%s:30903/", framework.ClusterIP))
	if err != nil {
		t.Fatal("Retrieving alertmanager landing page failed with error: ", err)
	} else if resp.StatusCode != 200 {
		t.Fatal("Retrieving alertmanager landing page failed with http status code: ", resp.StatusCode)
	}
}

func TestExposingAlertmanagerWithKubernetesAPI(t *testing.T) {
	alertmanager := framework.MakeBasicAlertmanager("test-alertmanager", 1)
	alertmanagerService := framework.MakeAlertmanagerService(alertmanager.Name, "alertmanager-service", v1.ServiceTypeClusterIP)

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(alertmanager.Name); err != nil {
			t.Fatal(err)
		}
		if err := framework.DeleteService(alertmanagerService.Name); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.CreateAlertmanagerAndWaitUntilReady(alertmanager); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateServiceAndWaitUntilReady(alertmanagerService); err != nil {
		t.Fatal(err)
	}

	proxyGet := framework.KubeClient.CoreV1().Services(framework.Namespace.Name).ProxyGet
	request := proxyGet("", alertmanagerService.Name, "web", "/", make(map[string]string))
	_, err := request.DoRaw()
	if err != nil {
		t.Fatal(err)
	}
}

func TestExposingAlertmanagerWithIngress(t *testing.T) {
	alertmanager := framework.MakeBasicAlertmanager("main", 1)
	alertmanagerService := framework.MakeAlertmanagerService(alertmanager.Name, "test-group", v1.ServiceTypeClusterIP)
	ingress := framework.MakeBasicIngress(alertmanagerService.Name, 9093)

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(alertmanager.Name); err != nil {
			t.Fatal(err)
		}
		if err := framework.DeleteService(alertmanagerService.Name); err != nil {
			t.Fatal(err)
		}
		if err := framework.KubeClient.Extensions().Ingresses(framework.Namespace.Name).Delete(ingress.Name, nil); err != nil {
			t.Fatal(err)
		}
		if err := framework.DeleteNginxIngressControllerIncDefaultBackend(); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.SetupNginxIngressControllerIncDefaultBackend(); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateAlertmanagerAndWaitUntilReady(alertmanager); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateServiceAndWaitUntilReady(alertmanagerService); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateIngress(ingress); err != nil {
		t.Fatal(err)
	}

	ip, err := framework.GetIngressIP(ingress.Name)
	if err != nil {
		t.Fatal(err)
	}

	err = framework.WaitForHTTPSuccessStatusCode(time.Second*30, fmt.Sprintf("http://%s/metrics", *ip))
	if err != nil {
		t.Fatal(err)
	}
}

func TestMeshInitialization(t *testing.T) {
	var amountAlertmanagers int32 = 3
	alertmanager := &v1alpha1.Alertmanager{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test-alertmanager",
		},
		Spec: v1alpha1.AlertmanagerSpec{
			Replicas: &amountAlertmanagers,
			Version:  "master",
		},
	}

	alertmanagerService := framework.MakeAlertmanagerService(alertmanager.Name, "alertmanager-service", v1.ServiceTypeClusterIP)

	defer func() {
		if err := framework.DeleteAlertmanagerAndWaitUntilGone(alertmanager.Name); err != nil {
			t.Fatal(err)
		}
		if err := framework.DeleteService(alertmanagerService.Name); err != nil {
			t.Fatal(err)
		}
	}()

	if err := framework.CreateAlertmanagerAndWaitUntilReady(alertmanager); err != nil {
		t.Fatal(err)
	}

	if err := framework.CreateServiceAndWaitUntilReady(alertmanagerService); err != nil {
		t.Fatal(err)
	}

	proxyGet := framework.KubeClient.CoreV1().Services(framework.Namespace.Name).ProxyGet
	err := framework.Poll(time.Second*20, time.Second, func() (bool, error) {
		request := proxyGet("", alertmanagerService.Name, "web", "/api/v1/status", make(map[string]string))
		resp, err := request.DoRaw()
		if err != nil {
			return false, err
		}

		var amStatus alertmanagerStatus
		if err := json.Unmarshal(resp, &amStatus); err != nil {
			return false, err
		}

		if len(amStatus.Data.MeshStatus.Peers) == int(amountAlertmanagers) {
			return true, nil
		}

		return false, nil
	})

	if err != nil {
		t.Fatal(err)
	}
}

type alertmanagerStatus struct {
	Data alertmanagerStatusData `json:"data"`
}

type alertmanagerStatusData struct {
	MeshStatus meshStatus `json:"meshStatus"`
}

type meshStatus struct {
	Peers []interface{} `json:"peers"`
}
<|endoftext|>


<commit_before>// Copyright 2010 GoDCCP Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

package ccid3

import (
	//"os"
	"math"
	//"github.com/petar/GoDCCP/dccp"
)

// rateCaclulator computers the allowed sending rate of the sender
type rateCalculator struct {
	x         uint32 // Current allowed sending rate, in bytes per second
	tld       int64  // Time Last Doubled (during slow start) or zero if unset; in ns since UTC zero
	recvLimit uint32 // Receive limit, in bytes per second
	xRecvSet         // Data structure for x_recv_set (see RFC 5348)
}

const (
	X_MAX_INIT_WIN          = 4380           // Maximum size of initial window in bytes
	X_MAX_BACKOFF_INTERVAL  = 64e9           // Maximum backoff interval in ns (See RFC 5348, Section 4.3)
	X_RECV_MAX              = math.MaxInt32  // Maximum receive rate, in bytes per second
	X_RECV_SET_SIZE         = 3              // Size of x_recv_set
)

// Init resets the rate calculator for new use and returns the initial
// allowed sending rate (in bytes per second). The latter is the rate
// to be used before the first feedback packet is received and hence before
// an RTT estimate is available.
func (t *rateCalculator) Init(now int64, ss uint32) {
	// The allowed sending rate before the first feedback packet is received
	// is one packet per second.
	t.x = ss
	// tld = 0 indicates that the first feedback packet has yet not been received.
	t.tld = 0
	// Because X_recv_set is initialized with a single item, with value Infinity, recvLimit is
	// set to Infinity for the first two round-trip times of the connection.  As a result, the
	// sending rate is not limited by the receive rate during that period.  This avoids the
	// problem of the sending rate being limited by the value of X_recv from the first feedback
	// packet.
	t.recvLimit = X_RECV_MAX
	t.xRecvSet.Init()
}

// onFirstRead is called internally to handle the very first feedback packet received.
func (t *rateCalculator) onFirstRead(now int64, ss uint32, rtt int64) uint32 {
	t.tld = now
	t.x = initRate(ss, rtt)
	return t.x
}

// initRate returns the allowed initial sending rate in bytes per second.
func initRate(ss uint32, rtt int64) uint32 {
	if ss <= 0 || rtt <= 0 {
		panic("unknown SS or RTT")
	}
	win := minu32(4*ss, maxu32(2*ss, X_MAX_INIT_WIN)) // window = bytes per round trip (bpr)
	return uint32(max64((1e9*int64(win)) / rtt, 1))
}

// XFeedback contains computed feedback variables that are used by the rate calculator to update the
// allowed sending rate
type XFeedback struct {
	Now   int64   // Time now
	SS    uint32  // Segment size
	XRecv uint32  // Receive rate
	RTT   int64   // Round-trip time
	LossFeedback  // Loss-related feedback
}

// Sender calls OnRead each time a new feedback packet arrives.
// OnRead returns the new allowed sending in bytes per second.
// x_recv is given in bytes per second.
// lossRateInv equals zero if the loss rate is still unknown.
func (t *rateCalculator) OnRead(f XFeedback) uint32 {
	if f.LossFeedback.RateInv < 1 {
		panic("invalid loss rate inverse")
	}
	if t.tld <= 0 {
		return t.onFirstRead(f.Now, f.SS, f.RTT)
	}
	if /* the entire interval covered by the feedback packet was a data-limited interval */ {
		if f.LossFeedback.RateInc || f.LossFeedback.NewLossCount > 0 {
			t.xRecvSet.Halve()
			f.XRecv = (85 * f.XRecv) / 100
			t.xRecvSet.Maximize(f.Now, f.XRecv)
			t.recvLimit = t.xRecvSet.Max()
		} else {
			t.xRecvSet.Maximize(f.Now, f.XRecv)
			t.recvLimit = 2 * t.xRecvSet.Max()
		}
	} else {
		t.xRecvSet.Update(f.Now, f.XRecv, f.RTT)
		t.recvLimit = 2 * t.xRecvSet.Max()
	}
	// Are we in the post-slow start phase
	if f.LossFeedback.RateInv < UnknownLossEventRateInv {
		xEq := t.thruEq(f.SS, f.RTT, f.LossFeedback.RateInv)
		t.x = maxu32(
			minu32(xEq, t.recvLimit),
			(1e9 * f.SS) / X_MAX_BACKOFF_INTERVAL
		)
	} else if f.Now - t.tld >= f.RTT {
		// Initial slow-start
		t.x = max(min(2*t.x, recvLimit), initRate(f.SS, f.RTT))
		t.tld = f.Now
	}
	// TODO: Place oscillation reduction code here (see RFC 5348, Section 4.3)
	return t.x
}

// thruEq returns the allowed sending rate, in bytes per second, according to the TCP
// throughput equation, for the regime b=1 and t_RTO=4*RTT (See RFC 5348, Section 3.1).
func (t *rateCalculator) thruEq(ss uint32, rtt int64, lossRateInv uint32) uint32 {
	bps := (1e3*1e9*int64(ss)) / (rtt * thruEqQ(lossRateInv))
	return uint32(bps)
}

// thruEqDenom computes the quantity 1e3*(sqrt(2*p/3) + 12*sqrt(3*p/8)*p*(1+32*p^2)).
func thruEqQ(lossRateInv uint32) int64 {
	j := min(int(lossRateInv), len(qTable))
	return qTable[j-1].Q
}

// —————
// xRecvSet maintains a set of recently received Receive Rates (via ReceiveRateOption)
type xRecvSet struct {
	set [X_RECV_SET_SIZE]xRecvEntry  // Set of recent rates
}

type xRecvEntry struct {
	Rate uint32   // Receive rate; in bytes per second
	Time int64    // Entry timestamp or zero if unset; in ns since UTC zero
}

// Init resets the xRecvSet object for new use
func (t *xRecvSet) Init() {
	for i, _ := range t.set {
		t.set[i] = xRecvEntry{}
	}
}

// Halve halves all the rates in the set
func (t *xRecvSet) Halve() {
	for i, _ := range t.set {
		t.set[i].Rate /= 2
	}
}

// Max returns the highest rate in the set; in bytes per second
func (t *xRecvSet) Max() uint32 {
	var set bool
	var r uint32
	for _, e := range t.set {
		if e.Time <= 0 {
			continue
		}
		if e.Rate > r {
			r = e.Rate
			set = true
		}
	}
	if !set {
		return X_RECV_MAX
	}
	return r
}

// The procedure for maximizing X_recv_set keeps a single value, the
// largest value from X_recv_set and the new X_recv.
//
//   Maximize X_recv_set():
//     Add X_recv to X_recv_set;
//     Delete initial value Infinity from X_recv_set, if it is still a member.
//     Set the timestamp of the largest item to the current time;
//     Delete all other items.
//
func (t *xRecvSet) Maximize(now int64, xRecvBPS uint32) {
	for i, e := range t.set {
		if e.Time > 0 {
			xRecvBPS = maxu32(xRecvBPS, e.Rate)
		}
		t.set[i] = xRecvEntry{}
	}
	t.set[0].Time = now
	t.set[0].Rate = xRecvBPS
}

// The procedure for updating X_recv_set keeps a set of X_recv values
// with timestamps from the two most recent round-trip times.
//
//   Update X_recv_set():
//     Add X_recv to X_recv_set;
//     Delete from X_recv_set values older than two round-trip times.
//
func (t *xRecvSet) Update(now int64, xRecvBPS uint32, rtt int64) {
	// Remove entries older than two RTT
	for i, e := range t.set {
		if e.Time > 0 && now - e.Time > 2*rtt {
			t.set[i] = xRecvEntry{}
		}
	}
	// Find free cell or oldest entry
	var j int = -1
	var j_time int64 = now
	for i, e := range t.set {
		if e.Time <= 0 {
			j = i
			break
		}
		if e.Time <= j_time {
			j = i
			j_time = e.Time
		}
	}
	t.set[j].Rate = xRecvBPS
	t.set[j].Time = now
}
<commit_msg>cosmetic<commit_after>// Copyright 2010 GoDCCP Authors. All rights reserved.
// Use of this source code is governed by a
// license that can be found in the LICENSE file.

package ccid3

import (
	//"os"
	"math"
	//"github.com/petar/GoDCCP/dccp"
)

// rateCaclulator computers the allowed sending rate of the sender
type rateCalculator struct {
	x         uint32 // Current allowed sending rate, in bytes per second
	tld       int64  // Time Last Doubled (during slow start) or zero if unset; in ns since UTC zero
	recvLimit uint32 // Receive limit, in bytes per second
	xRecvSet         // Data structure for x_recv_set (see RFC 5348)
}

const (
	X_MAX_INIT_WIN          = 4380           // Maximum size of initial window in bytes
	X_MAX_BACKOFF_INTERVAL  = 64e9           // Maximum backoff interval in ns (See RFC 5348, Section 4.3)
	X_RECV_MAX              = math.MaxInt32  // Maximum receive rate, in bytes per second
	X_RECV_SET_SIZE         = 3              // Size of x_recv_set
)

// Init resets the rate calculator for new use and returns the initial
// allowed sending rate (in bytes per second). The latter is the rate
// to be used before the first feedback packet is received and hence before
// an RTT estimate is available.
func (t *rateCalculator) Init(now int64, ss uint32) {
	// The allowed sending rate before the first feedback packet is received
	// is one packet per second.
	t.x = ss
	// tld = 0 indicates that the first feedback packet has yet not been received.
	t.tld = 0
	// Because X_recv_set is initialized with a single item, with value Infinity, recvLimit is
	// set to Infinity for the first two round-trip times of the connection.  As a result, the
	// sending rate is not limited by the receive rate during that period.  This avoids the
	// problem of the sending rate being limited by the value of X_recv from the first feedback
	// packet.
	t.recvLimit = X_RECV_MAX
	t.xRecvSet.Init()
}

// onFirstRead is called internally to handle the very first feedback packet received.
func (t *rateCalculator) onFirstRead(now int64, ss uint32, rtt int64) uint32 {
	t.tld = now
	t.x = initRate(ss, rtt)
	return t.x
}

// initRate returns the allowed initial sending rate in bytes per second.
func initRate(ss uint32, rtt int64) uint32 {
	if ss <= 0 || rtt <= 0 {
		panic("unknown SS or RTT")
	}
	win := minu32(4*ss, maxu32(2*ss, X_MAX_INIT_WIN)) // window = bytes per round trip (bpr)
	return uint32(max64((1e9*int64(win)) / rtt, 1))
}

// XFeedback contains computed feedback variables that are used by the rate calculator to update the
// allowed sending rate
type XFeedback struct {
	Now   int64   // Time now
	SS    uint32  // Segment size
	XRecv uint32  // Receive rate
	RTT   int64   // Round-trip time
	LossFeedback  // Loss-related feedback
}

// Sender calls OnRead each time a new feedback packet arrives.
// OnRead returns the new allowed sending in bytes per second.
func (t *rateCalculator) OnRead(f XFeedback) uint32 {
	if f.LossFeedback.RateInv < 1 {
		panic("invalid loss rate inverse")
	}
	if t.tld <= 0 {
		return t.onFirstRead(f.Now, f.SS, f.RTT)
	}
	if /* the entire interval covered by the feedback packet was a data-limited interval */ {
		if f.LossFeedback.RateInc || f.LossFeedback.NewLossCount > 0 {
			t.xRecvSet.Halve()
			f.XRecv = (85 * f.XRecv) / 100
			t.xRecvSet.Maximize(f.Now, f.XRecv)
			t.recvLimit = t.xRecvSet.Max()
		} else {
			t.xRecvSet.Maximize(f.Now, f.XRecv)
			t.recvLimit = 2 * t.xRecvSet.Max()
		}
	} else {
		t.xRecvSet.Update(f.Now, f.XRecv, f.RTT)
		t.recvLimit = 2 * t.xRecvSet.Max()
	}
	// Are we in the post-slow start phase
	if f.LossFeedback.RateInv < UnknownLossEventRateInv {
		xEq := t.thruEq(f.SS, f.RTT, f.LossFeedback.RateInv)
		t.x = maxu32(
			minu32(xEq, t.recvLimit),
			(1e9 * f.SS) / X_MAX_BACKOFF_INTERVAL
		)
	} else if f.Now - t.tld >= f.RTT {
		// Initial slow-start
		t.x = max(min(2*t.x, recvLimit), initRate(f.SS, f.RTT))
		t.tld = f.Now
	}
	// TODO: Place oscillation reduction code here (see RFC 5348, Section 4.3)
	return t.x
}

// thruEq returns the allowed sending rate, in bytes per second, according to the TCP
// throughput equation, for the regime b=1 and t_RTO=4*RTT (See RFC 5348, Section 3.1).
func (t *rateCalculator) thruEq(ss uint32, rtt int64, lossRateInv uint32) uint32 {
	bps := (1e3*1e9*int64(ss)) / (rtt * thruEqQ(lossRateInv))
	return uint32(bps)
}

// thruEqDenom computes the quantity 1e3*(sqrt(2*p/3) + 12*sqrt(3*p/8)*p*(1+32*p^2)).
func thruEqQ(lossRateInv uint32) int64 {
	j := min(int(lossRateInv), len(qTable))
	return qTable[j-1].Q
}

// —————
// xRecvSet maintains a set of recently received Receive Rates (via ReceiveRateOption)
type xRecvSet struct {
	set [X_RECV_SET_SIZE]xRecvEntry  // Set of recent rates
}

type xRecvEntry struct {
	Rate uint32   // Receive rate; in bytes per second
	Time int64    // Entry timestamp or zero if unset; in ns since UTC zero
}

// Init resets the xRecvSet object for new use
func (t *xRecvSet) Init() {
	for i, _ := range t.set {
		t.set[i] = xRecvEntry{}
	}
}

// Halve halves all the rates in the set
func (t *xRecvSet) Halve() {
	for i, _ := range t.set {
		t.set[i].Rate /= 2
	}
}

// Max returns the highest rate in the set; in bytes per second
func (t *xRecvSet) Max() uint32 {
	var set bool
	var r uint32
	for _, e := range t.set {
		if e.Time <= 0 {
			continue
		}
		if e.Rate > r {
			r = e.Rate
			set = true
		}
	}
	if !set {
		return X_RECV_MAX
	}
	return r
}

// The procedure for maximizing X_recv_set keeps a single value, the
// largest value from X_recv_set and the new X_recv.
//
//   Maximize X_recv_set():
//     Add X_recv to X_recv_set;
//     Delete initial value Infinity from X_recv_set, if it is still a member.
//     Set the timestamp of the largest item to the current time;
//     Delete all other items.
//
func (t *xRecvSet) Maximize(now int64, xRecvBPS uint32) {
	for i, e := range t.set {
		if e.Time > 0 {
			xRecvBPS = maxu32(xRecvBPS, e.Rate)
		}
		t.set[i] = xRecvEntry{}
	}
	t.set[0].Time = now
	t.set[0].Rate = xRecvBPS
}

// The procedure for updating X_recv_set keeps a set of X_recv values
// with timestamps from the two most recent round-trip times.
//
//   Update X_recv_set():
//     Add X_recv to X_recv_set;
//     Delete from X_recv_set values older than two round-trip times.
//
func (t *xRecvSet) Update(now int64, xRecvBPS uint32, rtt int64) {
	// Remove entries older than two RTT
	for i, e := range t.set {
		if e.Time > 0 && now - e.Time > 2*rtt {
			t.set[i] = xRecvEntry{}
		}
	}
	// Find free cell or oldest entry
	var j int = -1
	var j_time int64 = now
	for i, e := range t.set {
		if e.Time <= 0 {
			j = i
			break
		}
		if e.Time <= j_time {
			j = i
			j_time = e.Time
		}
	}
	t.set[j].Rate = xRecvBPS
	t.set[j].Time = now
}
<|endoftext|>


<commit_before>/*
 * (c) 2014, Caoimhe Chaos <caoimhechaos@protonmail.com>,
 *	     Starship Factory. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the name  of the Starship Factory  nor the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package file

import (
	"io"
	"net/url"
	"os"

	"code.google.com/p/go.exp/fsnotify"
	"github.com/caoimhechaos/go-file"
)

// Object for generating watchers for individual files.
type FileWatcherCreator struct {
}

// Create a new watcher object for watching for notifications on the
// given URL.
func (f *FileWatcherCreator) Watch(
	fileid *url.URL, cb func(string, io.ReadCloser)) (
	file.Watcher, error) {
	return NewFileWatcher(fileid.Path, cb)
}

// Object for watching an individual file for changes.
type FileWatcher struct {
	cb       func(string, io.ReadCloser)
	watcher  *fsnotify.Watcher
	path     string
	shutdown bool
}

// Automatically sign us up for file:// URLs.
func init() {
	file.RegisterWatcher("file", &FileWatcherCreator{})
}

// Create a new FileWatcher watching the file at "path".
func NewFileWatcher(path string, cb func(string, io.ReadCloser)) (
	*FileWatcher, error) {
	var ret *FileWatcher
	var watcher *fsnotify.Watcher
	var err error

	watcher, err = fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}

	err = watcher.Watch(path)
	if err != nil {
		return nil, err
	}

	ret = &FileWatcher{
		cb:      cb,
		watcher: watcher,
		path:    path,
	}

	return ret, nil
}

// Read events happening on the file being watched and forward them
// to the relevant callback.
func (f *FileWatcher) watchForChanges() {
	for !f.shutdown {
		var event *fsnotify.FileEvent

		event = <-f.watcher.Event

		if event.IsModify() {
			var fn *os.File
			var err error

			fn, err = os.Open(event.Name)
			if err == nil {
				go f.cb(event.Name, fn)
			} else {
				f.watcher.Error <- err
			}
		}
	}
}

// Stop listening for notifications on the file.
func (f *FileWatcher) Shutdown() error {
	var err error

	f.shutdown = true
	err = f.watcher.RemoveWatch(f.path)
	if err != nil {
		return err
	}

	return f.watcher.Close()
}

// Retrieve the error channel associated with the watcher.
// It will stream a list of all errors created while watching.
func (f *FileWatcher) ErrChan() chan error {
	return f.watcher.Error
}
<commit_msg>Actually start the thread to run fsnotify.<commit_after>/*
 * (c) 2014, Caoimhe Chaos <caoimhechaos@protonmail.com>,
 *	     Starship Factory. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the name  of the Starship Factory  nor the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package file

import (
	"io"
	"net/url"
	"os"

	"code.google.com/p/go.exp/fsnotify"
	"github.com/caoimhechaos/go-file"
)

// Object for generating watchers for individual files.
type FileWatcherCreator struct {
}

// Create a new watcher object for watching for notifications on the
// given URL.
func (f *FileWatcherCreator) Watch(
	fileid *url.URL, cb func(string, io.ReadCloser)) (
	file.Watcher, error) {
	return NewFileWatcher(fileid.Path, cb)
}

// Object for watching an individual file for changes.
type FileWatcher struct {
	cb       func(string, io.ReadCloser)
	watcher  *fsnotify.Watcher
	path     string
	shutdown bool
}

// Automatically sign us up for file:// URLs.
func init() {
	file.RegisterWatcher("file", &FileWatcherCreator{})
}

// Create a new FileWatcher watching the file at "path".
func NewFileWatcher(path string, cb func(string, io.ReadCloser)) (
	*FileWatcher, error) {
	var ret *FileWatcher
	var watcher *fsnotify.Watcher
	var err error

	watcher, err = fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}

	err = watcher.Watch(path)
	if err != nil {
		return nil, err
	}

	ret = &FileWatcher{
		cb:      cb,
		watcher: watcher,
		path:    path,
	}

	go ret.watchForChanges()

	return ret, nil
}

// Read events happening on the file being watched and forward them
// to the relevant callback.
func (f *FileWatcher) watchForChanges() {
	for !f.shutdown {
		var event *fsnotify.FileEvent

		event = <-f.watcher.Event

		if event.IsModify() {
			var fn *os.File
			var err error

			fn, err = os.Open(event.Name)
			if err == nil {
				go f.cb(event.Name, fn)
			} else {
				f.watcher.Error <- err
			}
		}
	}
}

// Stop listening for notifications on the file.
func (f *FileWatcher) Shutdown() error {
	var err error

	f.shutdown = true
	err = f.watcher.RemoveWatch(f.path)
	if err != nil {
		return err
	}

	return f.watcher.Close()
}

// Retrieve the error channel associated with the watcher.
// It will stream a list of all errors created while watching.
func (f *FileWatcher) ErrChan() chan error {
	return f.watcher.Error
}
<|endoftext|>