<commit_before>package air

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/textproto"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"
)

// Response is an HTTP response.
type Response struct {
	StatusCode int
	Headers    map[string]string
	Cookies    []*Cookie
	Size       int
	Written    bool

	request       *Request
	writer        http.ResponseWriter
	flusher       http.Flusher
	hijacker      http.Hijacker
	closeNotifier http.CloseNotifier
	pusher        http.Pusher
}

// newResponse returns a new instance of the `Response`.
func newResponse(r *Request, writer http.ResponseWriter) *Response {
	flusher, _ := writer.(http.Flusher)
	hijacker, _ := writer.(http.Hijacker)
	closeNotifier, _ := writer.(http.CloseNotifier)
	pusher, _ := writer.(http.Pusher)

	return &Response{
		StatusCode: 200,
		Headers:    map[string]string{},

		request:       r,
		writer:        writer,
		flusher:       flusher,
		hijacker:      hijacker,
		closeNotifier: closeNotifier,
		pusher:        pusher,
	}
}

// write writes the b to the client.
func (r *Response) write(b []byte) error {
	if !r.Written {
		if !checkPreconditions(r.request, r) {
			for k, v := range r.Headers {
				r.writer.Header().Set(k, v)
			}
			for _, c := range r.Cookies {
				if v := c.String(); v != "" {
					r.writer.Header().Add("Set-Cookie", v)
				}
			}
		} else if r.StatusCode == 304 {
			delete(r.Headers, "Content-Type")
			delete(r.Headers, "Content-Length")
		} else if r.StatusCode == 412 {
			return &Error{412, "Precondition Failed"}
		}
		r.writer.WriteHeader(r.StatusCode)
		r.Written = true
		if r.StatusCode == 304 {
			return nil
		}
	}
	n, err := r.writer.Write(b)
	r.Size += n
	return err
}

// NoContent responds to the client with no content.
func (r *Response) NoContent() error {
	return r.write(nil)
}

// Redirect responds to the client with a redirection to the url.
func (r *Response) Redirect(url string) error {
	r.Headers["Location"] = url
	return r.write(nil)
}

// Blob responds to the client with the contentType content b.
func (r *Response) Blob(contentType string, b []byte) error {
	var err error
	if b, err = theMinifier.minify(contentType, b); err != nil {
		return err
	}
	r.Headers["Content-Type"] = contentType
	return r.write(b)
}

// String responds to the client with the "text/plain" content s.
func (r *Response) String(s string) error {
	return r.Blob("text/plain; charset=utf-8", []byte(s))
}

// JSON responds to the client with the "application/json" content v.
func (r *Response) JSON(v interface{}) error {
	b, err := json.Marshal(v)
	if DebugMode {
		b, err = json.MarshalIndent(v, "", "\t")
	}
	if err != nil {
		return err
	}
	return r.Blob("application/json; charset=utf-8", b)
}

// XML responds to the client with the "application/xml" content v.
func (r *Response) XML(v interface{}) error {
	b, err := xml.Marshal(v)
	if DebugMode {
		b, err = xml.MarshalIndent(v, "", "\t")
	}
	if err != nil {
		return err
	}
	b = append([]byte(xml.Header), b...)
	return r.Blob("application/xml; charset=utf-8", b)
}

// HTML responds to the client with the "text/html" content html.
func (r *Response) HTML(html string) error {
	return r.Blob("text/html; charset=utf-8", []byte(html))
}

// Render renders one or more HTML templates with the m and responds to the
// client with the "text/html" content. The results rendered by the former can
// be inherited by accessing the `m["InheritedHTML"]`.
func (r *Response) Render(m map[string]interface{}, templates ...string) error {
	buf := &bytes.Buffer{}
	for _, t := range templates {
		m["InheritedHTML"] = template.HTML(buf.String())
		buf.Reset()
		if err := theRenderer.render(buf, t, m); err != nil {
			return err
		}
	}
	return r.HTML(buf.String())
}

// Stream responds to the client with the contentType streaming content reader.
func (r *Response) Stream(contentType string, reader io.Reader) error {
	if err := r.Blob(contentType, nil); err != nil {
		return err
	}
	_, err := io.Copy(r.writer, reader)
	return err
}

// File responds to the client with a file content with the file.
func (r *Response) File(file string) error {
	file, err := filepath.Abs(file)
	if err != nil {
		return err
	} else if fi, err := os.Stat(file); err != nil {
		return err
	} else if fi.IsDir() {
		if p := r.request.request.URL.Path; p[len(p)-1] != '/' {
			p = path.Base(p) + "/"
			if q := r.request.request.URL.RawQuery; q != "" {
				p += "?" + q
			}
			r.StatusCode = 301
			return r.Redirect(p)
		}
		file += "index.html"
	}

	c := []byte{}
	mt := time.Time{}
	if a, err := theCoffer.asset(file); err != nil {
		return err
	} else if a != nil {
		c = a.content
		mt = a.modTime
	} else if fi, err := os.Stat(file); err != nil {
		return err
	} else if c, err = ioutil.ReadFile(file); err != nil {
		return err
	} else {
		mt = fi.ModTime()
	}

	if _, ok := r.Headers["ETag"]; !ok {
		r.Headers["ETag"] = fmt.Sprintf(`"%x"`, md5.Sum(c))
	}

	for k, v := range r.Headers {
		r.writer.Header().Set(k, v)
	}

	for _, c := range r.Cookies {
		if v := c.String(); v != "" {
			r.writer.Header().Add("Set-Cookie", v)
		}
	}

	http.ServeContent(
		r.writer,
		r.request.request,
		file,
		mt,
		bytes.NewReader(c),
	)

	r.Written = true

	return nil
}

// Flush flushes buffered data to the client.
func (r *Response) Flush() {
	r.flusher.Flush()
}

// Hijack took over the connection from the server.
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return r.hijacker.Hijack()
}

// CloseNotify returns a channel that receives at most a single value when the
// connection has gone away.
func (r *Response) CloseNotify() <-chan bool {
	return r.closeNotifier.CloseNotify()
}

// Push initiates an HTTP/2 server push. This constructs a synthetic request
// using the given target and pos, serializes that request into a PUSH_PROMISE
// frame, then dispatches that request using the server's request handler. If
// pos is nil, default options are used.
func (r *Response) Push(target string, pos *http.PushOptions) error {
	return r.pusher.Push(target, pos)
}

// checkPreconditions evaluates request preconditions and reports whether a
// precondition resulted in sending not modified or precondition failed.
func checkPreconditions(req *Request, res *Response) bool {
	im, hasIfMatch := req.Headers["If-Match"]
	ius, _ := http.ParseTime(req.Headers["If-Unmodified-Since"])
	if hasIfMatch {
		if !checkIfMatch(res, im) {
			res.StatusCode = 412
			return true
		}
	} else if !ius.IsZero() && !checkIfModifiedSince(res, ius) {
		res.StatusCode = 412
		return true
	}
	inm, hasIfNoneMatch := req.Headers["If-None-Match"]
	ims, _ := http.ParseTime(req.Headers["If-Modified-Since"])
	if hasIfNoneMatch {
		if !checkIfNoneMatch(res, inm) {
			if req.Method == "GET" || req.Method == "HEAD" {
				res.StatusCode = 304
				return true
			}
			res.StatusCode = 412
			return true
		}
	} else if !ims.IsZero() &&
		(req.Method == "GET" || req.Method == "HEAD") &&
		!checkIfModifiedSince(res, ims) {
		res.StatusCode = 304
		return true
	}
	return false
}

// checkIfMatch reports whether the im and the ETag in the res match.
func checkIfMatch(res *Response, im string) bool {
	for {
		im = textproto.TrimString(im)
		if len(im) == 0 {
			break
		}
		if im[0] == ',' {
			im = im[1:]
			continue
		}
		if im[0] == '*' {
			return true
		}
		eTag, remain := scanETag(im)
		if eTag == "" {
			break
		}
		if eTagStrongMatch(eTag, res.Headers["ETag"]) {
			return true
		}
		im = remain
	}
	return false
}

// checkIfUnmodifiedSince reports whether the ius before the Last-Modified in
// the res.
func checkIfUnmodifiedSince(res *Response, ius time.Time) bool {
	lm, _ := http.ParseTime(res.Headers["Last-Modified"])
	return lm.Before(ius.Add(time.Second))
}

// checkIfNoneMatch reports whether the im and the ETag in the res not match.
func checkIfNoneMatch(res *Response, inm string) bool {
	for {
		inm = textproto.TrimString(inm)
		if len(inm) == 0 {
			break
		}
		if inm[0] == ',' {
			inm = inm[1:]
		}
		if inm[0] == '*' {
			return false
		}
		eTag, remain := scanETag(inm)
		if eTag == "" {
			break
		}
		if eTagWeakMatch(eTag, res.Headers["ETag"]) {
			return false
		}
		inm = remain
	}
	return true
}

// checkIfModifiedSince reports whether the ius not before the Last-Modified in
// the res.
func checkIfModifiedSince(res *Response, ims time.Time) bool {
	lm, _ := http.ParseTime(res.Headers["Last-Modified"])
	return !lm.Before(ims.Add(time.Second))
}

// scanETag determines if a syntactically valid ETag is present at s. If so, the
// ETag and remaining text after consuming ETag is returned. Otherwise, it
// returns "", "".
func scanETag(s string) (eTag string, remain string) {
	s = textproto.TrimString(s)
	start := 0
	if strings.HasPrefix(s, "W/") {
		start = 2
	}
	if len(s[start:]) < 2 || s[start] != '"' {
		return "", ""
	}
	// ETag is either W/"text" or "text".
	// See RFC 7232 2.3.
	for i := start + 1; i < len(s); i++ {
		c := s[i]
		switch {
		// Character values allowed in ETags.
		case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
		case c == '"':
			return string(s[:i+1]), s[i+1:]
		default:
			return "", ""
		}
	}
	return "", ""
}

// eTagStrongMatch reports whether a and b match using strong ETag comparison.
func eTagStrongMatch(a, b string) bool {
	return a == b && a != "" && a[0] == '"'
}

// eTagWeakMatch reports whether a and b match using weak ETag comparison.
func eTagWeakMatch(a, b string) bool {
	return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
}
<commit_msg>fix: no Content-Length header problem<commit_after>package air

import (
	"bufio"
	"bytes"
	"crypto/md5"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/textproto"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

// Response is an HTTP response.
type Response struct {
	StatusCode int
	Headers    map[string]string
	Cookies    []*Cookie
	Size       int
	Written    bool

	request       *Request
	writer        http.ResponseWriter
	flusher       http.Flusher
	hijacker      http.Hijacker
	closeNotifier http.CloseNotifier
	pusher        http.Pusher
}

// newResponse returns a new instance of the `Response`.
func newResponse(r *Request, writer http.ResponseWriter) *Response {
	flusher, _ := writer.(http.Flusher)
	hijacker, _ := writer.(http.Hijacker)
	closeNotifier, _ := writer.(http.CloseNotifier)
	pusher, _ := writer.(http.Pusher)

	return &Response{
		StatusCode: 200,
		Headers:    map[string]string{},

		request:       r,
		writer:        writer,
		flusher:       flusher,
		hijacker:      hijacker,
		closeNotifier: closeNotifier,
		pusher:        pusher,
	}
}

// write writes the b to the client.
func (r *Response) write(b []byte) error {
	if !r.Written {
		if !checkPreconditions(r.request, r) {
			r.Headers["Content-Length"] = strconv.Itoa(len(b))
			for k, v := range r.Headers {
				r.writer.Header().Set(k, v)
			}
			for _, c := range r.Cookies {
				if v := c.String(); v != "" {
					r.writer.Header().Add("Set-Cookie", v)
				}
			}
		} else if r.StatusCode == 304 {
			delete(r.Headers, "Content-Type")
			delete(r.Headers, "Content-Length")
		} else if r.StatusCode == 412 {
			return &Error{412, "Precondition Failed"}
		}
		r.writer.WriteHeader(r.StatusCode)
		r.Written = true
		if r.StatusCode == 304 {
			return nil
		}
	}
	n, err := r.writer.Write(b)
	r.Size += n
	return err
}

// NoContent responds to the client with no content.
func (r *Response) NoContent() error {
	return r.write(nil)
}

// Redirect responds to the client with a redirection to the url.
func (r *Response) Redirect(url string) error {
	r.Headers["Location"] = url
	return r.write(nil)
}

// Blob responds to the client with the contentType content b.
func (r *Response) Blob(contentType string, b []byte) error {
	var err error
	if b, err = theMinifier.minify(contentType, b); err != nil {
		return err
	}
	r.Headers["Content-Type"] = contentType
	return r.write(b)
}

// String responds to the client with the "text/plain" content s.
func (r *Response) String(s string) error {
	return r.Blob("text/plain; charset=utf-8", []byte(s))
}

// JSON responds to the client with the "application/json" content v.
func (r *Response) JSON(v interface{}) error {
	b, err := json.Marshal(v)
	if DebugMode {
		b, err = json.MarshalIndent(v, "", "\t")
	}
	if err != nil {
		return err
	}
	return r.Blob("application/json; charset=utf-8", b)
}

// XML responds to the client with the "application/xml" content v.
func (r *Response) XML(v interface{}) error {
	b, err := xml.Marshal(v)
	if DebugMode {
		b, err = xml.MarshalIndent(v, "", "\t")
	}
	if err != nil {
		return err
	}
	b = append([]byte(xml.Header), b...)
	return r.Blob("application/xml; charset=utf-8", b)
}

// HTML responds to the client with the "text/html" content html.
func (r *Response) HTML(html string) error {
	return r.Blob("text/html; charset=utf-8", []byte(html))
}

// Render renders one or more HTML templates with the m and responds to the
// client with the "text/html" content. The results rendered by the former can
// be inherited by accessing the `m["InheritedHTML"]`.
func (r *Response) Render(m map[string]interface{}, templates ...string) error {
	buf := &bytes.Buffer{}
	for _, t := range templates {
		m["InheritedHTML"] = template.HTML(buf.String())
		buf.Reset()
		if err := theRenderer.render(buf, t, m); err != nil {
			return err
		}
	}
	return r.HTML(buf.String())
}

// Stream responds to the client with the contentType streaming content reader.
func (r *Response) Stream(contentType string, reader io.Reader) error {
	if err := r.Blob(contentType, nil); err != nil {
		return err
	}
	n, err := io.Copy(r.writer, reader)
	r.Size += int(n)
	return err
}

// File responds to the client with a file content with the file.
func (r *Response) File(file string) error {
	file, err := filepath.Abs(file)
	if err != nil {
		return err
	} else if fi, err := os.Stat(file); err != nil {
		return err
	} else if fi.IsDir() {
		if p := r.request.request.URL.Path; p[len(p)-1] != '/' {
			p = path.Base(p) + "/"
			if q := r.request.request.URL.RawQuery; q != "" {
				p += "?" + q
			}
			r.StatusCode = 301
			return r.Redirect(p)
		}
		file += "index.html"
	}

	c := []byte{}
	mt := time.Time{}
	if a, err := theCoffer.asset(file); err != nil {
		return err
	} else if a != nil {
		c = a.content
		mt = a.modTime
	} else if fi, err := os.Stat(file); err != nil {
		return err
	} else if c, err = ioutil.ReadFile(file); err != nil {
		return err
	} else {
		mt = fi.ModTime()
	}

	if _, ok := r.Headers["ETag"]; !ok {
		r.Headers["ETag"] = fmt.Sprintf(`"%x"`, md5.Sum(c))
	}

	for k, v := range r.Headers {
		r.writer.Header().Set(k, v)
	}

	for _, c := range r.Cookies {
		if v := c.String(); v != "" {
			r.writer.Header().Add("Set-Cookie", v)
		}
	}

	http.ServeContent(
		r.writer,
		r.request.request,
		file,
		mt,
		bytes.NewReader(c),
	)

	r.Written = true
	r.Size += len(c)

	return nil
}

// Flush flushes buffered data to the client.
func (r *Response) Flush() {
	r.flusher.Flush()
}

// Hijack took over the connection from the server.
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return r.hijacker.Hijack()
}

// CloseNotify returns a channel that receives at most a single value when the
// connection has gone away.
func (r *Response) CloseNotify() <-chan bool {
	return r.closeNotifier.CloseNotify()
}

// Push initiates an HTTP/2 server push. This constructs a synthetic request
// using the given target and pos, serializes that request into a PUSH_PROMISE
// frame, then dispatches that request using the server's request handler. If
// pos is nil, default options are used.
func (r *Response) Push(target string, pos *http.PushOptions) error {
	return r.pusher.Push(target, pos)
}

// checkPreconditions evaluates request preconditions and reports whether a
// precondition resulted in sending not modified or precondition failed.
func checkPreconditions(req *Request, res *Response) bool {
	im, hasIfMatch := req.Headers["If-Match"]
	ius, _ := http.ParseTime(req.Headers["If-Unmodified-Since"])
	if hasIfMatch {
		if !checkIfMatch(res, im) {
			res.StatusCode = 412
			return true
		}
	} else if !ius.IsZero() && !checkIfModifiedSince(res, ius) {
		res.StatusCode = 412
		return true
	}
	inm, hasIfNoneMatch := req.Headers["If-None-Match"]
	ims, _ := http.ParseTime(req.Headers["If-Modified-Since"])
	if hasIfNoneMatch {
		if !checkIfNoneMatch(res, inm) {
			if req.Method == "GET" || req.Method == "HEAD" {
				res.StatusCode = 304
				return true
			}
			res.StatusCode = 412
			return true
		}
	} else if !ims.IsZero() &&
		(req.Method == "GET" || req.Method == "HEAD") &&
		!checkIfModifiedSince(res, ims) {
		res.StatusCode = 304
		return true
	}
	return false
}

// checkIfMatch reports whether the im and the ETag in the res match.
func checkIfMatch(res *Response, im string) bool {
	for {
		im = textproto.TrimString(im)
		if len(im) == 0 {
			break
		}
		if im[0] == ',' {
			im = im[1:]
			continue
		}
		if im[0] == '*' {
			return true
		}
		eTag, remain := scanETag(im)
		if eTag == "" {
			break
		}
		if eTagStrongMatch(eTag, res.Headers["ETag"]) {
			return true
		}
		im = remain
	}
	return false
}

// checkIfUnmodifiedSince reports whether the ius before the Last-Modified in
// the res.
func checkIfUnmodifiedSince(res *Response, ius time.Time) bool {
	lm, _ := http.ParseTime(res.Headers["Last-Modified"])
	return lm.Before(ius.Add(time.Second))
}

// checkIfNoneMatch reports whether the im and the ETag in the res not match.
func checkIfNoneMatch(res *Response, inm string) bool {
	for {
		inm = textproto.TrimString(inm)
		if len(inm) == 0 {
			break
		}
		if inm[0] == ',' {
			inm = inm[1:]
		}
		if inm[0] == '*' {
			return false
		}
		eTag, remain := scanETag(inm)
		if eTag == "" {
			break
		}
		if eTagWeakMatch(eTag, res.Headers["ETag"]) {
			return false
		}
		inm = remain
	}
	return true
}

// checkIfModifiedSince reports whether the ius not before the Last-Modified in
// the res.
func checkIfModifiedSince(res *Response, ims time.Time) bool {
	lm, _ := http.ParseTime(res.Headers["Last-Modified"])
	return !lm.Before(ims.Add(time.Second))
}

// scanETag determines if a syntactically valid ETag is present at s. If so, the
// ETag and remaining text after consuming ETag is returned. Otherwise, it
// returns "", "".
func scanETag(s string) (eTag string, remain string) {
	s = textproto.TrimString(s)
	start := 0
	if strings.HasPrefix(s, "W/") {
		start = 2
	}
	if len(s[start:]) < 2 || s[start] != '"' {
		return "", ""
	}
	// ETag is either W/"text" or "text".
	// See RFC 7232 2.3.
	for i := start + 1; i < len(s); i++ {
		c := s[i]
		switch {
		// Character values allowed in ETags.
		case c == 0x21 || c >= 0x23 && c <= 0x7E || c >= 0x80:
		case c == '"':
			return string(s[:i+1]), s[i+1:]
		default:
			return "", ""
		}
	}
	return "", ""
}

// eTagStrongMatch reports whether a and b match using strong ETag comparison.
func eTagStrongMatch(a, b string) bool {
	return a == b && a != "" && a[0] == '"'
}

// eTagWeakMatch reports whether a and b match using weak ETag comparison.
func eTagWeakMatch(a, b string) bool {
	return strings.TrimPrefix(a, "W/") == strings.TrimPrefix(b, "W/")
}
<|endoftext|>


<commit_before>package apns

// ReasonCodes defines keys & descriptions.
var ReasonCodes = map[string]string{
	"PayloadEmpty":              "The message payload was empty.",
	"PayloadTooLarge":           "The message payload was too large. The maximum payload size is 4096 bytes.",
	"BadTopic":                  "The apns-topic was invalid.",
	"TopicDisallowed":           "Pushing to this topic is not allowed.",
	"BadMessageId":              "The apns-id value is bad.",
	"BadExpirationDate":         "The apns-expiration value is bad.",
	"BadPriority":               "The apns-priority value is bad.",
	"MissingDeviceToken":        "The device token is not specified in the request :path. Verify that the :path header contains the device token.",
	"BadDeviceToken":            "The specified device token was bad. Verify that the request contains a valid token and that the token matches the environment.",
	"DeviceTokenNotForTopic":    "The device token does not match the specified topic.",
	"Unregistered":              "The device token is inactive for the specified topic.",
	"DuplicateHeaders":          "One or more headers were repeated.",
	"BadCertificateEnvironment": "The client certificate was for the wrong environment.",
	"BadCertificate":            "The certificate was bad.",
	"Forbidden":                 "The specified action is not allowed.",
	"BadPath":                   "The request contained a bad path value.",
	"MethodNotAllowed":          "The specified :method was not POST.",
	"TooManyRequests":           "Too many requests were made consecutively to the same device token.",
	"IdleTimeout":               "Idle time out.",
	"Shutdown":                  "The server is shutting down.",
	"InternalServerError":       "An internal server error occurred.",
	"ServiceUnavailable":        "The service is unavailable.",
	"MissingTopic":              "The apns-topic header of the request was not specified and was required. The apns-topic header is mandatory when the client is connected using a certificate that supports multiple topics.",
}

// StatusCodes defines keys & descriptions.
var StatusCodes = map[int]string{
	200: "Success.",
	400: "Bad request.",
	403: "There was an error with the certificate.",
	405: "The request used a bad method value. Only \"POST\" requests are supported.",
	410: "The device token is no longer active for the topic.",
	413: "The notification payload was too large.",
	429: "The server received too many requests for the same device token.",
	500: "Internal server error.",
	503: "The server is shutting down and unavailable.",
}

// Response describes a response from Apple push server.
type Response struct {
	ApnsID      string
	deviceID    string
	deviceToken string

	Status            int
	StatusDescription string
	ReasonDescription string

	Reason    string `json:"reason,omitempty"`
	Timestamp int64  `json:"timestamp,omitempty"`
}
<commit_msg>Add define keys.<commit_after>package apns

// Defines keys.
const (
	PayloadEmpty              = "PayloadEmpty"
	PayloadTooLarge           = "PayloadTooLarge"
	BadTopic                  = "BadTopic"
	TopicDisallowed           = "TopicDisallowed"
	BadMessageID              = "BadMessageId"
	BadExpirationDate         = "BadExpirationDate"
	BadPriority               = "BadPriority"
	MissingDeviceToken        = "MissingDeviceToken"
	BadDeviceToken            = "BadDeviceToken"
	DeviceTokenNotForTopic    = "DeviceTokenNotForTopic"
	Unregistered              = "Unregistered"
	DuplicateHeaders          = "DuplicateHeaders"
	BadCertificateEnvironment = "BadCertificateEnvironment"
	BadCertificate            = "BadCertificate"
	Forbidden                 = "Forbidden"
	BadPath                   = "BadPath"
	MethodNotAllowed          = "MethodNotAllowed"
	TooManyRequests           = "TooManyRequests"
	IdleTimeout               = "IdleTimeout"
	Shutdown                  = "Shutdown"
	InternalServerError       = "InternalServerError"
	ServiceUnavailable        = "ServiceUnavailable"
	MissingTopic              = "MissingTopic"
)

// ReasonCodes defines keys & descriptions.
var ReasonCodes = map[string]string{
	PayloadEmpty:              "The message payload was empty.",
	PayloadTooLarge:           "The message payload was too large. The maximum payload size is 4096 bytes.",
	BadTopic:                  "The apns-topic was invalid.",
	TopicDisallowed:           "Pushing to this topic is not allowed.",
	BadMessageID:              "The apns-id value is bad.",
	BadExpirationDate:         "The apns-expiration value is bad.",
	BadPriority:               "The apns-priority value is bad.",
	MissingDeviceToken:        "The device token is not specified in the request :path. Verify that the :path header contains the device token.",
	BadDeviceToken:            "The specified device token was bad. Verify that the request contains a valid token and that the token matches the environment.",
	DeviceTokenNotForTopic:    "The device token does not match the specified topic.",
	Unregistered:              "The device token is inactive for the specified topic.",
	DuplicateHeaders:          "One or more headers were repeated.",
	BadCertificateEnvironment: "The client certificate was for the wrong environment.",
	BadCertificate:            "The certificate was bad.",
	Forbidden:                 "The specified action is not allowed.",
	BadPath:                   "The request contained a bad path value.",
	MethodNotAllowed:          "The specified :method was not POST.",
	TooManyRequests:           "Too many requests were made consecutively to the same device token.",
	IdleTimeout:               "Idle time out.",
	Shutdown:                  "The server is shutting down.",
	InternalServerError:       "An internal server error occurred.",
	ServiceUnavailable:        "The service is unavailable.",
	MissingTopic:              "The apns-topic header of the request was not specified and was required. The apns-topic header is mandatory when the client is connected using a certificate that supports multiple topics.",
}

// StatusCodes defines keys & descriptions.
var StatusCodes = map[int]string{
	200: "Success.",
	400: "Bad request.",
	403: "There was an error with the certificate.",
	405: "The request used a bad method value. Only \"POST\" requests are supported.",
	410: "The device token is no longer active for the topic.",
	413: "The notification payload was too large.",
	429: "The server received too many requests for the same device token.",
	500: "Internal server error.",
	503: "The server is shutting down and unavailable.",
}

// Response describes a response from Apple push server.
type Response struct {
	ApnsID      string
	deviceID    string
	deviceToken string

	Status            int
	StatusDescription string
	ReasonDescription string

	Reason    string `json:"reason,omitempty"`
	Timestamp int64  `json:"timestamp,omitempty"`
}
<|endoftext|>


<commit_before>package integration_test

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/stretchr/testify/suite"
	"k8s.io/client-go/tools/clientcmd/api"

	"github.com/datawire/dlib/dlog"
	"github.com/telepresenceio/telepresence/rpc/v2/manager"
	"github.com/telepresenceio/telepresence/v2/integration_test/itest"
	"github.com/telepresenceio/telepresence/v2/pkg/client"
	"github.com/telepresenceio/telepresence/v2/pkg/client/connector/userd_k8s"
	"github.com/telepresenceio/telepresence/v2/pkg/client/connector/userd_trafficmgr"
	"github.com/telepresenceio/telepresence/v2/pkg/install"
	"github.com/telepresenceio/telepresence/v2/pkg/version"
)

type installSuite struct {
	itest.Suite
	itest.NamespacePair
}

func init() {
	itest.AddNamespacePairSuite("-auto-install", func(h itest.NamespacePair) suite.TestingSuite {
		return &installSuite{Suite: itest.Suite{Harness: h}, NamespacePair: h}
	})
}

func (is *installSuite) Test_FindTrafficManager_notPresent() {
	ctx := is.Context()
	ti := is.installer(ctx)

	sv := version.Version
	version.Version = "v0.0.0-bogus"
	defer func() { version.Version = sv }()

	_, err := ti.FindDeployment(ctx, is.ManagerNamespace(), install.ManagerAppName)
	is.Error(err, "expected find to not find traffic-manager deployment")
}

func (is *installSuite) Test_EnsureManager_updateFromLegacy() {
	require := is.Require()
	ctx := is.Context()

	defer is.UninstallTrafficManager(ctx, is.ManagerNamespace())

	f, err := os.ReadFile("testdata/legacyManifests/manifests.yml")
	require.NoError(err)
	manifest := string(f)
	manifest = strings.ReplaceAll(manifest, "{{.ManagerNamespace}}", is.ManagerNamespace())
	cmd := itest.Command(ctx, "kubectl", "--kubeconfig", itest.KubeConfig(ctx), "-n", is.ManagerNamespace(), "apply", "-f", "-")
	cmd.Stdin = strings.NewReader(manifest)
	out := dlog.StdLogger(ctx, dlog.LogLevelDebug).Writer()
	cmd.Stdout = out
	cmd.Stderr = out
	require.NoError(cmd.Run())
	require.NoError(itest.Kubectl(ctx, is.ManagerNamespace(), "rollout", "status", "-w", "deploy/traffic-manager"))

	is.findTrafficManagerPresent(ctx, is.ManagerNamespace())
}

func (is *installSuite) Test_EnsureManager_toleratesFailedInstall() {
	require := is.Require()
	ctx := is.Context()

	sv := version.Version
	version.Version = "v0.0.0-bogus"
	restoreVersion := func() { version.Version = sv }

	// We'll call this further down, but defer it to prevent polluting other tests if we don't leave this function gracefully
	defer restoreVersion()
	defer is.UninstallTrafficManager(ctx, is.ManagerNamespace())

	ctx = itest.WithConfig(ctx, &client.Config{
		Timeouts: client.Timeouts{
			PrivateHelm: 30 * time.Second,
		},
	})
	ti := is.installer(ctx)
	require.Error(ti.EnsureManager(ctx))
	restoreVersion()

	var err error
	require.Eventually(func() bool {
		err = ti.EnsureManager(ctx)
		return err == nil
	}, 20*time.Second, 5*time.Second, "Unable to install proper manager after failed install: %v", err)
}

func (is *installSuite) Test_EnsureManager_toleratesLeftoverState() {
	require := is.Require()
	ctx := is.Context()

	ti := is.installer(ctx)
	require.NoError(ti.EnsureManager(ctx))
	defer is.UninstallTrafficManager(ctx, is.ManagerNamespace())

	is.UninstallTrafficManager(ctx, is.ManagerNamespace())
	require.NoError(ti.EnsureManager(ctx))
	require.Eventually(func() bool {
		deploy, err := ti.FindDeployment(ctx, is.ManagerNamespace(), install.ManagerAppName)
		if err != nil {
			return false
		}
		return deploy.Status.ReadyReplicas == int32(1) && deploy.Status.Replicas == int32(1)
	}, 10*time.Second, time.Second, "timeout waiting for deployment to update")
}

func (is *installSuite) Test_RemoveManagerAndAgents_canUninstall() {
	require := is.Require()
	ctx := is.Context()
	ti := is.installer(ctx)

	require.NoError(ti.EnsureManager(ctx))
	require.NoError(ti.RemoveManagerAndAgents(ctx, false, []*manager.AgentInfo{}))
	// We want to make sure that we can re-install the agent after it's been uninstalled,
	// so try to ensureManager again.
	require.NoError(ti.EnsureManager(ctx))
	// Uninstall the agent one last time -- this should behave the same way as the previous uninstall
	require.NoError(ti.RemoveManagerAndAgents(ctx, false, []*manager.AgentInfo{}))
}

func (is *installSuite) Test_EnsureManager_upgrades() {
	require := is.Require()
	ctx := is.Context()
	ti := is.installer(ctx)

	require.NoError(ti.EnsureManager(ctx))
	defer is.UninstallTrafficManager(ctx, is.ManagerNamespace())

	sv := version.Version
	version.Version = "v3.0.0-bogus"
	restoreVersion := func() { version.Version = sv }
	defer restoreVersion()
	require.Error(ti.EnsureManager(ctx))

	require.Eventually(func() bool {
		deploy, err := ti.FindDeployment(ctx, is.ManagerNamespace(), install.ManagerAppName)
		if err != nil {
			return false
		}
		return deploy.Status.ReadyReplicas == int32(1) && deploy.Status.Replicas == int32(1)
	}, 30*time.Second, 5*time.Second, "timeout waiting for deployment to update")

	restoreVersion()
	require.NoError(ti.EnsureManager(ctx))
}

func (is *installSuite) Test_EnsureManager_doesNotChangeExistingHelm() {
	require := is.Require()
	ctx := is.Context()

	cfgAndFlags, err := userd_k8s.NewConfig(ctx, map[string]string{"kubeconfig": itest.KubeConfig(ctx), "namespace": is.ManagerNamespace()})
	require.NoError(err)
	kc, err := userd_k8s.NewCluster(ctx, cfgAndFlags, nil, userd_k8s.Callbacks{})
	require.NoError(err)

	// The helm chart is declared as 1.9.9 to make sure it's "older" than ours, but we set the tag to 2.4.0 so that it actually starts up.
	// 2.4.0 was the latest release at the time that testdata/telepresence-1.9.9.tgz was packaged
	tgzFile := filepath.Join(itest.GetWorkingDir(ctx), "testdata", "telepresence-1.9.9.tgz")
	err = itest.Run(itest.WithModuleRoot(ctx),
		"tools/bin/helm",
		"--kubeconfig", itest.KubeConfig(ctx),
		"-n", is.ManagerNamespace(),
		"install", "traffic-manager", tgzFile,
		"--create-namespace",
		"--atomic",
		"--set", "clusterID="+kc.GetClusterId(ctx),
		"--set", "image.tag=2.4.0",
		"--wait",
	)
	require.NoError(err)

	defer is.UninstallTrafficManager(ctx, is.ManagerNamespace())

	ti := is.installer(ctx)

	require.NoError(ti.EnsureManager(ctx))

	kc.Client().InvalidateCache()
	dep, err := ti.FindDeployment(ctx, is.ManagerNamespace(), install.ManagerAppName)
	require.NoError(err)
	require.NotNil(dep)
	require.Contains(dep.Spec.Template.Spec.Containers[0].Image, "2.4.0")
	require.Equal(dep.Labels["helm.sh/chart"], "telepresence-1.9.9")
}

func (is *installSuite) Test_findTrafficManager_differentNamespace_present() {
	ctx := is.Context()
	customNamespace := fmt.Sprintf("custom-%d", os.Getpid())
	itest.CreateNamespaces(ctx, customNamespace)
	defer itest.DeleteNamespaces(ctx, customNamespace)
	ctx = itest.WithEnv(ctx, map[string]string{"TELEPRESENCE_MANAGER_NAMESPACE": customNamespace})
	ctx = itest.WithKubeConfigExtension(ctx, func(cluster *api.Cluster) map[string]interface{} {
		return map[string]interface{}{"manager": map[string]string{"namespace": customNamespace}}
	})
	is.findTrafficManagerPresent(ctx, customNamespace)
}

func (is *installSuite) findTrafficManagerPresent(ctx context.Context, namespace string) {
	kc := is.cluster(ctx, namespace)
	watcherErr := make(chan error)
	watchCtx, watchCancel := context.WithCancel(ctx)
	defer func() {
		watchCancel()
		if err := <-watcherErr; err != nil {
			is.Fail(err.Error())
		}
	}()
	go func() {
		watcherErr <- kc.RunWatchers(watchCtx)
	}()
	waitCtx, waitCancel := context.WithTimeout(ctx, 10*time.Second)
	defer waitCancel()

	require := is.Require()
	require.NoError(kc.WaitUntilReady(waitCtx))
	ti, err := userd_trafficmgr.NewTrafficManagerInstaller(kc)
	require.NoError(err)
	require.NoError(ti.EnsureManager(ctx))
	require.Eventually(func() bool {
		dep, err := ti.FindDeployment(ctx, namespace, install.ManagerAppName)
		if err != nil {
			dlog.Error(ctx, err)
			return false
		}
		v := strings.TrimPrefix(version.Version, "v")
		img := dep.Spec.Template.Spec.Containers[0].Image
		dlog.Infof(ctx, "traffic-manager image %s, our version %s", img, v)
		return strings.Contains(img, v)
	}, 10*time.Second, 2*time.Second, "traffic-manager deployment not found")
}

func (is *installSuite) cluster(ctx context.Context, managerNamespace string) *userd_k8s.Cluster {
	require := is.Require()
	cfgAndFlags, err := userd_k8s.NewConfig(ctx, map[string]string{
		"kubeconfig": itest.KubeConfig(ctx),
		"context":    "default",
		"namespace":  managerNamespace})
	require.NoError(err)
	kc, err := userd_k8s.NewCluster(ctx, cfgAndFlags, nil, userd_k8s.Callbacks{})
	require.NoError(err)
	return kc
}

func (is *installSuite) installer(ctx context.Context) userd_trafficmgr.Installer {
	ti, err := userd_trafficmgr.NewTrafficManagerInstaller(is.cluster(ctx, is.ManagerNamespace()))
	is.Require().NoError(err)
	return ti
}
<commit_msg>Disable the Test_EnsureManager_upgrades() test because it's broken<commit_after>package integration_test

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/stretchr/testify/suite"
	"k8s.io/client-go/tools/clientcmd/api"

	"github.com/datawire/dlib/dlog"
	"github.com/telepresenceio/telepresence/rpc/v2/manager"
	"github.com/telepresenceio/telepresence/v2/integration_test/itest"
	"github.com/telepresenceio/telepresence/v2/pkg/client"
	"github.com/telepresenceio/telepresence/v2/pkg/client/connector/userd_k8s"
	"github.com/telepresenceio/telepresence/v2/pkg/client/connector/userd_trafficmgr"
	"github.com/telepresenceio/telepresence/v2/pkg/install"
	"github.com/telepresenceio/telepresence/v2/pkg/version"
)

type installSuite struct {
	itest.Suite
	itest.NamespacePair
}

func init() {
	itest.AddNamespacePairSuite("-auto-install", func(h itest.NamespacePair) suite.TestingSuite {
		return &installSuite{Suite: itest.Suite{Harness: h}, NamespacePair: h}
	})
}

func (is *installSuite) Test_FindTrafficManager_notPresent() {
	ctx := is.Context()
	ti := is.installer(ctx)

	sv := version.Version
	version.Version = "v0.0.0-bogus"
	defer func() { version.Version = sv }()

	_, err := ti.FindDeployment(ctx, is.ManagerNamespace(), install.ManagerAppName)
	is.Error(err, "expected find to not find traffic-manager deployment")
}

func (is *installSuite) Test_EnsureManager_updateFromLegacy() {
	require := is.Require()
	ctx := is.Context()

	defer is.UninstallTrafficManager(ctx, is.ManagerNamespace())

	f, err := os.ReadFile("testdata/legacyManifests/manifests.yml")
	require.NoError(err)
	manifest := string(f)
	manifest = strings.ReplaceAll(manifest, "{{.ManagerNamespace}}", is.ManagerNamespace())
	cmd := itest.Command(ctx, "kubectl", "--kubeconfig", itest.KubeConfig(ctx), "-n", is.ManagerNamespace(), "apply", "-f", "-")
	cmd.Stdin = strings.NewReader(manifest)
	out := dlog.StdLogger(ctx, dlog.LogLevelDebug).Writer()
	cmd.Stdout = out
	cmd.Stderr = out
	require.NoError(cmd.Run())
	require.NoError(itest.Kubectl(ctx, is.ManagerNamespace(), "rollout", "status", "-w", "deploy/traffic-manager"))

	is.findTrafficManagerPresent(ctx, is.ManagerNamespace())
}

func (is *installSuite) Test_EnsureManager_toleratesFailedInstall() {
	require := is.Require()
	ctx := is.Context()

	sv := version.Version
	version.Version = "v0.0.0-bogus"
	restoreVersion := func() { version.Version = sv }

	// We'll call this further down, but defer it to prevent polluting other tests if we don't leave this function gracefully
	defer restoreVersion()
	defer is.UninstallTrafficManager(ctx, is.ManagerNamespace())

	ctx = itest.WithConfig(ctx, &client.Config{
		Timeouts: client.Timeouts{
			PrivateHelm: 30 * time.Second,
		},
	})
	ti := is.installer(ctx)
	require.Error(ti.EnsureManager(ctx))
	restoreVersion()

	var err error
	require.Eventually(func() bool {
		err = ti.EnsureManager(ctx)
		return err == nil
	}, 20*time.Second, 5*time.Second, "Unable to install proper manager after failed install: %v", err)
}

func (is *installSuite) Test_EnsureManager_toleratesLeftoverState() {
	require := is.Require()
	ctx := is.Context()

	ti := is.installer(ctx)
	require.NoError(ti.EnsureManager(ctx))
	defer is.UninstallTrafficManager(ctx, is.ManagerNamespace())

	is.UninstallTrafficManager(ctx, is.ManagerNamespace())
	require.NoError(ti.EnsureManager(ctx))
	require.Eventually(func() bool {
		deploy, err := ti.FindDeployment(ctx, is.ManagerNamespace(), install.ManagerAppName)
		if err != nil {
			return false
		}
		return deploy.Status.ReadyReplicas == int32(1) && deploy.Status.Replicas == int32(1)
	}, 10*time.Second, time.Second, "timeout waiting for deployment to update")
}

func (is *installSuite) Test_RemoveManagerAndAgents_canUninstall() {
	require := is.Require()
	ctx := is.Context()
	ti := is.installer(ctx)

	require.NoError(ti.EnsureManager(ctx))
	require.NoError(ti.RemoveManagerAndAgents(ctx, false, []*manager.AgentInfo{}))
	// We want to make sure that we can re-install the agent after it's been uninstalled,
	// so try to ensureManager again.
	require.NoError(ti.EnsureManager(ctx))
	// Uninstall the agent one last time -- this should behave the same way as the previous uninstall
	require.NoError(ti.RemoveManagerAndAgents(ctx, false, []*manager.AgentInfo{}))
}

func (is *installSuite) Test_EnsureManager_upgrades() {
	// TODO: In order to properly check that an upgrade works, we need to install
	//  an older version first, which in turn will entail building that version
	//  and publishing an image fore it. The way the test looks right now, it just
	//  terminates with a timeout error.
	is.T().Skip()
	require := is.Require()
	ctx := is.Context()
	ti := is.installer(ctx)

	require.NoError(ti.EnsureManager(ctx))
	defer is.UninstallTrafficManager(ctx, is.ManagerNamespace())

	sv := version.Version
	version.Version = "v3.0.0-bogus"
	restoreVersion := func() { version.Version = sv }
	defer restoreVersion()
	require.Error(ti.EnsureManager(ctx))

	require.Eventually(func() bool {
		deploy, err := ti.FindDeployment(ctx, is.ManagerNamespace(), install.ManagerAppName)
		if err != nil {
			return false
		}
		return deploy.Status.ReadyReplicas == int32(1) && deploy.Status.Replicas == int32(1)
	}, 30*time.Second, 5*time.Second, "timeout waiting for deployment to update")

	restoreVersion()
	require.NoError(ti.EnsureManager(ctx))
}

func (is *installSuite) Test_EnsureManager_doesNotChangeExistingHelm() {
	require := is.Require()
	ctx := is.Context()

	cfgAndFlags, err := userd_k8s.NewConfig(ctx, map[string]string{"kubeconfig": itest.KubeConfig(ctx), "namespace": is.ManagerNamespace()})
	require.NoError(err)
	kc, err := userd_k8s.NewCluster(ctx, cfgAndFlags, nil, userd_k8s.Callbacks{})
	require.NoError(err)

	// The helm chart is declared as 1.9.9 to make sure it's "older" than ours, but we set the tag to 2.4.0 so that it actually starts up.
	// 2.4.0 was the latest release at the time that testdata/telepresence-1.9.9.tgz was packaged
	tgzFile := filepath.Join(itest.GetWorkingDir(ctx), "testdata", "telepresence-1.9.9.tgz")
	err = itest.Run(itest.WithModuleRoot(ctx),
		"tools/bin/helm",
		"--kubeconfig", itest.KubeConfig(ctx),
		"-n", is.ManagerNamespace(),
		"install", "traffic-manager", tgzFile,
		"--create-namespace",
		"--atomic",
		"--set", "clusterID="+kc.GetClusterId(ctx),
		"--set", "image.tag=2.4.0",
		"--wait",
	)
	require.NoError(err)

	defer is.UninstallTrafficManager(ctx, is.ManagerNamespace())

	ti := is.installer(ctx)

	require.NoError(ti.EnsureManager(ctx))

	kc.Client().InvalidateCache()
	dep, err := ti.FindDeployment(ctx, is.ManagerNamespace(), install.ManagerAppName)
	require.NoError(err)
	require.NotNil(dep)
	require.Contains(dep.Spec.Template.Spec.Containers[0].Image, "2.4.0")
	require.Equal(dep.Labels["helm.sh/chart"], "telepresence-1.9.9")
}

func (is *installSuite) Test_findTrafficManager_differentNamespace_present() {
	ctx := is.Context()
	customNamespace := fmt.Sprintf("custom-%d", os.Getpid())
	itest.CreateNamespaces(ctx, customNamespace)
	defer itest.DeleteNamespaces(ctx, customNamespace)
	ctx = itest.WithEnv(ctx, map[string]string{"TELEPRESENCE_MANAGER_NAMESPACE": customNamespace})
	ctx = itest.WithKubeConfigExtension(ctx, func(cluster *api.Cluster) map[string]interface{} {
		return map[string]interface{}{"manager": map[string]string{"namespace": customNamespace}}
	})
	is.findTrafficManagerPresent(ctx, customNamespace)
}

func (is *installSuite) findTrafficManagerPresent(ctx context.Context, namespace string) {
	kc := is.cluster(ctx, namespace)
	watcherErr := make(chan error)
	watchCtx, watchCancel := context.WithCancel(ctx)
	defer func() {
		watchCancel()
		if err := <-watcherErr; err != nil {
			is.Fail(err.Error())
		}
	}()
	go func() {
		watcherErr <- kc.RunWatchers(watchCtx)
	}()
	waitCtx, waitCancel := context.WithTimeout(ctx, 10*time.Second)
	defer waitCancel()

	require := is.Require()
	require.NoError(kc.WaitUntilReady(waitCtx))
	ti, err := userd_trafficmgr.NewTrafficManagerInstaller(kc)
	require.NoError(err)
	require.NoError(ti.EnsureManager(ctx))
	require.Eventually(func() bool {
		dep, err := ti.FindDeployment(ctx, namespace, install.ManagerAppName)
		if err != nil {
			dlog.Error(ctx, err)
			return false
		}
		v := strings.TrimPrefix(version.Version, "v")
		img := dep.Spec.Template.Spec.Containers[0].Image
		dlog.Infof(ctx, "traffic-manager image %s, our version %s", img, v)
		return strings.Contains(img, v)
	}, 10*time.Second, 2*time.Second, "traffic-manager deployment not found")
}

func (is *installSuite) cluster(ctx context.Context, managerNamespace string) *userd_k8s.Cluster {
	require := is.Require()
	cfgAndFlags, err := userd_k8s.NewConfig(ctx, map[string]string{
		"kubeconfig": itest.KubeConfig(ctx),
		"context":    "default",
		"namespace":  managerNamespace})
	require.NoError(err)
	kc, err := userd_k8s.NewCluster(ctx, cfgAndFlags, nil, userd_k8s.Callbacks{})
	require.NoError(err)
	return kc
}

func (is *installSuite) installer(ctx context.Context) userd_trafficmgr.Installer {
	ti, err := userd_trafficmgr.NewTrafficManagerInstaller(is.cluster(ctx, is.ManagerNamespace()))
	is.Require().NoError(err)
	return ti
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Ceph-CSI 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 cephfs

import (
	"context"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/ceph/ceph-csi/internal/util"
)

const (
	volumeMounterFuse   = "fuse"
	volumeMounterKernel = "kernel"
	netDev              = "_netdev"
)

var (
	availableMounters []string

	// maps a mountpoint to PID of its FUSE daemon
	fusePidMap    = make(map[string]int)
	fusePidMapMtx sync.Mutex

	fusePidRx = regexp.MustCompile(`(?m)^ceph-fuse\[(.+)\]: starting fuse$`)

	// nolint:gomnd // numbers specify Kernel versions.
	quotaSupport = []util.KernelVersion{
		{
			Version:      4,
			PatchLevel:   17,
			SubLevel:     0,
			ExtraVersion: 0, Distribution: "",
			Backport: false,
		}, // standard 4.17+ versions
		{
			Version:      3,
			PatchLevel:   10,
			SubLevel:     0,
			ExtraVersion: 1062,
			Distribution: ".el7",
			Backport:     true,
		}, // RHEL-7.7
	}
)

// Load available ceph mounters installed on system into availableMounters
// Called from driver.go's Run().
func loadAvailableMounters(conf *util.Config) error {
	// #nosec
	fuseMounterProbe := exec.Command("ceph-fuse", "--version")
	// #nosec
	kernelMounterProbe := exec.Command("mount.ceph")

	err := kernelMounterProbe.Run()
	if err != nil {
		util.ErrorLogMsg("failed to run mount.ceph %v", err)
	} else {
		// fetch the current running kernel info
		release, kvErr := util.GetKernelVersion()
		if kvErr != nil {
			return kvErr
		}

		if conf.ForceKernelCephFS || util.CheckKernelSupport(release, quotaSupport) {
			util.DefaultLog("loaded mounter: %s", volumeMounterKernel)
			availableMounters = append(availableMounters, volumeMounterKernel)
		} else {
			util.DefaultLog("kernel version < 4.17 might not support quota feature, hence not loading kernel client")
		}
	}

	err = fuseMounterProbe.Run()
	if err != nil {
		util.ErrorLogMsg("failed to run ceph-fuse %v", err)
	} else {
		util.DefaultLog("loaded mounter: %s", volumeMounterFuse)
		availableMounters = append(availableMounters, volumeMounterFuse)
	}

	if len(availableMounters) == 0 {
		return errors.New("no ceph mounters found on system")
	}

	return nil
}

type volumeMounter interface {
	mount(ctx context.Context, mountPoint string, cr *util.Credentials, volOptions *volumeOptions) error
	name() string
}

func newMounter(volOptions *volumeOptions) (volumeMounter, error) {
	// Get the mounter from the configuration

	wantMounter := volOptions.Mounter

	// Verify that it's available

	var chosenMounter string

	for _, availMounter := range availableMounters {
		if availMounter == wantMounter {
			chosenMounter = wantMounter
			break
		}
	}

	if chosenMounter == "" {
		// Otherwise pick whatever is left
		chosenMounter = availableMounters[0]
		util.DebugLogMsg("requested mounter: %s, chosen mounter: %s", wantMounter, chosenMounter)
	}

	// Create the mounter

	switch chosenMounter {
	case volumeMounterFuse:
		return &fuseMounter{}, nil
	case volumeMounterKernel:
		return &kernelMounter{}, nil
	}

	return nil, fmt.Errorf("unknown mounter '%s'", chosenMounter)
}

type fuseMounter struct{}

func mountFuse(ctx context.Context, mountPoint string, cr *util.Credentials, volOptions *volumeOptions) error {
	args := []string{
		mountPoint,
		"-m", volOptions.Monitors,
		"-c", util.CephConfigPath,
		"-n", cephEntityClientPrefix + cr.ID, "--keyfile=" + cr.KeyFile,
		"-r", volOptions.RootPath,
	}

	fmo := "nonempty"
	if volOptions.FuseMountOptions != "" {
		fmo += "," + strings.TrimSpace(volOptions.FuseMountOptions)
	}
	args = append(args, "-o", fmo)

	if volOptions.FsName != "" {
		args = append(args, "--client_mds_namespace="+volOptions.FsName)
	}

	_, stderr, err := util.ExecCommand(ctx, "ceph-fuse", args[:]...)
	if err != nil {
		return err
	}

	// Parse the output:
	// We need "starting fuse" meaning the mount is ok
	// and PID of the ceph-fuse daemon for unmount

	match := fusePidRx.FindSubmatch([]byte(stderr))
	// validMatchLength is set to 2 as match is expected
	// to have 2 items, starting fuse and PID of the fuse daemon
	const validMatchLength = 2
	if len(match) != validMatchLength {
		return fmt.Errorf("ceph-fuse failed: %s", stderr)
	}

	pid, err := strconv.Atoi(string(match[1]))
	if err != nil {
		return fmt.Errorf("failed to parse FUSE daemon PID: %w", err)
	}

	fusePidMapMtx.Lock()
	fusePidMap[mountPoint] = pid
	fusePidMapMtx.Unlock()

	return nil
}

func (m *fuseMounter) mount(ctx context.Context, mountPoint string, cr *util.Credentials, volOptions *volumeOptions) error {
	if err := util.CreateMountPoint(mountPoint); err != nil {
		return err
	}

	return mountFuse(ctx, mountPoint, cr, volOptions)
}

func (m *fuseMounter) name() string { return "Ceph FUSE driver" }

type kernelMounter struct{}

func mountKernel(ctx context.Context, mountPoint string, cr *util.Credentials, volOptions *volumeOptions) error {
	if err := execCommandErr(ctx, "modprobe", "ceph"); err != nil {
		return err
	}

	args := []string{
		"-t", "ceph",
		fmt.Sprintf("%s:%s", volOptions.Monitors, volOptions.RootPath),
		mountPoint,
	}

	optionsStr := fmt.Sprintf("name=%s,secretfile=%s", cr.ID, cr.KeyFile)
	mdsNamespace := ""
	if volOptions.FsName != "" {
		mdsNamespace = fmt.Sprintf("mds_namespace=%s", volOptions.FsName)
	}
	optionsStr = util.MountOptionsAdd(optionsStr, mdsNamespace, volOptions.KernelMountOptions, netDev)

	args = append(args, "-o", optionsStr)

	return execCommandErr(ctx, "mount", args[:]...)
}

func (m *kernelMounter) mount(ctx context.Context, mountPoint string, cr *util.Credentials, volOptions *volumeOptions) error {
	if err := util.CreateMountPoint(mountPoint); err != nil {
		return err
	}

	return mountKernel(ctx, mountPoint, cr, volOptions)
}

func (m *kernelMounter) name() string { return "Ceph kernel client" }

func bindMount(ctx context.Context, from, to string, readOnly bool, mntOptions []string) error {
	mntOptionSli := strings.Join(mntOptions, ",")
	if err := execCommandErr(ctx, "mount", "-o", mntOptionSli, from, to); err != nil {
		return fmt.Errorf("failed to bind-mount %s to %s: %v", from, to, err)
	}

	if readOnly {
		mntOptionSli = util.MountOptionsAdd(mntOptionSli, "remount")
		if err := execCommandErr(ctx, "mount", "-o", mntOptionSli, to); err != nil {
			return fmt.Errorf("failed read-only remount of %s: %v", to, err)
		}
	}

	return nil
}

func unmountVolume(ctx context.Context, mountPoint string) error {
	if err := execCommandErr(ctx, "umount", mountPoint); err != nil {
		if strings.Contains(err.Error(), fmt.Sprintf("exit status 32: umount: %s: not mounted", mountPoint)) ||
			strings.Contains(err.Error(), "No such file or directory") {
			return nil
		}
		return err
	}

	fusePidMapMtx.Lock()
	pid, ok := fusePidMap[mountPoint]
	if ok {
		delete(fusePidMap, mountPoint)
	}
	fusePidMapMtx.Unlock()

	if ok {
		p, err := os.FindProcess(pid)
		if err != nil {
			util.WarningLog(ctx, "failed to find process %d: %v", pid, err)
		} else {
			if _, err = p.Wait(); err != nil {
				util.WarningLog(ctx, "%d is not a child process: %v", pid, err)
			}
		}
	}

	return nil
}
<commit_msg>cleanup: return error from execCommandErr() in bindMount()<commit_after>/*
Copyright 2018 The Ceph-CSI 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 cephfs

import (
	"context"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/ceph/ceph-csi/internal/util"
)

const (
	volumeMounterFuse   = "fuse"
	volumeMounterKernel = "kernel"
	netDev              = "_netdev"
)

var (
	availableMounters []string

	// maps a mountpoint to PID of its FUSE daemon
	fusePidMap    = make(map[string]int)
	fusePidMapMtx sync.Mutex

	fusePidRx = regexp.MustCompile(`(?m)^ceph-fuse\[(.+)\]: starting fuse$`)

	// nolint:gomnd // numbers specify Kernel versions.
	quotaSupport = []util.KernelVersion{
		{
			Version:      4,
			PatchLevel:   17,
			SubLevel:     0,
			ExtraVersion: 0, Distribution: "",
			Backport: false,
		}, // standard 4.17+ versions
		{
			Version:      3,
			PatchLevel:   10,
			SubLevel:     0,
			ExtraVersion: 1062,
			Distribution: ".el7",
			Backport:     true,
		}, // RHEL-7.7
	}
)

// Load available ceph mounters installed on system into availableMounters
// Called from driver.go's Run().
func loadAvailableMounters(conf *util.Config) error {
	// #nosec
	fuseMounterProbe := exec.Command("ceph-fuse", "--version")
	// #nosec
	kernelMounterProbe := exec.Command("mount.ceph")

	err := kernelMounterProbe.Run()
	if err != nil {
		util.ErrorLogMsg("failed to run mount.ceph %v", err)
	} else {
		// fetch the current running kernel info
		release, kvErr := util.GetKernelVersion()
		if kvErr != nil {
			return kvErr
		}

		if conf.ForceKernelCephFS || util.CheckKernelSupport(release, quotaSupport) {
			util.DefaultLog("loaded mounter: %s", volumeMounterKernel)
			availableMounters = append(availableMounters, volumeMounterKernel)
		} else {
			util.DefaultLog("kernel version < 4.17 might not support quota feature, hence not loading kernel client")
		}
	}

	err = fuseMounterProbe.Run()
	if err != nil {
		util.ErrorLogMsg("failed to run ceph-fuse %v", err)
	} else {
		util.DefaultLog("loaded mounter: %s", volumeMounterFuse)
		availableMounters = append(availableMounters, volumeMounterFuse)
	}

	if len(availableMounters) == 0 {
		return errors.New("no ceph mounters found on system")
	}

	return nil
}

type volumeMounter interface {
	mount(ctx context.Context, mountPoint string, cr *util.Credentials, volOptions *volumeOptions) error
	name() string
}

func newMounter(volOptions *volumeOptions) (volumeMounter, error) {
	// Get the mounter from the configuration

	wantMounter := volOptions.Mounter

	// Verify that it's available

	var chosenMounter string

	for _, availMounter := range availableMounters {
		if availMounter == wantMounter {
			chosenMounter = wantMounter
			break
		}
	}

	if chosenMounter == "" {
		// Otherwise pick whatever is left
		chosenMounter = availableMounters[0]
		util.DebugLogMsg("requested mounter: %s, chosen mounter: %s", wantMounter, chosenMounter)
	}

	// Create the mounter

	switch chosenMounter {
	case volumeMounterFuse:
		return &fuseMounter{}, nil
	case volumeMounterKernel:
		return &kernelMounter{}, nil
	}

	return nil, fmt.Errorf("unknown mounter '%s'", chosenMounter)
}

type fuseMounter struct{}

func mountFuse(ctx context.Context, mountPoint string, cr *util.Credentials, volOptions *volumeOptions) error {
	args := []string{
		mountPoint,
		"-m", volOptions.Monitors,
		"-c", util.CephConfigPath,
		"-n", cephEntityClientPrefix + cr.ID, "--keyfile=" + cr.KeyFile,
		"-r", volOptions.RootPath,
	}

	fmo := "nonempty"
	if volOptions.FuseMountOptions != "" {
		fmo += "," + strings.TrimSpace(volOptions.FuseMountOptions)
	}
	args = append(args, "-o", fmo)

	if volOptions.FsName != "" {
		args = append(args, "--client_mds_namespace="+volOptions.FsName)
	}

	_, stderr, err := util.ExecCommand(ctx, "ceph-fuse", args[:]...)
	if err != nil {
		return err
	}

	// Parse the output:
	// We need "starting fuse" meaning the mount is ok
	// and PID of the ceph-fuse daemon for unmount

	match := fusePidRx.FindSubmatch([]byte(stderr))
	// validMatchLength is set to 2 as match is expected
	// to have 2 items, starting fuse and PID of the fuse daemon
	const validMatchLength = 2
	if len(match) != validMatchLength {
		return fmt.Errorf("ceph-fuse failed: %s", stderr)
	}

	pid, err := strconv.Atoi(string(match[1]))
	if err != nil {
		return fmt.Errorf("failed to parse FUSE daemon PID: %w", err)
	}

	fusePidMapMtx.Lock()
	fusePidMap[mountPoint] = pid
	fusePidMapMtx.Unlock()

	return nil
}

func (m *fuseMounter) mount(ctx context.Context, mountPoint string, cr *util.Credentials, volOptions *volumeOptions) error {
	if err := util.CreateMountPoint(mountPoint); err != nil {
		return err
	}

	return mountFuse(ctx, mountPoint, cr, volOptions)
}

func (m *fuseMounter) name() string { return "Ceph FUSE driver" }

type kernelMounter struct{}

func mountKernel(ctx context.Context, mountPoint string, cr *util.Credentials, volOptions *volumeOptions) error {
	if err := execCommandErr(ctx, "modprobe", "ceph"); err != nil {
		return err
	}

	args := []string{
		"-t", "ceph",
		fmt.Sprintf("%s:%s", volOptions.Monitors, volOptions.RootPath),
		mountPoint,
	}

	optionsStr := fmt.Sprintf("name=%s,secretfile=%s", cr.ID, cr.KeyFile)
	mdsNamespace := ""
	if volOptions.FsName != "" {
		mdsNamespace = fmt.Sprintf("mds_namespace=%s", volOptions.FsName)
	}
	optionsStr = util.MountOptionsAdd(optionsStr, mdsNamespace, volOptions.KernelMountOptions, netDev)

	args = append(args, "-o", optionsStr)

	return execCommandErr(ctx, "mount", args[:]...)
}

func (m *kernelMounter) mount(ctx context.Context, mountPoint string, cr *util.Credentials, volOptions *volumeOptions) error {
	if err := util.CreateMountPoint(mountPoint); err != nil {
		return err
	}

	return mountKernel(ctx, mountPoint, cr, volOptions)
}

func (m *kernelMounter) name() string { return "Ceph kernel client" }

func bindMount(ctx context.Context, from, to string, readOnly bool, mntOptions []string) error {
	mntOptionSli := strings.Join(mntOptions, ",")
	if err := execCommandErr(ctx, "mount", "-o", mntOptionSli, from, to); err != nil {
		return fmt.Errorf("failed to bind-mount %s to %s: %w", from, to, err)
	}

	if readOnly {
		mntOptionSli = util.MountOptionsAdd(mntOptionSli, "remount")
		if err := execCommandErr(ctx, "mount", "-o", mntOptionSli, to); err != nil {
			return fmt.Errorf("failed read-only remount of %s: %w", to, err)
		}
	}

	return nil
}

func unmountVolume(ctx context.Context, mountPoint string) error {
	if err := execCommandErr(ctx, "umount", mountPoint); err != nil {
		if strings.Contains(err.Error(), fmt.Sprintf("exit status 32: umount: %s: not mounted", mountPoint)) ||
			strings.Contains(err.Error(), "No such file or directory") {
			return nil
		}
		return err
	}

	fusePidMapMtx.Lock()
	pid, ok := fusePidMap[mountPoint]
	if ok {
		delete(fusePidMap, mountPoint)
	}
	fusePidMapMtx.Unlock()

	if ok {
		p, err := os.FindProcess(pid)
		if err != nil {
			util.WarningLog(ctx, "failed to find process %d: %v", pid, err)
		} else {
			if _, err = p.Wait(); err != nil {
				util.WarningLog(ctx, "%d is not a child process: %v", pid, err)
			}
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// 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 observability sets up and configures observability tools.
package observability

// ExporterType represents a type of observability exporter.
type ExporterType string

const (
	ExporterStackdriver ExporterType = "STACKDRIVER"
	ExporterPrometheus  ExporterType = "PROMETHEUS"
	ExporterOCAgent     ExporterType = "OCAGENT"
	ExporterNoop        ExporterType = "NOOP"
)

// Config holds all of the configuration options for the observability exporter
type Config struct {
	ExporterType ExporterType `env:"OBSERVABILITY_EXPORTER, default=STACKDRIVER"`

	OpenCensusConfig
	StackdriverConfig
	OCAgentConfig
}

// OpenCensusConfig holds the configuration options for the open census exporter
type OpenCensusConfig struct {
	TraceProbabilitySampleRate float64 `env:"TRACE_PROBABILITY, default=0.40"`
}

// StackdriverConfig holds the configuration options for the stackdriver exporter
type StackdriverConfig struct {
	ProjectID string `env:"PROJECT_ID, default:$GOOGLE_CLOUD_PROJECT"`
}

// OCAgentConfig holds the configuration options for the default opencensus exporter
type OCAgentConfig struct {
	Insecure bool   `env:"OCAGENT_INSECURE, default=true"`
	Endpoint string `env:"OCAGENT_TRACE_EXPORTER_ENDPOINT"`
}
<commit_msg>Use correct syntax for default (#651)<commit_after>// Copyright 2020 Google LLC
//
// 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 observability sets up and configures observability tools.
package observability

// ExporterType represents a type of observability exporter.
type ExporterType string

const (
	ExporterStackdriver ExporterType = "STACKDRIVER"
	ExporterPrometheus  ExporterType = "PROMETHEUS"
	ExporterOCAgent     ExporterType = "OCAGENT"
	ExporterNoop        ExporterType = "NOOP"
)

// Config holds all of the configuration options for the observability exporter
type Config struct {
	ExporterType ExporterType `env:"OBSERVABILITY_EXPORTER, default=STACKDRIVER"`

	OpenCensusConfig
	StackdriverConfig
	OCAgentConfig
}

// OpenCensusConfig holds the configuration options for the open census exporter
type OpenCensusConfig struct {
	TraceProbabilitySampleRate float64 `env:"TRACE_PROBABILITY, default=0.40"`
}

// StackdriverConfig holds the configuration options for the stackdriver exporter
type StackdriverConfig struct {
	ProjectID string `env:"PROJECT_ID, default=$GOOGLE_CLOUD_PROJECT"`
}

// OCAgentConfig holds the configuration options for the default opencensus exporter
type OCAgentConfig struct {
	Insecure bool   `env:"OCAGENT_INSECURE, default=true"`
	Endpoint string `env:"OCAGENT_TRACE_EXPORTER_ENDPOINT"`
}
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 restore

import (
	"io/ioutil"
	"log"
	"os"
	"path"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/dag"
	"github.com/jacobsa/comeback/internal/fs"
	"github.com/jacobsa/comeback/internal/repr"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"github.com/jacobsa/timeutil"
)

func TestVisitor(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func marshalFileOrDie(content []byte) (chunk []byte) {
	chunk, err := repr.MarshalFile(content)
	AssertEq(nil, err)
	return
}

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type VisitorTest struct {
	ctx       context.Context
	blobStore blob.Store

	// A directory that is deleted when the test completes.
	dir string

	// A visitor configured with the above directory.
	visitor dag.Visitor
}

var _ SetUpInterface = &VisitorTest{}
var _ TearDownInterface = &VisitorTest{}

func init() { RegisterTestSuite(&VisitorTest{}) }

func (t *VisitorTest) SetUp(ti *TestInfo) {
	var err error
	t.ctx = ti.Ctx

	// Create the blob store.
	t.blobStore, err = newFakeBlobStore(t.ctx)
	AssertEq(nil, err)

	// Set up the directory.
	t.dir, err = ioutil.TempDir("", "visitor_test")
	AssertEq(nil, err)

	// Create the visitor.
	t.visitor = newVisitor(t.dir, t.blobStore, log.New(ioutil.Discard, "", 0))
}

func (t *VisitorTest) TearDown() {
	var err error

	err = os.RemoveAll(t.dir)
	AssertEq(nil, err)
}

func (t *VisitorTest) call(n *node) (err error) {
	err = t.visitor.Visit(t.ctx, n)
	return
}

func (t *VisitorTest) store(b []byte) (s blob.Score, err error) {
	s, err = t.blobStore.Store(
		t.ctx,
		&blob.StoreRequest{Blob: b})

	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *VisitorTest) File_MissingBlob() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := marshalFileOrDie([]byte("burrito"))
	score1 := blob.ComputeScore(chunk1)

	chunk2 := marshalFileOrDie([]byte("enchilada"))
	score2, err := t.store(chunk2)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0700,
			Scores:      []blob.Score{score0, score1, score2},
		},
	}

	// Call
	err = t.call(n)

	ExpectThat(err, Error(HasSubstr(score1.Hex())))
	ExpectThat(err, Error(HasSubstr("not found")))
}

func (t *VisitorTest) File_CorruptBlob() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := []byte("burrito")
	score1, err := t.store(chunk1)
	AssertEq(nil, err)

	chunk2 := marshalFileOrDie([]byte("enchilada"))
	score2, err := t.store(chunk2)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0700,
			Scores:      []blob.Score{score0, score1, score2},
		},
	}

	// Call
	err = t.call(n)

	ExpectThat(err, Error(HasSubstr(score1.Hex())))
	ExpectThat(err, Error(HasSubstr("UnmarshalFile")))
}

func (t *VisitorTest) File_Empty() {
	AssertTrue(false, "TODO")
}

func (t *VisitorTest) File_NonEmpty() {
	AssertTrue(false, "TODO")
}

func (t *VisitorTest) File_PermsAndModTime() {
	AssertTrue(false, "TODO")
}

func (t *VisitorTest) Directory() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeDirectory,
			Name:        "baz",
			Permissions: 0741,
			MTime:       time.Date(2012, time.August, 15, 12, 56, 00, 0, time.Local),
		},
	}

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	// Stat
	p := path.Join(t.dir, n.RelPath)
	fi, err := os.Lstat(p)
	AssertEq(nil, err)

	ExpectEq("baz", fi.Name())
	ExpectEq(0741|os.ModeDir, fi.Mode())
	ExpectThat(fi.ModTime(), timeutil.TimeEq(n.Info.MTime))
}

func (t *VisitorTest) Directory_AlreadyExists() {
	var err error

	n := &node{
		RelPath: "foo",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeDirectory,
			Name:        "foo",
			Permissions: 0741,
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Create the directory.
	err = os.Mkdir(p, 0700)
	AssertEq(nil, err)

	// The call should succeed and the permissions should be updated as usual.
	err = t.call(n)
	AssertEq(nil, err)

	fi, err := os.Lstat(p)
	AssertEq(nil, err)
	ExpectEq(0741|os.ModeDir, fi.Mode())
}

func (t *VisitorTest) Symlink() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeSymlink,
			Name:        "baz",
			Permissions: 0741,
			MTime:       time.Date(2012, time.August, 15, 12, 56, 00, 0, time.Local),
			Target:      "taco/burrito",
		},
	}

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	// Stat
	p := path.Join(t.dir, n.RelPath)
	fi, err := os.Lstat(p)
	AssertEq(nil, err)

	ExpectEq("baz", fi.Name())
	ExpectEq(0741|os.ModeSymlink, fi.Mode())
	ExpectThat(fi.ModTime(), timeutil.TimeEq(n.Info.MTime))

	// Readlink
	target, err := os.Readlink(p)

	AssertEq(nil, err)
	ExpectEq("taco/burrito", target)
}

func (t *VisitorTest) ParentDirsAlreadyExist() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeSymlink,
			Name:        "baz",
			Permissions: 0700,
			Target:      "taco/burrito",
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Create leading directories.
	err = os.MkdirAll(path.Dir(p), 0700)
	AssertEq(nil, err)

	// Call. Everything should work as usual.
	err = t.call(n)
	AssertEq(nil, err)

	target, err := os.Readlink(p)
	AssertEq(nil, err)
	ExpectEq("taco/burrito", target)
}
<commit_msg>VisitorTest.File_NonEmpty<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 restore

import (
	"io/ioutil"
	"log"
	"os"
	"path"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/comeback/internal/dag"
	"github.com/jacobsa/comeback/internal/fs"
	"github.com/jacobsa/comeback/internal/repr"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"github.com/jacobsa/timeutil"
)

func TestVisitor(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func marshalFileOrDie(content []byte) (chunk []byte) {
	chunk, err := repr.MarshalFile(content)
	AssertEq(nil, err)
	return
}

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type VisitorTest struct {
	ctx       context.Context
	blobStore blob.Store

	// A directory that is deleted when the test completes.
	dir string

	// A visitor configured with the above directory.
	visitor dag.Visitor
}

var _ SetUpInterface = &VisitorTest{}
var _ TearDownInterface = &VisitorTest{}

func init() { RegisterTestSuite(&VisitorTest{}) }

func (t *VisitorTest) SetUp(ti *TestInfo) {
	var err error
	t.ctx = ti.Ctx

	// Create the blob store.
	t.blobStore, err = newFakeBlobStore(t.ctx)
	AssertEq(nil, err)

	// Set up the directory.
	t.dir, err = ioutil.TempDir("", "visitor_test")
	AssertEq(nil, err)

	// Create the visitor.
	t.visitor = newVisitor(t.dir, t.blobStore, log.New(ioutil.Discard, "", 0))
}

func (t *VisitorTest) TearDown() {
	var err error

	err = os.RemoveAll(t.dir)
	AssertEq(nil, err)
}

func (t *VisitorTest) call(n *node) (err error) {
	err = t.visitor.Visit(t.ctx, n)
	return
}

func (t *VisitorTest) store(b []byte) (s blob.Score, err error) {
	s, err = t.blobStore.Store(
		t.ctx,
		&blob.StoreRequest{Blob: b})

	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *VisitorTest) File_MissingBlob() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := marshalFileOrDie([]byte("burrito"))
	score1 := blob.ComputeScore(chunk1)

	chunk2 := marshalFileOrDie([]byte("enchilada"))
	score2, err := t.store(chunk2)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0400,
			Scores:      []blob.Score{score0, score1, score2},
		},
	}

	// Call
	err = t.call(n)

	ExpectThat(err, Error(HasSubstr(score1.Hex())))
	ExpectThat(err, Error(HasSubstr("not found")))
}

func (t *VisitorTest) File_CorruptBlob() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := []byte("burrito")
	score1, err := t.store(chunk1)
	AssertEq(nil, err)

	chunk2 := marshalFileOrDie([]byte("enchilada"))
	score2, err := t.store(chunk2)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0400,
			Scores:      []blob.Score{score0, score1, score2},
		},
	}

	// Call
	err = t.call(n)

	ExpectThat(err, Error(HasSubstr(score1.Hex())))
	ExpectThat(err, Error(HasSubstr("UnmarshalFile")))
}

func (t *VisitorTest) File_Empty() {
	AssertTrue(false, "TODO")
}

func (t *VisitorTest) File_NonEmpty() {
	var err error

	// Blobs
	chunk0 := marshalFileOrDie([]byte("taco"))
	score0, err := t.store(chunk0)
	AssertEq(nil, err)

	chunk1 := []byte("burrito")
	score1, err := t.store(chunk1)
	AssertEq(nil, err)

	// Node
	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeFile,
			Name:        "baz",
			Permissions: 0400,
			Scores:      []blob.Score{score0, score1},
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	contents, err := ioutil.ReadFile(p)
	AssertEq(nil, err)
	ExpectEq("tacoburrito", string(contents))
}

func (t *VisitorTest) File_PermsAndModTime() {
	AssertTrue(false, "TODO")
}

func (t *VisitorTest) Directory() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeDirectory,
			Name:        "baz",
			Permissions: 0741,
			MTime:       time.Date(2012, time.August, 15, 12, 56, 00, 0, time.Local),
		},
	}

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	// Stat
	p := path.Join(t.dir, n.RelPath)
	fi, err := os.Lstat(p)
	AssertEq(nil, err)

	ExpectEq("baz", fi.Name())
	ExpectEq(0741|os.ModeDir, fi.Mode())
	ExpectThat(fi.ModTime(), timeutil.TimeEq(n.Info.MTime))
}

func (t *VisitorTest) Directory_AlreadyExists() {
	var err error

	n := &node{
		RelPath: "foo",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeDirectory,
			Name:        "foo",
			Permissions: 0741,
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Create the directory.
	err = os.Mkdir(p, 0700)
	AssertEq(nil, err)

	// The call should succeed and the permissions should be updated as usual.
	err = t.call(n)
	AssertEq(nil, err)

	fi, err := os.Lstat(p)
	AssertEq(nil, err)
	ExpectEq(0741|os.ModeDir, fi.Mode())
}

func (t *VisitorTest) Symlink() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeSymlink,
			Name:        "baz",
			Permissions: 0741,
			MTime:       time.Date(2012, time.August, 15, 12, 56, 00, 0, time.Local),
			Target:      "taco/burrito",
		},
	}

	// Call
	err = t.call(n)
	AssertEq(nil, err)

	// Stat
	p := path.Join(t.dir, n.RelPath)
	fi, err := os.Lstat(p)
	AssertEq(nil, err)

	ExpectEq("baz", fi.Name())
	ExpectEq(0741|os.ModeSymlink, fi.Mode())
	ExpectThat(fi.ModTime(), timeutil.TimeEq(n.Info.MTime))

	// Readlink
	target, err := os.Readlink(p)

	AssertEq(nil, err)
	ExpectEq("taco/burrito", target)
}

func (t *VisitorTest) ParentDirsAlreadyExist() {
	var err error

	n := &node{
		RelPath: "foo/bar/baz",
		Info: fs.DirectoryEntry{
			Type:        fs.TypeSymlink,
			Name:        "baz",
			Permissions: 0700,
			Target:      "taco/burrito",
		},
	}

	p := path.Join(t.dir, n.RelPath)

	// Create leading directories.
	err = os.MkdirAll(path.Dir(p), 0700)
	AssertEq(nil, err)

	// Call. Everything should work as usual.
	err = t.call(n)
	AssertEq(nil, err)

	target, err := os.Readlink(p)
	AssertEq(nil, err)
	ExpectEq("taco/burrito", target)
}
<|endoftext|>


<commit_before>// Copyright 2015 Hajime Hoshi
//
// 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 js

import (
	"encoding/hex"
	"syscall/js"
	"unicode"

	"github.com/hajimehoshi/ebiten/v2/internal/driver"
	"github.com/hajimehoshi/ebiten/v2/internal/jsutil"
)

var (
	stringKeydown    = js.ValueOf("keydown")
	stringKeypress   = js.ValueOf("keypress")
	stringKeyup      = js.ValueOf("keyup")
	stringMousedown  = js.ValueOf("mousedown")
	stringMouseup    = js.ValueOf("mouseup")
	stringMousemove  = js.ValueOf("mousemove")
	stringWheel      = js.ValueOf("wheel")
	stringTouchstart = js.ValueOf("touchstart")
	stringTouchend   = js.ValueOf("touchend")
	stringTouchmove  = js.ValueOf("touchmove")
)

var jsKeys []js.Value

func init() {
	for _, k := range driverKeyToJSKey {
		jsKeys = append(jsKeys, k)
	}
}

func jsKeyToID(key js.Value) int {
	// js.Value cannot be used as a map key.
	// As the number of keys is around 100, just a dumb loop should work.
	for i, k := range jsKeys {
		if jsutil.Equal(k, key) {
			return i
		}
	}
	return -1
}

type pos struct {
	X int
	Y int
}

type gamepad struct {
	name          string
	axisNum       int
	axes          [16]float64
	buttonNum     int
	buttonPressed [256]bool
}

type Input struct {
	keyPressed         map[int]bool
	keyPressedEdge     map[int]bool
	mouseButtonPressed map[int]bool
	cursorX            int
	cursorY            int
	wheelX             float64
	wheelY             float64
	gamepads           map[driver.GamepadID]gamepad
	touches            map[driver.TouchID]pos
	runeBuffer         []rune
	ui                 *UserInterface
}

func (i *Input) CursorPosition() (x, y int) {
	xf, yf := i.ui.context.AdjustPosition(float64(i.cursorX), float64(i.cursorY), i.ui.DeviceScaleFactor())
	return int(xf), int(yf)
}

func (i *Input) GamepadSDLID(id driver.GamepadID) string {
	// This emulates the implementation of EMSCRIPTEN_JoystickGetDeviceGUID.
	// https://hg.libsdl.org/SDL/file/bc90ce38f1e2/src/joystick/emscripten/SDL_sysjoystick.c#l385
	if len(i.gamepads) <= int(id) {
		return ""
	}
	var sdlid [16]byte
	copy(sdlid[:], []byte(i.gamepads[id].name))
	return hex.EncodeToString(sdlid[:])
}

// GamepadName returns a string containing some information about the controller.
// A PS2 controller returned "810-3-USB Gamepad" on Firefox
// A Xbox 360 controller returned "xinput" on Firefox and "Xbox 360 Controller (XInput STANDARD GAMEPAD)" on Chrome
func (i *Input) GamepadName(id driver.GamepadID) string {
	if len(i.gamepads) <= int(id) {
		return ""
	}
	return i.gamepads[id].name
}

func (i *Input) GamepadIDs() []driver.GamepadID {
	if len(i.gamepads) == 0 {
		return nil
	}

	var r []driver.GamepadID
	for id := range i.gamepads {
		r = append(r, id)
	}
	return r
}

func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
	g, ok := i.gamepads[id]
	if !ok {
		return 0
	}
	return g.axisNum
}

func (i *Input) GamepadAxis(id driver.GamepadID, axis int) float64 {
	g, ok := i.gamepads[id]
	if !ok {
		return 0
	}
	if g.axisNum <= axis {
		return 0
	}
	return g.axes[axis]
}

func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
	g, ok := i.gamepads[id]
	if !ok {
		return 0
	}
	return g.buttonNum
}

func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
	g, ok := i.gamepads[id]
	if !ok {
		return false
	}
	if g.buttonNum <= int(button) {
		return false
	}
	return g.buttonPressed[button]
}

func (i *Input) TouchIDs() []driver.TouchID {
	if len(i.touches) == 0 {
		return nil
	}

	var ids []driver.TouchID
	for id := range i.touches {
		ids = append(ids, id)
	}
	return ids
}

func (i *Input) TouchPosition(id driver.TouchID) (x, y int) {
	d := i.ui.DeviceScaleFactor()
	for tid, pos := range i.touches {
		if id == tid {
			x, y := i.ui.context.AdjustPosition(float64(pos.X), float64(pos.Y), d)
			return int(x), int(y)
		}
	}
	return 0, 0
}

func (i *Input) RuneBuffer() []rune {
	rs := make([]rune, len(i.runeBuffer))
	copy(rs, i.runeBuffer)
	return rs
}

func (i *Input) resetForFrame() {
	i.runeBuffer = nil
	i.wheelX = 0
	i.wheelY = 0
}

func (i *Input) IsKeyPressed(key driver.Key) bool {
	if i.keyPressed != nil {
		if i.keyPressed[jsKeyToID(driverKeyToJSKey[key])] {
			return true
		}
	}
	if i.keyPressedEdge != nil {
		for c, k := range edgeKeyCodeToDriverKey {
			if k != key {
				continue
			}
			if i.keyPressedEdge[c] {
				return true
			}
		}
	}
	return false
}

var codeToMouseButton = map[int]driver.MouseButton{
	0: driver.MouseButtonLeft,
	1: driver.MouseButtonMiddle,
	2: driver.MouseButtonRight,
}

func (i *Input) IsMouseButtonPressed(button driver.MouseButton) bool {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	for c, b := range codeToMouseButton {
		if b != button {
			continue
		}
		if i.mouseButtonPressed[c] {
			return true
		}
	}
	return false
}

func (i *Input) Wheel() (xoff, yoff float64) {
	return i.wheelX, i.wheelY
}

func (i *Input) keyDown(code js.Value) {
	if i.keyPressed == nil {
		i.keyPressed = map[int]bool{}
	}
	i.keyPressed[jsKeyToID(code)] = true
}

func (i *Input) keyUp(code js.Value) {
	if i.keyPressed == nil {
		i.keyPressed = map[int]bool{}
	}
	i.keyPressed[jsKeyToID(code)] = false
}

func (i *Input) keyDownEdge(code int) {
	if i.keyPressedEdge == nil {
		i.keyPressedEdge = map[int]bool{}
	}
	i.keyPressedEdge[code] = true
}

func (i *Input) keyUpEdge(code int) {
	if i.keyPressedEdge == nil {
		i.keyPressedEdge = map[int]bool{}
	}
	i.keyPressedEdge[code] = false
}

func (i *Input) mouseDown(code int) {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	i.mouseButtonPressed[code] = true
}

func (i *Input) mouseUp(code int) {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	i.mouseButtonPressed[code] = false
}

func (i *Input) setMouseCursor(x, y int) {
	i.cursorX, i.cursorY = x, y
}

func (i *Input) updateGamepads() {
	nav := js.Global().Get("navigator")
	if !nav.Truthy() {
		return
	}

	if !nav.Get("getGamepads").Truthy() {
		return
	}

	i.gamepads = map[driver.GamepadID]gamepad{}

	gamepads := nav.Call("getGamepads")
	l := gamepads.Length()
	for idx := 0; idx < l; idx++ {
		gp := gamepads.Index(idx)
		if !gp.Truthy() {
			continue
		}

		id := driver.GamepadID(gp.Get("index").Int())
		g := gamepad{}
		g.name = gp.Get("id").String()

		axes := gp.Get("axes")
		axesNum := axes.Get("length").Int()
		g.axisNum = axesNum
		for a := 0; a < len(g.axes); a++ {
			if axesNum <= a {
				break
			}
			g.axes[a] = axes.Index(a).Float()
		}

		buttons := gp.Get("buttons")
		buttonsNum := buttons.Get("length").Int()
		g.buttonNum = buttonsNum
		for b := 0; b < len(g.buttonPressed); b++ {
			if buttonsNum <= b {
				break
			}
			g.buttonPressed[b] = buttons.Index(b).Get("pressed").Bool()
		}

		i.gamepads[id] = g
	}
}

func (i *Input) updateFromEvent(e js.Value) {
	// Avoid using js.Value.String() as String creates a Uint8Array via a TextEncoder and causes a heavy
	// overhead (#1437).
	switch t := e.Get("type"); {
	case jsutil.Equal(t, stringKeydown):
		c := e.Get("code")
		if c.Type() != js.TypeString {
			code := e.Get("keyCode").Int()
			if edgeKeyCodeToDriverKey[code] == driver.KeyUp ||
				edgeKeyCodeToDriverKey[code] == driver.KeyDown ||
				edgeKeyCodeToDriverKey[code] == driver.KeyLeft ||
				edgeKeyCodeToDriverKey[code] == driver.KeyRight ||
				edgeKeyCodeToDriverKey[code] == driver.KeyBackspace ||
				edgeKeyCodeToDriverKey[code] == driver.KeyTab {
				e.Call("preventDefault")
			}
			i.keyDownEdge(code)
			return
		}
		if jsutil.Equal(c, driverKeyToJSKey[driver.KeyUp]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyDown]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyLeft]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyRight]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyBackspace]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyTab]) {
			e.Call("preventDefault")
		}
		i.keyDown(c)
	case jsutil.Equal(t, stringKeypress):
		if r := rune(e.Get("charCode").Int()); unicode.IsPrint(r) {
			i.runeBuffer = append(i.runeBuffer, r)
		}
	case jsutil.Equal(t, stringKeyup):
		if e.Get("code").Type() != js.TypeString {
			// Assume that UA is Edge.
			code := e.Get("keyCode").Int()
			i.keyUpEdge(code)
			return
		}
		i.keyUp(e.Get("code"))
	case jsutil.Equal(t, stringMousedown):
		button := e.Get("button").Int()
		i.mouseDown(button)
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringMouseup):
		button := e.Get("button").Int()
		i.mouseUp(button)
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringMousemove):
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringWheel):
		// TODO: What if e.deltaMode is not DOM_DELTA_PIXEL?
		i.wheelX = -e.Get("deltaX").Float()
		i.wheelY = -e.Get("deltaY").Float()
	case jsutil.Equal(t, stringTouchstart) || jsutil.Equal(t, stringTouchend) || jsutil.Equal(t, stringTouchmove):
		i.updateTouchesFromEvent(e)
	}
}

func (i *Input) setMouseCursorFromEvent(e js.Value) {
	x, y := e.Get("clientX").Int(), e.Get("clientY").Int()
	i.setMouseCursor(x, y)
}

func (i *Input) updateTouchesFromEvent(e js.Value) {
	j := e.Get("targetTouches")
	ts := map[driver.TouchID]pos{}
	for i := 0; i < j.Length(); i++ {
		jj := j.Call("item", i)
		id := driver.TouchID(jj.Get("identifier").Int())
		ts[id] = pos{
			X: jj.Get("clientX").Int(),
			Y: jj.Get("clientY").Int(),
		}
	}
	i.touches = ts
}

func (i *Input) updateForGo2Cpp() {
	if !go2cpp.Truthy() {
		return
	}

	i.touches = map[driver.TouchID]pos{}
	touchCount := go2cpp.Get("touchCount").Int()
	for idx := 0; idx < touchCount; idx++ {
		id := go2cpp.Call("getTouchId", idx)
		x := go2cpp.Call("getTouchX", idx)
		y := go2cpp.Call("getTouchY", idx)
		i.touches[driver.TouchID(id.Int())] = pos{
			X: x.Int(),
			Y: y.Int(),
		}
	}
}
<commit_msg>uidriver/js: Implement gamepads for go2cpp<commit_after>// Copyright 2015 Hajime Hoshi
//
// 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 js

import (
	"encoding/hex"
	"syscall/js"
	"unicode"

	"github.com/hajimehoshi/ebiten/v2/internal/driver"
	"github.com/hajimehoshi/ebiten/v2/internal/jsutil"
)

var (
	stringKeydown    = js.ValueOf("keydown")
	stringKeypress   = js.ValueOf("keypress")
	stringKeyup      = js.ValueOf("keyup")
	stringMousedown  = js.ValueOf("mousedown")
	stringMouseup    = js.ValueOf("mouseup")
	stringMousemove  = js.ValueOf("mousemove")
	stringWheel      = js.ValueOf("wheel")
	stringTouchstart = js.ValueOf("touchstart")
	stringTouchend   = js.ValueOf("touchend")
	stringTouchmove  = js.ValueOf("touchmove")
)

var jsKeys []js.Value

func init() {
	for _, k := range driverKeyToJSKey {
		jsKeys = append(jsKeys, k)
	}
}

func jsKeyToID(key js.Value) int {
	// js.Value cannot be used as a map key.
	// As the number of keys is around 100, just a dumb loop should work.
	for i, k := range jsKeys {
		if jsutil.Equal(k, key) {
			return i
		}
	}
	return -1
}

type pos struct {
	X int
	Y int
}

type gamepad struct {
	name          string
	axisNum       int
	axes          [16]float64
	buttonNum     int
	buttonPressed [256]bool
}

type Input struct {
	keyPressed         map[int]bool
	keyPressedEdge     map[int]bool
	mouseButtonPressed map[int]bool
	cursorX            int
	cursorY            int
	wheelX             float64
	wheelY             float64
	gamepads           map[driver.GamepadID]gamepad
	touches            map[driver.TouchID]pos
	runeBuffer         []rune
	ui                 *UserInterface
}

func (i *Input) CursorPosition() (x, y int) {
	xf, yf := i.ui.context.AdjustPosition(float64(i.cursorX), float64(i.cursorY), i.ui.DeviceScaleFactor())
	return int(xf), int(yf)
}

func (i *Input) GamepadSDLID(id driver.GamepadID) string {
	// This emulates the implementation of EMSCRIPTEN_JoystickGetDeviceGUID.
	// https://hg.libsdl.org/SDL/file/bc90ce38f1e2/src/joystick/emscripten/SDL_sysjoystick.c#l385
	if len(i.gamepads) <= int(id) {
		return ""
	}
	var sdlid [16]byte
	copy(sdlid[:], []byte(i.gamepads[id].name))
	return hex.EncodeToString(sdlid[:])
}

// GamepadName returns a string containing some information about the controller.
// A PS2 controller returned "810-3-USB Gamepad" on Firefox
// A Xbox 360 controller returned "xinput" on Firefox and "Xbox 360 Controller (XInput STANDARD GAMEPAD)" on Chrome
func (i *Input) GamepadName(id driver.GamepadID) string {
	if len(i.gamepads) <= int(id) {
		return ""
	}
	return i.gamepads[id].name
}

func (i *Input) GamepadIDs() []driver.GamepadID {
	if len(i.gamepads) == 0 {
		return nil
	}

	var r []driver.GamepadID
	for id := range i.gamepads {
		r = append(r, id)
	}
	return r
}

func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
	g, ok := i.gamepads[id]
	if !ok {
		return 0
	}
	return g.axisNum
}

func (i *Input) GamepadAxis(id driver.GamepadID, axis int) float64 {
	g, ok := i.gamepads[id]
	if !ok {
		return 0
	}
	if g.axisNum <= axis {
		return 0
	}
	return g.axes[axis]
}

func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
	g, ok := i.gamepads[id]
	if !ok {
		return 0
	}
	return g.buttonNum
}

func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
	g, ok := i.gamepads[id]
	if !ok {
		return false
	}
	if g.buttonNum <= int(button) {
		return false
	}
	return g.buttonPressed[button]
}

func (i *Input) TouchIDs() []driver.TouchID {
	if len(i.touches) == 0 {
		return nil
	}

	var ids []driver.TouchID
	for id := range i.touches {
		ids = append(ids, id)
	}
	return ids
}

func (i *Input) TouchPosition(id driver.TouchID) (x, y int) {
	d := i.ui.DeviceScaleFactor()
	for tid, pos := range i.touches {
		if id == tid {
			x, y := i.ui.context.AdjustPosition(float64(pos.X), float64(pos.Y), d)
			return int(x), int(y)
		}
	}
	return 0, 0
}

func (i *Input) RuneBuffer() []rune {
	rs := make([]rune, len(i.runeBuffer))
	copy(rs, i.runeBuffer)
	return rs
}

func (i *Input) resetForFrame() {
	i.runeBuffer = nil
	i.wheelX = 0
	i.wheelY = 0
}

func (i *Input) IsKeyPressed(key driver.Key) bool {
	if i.keyPressed != nil {
		if i.keyPressed[jsKeyToID(driverKeyToJSKey[key])] {
			return true
		}
	}
	if i.keyPressedEdge != nil {
		for c, k := range edgeKeyCodeToDriverKey {
			if k != key {
				continue
			}
			if i.keyPressedEdge[c] {
				return true
			}
		}
	}
	return false
}

var codeToMouseButton = map[int]driver.MouseButton{
	0: driver.MouseButtonLeft,
	1: driver.MouseButtonMiddle,
	2: driver.MouseButtonRight,
}

func (i *Input) IsMouseButtonPressed(button driver.MouseButton) bool {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	for c, b := range codeToMouseButton {
		if b != button {
			continue
		}
		if i.mouseButtonPressed[c] {
			return true
		}
	}
	return false
}

func (i *Input) Wheel() (xoff, yoff float64) {
	return i.wheelX, i.wheelY
}

func (i *Input) keyDown(code js.Value) {
	if i.keyPressed == nil {
		i.keyPressed = map[int]bool{}
	}
	i.keyPressed[jsKeyToID(code)] = true
}

func (i *Input) keyUp(code js.Value) {
	if i.keyPressed == nil {
		i.keyPressed = map[int]bool{}
	}
	i.keyPressed[jsKeyToID(code)] = false
}

func (i *Input) keyDownEdge(code int) {
	if i.keyPressedEdge == nil {
		i.keyPressedEdge = map[int]bool{}
	}
	i.keyPressedEdge[code] = true
}

func (i *Input) keyUpEdge(code int) {
	if i.keyPressedEdge == nil {
		i.keyPressedEdge = map[int]bool{}
	}
	i.keyPressedEdge[code] = false
}

func (i *Input) mouseDown(code int) {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	i.mouseButtonPressed[code] = true
}

func (i *Input) mouseUp(code int) {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	i.mouseButtonPressed[code] = false
}

func (i *Input) setMouseCursor(x, y int) {
	i.cursorX, i.cursorY = x, y
}

func (i *Input) updateGamepads() {
	nav := js.Global().Get("navigator")
	if !nav.Truthy() {
		return
	}

	if !nav.Get("getGamepads").Truthy() {
		return
	}

	i.gamepads = map[driver.GamepadID]gamepad{}

	gamepads := nav.Call("getGamepads")
	l := gamepads.Length()
	for idx := 0; idx < l; idx++ {
		gp := gamepads.Index(idx)
		if !gp.Truthy() {
			continue
		}

		id := driver.GamepadID(gp.Get("index").Int())
		g := gamepad{}
		g.name = gp.Get("id").String()

		axes := gp.Get("axes")
		axesNum := axes.Get("length").Int()
		g.axisNum = axesNum
		for a := 0; a < len(g.axes); a++ {
			if axesNum <= a {
				break
			}
			g.axes[a] = axes.Index(a).Float()
		}

		buttons := gp.Get("buttons")
		buttonsNum := buttons.Get("length").Int()
		g.buttonNum = buttonsNum
		for b := 0; b < len(g.buttonPressed); b++ {
			if buttonsNum <= b {
				break
			}
			g.buttonPressed[b] = buttons.Index(b).Get("pressed").Bool()
		}

		i.gamepads[id] = g
	}
}

func (i *Input) updateFromEvent(e js.Value) {
	// Avoid using js.Value.String() as String creates a Uint8Array via a TextEncoder and causes a heavy
	// overhead (#1437).
	switch t := e.Get("type"); {
	case jsutil.Equal(t, stringKeydown):
		c := e.Get("code")
		if c.Type() != js.TypeString {
			code := e.Get("keyCode").Int()
			if edgeKeyCodeToDriverKey[code] == driver.KeyUp ||
				edgeKeyCodeToDriverKey[code] == driver.KeyDown ||
				edgeKeyCodeToDriverKey[code] == driver.KeyLeft ||
				edgeKeyCodeToDriverKey[code] == driver.KeyRight ||
				edgeKeyCodeToDriverKey[code] == driver.KeyBackspace ||
				edgeKeyCodeToDriverKey[code] == driver.KeyTab {
				e.Call("preventDefault")
			}
			i.keyDownEdge(code)
			return
		}
		if jsutil.Equal(c, driverKeyToJSKey[driver.KeyUp]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyDown]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyLeft]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyRight]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyBackspace]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyTab]) {
			e.Call("preventDefault")
		}
		i.keyDown(c)
	case jsutil.Equal(t, stringKeypress):
		if r := rune(e.Get("charCode").Int()); unicode.IsPrint(r) {
			i.runeBuffer = append(i.runeBuffer, r)
		}
	case jsutil.Equal(t, stringKeyup):
		if e.Get("code").Type() != js.TypeString {
			// Assume that UA is Edge.
			code := e.Get("keyCode").Int()
			i.keyUpEdge(code)
			return
		}
		i.keyUp(e.Get("code"))
	case jsutil.Equal(t, stringMousedown):
		button := e.Get("button").Int()
		i.mouseDown(button)
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringMouseup):
		button := e.Get("button").Int()
		i.mouseUp(button)
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringMousemove):
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringWheel):
		// TODO: What if e.deltaMode is not DOM_DELTA_PIXEL?
		i.wheelX = -e.Get("deltaX").Float()
		i.wheelY = -e.Get("deltaY").Float()
	case jsutil.Equal(t, stringTouchstart) || jsutil.Equal(t, stringTouchend) || jsutil.Equal(t, stringTouchmove):
		i.updateTouchesFromEvent(e)
	}
}

func (i *Input) setMouseCursorFromEvent(e js.Value) {
	x, y := e.Get("clientX").Int(), e.Get("clientY").Int()
	i.setMouseCursor(x, y)
}

func (i *Input) updateTouchesFromEvent(e js.Value) {
	j := e.Get("targetTouches")
	ts := map[driver.TouchID]pos{}
	for i := 0; i < j.Length(); i++ {
		jj := j.Call("item", i)
		id := driver.TouchID(jj.Get("identifier").Int())
		ts[id] = pos{
			X: jj.Get("clientX").Int(),
			Y: jj.Get("clientY").Int(),
		}
	}
	i.touches = ts
}

func (i *Input) updateForGo2Cpp() {
	if !go2cpp.Truthy() {
		return
	}

	i.touches = map[driver.TouchID]pos{}
	touchCount := go2cpp.Get("touchCount").Int()
	for idx := 0; idx < touchCount; idx++ {
		id := go2cpp.Call("getTouchId", idx)
		x := go2cpp.Call("getTouchX", idx)
		y := go2cpp.Call("getTouchY", idx)
		i.touches[driver.TouchID(id.Int())] = pos{
			X: x.Int(),
			Y: y.Int(),
		}
	}

	i.gamepads = map[driver.GamepadID]gamepad{}
	gamepadCount := go2cpp.Get("gamepadCount").Int()
	for idx := 0; idx < gamepadCount; idx++ {
		g := gamepad{}

		// Avoid buggy devices on GLFW (#1173).
		buttonCount := go2cpp.Call("getGamepadButtonCount", idx).Int()
		if buttonCount > len(g.buttonPressed) {
			continue
		}

		axisCount := go2cpp.Call("getGamepadAxisCount", idx).Int()
		if axisCount > len(g.axes) {
			continue
		}

		id := driver.GamepadID(go2cpp.Call("getGamepadId", idx).Int())

		g.buttonNum = buttonCount
		for j := 0; j < buttonCount; j++ {
			g.buttonPressed[j] = go2cpp.Call("isGamepadButtonPressed", idx, j).Bool()
		}

		g.axisNum = axisCount
		for j := 0; j < axisCount; j++ {
			g.axes[j] = go2cpp.Call("getGamepadAxis", idx, j).Float()
		}

		i.gamepads[id] = g
	}
}
<|endoftext|>