<commit_before>package amqprpc

import (
	"errors"
	"net/rpc"
	"sync"
	"sync/atomic"

	"github.com/streadway/amqp"
)

type route struct {
	messageID string
	routing   string
}

type serverCodec struct {
	*codec
	lock  *sync.RWMutex
	calls map[uint64]route
	seq   uint64
}

func (server *serverCodec) ReadRequestHeader(r *rpc.Request) error {
	server.curDelivery = <-server.message

	if server.curDelivery.CorrelationId == "" {
		return errors.New("no routing key in delivery")
	}

	r.Seq = atomic.AddUint64(&server.seq, 1)

	server.lock.Lock()
	server.calls[r.Seq] = route{server.curDelivery.MessageId, server.curDelivery.CorrelationId}
	server.lock.Unlock()

	r.ServiceMethod = server.curDelivery.ReplyTo

	return nil
}

func (server *serverCodec) ReadRequestBody(v interface{}) error {
	if v == nil {
		return nil
	}

	return server.codec.codec.Unmarshal(server.curDelivery.Body, v)
}

func (server *serverCodec) WriteResponse(resp *rpc.Response, v interface{}) error {
	body, err := server.codec.codec.Marshal(v)
	if err != nil {
		return err
	}

	server.lock.RLock()
	route, ok := server.calls[resp.Seq]
	server.lock.RUnlock()
	if !ok {
		return errors.New("sequence dosen't have a route")
	}

	publishing := amqp.Publishing{
		Headers:       amqp.Table{"error": resp.Error},
		ReplyTo:       resp.ServiceMethod,
		MessageId:     route.messageID,
		CorrelationId: route.routing,
		Body:          body,
	}

	return server.channel.Publish(
		"",
		route.routing,
		false,
		false,
		publishing,
	)
}
func (server *serverCodec) Close() error {
	return server.Close()
}

//NewServerCodec returns a new rpc.ClientCodec using AMQP on conn. serverRouting is the routing
//key with with RPC calls are received, encodingCodec is an EncodingCoding implementation. This package provdes JSONCodec and GobCodec for the JSON and Gob encodings respectively.
func NewServerCodec(conn *amqp.Connection, serverRouting string, encodingCodec EncodingCodec) (rpc.ServerCodec, error) {
	channel, err := conn.Channel()
	if err != nil {
		return nil, err
	}

	queue, err := channel.QueueDeclare(serverRouting, false, true, false, false, nil)
	if err != nil {
		return nil, err
	}

	messages, err := channel.Consume(queue.Name, "", false, false, false, false, nil)
	if err != nil {
		return nil, err
	}

	server := &serverCodec{
		codec: &codec{
			conn:    conn,
			channel: channel,
			routing: queue.Name,

			codec:   encodingCodec,
			message: messages,
		},
		lock:  new(sync.RWMutex),
		calls: make(map[uint64]route),
		seq:   0,
	}

	return server, err
}
<commit_msg>WriteResponse: Add error header correctly.<commit_after>package amqprpc

import (
	"errors"
	"net/rpc"
	"sync"
	"sync/atomic"

	"github.com/streadway/amqp"
)

type route struct {
	messageID string
	routing   string
}

type serverCodec struct {
	*codec
	lock  *sync.RWMutex
	calls map[uint64]route
	seq   uint64
}

func (server *serverCodec) ReadRequestHeader(r *rpc.Request) error {
	server.curDelivery = <-server.message

	if server.curDelivery.CorrelationId == "" {
		return errors.New("no routing key in delivery")
	}

	r.Seq = atomic.AddUint64(&server.seq, 1)

	server.lock.Lock()
	server.calls[r.Seq] = route{server.curDelivery.MessageId, server.curDelivery.CorrelationId}
	server.lock.Unlock()

	r.ServiceMethod = server.curDelivery.ReplyTo

	return nil
}

func (server *serverCodec) ReadRequestBody(v interface{}) error {
	if v == nil {
		return nil
	}

	return server.codec.codec.Unmarshal(server.curDelivery.Body, v)
}

func (server *serverCodec) WriteResponse(resp *rpc.Response, v interface{}) error {
	body, err := server.codec.codec.Marshal(v)
	if err != nil {
		return err
	}

	server.lock.RLock()
	route, ok := server.calls[resp.Seq]
	server.lock.RUnlock()
	if !ok {
		return errors.New("sequence dosen't have a route")
	}

	publishing := amqp.Publishing{
		ReplyTo:       resp.ServiceMethod,
		MessageId:     route.messageID,
		CorrelationId: route.routing,
		Body:          body,
	}

	if resp.Error != "" {
		publishing.Headers = amqp.Table{"error": resp.Error}
	}

	return server.channel.Publish(
		"",
		route.routing,
		false,
		false,
		publishing,
	)
}
func (server *serverCodec) Close() error {
	return server.Close()
}

//NewServerCodec returns a new rpc.ClientCodec using AMQP on conn. serverRouting is the routing
//key with with RPC calls are received, encodingCodec is an EncodingCoding implementation. This package provdes JSONCodec and GobCodec for the JSON and Gob encodings respectively.
func NewServerCodec(conn *amqp.Connection, serverRouting string, encodingCodec EncodingCodec) (rpc.ServerCodec, error) {
	channel, err := conn.Channel()
	if err != nil {
		return nil, err
	}

	queue, err := channel.QueueDeclare(serverRouting, false, true, false, false, nil)
	if err != nil {
		return nil, err
	}

	messages, err := channel.Consume(queue.Name, "", false, false, false, false, nil)
	if err != nil {
		return nil, err
	}

	server := &serverCodec{
		codec: &codec{
			conn:    conn,
			channel: channel,
			routing: queue.Name,

			codec:   encodingCodec,
			message: messages,
		},
		lock:  new(sync.RWMutex),
		calls: make(map[uint64]route),
		seq:   0,
	}

	return server, err
}
<|endoftext|>


<commit_before>package gosearch

import (
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"time"
)

type Status struct {
	Status  int
	Name    string
	Version struct {
		Number   string
		Snapshot bool `json:"snapshot_build"`
	}
}

type Server struct {
	url string
}

func Connect(host string, port int) *Server {
	return ConnectURL(fmt.Sprintf("http://%s:%d", host, port))
}

func ConnectURL(url string) *Server {
	es := &Server{url: url}
	return es
}

const DEFAULT_IDLE_TIMEOUT = time.Second
const MAX_CONNECTIONS = 100
const CONN_TIMEOUT = time.Second

func timeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
	return func(netw, addr string) (net.Conn, error) {
		conn, err := net.DialTimeout(netw, addr, cTimeout)
		if err != nil {
			return nil, err
		}
		conn.SetDeadline(time.Now().Add(rwTimeout))
		return conn, nil
	}
}

var tr = &http.Transport{
	DisableKeepAlives:   true,
	MaxIdleConnsPerHost: MAX_CONNECTIONS * 2,
	Dial:                timeoutDialer(time.Second, CONN_TIMEOUT),
}

var client = &http.Client{Transport: tr}
var throttle = make(chan int, MAX_CONNECTIONS)

func (self *Server) do(method, cmd string, body io.Reader, fn func(*http.Response) error) error {
	var url string
	if cmd == "" {
		url = self.url
	} else {
		url = fmt.Sprintf("%s/%s", self.url, cmd)
	}

	// fmt.Printf("Command => %s:%s\n", method, url)
	if req, err := http.NewRequest(method, url, body); err != nil {
		return err
	} else {
		throttle <- 1
		defer func() { <-throttle }()
		if resp, err := client.Do(req); err != nil {
			return err
		} else {
			defer resp.Body.Close()
			return fn(resp)
		}
	}
}

func (self *Server) Get(cmd string, fn func(*http.Response) error) error {
	return self.do("GET", cmd, nil, fn)
}

func (self *Server) Put(cmd string, body io.Reader, fn func(*http.Response) error) error {
	return self.do("PUT", cmd, body, fn)
}

func (self *Server) Post(cmd string, body io.Reader, fn func(*http.Response) error) error {
	return self.do("POST", cmd, body, fn)
}

func (self *Server) Delete(cmd string, fn func(*http.Response) error) error {
	return self.do("DELETE", cmd, nil, fn)
}

func (self *Server) Head(cmd string, fn func(*http.Response) error) error {
	return self.do("HEAD", cmd, nil, fn)
}

func (self *Server) Status() (*Status, error) {
	status := &Status{}

	return status, self.Get("", func(resp *http.Response) error {
		if resp.StatusCode != 200 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return json.NewDecoder(resp.Body).Decode(status)
		}
	})
}

func (self *Server) HasIndex(index string) bool {

	return self.Head(index, func(resp *http.Response) error {
		if resp.StatusCode != 200 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return nil
		}
	}) == nil
}

func (self *Server) CreateIndex(index string) bool {

	return self.Put(index, nil, func(resp *http.Response) error {
		if resp.StatusCode != 200 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return nil
		}
	}) == nil
}

func (self *Server) CreateIndexWithSettings(index string, settings io.Reader) bool {
	return self.Put(index, settings, func(resp *http.Response) error {
		if resp.StatusCode != 200 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return nil
		}
	}) == nil
}

func (self *Server) DeleteIndex(index string) bool {

	return self.Delete(index, func(resp *http.Response) error {
		if resp.StatusCode != 200 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return nil
		}
	}) == nil
}

type Document struct {
	Type    string
	Id      string
	Version float64
	Exists  bool
	Source  interface{}
}

func (self *Document) UnmarshalJSON(data []byte) error {
	var objmap map[string]*json.RawMessage

	if err := json.Unmarshal(data, &objmap); err != nil {
		return err
	}

	if _, ok := objmap["_type"]; ok {
		if err := json.Unmarshal(*objmap["_type"], &self.Type); err != nil {
			return err
		}
	}

	if _, ok := objmap["_id"]; ok {
		if err := json.Unmarshal(*objmap["_id"], &self.Id); err != nil {
			return err
		}
	}

	if _, ok := objmap["_version"]; ok {
		if err := json.Unmarshal(*objmap["_version"], &self.Version); err != nil {
			return err
		}
		fmt.Printf("Version => %d\n", self.Version)
	}

	if _, ok := objmap["exists"]; ok {
		if err := json.Unmarshal(*objmap["exists"], &self.Exists); err != nil {
			return err
		}
	}

	if _, ok := objmap["_source"]; ok {
		if err := json.Unmarshal(*objmap["_source"], &self.Source); err != nil {
			return err
		}
	} else if _, ok := objmap["fields"]; ok {
		if err := json.Unmarshal(*objmap["fields"], &self.Source); err != nil {
			return err
		}
	}
	return nil
}

func (self *Server) PutDocument(index string, doctype string, id string, doc io.Reader) bool {
	cmd := fmt.Sprintf("%s/%s/%s", index, doctype, id)

	err := self.Put(cmd, doc, func(resp *http.Response) error {
		if resp.StatusCode != 200 && resp.StatusCode != 201 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return nil
		}
	})

	return err == nil
}

func (self *Server) GetDocument(index string, doctype string, id string) *Document {
	return self.GetDocumentFields(index, doctype, id, "")
}

func (self *Server) GetDocumentFields(index string, doctype string, id string, fields string) *Document {
	var cmd string
	var doc *Document

	if fields == "" {
		cmd = fmt.Sprintf("%s/%s/%s", index, doctype, id)
	} else {
		cmd = fmt.Sprintf("%s/%s/%s?fields=%s", index, doctype, id, fields)
	}

	self.Get(cmd, func(resp *http.Response) error {
		switch resp.StatusCode {
		case 200:
			doc = &Document{}
			return json.NewDecoder(resp.Body).Decode(doc)
		case 404:
			doc = &Document{Type: doctype, Id: id, Exists: false}
			return nil
		default:
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		}
	})

	// fmt.Printf("Returning doc %s\n", *doc)
	return doc
}

func (self *Server) Search() *Search {
	s := &Search{Server: self}
	return s
}
<commit_msg>About to change<commit_after>package gosearch

import (
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"time"
)

type Status struct {
	Status  int
	Name    string
	Version struct {
		Number   string
		Snapshot bool `json:"snapshot_build"`
	}
}

type Server struct {
	url string
}

func Connect(host string, port int) *Server {
	return ConnectURL(fmt.Sprintf("http://%s:%d", host, port))
}

func ConnectURL(url string) *Server {
	es := &Server{url: url}
	return es
}

const DEFAULT_IDLE_TIMEOUT = time.Second
const MAX_CONNECTIONS = 100
const CONN_TIMEOUT = time.Second

func timeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
	return func(netw, addr string) (net.Conn, error) {
		conn, err := net.DialTimeout(netw, addr, cTimeout)
		if err != nil {
			return nil, err
		}
		conn.SetDeadline(time.Now().Add(rwTimeout))
		return conn, nil
	}
}

var tr = &http.Transport{
	DisableKeepAlives:   false,
	MaxIdleConnsPerHost: MAX_CONNECTIONS * 2,
	Dial:                timeoutDialer(time.Second, CONN_TIMEOUT),
}

var client = &http.Client{Transport: tr}
var throttle = make(chan int, MAX_CONNECTIONS)

func (self *Server) do(method, cmd string, body io.Reader, fn func(*http.Response) error) error {
	var url string
	if cmd == "" {
		url = self.url
	} else {
		url = fmt.Sprintf("%s/%s", self.url, cmd)
	}

	// fmt.Printf("Command => %s:%s\n", method, url)
	if req, err := http.NewRequest(method, url, body); err != nil {
		return err
	} else {
		throttle <- 1
		defer func() { <-throttle }()
		if resp, err := client.Do(req); err != nil {
			return err
		} else {
			defer resp.Body.Close()
			return fn(resp)
		}
	}
}

func (self *Server) Get(cmd string, fn func(*http.Response) error) error {
	return self.do("GET", cmd, nil, fn)
}

func (self *Server) Put(cmd string, body io.Reader, fn func(*http.Response) error) error {
	return self.do("PUT", cmd, body, fn)
}

func (self *Server) Post(cmd string, body io.Reader, fn func(*http.Response) error) error {
	return self.do("POST", cmd, body, fn)
}

func (self *Server) Delete(cmd string, fn func(*http.Response) error) error {
	return self.do("DELETE", cmd, nil, fn)
}

func (self *Server) Head(cmd string, fn func(*http.Response) error) error {
	return self.do("HEAD", cmd, nil, fn)
}

func (self *Server) Status() (*Status, error) {
	status := &Status{}

	return status, self.Get("", func(resp *http.Response) error {
		if resp.StatusCode != 200 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return json.NewDecoder(resp.Body).Decode(status)
		}
	})
}

func (self *Server) HasIndex(index string) bool {

	return self.Head(index, func(resp *http.Response) error {
		if resp.StatusCode != 200 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return nil
		}
	}) == nil
}

func (self *Server) CreateIndex(index string) bool {

	return self.Put(index, nil, func(resp *http.Response) error {
		if resp.StatusCode != 200 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return nil
		}
	}) == nil
}

func (self *Server) CreateIndexWithSettings(index string, settings io.Reader) bool {
	return self.Put(index, settings, func(resp *http.Response) error {
		if resp.StatusCode != 200 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return nil
		}
	}) == nil
}

func (self *Server) DeleteIndex(index string) bool {

	return self.Delete(index, func(resp *http.Response) error {
		if resp.StatusCode != 200 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return nil
		}
	}) == nil
}

type Document struct {
	Type    string
	Id      string
	Version float64
	Exists  bool
	Source  interface{}
}

func (self *Document) UnmarshalJSON(data []byte) error {
	var objmap map[string]*json.RawMessage

	if err := json.Unmarshal(data, &objmap); err != nil {
		return err
	}

	if _, ok := objmap["_type"]; ok {
		if err := json.Unmarshal(*objmap["_type"], &self.Type); err != nil {
			return err
		}
	}

	if _, ok := objmap["_id"]; ok {
		if err := json.Unmarshal(*objmap["_id"], &self.Id); err != nil {
			return err
		}
	}

	if _, ok := objmap["_version"]; ok {
		if err := json.Unmarshal(*objmap["_version"], &self.Version); err != nil {
			return err
		}
		fmt.Printf("Version => %d\n", self.Version)
	}

	if _, ok := objmap["exists"]; ok {
		if err := json.Unmarshal(*objmap["exists"], &self.Exists); err != nil {
			return err
		}
	}

	if _, ok := objmap["_source"]; ok {
		if err := json.Unmarshal(*objmap["_source"], &self.Source); err != nil {
			return err
		}
	} else if _, ok := objmap["fields"]; ok {
		if err := json.Unmarshal(*objmap["fields"], &self.Source); err != nil {
			return err
		}
	}
	return nil
}

func (self *Server) PutDocument(index string, doctype string, id string, doc io.Reader) bool {
	cmd := fmt.Sprintf("%s/%s/%s", index, doctype, id)

	err := self.Put(cmd, doc, func(resp *http.Response) error {
		if resp.StatusCode != 200 && resp.StatusCode != 201 {
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		} else {
			return nil
		}
	})

	return err == nil
}

func (self *Server) GetDocument(index string, doctype string, id string) *Document {
	return self.GetDocumentFields(index, doctype, id, "")
}

func (self *Server) GetDocumentFields(index string, doctype string, id string, fields string) *Document {
	var cmd string
	var doc *Document

	if fields == "" {
		cmd = fmt.Sprintf("%s/%s/%s", index, doctype, id)
	} else {
		cmd = fmt.Sprintf("%s/%s/%s?fields=%s", index, doctype, id, fields)
	}

	self.Get(cmd, func(resp *http.Response) error {
		switch resp.StatusCode {
		case 200:
			doc = &Document{}
			return json.NewDecoder(resp.Body).Decode(doc)
		case 404:
			doc = &Document{Type: doctype, Id: id, Exists: false}
			return nil
		default:
			return fmt.Errorf("Unexpected status code %d", resp.StatusCode)
		}
	})

	// fmt.Printf("Returning doc %s\n", *doc)
	return doc
}

func (self *Server) Search() *Search {
	s := &Search{Server: self}
	return s
}
<|endoftext|>


<commit_before>package dnsp

import (
	"regexp"
	"sync"
	"time"

	"github.com/miekg/dns"
)

// Server implements a DNS server.
type Server struct {
	c *dns.Client
	s *dns.Server

	// White, when set to true, causes the server to work in white-listing mode.
	// It will only resolve queries that have been white-listed.
	//
	// When set to false, it will resolve anything that is not blacklisted.
	white bool

	// Protect access to the hosts file with a mutex.
	m sync.RWMutex
	// A combined whitelist/blacklist. It contains both whitelist and blacklist entries.
	hosts hosts
	// Regex based whitelist and blacklist, depending on the value of `white`.
	hostsRX hostsRX

	privateHosts   map[string]struct{}
	privateHostsRX map[string]*regexp.Regexp

	// Information about the hosts file, used for polling:
	hostsFile struct {
		size  int64
		path  string
		mtime time.Time
	}
}

// NewServer creates a new Server with the given options.
func NewServer(o Options) (*Server, error) {
	if err := o.validate(); err != nil {
		return nil, err
	}

	s := Server{
		c: &dns.Client{},
		s: &dns.Server{
			Net:  o.Net,
			Addr: o.Bind,
		},
		white: o.Whitelist != "",
		hosts: hosts{},
	}

	hostListPath := o.Whitelist
	if hostListPath == "" {
		hostListPath = o.Blacklist
	}
	s.hostsFile.path = hostListPath
	if err := s.loadHostEntries(); err != nil {
		return nil, err
	}
	if o.Poll != 0 {
		go s.monitorHostEntries(o.Poll)
	}
	s.s.Handler = dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
		// If no upstream proxy is present, drop the query:
		if len(o.Resolve) == 0 {
			dns.HandleFailed(w, r)
			return
		}

		// Filter Questions:
		if r.Question = s.filter(r.Question); len(r.Question) == 0 {
			w.WriteMsg(r)
			return
		}

		// Proxy Query:
		for _, addr := range o.Resolve {
			in, _, err := s.c.Exchange(r, addr)
			if err != nil {
				continue
			}
			w.WriteMsg(in)
			return
		}
		dns.HandleFailed(w, r)
	})
	return &s, nil
}

// ListenAndServe runs the server
func (s *Server) ListenAndServe() error {
	return s.s.ListenAndServe()
}

// Shutdown stops the server, closing its connection.
func (s *Server) Shutdown() error {
	return s.s.Shutdown()
}
<commit_msg>initialise struct values<commit_after>package dnsp

import (
	"regexp"
	"sync"
	"time"

	"github.com/miekg/dns"
)

// Server implements a DNS server.
type Server struct {
	c *dns.Client
	s *dns.Server

	// White, when set to true, causes the server to work in white-listing mode.
	// It will only resolve queries that have been white-listed.
	//
	// When set to false, it will resolve anything that is not blacklisted.
	white bool

	// Protect access to the hosts file with a mutex.
	m sync.RWMutex
	// A combined whitelist/blacklist. It contains both whitelist and blacklist entries.
	hosts hosts
	// Regex based whitelist and blacklist, depending on the value of `white`.
	hostsRX hostsRX

	privateHosts   map[string]struct{}
	privateHostsRX map[string]*regexp.Regexp

	// Information about the hosts file, used for polling:
	hostsFile struct {
		size  int64
		path  string
		mtime time.Time
	}
}

// NewServer creates a new Server with the given options.
func NewServer(o Options) (*Server, error) {
	if err := o.validate(); err != nil {
		return nil, err
	}

	s := Server{
		c: &dns.Client{},
		s: &dns.Server{
			Net:  o.Net,
			Addr: o.Bind,
		},
		white:   o.Whitelist != "",
		hosts:   hosts{},
		hostsRX: hostsRX{},

		privateHosts:   map[string]struct{}{},
		privateHostsRX: map[string]*regexp.Regexp{},
	}

	hostListPath := o.Whitelist
	if hostListPath == "" {
		hostListPath = o.Blacklist
	}
	s.hostsFile.path = hostListPath
	if err := s.loadHostEntries(); err != nil {
		return nil, err
	}
	if o.Poll != 0 {
		go s.monitorHostEntries(o.Poll)
	}
	s.s.Handler = dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
		// If no upstream proxy is present, drop the query:
		if len(o.Resolve) == 0 {
			dns.HandleFailed(w, r)
			return
		}

		// Filter Questions:
		if r.Question = s.filter(r.Question); len(r.Question) == 0 {
			w.WriteMsg(r)
			return
		}

		// Proxy Query:
		for _, addr := range o.Resolve {
			in, _, err := s.c.Exchange(r, addr)
			if err != nil {
				continue
			}
			w.WriteMsg(in)
			return
		}
		dns.HandleFailed(w, r)
	})
	return &s, nil
}

// ListenAndServe runs the server
func (s *Server) ListenAndServe() error {
	return s.s.ListenAndServe()
}

// Shutdown stops the server, closing its connection.
func (s *Server) Shutdown() error {
	return s.s.Shutdown()
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"os"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/gin-gonic/gin"
)

const bufferLen = 500

type logData struct {
	app    *string
	tags   *[]string
	prefix *string
	line   *string
}

type ServerCtx struct {
	Port        string
	AllowedApps []string
	AppPasswd   map[string]string
	AppTags     map[string][]string
	AppPrefix   map[string]string
	StatsdUrl   string
	Debug       bool
	in          chan *logData
	out         chan *logMetrics
}

//Load configuration from envrionment variables, see list below
//ALLOWED_APPS=my-app,.. Required.
//Comma seperated list of app names

//<APP-NAME>_PASSWORD=.. Required.
//One per allowed app where <APP-NAME> corresponds to an app name from ALLOWED_APPS

//<APP-NAME>_TAGS=mytag,..  Optional.
// Comma seperated list of default tags for each app

//<APP-NAME>_PREFIX=yee     Optional.
//String to be prepended to all metrics from a given app

//STATSD_URL=..  Optional. Default: statsd://localhost:8125
//DATADOG_DRAIN_DEBUG=         Optional. If DEBUG is set, a lot of stuff w
func loadServerCtx() *ServerCtx {

	s := &ServerCtx{"8080",
		nil,
		make(map[string]string),
		make(map[string][]string),
		make(map[string]string),
		"statsd://localhost:8125",
		false,
		nil,
		nil,
	}
	port := os.Getenv("PORT")
	if port != "" {
		s.Port = port
	}

	allApps := os.Getenv("ALLOWED_APPS")
	if allApps != "" {
		apps := strings.Split(allApps, ",")
		log.WithField("apps", apps).Info("ALLOWED_APPS loaded.")
		for _, app := range apps {
			s.AllowedApps = append(s.AllowedApps, app)
			s.AppPasswd[app] = os.Getenv(app + "_PASSWORD")
			if s.AppPasswd[app] == "" {
				log.WithField("app", app).Warn("App is allowed but no password set")
			}
			tags := os.Getenv(app + "_TAGS")
			if tags != "" {
				s.AppTags[app] = strings.Split(tags, ",")
			}
			s.AppPrefix[app] = os.Getenv(app + "_PREFIX")
		}
	} else {
		log.Warn("No Allowed apps set, nobody can access this service!")
	}

	statsd := os.Getenv("STATSD_URL")
	if port != "" {
		s.StatsdUrl = statsd
	}

	if os.Getenv("DATADOG_DRAIN_DEBUG") != "" {
		s.Debug = true
	}

	log.WithFields(log.Fields{
		"port":         s.Port,
		"AlloweApps":   s.AllowedApps,
		"AppPasswords": s.AppPasswd,
		"AppTags":      s.AppTags,
		"AppPrefix":    s.AppPrefix,
		"StatsdUrl":    s.StatsdUrl,
		"Debug":        s.Debug,
	}).Info("Configuration loaded")

	return s
}

func init() {
	// Output to stderr instead of stdout
	log.SetOutput(os.Stderr)

	// Only log the Info severity or above.
	log.SetLevel(log.InfoLevel)
}

func (s *ServerCtx) processLogs(c *gin.Context) {

	app := c.MustGet(gin.AuthUserKey).(string)
	tags := s.AppTags[app]
	prefix := s.AppPrefix[app]

	scanner := bufio.NewScanner(c.Request.Body)
	for scanner.Scan() {
		line := scanner.Text()
		log.WithField("line", line).Debug("LINE")
		s.in <- &logData{&app, &tags, &prefix, &line}
	}
	if err := scanner.Err(); err != nil {
		log.Error(err)
	}

	c.String(200, "OK")
}

func main() {
	gin.SetMode(gin.ReleaseMode)

	s := loadServerCtx()
	if s.Debug {
		log.SetLevel(log.DebugLevel)
		gin.SetMode(gin.DebugMode)
	}

	c, err := statsdClient(s.StatsdUrl)
	if err != nil {
		log.WithField("statsdUrl", s.StatsdUrl).Fatal("Could not connect to statsd")
	}

	r := gin.Default()
	r.GET("/status", func(c *gin.Context) {
		c.String(200, "OK")
	})

	if len(s.AppPasswd) > 0 {
		auth := r.Group("/", gin.BasicAuth(s.AppPasswd))
		auth.POST("/", s.processLogs)
	}

	s.in = make(chan *logData, bufferLen)
	defer close(s.in)
	s.out = make(chan *logMetrics, bufferLen)
	defer close(s.out)
	go logProcess(s.in, s.out)
	go c.sendToStatsd(s.out)
	log.Infoln("Server ready ...")
	r.Run(":" + s.Port)

}
<commit_msg>Fix reading ENV vars.<commit_after>package main

import (
	"bufio"
	"os"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/gin-gonic/gin"
)

const bufferLen = 500

type logData struct {
	app    *string
	tags   *[]string
	prefix *string
	line   *string
}

type ServerCtx struct {
	Port        string
	AllowedApps []string
	AppPasswd   map[string]string
	AppTags     map[string][]string
	AppPrefix   map[string]string
	StatsdUrl   string
	Debug       bool
	in          chan *logData
	out         chan *logMetrics
}

//Load configuration from envrionment variables, see list below
//ALLOWED_APPS=my-app,.. Required.
//Comma seperated list of app names

//<APP-NAME>_PASSWORD=.. Required.
//One per allowed app where <APP-NAME> corresponds to an app name from ALLOWED_APPS

//<APP-NAME>_TAGS=mytag,..  Optional.
// Comma seperated list of default tags for each app

//<APP-NAME>_PREFIX=yee     Optional.
//String to be prepended to all metrics from a given app

//STATSD_URL=..  Optional. Default: statsd://localhost:8125
//DATADOG_DRAIN_DEBUG=         Optional. If DEBUG is set, a lot of stuff w
func loadServerCtx() *ServerCtx {

	s := &ServerCtx{"8080",
		nil,
		make(map[string]string),
		make(map[string][]string),
		make(map[string]string),
		"statsd://localhost:8125",
		false,
		nil,
		nil,
	}
	port := os.Getenv("PORT")
	if port != "" {
		s.Port = port
	}

	allApps := os.Getenv("ALLOWED_APPS")
	if allApps != "" {
		apps := strings.Split(allApps, ",")
		log.WithField("apps", apps).Info("ALLOWED_APPS loaded.")
		for _, app := range apps {
			name := strings.ToUpper(app)
			s.AllowedApps = append(s.AllowedApps, app)
			s.AppPasswd[app] = os.Getenv(name + "_PASSWORD")
			if s.AppPasswd[app] == "" {
				log.WithField("app", app).Warn("App is allowed but no password set")
			}
			tags := os.Getenv(name + "_TAGS")
			if tags != "" {
				s.AppTags[app] = strings.Split(tags, ",")
			}
			s.AppPrefix[app] = os.Getenv(name + "_PREFIX")
		}
	} else {
		log.Warn("No Allowed apps set, nobody can access this service!")
	}

	statsd := os.Getenv("STATSD_URL")
	if port != "" {
		s.StatsdUrl = statsd
	}

	if os.Getenv("DATADOG_DRAIN_DEBUG") != "" {
		s.Debug = true
	}

	log.WithFields(log.Fields{
		"port":         s.Port,
		"AlloweApps":   s.AllowedApps,
		"AppPasswords": "************",
		"AppTags":      s.AppTags,
		"AppPrefix":    s.AppPrefix,
		"StatsdUrl":    s.StatsdUrl,
		"Debug":        s.Debug,
	}).Info("Configuration loaded")

	return s
}

func init() {
	// Output to stderr instead of stdout
	log.SetOutput(os.Stderr)

	// Only log the Info severity or above.
	log.SetLevel(log.InfoLevel)
}

func (s *ServerCtx) processLogs(c *gin.Context) {

	app := c.MustGet(gin.AuthUserKey).(string)
	tags := s.AppTags[app]
	prefix := s.AppPrefix[app]

	scanner := bufio.NewScanner(c.Request.Body)
	for scanner.Scan() {
		line := scanner.Text()
		log.WithField("line", line).Debug("LINE")
		s.in <- &logData{&app, &tags, &prefix, &line}
	}
	if err := scanner.Err(); err != nil {
		log.Error(err)
	}

	c.String(200, "OK")
}

func main() {
	gin.SetMode(gin.ReleaseMode)

	s := loadServerCtx()
	if s.Debug {
		log.SetLevel(log.DebugLevel)
		gin.SetMode(gin.DebugMode)
	}

	c, err := statsdClient(s.StatsdUrl)
	if err != nil {
		log.WithField("statsdUrl", s.StatsdUrl).Fatal("Could not connect to statsd")
	}

	r := gin.Default()
	r.GET("/status", func(c *gin.Context) {
		c.String(200, "OK")
	})

	if len(s.AppPasswd) > 0 {
		auth := r.Group("/", gin.BasicAuth(s.AppPasswd))
		auth.POST("/", s.processLogs)
	}

	s.in = make(chan *logData, bufferLen)
	defer close(s.in)
	s.out = make(chan *logMetrics, bufferLen)
	defer close(s.out)
	go logProcess(s.in, s.out)
	go c.sendToStatsd(s.out)
	log.Infoln("Server ready ...")
	r.Run(":" + s.Port)

}
<|endoftext|>


<commit_before>/*
Package schedule grabs DJ schedule data from Ponyville FM's servers.
*/
package schedule

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

// ScheduleResult is a wrapper for a list of ScheduleEntry records.
type ScheduleResult struct {
	Result []ScheduleEntry `json:"result"`
}

// ScheduleEntry is an individual schedule datum.
type ScheduleEntry struct {
	StartTime   string `json:"start_time"`
	StartUnix   int    `json:"start_unix"`
	Duration    string `json:"duration"`
	EndTime     string `json:"end_time"`
	EndUnix     int    `json:"end_unix"`
	Name        string `json:"name"`
	Host        string `json:"host"`
	Description string `json:"description"`
	Showcard    string `json:"showcard"`
	Background  string `json:"background"`
	Timezone    string `json:"timezone"`
	Status      string `json:"status"`
}

func (s ScheduleEntry) String() string {
	startTimeUnix := time.Unix(int64(s.StartUnix), 0)
	dur := startTimeUnix.Sub(time.Now())

	return fmt.Sprintf(
		"In %d:%2d (%v %v): %s - %s",
		int(dur.Hours()), int(dur.Minutes()), s.StartTime, s.Timezone, s.Host, s.Name,
	)
}

var (
	latestInfo Wrapper

	bugTime = flag.Int("pvfm-schedule-poke-delay", 15, "how stale the info can get")
)

// Wrapper is a time, info pair. This is used to invalidate the cache of
// data from ponyvillefm.com.
type Wrapper struct {
	Age  time.Time
	Info *ScheduleResult
}

// Get returns schedule entries, only fetching new data at most every n
// seconds, where n is defined above.
func Get() ([]ScheduleEntry, error) {
	now := time.Now()

	if now.Before(latestInfo.Age.Add(time.Second * time.Duration(*bugTime))) {
		return latestInfo.Info.Result, nil
	}

	s := &ScheduleResult{}
	c := http.Client{
		Timeout: time.Duration(time.Second * 15),
	}

	resp, err := c.Get("http://ponyvillefm.com/data/schedule")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(content, s)
	if err != nil {
		return nil, err
	}

	// Update the age/contents of the latestInfo
	latestInfo.Info = s
	latestInfo.Age = now

	return s.Result, nil
}
<commit_msg>pvfm/schedule: mod 60<commit_after>/*
Package schedule grabs DJ schedule data from Ponyville FM's servers.
*/
package schedule

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"
)

// ScheduleResult is a wrapper for a list of ScheduleEntry records.
type ScheduleResult struct {
	Result []ScheduleEntry `json:"result"`
}

// ScheduleEntry is an individual schedule datum.
type ScheduleEntry struct {
	StartTime   string `json:"start_time"`
	StartUnix   int    `json:"start_unix"`
	Duration    string `json:"duration"`
	EndTime     string `json:"end_time"`
	EndUnix     int    `json:"end_unix"`
	Name        string `json:"name"`
	Host        string `json:"host"`
	Description string `json:"description"`
	Showcard    string `json:"showcard"`
	Background  string `json:"background"`
	Timezone    string `json:"timezone"`
	Status      string `json:"status"`
}

func (s ScheduleEntry) String() string {
	startTimeUnix := time.Unix(int64(s.StartUnix), 0)
	dur := startTimeUnix.Sub(time.Now())

	return fmt.Sprintf(
		"In %d:%2d (%v %v): %s - %s",
		int(dur.Hours()), int(dur.Minutes())%60, s.StartTime, s.Timezone, s.Host, s.Name,
	)
}

var (
	latestInfo Wrapper

	bugTime = flag.Int("pvfm-schedule-poke-delay", 15, "how stale the info can get")
)

// Wrapper is a time, info pair. This is used to invalidate the cache of
// data from ponyvillefm.com.
type Wrapper struct {
	Age  time.Time
	Info *ScheduleResult
}

// Get returns schedule entries, only fetching new data at most every n
// seconds, where n is defined above.
func Get() ([]ScheduleEntry, error) {
	now := time.Now()

	if now.Before(latestInfo.Age.Add(time.Second * time.Duration(*bugTime))) {
		return latestInfo.Info.Result, nil
	}

	s := &ScheduleResult{}
	c := http.Client{
		Timeout: time.Duration(time.Second * 15),
	}

	resp, err := c.Get("http://ponyvillefm.com/data/schedule")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	err = json.Unmarshal(content, s)
	if err != nil {
		return nil, err
	}

	// Update the age/contents of the latestInfo
	latestInfo.Info = s
	latestInfo.Age = now

	return s.Result, nil
}
<|endoftext|>


<commit_before>package jsre

const pp_js = `
function pp(object, indent) {
    try {
        JSON.stringify(object)
    } catch(e) {
        return pp(e, indent);
    }

    var str = "";
    if(object instanceof Array) {
        str += "[";
        for(var i = 0, l = object.length; i < l; i++) {
            str += pp(object[i], indent);

            if(i < l-1) {
                str += ", ";
            }
        }
        str += " ]";
    } else if (object instanceof Error) {
        str += "\033[31m" + "Error:\033[0m " + object.message;
    } else if (isBigNumber(object)) {
        str += "\033[32m'" + object.toString(10) + "'";
    } else if(typeof(object) === "object") {
        str += "{\n";
        indent += "  ";

        var fields = getFields(object);
        var last   = fields[fields.length - 1];
        fields.forEach(function (key) {
            str += indent + key + ": ";
            try {
                str += pp(object[key], indent);
            } catch (e) {
                str += pp(e, indent);
            }
            if(key !== last) {
                str += ",";
            }
            str += "\n";
        });
        str += indent.substr(2, indent.length) + "}";
    } else if(typeof(object) === "string") {
        str += "\033[32m'" + object + "'";
    } else if(typeof(object) === "undefined") {
        str += "\033[1m\033[30m" + object;
    } else if(typeof(object) === "number") {
        str += "\033[31m" + object;
    } else if(typeof(object) === "function") {
        str += "\033[35m[Function]";
    } else {
        str += object;
    }

    str += "\033[0m";

    return str;
}

var redundantFields = [
    'valueOf',
    'toString',
    'toLocaleString',
    'hasOwnProperty',
    'isPrototypeOf',
    'propertyIsEnumerable',
    'constructor'
];

var getFields = function (object) {
    var result = Object.getOwnPropertyNames(object);
    if (object.constructor && object.constructor.prototype) {
        result = result.concat(Object.getOwnPropertyNames(object.constructor.prototype));
    }
    return result.filter(function (field) {
        return redundantFields.indexOf(field) === -1;
    });
};

var isBigNumber = function (object) {
    return typeof BigNumber !== 'undefined' && object instanceof BigNumber;
};

function prettyPrint(/* */) {
    var args = arguments;
    var ret = "";
    for(var i = 0, l = args.length; i < l; i++) {
	    ret += pp(args[i], "") + "\n";
    }
    return ret;
}

var print = prettyPrint;
`
<commit_msg>jsre: sort pretty print output, fields before funcs<commit_after>package jsre

const pp_js = `
function pp(object, indent) {
    try {
        JSON.stringify(object)
    } catch(e) {
        return pp(e, indent);
    }

    var str = "";
    if(object instanceof Array) {
        str += "[";
        for(var i = 0, l = object.length; i < l; i++) {
            str += pp(object[i], indent);

            if(i < l-1) {
                str += ", ";
            }
        }
        str += " ]";
    } else if (object instanceof Error) {
        str += "\033[31m" + "Error:\033[0m " + object.message;
    } else if (isBigNumber(object)) {
        str += "\033[32m'" + object.toString(10) + "'";
    } else if(typeof(object) === "object") {
        str += "{\n";
        indent += "  ";

        var fields = getFields(object);
        var last   = fields[fields.length - 1];
        fields.forEach(function (key) {
            str += indent + key + ": ";
            try {
                str += pp(object[key], indent);
            } catch (e) {
                str += pp(e, indent);
            }
            if(key !== last) {
                str += ",";
            }
            str += "\n";
        });
        str += indent.substr(2, indent.length) + "}";
    } else if(typeof(object) === "string") {
        str += "\033[32m'" + object + "'";
    } else if(typeof(object) === "undefined") {
        str += "\033[1m\033[30m" + object;
    } else if(typeof(object) === "number") {
        str += "\033[31m" + object;
    } else if(typeof(object) === "function") {
        str += "\033[35m[Function]";
    } else {
        str += object;
    }

    str += "\033[0m";

    return str;
}

var redundantFields = [
    'valueOf',
    'toString',
    'toLocaleString',
    'hasOwnProperty',
    'isPrototypeOf',
    'propertyIsEnumerable',
    'constructor'
];

var getFields = function (object) {
    var members = Object.getOwnPropertyNames(object);
    if (object.constructor && object.constructor.prototype) {
        members = members.concat(Object.getOwnPropertyNames(object.constructor.prototype));
    }

    var fields = members.filter(function (member) {
        return !isMemberFunction(object, member)
    }).sort()
    var funcs = members.filter(function (member) {
        return isMemberFunction(object, member)
    }).sort()

    var results = fields.concat(funcs);
    return results.filter(function (field) {
        return redundantFields.indexOf(field) === -1;
    });
};

var isMemberFunction = function(object, member) {
    try {
        return typeof(object[member]) === "function";
    } catch(e) {
        return false;
    }
}

var isBigNumber = function (object) {
    return typeof BigNumber !== 'undefined' && object instanceof BigNumber;
};

function prettyPrint(/* */) {
    var args = arguments;
    var ret = "";
    for(var i = 0, l = args.length; i < l; i++) {
	    ret += pp(args[i], "") + "\n";
    }
    return ret;
}

var print = prettyPrint;
`
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package skaffold

import (
	"bytes"
	"context"
	"io"
	"os"
	"os/exec"
	"strings"
	"testing"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

	"github.com/GoogleContainerTools/skaffold/cmd/skaffold/app/cmd"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
)

// RunBuilder is used to build a command line to run `skaffold`.
type RunBuilder struct {
	command    string
	configFile string
	dir        string
	ns         string
	repo       string
	args       []string
	env        []string
	stdin      []byte
}

// Dev runs `skaffold dev` with the given arguments.
func Dev(args ...string) *RunBuilder {
	return withDefaults("dev", args)
}

// Fix runs `skaffold fix` with the given arguments.
func Fix(args ...string) *RunBuilder {
	return withDefaults("fix", args)
}

// Build runs `skaffold build` with the given arguments.
func Build(args ...string) *RunBuilder {
	return withDefaults("build", args)
}

// Deploy runs `skaffold deploy` with the given arguments.
func Deploy(args ...string) *RunBuilder {
	return withDefaults("deploy", args)
}

// Debug runs `skaffold debug` with the given arguments.
func Debug(args ...string) *RunBuilder {
	return withDefaults("debug", args)
}

// Run runs `skaffold run` with the given arguments.
func Run(args ...string) *RunBuilder {
	return withDefaults("run", args)
}

// Delete runs `skaffold delete` with the given arguments.
func Delete(args ...string) *RunBuilder {
	return withDefaults("delete", args)
}

// Config runs `skaffold config` with the given arguments.
func Config(args ...string) *RunBuilder {
	return withDefaults("config", args)
}

// Init runs `skaffold init` with the given arguments.
func Init(args ...string) *RunBuilder {
	return withDefaults("init", args)
}

// Diagnose runs `skaffold diagnose` with the given arguments.
func Diagnose(args ...string) *RunBuilder {
	return withDefaults("diagnose", args)
}

// Schema runs `skaffold schema` with the given arguments.
func Schema(args ...string) *RunBuilder {
	return &RunBuilder{command: "schema", args: args}
}

// Credits runs `skaffold credits` with the given arguments.
func Credits(args ...string) *RunBuilder {
	return &RunBuilder{command: "credits", args: args}
}

func GeneratePipeline(args ...string) *RunBuilder {
	return withDefaults("generate-pipeline", args)
}

func withDefaults(command string, args []string) *RunBuilder {
	return &RunBuilder{command: command, args: args, repo: "gcr.io/k8s-skaffold"}
}

// InDir sets the directory in which skaffold is running.
func (b *RunBuilder) InDir(dir string) *RunBuilder {
	b.dir = dir
	return b
}

// WithConfig sets the config file to be used by skaffold.
func (b *RunBuilder) WithConfig(configFile string) *RunBuilder {
	b.configFile = configFile
	return b
}

// WithRepo sets the default repository to be used by skaffold.
func (b *RunBuilder) WithRepo(repo string) *RunBuilder {
	b.repo = repo
	return b
}

// WithStdin sets the stdin.
func (b *RunBuilder) WithStdin(input []byte) *RunBuilder {
	b.stdin = input
	return b
}

// InNs sets the Kubernetes namespace in which skaffold deploys.
func (b *RunBuilder) InNs(ns string) *RunBuilder {
	b.ns = ns
	return b
}

// WithEnv sets environment variables.
func (b *RunBuilder) WithEnv(env []string) *RunBuilder {
	b.env = env
	return b
}

// RunBackground runs the skaffold command in the background.
// Returns a teardown function that stops skaffold.
func (b *RunBuilder) RunBackground(t *testing.T) context.CancelFunc {
	_, cancel := b.RunBackgroundOutput(t)
	return cancel
}

// RunBackgroundOutput runs the skaffold command in the background.
// Returns a teardown function that stops skaffold.
func (b *RunBuilder) RunBackgroundOutput(t *testing.T) (io.ReadCloser, context.CancelFunc) {
	t.Helper()

	pr, pw := io.Pipe()

	ctx, cancel := context.WithCancel(context.Background())
	cmd := b.cmd(ctx)
	cmd.Stdout = pw
	logrus.Infoln(cmd.Args)

	start := time.Now()
	if err := cmd.Start(); err != nil {
		t.Fatalf("skaffold %s: %v", b.command, err)
	}

	go func() {
		cmd.Wait()
		logrus.Infoln("Ran in", time.Since(start))
	}()

	return pr, func() {
		cancel()
		cmd.Wait()
		pr.Close()
	}
}

// RunOrFail runs the skaffold command and fails the test
// if the command returns an error.
func (b *RunBuilder) RunOrFail(t *testing.T) {
	b.RunOrFailOutput(t)
}

// Run runs the skaffold command.
func (b *RunBuilder) Run(t *testing.T) error {
	t.Helper()

	cmd := b.cmd(context.Background())
	logrus.Infoln(cmd.Args)

	start := time.Now()
	if err := cmd.Run(); err != nil {
		return errors.Wrapf(err, "skaffold %s", b.command)
	}

	logrus.Infoln("Ran in", time.Since(start))
	return nil
}

// RunWithCombinedOutput runs the skaffold command and returns the combined standard output and error.
func (b *RunBuilder) RunWithCombinedOutput(t *testing.T) ([]byte, error) {
	t.Helper()

	cmd := b.cmd(context.Background())
	cmd.Stdout, cmd.Stderr = nil, nil
	logrus.Infoln(cmd.Args)

	start := time.Now()
	out, err := cmd.CombinedOutput()
	if err != nil {
		return out, errors.Wrapf(err, "skaffold %s", b.command)
	}

	logrus.Infoln("Ran in", time.Since(start))
	return out, nil
}

// RunOrFailOutput runs the skaffold command and fails the test
// if the command returns an error.
// It only returns the standard output.
func (b *RunBuilder) RunOrFailOutput(t *testing.T) []byte {
	t.Helper()

	cmd := b.cmd(context.Background())
	cmd.Stdout, cmd.Stderr = nil, nil
	logrus.Infoln(cmd.Args)

	start := time.Now()
	out, err := cmd.Output()
	if err != nil {
		if ee, ok := err.(*exec.ExitError); ok {
			defer t.Errorf(string(ee.Stderr))
		}
		t.Fatalf("skaffold %s: %v, %s", b.command, err, out)
	}

	logrus.Infoln("Ran in", time.Since(start))
	return out
}

func (b *RunBuilder) cmd(ctx context.Context) *exec.Cmd {
	args := []string{b.command}
	command := b.getCobraCommand()

	if b.ns != "" && command.Flags().Lookup("namespace") != nil {
		args = append(args, "--namespace", b.ns)
	}
	if b.configFile != "" && command.Flags().ShorthandLookup("f") != nil {
		args = append(args, "-f", b.configFile)
	}
	if b.repo != "" && command.Flags().Lookup("default-repo") != nil {
		args = append(args, "--default-repo", b.repo)
	}
	args = append(args, b.args...)

	cmd := exec.CommandContext(ctx, "skaffold", args...)
	cmd.Env = append(removeSkaffoldEnvVariables(util.OSEnviron()), b.env...)
	if b.stdin != nil {
		cmd.Stdin = bytes.NewReader(b.stdin)
	}
	if b.dir != "" {
		cmd.Dir = b.dir
	}

	// If the test is killed by a timeout, go test will wait for
	// os.Stderr and os.Stdout to close as a result.
	//
	// However, the `cmd` will still run in the background
	// and hold those descriptors open.
	// As a result, go test will hang forever.
	//
	// Avoid that by wrapping stderr and stdout, breaking the short
	// circuit and forcing cmd.Run to use another pipe and goroutine
	// to pass along stderr and stdout.
	// See https://github.com/golang/go/issues/23019
	cmd.Stdout = struct{ io.Writer }{os.Stdout}
	cmd.Stderr = struct{ io.Writer }{os.Stderr}

	return cmd
}

// getCobraCommand returns the matching cobra command for the command
// in b, or return a dummy without flags
func (b *RunBuilder) getCobraCommand() *cobra.Command {
	c := cmd.NewSkaffoldCommand(os.Stdout, os.Stderr)
	for _, comm := range c.Commands() {
		if comm.Name() == b.command {
			return comm
		}
	}
	return &cobra.Command{}
}

// removeSkaffoldEnvVariables makes sure Skaffold runs without
// any env variable that might change its behavior, such as
// enabling caching.
func removeSkaffoldEnvVariables(env []string) []string {
	var clean []string

	for _, value := range env {
		if !strings.HasPrefix(value, "SKAFFOLD_") {
			clean = append(clean, value)
		}
	}

	// Disable update check
	return append(clean, "SKAFFOLD_UPDATE_CHECK=false")
}
<commit_msg>Add profile option to RunBuilder in test helper (#3761)<commit_after>/*
Copyright 2019 The Skaffold Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package skaffold

import (
	"bytes"
	"context"
	"io"
	"os"
	"os/exec"
	"strings"
	"testing"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"

	"github.com/GoogleContainerTools/skaffold/cmd/skaffold/app/cmd"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
)

// RunBuilder is used to build a command line to run `skaffold`.
type RunBuilder struct {
	command    string
	configFile string
	dir        string
	ns         string
	repo       string
	profiles   []string
	args       []string
	env        []string
	stdin      []byte
}

// Dev runs `skaffold dev` with the given arguments.
func Dev(args ...string) *RunBuilder {
	return withDefaults("dev", args)
}

// Fix runs `skaffold fix` with the given arguments.
func Fix(args ...string) *RunBuilder {
	return withDefaults("fix", args)
}

// Build runs `skaffold build` with the given arguments.
func Build(args ...string) *RunBuilder {
	return withDefaults("build", args)
}

// Deploy runs `skaffold deploy` with the given arguments.
func Deploy(args ...string) *RunBuilder {
	return withDefaults("deploy", args)
}

// Debug runs `skaffold debug` with the given arguments.
func Debug(args ...string) *RunBuilder {
	return withDefaults("debug", args)
}

// Run runs `skaffold run` with the given arguments.
func Run(args ...string) *RunBuilder {
	return withDefaults("run", args)
}

// Delete runs `skaffold delete` with the given arguments.
func Delete(args ...string) *RunBuilder {
	return withDefaults("delete", args)
}

// Config runs `skaffold config` with the given arguments.
func Config(args ...string) *RunBuilder {
	return withDefaults("config", args)
}

// Init runs `skaffold init` with the given arguments.
func Init(args ...string) *RunBuilder {
	return withDefaults("init", args)
}

// Diagnose runs `skaffold diagnose` with the given arguments.
func Diagnose(args ...string) *RunBuilder {
	return withDefaults("diagnose", args)
}

// Schema runs `skaffold schema` with the given arguments.
func Schema(args ...string) *RunBuilder {
	return &RunBuilder{command: "schema", args: args}
}

// Credits runs `skaffold credits` with the given arguments.
func Credits(args ...string) *RunBuilder {
	return &RunBuilder{command: "credits", args: args}
}

func GeneratePipeline(args ...string) *RunBuilder {
	return withDefaults("generate-pipeline", args)
}

func withDefaults(command string, args []string) *RunBuilder {
	return &RunBuilder{command: command, args: args, repo: "gcr.io/k8s-skaffold"}
}

// InDir sets the directory in which skaffold is running.
func (b *RunBuilder) InDir(dir string) *RunBuilder {
	b.dir = dir
	return b
}

// WithConfig sets the config file to be used by skaffold.
func (b *RunBuilder) WithConfig(configFile string) *RunBuilder {
	b.configFile = configFile
	return b
}

// WithRepo sets the default repository to be used by skaffold.
func (b *RunBuilder) WithRepo(repo string) *RunBuilder {
	b.repo = repo
	return b
}

// WithStdin sets the stdin.
func (b *RunBuilder) WithStdin(input []byte) *RunBuilder {
	b.stdin = input
	return b
}

// InNs sets the Kubernetes namespace in which skaffold deploys.
func (b *RunBuilder) InNs(ns string) *RunBuilder {
	b.ns = ns
	return b
}

// WithEnv sets environment variables.
func (b *RunBuilder) WithEnv(env []string) *RunBuilder {
	b.env = env
	return b
}

// WithProfiles sets profiles.
func (b *RunBuilder) WithProfiles(profiles []string) *RunBuilder {
	b.profiles = profiles
	return b
}

// RunBackground runs the skaffold command in the background.
// Returns a teardown function that stops skaffold.
func (b *RunBuilder) RunBackground(t *testing.T) context.CancelFunc {
	_, cancel := b.RunBackgroundOutput(t)
	return cancel
}

// RunBackgroundOutput runs the skaffold command in the background.
// Returns a teardown function that stops skaffold.
func (b *RunBuilder) RunBackgroundOutput(t *testing.T) (io.ReadCloser, context.CancelFunc) {
	t.Helper()

	pr, pw := io.Pipe()

	ctx, cancel := context.WithCancel(context.Background())
	cmd := b.cmd(ctx)
	cmd.Stdout = pw
	logrus.Infoln(cmd.Args)

	start := time.Now()
	if err := cmd.Start(); err != nil {
		t.Fatalf("skaffold %s: %v", b.command, err)
	}

	go func() {
		cmd.Wait()
		logrus.Infoln("Ran in", time.Since(start))
	}()

	return pr, func() {
		cancel()
		cmd.Wait()
		pr.Close()
	}
}

// RunOrFail runs the skaffold command and fails the test
// if the command returns an error.
func (b *RunBuilder) RunOrFail(t *testing.T) {
	b.RunOrFailOutput(t)
}

// Run runs the skaffold command.
func (b *RunBuilder) Run(t *testing.T) error {
	t.Helper()

	cmd := b.cmd(context.Background())
	logrus.Infoln(cmd.Args)

	start := time.Now()
	if err := cmd.Run(); err != nil {
		return errors.Wrapf(err, "skaffold %s", b.command)
	}

	logrus.Infoln("Ran in", time.Since(start))
	return nil
}

// RunWithCombinedOutput runs the skaffold command and returns the combined standard output and error.
func (b *RunBuilder) RunWithCombinedOutput(t *testing.T) ([]byte, error) {
	t.Helper()

	cmd := b.cmd(context.Background())
	cmd.Stdout, cmd.Stderr = nil, nil
	logrus.Infoln(cmd.Args)

	start := time.Now()
	out, err := cmd.CombinedOutput()
	if err != nil {
		return out, errors.Wrapf(err, "skaffold %s", b.command)
	}

	logrus.Infoln("Ran in", time.Since(start))
	return out, nil
}

// RunOrFailOutput runs the skaffold command and fails the test
// if the command returns an error.
// It only returns the standard output.
func (b *RunBuilder) RunOrFailOutput(t *testing.T) []byte {
	t.Helper()

	cmd := b.cmd(context.Background())
	cmd.Stdout, cmd.Stderr = nil, nil
	logrus.Infoln(cmd.Args)

	start := time.Now()
	out, err := cmd.Output()
	if err != nil {
		if ee, ok := err.(*exec.ExitError); ok {
			defer t.Errorf(string(ee.Stderr))
		}
		t.Fatalf("skaffold %s: %v, %s", b.command, err, out)
	}

	logrus.Infoln("Ran in", time.Since(start))
	return out
}

func (b *RunBuilder) cmd(ctx context.Context) *exec.Cmd {
	args := []string{b.command}
	command := b.getCobraCommand()

	if b.ns != "" && command.Flags().Lookup("namespace") != nil {
		args = append(args, "--namespace", b.ns)
	}
	if b.configFile != "" && command.Flags().ShorthandLookup("f") != nil {
		args = append(args, "-f", b.configFile)
	}
	if b.repo != "" && command.Flags().Lookup("default-repo") != nil {
		args = append(args, "--default-repo", b.repo)
	}
	if len(b.profiles) > 0 && command.Flags().Lookup("profile") != nil {
		args = append(args, "--profile", strings.Join(b.profiles, ","))
	}
	args = append(args, b.args...)

	cmd := exec.CommandContext(ctx, "skaffold", args...)
	cmd.Env = append(removeSkaffoldEnvVariables(util.OSEnviron()), b.env...)
	if b.stdin != nil {
		cmd.Stdin = bytes.NewReader(b.stdin)
	}
	if b.dir != "" {
		cmd.Dir = b.dir
	}

	// If the test is killed by a timeout, go test will wait for
	// os.Stderr and os.Stdout to close as a result.
	//
	// However, the `cmd` will still run in the background
	// and hold those descriptors open.
	// As a result, go test will hang forever.
	//
	// Avoid that by wrapping stderr and stdout, breaking the short
	// circuit and forcing cmd.Run to use another pipe and goroutine
	// to pass along stderr and stdout.
	// See https://github.com/golang/go/issues/23019
	cmd.Stdout = struct{ io.Writer }{os.Stdout}
	cmd.Stderr = struct{ io.Writer }{os.Stderr}

	return cmd
}

// getCobraCommand returns the matching cobra command for the command
// in b, or return a dummy without flags
func (b *RunBuilder) getCobraCommand() *cobra.Command {
	c := cmd.NewSkaffoldCommand(os.Stdout, os.Stderr)
	for _, comm := range c.Commands() {
		if comm.Name() == b.command {
			return comm
		}
	}
	return &cobra.Command{}
}

// removeSkaffoldEnvVariables makes sure Skaffold runs without
// any env variable that might change its behavior, such as
// enabling caching.
func removeSkaffoldEnvVariables(env []string) []string {
	var clean []string

	for _, value := range env {
		if !strings.HasPrefix(value, "SKAFFOLD_") {
			clean = append(clean, value)
		}
	}

	// Disable update check
	return append(clean, "SKAFFOLD_UPDATE_CHECK=false")
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 inode_test

import (
	"fmt"
	"io"
	"os"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/googlecloudplatform/gcsfuse/internal/fs/inode"
	"github.com/googlecloudplatform/gcsfuse/internal/gcsx"
	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcsfake"
	"github.com/jacobsa/gcloud/gcs/gcsutil"
	. "github.com/jacobsa/ogletest"
	"github.com/jacobsa/timeutil"
)

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

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

const uid = 123
const gid = 456

const fileInodeID = 17
const fileInodeName = "foo/bar"
const fileMode os.FileMode = 0641

type FileTest struct {
	ctx    context.Context
	bucket gcs.Bucket
	clock  timeutil.SimulatedClock

	initialContents string
	backingObj      *gcs.Object

	in *inode.FileInode
}

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

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

func (t *FileTest) SetUp(ti *TestInfo) {
	t.ctx = ti.Ctx
	t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local))
	t.bucket = gcsfake.NewFakeBucket(&t.clock, "some_bucket")

	// Set up the backing object.
	var err error

	t.initialContents = "taco"
	t.backingObj, err = gcsutil.CreateObject(
		t.ctx,
		t.bucket,
		fileInodeName,
		[]byte(t.initialContents))

	AssertEq(nil, err)

	// Create the inode.
	t.in = inode.NewFileInode(
		fileInodeID,
		t.backingObj,
		fuseops.InodeAttributes{
			Uid:  uid,
			Gid:  gid,
			Mode: fileMode,
		},
		t.bucket,
		gcsx.NewSyncer(
			1, // Append threshold
			".gcsfuse_tmp/",
			t.bucket),
		"",
		&t.clock)

	t.in.Lock()
}

func (t *FileTest) TearDown() {
	t.in.Unlock()
}

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

func (t *FileTest) ID() {
	ExpectEq(fileInodeID, t.in.ID())
}

func (t *FileTest) Name() {
	ExpectEq(fileInodeName, t.in.Name())
}

func (t *FileTest) InitialSourceGeneration() {
	ExpectEq(t.backingObj.Generation, t.in.SourceGeneration())
}

func (t *FileTest) InitialAttributes() {
	attrs, err := t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(len(t.initialContents), attrs.Size)
	ExpectEq(1, attrs.Nlink)
	ExpectEq(uid, attrs.Uid)
	ExpectEq(gid, attrs.Gid)
	ExpectEq(fileMode, attrs.Mode)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(t.backingObj.Updated))
}

func (t *FileTest) InitialAttributes_MtimeFromObjectMetadata() {
	AssertTrue(false, "TODO")
}

func (t *FileTest) Read() {
	AssertEq("taco", t.initialContents)

	// Make several reads, checking the expected contents.
	testCases := []struct {
		offset   int64
		size     int
		expected string
	}{
		{0, 1, "t"},
		{0, 2, "ta"},
		{0, 3, "tac"},
		{0, 4, "taco"},
		{0, 5, "taco"},

		{1, 1, "a"},
		{1, 2, "ac"},
		{1, 3, "aco"},
		{1, 4, "aco"},

		{3, 1, "o"},
		{3, 2, "o"},

		// Empty ranges
		{0, 0, ""},
		{3, 0, ""},
		{4, 0, ""},
		{4, 1, ""},
		{5, 0, ""},
		{5, 1, ""},
		{5, 2, ""},
	}

	for _, tc := range testCases {
		desc := fmt.Sprintf("offset: %d, size: %d", tc.offset, tc.size)

		data := make([]byte, tc.size)
		n, err := t.in.Read(t.ctx, data, tc.offset)
		data = data[:n]

		// Ignore EOF.
		if err == io.EOF {
			err = nil
		}

		AssertEq(nil, err, "%s", desc)
		ExpectEq(tc.expected, string(data), "%s", desc)
	}
}

func (t *FileTest) Write() {
	var err error

	AssertEq("taco", t.initialContents)

	// Overwite a byte.
	err = t.in.Write(t.ctx, []byte("p"), 0)
	AssertEq(nil, err)

	// Add some data at the end.
	t.clock.AdvanceTime(time.Second)
	writeTime := t.clock.Now()

	err = t.in.Write(t.ctx, []byte("burrito"), 4)
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Read back the content.
	var buf [1024]byte
	n, err := t.in.Read(t.ctx, buf[:], 0)

	if err == io.EOF {
		err = nil
	}

	AssertEq(nil, err)
	ExpectEq("pacoburrito", string(buf[:n]))

	// Check attributes.
	attrs, err := t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(len("pacoburrito"), attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(writeTime))
}

func (t *FileTest) Truncate() {
	var attrs fuseops.InodeAttributes
	var err error

	AssertEq("taco", t.initialContents)

	// Truncate downward.
	t.clock.AdvanceTime(time.Second)
	truncateTime := t.clock.Now()

	err = t.in.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Read the contents.
	var buf [1024]byte
	n, err := t.in.Read(t.ctx, buf[:], 0)

	if err == io.EOF {
		err = nil
	}

	AssertEq(nil, err)
	ExpectEq("ta", string(buf[:n]))

	// Check attributes.
	attrs, err = t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(len("ta"), attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(truncateTime))
}

func (t *FileTest) WriteThenSync() {
	var attrs fuseops.InodeAttributes
	var err error

	AssertEq("taco", t.initialContents)

	// Overwite a byte.
	t.clock.AdvanceTime(time.Second)
	writeTime := t.clock.Now()

	err = t.in.Write(t.ctx, []byte("p"), 0)
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Sync.
	err = t.in.Sync(t.ctx)
	AssertEq(nil, err)

	// The generation should have advanced.
	ExpectLt(t.backingObj.Generation, t.in.SourceGeneration())

	// Stat the current object in the bucket.
	statReq := &gcs.StatObjectRequest{Name: t.in.Name()}
	o, err := t.bucket.StatObject(t.ctx, statReq)

	AssertEq(nil, err)
	ExpectEq(t.in.SourceGeneration(), o.Generation)
	ExpectEq(len("paco"), o.Size)
	ExpectEq(
		writeTime.UTC().Format(time.RFC3339Nano),
		o.Metadata["gcsfuse_mtime"])

	// Read the object's contents.
	contents, err := gcsutil.ReadObject(t.ctx, t.bucket, t.in.Name())

	AssertEq(nil, err)
	ExpectEq("paco", string(contents))

	// Check attributes.
	attrs, err = t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(len("paco"), attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(writeTime))
}

func (t *FileTest) AppendThenSync() {
	var attrs fuseops.InodeAttributes
	var err error

	AssertEq("taco", t.initialContents)

	// Append some data.
	t.clock.AdvanceTime(time.Second)
	writeTime := t.clock.Now()

	err = t.in.Write(t.ctx, []byte("burrito"), int64(len("taco")))
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Sync.
	err = t.in.Sync(t.ctx)
	AssertEq(nil, err)

	// The generation should have advanced.
	ExpectLt(t.backingObj.Generation, t.in.SourceGeneration())

	// Stat the current object in the bucket.
	statReq := &gcs.StatObjectRequest{Name: t.in.Name()}
	o, err := t.bucket.StatObject(t.ctx, statReq)

	AssertEq(nil, err)
	ExpectEq(t.in.SourceGeneration(), o.Generation)
	ExpectEq(len("tacoburrito"), o.Size)
	ExpectEq(
		writeTime.UTC().Format(time.RFC3339Nano),
		o.Metadata["gcsfuse_mtime"])

	// Read the object's contents.
	contents, err := gcsutil.ReadObject(t.ctx, t.bucket, t.in.Name())

	AssertEq(nil, err)
	ExpectEq("tacoburrito", string(contents))

	// Check attributes.
	attrs, err = t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(len("tacoburrito"), attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(writeTime))
}

func (t *FileTest) TruncateDownwardThenSync() {
	var attrs fuseops.InodeAttributes
	var err error

	// Truncate downward.
	t.clock.AdvanceTime(time.Second)
	truncateTime := t.clock.Now()

	err = t.in.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Sync.
	err = t.in.Sync(t.ctx)
	AssertEq(nil, err)

	// The generation should have advanced.
	ExpectLt(t.backingObj.Generation, t.in.SourceGeneration())

	// Stat the current object in the bucket.
	statReq := &gcs.StatObjectRequest{Name: t.in.Name()}
	o, err := t.bucket.StatObject(t.ctx, statReq)

	AssertEq(nil, err)
	ExpectEq(t.in.SourceGeneration(), o.Generation)
	ExpectEq(2, o.Size)
	ExpectEq(
		truncateTime.UTC().Format(time.RFC3339Nano),
		o.Metadata["gcsfuse_mtime"])

	// Check attributes.
	attrs, err = t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(2, attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(truncateTime))
}

func (t *FileTest) TruncateUpwardThenSync() {
	var attrs fuseops.InodeAttributes
	var err error

	AssertEq(4, len(t.initialContents))

	// Truncate upward.
	t.clock.AdvanceTime(time.Second)
	truncateTime := t.clock.Now()

	err = t.in.Truncate(t.ctx, 6)
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Sync.
	err = t.in.Sync(t.ctx)
	AssertEq(nil, err)

	// The generation should have advanced.
	ExpectLt(t.backingObj.Generation, t.in.SourceGeneration())

	// Stat the current object in the bucket.
	statReq := &gcs.StatObjectRequest{Name: t.in.Name()}
	o, err := t.bucket.StatObject(t.ctx, statReq)
	ExpectEq(
		truncateTime.UTC().Format(time.RFC3339Nano),
		o.Metadata["gcsfuse_mtime"])

	AssertEq(nil, err)
	ExpectEq(t.in.SourceGeneration(), o.Generation)
	ExpectEq(6, o.Size)

	// Check attributes.
	attrs, err = t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(6, attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(truncateTime))
}

func (t *FileTest) Sync_Clobbered() {
	var err error

	// Truncate downward.
	err = t.in.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	// Clobber the backing object.
	newObj, err := gcsutil.CreateObject(
		t.ctx,
		t.bucket,
		t.in.Name(),
		[]byte("burrito"))

	AssertEq(nil, err)

	// Sync. The call should succeed, but nothing should change.
	err = t.in.Sync(t.ctx)

	AssertEq(nil, err)
	ExpectEq(t.backingObj.Generation, t.in.SourceGeneration())

	// The object in the bucket should not have been changed.
	statReq := &gcs.StatObjectRequest{Name: t.in.Name()}
	o, err := t.bucket.StatObject(t.ctx, statReq)

	AssertEq(nil, err)
	ExpectEq(newObj.Generation, o.Generation)
	ExpectEq(newObj.Size, o.Size)
}
<commit_msg>FileTest.InitialAttributes_MtimeFromObjectMetadata<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 inode_test

import (
	"fmt"
	"io"
	"os"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/googlecloudplatform/gcsfuse/internal/fs/inode"
	"github.com/googlecloudplatform/gcsfuse/internal/gcsx"
	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcsfake"
	"github.com/jacobsa/gcloud/gcs/gcsutil"
	. "github.com/jacobsa/ogletest"
	"github.com/jacobsa/timeutil"
)

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

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

const uid = 123
const gid = 456

const fileInodeID = 17
const fileInodeName = "foo/bar"
const fileMode os.FileMode = 0641

type FileTest struct {
	ctx    context.Context
	bucket gcs.Bucket
	clock  timeutil.SimulatedClock

	initialContents string
	backingObj      *gcs.Object

	in *inode.FileInode
}

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

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

func (t *FileTest) SetUp(ti *TestInfo) {
	t.ctx = ti.Ctx
	t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local))
	t.bucket = gcsfake.NewFakeBucket(&t.clock, "some_bucket")

	// Set up the backing object.
	var err error

	t.initialContents = "taco"
	t.backingObj, err = gcsutil.CreateObject(
		t.ctx,
		t.bucket,
		fileInodeName,
		[]byte(t.initialContents))

	AssertEq(nil, err)

	// Create the inode.
	t.createInode()
}

func (t *FileTest) TearDown() {
	t.in.Unlock()
}

func (t *FileTest) createInode() {
	if t.in != nil {
		t.in.Unlock()
	}

	t.in = inode.NewFileInode(
		fileInodeID,
		t.backingObj,
		fuseops.InodeAttributes{
			Uid:  uid,
			Gid:  gid,
			Mode: fileMode,
		},
		t.bucket,
		gcsx.NewSyncer(
			1, // Append threshold
			".gcsfuse_tmp/",
			t.bucket),
		"",
		&t.clock)

	t.in.Lock()
}

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

func (t *FileTest) ID() {
	ExpectEq(fileInodeID, t.in.ID())
}

func (t *FileTest) Name() {
	ExpectEq(fileInodeName, t.in.Name())
}

func (t *FileTest) InitialSourceGeneration() {
	ExpectEq(t.backingObj.Generation, t.in.SourceGeneration())
}

func (t *FileTest) InitialAttributes() {
	attrs, err := t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(len(t.initialContents), attrs.Size)
	ExpectEq(1, attrs.Nlink)
	ExpectEq(uid, attrs.Uid)
	ExpectEq(gid, attrs.Gid)
	ExpectEq(fileMode, attrs.Mode)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(t.backingObj.Updated))
}

func (t *FileTest) InitialAttributes_MtimeFromObjectMetadata() {
	// Set up an explicit mtime on the backing object and re-create the inode.
	if t.backingObj.Metadata == nil {
		t.backingObj.Metadata = make(map[string]string)
	}

	mtime := time.Now().Add(123 * time.Second).UTC()
	t.backingObj.Metadata["gcsfuse_mtime"] = mtime.Format(time.RFC3339Nano)

	t.createInode()

	// Ask it for its attributes.
	attrs, err := t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectThat(attrs.Mtime, timeutil.TimeEq(mtime))
}

func (t *FileTest) Read() {
	AssertEq("taco", t.initialContents)

	// Make several reads, checking the expected contents.
	testCases := []struct {
		offset   int64
		size     int
		expected string
	}{
		{0, 1, "t"},
		{0, 2, "ta"},
		{0, 3, "tac"},
		{0, 4, "taco"},
		{0, 5, "taco"},

		{1, 1, "a"},
		{1, 2, "ac"},
		{1, 3, "aco"},
		{1, 4, "aco"},

		{3, 1, "o"},
		{3, 2, "o"},

		// Empty ranges
		{0, 0, ""},
		{3, 0, ""},
		{4, 0, ""},
		{4, 1, ""},
		{5, 0, ""},
		{5, 1, ""},
		{5, 2, ""},
	}

	for _, tc := range testCases {
		desc := fmt.Sprintf("offset: %d, size: %d", tc.offset, tc.size)

		data := make([]byte, tc.size)
		n, err := t.in.Read(t.ctx, data, tc.offset)
		data = data[:n]

		// Ignore EOF.
		if err == io.EOF {
			err = nil
		}

		AssertEq(nil, err, "%s", desc)
		ExpectEq(tc.expected, string(data), "%s", desc)
	}
}

func (t *FileTest) Write() {
	var err error

	AssertEq("taco", t.initialContents)

	// Overwite a byte.
	err = t.in.Write(t.ctx, []byte("p"), 0)
	AssertEq(nil, err)

	// Add some data at the end.
	t.clock.AdvanceTime(time.Second)
	writeTime := t.clock.Now()

	err = t.in.Write(t.ctx, []byte("burrito"), 4)
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Read back the content.
	var buf [1024]byte
	n, err := t.in.Read(t.ctx, buf[:], 0)

	if err == io.EOF {
		err = nil
	}

	AssertEq(nil, err)
	ExpectEq("pacoburrito", string(buf[:n]))

	// Check attributes.
	attrs, err := t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(len("pacoburrito"), attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(writeTime))
}

func (t *FileTest) Truncate() {
	var attrs fuseops.InodeAttributes
	var err error

	AssertEq("taco", t.initialContents)

	// Truncate downward.
	t.clock.AdvanceTime(time.Second)
	truncateTime := t.clock.Now()

	err = t.in.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Read the contents.
	var buf [1024]byte
	n, err := t.in.Read(t.ctx, buf[:], 0)

	if err == io.EOF {
		err = nil
	}

	AssertEq(nil, err)
	ExpectEq("ta", string(buf[:n]))

	// Check attributes.
	attrs, err = t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(len("ta"), attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(truncateTime))
}

func (t *FileTest) WriteThenSync() {
	var attrs fuseops.InodeAttributes
	var err error

	AssertEq("taco", t.initialContents)

	// Overwite a byte.
	t.clock.AdvanceTime(time.Second)
	writeTime := t.clock.Now()

	err = t.in.Write(t.ctx, []byte("p"), 0)
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Sync.
	err = t.in.Sync(t.ctx)
	AssertEq(nil, err)

	// The generation should have advanced.
	ExpectLt(t.backingObj.Generation, t.in.SourceGeneration())

	// Stat the current object in the bucket.
	statReq := &gcs.StatObjectRequest{Name: t.in.Name()}
	o, err := t.bucket.StatObject(t.ctx, statReq)

	AssertEq(nil, err)
	ExpectEq(t.in.SourceGeneration(), o.Generation)
	ExpectEq(len("paco"), o.Size)
	ExpectEq(
		writeTime.UTC().Format(time.RFC3339Nano),
		o.Metadata["gcsfuse_mtime"])

	// Read the object's contents.
	contents, err := gcsutil.ReadObject(t.ctx, t.bucket, t.in.Name())

	AssertEq(nil, err)
	ExpectEq("paco", string(contents))

	// Check attributes.
	attrs, err = t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(len("paco"), attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(writeTime))
}

func (t *FileTest) AppendThenSync() {
	var attrs fuseops.InodeAttributes
	var err error

	AssertEq("taco", t.initialContents)

	// Append some data.
	t.clock.AdvanceTime(time.Second)
	writeTime := t.clock.Now()

	err = t.in.Write(t.ctx, []byte("burrito"), int64(len("taco")))
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Sync.
	err = t.in.Sync(t.ctx)
	AssertEq(nil, err)

	// The generation should have advanced.
	ExpectLt(t.backingObj.Generation, t.in.SourceGeneration())

	// Stat the current object in the bucket.
	statReq := &gcs.StatObjectRequest{Name: t.in.Name()}
	o, err := t.bucket.StatObject(t.ctx, statReq)

	AssertEq(nil, err)
	ExpectEq(t.in.SourceGeneration(), o.Generation)
	ExpectEq(len("tacoburrito"), o.Size)
	ExpectEq(
		writeTime.UTC().Format(time.RFC3339Nano),
		o.Metadata["gcsfuse_mtime"])

	// Read the object's contents.
	contents, err := gcsutil.ReadObject(t.ctx, t.bucket, t.in.Name())

	AssertEq(nil, err)
	ExpectEq("tacoburrito", string(contents))

	// Check attributes.
	attrs, err = t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(len("tacoburrito"), attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(writeTime))
}

func (t *FileTest) TruncateDownwardThenSync() {
	var attrs fuseops.InodeAttributes
	var err error

	// Truncate downward.
	t.clock.AdvanceTime(time.Second)
	truncateTime := t.clock.Now()

	err = t.in.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Sync.
	err = t.in.Sync(t.ctx)
	AssertEq(nil, err)

	// The generation should have advanced.
	ExpectLt(t.backingObj.Generation, t.in.SourceGeneration())

	// Stat the current object in the bucket.
	statReq := &gcs.StatObjectRequest{Name: t.in.Name()}
	o, err := t.bucket.StatObject(t.ctx, statReq)

	AssertEq(nil, err)
	ExpectEq(t.in.SourceGeneration(), o.Generation)
	ExpectEq(2, o.Size)
	ExpectEq(
		truncateTime.UTC().Format(time.RFC3339Nano),
		o.Metadata["gcsfuse_mtime"])

	// Check attributes.
	attrs, err = t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(2, attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(truncateTime))
}

func (t *FileTest) TruncateUpwardThenSync() {
	var attrs fuseops.InodeAttributes
	var err error

	AssertEq(4, len(t.initialContents))

	// Truncate upward.
	t.clock.AdvanceTime(time.Second)
	truncateTime := t.clock.Now()

	err = t.in.Truncate(t.ctx, 6)
	AssertEq(nil, err)

	t.clock.AdvanceTime(time.Second)

	// Sync.
	err = t.in.Sync(t.ctx)
	AssertEq(nil, err)

	// The generation should have advanced.
	ExpectLt(t.backingObj.Generation, t.in.SourceGeneration())

	// Stat the current object in the bucket.
	statReq := &gcs.StatObjectRequest{Name: t.in.Name()}
	o, err := t.bucket.StatObject(t.ctx, statReq)
	ExpectEq(
		truncateTime.UTC().Format(time.RFC3339Nano),
		o.Metadata["gcsfuse_mtime"])

	AssertEq(nil, err)
	ExpectEq(t.in.SourceGeneration(), o.Generation)
	ExpectEq(6, o.Size)

	// Check attributes.
	attrs, err = t.in.Attributes(t.ctx)
	AssertEq(nil, err)

	ExpectEq(6, attrs.Size)
	ExpectThat(attrs.Mtime, timeutil.TimeEq(truncateTime))
}

func (t *FileTest) Sync_Clobbered() {
	var err error

	// Truncate downward.
	err = t.in.Truncate(t.ctx, 2)
	AssertEq(nil, err)

	// Clobber the backing object.
	newObj, err := gcsutil.CreateObject(
		t.ctx,
		t.bucket,
		t.in.Name(),
		[]byte("burrito"))

	AssertEq(nil, err)

	// Sync. The call should succeed, but nothing should change.
	err = t.in.Sync(t.ctx)

	AssertEq(nil, err)
	ExpectEq(t.backingObj.Generation, t.in.SourceGeneration())

	// The object in the bucket should not have been changed.
	statReq := &gcs.StatObjectRequest{Name: t.in.Name()}
	o, err := t.bucket.StatObject(t.ctx, statReq)

	AssertEq(nil, err)
	ExpectEq(newObj.Generation, o.Generation)
	ExpectEq(newObj.Size, o.Size)
}
<|endoftext|>


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

// +build android ios

package mobile

import (
	"context"
	"fmt"
	"runtime"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"unicode"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/internal/devicescale"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl"
	"github.com/hajimehoshi/ebiten/internal/hooks"
	"github.com/hajimehoshi/ebiten/internal/restorable"
	"github.com/hajimehoshi/ebiten/internal/thread"
)

var (
	glContextCh = make(chan gl.Context, 1)

	// renderCh receives when updating starts.
	renderCh = make(chan struct{})

	// renderEndCh receives when updating finishes.
	renderEndCh = make(chan struct{})

	theUI = &UserInterface{
		foreground: 1,
		errCh:      make(chan error),

		// Give a default outside size so that the game can start without initializing them.
		outsideWidth:  640,
		outsideHeight: 480,
		sizeChanged:   true,
	}
)

func init() {
	theUI.input.ui = theUI
}

func Get() *UserInterface {
	return theUI
}

// Update is called from mobile/ebitenmobileview.
//
// Update must be called on the rendering thread.
func (u *UserInterface) Update() error {
	select {
	case err := <-u.errCh:
		return err
	default:
	}

	if !u.IsFocused() {
		return nil
	}

	renderCh <- struct{}{}
	if u.Graphics().IsGL() {
		if u.glWorker == nil {
			panic("mobile: glWorker must be initialized but not")
		}

		ctx, cancel := context.WithCancel(context.Background())
		go func() {
			<-renderEndCh
			cancel()
		}()

		workAvailable := u.glWorker.WorkAvailable()
	loop:
		for {
			select {
			case <-workAvailable:
				u.glWorker.DoWork()

				// This is a dirty hack to avoid freezing on Pixel 4 (#1322).
				// Apprently there is an issue in the usage of Worker in gomobile or gomobile itself.
				// At least, freezing doesn't happen with this Gosched.
				runtime.Gosched()
			case <-ctx.Done():
				break loop
			}
		}
		return nil
	}

	go func() {
		<-renderEndCh
		u.t.Call(func() error {
			return thread.BreakLoop
		})
	}()
	u.t.Loop()
	return nil
}

type UserInterface struct {
	outsideWidth  float64
	outsideHeight float64

	sizeChanged bool
	foreground  int32
	errCh       chan error

	// Used for gomobile-build
	gbuildWidthPx   int
	gbuildHeightPx  int
	setGBuildSizeCh chan struct{}
	once            sync.Once

	context driver.UIContext

	input Input

	t        *thread.Thread
	glWorker gl.Worker

	m sync.RWMutex
}

func deviceScale() float64 {
	return devicescale.GetAt(0, 0)
}

// appMain is the main routine for gomobile-build mode.
func (u *UserInterface) appMain(a app.App) {
	var glctx gl.Context
	var sizeInited bool

	touches := map[touch.Sequence]*Touch{}
	keys := map[driver.Key]struct{}{}

	for e := range a.Events() {
		var updateInput bool
		var runes []rune

		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				u.SetForeground(true)
				restorable.OnContextLost()
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				u.SetForeground(false)
				glctx = nil
			}
		case size.Event:
			u.setGBuildSize(e.WidthPx, e.HeightPx)
			sizeInited = true
		case paint.Event:
			if !sizeInited {
				a.Send(paint.Event{})
				continue
			}
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderEndCh
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			if !sizeInited {
				continue
			}
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := deviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				touches[e.Sequence] = &Touch{
					ID: int(e.Sequence),
					X:  int(x),
					Y:  int(y),
				}
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			updateInput = true
		case key.Event:
			k, ok := gbuildKeyToDriverKey[e.Code]
			if ok {
				switch e.Direction {
				case key.DirPress, key.DirNone:
					keys[k] = struct{}{}
				case key.DirRelease:
					delete(keys, k)
				}
			}

			switch e.Direction {
			case key.DirPress, key.DirNone:
				if e.Rune != -1 && unicode.IsPrint(e.Rune) {
					runes = []rune{e.Rune}
				}
			}
			updateInput = true
		}

		if updateInput {
			ts := []*Touch{}
			for _, t := range touches {
				ts = append(ts, t)
			}
			u.input.update(keys, runes, ts, nil)
		}
	}
}

func (u *UserInterface) SetForeground(foreground bool) {
	var v int32
	if foreground {
		v = 1
	}
	atomic.StoreInt32(&u.foreground, v)

	if foreground {
		hooks.ResumeAudio()
	} else {
		hooks.SuspendAudio()
	}
}

func (u *UserInterface) Run(context driver.UIContext) error {
	u.setGBuildSizeCh = make(chan struct{})
	go func() {
		if err := u.run(context, true); err != nil {
			// As mobile apps never ends, Loop can't return. Just panic here.
			panic(err)
		}
	}()
	app.Main(u.appMain)
	return nil
}

func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) {
	go func() {
		// title is ignored?
		if err := u.run(context, false); err != nil {
			u.errCh <- err
		}
	}()
}

func (u *UserInterface) run(context driver.UIContext, mainloop bool) (err error) {
	// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
	// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
	// unfortunately.
	// TODO: Panic on other goroutines cannot be handled here.
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v\n%q", r, string(debug.Stack()))
		}
	}()

	u.m.Lock()
	u.sizeChanged = true
	u.m.Unlock()

	u.context = context

	if u.Graphics().IsGL() {
		var ctx gl.Context
		if mainloop {
			ctx = <-glContextCh
		} else {
			ctx, u.glWorker = gl.NewContext()
		}
		u.Graphics().(*opengl.Graphics).SetMobileGLContext(ctx)
	} else {
		u.t = thread.New()
		u.Graphics().SetThread(u.t)
	}

	// If gomobile-build is used, wait for the outside size fixed.
	if u.setGBuildSizeCh != nil {
		<-u.setGBuildSizeCh
	}

	// Force to set the screen size
	u.layoutIfNeeded()
	for {
		if err := u.update(); err != nil {
			return err
		}
	}
}

// layoutIfNeeded must be called on the same goroutine as update().
func (u *UserInterface) layoutIfNeeded() {
	var outsideWidth, outsideHeight float64

	u.m.RLock()
	sizeChanged := u.sizeChanged
	if sizeChanged {
		if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
			outsideWidth = u.outsideWidth
			outsideHeight = u.outsideHeight
		} else {
			// gomobile build
			d := deviceScale()
			outsideWidth = float64(u.gbuildWidthPx) / d
			outsideHeight = float64(u.gbuildHeightPx) / d
		}
	}
	u.sizeChanged = false
	u.m.RUnlock()

	if sizeChanged {
		u.context.Layout(outsideWidth, outsideHeight)
	}
}

func (u *UserInterface) update() error {
	<-renderCh
	defer func() {
		renderEndCh <- struct{}{}
	}()

	if err := u.context.Update(); err != nil {
		return err
	}
	if err := u.context.Draw(); err != nil {
		return err
	}
	return nil
}

func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
	// TODO: This function should return gbuildWidthPx, gbuildHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

// SetOutsideSize is called from mobile/ebitenmobileview.
//
// SetOutsideSize is concurrent safe.
func (u *UserInterface) SetOutsideSize(outsideWidth, outsideHeight float64) {
	u.m.Lock()
	if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
		u.outsideWidth = outsideWidth
		u.outsideHeight = outsideHeight
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func (u *UserInterface) setGBuildSize(widthPx, heightPx int) {
	u.m.Lock()
	u.gbuildWidthPx = widthPx
	u.gbuildHeightPx = heightPx
	u.sizeChanged = true
	u.m.Unlock()

	u.once.Do(func() {
		close(u.setGBuildSizeCh)
	})
}

func (u *UserInterface) adjustPosition(x, y int) (int, int) {
	xf, yf := u.context.AdjustPosition(float64(x), float64(y))
	return int(xf), int(yf)
}

func (u *UserInterface) CursorMode() driver.CursorMode {
	return driver.CursorModeHidden
}

func (u *UserInterface) SetCursorMode(mode driver.CursorMode) {
	// Do nothing
}

func (u *UserInterface) IsFullscreen() bool {
	return false
}

func (u *UserInterface) SetFullscreen(fullscreen bool) {
	// Do nothing
}

func (u *UserInterface) IsFocused() bool {
	return atomic.LoadInt32(&u.foreground) != 0
}

func (u *UserInterface) IsRunnableOnUnfocused() bool {
	return false
}

func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
	// Do nothing
}

func (u *UserInterface) IsVsyncEnabled() bool {
	return true
}

func (u *UserInterface) SetVsyncEnabled(enabled bool) {
	// Do nothing
}

func (u *UserInterface) DeviceScaleFactor() float64 {
	return deviceScale()
}

func (u *UserInterface) SetScreenTransparent(transparent bool) {
	// Do nothing
}

func (u *UserInterface) IsScreenTransparent() bool {
	return false
}

func (u *UserInterface) ResetForFrame() {
	u.layoutIfNeeded()
	u.input.resetForFrame()
}

func (u *UserInterface) SetInitFocused(focused bool) {
	// Do nothing
}

func (u *UserInterface) Input() driver.Input {
	return &u.input
}

func (u *UserInterface) Window() driver.Window {
	return nil
}

type Touch struct {
	ID int
	X  int
	Y  int
}

type Gamepad struct {
	ID        int
	SDLID     string
	Name      string
	Buttons   [driver.GamepadButtonNum]bool
	ButtonNum int
	Axes      [32]float32
	AxisNum   int
}

func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []*Touch, gamepads []Gamepad) {
	u.input.update(keys, runes, touches, gamepads)
}
<commit_msg>uidriver/mobile: Refactoring<commit_after>// Copyright 2016 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build android ios

package mobile

import (
	"fmt"
	"runtime"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"unicode"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/internal/devicescale"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/graphicsdriver/opengl"
	"github.com/hajimehoshi/ebiten/internal/hooks"
	"github.com/hajimehoshi/ebiten/internal/restorable"
	"github.com/hajimehoshi/ebiten/internal/thread"
)

var (
	glContextCh = make(chan gl.Context, 1)

	// renderCh receives when updating starts.
	renderCh = make(chan struct{})

	// renderEndCh receives when updating finishes.
	renderEndCh = make(chan struct{})

	theUI = &UserInterface{
		foreground: 1,
		errCh:      make(chan error),

		// Give a default outside size so that the game can start without initializing them.
		outsideWidth:  640,
		outsideHeight: 480,
		sizeChanged:   true,
	}
)

func init() {
	theUI.input.ui = theUI
}

func Get() *UserInterface {
	return theUI
}

// Update is called from mobile/ebitenmobileview.
//
// Update must be called on the rendering thread.
func (u *UserInterface) Update() error {
	select {
	case err := <-u.errCh:
		return err
	default:
	}

	if !u.IsFocused() {
		return nil
	}

	renderCh <- struct{}{}
	if u.Graphics().IsGL() {
		if u.glWorker == nil {
			panic("mobile: glWorker must be initialized but not")
		}

		done := make(chan struct{})
		go func() {
			<-renderEndCh
			close(done)
		}()

		workAvailable := u.glWorker.WorkAvailable()
		for {
			select {
			case <-workAvailable:
				u.glWorker.DoWork()

				// This is a dirty hack to avoid freezing on Pixel 4 (#1322).
				// Apprently there is an issue in the usage of Worker in gomobile or gomobile itself.
				// At least, freezing doesn't happen with this Gosched.
				runtime.Gosched()
			case <-done:
				return nil
			}
		}
	}

	go func() {
		<-renderEndCh
		u.t.Call(func() error {
			return thread.BreakLoop
		})
	}()
	u.t.Loop()
	return nil
}

type UserInterface struct {
	outsideWidth  float64
	outsideHeight float64

	sizeChanged bool
	foreground  int32
	errCh       chan error

	// Used for gomobile-build
	gbuildWidthPx   int
	gbuildHeightPx  int
	setGBuildSizeCh chan struct{}
	once            sync.Once

	context driver.UIContext

	input Input

	t        *thread.Thread
	glWorker gl.Worker

	m sync.RWMutex
}

func deviceScale() float64 {
	return devicescale.GetAt(0, 0)
}

// appMain is the main routine for gomobile-build mode.
func (u *UserInterface) appMain(a app.App) {
	var glctx gl.Context
	var sizeInited bool

	touches := map[touch.Sequence]*Touch{}
	keys := map[driver.Key]struct{}{}

	for e := range a.Events() {
		var updateInput bool
		var runes []rune

		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				u.SetForeground(true)
				restorable.OnContextLost()
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				u.SetForeground(false)
				glctx = nil
			}
		case size.Event:
			u.setGBuildSize(e.WidthPx, e.HeightPx)
			sizeInited = true
		case paint.Event:
			if !sizeInited {
				a.Send(paint.Event{})
				continue
			}
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderEndCh
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			if !sizeInited {
				continue
			}
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := deviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				touches[e.Sequence] = &Touch{
					ID: int(e.Sequence),
					X:  int(x),
					Y:  int(y),
				}
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			updateInput = true
		case key.Event:
			k, ok := gbuildKeyToDriverKey[e.Code]
			if ok {
				switch e.Direction {
				case key.DirPress, key.DirNone:
					keys[k] = struct{}{}
				case key.DirRelease:
					delete(keys, k)
				}
			}

			switch e.Direction {
			case key.DirPress, key.DirNone:
				if e.Rune != -1 && unicode.IsPrint(e.Rune) {
					runes = []rune{e.Rune}
				}
			}
			updateInput = true
		}

		if updateInput {
			ts := []*Touch{}
			for _, t := range touches {
				ts = append(ts, t)
			}
			u.input.update(keys, runes, ts, nil)
		}
	}
}

func (u *UserInterface) SetForeground(foreground bool) {
	var v int32
	if foreground {
		v = 1
	}
	atomic.StoreInt32(&u.foreground, v)

	if foreground {
		hooks.ResumeAudio()
	} else {
		hooks.SuspendAudio()
	}
}

func (u *UserInterface) Run(context driver.UIContext) error {
	u.setGBuildSizeCh = make(chan struct{})
	go func() {
		if err := u.run(context, true); err != nil {
			// As mobile apps never ends, Loop can't return. Just panic here.
			panic(err)
		}
	}()
	app.Main(u.appMain)
	return nil
}

func (u *UserInterface) RunWithoutMainLoop(context driver.UIContext) {
	go func() {
		// title is ignored?
		if err := u.run(context, false); err != nil {
			u.errCh <- err
		}
	}()
}

func (u *UserInterface) run(context driver.UIContext, mainloop bool) (err error) {
	// Convert the panic to a regular error so that Java/Objective-C layer can treat this easily e.g., for
	// Crashlytics. A panic is treated as SIGABRT, and there is no way to handle this on Java/Objective-C layer
	// unfortunately.
	// TODO: Panic on other goroutines cannot be handled here.
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v\n%q", r, string(debug.Stack()))
		}
	}()

	u.m.Lock()
	u.sizeChanged = true
	u.m.Unlock()

	u.context = context

	if u.Graphics().IsGL() {
		var ctx gl.Context
		if mainloop {
			ctx = <-glContextCh
		} else {
			ctx, u.glWorker = gl.NewContext()
		}
		u.Graphics().(*opengl.Graphics).SetMobileGLContext(ctx)
	} else {
		u.t = thread.New()
		u.Graphics().SetThread(u.t)
	}

	// If gomobile-build is used, wait for the outside size fixed.
	if u.setGBuildSizeCh != nil {
		<-u.setGBuildSizeCh
	}

	// Force to set the screen size
	u.layoutIfNeeded()
	for {
		if err := u.update(); err != nil {
			return err
		}
	}
}

// layoutIfNeeded must be called on the same goroutine as update().
func (u *UserInterface) layoutIfNeeded() {
	var outsideWidth, outsideHeight float64

	u.m.RLock()
	sizeChanged := u.sizeChanged
	if sizeChanged {
		if u.gbuildWidthPx == 0 || u.gbuildHeightPx == 0 {
			outsideWidth = u.outsideWidth
			outsideHeight = u.outsideHeight
		} else {
			// gomobile build
			d := deviceScale()
			outsideWidth = float64(u.gbuildWidthPx) / d
			outsideHeight = float64(u.gbuildHeightPx) / d
		}
	}
	u.sizeChanged = false
	u.m.RUnlock()

	if sizeChanged {
		u.context.Layout(outsideWidth, outsideHeight)
	}
}

func (u *UserInterface) update() error {
	<-renderCh
	defer func() {
		renderEndCh <- struct{}{}
	}()

	if err := u.context.Update(); err != nil {
		return err
	}
	if err := u.context.Draw(); err != nil {
		return err
	}
	return nil
}

func (u *UserInterface) ScreenSizeInFullscreen() (int, int) {
	// TODO: This function should return gbuildWidthPx, gbuildHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

// SetOutsideSize is called from mobile/ebitenmobileview.
//
// SetOutsideSize is concurrent safe.
func (u *UserInterface) SetOutsideSize(outsideWidth, outsideHeight float64) {
	u.m.Lock()
	if u.outsideWidth != outsideWidth || u.outsideHeight != outsideHeight {
		u.outsideWidth = outsideWidth
		u.outsideHeight = outsideHeight
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func (u *UserInterface) setGBuildSize(widthPx, heightPx int) {
	u.m.Lock()
	u.gbuildWidthPx = widthPx
	u.gbuildHeightPx = heightPx
	u.sizeChanged = true
	u.m.Unlock()

	u.once.Do(func() {
		close(u.setGBuildSizeCh)
	})
}

func (u *UserInterface) adjustPosition(x, y int) (int, int) {
	xf, yf := u.context.AdjustPosition(float64(x), float64(y))
	return int(xf), int(yf)
}

func (u *UserInterface) CursorMode() driver.CursorMode {
	return driver.CursorModeHidden
}

func (u *UserInterface) SetCursorMode(mode driver.CursorMode) {
	// Do nothing
}

func (u *UserInterface) IsFullscreen() bool {
	return false
}

func (u *UserInterface) SetFullscreen(fullscreen bool) {
	// Do nothing
}

func (u *UserInterface) IsFocused() bool {
	return atomic.LoadInt32(&u.foreground) != 0
}

func (u *UserInterface) IsRunnableOnUnfocused() bool {
	return false
}

func (u *UserInterface) SetRunnableOnUnfocused(runnableOnUnfocused bool) {
	// Do nothing
}

func (u *UserInterface) IsVsyncEnabled() bool {
	return true
}

func (u *UserInterface) SetVsyncEnabled(enabled bool) {
	// Do nothing
}

func (u *UserInterface) DeviceScaleFactor() float64 {
	return deviceScale()
}

func (u *UserInterface) SetScreenTransparent(transparent bool) {
	// Do nothing
}

func (u *UserInterface) IsScreenTransparent() bool {
	return false
}

func (u *UserInterface) ResetForFrame() {
	u.layoutIfNeeded()
	u.input.resetForFrame()
}

func (u *UserInterface) SetInitFocused(focused bool) {
	// Do nothing
}

func (u *UserInterface) Input() driver.Input {
	return &u.input
}

func (u *UserInterface) Window() driver.Window {
	return nil
}

type Touch struct {
	ID int
	X  int
	Y  int
}

type Gamepad struct {
	ID        int
	SDLID     string
	Name      string
	Buttons   [driver.GamepadButtonNum]bool
	ButtonNum int
	Axes      [32]float32
	AxisNum   int
}

func (u *UserInterface) UpdateInput(keys map[driver.Key]struct{}, runes []rune, touches []*Touch, gamepads []Gamepad) {
	u.input.update(keys, runes, touches, gamepads)
}
<|endoftext|>