<commit_before>package restful

// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

import (
	"bufio"
	"errors"
	"net"
	"net/http"
)

// DefaultResponseMimeType is DEPRECATED, use DefaultResponseContentType(mime)
var DefaultResponseMimeType string

//PrettyPrintResponses controls the indentation feature of XML and JSON serialization
var PrettyPrintResponses = true

// Response is a wrapper on the actual http ResponseWriter
// It provides several convenience methods to prepare and write response content.
type Response struct {
	http.ResponseWriter
	requestAccept string        // mime-type what the Http Request says it wants to receive
	routeProduces []string      // mime-types what the Route says it can produce
	statusCode    int           // HTTP status code that has been written explicitly (if zero then net/http has written 200)
	contentLength int           // number of bytes written for the response body
	prettyPrint   bool          // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses.
	err           error         // err property is kept when WriteError is called
	hijacker      http.Hijacker // if underlying ResponseWriter supports it
}

// NewResponse creates a new response based on a http ResponseWriter.
func NewResponse(httpWriter http.ResponseWriter) *Response {
	hijacker, _ := httpWriter.(http.Hijacker)
	return &Response{ResponseWriter: httpWriter, routeProduces: []string{}, statusCode: http.StatusOK, prettyPrint: PrettyPrintResponses, hijacker: hijacker}
}

// DefaultResponseContentType set a default.
// If Accept header matching fails, fall back to this type.
// Valid values are restful.MIME_JSON and restful.MIME_XML
// Example:
// 	restful.DefaultResponseContentType(restful.MIME_JSON)
func DefaultResponseContentType(mime string) {
	DefaultResponseMimeType = mime
}

// InternalServerError writes the StatusInternalServerError header.
// DEPRECATED, use WriteErrorString(http.StatusInternalServerError,reason)
func (r Response) InternalServerError() Response {
	r.WriteHeader(http.StatusInternalServerError)
	return r
}

// Hijack implements the http.Hijacker interface.  This expands
// the Response to fulfill http.Hijacker if the underlying
// http.ResponseWriter supports it.
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	if r.hijacker == nil {
		return nil, nil, errors.New("http.Hijacker not implemented by underlying http.ResponseWriter")
	}
	return r.hijacker.Hijack()
}

// PrettyPrint changes whether this response must produce pretty (line-by-line, indented) JSON or XML output.
func (r *Response) PrettyPrint(bePretty bool) {
	r.prettyPrint = bePretty
}

// AddHeader is a shortcut for .Header().Add(header,value)
func (r Response) AddHeader(header string, value string) Response {
	r.Header().Add(header, value)
	return r
}

// SetRequestAccepts tells the response what Mime-type(s) the HTTP request said it wants to accept. Exposed for testing.
func (r *Response) SetRequestAccepts(mime string) {
	r.requestAccept = mime
}

// EntityWriter returns the registered EntityWriter that the entity (requested resource)
// can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say.
// If called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable.
func (r *Response) EntityWriter() (EntityReaderWriter, bool) {
	sorted := sortedMimes(r.requestAccept)
	for _, eachAccept := range sorted {
		for _, eachProduce := range r.routeProduces {
			if eachProduce == eachAccept.media {
				if w, ok := entityAccessRegistry.accessorAt(eachAccept.media); ok {
					return w, true
				}
			}
		}
		if eachAccept.media == "*/*" {
			for _, each := range r.routeProduces {
				if w, ok := entityAccessRegistry.accessorAt(each); ok {
					return w, true
				}
			}
		}
	}
	// if requestAccept is empty
	writer, ok := entityAccessRegistry.accessorAt(r.requestAccept)
	if !ok {
		// if not registered then fallback to the defaults (if set)
		if DefaultResponseMimeType == MIME_JSON {
			return entityAccessRegistry.accessorAt(MIME_JSON)
		}
		if DefaultResponseMimeType == MIME_XML {
			return entityAccessRegistry.accessorAt(MIME_XML)
		}
		// Fallback to whatever the route says it can produce.
		// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
		for _, each := range r.routeProduces {
			if w, ok := entityAccessRegistry.accessorAt(each); ok {
				return w, true
			}
		}
		if trace {
			traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept)
		}
	}
	return writer, ok
}

// WriteEntity calls WriteHeaderAndEntity with Http Status OK (200)
func (r *Response) WriteEntity(value interface{}) error {
	return r.WriteHeaderAndEntity(http.StatusOK, value)
}

// WriteHeaderAndEntity marshals the value using the representation denoted by the Accept Header and the registered EntityWriters.
// If no Accept header is specified (or */*) then respond with the Content-Type as specified by the first in the Route.Produces.
// If an Accept header is specified then respond with the Content-Type as specified by the first in the Route.Produces that is matched with the Accept header.
// If the value is nil then no response is send except for the Http status. You may want to call WriteHeader(http.StatusNotFound) instead.
// If there is no writer available that can represent the value in the requested MIME type then Http Status NotAcceptable is written.
// Current implementation ignores any q-parameters in the Accept Header.
// Returns an error if the value could not be written on the response.
func (r *Response) WriteHeaderAndEntity(status int, value interface{}) error {
	writer, ok := r.EntityWriter()
	if !ok {
		r.WriteHeader(http.StatusNotAcceptable)
		return nil
	}
	return writer.Write(r, status, value)
}

// WriteAsXml is a convenience method for writing a value in xml (requires Xml tags on the value)
// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteAsXml(value interface{}) error {
	return writeXML(r, http.StatusOK, MIME_XML, value)
}

// WriteHeaderAndXml is a convenience method for writing a status and value in xml (requires Xml tags on the value)
// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteHeaderAndXml(status int, value interface{}) error {
	return writeXML(r, status, MIME_XML, value)
}

// WriteAsJson is a convenience method for writing a value in json.
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteAsJson(value interface{}) error {
	return writeJSON(r, http.StatusOK, MIME_JSON, value)
}

// WriteJson is a convenience method for writing a value in Json with a given Content-Type.
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteJson(value interface{}, contentType string) error {
	return writeJSON(r, http.StatusOK, contentType, value)
}

// WriteHeaderAndJson is a convenience method for writing the status and a value in Json with a given Content-Type.
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteHeaderAndJson(status int, value interface{}, contentType string) error {
	return writeJSON(r, status, contentType, value)
}

// WriteError writes the http status and the error string on the response. err can be nil.
// Return an error if writing was not successful.
func (r *Response) WriteError(httpStatus int, err error) (writeErr error) {
	r.err = err
	if err == nil {
		writeErr = r.WriteErrorString(httpStatus, "")
	} else {
		writeErr = r.WriteErrorString(httpStatus, err.Error())
	}
	return writeErr
}

// WriteServiceError is a convenience method for a responding with a status and a ServiceError
func (r *Response) WriteServiceError(httpStatus int, err ServiceError) error {
	r.err = err
	return r.WriteHeaderAndEntity(httpStatus, err)
}

// WriteErrorString is a convenience method for an error status with the actual error
func (r *Response) WriteErrorString(httpStatus int, errorReason string) error {
	if r.err == nil {
		// if not called from WriteError
		r.err = errors.New(errorReason)
	}
	r.WriteHeader(httpStatus)
	if _, err := r.Write([]byte(errorReason)); err != nil {
		return err
	}
	return nil
}

// Flush implements http.Flusher interface, which sends any buffered data to the client.
func (r *Response) Flush() {
	if f, ok := r.ResponseWriter.(http.Flusher); ok {
		f.Flush()
	} else if trace {
		traceLogger.Printf("ResponseWriter %v doesn't support Flush", r)
	}
}

// WriteHeader is overridden to remember the Status Code that has been written.
// Changes to the Header of the response have no effect after this.
func (r *Response) WriteHeader(httpStatus int) {
	r.statusCode = httpStatus
	r.ResponseWriter.WriteHeader(httpStatus)
}

// StatusCode returns the code that has been written using WriteHeader.
func (r Response) StatusCode() int {
	if 0 == r.statusCode {
		// no status code has been written yet; assume OK
		return http.StatusOK
	}
	return r.statusCode
}

// Write writes the data to the connection as part of an HTTP reply.
// Write is part of http.ResponseWriter interface.
func (r *Response) Write(bytes []byte) (int, error) {
	written, err := r.ResponseWriter.Write(bytes)
	r.contentLength += written
	return written, err
}

// ContentLength returns the number of bytes written for the response content.
// Note that this value is only correct if all data is written through the Response using its Write* methods.
// Data written directly using the underlying http.ResponseWriter is not accounted for.
func (r Response) ContentLength() int {
	return r.contentLength
}

// CloseNotify is part of http.CloseNotifier interface
func (r Response) CloseNotify() <-chan bool {
	return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

// Error returns the err created by WriteError
func (r Response) Error() error {
	return r.err
}
<commit_msg>Add MIME_ZIP (#512)<commit_after>package restful

// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.

import (
	"bufio"
	"errors"
	"net"
	"net/http"
)

// DefaultResponseMimeType is DEPRECATED, use DefaultResponseContentType(mime)
var DefaultResponseMimeType string

//PrettyPrintResponses controls the indentation feature of XML and JSON serialization
var PrettyPrintResponses = true

// Response is a wrapper on the actual http ResponseWriter
// It provides several convenience methods to prepare and write response content.
type Response struct {
	http.ResponseWriter
	requestAccept string        // mime-type what the Http Request says it wants to receive
	routeProduces []string      // mime-types what the Route says it can produce
	statusCode    int           // HTTP status code that has been written explicitly (if zero then net/http has written 200)
	contentLength int           // number of bytes written for the response body
	prettyPrint   bool          // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses.
	err           error         // err property is kept when WriteError is called
	hijacker      http.Hijacker // if underlying ResponseWriter supports it
}

// NewResponse creates a new response based on a http ResponseWriter.
func NewResponse(httpWriter http.ResponseWriter) *Response {
	hijacker, _ := httpWriter.(http.Hijacker)
	return &Response{ResponseWriter: httpWriter, routeProduces: []string{}, statusCode: http.StatusOK, prettyPrint: PrettyPrintResponses, hijacker: hijacker}
}

// DefaultResponseContentType set a default.
// If Accept header matching fails, fall back to this type.
// Valid values are restful.MIME_JSON and restful.MIME_XML
// Example:
// 	restful.DefaultResponseContentType(restful.MIME_JSON)
func DefaultResponseContentType(mime string) {
	DefaultResponseMimeType = mime
}

// InternalServerError writes the StatusInternalServerError header.
// DEPRECATED, use WriteErrorString(http.StatusInternalServerError,reason)
func (r Response) InternalServerError() Response {
	r.WriteHeader(http.StatusInternalServerError)
	return r
}

// Hijack implements the http.Hijacker interface.  This expands
// the Response to fulfill http.Hijacker if the underlying
// http.ResponseWriter supports it.
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	if r.hijacker == nil {
		return nil, nil, errors.New("http.Hijacker not implemented by underlying http.ResponseWriter")
	}
	return r.hijacker.Hijack()
}

// PrettyPrint changes whether this response must produce pretty (line-by-line, indented) JSON or XML output.
func (r *Response) PrettyPrint(bePretty bool) {
	r.prettyPrint = bePretty
}

// AddHeader is a shortcut for .Header().Add(header,value)
func (r Response) AddHeader(header string, value string) Response {
	r.Header().Add(header, value)
	return r
}

// SetRequestAccepts tells the response what Mime-type(s) the HTTP request said it wants to accept. Exposed for testing.
func (r *Response) SetRequestAccepts(mime string) {
	r.requestAccept = mime
}

// EntityWriter returns the registered EntityWriter that the entity (requested resource)
// can write according to what the request wants (Accept) and what the Route can produce or what the restful defaults say.
// If called before WriteEntity and WriteHeader then a false return value can be used to write a 406: Not Acceptable.
func (r *Response) EntityWriter() (EntityReaderWriter, bool) {
	sorted := sortedMimes(r.requestAccept)
	for _, eachAccept := range sorted {
		for _, eachProduce := range r.routeProduces {
			if eachProduce == eachAccept.media {
				if w, ok := entityAccessRegistry.accessorAt(eachAccept.media); ok {
					return w, true
				}
			}
		}
		if eachAccept.media == "*/*" {
			for _, each := range r.routeProduces {
				if w, ok := entityAccessRegistry.accessorAt(each); ok {
					return w, true
				}
			}
		}
	}
	// if requestAccept is empty
	writer, ok := entityAccessRegistry.accessorAt(r.requestAccept)
	if !ok {
		// if not registered then fallback to the defaults (if set)
		if DefaultResponseMimeType == MIME_JSON {
			return entityAccessRegistry.accessorAt(MIME_JSON)
		}
		if DefaultResponseMimeType == MIME_XML {
			return entityAccessRegistry.accessorAt(MIME_XML)
		}
		if DefaultResponseMimeType == MIME_ZIP {
			return entityAccessRegistry.accessorAt(MIME_ZIP)
		}
		// Fallback to whatever the route says it can produce.
		// https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
		for _, each := range r.routeProduces {
			if w, ok := entityAccessRegistry.accessorAt(each); ok {
				return w, true
			}
		}
		if trace {
			traceLogger.Printf("no registered EntityReaderWriter found for %s", r.requestAccept)
		}
	}
	return writer, ok
}

// WriteEntity calls WriteHeaderAndEntity with Http Status OK (200)
func (r *Response) WriteEntity(value interface{}) error {
	return r.WriteHeaderAndEntity(http.StatusOK, value)
}

// WriteHeaderAndEntity marshals the value using the representation denoted by the Accept Header and the registered EntityWriters.
// If no Accept header is specified (or */*) then respond with the Content-Type as specified by the first in the Route.Produces.
// If an Accept header is specified then respond with the Content-Type as specified by the first in the Route.Produces that is matched with the Accept header.
// If the value is nil then no response is send except for the Http status. You may want to call WriteHeader(http.StatusNotFound) instead.
// If there is no writer available that can represent the value in the requested MIME type then Http Status NotAcceptable is written.
// Current implementation ignores any q-parameters in the Accept Header.
// Returns an error if the value could not be written on the response.
func (r *Response) WriteHeaderAndEntity(status int, value interface{}) error {
	writer, ok := r.EntityWriter()
	if !ok {
		r.WriteHeader(http.StatusNotAcceptable)
		return nil
	}
	return writer.Write(r, status, value)
}

// WriteAsXml is a convenience method for writing a value in xml (requires Xml tags on the value)
// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteAsXml(value interface{}) error {
	return writeXML(r, http.StatusOK, MIME_XML, value)
}

// WriteHeaderAndXml is a convenience method for writing a status and value in xml (requires Xml tags on the value)
// It uses the standard encoding/xml package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteHeaderAndXml(status int, value interface{}) error {
	return writeXML(r, status, MIME_XML, value)
}

// WriteAsJson is a convenience method for writing a value in json.
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteAsJson(value interface{}) error {
	return writeJSON(r, http.StatusOK, MIME_JSON, value)
}

// WriteJson is a convenience method for writing a value in Json with a given Content-Type.
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteJson(value interface{}, contentType string) error {
	return writeJSON(r, http.StatusOK, contentType, value)
}

// WriteHeaderAndJson is a convenience method for writing the status and a value in Json with a given Content-Type.
// It uses the standard encoding/json package for marshalling the value ; not using a registered EntityReaderWriter.
func (r *Response) WriteHeaderAndJson(status int, value interface{}, contentType string) error {
	return writeJSON(r, status, contentType, value)
}

// WriteError writes the http status and the error string on the response. err can be nil.
// Return an error if writing was not successful.
func (r *Response) WriteError(httpStatus int, err error) (writeErr error) {
	r.err = err
	if err == nil {
		writeErr = r.WriteErrorString(httpStatus, "")
	} else {
		writeErr = r.WriteErrorString(httpStatus, err.Error())
	}
	return writeErr
}

// WriteServiceError is a convenience method for a responding with a status and a ServiceError
func (r *Response) WriteServiceError(httpStatus int, err ServiceError) error {
	r.err = err
	return r.WriteHeaderAndEntity(httpStatus, err)
}

// WriteErrorString is a convenience method for an error status with the actual error
func (r *Response) WriteErrorString(httpStatus int, errorReason string) error {
	if r.err == nil {
		// if not called from WriteError
		r.err = errors.New(errorReason)
	}
	r.WriteHeader(httpStatus)
	if _, err := r.Write([]byte(errorReason)); err != nil {
		return err
	}
	return nil
}

// Flush implements http.Flusher interface, which sends any buffered data to the client.
func (r *Response) Flush() {
	if f, ok := r.ResponseWriter.(http.Flusher); ok {
		f.Flush()
	} else if trace {
		traceLogger.Printf("ResponseWriter %v doesn't support Flush", r)
	}
}

// WriteHeader is overridden to remember the Status Code that has been written.
// Changes to the Header of the response have no effect after this.
func (r *Response) WriteHeader(httpStatus int) {
	r.statusCode = httpStatus
	r.ResponseWriter.WriteHeader(httpStatus)
}

// StatusCode returns the code that has been written using WriteHeader.
func (r Response) StatusCode() int {
	if 0 == r.statusCode {
		// no status code has been written yet; assume OK
		return http.StatusOK
	}
	return r.statusCode
}

// Write writes the data to the connection as part of an HTTP reply.
// Write is part of http.ResponseWriter interface.
func (r *Response) Write(bytes []byte) (int, error) {
	written, err := r.ResponseWriter.Write(bytes)
	r.contentLength += written
	return written, err
}

// ContentLength returns the number of bytes written for the response content.
// Note that this value is only correct if all data is written through the Response using its Write* methods.
// Data written directly using the underlying http.ResponseWriter is not accounted for.
func (r Response) ContentLength() int {
	return r.contentLength
}

// CloseNotify is part of http.CloseNotifier interface
func (r Response) CloseNotify() <-chan bool {
	return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

// Error returns the err created by WriteError
func (r Response) Error() error {
	return r.err
}
<|endoftext|>


<commit_before>package air

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path/filepath"

	yaml "gopkg.in/yaml.v2"
)

// Response represents the current HTTP response.
//
// It's embedded with the `http.ResponseWriter`, the `http.Hijacker`, the `http.CloseNotifier`,
// the `http.Flusher` and the `http.Pusher`.
type Response struct {
	http.ResponseWriter
	http.Hijacker
	http.CloseNotifier
	http.Flusher
	http.Pusher

	context *Context

	statusCode int
	size       int
	written    bool

	Data JSONMap
}

// NewResponse returns a pointer of a new instance of the `Response`.
func NewResponse(c *Context) *Response {
	return &Response{
		context: c,
		Data:    make(JSONMap),
	}
}

// Write implements the `http.ResponseWriter#Write()`.
func (res *Response) Write(b []byte) (int, error) {
	if !res.written {
		res.WriteHeader(http.StatusOK)
	}
	n, err := res.ResponseWriter.Write(b)
	res.size += n
	return n, err
}

// WriteHeader implements the `http.ResponseWriter#WriteHeader()`.
func (res *Response) WriteHeader(statusCode int) {
	if res.written {
		res.context.Air.Logger.Warn("response already written")
		return
	}
	res.statusCode = statusCode
	res.ResponseWriter.WriteHeader(statusCode)
	res.written = true
}

// StatusCode returns the HTTP status code of the res.
func (res *Response) StatusCode() int {
	return res.statusCode
}

// Size returns the number of bytes already written into the HTTP body of the res.
func (res *Response) Size() int {
	return res.size
}

// Written reports whether the HTTP body of the res is already written.
func (res *Response) Written() bool {
	return res.written
}

// SetCookie adds a "Set-Cookie" header in the res. The provided cookie must have a valid `Name`.
// Invalid cookies may be silently dropped.
func (res *Response) SetCookie(cookie *http.Cookie) {
	http.SetCookie(res, cookie)
}

// Render renders one or more HTML templates with the `Data` of the res and sends a "text/html" HTTP
// response. The default `Renderer` does it by using the `template.Template`. The results rendered
// by the former can be inherited by accessing the `Data["InheritedHTML"]` of the res.
func (res *Response) Render(templates ...string) error {
	buf := &bytes.Buffer{}
	for _, t := range templates {
		res.Data["InheritedHTML"] = template.HTML(buf.String())
		buf.Reset()
		err := res.context.Air.Renderer.Render(buf, t, res.Data)
		if err != nil {
			return err
		}
	}
	return res.Blob(MIMETextHTML, buf.Bytes())
}

// HTML sends a "text/html" HTTP response with the html.
func (res *Response) HTML(html string) error {
	return res.Blob(MIMETextHTML, []byte(html))
}

// String sends a "text/plain" HTTP response with the s.
func (res *Response) String(s string) error {
	return res.Blob(MIMETextPlain, []byte(s))
}

// JSON sends an "application/json" HTTP response with the type i.
func (res *Response) JSON(i interface{}) error {
	b, err := json.Marshal(i)
	if res.context.Air.Config.DebugMode {
		b, err = json.MarshalIndent(i, "", "\t")
	}
	if err != nil {
		return err
	}
	return res.Blob(MIMEApplicationJSON, b)
}

// JSONP sends an "application/javascript" HTTP response with the type i. It uses the callback to
// construct the JSONP payload.
func (res *Response) JSONP(i interface{}, callback string) error {
	b, err := json.Marshal(i)
	if err != nil {
		return err
	}
	b = append([]byte(callback+"("), b...)
	b = append(b, []byte(");")...)
	return res.Blob(MIMEApplicationJavaScript, b)
}

// XML sends an "application/xml" HTTP response with the type i.
func (res *Response) XML(i interface{}) error {
	b, err := xml.Marshal(i)
	if res.context.Air.Config.DebugMode {
		b, err = xml.MarshalIndent(i, "", "\t")
	}
	if err != nil {
		return err
	}
	return res.Blob(MIMEApplicationXML, append([]byte(xml.Header), b...))
}

// YAML sends an "application/x-yaml" HTTP response with the type i.
func (res *Response) YAML(i interface{}) error {
	b, err := yaml.Marshal(i)
	if err != nil {
		return err
	}
	return res.Blob(MIMEApplicationYAML, b)
}

// Blob sends a blob HTTP response with the contentType and the b.
func (res *Response) Blob(contentType string, b []byte) error {
	res.Header().Set(HeaderContentType, contentType)
	_, err := res.Write(b)
	return err
}

// Stream sends a streaming HTTP response with the contentType and the r.
func (res *Response) Stream(contentType string, r io.Reader) error {
	res.Header().Set(HeaderContentType, contentType)
	_, err := io.Copy(res, r)
	return err
}

// File sends a file HTTP response with the file.
func (res *Response) File(file string) error {
	f, err := os.Open(file)
	if err != nil {
		return ErrNotFound
	}
	defer f.Close()

	fi, _ := f.Stat()
	if fi.IsDir() {
		file = filepath.Join(file, "index.html")
		f, err = os.Open(file)
		if err != nil {
			return ErrNotFound
		}
		defer f.Close()
		if fi, err = f.Stat(); err != nil {
			return err
		}
	}

	http.ServeContent(res, res.context.Request.Request, fi.Name(), fi.ModTime(), f)

	return nil
}

// Attachment sends an HTTP response with the file and the filename as attachment, prompting client
// to save the file.
func (res *Response) Attachment(file, filename string) error {
	return res.contentDisposition("attachment", file, filename)
}

// Inline sends an HTTP response with the file and the filename as inline, opening the file in the
// browser.
func (res *Response) Inline(file, filename string) error {
	return res.contentDisposition("inline", file, filename)
}

// contentDisposition sends an HTTP response with the file and the filename as the dispositionType.
func (res *Response) contentDisposition(dispositionType, file, filename string) error {
	res.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%s",
		dispositionType, filename))
	return res.File(file)
}

// NoContent sends an HTTP response with no body.
func (res *Response) NoContent() error { return nil }

// Redirect redirects the current HTTP request to the url with the statusCode.
func (res *Response) Redirect(statusCode int, url string) error {
	if statusCode < http.StatusMultipleChoices || statusCode > http.StatusTemporaryRedirect {
		return ErrInvalidRedirectCode
	}
	res.Header().Set(HeaderLocation, url)
	res.WriteHeader(statusCode)
	return nil
}

// feed feeds the rw into where it should be.
func (res *Response) feed(rw http.ResponseWriter) {
	res.ResponseWriter = rw
	res.Hijacker, _ = rw.(http.Hijacker)
	res.CloseNotifier, _ = rw.(http.CloseNotifier)
	res.Flusher, _ = rw.(http.Flusher)
	res.Pusher, _ = rw.(http.Pusher)
}

// reset resets all fields in the res.
func (res *Response) reset() {
	res.ResponseWriter = nil
	res.statusCode = 0
	res.size = 0
	res.written = false
	for k := range res.Data {
		delete(res.Data, k)
	}
}
<commit_msg>refactor: enhance the `Response#Render()`<commit_after>package air

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html/template"
	"io"
	"net/http"
	"os"
	"path/filepath"

	yaml "gopkg.in/yaml.v2"
)

// Response represents the current HTTP response.
//
// It's embedded with the `http.ResponseWriter`, the `http.Hijacker`, the `http.CloseNotifier`,
// the `http.Flusher` and the `http.Pusher`.
type Response struct {
	http.ResponseWriter
	http.Hijacker
	http.CloseNotifier
	http.Flusher
	http.Pusher

	context *Context

	statusCode int
	size       int
	written    bool

	Data JSONMap
}

// NewResponse returns a pointer of a new instance of the `Response`.
func NewResponse(c *Context) *Response {
	return &Response{
		context: c,
		Data:    make(JSONMap),
	}
}

// Write implements the `http.ResponseWriter#Write()`.
func (res *Response) Write(b []byte) (int, error) {
	if !res.written {
		res.WriteHeader(http.StatusOK)
	}
	n, err := res.ResponseWriter.Write(b)
	res.size += n
	return n, err
}

// WriteHeader implements the `http.ResponseWriter#WriteHeader()`.
func (res *Response) WriteHeader(statusCode int) {
	if res.written {
		res.context.Air.Logger.Warn("response already written")
		return
	}
	res.statusCode = statusCode
	res.ResponseWriter.WriteHeader(statusCode)
	res.written = true
}

// StatusCode returns the HTTP status code of the res.
func (res *Response) StatusCode() int {
	return res.statusCode
}

// Size returns the number of bytes already written into the HTTP body of the res.
func (res *Response) Size() int {
	return res.size
}

// Written reports whether the HTTP body of the res is already written.
func (res *Response) Written() bool {
	return res.written
}

// SetCookie adds a "Set-Cookie" header in the res. The provided cookie must have a valid `Name`.
// Invalid cookies may be silently dropped.
func (res *Response) SetCookie(cookie *http.Cookie) {
	http.SetCookie(res, cookie)
}

// Render renders one or more HTML templates with the `Data` of the res and sends a "text/html" HTTP
// response. The default `Renderer` does it by using the `template.Template`. The results rendered
// by the former can be inherited by accessing the `Data["InheritedHTML"]` of the res.
func (res *Response) Render(templates ...string) error {
	buf := &bytes.Buffer{}
	for _, t := range templates {
		res.Data["InheritedHTML"] = template.HTML(buf.Bytes())
		buf.Reset()
		err := res.context.Air.Renderer.Render(buf, t, res.Data)
		if err != nil {
			return err
		}
	}
	return res.Blob(MIMETextHTML, buf.Bytes())
}

// HTML sends a "text/html" HTTP response with the html.
func (res *Response) HTML(html string) error {
	return res.Blob(MIMETextHTML, []byte(html))
}

// String sends a "text/plain" HTTP response with the s.
func (res *Response) String(s string) error {
	return res.Blob(MIMETextPlain, []byte(s))
}

// JSON sends an "application/json" HTTP response with the type i.
func (res *Response) JSON(i interface{}) error {
	b, err := json.Marshal(i)
	if res.context.Air.Config.DebugMode {
		b, err = json.MarshalIndent(i, "", "\t")
	}
	if err != nil {
		return err
	}
	return res.Blob(MIMEApplicationJSON, b)
}

// JSONP sends an "application/javascript" HTTP response with the type i. It uses the callback to
// construct the JSONP payload.
func (res *Response) JSONP(i interface{}, callback string) error {
	b, err := json.Marshal(i)
	if err != nil {
		return err
	}
	b = append([]byte(callback+"("), b...)
	b = append(b, []byte(");")...)
	return res.Blob(MIMEApplicationJavaScript, b)
}

// XML sends an "application/xml" HTTP response with the type i.
func (res *Response) XML(i interface{}) error {
	b, err := xml.Marshal(i)
	if res.context.Air.Config.DebugMode {
		b, err = xml.MarshalIndent(i, "", "\t")
	}
	if err != nil {
		return err
	}
	return res.Blob(MIMEApplicationXML, append([]byte(xml.Header), b...))
}

// YAML sends an "application/x-yaml" HTTP response with the type i.
func (res *Response) YAML(i interface{}) error {
	b, err := yaml.Marshal(i)
	if err != nil {
		return err
	}
	return res.Blob(MIMEApplicationYAML, b)
}

// Blob sends a blob HTTP response with the contentType and the b.
func (res *Response) Blob(contentType string, b []byte) error {
	res.Header().Set(HeaderContentType, contentType)
	_, err := res.Write(b)
	return err
}

// Stream sends a streaming HTTP response with the contentType and the r.
func (res *Response) Stream(contentType string, r io.Reader) error {
	res.Header().Set(HeaderContentType, contentType)
	_, err := io.Copy(res, r)
	return err
}

// File sends a file HTTP response with the file.
func (res *Response) File(file string) error {
	f, err := os.Open(file)
	if err != nil {
		return ErrNotFound
	}
	defer f.Close()

	fi, _ := f.Stat()
	if fi.IsDir() {
		file = filepath.Join(file, "index.html")
		f, err = os.Open(file)
		if err != nil {
			return ErrNotFound
		}
		defer f.Close()
		if fi, err = f.Stat(); err != nil {
			return err
		}
	}

	http.ServeContent(res, res.context.Request.Request, fi.Name(), fi.ModTime(), f)

	return nil
}

// Attachment sends an HTTP response with the file and the filename as attachment, prompting client
// to save the file.
func (res *Response) Attachment(file, filename string) error {
	return res.contentDisposition("attachment", file, filename)
}

// Inline sends an HTTP response with the file and the filename as inline, opening the file in the
// browser.
func (res *Response) Inline(file, filename string) error {
	return res.contentDisposition("inline", file, filename)
}

// contentDisposition sends an HTTP response with the file and the filename as the dispositionType.
func (res *Response) contentDisposition(dispositionType, file, filename string) error {
	res.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%s",
		dispositionType, filename))
	return res.File(file)
}

// NoContent sends an HTTP response with no body.
func (res *Response) NoContent() error { return nil }

// Redirect redirects the current HTTP request to the url with the statusCode.
func (res *Response) Redirect(statusCode int, url string) error {
	if statusCode < http.StatusMultipleChoices || statusCode > http.StatusTemporaryRedirect {
		return ErrInvalidRedirectCode
	}
	res.Header().Set(HeaderLocation, url)
	res.WriteHeader(statusCode)
	return nil
}

// feed feeds the rw into where it should be.
func (res *Response) feed(rw http.ResponseWriter) {
	res.ResponseWriter = rw
	res.Hijacker, _ = rw.(http.Hijacker)
	res.CloseNotifier, _ = rw.(http.CloseNotifier)
	res.Flusher, _ = rw.(http.Flusher)
	res.Pusher, _ = rw.(http.Pusher)
}

// reset resets all fields in the res.
func (res *Response) reset() {
	res.ResponseWriter = nil
	res.statusCode = 0
	res.size = 0
	res.written = false
	for k := range res.Data {
		delete(res.Data, k)
	}
}
<|endoftext|>


<commit_before>package cart

import (
	"net/http"
	"net"
	"bufio"
	"io"
)

const (
	noWritten     = -1
	defaultStatus = 200
)

type (
	ResponseWriter interface {
		http.ResponseWriter
		http.Hijacker
		http.Flusher
		http.CloseNotifier
	}
	responseWriter struct {
		http.ResponseWriter
		size   int
		status int
	}
)

var _ ResponseWriter = &responseWriter{}

func (w *responseWriter) reset(writer http.ResponseWriter) {
	w.ResponseWriter = writer
	w.size = noWritten
	w.status = defaultStatus
}

func (w *responseWriter) WriteHeader(code int) {
	if code > 0 && w.status != code {
		if w.Written() {
			printDebug("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
		}
		w.status = code
	}
}

func (w *responseWriter) WriteHeaderNow() {
	if !w.Written() {
		w.size = 0
		w.ResponseWriter.WriteHeader(w.status)
	}
}

func (w *responseWriter) Write(data []byte) (n int, err error) {
	w.WriteHeaderNow()
	n, err = w.ResponseWriter.Write(data)
	w.size += n
	return
}

func (w *responseWriter) WriteString(s string) (n int, err error) {
	w.WriteHeaderNow()
	n, err = io.WriteString(w.ResponseWriter, s)
	w.size += n
	return
}

func (w *responseWriter) Status() int {
	return w.status
}

func (w *responseWriter) Size() int {
	return w.size
}

func (w *responseWriter) Written() bool {
	return w.size != noWritten
}

// Implements the http.Hijacker interface
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	if w.size < 0 {
		w.size = 0
	}
	return w.ResponseWriter.(http.Hijacker).Hijack()
}

// Implements the http.CloseNotify interface
func (w *responseWriter) CloseNotify() <-chan bool {
	return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

// Implements the http.Flush interface
func (w *responseWriter) Flush() {
	w.ResponseWriter.(http.Flusher).Flush()
}
<commit_msg>add test file<commit_after>package cart

import (
	"net/http"
	"net"
	"bufio"
	"io"
)

const (
	noWritten     = -1
	defaultStatus = 200
)

type (
	ResponseWriter interface {
		http.ResponseWriter
		http.Hijacker
		http.Flusher
		http.CloseNotifier
		WriteHeaderNow()
	}
	responseWriter struct {
		http.ResponseWriter
		size   int
		status int
	}
)

var _ ResponseWriter = &responseWriter{}

func (w *responseWriter) reset(writer http.ResponseWriter) {
	w.ResponseWriter = writer
	w.size = noWritten
	w.status = defaultStatus
}

func (w *responseWriter) WriteHeader(code int) {
	if code > 0 && w.status != code {
		if w.Written() {
			printDebug("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
		}
		w.status = code
	}
}

func (w *responseWriter) WriteHeaderNow() {
	if !w.Written() {
		w.size = 0
		w.ResponseWriter.WriteHeader(w.status)
	}
}

func (w *responseWriter) Write(data []byte) (n int, err error) {
	w.WriteHeaderNow()
	n, err = w.ResponseWriter.Write(data)
	w.size += n
	return
}

func (w *responseWriter) WriteString(s string) (n int, err error) {
	w.WriteHeaderNow()
	n, err = io.WriteString(w.ResponseWriter, s)
	w.size += n
	return
}

func (w *responseWriter) Status() int {
	return w.status
}

func (w *responseWriter) Size() int {
	return w.size
}

func (w *responseWriter) Written() bool {
	return w.size != noWritten
}

// Implements the http.Hijacker interface
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	if w.size < 0 {
		w.size = 0
	}
	return w.ResponseWriter.(http.Hijacker).Hijack()
}

// Implements the http.CloseNotify interface
func (w *responseWriter) CloseNotify() <-chan bool {
	return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
}

// Implements the http.Flush interface
func (w *responseWriter) Flush() {
	w.ResponseWriter.(http.Flusher).Flush()
}
<|endoftext|>


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

package time

import "errors"

// A Ticker holds a channel that delivers `ticks' of a clock
// at intervals.
type Ticker struct {
	C <-chan Time // The channel on which the ticks are delivered.
	r runtimeTimer
}

// NewTicker returns a new Ticker containing a channel that will send the
// time with a period specified by the duration argument.
// It adjusts the intervals or drops ticks to make up for slow receivers.
// The duration d must be greater than zero; if not, NewTicker will panic.
func NewTicker(d Duration) *Ticker {
	if d <= 0 {
		panic(errors.New("non-positive interval for NewTicker"))
	}
	// Give the channel a 1-element time buffer.
	// If the client falls behind while reading, we drop ticks
	// on the floor until the client catches up.
	c := make(chan Time, 1)
	t := &Ticker{
		C: c,
		r: runtimeTimer{
			when:   nano() + int64(d),
			period: int64(d),
			f:      sendTime,
			arg:    c,
		},
	}
	startTimer(&t.r)
	return t
}

// Stop turns off a ticker.  After Stop, no more ticks will be sent.
// Stop does not close the channel, to prevent a read from the channel succeeding
// incorrectly.
func (t *Ticker) Stop() {
	stopTimer(&t.r)
}

// Tick is a convenience wrapper for NewTicker providing access to the ticking
// channel only.  Useful for clients that have no need to shut down the ticker.
func Tick(d Duration) <-chan Time {
	if d <= 0 {
		return nil
	}
	return NewTicker(d).C
}
<commit_msg>time: explicitely mention Tickers have to be stopped<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package time

import "errors"

// A Ticker holds a channel that delivers `ticks' of a clock
// at intervals.
type Ticker struct {
	C <-chan Time // The channel on which the ticks are delivered.
	r runtimeTimer
}

// NewTicker returns a new Ticker containing a channel that will send the
// time with a period specified by the duration argument.
// It adjusts the intervals or drops ticks to make up for slow receivers.
// The duration d must be greater than zero; if not, NewTicker will panic.
// Stop the ticker to release associated resources.
func NewTicker(d Duration) *Ticker {
	if d <= 0 {
		panic(errors.New("non-positive interval for NewTicker"))
	}
	// Give the channel a 1-element time buffer.
	// If the client falls behind while reading, we drop ticks
	// on the floor until the client catches up.
	c := make(chan Time, 1)
	t := &Ticker{
		C: c,
		r: runtimeTimer{
			when:   nano() + int64(d),
			period: int64(d),
			f:      sendTime,
			arg:    c,
		},
	}
	startTimer(&t.r)
	return t
}

// Stop turns off a ticker.  After Stop, no more ticks will be sent.
// Stop does not close the channel, to prevent a read from the channel succeeding
// incorrectly.
func (t *Ticker) Stop() {
	stopTimer(&t.r)
}

// Tick is a convenience wrapper for NewTicker providing access to the ticking
// channel only.  Useful for clients that have no need to shut down the ticker.
func Tick(d Duration) <-chan Time {
	if d <= 0 {
		return nil
	}
	return NewTicker(d).C
}
<|endoftext|>


<commit_before>// Copyright 2013 Prometheus Team
// 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 main

import (
	"bytes"
	"encoding/csv"
	"fmt"
	"strconv"
	"strings"

	"github.com/prometheus/client_golang/model"
)

// Interface for query results of various result types.
type QueryResponse interface {
	ToText() string
	ToCSV(delim rune) string
}

// Type for unserializing a generic Prometheus query response.
type StubQueryResponse struct {
	Type    string      `json:"type"`
	Value   interface{} `json:"value"`
	Version int         `json:"version"`
}

// Type for unserializing a scalar-typed Prometheus query response.
type ScalarQueryResponse struct {
	Value string `json:"value"`
}

// Type for unserializing a vector-typed Prometheus query response.
type VectorQueryResponse struct {
	Value []struct {
		Metric    model.Metric `json:"metric"`
		Value     string       `json:"value"`
		Timestamp float64      `json:"timestamp"`
	} `json:"value"`
}

// Type for unserializing a matrix-typed Prometheus query response.
type MatrixQueryResponse struct {
	Value []struct {
		Metric model.Metric `json:"metric"`
		Values [][]interface{}
	} `json:"value"`
}

func (r ScalarQueryResponse) ToText() string {
	return fmt.Sprint(r.Value)
}

func formatCSV(rows [][]string, delim rune) string {
	var buf bytes.Buffer
	w := csv.NewWriter(&buf)
	w.Comma = delim
	for _, row := range rows {
		w.Write(row)
		if err := w.Error(); err != nil {
			panic("error formatting CSV")
		}
	}
	w.Flush()
	if err := w.Error(); err != nil {
		panic("error dumping CSV")
	}
	return buf.String()
}

func (r ScalarQueryResponse) ToCSV(delim rune) string {
	return formatCSV([][]string{{r.Value}}, delim)
}

func (r VectorQueryResponse) ToText() string {
	lines := make([]string, 0, len(r.Value))
	for _, v := range r.Value {
		lines = append(lines, fmt.Sprintf("%s %s@%.3f\n", v.Metric, v.Value, v.Timestamp))
	}
	return strings.Join(lines, "")
}

func (r VectorQueryResponse) ToCSV(delim rune) string {
	rows := make([][]string, 0, len(r.Value))
	for _, v := range r.Value {
		rows = append(rows, []string{
			v.Metric.String(),
			v.Value,
			strconv.FormatFloat(v.Timestamp, 'f', -1, 64),
		})
	}
	return formatCSV(rows, delim)
}

func (r MatrixQueryResponse) ToText() string {
	lines := make([]string, 0, len(r.Value))
	for _, v := range r.Value {
		vals := make([]string, 0, len(v.Values))
		for _, s := range v.Values {
			vals = append(vals, fmt.Sprintf("%s@%.3f ", s[1], s[0]))
		}
		lines = append(lines, fmt.Sprintf("%s %s\n", v.Metric, strings.Join(vals, " ")))
	}
	return strings.Join(lines, "")
}

func (r MatrixQueryResponse) ToCSV(delim rune) string {
	rows := make([][]string, 0, len(r.Value))
	for _, v := range r.Value {
		vals := make([]string, 0, len(v.Values))
		for _, s := range v.Values {
			vals = append(vals, fmt.Sprintf("%s@%.3f", s[1], s[0]))
		}
		rows = append(rows, []string{
			v.Metric.String(),
			strings.Join(vals, " "),
		})
	}
	return formatCSV(rows, delim)
}
<commit_msg>Move from client_golang/model to common/model<commit_after>// Copyright 2013 Prometheus Team
// 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 main

import (
	"bytes"
	"encoding/csv"
	"fmt"
	"strconv"
	"strings"

	"github.com/prometheus/common/model"
)

// Interface for query results of various result types.
type QueryResponse interface {
	ToText() string
	ToCSV(delim rune) string
}

// Type for unserializing a generic Prometheus query response.
type StubQueryResponse struct {
	Type    string      `json:"type"`
	Value   interface{} `json:"value"`
	Version int         `json:"version"`
}

// Type for unserializing a scalar-typed Prometheus query response.
type ScalarQueryResponse struct {
	Value string `json:"value"`
}

// Type for unserializing a vector-typed Prometheus query response.
type VectorQueryResponse struct {
	Value []struct {
		Metric    model.Metric `json:"metric"`
		Value     string       `json:"value"`
		Timestamp float64      `json:"timestamp"`
	} `json:"value"`
}

// Type for unserializing a matrix-typed Prometheus query response.
type MatrixQueryResponse struct {
	Value []struct {
		Metric model.Metric `json:"metric"`
		Values [][]interface{}
	} `json:"value"`
}

func (r ScalarQueryResponse) ToText() string {
	return fmt.Sprint(r.Value)
}

func formatCSV(rows [][]string, delim rune) string {
	var buf bytes.Buffer
	w := csv.NewWriter(&buf)
	w.Comma = delim
	for _, row := range rows {
		w.Write(row)
		if err := w.Error(); err != nil {
			panic("error formatting CSV")
		}
	}
	w.Flush()
	if err := w.Error(); err != nil {
		panic("error dumping CSV")
	}
	return buf.String()
}

func (r ScalarQueryResponse) ToCSV(delim rune) string {
	return formatCSV([][]string{{r.Value}}, delim)
}

func (r VectorQueryResponse) ToText() string {
	lines := make([]string, 0, len(r.Value))
	for _, v := range r.Value {
		lines = append(lines, fmt.Sprintf("%s %s@%.3f\n", v.Metric, v.Value, v.Timestamp))
	}
	return strings.Join(lines, "")
}

func (r VectorQueryResponse) ToCSV(delim rune) string {
	rows := make([][]string, 0, len(r.Value))
	for _, v := range r.Value {
		rows = append(rows, []string{
			v.Metric.String(),
			v.Value,
			strconv.FormatFloat(v.Timestamp, 'f', -1, 64),
		})
	}
	return formatCSV(rows, delim)
}

func (r MatrixQueryResponse) ToText() string {
	lines := make([]string, 0, len(r.Value))
	for _, v := range r.Value {
		vals := make([]string, 0, len(v.Values))
		for _, s := range v.Values {
			vals = append(vals, fmt.Sprintf("%s@%.3f ", s[1], s[0]))
		}
		lines = append(lines, fmt.Sprintf("%s %s\n", v.Metric, strings.Join(vals, " ")))
	}
	return strings.Join(lines, "")
}

func (r MatrixQueryResponse) ToCSV(delim rune) string {
	rows := make([][]string, 0, len(r.Value))
	for _, v := range r.Value {
		vals := make([]string, 0, len(v.Values))
		for _, s := range v.Values {
			vals = append(vals, fmt.Sprintf("%s@%.3f", s[1], s[0]))
		}
		rows = append(rows, []string{
			v.Metric.String(),
			strings.Join(vals, " "),
		})
	}
	return formatCSV(rows, delim)
}
<|endoftext|>


<commit_before>/*
 * Groovy music player daemon.
 *
 * Copyright (c) 2014, Alessandro Ghedini
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package player

// #cgo pkg-config: mpv
// #include <mpv/client.h>
import "C"

import "fmt"
import "log"
import "strconv"
import "sync"

import "github.com/vaughan0/go-ini"

import "library"
import "notify"
import "util"

type Event byte
type Status byte

const (
	StatusPlaying Status = iota
	StatusPaused
	StatusStopped
)

func (s Status) String() string {
	switch s {
	case StatusPlaying:
		return "play"

	case StatusPaused:
		return "pause"

	case StatusStopped:
		return "stop"
	}

	return "invalid"
}

type Player struct {
	handle *C.mpv_handle
	Status Status

	library string
	notify  bool
	started bool

	HandleStatusChange func()
	HandleTrackChange  func()
	HandleTracksChange func()

	Wait sync.WaitGroup
}

func (p *Player) ChangeStatus(status Status) {
	p.Status = status
	p.HandleStatusChange()
}

func (p *Player) Play() error {
	switch p.Status {
	case StatusPlaying:
		return nil

	case StatusStopped:
		count, err := p.GetProperty("playlist-count")
		if err == nil && count.(int64) > 0 {
			return p.GotoTrack(0)
		}

		return p.AddTrack("", true)

	case StatusPaused:
		return p.Command([]string{"cycle", "pause"})
	}

	return fmt.Errorf("Invalid player state")
}

func (p *Player) Pause() error {
	switch p.Status {
	case StatusPaused, StatusStopped:
		return nil

	case StatusPlaying:
		return p.Command([]string{"cycle", "pause"})
	}

	return fmt.Errorf("Invalid player state")
}

func (p *Player) Toggle() error {
	switch p.Status {
	case StatusPaused, StatusStopped:
		return p.Play()

	case StatusPlaying:
		return p.Pause()
	}

	return fmt.Errorf("Invalid player state")
}

func (p *Player) Next() error {
	return p.Command([]string{"playlist_next", "force"})
}

func (p *Player) Prev() error {
	return p.Command([]string{"playlist_prev", "weak"})
}

func (p *Player) Stop() error {
	err := p.Command([]string{"stop"})

	p.ChangeStatus(StatusStopped)
	p.HandleTrackChange()

	return err
}

func (p *Player) Seek(seconds int64) error {
	secs := strconv.FormatInt(seconds, 10)
	return p.Command([]string{"seek", secs})
}

func (p *Player) List() ([]string, error) {
	playlist, err := p.GetProperty("playlist")
	if err != nil {
		return nil, nil
	}

	var files []string

	for _, entry := range playlist.([]interface{}) {
		if entry == nil {
			continue
		}

		entry_map := entry.(map[string]interface{})

		files = append(files, entry_map["filename"].(string))
	}

	return files, nil
}

func (p *Player) AddTrack(path string, play bool) error {
	var mode string

	if play {
		mode = "append-play"
	} else {
		mode = "append"
	}

	if path == "" {
		var err error

		path, err = library.Random(p.library)
		if err != nil {
			return fmt.Errorf("Could not get random track: %s", err)
		}
	}

	return p.Command([]string{"loadfile", path, mode})
}

func (p *Player) AddList(path string) error {
	return p.Command([]string{"loadlist", path, "append"})
}

func (p *Player) GotoTrack(index int64) error {
	return p.SetProperty("playlist-pos", index)
}

func (p *Player) RemoveTrack(index int64) error {
	var track string

	if index < 0 {
		track = "current"
	} else {
		track = strconv.FormatInt(index, 10)
	}

	return p.Command([]string{"playlist_remove", track})
}

func (p *Player) Quit() error {
	return p.Command([]string{"quit"})
}

func (p *Player) GetTrackMetadata() (map[string]string, error) {
	metadata, err := p.GetProperty("metadata")
	if err != nil {
		return nil, err
	}

	metadata_str := map[string]string{}

	for key, val := range metadata.(map[string]interface{}) {
		metadata_str[key] = val.(string)
	}

	return metadata_str, err
}

func (p *Player) GetTrackLength() (float64, error) {
	length, err := p.GetProperty("length")
	if err != nil {
		return 0.0, err
	}

	return length.(float64), nil
}

func (p *Player) GetTrackPath() (string, error) {
	path, err := p.GetProperty("path")
	if err != nil {
		return "", err
	}

	return path.(string), nil
}

func (p *Player) GetTrackPosition(percent bool) (float64, error) {
	var err error
	var pos interface{}

	if !percent {
		pos, err = p.GetProperty("time-pos")
	} else {
		pos, err = p.GetProperty("percent-pos")
	}

	if err != nil {
		return 0.0, err
	}

	return pos.(float64), nil
}

func (p *Player) GetTrackTitle() (string, error) {
	title, err := p.GetProperty("media-title")
	if err != nil {
		return "", nil
	}

	metadata, err := p.GetTrackMetadata()
	if err != nil {
		return title.(string), nil
	}

	artist := metadata["artist"]
	if artist == "" {
		artist = metadata["ARTIST"]
	}

	if artist != "" {
		return fmt.Sprintf("%s - %s", artist, title.(string)), nil
	}

	return title.(string), nil
}

func (p *Player) SetLoopStatus(mode string) error {
	switch mode {
	case "none":
		p.SetProperty("loop-file", false)
		p.SetProperty("loop", "no")

	case "track":
		p.SetLoopStatus("none")
		p.SetProperty("loop-file", true)

	case "list":
		p.SetLoopStatus("none")
		p.SetProperty("loop", "inf")

	case "force":
		p.SetLoopStatus("none")
		p.SetProperty("loop", "force")

	default:
		return fmt.Errorf("Invalid mode")
	}

	return nil
}

func (p *Player) GetOutputList() ([]string, error) {
	outputs, err := p.GetProperty("option-info/ao/choices")
	if err != nil {
		return nil, err
	}

	outs := []string{}
	for _, output := range outputs.([]interface{}) {
		outs = append(outs, output.(string))
	}

	return outs, nil
}

func Init(cfg ini.File) (*Player, error) {
	p := &Player{
		Status:  StatusStopped,
		started: false,
	}

	p.handle = C.mpv_create()
	if p.handle == nil {
		return nil, fmt.Errorf("Could not create player")
	}

	err := p.SetOptionString("no-config", "")
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-config': %s", err)
	}

	err = p.SetOptionString("no-video", "")
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-video': %s", err)
	}

	err = p.SetOptionString("no-sub", "")
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-sub': %s", err)
	}

	err = p.SetOptionString("no-softvol", "")
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-softvol': %s", err)
	}

	if cfg["default"]["cache"] != "" {
		p.SetOptionString("cache", cfg["default"]["cache"])
	}

	if cfg["default"]["gapless"] != "" {
		p.SetOptionString("gapless-audio", cfg["default"]["gapless"])
	}

	p.library, _ = util.ExpandUser(cfg["default"]["library"])

	if cfg["default"]["notify"] == "yes" {
		p.notify = true
	} else {
		p.notify = false
	}

	if cfg["default"]["replaygain"] != "" {
		rgain_af := fmt.Sprintf("volume=replaygain-%s", cfg["default"]["replaygain"])
		if cfg["default"]["filters"] != "" {
			cfg["default"]["filters"] += "," + rgain_af
		} else {
			cfg["default"]["filters"] = rgain_af
		}
	}

	if cfg["default"]["output"] != "" {
		p.SetOptionString("ao", cfg["default"]["output"])
	}

	if cfg["default"]["ytdl"] != "" {
		p.SetOptionString("ytdl", cfg["default"]["ytdl"])
	}

	if cfg["default"]["filters"] != "" {
		p.SetOptionString("af", cfg["default"]["filters"])
	}

	if cfg["default"]["scripts"] != "" {
		p.SetOptionString("lua", cfg["default"]["scripts"])
	}

	C.mpv_request_log_messages(p.handle, C.CString("warn"))

	mp_err := C.mpv_initialize(p.handle)
	if mp_err != 0 {
		return nil, ErrorString(mp_err)
	}

	return p, nil
}

func (p *Player) Run() error {
	p.Wait.Add(1)

	go p.EventLoop()

	return nil
}

func (p *Player) HandlePauseChange() {
	if !p.started {
		return
	}

	pause, err := p.GetProperty("pause")
	if err != nil {
		return
	}

	if pause.(bool) {
		p.ChangeStatus(StatusPaused)
	} else {
		p.ChangeStatus(StatusPlaying)
	}
}

func (p *Player) HandleMetadataChange() {
	if !p.started {
		return
	}

	if p.notify {
		msg, _ := p.GetTrackTitle()
		notify.Notify("Now Playing:", msg, "media-playback-start")
	}

	p.HandleTrackChange()
}

func (p *Player) EventLoop() {
	p.ObserveProperty("pause",    FormatFlag)
	p.ObserveProperty("metadata", FormatNode)
	p.ObserveProperty("playlist", FormatNode)

	for {
		ev := C.mpv_wait_event(p.handle, -1)
		ev_name := C.GoString(C.mpv_event_name(ev.event_id))

		switch ev_name {
		case "idle":
			if p.Status == StatusStopped {
				break
			}

			err := p.AddTrack("", true)
			if err != nil {
				log.Println("Could not add track: %s", err)
				p.ChangeStatus(StatusStopped)
			}

		case "start-file":
			p.started = true
			p.ChangeStatus(StatusPlaying)

		case "property-change":
			prop := (*C.mpv_event_property)(ev.data)
			prop_name := C.GoString(prop.name)

			if prop.format == FormatNone {
				break
			}

			switch prop_name {
			case "pause":
				p.HandlePauseChange()

			case "metadata":
				p.HandleMetadataChange()

			case "playlist":
				p.HandleTracksChange()
			}

		case "log-message":
			mp_log := (*C.mpv_event_log_message)(ev.data)
			log.Printf("%s: %s: %s",
			           C.GoString(mp_log.level),
			           C.GoString(mp_log.prefix),
			           C.GoString(mp_log.text))

		case "shutdown":
			p.Wait.Done()
			return
		}
	}
}
<commit_msg>player: only set status on start-file once<commit_after>/*
 * Groovy music player daemon.
 *
 * Copyright (c) 2014, Alessandro Ghedini
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package player

// #cgo pkg-config: mpv
// #include <mpv/client.h>
import "C"

import "fmt"
import "log"
import "strconv"
import "sync"

import "github.com/vaughan0/go-ini"

import "library"
import "notify"
import "util"

type Event byte
type Status byte

const (
	StatusPlaying Status = iota
	StatusPaused
	StatusStopped
)

func (s Status) String() string {
	switch s {
	case StatusPlaying:
		return "play"

	case StatusPaused:
		return "pause"

	case StatusStopped:
		return "stop"
	}

	return "invalid"
}

type Player struct {
	handle *C.mpv_handle
	Status Status

	library string
	notify  bool
	started bool

	HandleStatusChange func()
	HandleTrackChange  func()
	HandleTracksChange func()

	Wait sync.WaitGroup
}

func (p *Player) ChangeStatus(status Status) {
	p.Status = status
	p.HandleStatusChange()
}

func (p *Player) Play() error {
	switch p.Status {
	case StatusPlaying:
		return nil

	case StatusStopped:
		count, err := p.GetProperty("playlist-count")
		if err == nil && count.(int64) > 0 {
			return p.GotoTrack(0)
		}

		return p.AddTrack("", true)

	case StatusPaused:
		return p.Command([]string{"cycle", "pause"})
	}

	return fmt.Errorf("Invalid player state")
}

func (p *Player) Pause() error {
	switch p.Status {
	case StatusPaused, StatusStopped:
		return nil

	case StatusPlaying:
		return p.Command([]string{"cycle", "pause"})
	}

	return fmt.Errorf("Invalid player state")
}

func (p *Player) Toggle() error {
	switch p.Status {
	case StatusPaused, StatusStopped:
		return p.Play()

	case StatusPlaying:
		return p.Pause()
	}

	return fmt.Errorf("Invalid player state")
}

func (p *Player) Next() error {
	return p.Command([]string{"playlist_next", "force"})
}

func (p *Player) Prev() error {
	return p.Command([]string{"playlist_prev", "weak"})
}

func (p *Player) Stop() error {
	err := p.Command([]string{"stop"})

	p.ChangeStatus(StatusStopped)
	p.HandleTrackChange()

	return err
}

func (p *Player) Seek(seconds int64) error {
	secs := strconv.FormatInt(seconds, 10)
	return p.Command([]string{"seek", secs})
}

func (p *Player) List() ([]string, error) {
	playlist, err := p.GetProperty("playlist")
	if err != nil {
		return nil, nil
	}

	var files []string

	for _, entry := range playlist.([]interface{}) {
		if entry == nil {
			continue
		}

		entry_map := entry.(map[string]interface{})

		files = append(files, entry_map["filename"].(string))
	}

	return files, nil
}

func (p *Player) AddTrack(path string, play bool) error {
	var mode string

	if play {
		mode = "append-play"
	} else {
		mode = "append"
	}

	if path == "" {
		var err error

		path, err = library.Random(p.library)
		if err != nil {
			return fmt.Errorf("Could not get random track: %s", err)
		}
	}

	return p.Command([]string{"loadfile", path, mode})
}

func (p *Player) AddList(path string) error {
	return p.Command([]string{"loadlist", path, "append"})
}

func (p *Player) GotoTrack(index int64) error {
	return p.SetProperty("playlist-pos", index)
}

func (p *Player) RemoveTrack(index int64) error {
	var track string

	if index < 0 {
		track = "current"
	} else {
		track = strconv.FormatInt(index, 10)
	}

	return p.Command([]string{"playlist_remove", track})
}

func (p *Player) Quit() error {
	return p.Command([]string{"quit"})
}

func (p *Player) GetTrackMetadata() (map[string]string, error) {
	metadata, err := p.GetProperty("metadata")
	if err != nil {
		return nil, err
	}

	metadata_str := map[string]string{}

	for key, val := range metadata.(map[string]interface{}) {
		metadata_str[key] = val.(string)
	}

	return metadata_str, err
}

func (p *Player) GetTrackLength() (float64, error) {
	length, err := p.GetProperty("length")
	if err != nil {
		return 0.0, err
	}

	return length.(float64), nil
}

func (p *Player) GetTrackPath() (string, error) {
	path, err := p.GetProperty("path")
	if err != nil {
		return "", err
	}

	return path.(string), nil
}

func (p *Player) GetTrackPosition(percent bool) (float64, error) {
	var err error
	var pos interface{}

	if !percent {
		pos, err = p.GetProperty("time-pos")
	} else {
		pos, err = p.GetProperty("percent-pos")
	}

	if err != nil {
		return 0.0, err
	}

	return pos.(float64), nil
}

func (p *Player) GetTrackTitle() (string, error) {
	title, err := p.GetProperty("media-title")
	if err != nil {
		return "", nil
	}

	metadata, err := p.GetTrackMetadata()
	if err != nil {
		return title.(string), nil
	}

	artist := metadata["artist"]
	if artist == "" {
		artist = metadata["ARTIST"]
	}

	if artist != "" {
		return fmt.Sprintf("%s - %s", artist, title.(string)), nil
	}

	return title.(string), nil
}

func (p *Player) SetLoopStatus(mode string) error {
	switch mode {
	case "none":
		p.SetProperty("loop-file", false)
		p.SetProperty("loop", "no")

	case "track":
		p.SetLoopStatus("none")
		p.SetProperty("loop-file", true)

	case "list":
		p.SetLoopStatus("none")
		p.SetProperty("loop", "inf")

	case "force":
		p.SetLoopStatus("none")
		p.SetProperty("loop", "force")

	default:
		return fmt.Errorf("Invalid mode")
	}

	return nil
}

func (p *Player) GetOutputList() ([]string, error) {
	outputs, err := p.GetProperty("option-info/ao/choices")
	if err != nil {
		return nil, err
	}

	outs := []string{}
	for _, output := range outputs.([]interface{}) {
		outs = append(outs, output.(string))
	}

	return outs, nil
}

func Init(cfg ini.File) (*Player, error) {
	p := &Player{
		Status:  StatusStopped,
		started: false,
	}

	p.handle = C.mpv_create()
	if p.handle == nil {
		return nil, fmt.Errorf("Could not create player")
	}

	err := p.SetOptionString("no-config", "")
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-config': %s", err)
	}

	err = p.SetOptionString("no-video", "")
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-video': %s", err)
	}

	err = p.SetOptionString("no-sub", "")
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-sub': %s", err)
	}

	err = p.SetOptionString("no-softvol", "")
	if err != nil {
		return nil, fmt.Errorf("Could not set option 'no-softvol': %s", err)
	}

	if cfg["default"]["cache"] != "" {
		p.SetOptionString("cache", cfg["default"]["cache"])
	}

	if cfg["default"]["gapless"] != "" {
		p.SetOptionString("gapless-audio", cfg["default"]["gapless"])
	}

	p.library, _ = util.ExpandUser(cfg["default"]["library"])

	if cfg["default"]["notify"] == "yes" {
		p.notify = true
	} else {
		p.notify = false
	}

	if cfg["default"]["replaygain"] != "" {
		rgain_af := fmt.Sprintf("volume=replaygain-%s", cfg["default"]["replaygain"])
		if cfg["default"]["filters"] != "" {
			cfg["default"]["filters"] += "," + rgain_af
		} else {
			cfg["default"]["filters"] = rgain_af
		}
	}

	if cfg["default"]["output"] != "" {
		p.SetOptionString("ao", cfg["default"]["output"])
	}

	if cfg["default"]["ytdl"] != "" {
		p.SetOptionString("ytdl", cfg["default"]["ytdl"])
	}

	if cfg["default"]["filters"] != "" {
		p.SetOptionString("af", cfg["default"]["filters"])
	}

	if cfg["default"]["scripts"] != "" {
		p.SetOptionString("lua", cfg["default"]["scripts"])
	}

	C.mpv_request_log_messages(p.handle, C.CString("warn"))

	mp_err := C.mpv_initialize(p.handle)
	if mp_err != 0 {
		return nil, ErrorString(mp_err)
	}

	return p, nil
}

func (p *Player) Run() error {
	p.Wait.Add(1)

	go p.EventLoop()

	return nil
}

func (p *Player) HandlePauseChange() {
	if !p.started {
		return
	}

	pause, err := p.GetProperty("pause")
	if err != nil {
		return
	}

	if pause.(bool) {
		p.ChangeStatus(StatusPaused)
	} else {
		p.ChangeStatus(StatusPlaying)
	}
}

func (p *Player) HandleMetadataChange() {
	if !p.started {
		return
	}

	if p.notify {
		msg, _ := p.GetTrackTitle()
		notify.Notify("Now Playing:", msg, "media-playback-start")
	}

	p.HandleTrackChange()
}

func (p *Player) EventLoop() {
	p.ObserveProperty("pause",    FormatFlag)
	p.ObserveProperty("metadata", FormatNode)
	p.ObserveProperty("playlist", FormatNode)

	for {
		ev := C.mpv_wait_event(p.handle, -1)
		ev_name := C.GoString(C.mpv_event_name(ev.event_id))

		switch ev_name {
		case "idle":
			if p.Status == StatusStopped {
				break
			}

			err := p.AddTrack("", true)
			if err != nil {
				log.Println("Could not add track: %s", err)
				p.ChangeStatus(StatusStopped)
			}

		case "start-file":
			if !p.started {
				p.started = true
				p.ChangeStatus(StatusPlaying)
			}

		case "property-change":
			prop := (*C.mpv_event_property)(ev.data)
			prop_name := C.GoString(prop.name)

			if prop.format == FormatNone {
				break
			}

			switch prop_name {
			case "pause":
				p.HandlePauseChange()

			case "metadata":
				p.HandleMetadataChange()

			case "playlist":
				p.HandleTracksChange()
			}

		case "log-message":
			mp_log := (*C.mpv_event_log_message)(ev.data)
			log.Printf("%s: %s: %s",
			           C.GoString(mp_log.level),
			           C.GoString(mp_log.prefix),
			           C.GoString(mp_log.text))

		case "shutdown":
			p.Wait.Done()
			return
		}
	}
}
<|endoftext|>


<commit_before>package media_library

import (
	"bytes"
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"image"
	"io"
	"mime/multipart"
	"os"
	"path"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/disintegration/imaging"
	"github.com/jinzhu/gorm"
)

var ErrNotImplemented = errors.New("not implemented")

type Base struct {
	Url        string
	Valid      bool
	FileName   string
	FileHeader *multipart.FileHeader
	CropOption *image.Rectangle
	Reader     io.Reader
}

func (b *Base) Scan(value interface{}) error {
	switch v := value.(type) {
	case []*multipart.FileHeader:
		if len(v) > 0 {
			file := v[0]
			b.FileHeader, b.FileName, b.Valid = file, file.Filename, true
		}
	case []uint8:
		b.Url, b.Valid = string(v), true
	case string:
		if strings.HasPrefix(v, "{") && strings.HasSuffix(v, "}") {
			var cropOption struct{ X, Y, Width, Height int }
			if err := json.Unmarshal([]byte(v), &cropOption); err == nil {
				b.SetCropOption(image.Rectangle{
					Min: image.Point{X: cropOption.X, Y: cropOption.Y},
					Max: image.Point{X: cropOption.X + cropOption.Width, Y: cropOption.Y + cropOption.Height},
				})
			} else {
				return err
			}
		} else {
			b.Url, b.Valid = v, true
		}
	default:
		fmt.Errorf("unsupported driver -> Scan pair for MediaLibrary")
	}
	return nil
}

func (b Base) Value() (driver.Value, error) {
	if b.Valid {
		return b.Url, nil
	}
	return nil, nil
}

func (b Base) URL(styles ...string) string {
	if len(styles) > 0 {
		ext := path.Ext(b.Url)
		return fmt.Sprintf("%v.%v%v", strings.TrimSuffix(b.Url, ext), styles[0], ext)
	}
	return b.Url
}

func (b Base) String() string {
	return b.URL()
}

func (b Base) GetFileName() string {
	return b.FileName
}

func (b Base) GetFileHeader() *multipart.FileHeader {
	return b.FileHeader
}

func (b Base) GetURLTemplate(option *Option) (path string) {
	if path = option.Get("URL"); path == "" {
		path = "/system/{{class}}/{{primary_key}}/{{column}}/{{filename_with_hash}}"
	}
	return
}

func getFuncMap(scope *gorm.Scope, field *gorm.Field, filename string) template.FuncMap {
	hash := func() string { return strings.Replace(time.Now().Format("20060102150506.000000000"), ".", "", -1) }
	return template.FuncMap{
		"class":       scope.TableName,
		"primary_key": func() string { return fmt.Sprintf("%v", scope.PrimaryKeyValue()) },
		"column":      func() string { return field.Name },
		"filename":    func() string { return filename },
		"basename":    func() string { return strings.TrimSuffix(path.Base(filename), path.Ext(filename)) },
		"hash":        hash,
		"filename_with_hash": func() string {
			return fmt.Sprintf("%v.%v%v", strings.TrimSuffix(filename, path.Ext(filename)), hash(), path.Ext(filename))
		},
		"extension": func() string { return strings.TrimPrefix(path.Ext(filename), ".") },
	}
}

func (b Base) GetURL(option *Option, scope *gorm.Scope, field *gorm.Field) string {
	if path := b.GetURLTemplate(option); path != "" {
		tmpl := template.New("").Funcs(getFuncMap(scope, field, b.GetFileName()))
		if tmpl, err := tmpl.Parse(path); err == nil {
			var result = bytes.NewBufferString("")
			if err := tmpl.Execute(result, scope.Value); err == nil {
				return result.String()
			}
		}
	}
	return ""
}

func (b *Base) SetCropOption(option image.Rectangle) {
	b.CropOption = &option
}

func (b *Base) GetCropOption() *image.Rectangle {
	return b.CropOption
}

func (b Base) Retrieve(url string) (*os.File, error) {
	return nil, ErrNotImplemented
}

func (b Base) GetSizes() map[string]Size {
	return map[string]Size{}
}

func (b Base) IsImage() bool {
	_, err := getImageFormat(b.URL())
	return err == nil
}

func getImageFormat(url string) (*imaging.Format, error) {
	formats := map[string]imaging.Format{
		".jpg":  imaging.JPEG,
		".jpeg": imaging.JPEG,
		".png":  imaging.PNG,
		".tif":  imaging.TIFF,
		".tiff": imaging.TIFF,
		".bmp":  imaging.BMP,
		".gif":  imaging.GIF,
	}

	ext := strings.ToLower(filepath.Ext(url))
	if f, ok := formats[ext]; ok {
		return &f, nil
	} else {
		return nil, imaging.ErrUnsupportedFormat
	}
}
<commit_msg>Change cropOption key from upcase to downcase<commit_after>package media_library

import (
	"bytes"
	"database/sql/driver"
	"encoding/json"
	"errors"
	"fmt"
	"image"
	"io"
	"mime/multipart"
	"os"
	"path"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/disintegration/imaging"
	"github.com/jinzhu/gorm"
)

var ErrNotImplemented = errors.New("not implemented")

type Base struct {
	Url        string
	Valid      bool
	FileName   string
	FileHeader *multipart.FileHeader
	CropOption *image.Rectangle
	Reader     io.Reader
}

func (b *Base) Scan(value interface{}) error {
	switch v := value.(type) {
	case []*multipart.FileHeader:
		if len(v) > 0 {
			file := v[0]
			b.FileHeader, b.FileName, b.Valid = file, file.Filename, true
		}
	case []uint8:
		b.Url, b.Valid = string(v), true
	case string:
		if strings.HasPrefix(v, "{") && strings.HasSuffix(v, "}") {
			var cropOption struct{ x, y, width, height int }
			if err := json.Unmarshal([]byte(v), &cropOption); err == nil {
				b.SetCropOption(image.Rectangle{
					Min: image.Point{X: cropOption.x, Y: cropOption.y},
					Max: image.Point{X: cropOption.x + cropOption.width, Y: cropOption.y + cropOption.height},
				})
			} else {
				return err
			}
		} else {
			b.Url, b.Valid = v, true
		}
	default:
		fmt.Errorf("unsupported driver -> Scan pair for MediaLibrary")
	}
	return nil
}

func (b Base) Value() (driver.Value, error) {
	if b.Valid {
		return b.Url, nil
	}
	return nil, nil
}

func (b Base) URL(styles ...string) string {
	if len(styles) > 0 {
		ext := path.Ext(b.Url)
		return fmt.Sprintf("%v.%v%v", strings.TrimSuffix(b.Url, ext), styles[0], ext)
	}
	return b.Url
}

func (b Base) String() string {
	return b.URL()
}

func (b Base) GetFileName() string {
	return b.FileName
}

func (b Base) GetFileHeader() *multipart.FileHeader {
	return b.FileHeader
}

func (b Base) GetURLTemplate(option *Option) (path string) {
	if path = option.Get("URL"); path == "" {
		path = "/system/{{class}}/{{primary_key}}/{{column}}/{{filename_with_hash}}"
	}
	return
}

func getFuncMap(scope *gorm.Scope, field *gorm.Field, filename string) template.FuncMap {
	hash := func() string { return strings.Replace(time.Now().Format("20060102150506.000000000"), ".", "", -1) }
	return template.FuncMap{
		"class":       scope.TableName,
		"primary_key": func() string { return fmt.Sprintf("%v", scope.PrimaryKeyValue()) },
		"column":      func() string { return field.Name },
		"filename":    func() string { return filename },
		"basename":    func() string { return strings.TrimSuffix(path.Base(filename), path.Ext(filename)) },
		"hash":        hash,
		"filename_with_hash": func() string {
			return fmt.Sprintf("%v.%v%v", strings.TrimSuffix(filename, path.Ext(filename)), hash(), path.Ext(filename))
		},
		"extension": func() string { return strings.TrimPrefix(path.Ext(filename), ".") },
	}
}

func (b Base) GetURL(option *Option, scope *gorm.Scope, field *gorm.Field) string {
	if path := b.GetURLTemplate(option); path != "" {
		tmpl := template.New("").Funcs(getFuncMap(scope, field, b.GetFileName()))
		if tmpl, err := tmpl.Parse(path); err == nil {
			var result = bytes.NewBufferString("")
			if err := tmpl.Execute(result, scope.Value); err == nil {
				return result.String()
			}
		}
	}
	return ""
}

func (b *Base) SetCropOption(option image.Rectangle) {
	b.CropOption = &option
}

func (b *Base) GetCropOption() *image.Rectangle {
	return b.CropOption
}

func (b Base) Retrieve(url string) (*os.File, error) {
	return nil, ErrNotImplemented
}

func (b Base) GetSizes() map[string]Size {
	return map[string]Size{}
}

func (b Base) IsImage() bool {
	_, err := getImageFormat(b.URL())
	return err == nil
}

func getImageFormat(url string) (*imaging.Format, error) {
	formats := map[string]imaging.Format{
		".jpg":  imaging.JPEG,
		".jpeg": imaging.JPEG,
		".png":  imaging.PNG,
		".tif":  imaging.TIFF,
		".tiff": imaging.TIFF,
		".bmp":  imaging.BMP,
		".gif":  imaging.GIF,
	}

	ext := strings.ToLower(filepath.Ext(url))
	if f, ok := formats[ext]; ok {
		return &f, nil
	} else {
		return nil, imaging.ErrUnsupportedFormat
	}
}
<|endoftext|>


<commit_before>package cache

import (
	"os"

	"github.com/boltdb/bolt"
	"github.com/johnny-morrice/godless/api"
	"github.com/johnny-morrice/godless/crdt"
	"github.com/pkg/errors"
)

type BoltOptions struct {
	DBOptions *bolt.Options
	FilePath  string
	Mode      os.FileMode
	Db        *bolt.DB
}

type BoltFactory struct {
	BoltOptions
}

func (factory BoltFactory) MakeCache() (api.Cache, error) {
	const failMsg = "BoltFactory.MakeCache failed"

	cache := boltCache{db: factory.Db}

	err := cache.initBuckets()

	if err != nil {
		return nil, errors.Wrap(err, failMsg)
	}

	return cache, nil
}

func (factory BoltFactory) MakeMemoryImage() (api.MemoryImage, error) {
	const failMsg = "BoltFactory.MakeMemoryImage failed"

	memImg := boltMemoryImage{db: factory.Db}

	err := memImg.initBuckets()

	if err != nil {
		return nil, errors.Wrap(err, failMsg)
	}

	return memImg, nil
}

func MakeBoltCacheFactory(options BoltOptions) (BoltFactory, error) {
	const failMsg = "MakeBoltCacheFactory"

	db, err := connectBolt(options)

	if err != nil {
		return BoltFactory{}, errors.Wrap(err, failMsg)
	}

	factory := BoltFactory{
		BoltOptions: options,
	}
	factory.Db = db

	return factory, nil
}

type boltCache struct {
	db *bolt.DB
}

func (cache boltCache) initBuckets() error {
	panic("not implemented")
}

func (cache boltCache) Close() error {
	panic("not implemented")
}

func (cache boltCache) GetHead() (crdt.IPFSPath, error) {
	panic("not implemented")
}

func (cache boltCache) SetHead(head crdt.IPFSPath) error {
	panic("not implemented")
}

func (cache boltCache) GetIndex(indexAddr crdt.IPFSPath) (crdt.Index, error) {
	panic("not implemented")
}

func (cache boltCache) SetIndex(indexAddr crdt.IPFSPath, index crdt.Index) error {
	panic("not implemented")
}

func (cache boltCache) GetNamespace(namespaceAddr crdt.IPFSPath) (crdt.Namespace, error) {
	panic("not implemented")
}

func (cache boltCache) SetNamespace(namespaceAddr crdt.IPFSPath, namespace crdt.Namespace) error {
	panic("not implemented")
}

func (cache boltCache) CloseCache() error {
	panic("not implemented")
}

type boltMemoryImage struct {
	db *bolt.DB
}

func (memimg boltMemoryImage) initBuckets() error {
	panic("not implemented")
}

func (memimg boltMemoryImage) GetIndex() (crdt.Index, error) {
	panic("not implemented")
}

func (memimg boltMemoryImage) JoinIndex(index crdt.Index) error {
	panic("not implemented")
}

func connectBolt(options BoltOptions) (*bolt.DB, error) {
	return bolt.Open(options.FilePath, options.Mode, options.DBOptions)
}
<commit_msg>Useful bolt functions<commit_after>package cache

import (
	"fmt"
	"os"

	"github.com/boltdb/bolt"
	"github.com/johnny-morrice/godless/api"
	"github.com/johnny-morrice/godless/crdt"
	"github.com/pkg/errors"

	pb "github.com/gogo/protobuf/proto"
)

type BoltOptions struct {
	DBOptions *bolt.Options
	FilePath  string
	Mode      os.FileMode
	Db        *bolt.DB
}

type BoltFactory struct {
	BoltOptions
}

func (factory BoltFactory) MakeCache() (api.Cache, error) {
	const failMsg = "BoltFactory.MakeCache failed"

	cache := boltCache{db: factory.Db}

	err := cache.initBuckets()

	if err != nil {
		return nil, errors.Wrap(err, failMsg)
	}

	return cache, nil
}

func (factory BoltFactory) MakeMemoryImage() (api.MemoryImage, error) {
	const failMsg = "BoltFactory.MakeMemoryImage failed"

	memImg := boltMemoryImage{db: factory.Db}

	err := memImg.initBuckets()

	if err != nil {
		return nil, errors.Wrap(err, failMsg)
	}

	return memImg, nil
}

func MakeBoltCacheFactory(options BoltOptions) (BoltFactory, error) {
	const failMsg = "MakeBoltCacheFactory"

	db, err := connectBolt(options)

	if err != nil {
		return BoltFactory{}, errors.Wrap(err, failMsg)
	}

	factory := BoltFactory{
		BoltOptions: options,
	}
	factory.Db = db

	return factory, nil
}

type boltCache struct {
	db *bolt.DB
}

func (cache boltCache) initBuckets() error {
	panic("not implemented")
}

func (cache boltCache) Close() error {
	panic("not implemented")
}

func (cache boltCache) GetHead() (crdt.IPFSPath, error) {
	panic("not implemented")
}

func (cache boltCache) SetHead(head crdt.IPFSPath) error {
	panic("not implemented")
}

func (cache boltCache) GetIndex(indexAddr crdt.IPFSPath) (crdt.Index, error) {
	panic("not implemented")
}

func (cache boltCache) SetIndex(indexAddr crdt.IPFSPath, index crdt.Index) error {
	panic("not implemented")
}

func (cache boltCache) GetNamespace(namespaceAddr crdt.IPFSPath) (crdt.Namespace, error) {
	panic("not implemented")
}

func (cache boltCache) SetNamespace(namespaceAddr crdt.IPFSPath, namespace crdt.Namespace) error {
	panic("not implemented")
}

func (cache boltCache) CloseCache() error {
	panic("not implemented")
}

type boltMemoryImage struct {
	db *bolt.DB
}

func (memimg boltMemoryImage) initBuckets() error {
	return initBucket(memimg.db, BOLT_MEMORY_IMAGE_BUCKET_NAME)
}

func (memimg boltMemoryImage) GetIndex() (crdt.Index, error) {
	panic("not implemented")
}

func (memimg boltMemoryImage) JoinIndex(index crdt.Index) error {
	panic("not implemented")
}

func connectBolt(options BoltOptions) (*bolt.DB, error) {
	return bolt.Open(options.FilePath, options.Mode, options.DBOptions)
}

func initBucket(db *bolt.DB, bucketName []byte) error {
	return db.Update(func(transaction *bolt.Tx) error {
		_, err := transaction.CreateBucketIfNotExists(bucketName)
		return err
	})
}

func putMessage(bucket *bolt.Bucket, key []byte, value pb.Message) error {
	keyText := string(key)
	valueBytes, err := pb.Marshal(value)

	if err != nil {
		msg := fmt.Sprintf("Failed to Marshal protobuf message for Bolt key: %v", keyText)
		return errors.Wrap(err, msg)
	}

	err = bucket.Put(key, valueBytes)

	if err != nil {
		msg := fmt.Sprintf("Failed to Put value at Bolt key: %v", keyText)
		return errors.Wrap(err, msg)
	}

	return nil
}

func getMessage(bucket *bolt.Bucket, key []byte, value pb.Message) error {
	keyText := string(key)
	valueBytes := bucket.Get(key)

	if valueBytes == nil {
		return fmt.Errorf("Failed to Get value at Bolt key: %v", keyText)
	}

	err := pb.Unmarshal(valueBytes, value)

	if err != nil {
		msg := fmt.Sprintf("Failed to Unmarshal protobuf message for Bolt key: %v", keyText)
		return errors.Wrap(err, msg)
	}

	return nil
}

var CURRENT_INDEX_KEY = []byte("current_index")
var BOLT_MEMORY_IMAGE_BUCKET_NAME = []byte("memory_image")
var BOLT_CACHE_BUCKET_NAME = []byte("cache")
<|endoftext|>


<commit_before>// Package raw provides a raw.Data type that is useful
// for writing and reading raw data blobs to and from
// HTTP clients and persistent storage.
package raw

import (
	"bytes"
	"compress/flate"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"

	"golang.org/x/net/context"
	"sp.com.au/exp/errs"
	"sp.com.au/exp/log"
)

// Maximum size we are prepared to read from a HTTP client.
// Anything this size or larger gets discarded.
var MaxLen = 1024 * 1024 * 16

// Content encodings
const (
	ceIdentity = "identity"
	ceDeflate  = "deflate"
)

// Represents a data BLOB that can be read from or written to
// persistent storage, or a HTTP client.
type Data struct {
	ContentType        string
	ContentEncoding    string
	Content            []byte
	UncompressedLength int
}

// IsCompressed returns whether the content is compressed.
func (data *Data) IsCompressed() bool {
	if data.ContentEncoding == "" {
		data.ContentEncoding = ceIdentity
	}
	return data.ContentEncoding != ceIdentity
}

// ReadRequest reads the data from the request into the raw.Data.
func (data *Data) ReadRequest(ctx context.Context, r *http.Request) error {
	if cl := r.Header.Get("Content-Length"); cl != "" {
		v, err := strconv.ParseInt(cl, 10, 64)
		if err != nil || v < 0 {
			return log.Warn("invalid content-length",
				log.WithValue("content-length", cl),
				log.WithContext(ctx),
				log.WithStatusBadRequest())
		}

		if v >= int64(MaxLen) {
			return log.Warn("max length excceeded",
				log.WithContext(ctx),
				log.WithStatusBadRequest(),
				log.WithValue("MaxLen", MaxLen))
		}

		buf := make([]byte, v)

		_, err = io.ReadFull(r.Body, buf)
		if err != nil {
			return log.Warn("cannot read content",
				log.WithContext(ctx),
				log.WithError(err),
				log.WithStatusBadRequest())
		}
		data.Content = buf
	} else {
		reader := io.LimitReader(r.Body, int64(MaxLen))
		content, err := ioutil.ReadAll(reader)
		if err != nil {
			return err
		}
		if len(content) >= MaxLen {
			return log.Warn("max size exceeded",
				log.WithContext(ctx),
				log.WithStatusBadRequest(),
				log.WithValue("MaxLen", MaxLen))
		}
		data.Content = content
	}

	// The HTTP specification does not mention Content-Encoding for
	// requests, but sometimes it is handy to allow the client to do
	// so.
	if ce := r.Header.Get("Content-Encoding"); ce != "" {
		data.ContentEncoding = ce
		data.UncompressedLength = 0 // not known
	} else {
		data.UncompressedLength = len(data.Content)
		data.ContentEncoding = ceIdentity
	}

	data.ContentType = r.Header.Get("Content-Type")
	if data.ContentType == "" {
		data.ContentType = "application/octet-stream"
	}
	return nil
}

// ReadRequest reads the data from the request into the raw.Data.
func (data *Data) ReadResponse(ctx context.Context, r *http.Response) error {
	if cl := r.Header.Get("Content-Length"); cl != "" {
		v, err := strconv.ParseInt(cl, 10, 64)
		if err != nil || v < 0 {
			return log.Warn("invalid content-length",
				log.WithValue("content-length", cl),
				log.WithContext(ctx))
		}

		if v >= int64(MaxLen) {
			return log.Warn("max length excceeded",
				log.WithContext(ctx),
				log.WithStatusBadRequest(),
				log.WithValue("MaxLen", MaxLen))
		}

		buf := make([]byte, v)

		_, err = io.ReadFull(r.Body, buf)
		if err != nil {
			return log.Warn("cannot read content",
				log.WithContext(ctx),
				log.WithError(err))
		}
		data.Content = buf
	} else {
		reader := io.LimitReader(r.Body, int64(MaxLen))
		content, err := ioutil.ReadAll(reader)
		if err != nil {
			return err
		}
		if len(content) >= MaxLen {
			return log.Warn("max size exceeded",
				log.WithContext(ctx),
				log.WithStatusBadRequest(),
				log.WithValue("MaxLen", MaxLen))
		}
		data.Content = content
	}

	// The HTTP specification does not mention Content-Encoding for
	// requests, but sometimes it is handy to allow the client to do
	// so.
	if ce := r.Header.Get("Content-Encoding"); ce != "" {
		data.ContentEncoding = ce
		data.UncompressedLength = 0 // not known
	} else {
		data.UncompressedLength = len(data.Content)
		data.ContentEncoding = ceIdentity
	}

	data.ContentType = r.Header.Get("Content-Type")
	if data.ContentType == "" {
		data.ContentType = "application/octet-stream"
	}
	return nil
}

// WriteResponse writes the contents to the client as a response.
func (data *Data) WriteResponse(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
	// TODO: this is a very naive handling of the Accept-Encoding
	// header. In particular it does not handle deflate;q=0, which is
	// a valid way of saying that deflate is not acceptable.
	if data.IsCompressed() {
		if ae := r.Header.Get("Accept-Encoding"); !strings.Contains(ae, data.ContentEncoding) {
			// the user agent does not accept the content encoding, so we
			// have to decompress before sending
			err := data.Decompress()
			if err != nil {
				return err
			}
		}
	}

	if len(data.Content) == 0 {
		w.Header().Set("Content-Length", "0")
		w.Header().Del("Content-Type")
		w.Header().Del("Content-Encoding")
		w.WriteHeader(http.StatusNoContent)
		return nil
	}

	if data.IsCompressed() {
		w.Header().Set("Content-Encoding", data.ContentEncoding)
	} else {
		w.Header().Del("Content-Encoding")
	}
	w.Header().Set("Content-Type", data.ContentType)
	w.Header().Set("Content-Length", strconv.Itoa(len(data.Content)))
	n, err := w.Write(data.Content)
	if err != nil {
		// Failed to write, but do not return an error code, as there
		// is no way to return an error message to the client after
		// writing has started.
		log.Warn("cannot write response",
			log.WithError(err),
			log.WithContext(ctx))
	}
	if n != len(data.Content) {
		log.Warn("not all bytes sent",
			log.WithValue("expected", len(data.Content)),
			log.WithValue("actual", n),
			log.WithContext(ctx))
	}
	return nil
}

func (data *Data) Decompress() error {
	if !data.IsCompressed() {
		return nil
	}
	if data.ContentEncoding != ceDeflate {
		return log.Error("unknown content-encoding",
			log.WithValue("content-encoding", data.ContentEncoding))
	}
	input := bytes.NewBuffer(data.Content)
	reader := flate.NewReader(input)
	writer := bytes.Buffer{}
	_, err := io.Copy(&writer, reader)
	if err != nil {
		return err
	}
	data.Content = writer.Bytes()
	data.ContentEncoding = ""
	data.UncompressedLength = len(data.Content)
	return nil
}

func (data *Data) Compress() error {
	if data.IsCompressed() || len(data.Content) < 32 {
		// already compressed, or not worth compressing
		// because data is nil or too short
		return nil
	}

	buf := bytes.Buffer{}
	w, err := flate.NewWriter(&buf, flate.BestCompression)
	if err != nil {
		return err
	}
	n, err := w.Write(data.Content)
	if err != nil {
		return err
	}
	if n != len(data.Content) {
		return errs.ServerError("cannot compress")
	}
	err = w.Close()
	if err != nil {
		return err
	}
	compressedBytes := buf.Bytes()

	if len(compressedBytes) < len(data.Content) {
		data.UncompressedLength = len(data.Content)
		data.Content = compressedBytes
		data.ContentEncoding = ceDeflate
	}

	return nil
}

func (data *Data) UnmarshalTo(v interface{}) error {
	err := data.Decompress()
	if err != nil {
		return err
	}
	err = json.Unmarshal(data.Content, v)
	if err != nil {
		return err
	}
	return nil
}

func (data *Data) MarshalFrom(v interface{}) error {
	b, err := json.Marshal(v)
	if err != nil {
		return err
	}
	data.Content = b
	data.ContentType = "application/json"
	data.ContentEncoding = ""
	data.UncompressedLength = len(b)
	return nil
}
<commit_msg>Handle decompressing "gzip".<commit_after>// Package raw provides a raw.Data type that is useful
// for writing and reading raw data blobs to and from
// HTTP clients and persistent storage.
package raw

import (
	"bytes"
	"compress/flate"
	"compress/gzip"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"

	"golang.org/x/net/context"
	"sp.com.au/exp/errs"
	"sp.com.au/exp/log"
)

// Maximum size we are prepared to read from a HTTP client.
// Anything this size or larger gets discarded.
var MaxLen = 1024 * 1024 * 16

// Content encodings
const (
	ceIdentity = "identity"
	ceDeflate  = "deflate"
	ceGzip     = "gzip"
)

// Represents a data BLOB that can be read from or written to
// persistent storage, or a HTTP client.
type Data struct {
	ContentType        string
	ContentEncoding    string
	Content            []byte
	UncompressedLength int
}

// IsCompressed returns whether the content is compressed.
func (data *Data) IsCompressed() bool {
	if data.ContentEncoding == "" {
		data.ContentEncoding = ceIdentity
	}
	return data.ContentEncoding != ceIdentity
}

// ReadRequest reads the data from the request into the raw.Data.
func (data *Data) ReadRequest(ctx context.Context, r *http.Request) error {
	if cl := r.Header.Get("Content-Length"); cl != "" {
		v, err := strconv.ParseInt(cl, 10, 64)
		if err != nil || v < 0 {
			return log.Warn("invalid content-length",
				log.WithValue("content-length", cl),
				log.WithContext(ctx),
				log.WithStatusBadRequest())
		}

		if v >= int64(MaxLen) {
			return log.Warn("max length excceeded",
				log.WithContext(ctx),
				log.WithStatusBadRequest(),
				log.WithValue("MaxLen", MaxLen))
		}

		buf := make([]byte, v)

		_, err = io.ReadFull(r.Body, buf)
		if err != nil {
			return log.Warn("cannot read content",
				log.WithContext(ctx),
				log.WithError(err),
				log.WithStatusBadRequest())
		}
		data.Content = buf
	} else {
		reader := io.LimitReader(r.Body, int64(MaxLen))
		content, err := ioutil.ReadAll(reader)
		if err != nil {
			return err
		}
		if len(content) >= MaxLen {
			return log.Warn("max size exceeded",
				log.WithContext(ctx),
				log.WithStatusBadRequest(),
				log.WithValue("MaxLen", MaxLen))
		}
		data.Content = content
	}

	// The HTTP specification does not mention Content-Encoding for
	// requests, but sometimes it is handy to allow the client to do
	// so.
	if ce := r.Header.Get("Content-Encoding"); ce != "" {
		data.ContentEncoding = ce
		data.UncompressedLength = 0 // not known
	} else {
		data.UncompressedLength = len(data.Content)
		data.ContentEncoding = ceIdentity
	}

	data.ContentType = r.Header.Get("Content-Type")
	if data.ContentType == "" {
		data.ContentType = "application/octet-stream"
	}
	return nil
}

// ReadRequest reads the data from the request into the raw.Data.
func (data *Data) ReadResponse(ctx context.Context, r *http.Response) error {
	if cl := r.Header.Get("Content-Length"); cl != "" {
		v, err := strconv.ParseInt(cl, 10, 64)
		if err != nil || v < 0 {
			return log.Warn("invalid content-length",
				log.WithValue("content-length", cl),
				log.WithContext(ctx))
		}

		if v >= int64(MaxLen) {
			return log.Warn("max length excceeded",
				log.WithContext(ctx),
				log.WithStatusBadRequest(),
				log.WithValue("MaxLen", MaxLen))
		}

		buf := make([]byte, v)

		_, err = io.ReadFull(r.Body, buf)
		if err != nil {
			return log.Warn("cannot read content",
				log.WithContext(ctx),
				log.WithError(err))
		}
		data.Content = buf
	} else {
		reader := io.LimitReader(r.Body, int64(MaxLen))
		content, err := ioutil.ReadAll(reader)
		if err != nil {
			return err
		}
		if len(content) >= MaxLen {
			return log.Warn("max size exceeded",
				log.WithContext(ctx),
				log.WithStatusBadRequest(),
				log.WithValue("MaxLen", MaxLen))
		}
		data.Content = content
	}

	// The HTTP specification does not mention Content-Encoding for
	// requests, but sometimes it is handy to allow the client to do
	// so.
	if ce := r.Header.Get("Content-Encoding"); ce != "" {
		data.ContentEncoding = ce
		data.UncompressedLength = 0 // not known
	} else {
		data.UncompressedLength = len(data.Content)
		data.ContentEncoding = ceIdentity
	}

	data.ContentType = r.Header.Get("Content-Type")
	if data.ContentType == "" {
		data.ContentType = "application/octet-stream"
	}
	return nil
}

// WriteResponse writes the contents to the client as a response.
func (data *Data) WriteResponse(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
	// TODO: this is a very naive handling of the Accept-Encoding
	// header. In particular it does not handle deflate;q=0, which is
	// a valid way of saying that deflate is not acceptable.
	if data.IsCompressed() {
		if ae := r.Header.Get("Accept-Encoding"); !strings.Contains(ae, data.ContentEncoding) {
			// the user agent does not accept the content encoding, so we
			// have to decompress before sending
			err := data.Decompress()
			if err != nil {
				return err
			}
		}
	}

	if len(data.Content) == 0 {
		w.Header().Set("Content-Length", "0")
		w.Header().Del("Content-Type")
		w.Header().Del("Content-Encoding")
		w.WriteHeader(http.StatusNoContent)
		return nil
	}

	if data.IsCompressed() {
		w.Header().Set("Content-Encoding", data.ContentEncoding)
	} else {
		w.Header().Del("Content-Encoding")
	}
	w.Header().Set("Content-Type", data.ContentType)
	w.Header().Set("Content-Length", strconv.Itoa(len(data.Content)))
	n, err := w.Write(data.Content)
	if err != nil {
		// Failed to write, but do not return an error code, as there
		// is no way to return an error message to the client after
		// writing has started.
		log.Warn("cannot write response",
			log.WithError(err),
			log.WithContext(ctx))
	}
	if n != len(data.Content) {
		log.Warn("not all bytes sent",
			log.WithValue("expected", len(data.Content)),
			log.WithValue("actual", n),
			log.WithContext(ctx))
	}
	return nil
}

func (data *Data) Decompress() error {
	if !data.IsCompressed() {
		return nil
	}
	input := bytes.NewBuffer(data.Content)
	var reader io.Reader
	if data.ContentEncoding == ceDeflate {
		reader = flate.NewReader(input)
	} else if data.ContentEncoding == ceGzip {
		var err error
		if reader, err = gzip.NewReader(input); err != nil {
			return err
		}
	} else {
		return log.Error("unknown content-encoding",
			log.WithValue("content-encoding", data.ContentEncoding))
	}
	writer := bytes.Buffer{}
	_, err := io.Copy(&writer, reader)
	if err != nil {
		return err
	}
	data.Content = writer.Bytes()
	data.ContentEncoding = ""
	data.UncompressedLength = len(data.Content)
	return nil
}

func (data *Data) Compress() error {
	if data.IsCompressed() || len(data.Content) < 32 {
		// already compressed, or not worth compressing
		// because data is nil or too short
		return nil
	}

	buf := bytes.Buffer{}
	w, err := flate.NewWriter(&buf, flate.BestCompression)
	if err != nil {
		return err
	}
	n, err := w.Write(data.Content)
	if err != nil {
		return err
	}
	if n != len(data.Content) {
		return errs.ServerError("cannot compress")
	}
	err = w.Close()
	if err != nil {
		return err
	}
	compressedBytes := buf.Bytes()

	if len(compressedBytes) < len(data.Content) {
		data.UncompressedLength = len(data.Content)
		data.Content = compressedBytes
		data.ContentEncoding = ceDeflate
	}

	return nil
}

func (data *Data) UnmarshalTo(v interface{}) error {
	err := data.Decompress()
	if err != nil {
		return err
	}
	err = json.Unmarshal(data.Content, v)
	if err != nil {
		return err
	}
	return nil
}

func (data *Data) MarshalFrom(v interface{}) error {
	b, err := json.Marshal(v)
	if err != nil {
		return err
	}
	data.Content = b
	data.ContentType = "application/json"
	data.ContentEncoding = ""
	data.UncompressedLength = len(b)
	return nil
}
<|endoftext|>