<commit_before>package lazycache

import "fmt"
import "net/http"
import "strings"
import "strconv"
import "path/filepath"

//import "strings"
import "io"
import "encoding/json"
import "github.com/amarburg/go-fast-png"
import "image/jpeg"
import "bytes"
import "regexp"
import "time"

import "sync"

import "github.com/amarburg/go-lazyquicktime"

var leadingNumbers, _ = regexp.Compile("^\\d+")

type MoovHandlerTiming struct {
	Handler, Metadata, Extraction, Encode time.Duration
}

type QTStore struct {
	Cache map[string](*lazyquicktime.LazyQuicktime)
	Mutex sync.Mutex

	Stats struct {
		Requests, Misses int64
	}
}

var qtCache QTStore

func init() {
	qtCache = QTStore{
		Cache: make(map[string](*lazyquicktime.LazyQuicktime)),
	}
}

func (cache *QTStore) getLQT(node *Node) (*lazyquicktime.LazyQuicktime, error) {

	cache.Mutex.Lock()
	defer cache.Mutex.Unlock()

	cache.Stats.Requests++

	// Initialize or update as necessary
	//Logger.Log("debug", fmt.Sprintf("Querying metadata store for %s", node.Path))
	lqt, has := cache.Cache[node.trimPath]

	if !has {
		cache.Stats.Misses++

		//Logger.Log("msg", fmt.Errorf("Initializing LazyFile to %s", node.Path))
		fs, err := node.Fs.LazyFile(node.Path)

		if err != nil {
			return nil, fmt.Errorf("Something's went boom opening the HTTP Source!")
		}

		//Logger.Log("msg", fmt.Sprintf("Need to pull quicktime information for %s", fs.Path()))
		lqt, err = lazyquicktime.LoadMovMetadata(fs)
		if err != nil {
			return nil, fmt.Errorf("Something's went boom storing the quicktime file: %s", err.Error())
		}

		//Logger.Log("msg", fmt.Sprintf("Updating metadata store for %s", fs.Path()))
		cache.Cache[node.trimPath] = lqt
		if err != nil {
			return nil, fmt.Errorf("Something's went boom storing the quicktime file: %s", err.Error())
		}

	}

	return lqt, nil
}

func MoovHandler(node *Node, path []string, w http.ResponseWriter, req *http.Request) *Node {
	Logger.Log("msg", fmt.Sprintf("Quicktime handler: %s with residual path (%d): (%s)", node.Path, len(path), strings.Join(path, ":")))

	timing := MoovHandlerTiming{}
	movStart := time.Now()

	// uri := node.Fs.Uri
	// uri.Path += node.Path

	metadataStart := time.Now()
	lqt, err := qtCache.getLQT(node)
	timeTrack(metadataStart, &timing.Metadata)

	if err != nil {
		Logger.Log("msg", err.Error())

		b,_ := json.MarshalIndent(struct {
							URL, Error string
						}{
							URL: node.Path,
							Error: err.Error(),
						}, "", "  ")

		// http.Error(w, err.Error(), 500)
		w.Write(b)
		return nil
	}

	if len(path) == 0 {
		// Leaf node

		Logger.Log("msg", fmt.Sprintf("Returning movie information for %s", node.Path))

		// Temporary structure for JSON output
		out := struct {
			URL       string
			NumFrames int
			Duration  float32
		}{
			URL:       node.Path,
			NumFrames: lqt.NumFrames(),
			Duration:  lqt.Duration(),
		}

		b, err := json.MarshalIndent(out, "", "  ")
		if err != nil {
			fmt.Fprintln(w, "JSON error:", err)
		}

		//Logger.Log("msg", fmt.Sprintf("..... done"))

		w.Write(b)
	} else {

		// Handle any residual path elements (frames, etc) here
		switch strings.ToLower(path[0]) {
		case "frame":
			extractFrame(node, lqt, path[1:], w, req, &timing)
		default:
			http.Error(w, fmt.Sprintf("Didn't understand request \"%s\"", path[0]), 500)
		}
	}

	timeTrack(movStart, &timing.Handler)

	t, _ := json.Marshal(timing)
	Logger.Log("timing", t)

	return nil
}

func extractFrame(node *Node, lqt *lazyquicktime.LazyQuicktime, path []string, w http.ResponseWriter, req *http.Request, timing *MoovHandlerTiming) {

	if len(path) == 0 {
		http.Error(w, fmt.Sprintf("Need to specify frame number"), 500)
		return

	}

	frameNum, err := strconv.Atoi(leadingNumbers.FindString(path[0]))
	if err != nil {
		http.Error(w, fmt.Sprintf("Error parsing frame number \"%s\"", path[0]), 500)
		return
	}

	if frameNum > lqt.NumFrames() {
		http.Error(w, fmt.Sprintf("Requested frame %d in movie of length %d frames", frameNum, lqt.NumFrames()), 400)
		return
	}

	if frameNum < 1 {
		http.Error(w, "Requested frame 0, Quicktime movies start with frame 1", 400)
		return
	}

	// Looks for extension
	extension := filepath.Ext(path[0])

	var contentType string

	switch extension {
	case ".jpg", ".jpeg":
		contentType = "image/jpeg"
		extension = ".jpg"
	case "", ".png":
		extension = ".png"
		contentType = "image/png"
	default:
		http.Error(w, fmt.Sprintf("Unknown image extension \"%s\"", extension), 500)
		return
	}

	UUID := req.URL.Path + extension
	url, ok := ImageCache.Url(UUID)

	if ok {
		Logger.Log("msg", fmt.Sprintf("Image %s exists in the Image store at %s", UUID, url))
		// Set Content-Type or response
		w.Header().Set("Content-Type", contentType)
		// w.Header().Set("Location", url)
		Logger.Log("msg", fmt.Sprintf("Redirecting to %s", url))
		http.Redirect(w, req, url, http.StatusTemporaryRedirect)

	} else {

		startExt := time.Now()
		img, err := lqt.ExtractFrame(frameNum)
		if err != nil {
			http.Error(w, fmt.Sprintf("Error generating image for frame %d: %s", frameNum, err.Error()), 500)
			return
		}
		timeTrack(startExt, &timing.Extraction)

		buffer := new(bytes.Buffer)

		startEncode := time.Now()

		switch contentType {
		case "image/png":
			encoder := fastpng.Encoder{
				CompressionLevel: fastpng.DefaultCompression,
			}
			err = encoder.Encode(buffer, img)
		case "image/jpeg":
			err = jpeg.Encode(buffer, img, &jpeg.Options{Quality: jpeg.DefaultQuality})
		}

		timeTrack(startEncode, &timing.Encode)

		Logger.Log("debug", fmt.Sprintf("%s size %d MB\n", contentType, buffer.Len()/(1024*1024)))

		imgReader := bytes.NewReader(buffer.Bytes())

		// write image to Image store
		ImageCache.Store(UUID, imgReader)

		imgReader.Seek(0, io.SeekStart)
		_, err = imgReader.WriteTo(w)
		if err != nil {
			fmt.Printf("Error writing to HTTP buffer: %s\n", err.Error())
		}

	}

}
<commit_msg>Present file size in movie metadata JSON.<commit_after>package lazycache

import "fmt"
import "net/http"
import "strings"
import "strconv"
import "path/filepath"

//import "strings"
import "io"
import "encoding/json"
import "github.com/amarburg/go-fast-png"
import "image/jpeg"
import "bytes"
import "regexp"
import "time"

import "sync"

import "github.com/amarburg/go-lazyquicktime"

var leadingNumbers, _ = regexp.Compile("^\\d+")

type MoovHandlerTiming struct {
	Handler, Metadata, Extraction, Encode time.Duration
}

type QTStore struct {
	Cache map[string](*lazyquicktime.LazyQuicktime)
	Mutex sync.Mutex

	Stats struct {
		Requests, Misses int64
	}
}

var qtCache QTStore

func init() {
	qtCache = QTStore{
		Cache: make(map[string](*lazyquicktime.LazyQuicktime)),
	}
}

func (cache *QTStore) getLQT(node *Node) (*lazyquicktime.LazyQuicktime, error) {

	cache.Mutex.Lock()
	defer cache.Mutex.Unlock()

	cache.Stats.Requests++

	// Initialize or update as necessary
	//Logger.Log("debug", fmt.Sprintf("Querying metadata store for %s", node.Path))
	lqt, has := cache.Cache[node.trimPath]

	if !has {
		cache.Stats.Misses++

		//Logger.Log("msg", fmt.Errorf("Initializing LazyFile to %s", node.Path))
		fs, err := node.Fs.LazyFile(node.Path)

		if err != nil {
			return nil, fmt.Errorf("Something's went boom opening the HTTP Source!")
		}

		//Logger.Log("msg", fmt.Sprintf("Need to pull quicktime information for %s", fs.Path()))
		lqt, err = lazyquicktime.LoadMovMetadata(fs)
		if err != nil {
			return nil, fmt.Errorf("Something's went boom storing the quicktime file: %s", err.Error())
		}

		//Logger.Log("msg", fmt.Sprintf("Updating metadata store for %s", fs.Path()))
		cache.Cache[node.trimPath] = lqt
		if err != nil {
			return nil, fmt.Errorf("Something's went boom storing the quicktime file: %s", err.Error())
		}

	}

	return lqt, nil
}

func MoovHandler(node *Node, path []string, w http.ResponseWriter, req *http.Request) *Node {
	Logger.Log("msg", fmt.Sprintf("Quicktime handler: %s with residual path (%d): (%s)", node.Path, len(path), strings.Join(path, ":")))

	timing := MoovHandlerTiming{}
	movStart := time.Now()

	// uri := node.Fs.Uri
	// uri.Path += node.Path

	metadataStart := time.Now()
	lqt, err := qtCache.getLQT(node)
	timeTrack(metadataStart, &timing.Metadata)

	if err != nil {
		Logger.Log("msg", err.Error())

		b,_ := json.MarshalIndent(struct {
							URL, Error string
						}{
							URL: node.Path,
							Error: err.Error(),
						}, "", "  ")

		// http.Error(w, err.Error(), 500)
		w.Write(b)
		return nil
	}

	if len(path) == 0 {
		// Leaf node

		Logger.Log("msg", fmt.Sprintf("Returning movie information for %s", node.Path))

		// Temporary structure for JSON output
		out := struct {
			URL       string
			NumFrames int
			Duration  float32
			FileSize  uint64
		}{
			URL:       node.Path,
			NumFrames: lqt.NumFrames(),
			Duration:  lqt.Duration(),
			FileSize:  uint64(lqt.FileSize()),
		}

		b, err := json.MarshalIndent(out, "", "  ")
		if err != nil {
			fmt.Fprintln(w, "JSON error:", err)
		}

		//Logger.Log("msg", fmt.Sprintf("..... done"))

		w.Write(b)
	} else {

		// Handle any residual path elements (frames, etc) here
		switch strings.ToLower(path[0]) {
		case "frame":
			extractFrame(node, lqt, path[1:], w, req, &timing)
		default:
			http.Error(w, fmt.Sprintf("Didn't understand request \"%s\"", path[0]), 500)
		}
	}

	timeTrack(movStart, &timing.Handler)

	t, _ := json.Marshal(timing)
	Logger.Log("timing", t)

	return nil
}

func extractFrame(node *Node, lqt *lazyquicktime.LazyQuicktime, path []string, w http.ResponseWriter, req *http.Request, timing *MoovHandlerTiming) {

	if len(path) == 0 {
		http.Error(w, fmt.Sprintf("Need to specify frame number"), 500)
		return

	}

	frameNum, err := strconv.Atoi(leadingNumbers.FindString(path[0]))
	if err != nil {
		http.Error(w, fmt.Sprintf("Error parsing frame number \"%s\"", path[0]), 500)
		return
	}

	if frameNum > lqt.NumFrames() {
		http.Error(w, fmt.Sprintf("Requested frame %d in movie of length %d frames", frameNum, lqt.NumFrames()), 400)
		return
	}

	if frameNum < 1 {
		http.Error(w, "Requested frame 0, Quicktime movies start with frame 1", 400)
		return
	}

	// Looks for extension
	extension := filepath.Ext(path[0])

	var contentType string

	switch extension {
	case ".jpg", ".jpeg":
		contentType = "image/jpeg"
		extension = ".jpg"
	case "", ".png":
		extension = ".png"
		contentType = "image/png"
	default:
		http.Error(w, fmt.Sprintf("Unknown image extension \"%s\"", extension), 500)
		return
	}

	UUID := req.URL.Path + extension
	url, ok := ImageCache.Url(UUID)

	if ok {
		Logger.Log("msg", fmt.Sprintf("Image %s exists in the Image store at %s", UUID, url))
		// Set Content-Type or response
		w.Header().Set("Content-Type", contentType)
		// w.Header().Set("Location", url)
		Logger.Log("msg", fmt.Sprintf("Redirecting to %s", url))
		http.Redirect(w, req, url, http.StatusTemporaryRedirect)

	} else {

		startExt := time.Now()
		img, err := lqt.ExtractFrame(frameNum)
		if err != nil {
			http.Error(w, fmt.Sprintf("Error generating image for frame %d: %s", frameNum, err.Error()), 500)
			return
		}
		timeTrack(startExt, &timing.Extraction)

		buffer := new(bytes.Buffer)

		startEncode := time.Now()

		switch contentType {
		case "image/png":
			encoder := fastpng.Encoder{
				CompressionLevel: fastpng.DefaultCompression,
			}
			err = encoder.Encode(buffer, img)
		case "image/jpeg":
			err = jpeg.Encode(buffer, img, &jpeg.Options{Quality: jpeg.DefaultQuality})
		}

		timeTrack(startEncode, &timing.Encode)

		Logger.Log("debug", fmt.Sprintf("%s size %d MB\n", contentType, buffer.Len()/(1024*1024)))

		imgReader := bytes.NewReader(buffer.Bytes())

		// write image to Image store
		ImageCache.Store(UUID, imgReader)

		imgReader.Seek(0, io.SeekStart)
		_, err = imgReader.WriteTo(w)
		if err != nil {
			fmt.Printf("Error writing to HTTP buffer: %s\n", err.Error())
		}

	}

}
<|endoftext|>


<commit_before>package lazycache

import "fmt"
import "net/http"
import "strings"
import "strconv"

//import "strings"
import "io"
import "encoding/json"
import "github.com/amarburg/go-fast-png"
import "bytes"
import "regexp"
import "time"

import "github.com/amarburg/go-lazyquicktime"

var leadingNumbers, _ = regexp.Compile("^\\d+")

type QTMetadata struct {
	URL       string
	NumFrames int
	Duration  float32
}

//const qtPrefix = "qt."

var QTMetadataStore JSONStore

func init() {
	// Establish a default handler
	QTMetadataStore = CreateMapJSONStore()
}

func MoovHandler(node *Node, path []string, w http.ResponseWriter, req *http.Request) *Node {
	DefaultLogger.Log("msg", fmt.Sprintf("Quicktime handler: %s with residual path (%d): (%s)", node.Path, len(path), strings.Join(path, ":")))

	movStart := time.Now()

	// uri := node.Fs.Uri
	// uri.Path += node.Path
	//

	// Initialize or update as necessary
	lqt := &lazyquicktime.LazyQuicktime{}

	QTMetadataStore.Lock()
	has, _ := QTMetadataStore.Get(node.trimPath, lqt)

	if !has {
		fs, err := node.Fs.LazyFile(node.Path)

		//fs, err := lazyfs.OpenHttpSource(uri)
		if err != nil {
			http.Error(w, "Something's went boom opening the HTTP Source!", 500)
			return nil
		}

		DefaultLogger.Log("msg", fmt.Sprintf("Need to pull quicktime information for %s", fs.Path()))
		lqt, err = lazyquicktime.LoadMovMetadata(fs)
		if err != nil {
			http.Error(w, fmt.Sprintf("Something's went boom storing the quicktime file: %s", err.Error()), 500)
			return nil
		}

		//fmt.Println(lqt)

		err = QTMetadataStore.Update(node.trimPath, *lqt)
		if err != nil {
			http.Error(w, fmt.Sprintf("Something's went boom storing the quicktime file: %s", err.Error()), 500)
			return nil
		}
	} else {
		DefaultLogger.Log("msg", fmt.Sprintf("Map store had entry for %s", node.trimPath))
	}
	QTMetadataStore.Unlock()

	if len(path) == 0 {
		// Leaf node

		out := QTMetadata{
			URL:       node.Path,
			NumFrames: lqt.NumFrames(),
			Duration:  lqt.Duration(),
		}

		// Temporary structure for JSON output
		b, err := json.MarshalIndent(out, "", "  ")
		if err != nil {
			fmt.Fprintln(w, "JSON error:", err)
		}

		w.Write(b)
	} else {

		// Handle any residual path elements (frames, etc) here
		switch strings.ToLower(path[0]) {
		case "frame":
			handleFrame(node, lqt, path[1:], w, req)
		default:
			http.Error(w, fmt.Sprintf("Didn't understand request \"%s\"", path[0]), 500)
		}
	}

	timeTrack(movStart, "Moov handler")

	return nil
}

func handleFrame(node *Node, lqt *lazyquicktime.LazyQuicktime, path []string, w http.ResponseWriter, req *http.Request) {

	if len(path) == 0 {
		http.Error(w, fmt.Sprintf("Need to specify frame number"), 500)
		return

	}

	frameNum, err := strconv.Atoi(leadingNumbers.FindString(path[0]))
	if err != nil {
		http.Error(w, fmt.Sprintf("Error parsing frame number \"%s\"", path[0]), 500)
		return
	}

	if frameNum > lqt.NumFrames() {
		http.Error(w, fmt.Sprintf("Requested frame %d in movie of length %d frames", frameNum, lqt.NumFrames()), 400)
		return
	}
	if frameNum < 1 {
		http.Error(w, "Requested frame 0, Quicktime movies start with frame 1", 400)
		return
	}

	UUID := req.URL.Path + ".png"
	url, ok := DefaultImageStore.Url(UUID)

	if ok {
		DefaultLogger.Log("msg", fmt.Sprintf("Image %s exists in the Image store at %s", UUID, url))
		// Set Content-Type or response
		w.Header().Set("Content-Type", "image/png")
		// w.Header().Set("Location", url)
		DefaultLogger.Log("msg", fmt.Sprintf("Redirecting to %s", url))
		http.Redirect(w, req, url, http.StatusTemporaryRedirect)

	} else {

		startExt := time.Now()
		img, err := lqt.ExtractFrame(frameNum)
		if err != nil {
			http.Error(w, fmt.Sprintf("Error generating image for frame %d: %s", frameNum, err.Error()), 500)
			return
		}
		timeTrack(startExt, "Frame extraction")

		buffer := new(bytes.Buffer)

		encoder := fastpng.Encoder{
			CompressionLevel: fastpng.DefaultCompression,
		}

		startEncode := time.Now()
		err = encoder.Encode(buffer, img)
		timeTrack(startEncode, "Png encode")

		fmt.Println("PNG size", buffer.Len()/(1024*1024), "MB")

		imgReader := bytes.NewReader(buffer.Bytes())

		// write image to Image store
		DefaultImageStore.Store(UUID, imgReader)

		//Rewind the io, and write to the HTTP channel
		imgReader.Seek(0, io.SeekStart)
		_, err = imgReader.WriteTo(w)
		//fmt.Printf("Wrote %d bytes to http buffer\n", n)
		if err != nil {
			fmt.Printf("Error writing to HTTP buffer: %s\n", err.Error())
		}

		timeTrack(startExt, "Full extract and write")

	}

}
<commit_msg>Added debugging information to lazycache.<commit_after>package lazycache

import "fmt"
import "net/http"
import "strings"
import "strconv"

//import "strings"
import "io"
import "encoding/json"
import "github.com/amarburg/go-fast-png"
import "bytes"
import "regexp"
import "time"

import "github.com/amarburg/go-lazyquicktime"

var leadingNumbers, _ = regexp.Compile("^\\d+")

type QTMetadata struct {
	URL       string
	NumFrames int
	Duration  float32
}

//const qtPrefix = "qt."

var QTMetadataStore JSONStore

func init() {
	// Establish a default handler
	QTMetadataStore = CreateMapJSONStore()
}

func MoovHandler(node *Node, path []string, w http.ResponseWriter, req *http.Request) *Node {
	DefaultLogger.Log("msg", fmt.Sprintf("Quicktime handler: %s with residual path (%d): (%s)", node.Path, len(path), strings.Join(path, ":")))

	movStart := time.Now()

	// uri := node.Fs.Uri
	// uri.Path += node.Path
	//

	// Initialize or update as necessary
	lqt := &lazyquicktime.LazyQuicktime{}

	QTMetadataStore.Lock()
	has, _ := QTMetadataStore.Get(node.trimPath, lqt)

	if !has {
		fs, err := node.Fs.LazyFile(node.Path)

		//fs, err := lazyfs.OpenHttpSource(uri)
		if err != nil {
			http.Error(w, "Something's went boom opening the HTTP Source!", 500)
			return nil
		}

		DefaultLogger.Log("msg", fmt.Sprintf("Need to pull quicktime information for %s", fs.Path()))
		lqt, err = lazyquicktime.LoadMovMetadata(fs)
		if err != nil {
			http.Error(w, fmt.Sprintf("Something's went boom storing the quicktime file: %s", err.Error()), 500)
			return nil
		}

		//fmt.Println(lqt)

		err = QTMetadataStore.Update(node.trimPath, *lqt)
		if err != nil {
			http.Error(w, fmt.Sprintf("Something's went boom storing the quicktime file: %s", err.Error()), 500)
			return nil
		}
	} else {
		DefaultLogger.Log("msg", fmt.Sprintf("Map store had entry for %s", node.trimPath))
	}
	QTMetadataStore.Unlock()

	if len(path) == 0 {
		// Leaf node

		out := QTMetadata{
			URL:       node.Path,
			NumFrames: lqt.NumFrames(),
			Duration:  lqt.Duration(),
		}

		// Temporary structure for JSON output
		b, err := json.MarshalIndent(out, "", "  ")
		if err != nil {
			fmt.Fprintln(w, "JSON error:", err)
		}

		DefaultLogger.Log("msg", fmt.Sprintf("Returning movie information for %s", node.Path ))

		w.Write(b)
	} else {

		// Handle any residual path elements (frames, etc) here
		switch strings.ToLower(path[0]) {
		case "frame":
			handleFrame(node, lqt, path[1:], w, req)
		default:
			http.Error(w, fmt.Sprintf("Didn't understand request \"%s\"", path[0]), 500)
		}
	}

	timeTrack(movStart, "Moov handler")

	return nil
}

func handleFrame(node *Node, lqt *lazyquicktime.LazyQuicktime, path []string, w http.ResponseWriter, req *http.Request) {

	if len(path) == 0 {
		http.Error(w, fmt.Sprintf("Need to specify frame number"), 500)
		return

	}

	frameNum, err := strconv.Atoi(leadingNumbers.FindString(path[0]))
	if err != nil {
		http.Error(w, fmt.Sprintf("Error parsing frame number \"%s\"", path[0]), 500)
		return
	}

	if frameNum > lqt.NumFrames() {
		http.Error(w, fmt.Sprintf("Requested frame %d in movie of length %d frames", frameNum, lqt.NumFrames()), 400)
		return
	}
	if frameNum < 1 {
		http.Error(w, "Requested frame 0, Quicktime movies start with frame 1", 400)
		return
	}

	UUID := req.URL.Path + ".png"
	url, ok := DefaultImageStore.Url(UUID)

	if ok {
		DefaultLogger.Log("msg", fmt.Sprintf("Image %s exists in the Image store at %s", UUID, url))
		// Set Content-Type or response
		w.Header().Set("Content-Type", "image/png")
		// w.Header().Set("Location", url)
		DefaultLogger.Log("msg", fmt.Sprintf("Redirecting to %s", url))
		http.Redirect(w, req, url, http.StatusTemporaryRedirect)

	} else {

		startExt := time.Now()
		img, err := lqt.ExtractFrame(frameNum)
		if err != nil {
			http.Error(w, fmt.Sprintf("Error generating image for frame %d: %s", frameNum, err.Error()), 500)
			return
		}
		timeTrack(startExt, "Frame extraction")

		buffer := new(bytes.Buffer)

		encoder := fastpng.Encoder{
			CompressionLevel: fastpng.DefaultCompression,
		}

		startEncode := time.Now()
		err = encoder.Encode(buffer, img)
		timeTrack(startEncode, "Png encode")

		fmt.Println("PNG size", buffer.Len()/(1024*1024), "MB")

		imgReader := bytes.NewReader(buffer.Bytes())

		// write image to Image store
		DefaultImageStore.Store(UUID, imgReader)

		//Rewind the io, and write to the HTTP channel
		imgReader.Seek(0, io.SeekStart)
		_, err = imgReader.WriteTo(w)
		//fmt.Printf("Wrote %d bytes to http buffer\n", n)
		if err != nil {
			fmt.Printf("Error writing to HTTP buffer: %s\n", err.Error())
		}

		timeTrack(startExt, "Full extract and write")

	}

}
<|endoftext|>


<commit_before>package router

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/Nivl/go-rest-tools/types/apierror"
)

// ResponseError represents the data sent the client when an error occurs
type ResponseError struct {
	Error string `json:"error,omitempty"`
	Field string `json:"field,omitempty"`
}

// HTTPResponse represents an http response
//go:generate mockgen -destination mockrouter/response.go -package mockrouter github.com/Nivl/go-rest-tools/router HTTPResponse
type HTTPResponse interface {
	// Header returns the header map that will be sent by WriteHeader
	Header() http.Header

	// NoContent sends a http.StatusNoContent response
	NoContent()

	// Created sends a http.StatusCreated response with a JSON object attached
	Created(obj interface{}) error
	Ok(obj interface{}) error
}

// Response is a basic implementation of the HTTPResponse that uses a ResponseWriter
type Response struct {
	writer http.ResponseWriter
	deps   *Dependencies
}

// NewResponse creates a new response
func NewResponse(writer http.ResponseWriter, deps *Dependencies) *Response {
	return &Response{
		writer: writer,
		deps:   deps,
	}
}

// Header returns the header object of the response
func (res *Response) Header() http.Header {
	return res.writer.Header()
}

// NoContent sends a http.StatusNoContent response
func (res *Response) NoContent() {
	res.writer.WriteHeader(http.StatusNoContent)
}

// Created sends a http.StatusCreated response with a JSON object attached
func (res *Response) Created(obj interface{}) error {
	return res.renderJSON(http.StatusCreated, obj)
}

// Ok sends a http.StatusOK response with a JSON object attached
func (res *Response) Ok(obj interface{}) error {
	return res.renderJSON(http.StatusOK, obj)
}

// renderJSON attaches a json object to the response
func (res *Response) renderJSON(code int, obj interface{}) error {
	res.setJSON(code)

	if obj != nil {
		return json.NewEncoder(res.writer).Encode(obj)
	}
	return nil
}

// Error sends an error to the client
// If the error is an instance of HTTPError, the returned code will
// match HTTPError.HTTPStatus(). It returns a 500 if no code has been set.
func (res *Response) Error(e error, req HTTPRequest) {
	err := apierror.Convert(e)
	res.errorJSON(err)

	// if the error has a field attached we log it
	field := ""
	if err.Field() != "" {
		field = fmt.Sprintf(`, field: "%s"`, err.Field())
	}
	req.Logger().Errorf(`code: "%d"%s, message: "%s", %s`, err.HTTPStatus(), field, err.Error(), req)

	// We send a report for all server errors
	if err.HTTPStatus() == http.StatusInternalServerError {
		if req.Reporter() != nil {
			req.Reporter().ReportError(err)
		}
	}
}

// errorJSON set the request content to the specified error message and HTTP code.
// The error message should be valid json.
func (res *Response) errorJSON(err apierror.Error) {
	if err.Error() == "" {
		res.writer.WriteHeader(err.HTTPStatus())
		return
	}
	resError := &ResponseError{
		Error: err.Error(),
		Field: err.Field(),
	}

	if err.HTTPStatus() == http.StatusInternalServerError {
		resError.Error = "Something went wrong"
		resError.Field = ""
	}
	res.renderJSON(err.HTTPStatus(), resError)
}

// setJSON set the response to JSON and with the specify HTTP code.
func (res *Response) setJSON(code int) {
	res.writer.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.writer.Header().Set("X-Content-Type-Options", "nosniff")
	res.writer.WriteHeader(code)
}
<commit_msg>fix(router): fix segfault when no loggerCreator is set<commit_after>package router

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/Nivl/go-rest-tools/types/apierror"
)

// ResponseError represents the data sent the client when an error occurs
type ResponseError struct {
	Error string `json:"error,omitempty"`
	Field string `json:"field,omitempty"`
}

// HTTPResponse represents an http response
//go:generate mockgen -destination mockrouter/response.go -package mockrouter github.com/Nivl/go-rest-tools/router HTTPResponse
type HTTPResponse interface {
	// Header returns the header map that will be sent by WriteHeader
	Header() http.Header

	// NoContent sends a http.StatusNoContent response
	NoContent()

	// Created sends a http.StatusCreated response with a JSON object attached
	Created(obj interface{}) error
	Ok(obj interface{}) error
}

// Response is a basic implementation of the HTTPResponse that uses a ResponseWriter
type Response struct {
	writer http.ResponseWriter
	deps   *Dependencies
}

// NewResponse creates a new response
func NewResponse(writer http.ResponseWriter, deps *Dependencies) *Response {
	return &Response{
		writer: writer,
		deps:   deps,
	}
}

// Header returns the header object of the response
func (res *Response) Header() http.Header {
	return res.writer.Header()
}

// NoContent sends a http.StatusNoContent response
func (res *Response) NoContent() {
	res.writer.WriteHeader(http.StatusNoContent)
}

// Created sends a http.StatusCreated response with a JSON object attached
func (res *Response) Created(obj interface{}) error {
	return res.renderJSON(http.StatusCreated, obj)
}

// Ok sends a http.StatusOK response with a JSON object attached
func (res *Response) Ok(obj interface{}) error {
	return res.renderJSON(http.StatusOK, obj)
}

// renderJSON attaches a json object to the response
func (res *Response) renderJSON(code int, obj interface{}) error {
	res.setJSON(code)

	if obj != nil {
		return json.NewEncoder(res.writer).Encode(obj)
	}
	return nil
}

// Error sends an error to the client
// If the error is an instance of HTTPError, the returned code will
// match HTTPError.HTTPStatus(). It returns a 500 if no code has been set.
func (res *Response) Error(e error, req HTTPRequest) {
	err := apierror.Convert(e)
	res.errorJSON(err)

	// if the error has a field attached we log it
	field := ""
	if err.Field() != "" {
		field = fmt.Sprintf(`, field: "%s"`, err.Field())
	}
	if req.Logger() != nil {
		req.Logger().Errorf(`code: "%d"%s, message: "%s", %s`, err.HTTPStatus(), field, err.Error(), req)
	}

	// We send a report for all server errors
	if err.HTTPStatus() == http.StatusInternalServerError {
		if req.Reporter() != nil {
			req.Reporter().ReportError(err)
		}
	}
}

// errorJSON set the request content to the specified error message and HTTP code.
// The error message should be valid json.
func (res *Response) errorJSON(err apierror.Error) {
	if err.Error() == "" {
		res.writer.WriteHeader(err.HTTPStatus())
		return
	}
	resError := &ResponseError{
		Error: err.Error(),
		Field: err.Field(),
	}

	if err.HTTPStatus() == http.StatusInternalServerError {
		resError.Error = "Something went wrong"
		resError.Field = ""
	}
	res.renderJSON(err.HTTPStatus(), resError)
}

// setJSON set the response to JSON and with the specify HTTP code.
func (res *Response) setJSON(code int) {
	res.writer.Header().Set("Content-Type", "application/json; charset=utf-8")
	res.writer.Header().Set("X-Content-Type-Options", "nosniff")
	res.writer.WriteHeader(code)
}
<|endoftext|>


<commit_before>/*
 * Spreed WebRTC.
 * Copyright (C) 2013-2015 struktur AG
 *
 * This file is part of Spreed WebRTC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package main

import (
	"errors"
	"fmt"
	"testing"
)

type fakeClient struct {
}

func (fake *fakeClient) Send(_ Buffer) {
}

type fakeRoomManager struct {
	joinedRoomID string
	leftRoomID   string
	roomUsers    []*DataSession
	joinedID     string
	joinError    error
	leftID       string
	broadcasts   []interface{}
	updatedRoom  *DataRoom
	updateError  error
}

func (fake *fakeRoomManager) RoomUsers(session *Session) []*DataSession {
	return fake.roomUsers
}

func (fake *fakeRoomManager) JoinRoom(id, roomName, roomType string, _ *DataRoomCredentials, session *Session, sessionAuthenticated bool, _ Sender) (*DataRoom, error) {
	fake.joinedID = id
	return &DataRoom{Name: roomName, Type: roomType}, fake.joinError
}

func (fake *fakeRoomManager) LeaveRoom(roomID, sessionID string) {
	fake.leftID = roomID
}

func (fake *fakeRoomManager) Broadcast(_, _ string, outgoing *DataOutgoing) {
	fake.broadcasts = append(fake.broadcasts, outgoing.Data)
}

func (fake *fakeRoomManager) UpdateRoom(_ *Session, _ *DataRoom) (*DataRoom, error) {
	return fake.updatedRoom, fake.updateError
}

func (fake *fakeRoomManager) MakeRoomID(roomName, roomType string) string {
	if roomType == "" {
		roomType = "Room"
	}
	return fmt.Sprintf("%s:%s", roomType, roomName)
}

func NewTestChannellingAPI() (ChannellingAPI, *fakeClient, *Session, *fakeRoomManager) {
	client, roomManager := &fakeClient{}, &fakeRoomManager{}
	session := &Session{
		attestations:      sessionNonces,
		Broadcaster:       roomManager,
		RoomStatusManager: roomManager,
	}
	session.attestation = &SessionAttestation{s: session}
	return NewChannellingAPI(nil, roomManager, nil, nil, nil, nil, nil, nil), client, session, roomManager
}

func Test_ChannellingAPI_OnIncoming_HelloMessage_JoinsTheSelectedRoom(t *testing.T) {
	roomID, roomName, ua := "Room:foobar", "foobar", "unit tests"
	api, client, session, roomManager := NewTestChannellingAPI()

	api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName, Ua: ua}})

	if roomManager.joinedID != roomID {
		t.Errorf("Expected to have joined room %v, but got %v", roomID, roomManager.joinedID)
	}

	if broadcastCount := len(roomManager.broadcasts); broadcastCount != 1 {
		t.Fatalf("Expected 1 broadcast, but got %d", broadcastCount)
	}

	dataSession, ok := roomManager.broadcasts[0].(*DataSession)
	if !ok {
		t.Fatal("Expected a session data broadcast")
	}

	if dataSession.Ua != ua {
		t.Errorf("Expected to have broadcasted a user agent of %v, but was %v", ua, dataSession.Ua)
	}
}

func Test_ChannellingAPI_OnIncoming_HelloMessage_LeavesAnyPreviouslyJoinedRooms(t *testing.T) {
	roomID, roomName := "Room:foobar", "foobar"
	api, client, session, roomManager := NewTestChannellingAPI()

	api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}})
	api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: "baz"}})

	if roomManager.leftID != roomID {
		t.Errorf("Expected to have left room %v, but got %v", roomID, roomManager.leftID)
	}

	if broadcastCount := len(roomManager.broadcasts); broadcastCount != 3 {
		t.Fatalf("Expected 3 broadcasts, but got %d", broadcastCount)
	}

	dataSession, ok := roomManager.broadcasts[1].(*DataSession)
	if !ok {
		t.Fatal("Expected a session data broadcast")
	}

	if status := "soft"; dataSession.Status != status {
		t.Errorf("Expected to have broadcast a leave status of of %v, but was %v", status, dataSession.Status)
	}
}

func Test_ChannellingAPI_OnIncoming_HelloMessage_DoesNotJoinIfNotPermitted(t *testing.T) {
	api, client, session, roomManager := NewTestChannellingAPI()
	roomManager.joinError = errors.New("Can't enter this room")

	api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{}})

	if broadcastCount := len(roomManager.broadcasts); broadcastCount != 0 {
		t.Fatalf("Expected no broadcasts, but got %d", broadcastCount)
	}
}

func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAWelcome(t *testing.T) {
	roomID := "a-room"
	api, client, session, roomManager := NewTestChannellingAPI()
	roomManager.roomUsers = []*DataSession{&DataSession{}}

	reply, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomID}})
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}

	welcome, ok := reply.(*DataWelcome)
	if !ok {
		t.Fatalf("Expected response %#v to be a Welcome", reply)
	}

	if welcome.Type != "Welcome" {
		t.Error("Message did not have the correct type")
	}

	if welcome.Room == nil || welcome.Room.Name != roomID {
		t.Errorf("Expected room with name %v, but got %#v", roomID, welcome.Room)
	}

	if len(welcome.Users) != len(roomManager.roomUsers) {
		t.Errorf("Expected to get users %#v, but was %#v", roomManager.roomUsers, welcome.Users)
	}
}

func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAnErrorIfTheRoomCannotBeJoined(t *testing.T) {
	api, client, session, roomManager := NewTestChannellingAPI()
	roomManager.joinError = NewDataError("bad_join", "")

	_, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{}})

	assertDataError(t, err, "bad_join")
}

func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAndBroadcastsTheUpdatedRoom(t *testing.T) {
	roomName := "foo"
	api, client, session, roomManager := NewTestChannellingAPI()
	roomManager.updatedRoom = &DataRoom{Name: "FOO"}

	_, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}})
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}

	reply, err := api.OnIncoming(client, session, &DataIncoming{Type: "Room", Room: &DataRoom{Name: roomName}})
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}

	room, ok := reply.(*DataRoom)
	if !ok {
		t.Fatalf("Expected response message to be a Room")
	}

	if room.Name != roomManager.updatedRoom.Name {
		t.Errorf("Expected updated room with name %v, but got %#v", roomManager.updatedRoom, room)
	}

	if broadcastCount := len(roomManager.broadcasts); broadcastCount != 2 {
		t.Fatalf("Expected 1 broadcasts, but got %d", broadcastCount)
	}

	if _, ok := roomManager.broadcasts[1].(*DataRoom); !ok {
		t.Fatal("Expected a room data broadcast")
	}
}

func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAnErrorIfUpdatingTheRoomFails(t *testing.T) {
	roomName := "foo"
	api, client, session, roomManager := NewTestChannellingAPI()
	roomManager.updateError = NewDataError("a_room_error", "")

	_, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}})
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}
	_, err = api.OnIncoming(client, session, &DataIncoming{Type: "Room", Room: &DataRoom{Name: roomName}})

	assertDataError(t, err, "a_room_error")
}
<commit_msg>Fixed tests to reflect busManager changes.<commit_after>/*
 * Spreed WebRTC.
 * Copyright (C) 2013-2015 struktur AG
 *
 * This file is part of Spreed WebRTC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package main

import (
	"errors"
	"fmt"
	"testing"
)

type fakeClient struct {
}

func (fake *fakeClient) Send(_ Buffer) {
}

type fakeRoomManager struct {
	joinedRoomID string
	leftRoomID   string
	roomUsers    []*DataSession
	joinedID     string
	joinError    error
	leftID       string
	broadcasts   []interface{}
	updatedRoom  *DataRoom
	updateError  error
}

func (fake *fakeRoomManager) RoomUsers(session *Session) []*DataSession {
	return fake.roomUsers
}

func (fake *fakeRoomManager) JoinRoom(id, roomName, roomType string, _ *DataRoomCredentials, session *Session, sessionAuthenticated bool, _ Sender) (*DataRoom, error) {
	fake.joinedID = id
	return &DataRoom{Name: roomName, Type: roomType}, fake.joinError
}

func (fake *fakeRoomManager) LeaveRoom(roomID, sessionID string) {
	fake.leftID = roomID
}

func (fake *fakeRoomManager) Broadcast(_, _ string, outgoing *DataOutgoing) {
	fake.broadcasts = append(fake.broadcasts, outgoing.Data)
}

func (fake *fakeRoomManager) UpdateRoom(_ *Session, _ *DataRoom) (*DataRoom, error) {
	return fake.updatedRoom, fake.updateError
}

func (fake *fakeRoomManager) MakeRoomID(roomName, roomType string) string {
	if roomType == "" {
		roomType = "Room"
	}
	return fmt.Sprintf("%s:%s", roomType, roomName)
}

func NewTestChannellingAPI() (ChannellingAPI, *fakeClient, *Session, *fakeRoomManager) {
	client, roomManager := &fakeClient{}, &fakeRoomManager{}
	session := &Session{
		attestations:      sessionNonces,
		Broadcaster:       roomManager,
		RoomStatusManager: roomManager,
	}
	busManager := NewBusManager("", false, "")
	session.attestation = &SessionAttestation{s: session}
	return NewChannellingAPI(nil, roomManager, nil, nil, nil, nil, nil, nil, busManager), client, session, roomManager
}

func Test_ChannellingAPI_OnIncoming_HelloMessage_JoinsTheSelectedRoom(t *testing.T) {
	roomID, roomName, ua := "Room:foobar", "foobar", "unit tests"
	api, client, session, roomManager := NewTestChannellingAPI()

	api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName, Ua: ua}})

	if roomManager.joinedID != roomID {
		t.Errorf("Expected to have joined room %v, but got %v", roomID, roomManager.joinedID)
	}

	if broadcastCount := len(roomManager.broadcasts); broadcastCount != 1 {
		t.Fatalf("Expected 1 broadcast, but got %d", broadcastCount)
	}

	dataSession, ok := roomManager.broadcasts[0].(*DataSession)
	if !ok {
		t.Fatal("Expected a session data broadcast")
	}

	if dataSession.Ua != ua {
		t.Errorf("Expected to have broadcasted a user agent of %v, but was %v", ua, dataSession.Ua)
	}
}

func Test_ChannellingAPI_OnIncoming_HelloMessage_LeavesAnyPreviouslyJoinedRooms(t *testing.T) {
	roomID, roomName := "Room:foobar", "foobar"
	api, client, session, roomManager := NewTestChannellingAPI()

	api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}})
	api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: "baz"}})

	if roomManager.leftID != roomID {
		t.Errorf("Expected to have left room %v, but got %v", roomID, roomManager.leftID)
	}

	if broadcastCount := len(roomManager.broadcasts); broadcastCount != 3 {
		t.Fatalf("Expected 3 broadcasts, but got %d", broadcastCount)
	}

	dataSession, ok := roomManager.broadcasts[1].(*DataSession)
	if !ok {
		t.Fatal("Expected a session data broadcast")
	}

	if status := "soft"; dataSession.Status != status {
		t.Errorf("Expected to have broadcast a leave status of of %v, but was %v", status, dataSession.Status)
	}
}

func Test_ChannellingAPI_OnIncoming_HelloMessage_DoesNotJoinIfNotPermitted(t *testing.T) {
	api, client, session, roomManager := NewTestChannellingAPI()
	roomManager.joinError = errors.New("Can't enter this room")

	api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{}})

	if broadcastCount := len(roomManager.broadcasts); broadcastCount != 0 {
		t.Fatalf("Expected no broadcasts, but got %d", broadcastCount)
	}
}

func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAWelcome(t *testing.T) {
	roomID := "a-room"
	api, client, session, roomManager := NewTestChannellingAPI()
	roomManager.roomUsers = []*DataSession{&DataSession{}}

	reply, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomID}})
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}

	welcome, ok := reply.(*DataWelcome)
	if !ok {
		t.Fatalf("Expected response %#v to be a Welcome", reply)
	}

	if welcome.Type != "Welcome" {
		t.Error("Message did not have the correct type")
	}

	if welcome.Room == nil || welcome.Room.Name != roomID {
		t.Errorf("Expected room with name %v, but got %#v", roomID, welcome.Room)
	}

	if len(welcome.Users) != len(roomManager.roomUsers) {
		t.Errorf("Expected to get users %#v, but was %#v", roomManager.roomUsers, welcome.Users)
	}
}

func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAnErrorIfTheRoomCannotBeJoined(t *testing.T) {
	api, client, session, roomManager := NewTestChannellingAPI()
	roomManager.joinError = NewDataError("bad_join", "")

	_, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{}})

	assertDataError(t, err, "bad_join")
}

func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAndBroadcastsTheUpdatedRoom(t *testing.T) {
	roomName := "foo"
	api, client, session, roomManager := NewTestChannellingAPI()
	roomManager.updatedRoom = &DataRoom{Name: "FOO"}

	_, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}})
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}

	reply, err := api.OnIncoming(client, session, &DataIncoming{Type: "Room", Room: &DataRoom{Name: roomName}})
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}

	room, ok := reply.(*DataRoom)
	if !ok {
		t.Fatalf("Expected response message to be a Room")
	}

	if room.Name != roomManager.updatedRoom.Name {
		t.Errorf("Expected updated room with name %v, but got %#v", roomManager.updatedRoom, room)
	}

	if broadcastCount := len(roomManager.broadcasts); broadcastCount != 2 {
		t.Fatalf("Expected 1 broadcasts, but got %d", broadcastCount)
	}

	if _, ok := roomManager.broadcasts[1].(*DataRoom); !ok {
		t.Fatal("Expected a room data broadcast")
	}
}

func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAnErrorIfUpdatingTheRoomFails(t *testing.T) {
	roomName := "foo"
	api, client, session, roomManager := NewTestChannellingAPI()
	roomManager.updateError = NewDataError("a_room_error", "")

	_, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}})
	if err != nil {
		t.Fatalf("Unexpected error %v", err)
	}
	_, err = api.OnIncoming(client, session, &DataIncoming{Type: "Room", Room: &DataRoom{Name: roomName}})

	assertDataError(t, err, "a_room_error")
}
<|endoftext|>


<commit_before>package moves

import (
	"github.com/jkomoros/boardgame"
)

//CustomConfigurationOption is a function that takes a PropertyCollection and
//modifies a key on it. This package defines a number of functions that return
//funcs that satisfy this interface and can be used in DefaultConfig to pass
//in configuration to the base moves without requiring verbose embedding and
//method overriding. All of those functions in this package start with "With".
type CustomConfigurationOption func(boardgame.PropertyCollection)

const fullyQualifiedPackageName = "github.com/jkomoros/boardgame/moves."

const configNameStartPhase = fullyQualifiedPackageName + "StartPhase"
const configNameSourceStack = fullyQualifiedPackageName + "SourceStack"
const configNameDestinationStack = fullyQualifiedPackageName + "DestinationStack"
const configNameTargetCount = fullyQualifiedPackageName + "TargetCount"
const configNameNumRounds = fullyQualifiedPackageName + "NumRounds"
const configNameGameStack = fullyQualifiedPackageName + "GameStack"
const configNamePlayerStack = fullyQualifiedPackageName + "PlayerStack"
const configNameMoveName = fullyQualifiedPackageName + "MoveName"
const configNameHelpText = fullyQualifiedPackageName + "HelpText"
const configNameIsFixUp = fullyQualifiedPackageName + "IsFixUp"

//WithMoveName returns a function configuration option suitable for being
//passed to DefaultConfig. moves.Base uses this, if provided, to power
//MoveTypeName, which means that DefaultConfig will use this name in some
//cases. See the documentation for moves.Base.MoveTypeName for more
//information.
func WithMoveName(moveName string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameMoveName] = moveName
	}
}

//WithHelpText returns a function configuration option suitable for being
//passed to DefaultConfig. moves.Base uses this, if provided, to power
//MoveTypeHelpText, which means that DefaultConfig will use this name in some
//cases. See the documentation for moves.Base.MoveTypeHelpText for more
//information.
func WithHelpText(helpText string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameHelpText] = helpText
	}
}

//WithIsFixUp returns a function configuration option suitable for being
//passed to DefaultConfig. moves.Base uses this, if provided, to power
//MoveTypeIsFixUp, which means that DefaultConfig will use this name in some
//cases. See the documentation for moves.Base.MoveTypeIsFixup for more
//information. All moves in this package will return reasonable values for
//MoveTypeIsFixUp on their own, so it is much more rare to use this than other
//config options in this package.
func WithIsFixUp(isFixUp bool) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameIsFixUp] = isFixUp
	}
}

//WithPhaseToStart returns a function configuration option suitable for being
//passed to DefaultConfig.
func WithPhaseToStart(phaseToStart int) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameStartPhase] = phaseToStart
	}
}

//WithSourceStack returns a function configuration option suitable for being
//passed to DefaultConfig. The stackPropName is assumed to be on the GameState
//object. If it isn't, you'll need to embed the move and override Sourcetack
//yourself.
func WithSourceStack(stackPropName string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameSourceStack] = stackPropName
	}
}

//WithDestinationStack returns a function configuration option suitable for
//being passed to DefaultConfig. The stackPropName is assumed to be on the
//GameState object. If it isn't, you'll need to embed the move and override
//DestinationStack yourself.
func WithDestinationStack(stackPropName string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameDestinationStack] = stackPropName
	}
}

//WithGameStack returns a function configuration option suitable for being
//passed to DefaultConfig.
func WithGameStack(stackPropName string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameGameStack] = stackPropName
	}
}

//WithPlayerStack returns a function configuration option suitable for being
//passed to DefaultConfig.
func WithPlayerStack(stackPropName string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNamePlayerStack] = stackPropName
	}
}

//WithNumRounds returns a function configuration option suitable for being
//passed to DefaultConfig.
func WithNumRounds(numRounds int) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameNumRounds] = numRounds
	}
}

//WithTargetCount returns a function configuration option suitable for being
//passed to DefaultConfig.
func WithTargetCount(targetCount int) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameTargetCount] = targetCount
	}
}
<commit_msg>Clarify documentation about when WithMoveName is required. Part of #563.<commit_after>package moves

import (
	"github.com/jkomoros/boardgame"
)

//CustomConfigurationOption is a function that takes a PropertyCollection and
//modifies a key on it. This package defines a number of functions that return
//funcs that satisfy this interface and can be used in DefaultConfig to pass
//in configuration to the base moves without requiring verbose embedding and
//method overriding. All of those functions in this package start with "With".
type CustomConfigurationOption func(boardgame.PropertyCollection)

const fullyQualifiedPackageName = "github.com/jkomoros/boardgame/moves."

const configNameStartPhase = fullyQualifiedPackageName + "StartPhase"
const configNameSourceStack = fullyQualifiedPackageName + "SourceStack"
const configNameDestinationStack = fullyQualifiedPackageName + "DestinationStack"
const configNameTargetCount = fullyQualifiedPackageName + "TargetCount"
const configNameNumRounds = fullyQualifiedPackageName + "NumRounds"
const configNameGameStack = fullyQualifiedPackageName + "GameStack"
const configNamePlayerStack = fullyQualifiedPackageName + "PlayerStack"
const configNameMoveName = fullyQualifiedPackageName + "MoveName"
const configNameHelpText = fullyQualifiedPackageName + "HelpText"
const configNameIsFixUp = fullyQualifiedPackageName + "IsFixUp"

//WithMoveName returns a function configuration option suitable for being
//passed to DefaultConfig. moves.Base uses this, if provided, to power
//MoveTypeName, which means that DefaultConfig will use this name in some
//cases. If you're passing a move struct that not's from this package, the
//auto-generated move name is likely sufficient and you don't need this. See
//the documentation for moves.Base.MoveTypeName for more information.
func WithMoveName(moveName string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameMoveName] = moveName
	}
}

//WithHelpText returns a function configuration option suitable for being
//passed to DefaultConfig. moves.Base uses this, if provided, to power
//MoveTypeHelpText, which means that DefaultConfig will use this name in some
//cases. See the documentation for moves.Base.MoveTypeHelpText for more
//information.
func WithHelpText(helpText string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameHelpText] = helpText
	}
}

//WithIsFixUp returns a function configuration option suitable for being
//passed to DefaultConfig. moves.Base uses this, if provided, to power
//MoveTypeIsFixUp, which means that DefaultConfig will use this name in some
//cases. See the documentation for moves.Base.MoveTypeIsFixup for more
//information. All moves in this package will return reasonable values for
//MoveTypeIsFixUp on their own, so it is much more rare to use this than other
//config options in this package.
func WithIsFixUp(isFixUp bool) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameIsFixUp] = isFixUp
	}
}

//WithPhaseToStart returns a function configuration option suitable for being
//passed to DefaultConfig.
func WithPhaseToStart(phaseToStart int) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameStartPhase] = phaseToStart
	}
}

//WithSourceStack returns a function configuration option suitable for being
//passed to DefaultConfig. The stackPropName is assumed to be on the GameState
//object. If it isn't, you'll need to embed the move and override Sourcetack
//yourself.
func WithSourceStack(stackPropName string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameSourceStack] = stackPropName
	}
}

//WithDestinationStack returns a function configuration option suitable for
//being passed to DefaultConfig. The stackPropName is assumed to be on the
//GameState object. If it isn't, you'll need to embed the move and override
//DestinationStack yourself.
func WithDestinationStack(stackPropName string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameDestinationStack] = stackPropName
	}
}

//WithGameStack returns a function configuration option suitable for being
//passed to DefaultConfig.
func WithGameStack(stackPropName string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameGameStack] = stackPropName
	}
}

//WithPlayerStack returns a function configuration option suitable for being
//passed to DefaultConfig.
func WithPlayerStack(stackPropName string) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNamePlayerStack] = stackPropName
	}
}

//WithNumRounds returns a function configuration option suitable for being
//passed to DefaultConfig.
func WithNumRounds(numRounds int) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameNumRounds] = numRounds
	}
}

//WithTargetCount returns a function configuration option suitable for being
//passed to DefaultConfig.
func WithTargetCount(targetCount int) CustomConfigurationOption {
	return func(config boardgame.PropertyCollection) {
		config[configNameTargetCount] = targetCount
	}
}
<|endoftext|>


<commit_before>// 28 july 2014

package ui

import (
	"fmt"
	"reflect"
	"sync"
)

// Table is a Control that displays a list of like-structured data in a grid where each row represents an item and each column represents a bit of data.
// As such, a Table renders a []struct{...} where each field of the struct can be a string, a number, [TODO an image, or a checkbox].
// Tables maintain their own storage behind a sync.RWMutex-compatible sync.Locker; use Table.Lock()/Table.Unlock() to make changes and Table.RLock()/Table.RUnlock() to merely read values.
// TODO headers
type Table interface {
	// Lock and Unlock lock and unlock Data for reading or writing.
	// RLock and RUnlock lock and unlock Data for reading only.
	// These methods have identical semantics to the analogous methods of sync.RWMutex.
	Lock()
	Unlock()
	RLock()
	RUnlock()

	// Data returns the internal data.
	// The returned value will contain an object of type pointer to slice of some structure; use a type assertion to get the properly typed object out.
	// Do not call this outside a Lock()..Unlock() or RLock()..RUnlock() pair.
	Data() interface{}
}

type tablebase struct {
	lock		sync.RWMutex
	data		interface{}
}

// NewTable creates a new Table.
// Currently, the argument must be a reflect.Type representing the structure that each item in the Table will hold, and the Table will be initially empty.
// This will change in the future.
func NewTable(ty reflect.Type) Table {
	if ty.Kind() != reflect.Struct {
		panic(fmt.Errorf("unknown or unsupported type %v given to NewTable()", ty))
	}
	b := new(tablebase)
	// arbitrary starting capacity
	b.data = reflect.NewSlice(ty, 0, 512).Addr().Interface()
	return finishNewTable(b)
}

func (b *tablebase) Lock() {
	b.lock.Lock()
}

func (b *tablebase) Unlock() {
	b.lock.Unlock()
}

func (b *tablebase) RLock() {
	b.lock.RLock()
}

func (b *tablebase) RUnlock() {
	b.lock.RUnlock()
}

func (b *tablebase) Data() {
	return b.data
}
<commit_msg>Quick note about Table.Unlock() and updates.<commit_after>// 28 july 2014

package ui

import (
	"fmt"
	"reflect"
	"sync"
)

// Table is a Control that displays a list of like-structured data in a grid where each row represents an item and each column represents a bit of data.
// As such, a Table renders a []struct{...} where each field of the struct can be a string, a number, [TODO an image, or a checkbox].
// Tables maintain their own storage behind a sync.RWMutex-compatible sync.Locker; use Table.Lock()/Table.Unlock() to make changes and Table.RLock()/Table.RUnlock() to merely read values.
// TODO headers
type Table interface {
	// Lock and Unlock lock and unlock Data for reading or writing.
	// RLock and RUnlock lock and unlock Data for reading only.
	// These methods have identical semantics to the analogous methods of sync.RWMutex.
	// In addition, Unlock() will request an update of the Table to account for whatever was changed.
	Lock()
	Unlock()
	RLock()
	RUnlock()

	// Data returns the internal data.
	// The returned value will contain an object of type pointer to slice of some structure; use a type assertion to get the properly typed object out.
	// Do not call this outside a Lock()..Unlock() or RLock()..RUnlock() pair.
	Data() interface{}
}

type tablebase struct {
	lock		sync.RWMutex
	data		interface{}
}

// NewTable creates a new Table.
// Currently, the argument must be a reflect.Type representing the structure that each item in the Table will hold, and the Table will be initially empty.
// This will change in the future.
func NewTable(ty reflect.Type) Table {
	if ty.Kind() != reflect.Struct {
		panic(fmt.Errorf("unknown or unsupported type %v given to NewTable()", ty))
	}
	b := new(tablebase)
	// arbitrary starting capacity
	b.data = reflect.NewSlice(ty, 0, 512).Addr().Interface()
	return finishNewTable(b)
}

func (b *tablebase) Lock() {
	b.lock.Lock()
}

func (b *tablebase) Unlock() {
	b.lock.Unlock()
}

func (b *tablebase) RLock() {
	b.lock.RLock()
}

func (b *tablebase) RUnlock() {
	b.lock.RUnlock()
}

func (b *tablebase) Data() {
	return b.data
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/giantswarm/mayu/hostmgr"
	"github.com/joho/godotenv"
	"github.com/spf13/cobra"
)

type BootCompleteFlags struct {
	UpdateVersions bool
}

var (
	bootCompleteCmd = &cobra.Command{
		Use:   "boot-complete",
		Short: "Change the state of a host to 'running' (only run on provisioned machines).",
		Long: `Change the state of a host to 'running' (only run on provisioned machines).

Update the software versions running on a host with '--update-versions'.
This includes versions of CoreOS, mayu, docker, etcd, fleet, rkt, kubectl and the
Giant Swarm yochu.
`,
		Run: bootCompleteRun,
	}

	bootCompleteFlags = &BootCompleteFlags{}
)

func init() {
	bootCompleteCmd.PersistentFlags().BoolVar(&bootCompleteFlags.UpdateVersions, "update-versions", false, "Update installed software versions in the mayu catalog")
}

func bootCompleteRun(cmd *cobra.Command, args []string) {
	hostEnvironment, err := godotenv.Read(
		"/etc/os-release",
		"/etc/yochu-env",
		"/etc/mayu-env",
	)
	assert(err)

	serial, ok := hostEnvironment["SERIAL"]
	if !ok {
		fmt.Printf("Can't find serial in host environment (/etc/mayu-env)")
		os.Exit(1)
	}

	var host hostmgr.Host
	if bootCompleteFlags.UpdateVersions {
		for key, value := range hostEnvironment {
			switch key {
			case "VERSION":
				host.CoreOSVersion = value
			case "MAYU_VERSION":
				host.MayuVersion = value
			case "DOCKER_VERSION":
				host.DockerVersion = value
			case "ETCD_VERSION":
				host.EtcdVersion = value
			case "FLEET_VERSION":
				host.FleetVersion = value
			case "YOCHU_VERSION":
				host.YochuVersion = value
			case "RKT_VERSION":
				host.RktVersion = value
			}
			case "K8S_VERSION":
				host.K8sVersion = value
			}
		}
	}

	err = mayu.BootComplete(serial, host)
	assert(err)
}
<commit_msg>Fixed failing tests<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/giantswarm/mayu/hostmgr"
	"github.com/joho/godotenv"
	"github.com/spf13/cobra"
)

type BootCompleteFlags struct {
	UpdateVersions bool
}

var (
	bootCompleteCmd = &cobra.Command{
		Use:   "boot-complete",
		Short: "Change the state of a host to 'running' (only run on provisioned machines).",
		Long: `Change the state of a host to 'running' (only run on provisioned machines).

Update the software versions running on a host with '--update-versions'.
This includes versions of CoreOS, mayu, docker, etcd, fleet, rkt, kubectl and the
Giant Swarm yochu.
`,
		Run: bootCompleteRun,
	}

	bootCompleteFlags = &BootCompleteFlags{}
)

func init() {
	bootCompleteCmd.PersistentFlags().BoolVar(&bootCompleteFlags.UpdateVersions, "update-versions", false, "Update installed software versions in the mayu catalog")
}

func bootCompleteRun(cmd *cobra.Command, args []string) {
	hostEnvironment, err := godotenv.Read(
		"/etc/os-release",
		"/etc/yochu-env",
		"/etc/mayu-env",
	)
	assert(err)

	serial, ok := hostEnvironment["SERIAL"]
	if !ok {
		fmt.Printf("Can't find serial in host environment (/etc/mayu-env)")
		os.Exit(1)
	}

	var host hostmgr.Host
	if bootCompleteFlags.UpdateVersions {
		for key, value := range hostEnvironment {
			switch key {
			case "VERSION":
				host.CoreOSVersion = value
			case "MAYU_VERSION":
				host.MayuVersion = value
			case "DOCKER_VERSION":
				host.DockerVersion = value
			case "ETCD_VERSION":
				host.EtcdVersion = value
			case "FLEET_VERSION":
				host.FleetVersion = value
			case "YOCHU_VERSION":
				host.YochuVersion = value
			case "RKT_VERSION":
				host.RktVersion = value
			case "K8S_VERSION":
				host.K8sVersion = value
			}
		}
	}

	err = mayu.BootComplete(serial, host)
	assert(err)
}
<|endoftext|>


<commit_before>/*
    DNS-over-HTTPS
    Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"time"
	"github.com/miekg/dns"
	"../json-dns"
)

type Client struct {
	addr		string
	upstream	string
	udpServer	*dns.Server
	tcpServer	*dns.Server
}

func NewClient(addr, upstream string) (c *Client) {
	c = &Client {
		addr: addr,
		upstream: upstream,
	}
	c.udpServer = &dns.Server {
		Addr: addr,
		Net: "udp",
		Handler: dns.HandlerFunc(c.udpHandlerFunc),
		UDPSize: 4096,
	}
	c.tcpServer = &dns.Server {
		Addr: addr,
		Net: "tcp",
		Handler: dns.HandlerFunc(c.tcpHandlerFunc),
	}
	return
}

func (c *Client) Start() error {
	result := make(chan error)
	go func() {
		err := c.udpServer.ListenAndServe()
		if err != nil {
			log.Println(err)
		}
		result <- err
	} ()
	go func() {
		err := c.tcpServer.ListenAndServe()
		if err != nil {
			log.Println(err)
		}
		result <- err
	} ()
	err := <-result
	if err != nil {
		return err
	}
	err = <-result
	return err
}

func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
	if r.Response == true {
		log.Println("Received a response packet")
		return
	}

	reply := jsonDNS.PrepareReply(r)

	if len(r.Question) != 1 {
		log.Println("Number of questions is not 1")
		reply.Rcode = dns.RcodeFormatError
		w.WriteMsg(reply)
		return
	}
	question := r.Question[0]
	questionName := question.Name
	questionType := ""
	if qtype, ok := dns.TypeToString[question.Qtype]; ok {
		questionType = qtype
	} else {
		questionType = strconv.Itoa(int(question.Qtype))
	}

	fmt.Printf("%s - - [%s] \"%s IN %s\"\n", w.RemoteAddr(), time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionType)

	requestURL := fmt.Sprintf("%s?name=%s&type=%s", c.upstream, url.QueryEscape(questionName), url.QueryEscape(questionType))

	if r.CheckingDisabled {
		requestURL += "&cd=1"
	}

	udpSize := uint16(512)
	if opt := r.IsEdns0(); opt != nil {
		udpSize = opt.UDPSize()
	}

	ednsClientAddress, ednsClientNetmask := c.findClientIP(w, r)
	if ednsClientAddress != nil {
		requestURL += fmt.Sprintf("&edns_client_subnet=%s/%d", ednsClientAddress.String(), ednsClientNetmask)
	}

	resp, err := http.Get(requestURL)
	if err != nil {
		log.Println(err)
		reply.Rcode = dns.RcodeServerFailure
		w.WriteMsg(reply)
		return
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Println(err)
		reply.Rcode = dns.RcodeServerFailure
		w.WriteMsg(reply)
		return
	}

	var respJson jsonDNS.Response
	err = json.Unmarshal(body, &respJson)
	if err != nil {
		log.Println(err)
		reply.Rcode = dns.RcodeServerFailure
		w.WriteMsg(reply)
		return
	}

	fullReply := jsonDNS.Unmarshal(reply, &respJson, udpSize, ednsClientNetmask)
	buf, err := fullReply.Pack()
	if err != nil {
		log.Println(err)
		reply.Rcode = dns.RcodeServerFailure
		w.WriteMsg(reply)
		return
	}
	if !isTCP && len(buf) > int(udpSize) {
		fullReply.Truncated = true
		buf, err = fullReply.Pack()
		if err != nil {
			log.Println(err)
			return
		}
		buf = buf[:udpSize]
	}
	w.Write(buf)
}

func (c *Client) udpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
	c.handlerFunc(w, r, false)
}

func (c *Client) tcpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
	c.handlerFunc(w, r, true)
}

var (
	ipv4Mask24	net.IPMask = net.IPMask { 255, 255, 255, 0 }
	ipv6Mask48	net.IPMask = net.CIDRMask(48, 128)
)

func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddress net.IP, ednsClientNetmask uint8) {
	ednsClientNetmask = 255
	opt := r.IsEdns0()
	for _, option := range opt.Option {
		if option.Option() == dns.EDNS0SUBNET {
			edns0Subnet := option.(*dns.EDNS0_SUBNET)
			ednsClientAddress = edns0Subnet.Address
			ednsClientNetmask = edns0Subnet.SourceNetmask
			return
		}
	}
	remoteAddr, err := net.ResolveUDPAddr("udp", w.RemoteAddr().String())
	if err != nil {
		return
	}
	if ip := remoteAddr.IP; jsonDNS.IsGlobalIP(ip) {
		if ipv4 := ip.To4(); ipv4 != nil {
			ednsClientAddress = ipv4.Mask(ipv4Mask24)
			ednsClientNetmask = 24
		} else {
			ednsClientAddress = ip.Mask(ipv6Mask48)
			ednsClientNetmask = 48
		}
	}
	return
}
<commit_msg>Fix segfault<commit_after>/*
    DNS-over-HTTPS
    Copyright (C) 2017 Star Brilliant <m13253@hotmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"time"
	"github.com/miekg/dns"
	"../json-dns"
)

type Client struct {
	addr		string
	upstream	string
	udpServer	*dns.Server
	tcpServer	*dns.Server
}

func NewClient(addr, upstream string) (c *Client) {
	c = &Client {
		addr: addr,
		upstream: upstream,
	}
	c.udpServer = &dns.Server {
		Addr: addr,
		Net: "udp",
		Handler: dns.HandlerFunc(c.udpHandlerFunc),
		UDPSize: 4096,
	}
	c.tcpServer = &dns.Server {
		Addr: addr,
		Net: "tcp",
		Handler: dns.HandlerFunc(c.tcpHandlerFunc),
	}
	return
}

func (c *Client) Start() error {
	result := make(chan error)
	go func() {
		err := c.udpServer.ListenAndServe()
		if err != nil {
			log.Println(err)
		}
		result <- err
	} ()
	go func() {
		err := c.tcpServer.ListenAndServe()
		if err != nil {
			log.Println(err)
		}
		result <- err
	} ()
	err := <-result
	if err != nil {
		return err
	}
	err = <-result
	return err
}

func (c *Client) handlerFunc(w dns.ResponseWriter, r *dns.Msg, isTCP bool) {
	if r.Response == true {
		log.Println("Received a response packet")
		return
	}

	reply := jsonDNS.PrepareReply(r)

	if len(r.Question) != 1 {
		log.Println("Number of questions is not 1")
		reply.Rcode = dns.RcodeFormatError
		w.WriteMsg(reply)
		return
	}
	question := r.Question[0]
	questionName := question.Name
	questionType := ""
	if qtype, ok := dns.TypeToString[question.Qtype]; ok {
		questionType = qtype
	} else {
		questionType = strconv.Itoa(int(question.Qtype))
	}

	fmt.Printf("%s - - [%s] \"%s IN %s\"\n", w.RemoteAddr(), time.Now().Format("02/Jan/2006:15:04:05 -0700"), questionName, questionType)

	requestURL := fmt.Sprintf("%s?name=%s&type=%s", c.upstream, url.QueryEscape(questionName), url.QueryEscape(questionType))

	if r.CheckingDisabled {
		requestURL += "&cd=1"
	}

	udpSize := uint16(512)
	if opt := r.IsEdns0(); opt != nil {
		udpSize = opt.UDPSize()
	}

	ednsClientAddress, ednsClientNetmask := c.findClientIP(w, r)
	if ednsClientAddress != nil {
		requestURL += fmt.Sprintf("&edns_client_subnet=%s/%d", ednsClientAddress.String(), ednsClientNetmask)
	}

	resp, err := http.Get(requestURL)
	if err != nil {
		log.Println(err)
		reply.Rcode = dns.RcodeServerFailure
		w.WriteMsg(reply)
		return
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Println(err)
		reply.Rcode = dns.RcodeServerFailure
		w.WriteMsg(reply)
		return
	}

	var respJson jsonDNS.Response
	err = json.Unmarshal(body, &respJson)
	if err != nil {
		log.Println(err)
		reply.Rcode = dns.RcodeServerFailure
		w.WriteMsg(reply)
		return
	}

	fullReply := jsonDNS.Unmarshal(reply, &respJson, udpSize, ednsClientNetmask)
	buf, err := fullReply.Pack()
	if err != nil {
		log.Println(err)
		reply.Rcode = dns.RcodeServerFailure
		w.WriteMsg(reply)
		return
	}
	if !isTCP && len(buf) > int(udpSize) {
		fullReply.Truncated = true
		buf, err = fullReply.Pack()
		if err != nil {
			log.Println(err)
			return
		}
		buf = buf[:udpSize]
	}
	w.Write(buf)
}

func (c *Client) udpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
	c.handlerFunc(w, r, false)
}

func (c *Client) tcpHandlerFunc(w dns.ResponseWriter, r *dns.Msg) {
	c.handlerFunc(w, r, true)
}

var (
	ipv4Mask24	net.IPMask = net.IPMask { 255, 255, 255, 0 }
	ipv6Mask48	net.IPMask = net.CIDRMask(48, 128)
)

func (c *Client) findClientIP(w dns.ResponseWriter, r *dns.Msg) (ednsClientAddress net.IP, ednsClientNetmask uint8) {
	ednsClientNetmask = 255
	if opt := r.IsEdns0(); opt != nil {
		for _, option := range opt.Option {
			if option.Option() == dns.EDNS0SUBNET {
				edns0Subnet := option.(*dns.EDNS0_SUBNET)
				ednsClientAddress = edns0Subnet.Address
				ednsClientNetmask = edns0Subnet.SourceNetmask
				return
			}
		}
	}
	remoteAddr, err := net.ResolveUDPAddr("udp", w.RemoteAddr().String())
	if err != nil {
		return
	}
	if ip := remoteAddr.IP; jsonDNS.IsGlobalIP(ip) {
		if ipv4 := ip.To4(); ipv4 != nil {
			ednsClientAddress = ipv4.Mask(ipv4Mask24)
			ednsClientNetmask = 24
		} else {
			ednsClientAddress = ip.Mask(ipv6Mask48)
			ednsClientNetmask = 48
		}
	}
	return
}
<|endoftext|>


<commit_before>package download

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/blang/semver"
	"github.com/subutai-io/agent/log"
	"github.com/subutai-io/gorjun/config"
	"github.com/subutai-io/gorjun/db"
	"net/url"
)

// ListItem describes Gorjun entity. It can be APT package, Subutai template or Raw file.
type ListItem struct {
	ID           string            `json:"id"`
	Hash         hashsums          `json:"hash"`
	Size         int               `json:"size"`
	Date         time.Time         `json:"upload-date-formatted"`
	Timestamp    string            `json:"upload-date-timestamp,omitempty"`
	Name         string            `json:"name,omitempty"`
	Tags         []string          `json:"tags,omitempty"`
	Owner        []string          `json:"owner,omitempty"`
	Parent       string            `json:"parent,omitempty"`
	Version      string            `json:"version,omitempty"`
	Filename     string            `json:"filename,omitempty"`
	Prefsize     string            `json:"prefsize,omitempty"`
	Signature    map[string]string `json:"signature,omitempty"`
	Description  string            `json:"description,omitempty"`
	Architecture string            `json:"architecture,omitempty"`
}

type hashsums struct {
	Md5    string `json:"md5,omitempty"`
	Sha256 string `json:"sha256,omitempty"`
}

// Handler provides download functionality for all artifacts.
func Handler(repo string, w http.ResponseWriter, r *http.Request) {
	id := r.URL.Query().Get("id")
	name := r.URL.Query().Get("name")
	if len(id) == 0 && len(name) == 0 {
		io.WriteString(w, "Please specify id or name")
		return
	} else if len(name) != 0 {
		id = db.LastHash(name, repo)
	}

	if len(db.Read(id)) > 0 && !db.Public(id) && !db.CheckShare(id, db.CheckToken(r.URL.Query().Get("token"))) {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("Not found"))
		return
	}

	path := config.Storage.Path + id
	if md5, _ := db.Hash(id); len(md5) != 0 {
		path = config.Storage.Path + md5
	}

	f, err := os.Open(path)
	defer f.Close()

	if log.Check(log.WarnLevel, "Opening file "+config.Storage.Path+id, err) || len(id) == 0 {
		if len(config.CDN.Node) > 0 {
			client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
			resp, err := client.Get(config.CDN.Node + r.URL.RequestURI())
			if !log.Check(log.WarnLevel, "Getting file from CDN", err) {
				w.Header().Set("Content-Length", resp.Header.Get("Content-Length"))
				w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
				w.Header().Set("Last-Modified", resp.Header.Get("Last-Modified"))
				w.Header().Set("Content-Disposition", resp.Header.Get("Content-Disposition"))

				io.Copy(w, resp.Body)
				resp.Body.Close()
				return
			}
		}

		w.WriteHeader(http.StatusNotFound)
		io.WriteString(w, "File not found")
		return
	}
	fi, _ := f.Stat()

	if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && fi.ModTime().Unix() <= t.Unix() {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	w.Header().Set("Content-Length", fmt.Sprint(fi.Size()))
	w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
	w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))

	if name = db.Read(id); len(name) == 0 && len(config.CDN.Node) > 0 {
		httpclient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
		resp, err := httpclient.Get(config.CDN.Node + "/kurjun/rest/template/info?id=" + id)
		if !log.Check(log.WarnLevel, "Getting info from CDN", err) {
			var info ListItem
			rsp, err := ioutil.ReadAll(resp.Body)
			if log.Check(log.WarnLevel, "Reading from CDN response", err) {
				w.WriteHeader(http.StatusNotFound)
				io.WriteString(w, "File not found")
				return
			}
			if !log.Check(log.WarnLevel, "Decrypting request", json.Unmarshal([]byte(rsp), &info)) {
				w.Header().Set("Content-Disposition", "attachment; filename=\""+info.Filename+"\"")
			}
			resp.Body.Close()
		}
	} else {
		w.Header().Set("Content-Disposition", "attachment; filename=\""+db.Read(id)+"\"")
	}

	io.Copy(w, f)
}

// Info returns JSON formatted list of elements. It allows to apply some filters to Search.
func Info(repo string, r *http.Request) []byte {
	var items []ListItem
	var fullname bool
	var itemLatestVersion ListItem
	p := []int{0, 1000}
	id := r.URL.Query().Get("id")
	tag := r.URL.Query().Get("tag")
	name := r.URL.Query().Get("name")
	page := r.URL.Query().Get("page")
	owner := r.URL.Query().Get("owner")
	token := r.URL.Query().Get("token")
	subname := r.URL.Query().Get("subname")
	version := r.URL.Query().Get("version")
	verified := r.URL.Query().Get("verified")
	if len(subname) != 0 {
		name = subname
	}
	list := db.Search(name)
	if len(tag) > 0 {
		listByTag, err := db.Tag(tag)
		log.Check(log.DebugLevel, "Looking for artifacts with tag "+tag, err)
		list = intersect(list, listByTag)
	}
	if len(token) > 0 {
		o := db.CheckToken(token)
		listByOwner := db.All(o, repo)
		listShared := SearchInShared(list, token)
		if len(listByOwner) > 0 {
			list = intersect(list, listByOwner)
		}
		if len(listShared) > 0 {
			list = intersect(list, listShared)
		}
	}
	if onlyOneParameterProvided("name", r) {
		verified = "true"
	}
	if len(name) > 0 && token == "" && owner == "" {
		verified = "true"
	}
	if len(id) > 0 {
		list = append(list[:0], id)
	} else if verified == "true" {
		itemLatestVersion = getVerified(list, name, repo, version)
		if itemLatestVersion.ID != "" {
			items = append(items, getVerified(list, name, repo, version))
			items[0].Signature = db.FileSignatures(items[0].ID)
		}
		output, err := json.Marshal(items)
		if err == nil && len(items) > 0 && items[0].ID != "" {
			return output
		}
	}

	pstr := strings.Split(page, ",")
	p[0], _ = strconv.Atoi(pstr[0])
	if len(pstr) == 2 {
		p[1], _ = strconv.Atoi(pstr[1])
	}
	latestVersion, _ := semver.Make("")
	for _, k := range list {
		if (!db.Public(k) && !db.CheckShare(k, db.CheckToken(token))) ||
			(len(owner) > 0 && db.CheckRepo(owner, repo, k) == 0) ||
			db.CheckRepo("", repo, k) == 0 {
			continue
		}

		if p[0]--; p[0] > 0 {
			continue
		}

		item := FormatItem(db.Info(k), repo, name)
		if len(subname) == 0 && name == item.Name {
			if strings.HasSuffix(item.Version, version) || len(version) == 0 {
				items = []ListItem{item}
				fullname = true
				itemVersion, _ := semver.Make(item.Version)
				if itemVersion.GTE(latestVersion) {
					latestVersion = itemVersion
					itemLatestVersion = item
				}
			}
		} else if !fullname && (len(version) == 0 || item.Version == version) {
			items = append(items, item)
		}

		if len(items) >= p[1] {
			break
		}
	}
	if len(items) == 1 {
		if version == "" && repo == "template" && itemLatestVersion.ID != "" {
			items[0] = itemLatestVersion
		}
		items[0].Signature = db.FileSignatures(items[0].ID)
	}
	output, err := json.Marshal(items)
	if err != nil || string(output) == "null" {
		return nil
	}
	return output
}

func in(str string, list []string) bool {
	for _, s := range list {
		if s == str {
			return true
		}
	}
	return false
}

func getVerified(list []string, name, repo string, versionTemplate string) ListItem {
	latestVersion, _ := semver.Make("")
	var itemLatestVersion ListItem
	for _, k := range list {
		if info := db.Info(k); db.CheckRepo("", repo, k) > 0 {
			if info["name"] == name || (strings.HasPrefix(info["name"], name+"-subutai-template") && repo == "template") {
				for _, owner := range db.FileField(info["id"], "owner") {
					itemVersion, _ := semver.Make(info["version"])
					if in(owner, []string{"subutai", "jenkins", "docker"}) {
						if itemVersion.GTE(latestVersion) && len(versionTemplate) == 0 {
							latestVersion = itemVersion
							itemLatestVersion = FormatItem(db.Info(k), repo, name)
						} else if versionTemplate == itemVersion.String() {
							itemLatestVersion = FormatItem(db.Info(k), repo, name)
						}
					}
				}
			}
		}
	}
	return itemLatestVersion
}

func FormatItem(info map[string]string, repo, name string) ListItem {
	if len(info["prefsize"]) == 0 && repo == "template" {
		info["prefsize"] = "tiny"
	}

	date, _ := time.Parse(time.RFC3339Nano, info["date"])
	timestamp := strconv.FormatInt(date.Unix(), 10)
	item := ListItem{
		ID:           info["id"],
		Date:         date,
		Hash:         hashsums{Md5: info["md5"], Sha256: info["sha256"]},
		Name:         strings.Split(info["name"], "-subutai-template")[0],
		Tags:         db.FileField(info["id"], "tags"),
		Owner:        db.FileField(info["id"], "owner"),
		Version:      info["version"],
		Filename:     info["name"],
		Parent:       info["parent"],
		Prefsize:     info["prefsize"],
		Architecture: strings.ToUpper(info["arch"]),
		Description:  info["Description"],
		Timestamp:    timestamp,
	}
	item.Size, _ = strconv.Atoi(info["size"])

	if repo == "apt" {
		item.Version = info["Version"]
		item.Architecture = info["Architecture"]
		item.Size, _ = strconv.Atoi(info["Size"])
	}
	if len(item.Hash.Md5) == 0 {
		item.Hash.Md5 = item.ID
	}
	return item
}

func intersect(listA, listB []string) (list []string) {
	mapA := map[string]bool{}
	for _, item := range listA {
		mapA[item] = true
	}
	for _, item := range listB {
		if mapA[item] {
			list = append(list, item)
		}
	}
	return list
}

func onlyOneParameterProvided(parameter string, r *http.Request) bool {
	u, _ := url.Parse(r.RequestURI)
	parameters, _ := url.ParseQuery(u.RawQuery)
	for key, _ := range parameters {
		if key != parameter {
			return false
		}
	}
	return len(parameters) > 0
}

func SearchInShared(listByName []string, token string) []string {
	var list []string
	for _, k := range listByName {
		if !db.Public(k) && db.CheckShare(k, db.CheckToken(token)) {
			list = append(list, k)
		}
	}
	return list
}
<commit_msg>#289<commit_after>package download

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/blang/semver"
	"github.com/subutai-io/agent/log"
	"github.com/subutai-io/gorjun/config"
	"github.com/subutai-io/gorjun/db"
	"net/url"
)

// ListItem describes Gorjun entity. It can be APT package, Subutai template or Raw file.
type ListItem struct {
	ID           string            `json:"id"`
	Hash         hashsums          `json:"hash"`
	Size         int               `json:"size"`
	Date         time.Time         `json:"upload-date-formatted"`
	Timestamp    string            `json:"upload-date-timestamp,omitempty"`
	Name         string            `json:"name,omitempty"`
	Tags         []string          `json:"tags,omitempty"`
	Owner        []string          `json:"owner,omitempty"`
	Parent       string            `json:"parent,omitempty"`
	Version      string            `json:"version,omitempty"`
	Filename     string            `json:"filename,omitempty"`
	Prefsize     string            `json:"prefsize,omitempty"`
	Signature    map[string]string `json:"signature,omitempty"`
	Description  string            `json:"description,omitempty"`
	Architecture string            `json:"architecture,omitempty"`
}

type hashsums struct {
	Md5    string `json:"md5,omitempty"`
	Sha256 string `json:"sha256,omitempty"`
}

// Handler provides download functionality for all artifacts.
func Handler(repo string, w http.ResponseWriter, r *http.Request) {
	id := r.URL.Query().Get("id")
	name := r.URL.Query().Get("name")
	if len(id) == 0 && len(name) == 0 {
		io.WriteString(w, "Please specify id or name")
		return
	} else if len(name) != 0 {
		id = db.LastHash(name, repo)
	}

	if len(db.Read(id)) > 0 && !db.Public(id) && !db.CheckShare(id, db.CheckToken(r.URL.Query().Get("token"))) {
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("Not found"))
		return
	}

	path := config.Storage.Path + id
	if md5, _ := db.Hash(id); len(md5) != 0 {
		path = config.Storage.Path + md5
	}

	f, err := os.Open(path)
	defer f.Close()

	if log.Check(log.WarnLevel, "Opening file "+config.Storage.Path+id, err) || len(id) == 0 {
		if len(config.CDN.Node) > 0 {
			client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
			resp, err := client.Get(config.CDN.Node + r.URL.RequestURI())
			if !log.Check(log.WarnLevel, "Getting file from CDN", err) {
				w.Header().Set("Content-Length", resp.Header.Get("Content-Length"))
				w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
				w.Header().Set("Last-Modified", resp.Header.Get("Last-Modified"))
				w.Header().Set("Content-Disposition", resp.Header.Get("Content-Disposition"))

				io.Copy(w, resp.Body)
				resp.Body.Close()
				return
			}
		}

		w.WriteHeader(http.StatusNotFound)
		io.WriteString(w, "File not found")
		return
	}
	fi, _ := f.Stat()

	if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && fi.ModTime().Unix() <= t.Unix() {
		w.WriteHeader(http.StatusNotModified)
		return
	}

	w.Header().Set("Content-Length", fmt.Sprint(fi.Size()))
	w.Header().Set("Content-Type", r.Header.Get("Content-Type"))
	w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))

	if name = db.Read(id); len(name) == 0 && len(config.CDN.Node) > 0 {
		httpclient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
		resp, err := httpclient.Get(config.CDN.Node + "/kurjun/rest/template/info?id=" + id)
		if !log.Check(log.WarnLevel, "Getting info from CDN", err) {
			var info ListItem
			rsp, err := ioutil.ReadAll(resp.Body)
			if log.Check(log.WarnLevel, "Reading from CDN response", err) {
				w.WriteHeader(http.StatusNotFound)
				io.WriteString(w, "File not found")
				return
			}
			if !log.Check(log.WarnLevel, "Decrypting request", json.Unmarshal([]byte(rsp), &info)) {
				w.Header().Set("Content-Disposition", "attachment; filename=\""+info.Filename+"\"")
			}
			resp.Body.Close()
		}
	} else {
		w.Header().Set("Content-Disposition", "attachment; filename=\""+db.Read(id)+"\"")
	}

	io.Copy(w, f)
}

// Info returns JSON formatted list of elements. It allows to apply some filters to Search.
func Info(repo string, r *http.Request) []byte {
	var items []ListItem
	var fullname bool
	var itemLatestVersion ListItem
	p := []int{0, 1000}
	id := r.URL.Query().Get("id")
	tag := r.URL.Query().Get("tag")
	name := r.URL.Query().Get("name")
	page := r.URL.Query().Get("page")
	owner := r.URL.Query().Get("owner")
	token := r.URL.Query().Get("token")
	subname := r.URL.Query().Get("subname")
	version := r.URL.Query().Get("version")
	verified := r.URL.Query().Get("verified")
	if len(subname) != 0 {
		name = subname
	}
	list := db.Search(name)
	if len(tag) > 0 {
		listByTag, err := db.Tag(tag)
		log.Check(log.DebugLevel, "Looking for artifacts with tag "+tag, err)
		list = intersect(list, listByTag)
	}
	if len(token) > 0 {
		o := db.CheckToken(token)
		listByOwner := db.All(o, repo)
		listShared := SearchInShared(list, token)
		if len(listByOwner) > 0 {
			list = intersect(list, listByOwner)
		}
		list = append(list, listShared...)
	}
	if onlyOneParameterProvided("name", r) {
		verified = "true"
	}
	if len(name) > 0 && token == "" && owner == "" {
		verified = "true"
	}
	if len(id) > 0 {
		list = append(list[:0], id)
	} else if verified == "true" {
		itemLatestVersion = getVerified(list, name, repo, version)
		if itemLatestVersion.ID != "" {
			items = append(items, getVerified(list, name, repo, version))
			items[0].Signature = db.FileSignatures(items[0].ID)
		}
		output, err := json.Marshal(items)
		if err == nil && len(items) > 0 && items[0].ID != "" {
			return output
		}
	}

	pstr := strings.Split(page, ",")
	p[0], _ = strconv.Atoi(pstr[0])
	if len(pstr) == 2 {
		p[1], _ = strconv.Atoi(pstr[1])
	}
	latestVersion, _ := semver.Make("")
	for _, k := range list {
		if (!db.Public(k) && !db.CheckShare(k, db.CheckToken(token))) ||
			(len(owner) > 0 && db.CheckRepo(owner, repo, k) == 0) ||
			db.CheckRepo("", repo, k) == 0 {
			continue
		}

		if p[0]--; p[0] > 0 {
			continue
		}

		item := FormatItem(db.Info(k), repo, name)
		if len(subname) == 0 && name == item.Name {
			if strings.HasSuffix(item.Version, version) || len(version) == 0 {
				items = []ListItem{item}
				fullname = true
				itemVersion, _ := semver.Make(item.Version)
				if itemVersion.GTE(latestVersion) {
					latestVersion = itemVersion
					itemLatestVersion = item
				}
			}
		} else if !fullname && (len(version) == 0 || item.Version == version) {
			items = append(items, item)
		}

		if len(items) >= p[1] {
			break
		}
	}
	if len(items) == 1 {
		if version == "" && repo == "template" && itemLatestVersion.ID != "" {
			items[0] = itemLatestVersion
		}
		items[0].Signature = db.FileSignatures(items[0].ID)
	}
	output, err := json.Marshal(items)
	if err != nil || string(output) == "null" {
		return nil
	}
	return output
}

func in(str string, list []string) bool {
	for _, s := range list {
		if s == str {
			return true
		}
	}
	return false
}

func getVerified(list []string, name, repo string, versionTemplate string) ListItem {
	latestVersion, _ := semver.Make("")
	var itemLatestVersion ListItem
	for _, k := range list {
		if info := db.Info(k); db.CheckRepo("", repo, k) > 0 {
			if info["name"] == name || (strings.HasPrefix(info["name"], name+"-subutai-template") && repo == "template") {
				for _, owner := range db.FileField(info["id"], "owner") {
					itemVersion, _ := semver.Make(info["version"])
					if in(owner, []string{"subutai", "jenkins", "docker"}) {
						if itemVersion.GTE(latestVersion) && len(versionTemplate) == 0 {
							latestVersion = itemVersion
							itemLatestVersion = FormatItem(db.Info(k), repo, name)
						} else if versionTemplate == itemVersion.String() {
							itemLatestVersion = FormatItem(db.Info(k), repo, name)
						}
					}
				}
			}
		}
	}
	return itemLatestVersion
}

func FormatItem(info map[string]string, repo, name string) ListItem {
	if len(info["prefsize"]) == 0 && repo == "template" {
		info["prefsize"] = "tiny"
	}

	date, _ := time.Parse(time.RFC3339Nano, info["date"])
	timestamp := strconv.FormatInt(date.Unix(), 10)
	item := ListItem{
		ID:           info["id"],
		Date:         date,
		Hash:         hashsums{Md5: info["md5"], Sha256: info["sha256"]},
		Name:         strings.Split(info["name"], "-subutai-template")[0],
		Tags:         db.FileField(info["id"], "tags"),
		Owner:        db.FileField(info["id"], "owner"),
		Version:      info["version"],
		Filename:     info["name"],
		Parent:       info["parent"],
		Prefsize:     info["prefsize"],
		Architecture: strings.ToUpper(info["arch"]),
		Description:  info["Description"],
		Timestamp:    timestamp,
	}
	item.Size, _ = strconv.Atoi(info["size"])

	if repo == "apt" {
		item.Version = info["Version"]
		item.Architecture = info["Architecture"]
		item.Size, _ = strconv.Atoi(info["Size"])
	}
	if len(item.Hash.Md5) == 0 {
		item.Hash.Md5 = item.ID
	}
	return item
}

func intersect(listA, listB []string) (list []string) {
	mapA := map[string]bool{}
	for _, item := range listA {
		mapA[item] = true
	}
	for _, item := range listB {
		if mapA[item] {
			list = append(list, item)
		}
	}
	return list
}

func onlyOneParameterProvided(parameter string, r *http.Request) bool {
	u, _ := url.Parse(r.RequestURI)
	parameters, _ := url.ParseQuery(u.RawQuery)
	for key, _ := range parameters {
		if key != parameter {
			return false
		}
	}
	return len(parameters) > 0
}

func SearchInShared(listByName []string, token string) []string {
	var list []string
	for _, k := range listByName {
		if !db.Public(k) && db.CheckShare(k, db.CheckToken(token)) {
			list = append(list, k)
		}
	}
	return list
}
<|endoftext|>