<commit_before>package reactive

import (
	"context"
	"errors"
	"sync"
	"time"
)

var (
	// Sentrinel error to tell the rerunner to not dump the current
	// computation cache and let the error'd function retry.
	RetrySentinelError = errors.New("retry")
)

// locker is a collection of mutexes indexed by arbitrary keys
type locker struct {
	mu sync.Mutex
	m  map[interface{}]*lock
}

// newLocker creates a new locker instance.
func newLocker() *locker {
	return &locker{
		m: make(map[interface{}]*lock),
	}
}

// lock is a single mutex in a locker
type lock struct {
	ref int
	mu  sync.Mutex
}

// Lock locks a locker by (optionally) allocating, increasing the ref count,
// and locking
func (l *locker) Lock(k interface{}) {
	l.mu.Lock()
	m, ok := l.m[k]
	if !ok {
		m = new(lock)
		l.m[k] = m
	}
	m.ref++
	l.mu.Unlock()
	m.mu.Lock()
}

// Unlock unlocks a locker by unlocking, decreasing the ref count, and
// (optionally) deleting
func (l *locker) Unlock(k interface{}) {
	l.mu.Lock()
	m := l.m[k]
	m.mu.Unlock()
	m.ref--
	if m.ref == 0 {
		delete(l.m, k)
	}
	l.mu.Unlock()
}

type computation struct {
	node  node
	value interface{}
}

// cache caches computations
type cache struct {
	mu           sync.Mutex
	locker       *locker
	computations map[interface{}]*computation
}

func (c *cache) get(key interface{}) *computation {
	c.mu.Lock()
	defer c.mu.Unlock()

	return c.computations[key]
}

// set adds a computation to the cache for the given key
func (c *cache) set(key interface{}, computation *computation) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.computations[key] == nil {
		c.computations[key] = computation
	}
}

func (c *cache) cleanInvalidated() {
	c.mu.Lock()
	defer c.mu.Unlock()

	for key, computation := range c.computations {
		if computation.node.Invalidated() {
			delete(c.computations, key)
		}
	}
}

// Resource represents a leaf-level dependency in a computation
type Resource struct {
	node
}

// NewResource creates a new Resource
func NewResource() *Resource {
	return &Resource{
		node: node{},
	}
}

// Invalidate permanently invalidates r
func (r *Resource) Invalidate() {
	go r.invalidate()
}

// Store invalidates all computations currently depending on r
func (r *Resource) Strobe() {
	go r.strobe()
}

// Cleanup registers a handler to be called when all computations using r stop
//
// NOTE: For f to be called, at least one computation must AddDependency r!
func (r *Resource) Cleanup(f func()) {
	r.node.handleRelease(f)
}

type computationKey struct{}
type cacheKey struct{}

func AddDependency(ctx context.Context, r *Resource) {
	if !HasRerunner(ctx) {
		r.node.addOut(&node{released: true})
		return
	}

	computation := ctx.Value(computationKey{}).(*computation)
	r.node.addOut(&computation.node)
}

type ComputeFunc func(context.Context) (interface{}, error)

func run(ctx context.Context, f ComputeFunc) (*computation, error) {
	// build result computation and local computation Ctx
	c := &computation{
		// this node will be freed either when the computation fails, or by our
		// caller
		node: node{},
	}

	childCtx := context.WithValue(ctx, computationKey{}, c)

	// Compute f and write the results to the c
	value, err := f(childCtx)
	if err != nil {
		go c.node.release()
		return nil, err
	}

	c.value = value

	return c, nil
}

func Cache(ctx context.Context, key interface{}, f ComputeFunc) (interface{}, error) {
	if !HasRerunner(ctx) {
		val, err := f(ctx)
		return val, err
	}

	cache := ctx.Value(cacheKey{}).(*cache)
	computation := ctx.Value(computationKey{}).(*computation)

	cache.locker.Lock(key)
	defer cache.locker.Unlock(key)

	if child := cache.get(key); child != nil {
		child.node.addOut(&computation.node)
		return child.value, nil
	}

	child, err := run(ctx, f)
	if err != nil {
		return nil, err
	}
	cache.set(key, child)

	child.node.addOut(&computation.node)
	return child.value, nil
}

// Rerunner automatically reruns a computation whenever its dependencies
// change.
//
// The computation stops when it returns an error or after calling Stop.  There
// is no way to get the output value from a computation. Instead, the
// computation should communicate its result before returning.
type Rerunner struct {
	ctx       context.Context
	cancelCtx context.CancelFunc

	f                ComputeFunc
	cache            *cache
	minRerunInterval time.Duration
	retryDelay       time.Duration

	mu          sync.Mutex
	computation *computation
	stop        bool

	lastRun time.Time
}

// NewRerunner runs f continuously
func NewRerunner(ctx context.Context, f ComputeFunc, minRerunInterval time.Duration) *Rerunner {
	ctx, cancelCtx := context.WithCancel(ctx)

	r := &Rerunner{
		ctx:       ctx,
		cancelCtx: cancelCtx,

		f: f,
		cache: &cache{
			computations: make(map[interface{}]*computation),
			locker:       newLocker(),
		},
		minRerunInterval: minRerunInterval,
		retryDelay:       minRerunInterval,
	}
	go r.run()
	return r
}

// run performs an actual computation
func (r *Rerunner) run() {
	// Wait for the minimum rerun interval. Exit early if the computation is stopped.
	delta := r.retryDelay - time.Now().Sub(r.lastRun)
	t := time.NewTimer(delta)
	select {
	case <-r.ctx.Done():
		t.Stop()
		return
	case <-t.C:
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	// Bail out if the computation has been stopped.
	if r.stop {
		return
	}

	r.cache.cleanInvalidated()
	ctx := context.WithValue(r.ctx, cacheKey{}, r.cache)

	// Run f, and release the old computation right after.
	computation, err := run(ctx, r.f)
	if err != nil {
		if err == RetrySentinelError {
			r.retryDelay = r.retryDelay * 2

			// Max out the retry delay to at 1 minute
			if r.retryDelay > time.Minute {
				r.retryDelay = time.Minute
			}
			go r.run()
		} else {
			// If we encountered an error that is not the retry sentinel,
			// we should stop the rerunner.
			return
		}
	} else {
		// If we succeeded in the computation, we can replace the old computation
		// and reset the retry delay.
		if r.computation != nil {
			go r.computation.node.release()
			r.computation = nil
		}

		r.computation = computation
		r.retryDelay = r.minRerunInterval

		// schedule a rerun whenever our node becomes invalidated (which might already
		// have happened!)
		computation.node.handleInvalidate(r.run)
	}

	r.lastRun = time.Now()
}

func (r *Rerunner) Stop() {
	// Call cancelCtx before acquiring the lock as the lock might be held for a long time during a running computation.
	r.cancelCtx()

	r.mu.Lock()
	r.stop = true
	if r.computation != nil {
		go r.computation.node.release()
		r.computation = nil
	}
	r.mu.Unlock()
}

func HasRerunner(ctx context.Context) bool {
	return ctx.Value(computationKey{}) != nil
}
<commit_msg>reactive: fix race/out of order setting for lastRun<commit_after>package reactive

import (
	"context"
	"errors"
	"sync"
	"time"
)

var (
	// Sentrinel error to tell the rerunner to not dump the current
	// computation cache and let the error'd function retry.
	RetrySentinelError = errors.New("retry")
)

// locker is a collection of mutexes indexed by arbitrary keys
type locker struct {
	mu sync.Mutex
	m  map[interface{}]*lock
}

// newLocker creates a new locker instance.
func newLocker() *locker {
	return &locker{
		m: make(map[interface{}]*lock),
	}
}

// lock is a single mutex in a locker
type lock struct {
	ref int
	mu  sync.Mutex
}

// Lock locks a locker by (optionally) allocating, increasing the ref count,
// and locking
func (l *locker) Lock(k interface{}) {
	l.mu.Lock()
	m, ok := l.m[k]
	if !ok {
		m = new(lock)
		l.m[k] = m
	}
	m.ref++
	l.mu.Unlock()
	m.mu.Lock()
}

// Unlock unlocks a locker by unlocking, decreasing the ref count, and
// (optionally) deleting
func (l *locker) Unlock(k interface{}) {
	l.mu.Lock()
	m := l.m[k]
	m.mu.Unlock()
	m.ref--
	if m.ref == 0 {
		delete(l.m, k)
	}
	l.mu.Unlock()
}

type computation struct {
	node  node
	value interface{}
}

// cache caches computations
type cache struct {
	mu           sync.Mutex
	locker       *locker
	computations map[interface{}]*computation
}

func (c *cache) get(key interface{}) *computation {
	c.mu.Lock()
	defer c.mu.Unlock()

	return c.computations[key]
}

// set adds a computation to the cache for the given key
func (c *cache) set(key interface{}, computation *computation) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.computations[key] == nil {
		c.computations[key] = computation
	}
}

func (c *cache) cleanInvalidated() {
	c.mu.Lock()
	defer c.mu.Unlock()

	for key, computation := range c.computations {
		if computation.node.Invalidated() {
			delete(c.computations, key)
		}
	}
}

// Resource represents a leaf-level dependency in a computation
type Resource struct {
	node
}

// NewResource creates a new Resource
func NewResource() *Resource {
	return &Resource{
		node: node{},
	}
}

// Invalidate permanently invalidates r
func (r *Resource) Invalidate() {
	go r.invalidate()
}

// Store invalidates all computations currently depending on r
func (r *Resource) Strobe() {
	go r.strobe()
}

// Cleanup registers a handler to be called when all computations using r stop
//
// NOTE: For f to be called, at least one computation must AddDependency r!
func (r *Resource) Cleanup(f func()) {
	r.node.handleRelease(f)
}

type computationKey struct{}
type cacheKey struct{}

func AddDependency(ctx context.Context, r *Resource) {
	if !HasRerunner(ctx) {
		r.node.addOut(&node{released: true})
		return
	}

	computation := ctx.Value(computationKey{}).(*computation)
	r.node.addOut(&computation.node)
}

type ComputeFunc func(context.Context) (interface{}, error)

func run(ctx context.Context, f ComputeFunc) (*computation, error) {
	// build result computation and local computation Ctx
	c := &computation{
		// this node will be freed either when the computation fails, or by our
		// caller
		node: node{},
	}

	childCtx := context.WithValue(ctx, computationKey{}, c)

	// Compute f and write the results to the c
	value, err := f(childCtx)
	if err != nil {
		go c.node.release()
		return nil, err
	}

	c.value = value

	return c, nil
}

func Cache(ctx context.Context, key interface{}, f ComputeFunc) (interface{}, error) {
	if !HasRerunner(ctx) {
		val, err := f(ctx)
		return val, err
	}

	cache := ctx.Value(cacheKey{}).(*cache)
	computation := ctx.Value(computationKey{}).(*computation)

	cache.locker.Lock(key)
	defer cache.locker.Unlock(key)

	if child := cache.get(key); child != nil {
		child.node.addOut(&computation.node)
		return child.value, nil
	}

	child, err := run(ctx, f)
	if err != nil {
		return nil, err
	}
	cache.set(key, child)

	child.node.addOut(&computation.node)
	return child.value, nil
}

// Rerunner automatically reruns a computation whenever its dependencies
// change.
//
// The computation stops when it returns an error or after calling Stop.  There
// is no way to get the output value from a computation. Instead, the
// computation should communicate its result before returning.
type Rerunner struct {
	ctx       context.Context
	cancelCtx context.CancelFunc

	f                ComputeFunc
	cache            *cache
	minRerunInterval time.Duration
	retryDelay       time.Duration

	mu          sync.Mutex
	computation *computation
	stop        bool

	lastRun time.Time
}

// NewRerunner runs f continuously
func NewRerunner(ctx context.Context, f ComputeFunc, minRerunInterval time.Duration) *Rerunner {
	ctx, cancelCtx := context.WithCancel(ctx)

	r := &Rerunner{
		ctx:       ctx,
		cancelCtx: cancelCtx,

		f: f,
		cache: &cache{
			computations: make(map[interface{}]*computation),
			locker:       newLocker(),
		},
		minRerunInterval: minRerunInterval,
		retryDelay:       minRerunInterval,
	}
	go r.run()
	return r
}

// run performs an actual computation
func (r *Rerunner) run() {
	// Wait for the minimum rerun interval. Exit early if the computation is stopped.
	delta := r.retryDelay - time.Now().Sub(r.lastRun)
	t := time.NewTimer(delta)
	select {
	case <-r.ctx.Done():
		t.Stop()
		return
	case <-t.C:
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	// Bail out if the computation has been stopped.
	if r.stop {
		return
	}

	r.cache.cleanInvalidated()
	ctx := context.WithValue(r.ctx, cacheKey{}, r.cache)

	// Run f, and release the old computation right after.
	computation, err := run(ctx, r.f)
	r.lastRun = time.Now()
	if err != nil {
		if err == RetrySentinelError {
			r.retryDelay = r.retryDelay * 2

			// Max out the retry delay to at 1 minute
			if r.retryDelay > time.Minute {
				r.retryDelay = time.Minute
			}
			go r.run()
		} else {
			// If we encountered an error that is not the retry sentinel,
			// we should stop the rerunner.
			return
		}
	} else {
		// If we succeeded in the computation, we can replace the old computation
		// and reset the retry delay.
		if r.computation != nil {
			go r.computation.node.release()
			r.computation = nil
		}

		r.computation = computation
		r.retryDelay = r.minRerunInterval

		// schedule a rerun whenever our node becomes invalidated (which might already
		// have happened!)
		computation.node.handleInvalidate(r.run)
	}
}

func (r *Rerunner) Stop() {
	// Call cancelCtx before acquiring the lock as the lock might be held for a long time during a running computation.
	r.cancelCtx()

	r.mu.Lock()
	r.stop = true
	if r.computation != nil {
		go r.computation.node.release()
		r.computation = nil
	}
	r.mu.Unlock()
}

func HasRerunner(ctx context.Context) bool {
	return ctx.Value(computationKey{}) != nil
}
<|endoftext|>


<commit_before>package termui

type colorScheme struct {
	BodyBg            Attribute
	BlockBg           Attribute
	HasBorder         bool
	BorderFg          Attribute
	BorderBg          Attribute
	BorderLabelTextFg Attribute
	BorderLabelTextBg Attribute
	ParTextFg         Attribute
	ParTextBg         Attribute
	SparklineLine     Attribute
	SparklineTitle    Attribute
	GaugeBar          Attribute
	GaugePercent      Attribute
	LineChartLine     Attribute
	LineChartAxes     Attribute
	ListItemFg        Attribute
	ListItemBg        Attribute
	BarChartBar       Attribute
	BarChartText      Attribute
	BarChartNum       Attribute
}

// default color scheme depends on the user's terminal setting.
var themeDefault = colorScheme{HasBorder: true}

var themeHelloWorld = colorScheme{
	BodyBg:            ColorBlack,
	BlockBg:           ColorBlack,
	HasBorder:         true,
	BorderFg:          ColorWhite,
	BorderBg:          ColorBlack,
	BorderLabelTextBg: ColorBlack,
	BorderLabelTextFg: ColorGreen,
	ParTextBg:         ColorBlack,
	ParTextFg:         ColorWhite,
	SparklineLine:     ColorMagenta,
	SparklineTitle:    ColorWhite,
	GaugeBar:          ColorRed,
	GaugePercent:      ColorWhite,
	LineChartLine:     ColorYellow | AttrBold,
	LineChartAxes:     ColorWhite,
	ListItemBg:        ColorBlack,
	ListItemFg:        ColorYellow,
	BarChartBar:       ColorRed,
	BarChartNum:       ColorWhite,
	BarChartText:      ColorCyan,
}

var theme = themeDefault // global dep

func UseTheme(th string) {
	switch th {
	case "helloworld":
		theme = themeHelloWorld
	default:
		theme = themeDefault
	}
}
<commit_msg>Added new public (exported) getter for Theme. Fixes #6 Added new public setter for Theme (which uses a bare theme). Made ColorScheme public.<commit_after>package termui

// A ColorScheme represents the current look-and-feel of the dashboard.
type ColorScheme struct {
	BodyBg            Attribute
	BlockBg           Attribute
	HasBorder         bool
	BorderFg          Attribute
	BorderBg          Attribute
	BorderLabelTextFg Attribute
	BorderLabelTextBg Attribute
	ParTextFg         Attribute
	ParTextBg         Attribute
	SparklineLine     Attribute
	SparklineTitle    Attribute
	GaugeBar          Attribute
	GaugePercent      Attribute
	LineChartLine     Attribute
	LineChartAxes     Attribute
	ListItemFg        Attribute
	ListItemBg        Attribute
	BarChartBar       Attribute
	BarChartText      Attribute
	BarChartNum       Attribute
}

// default color scheme depends on the user's terminal setting.
var themeDefault = ColorScheme{HasBorder: true}

var themeHelloWorld = ColorScheme{
	BodyBg:            ColorBlack,
	BlockBg:           ColorBlack,
	HasBorder:         true,
	BorderFg:          ColorWhite,
	BorderBg:          ColorBlack,
	BorderLabelTextBg: ColorBlack,
	BorderLabelTextFg: ColorGreen,
	ParTextBg:         ColorBlack,
	ParTextFg:         ColorWhite,
	SparklineLine:     ColorMagenta,
	SparklineTitle:    ColorWhite,
	GaugeBar:          ColorRed,
	GaugePercent:      ColorWhite,
	LineChartLine:     ColorYellow | AttrBold,
	LineChartAxes:     ColorWhite,
	ListItemBg:        ColorBlack,
	ListItemFg:        ColorYellow,
	BarChartBar:       ColorRed,
	BarChartNum:       ColorWhite,
	BarChartText:      ColorCyan,
}

var theme = themeDefault // global dep

// Theme returns the currently used theme.
func Theme() ColorScheme {
	return theme
}

// SetTheme sets a new, custom theme.
func SetTheme(newTheme ColorScheme) {
	theme = newTheme
}

// UseTheme sets a predefined scheme. Currently available: "hello-world" and
// "black-and-white".
func UseTheme(th string) {
	switch th {
	case "helloworld":
		theme = themeHelloWorld
	default:
		theme = themeDefault
	}
}
<|endoftext|>


<commit_before>package netscaler

import (
	"github.com/chiradeep/go-nitro/config/basic"

	"github.com/chiradeep/go-nitro/netscaler"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"fmt"
	"log"
)

func resourceNetScalerServer() *schema.Resource {
	return &schema.Resource{
		SchemaVersion: 1,
		Create:        createServerFunc,
		Read:          readServerFunc,
		Update:        updateServerFunc,
		Delete:        deleteServerFunc,
		Schema: map[string]*schema.Schema{
			"comment": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"delay": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"domain": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"domainresolvenow": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},
			"domainresolveretry": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"graceful": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"internal": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},
			"ipaddress": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"ipv6address": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"newname": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"state": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"td": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"translationip": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"translationmask": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}

func createServerFunc(d *schema.ResourceData, meta interface{}) error {
	log.Printf("[DEBUG]  netscaler-provider: In createServerFunc")
	client := meta.(*NetScalerNitroClient).client
	var serverName string
	if v, ok := d.GetOk("name"); ok {
		serverName = v.(string)
	} else {
		serverName = resource.PrefixedUniqueId("tf-server-")
		d.Set("name", serverName)
	}
	server := basic.Server{
		Comment:            d.Get("comment").(string),
		Delay:              d.Get("delay").(int),
		Domain:             d.Get("domain").(string),
		Domainresolvenow:   d.Get("domainresolvenow").(bool),
		Domainresolveretry: d.Get("domainresolveretry").(int),
		Graceful:           d.Get("graceful").(string),
		Internal:           d.Get("internal").(bool),
		Ipaddress:          d.Get("ipaddress").(string),
		Ipv6address:        d.Get("ipv6address").(string),
		Name:               d.Get("name").(string),
		Newname:            d.Get("newname").(string),
		State:              d.Get("state").(string),
		Td:                 d.Get("td").(int),
		Translationip:      d.Get("translationip").(string),
		Translationmask:    d.Get("translationmask").(string),
	}

	_, err := client.AddResource(netscaler.Server.Type(), serverName, &server)
	if err != nil {
		return err
	}

	d.SetId(serverName)

	err = readServerFunc(d, meta)
	if err != nil {
		log.Printf("[ERROR] netscaler-provider: ?? we just created this server but we can't read it ?? %s", serverName)
		return nil
	}
	return nil
}

func readServerFunc(d *schema.ResourceData, meta interface{}) error {
	log.Printf("[DEBUG] netscaler-provider:  In readServerFunc")
	client := meta.(*NetScalerNitroClient).client
	serverName := d.Id()
	log.Printf("[DEBUG] netscaler-provider: Reading server state %s", serverName)
	data, err := client.FindResource(netscaler.Server.Type(), serverName)
	if err != nil {
		log.Printf("[WARN] netscaler-provider: Clearing server state %s", serverName)
		d.SetId("")
		return nil
	}
	d.Set("name", data["name"])
	d.Set("comment", data["comment"])
	d.Set("delay", data["delay"])
	d.Set("domain", data["domain"])
	d.Set("domainresolvenow", data["domainresolvenow"])
	d.Set("domainresolveretry", data["domainresolveretry"])
	d.Set("graceful", data["graceful"])
	d.Set("internal", data["internal"])
	d.Set("ipaddress", data["ipaddress"])
	d.Set("ipv6address", data["ipv6address"])
	d.Set("name", data["name"])
	d.Set("newname", data["newname"])
	d.Set("state", data["state"])
	d.Set("td", data["td"])
	d.Set("translationip", data["translationip"])
	d.Set("translationmask", data["translationmask"])

	return nil

}

func updateServerFunc(d *schema.ResourceData, meta interface{}) error {
	log.Printf("[DEBUG]  netscaler-provider: In updateServerFunc")
	client := meta.(*NetScalerNitroClient).client
	serverName := d.Get("name").(string)

	server := basic.Server{
		Name: d.Get("name").(string),
	}
	hasChange := false
	if d.HasChange("comment") {
		log.Printf("[DEBUG]  netscaler-provider: Comment has changed for server %s, starting update", serverName)
		server.Comment = d.Get("comment").(string)
		hasChange = true
	}
	if d.HasChange("delay") {
		log.Printf("[DEBUG]  netscaler-provider: Delay has changed for server %s, starting update", serverName)
		server.Delay = d.Get("delay").(int)
		hasChange = true
	}
	if d.HasChange("domain") {
		log.Printf("[DEBUG]  netscaler-provider: Domain has changed for server %s, starting update", serverName)
		server.Domain = d.Get("domain").(string)
		hasChange = true
	}
	if d.HasChange("domainresolvenow") {
		log.Printf("[DEBUG]  netscaler-provider: Domainresolvenow has changed for server %s, starting update", serverName)
		server.Domainresolvenow = d.Get("domainresolvenow").(bool)
		hasChange = true
	}
	if d.HasChange("domainresolveretry") {
		log.Printf("[DEBUG]  netscaler-provider: Domainresolveretry has changed for server %s, starting update", serverName)
		server.Domainresolveretry = d.Get("domainresolveretry").(int)
		hasChange = true
	}
	if d.HasChange("graceful") {
		log.Printf("[DEBUG]  netscaler-provider: Graceful has changed for server %s, starting update", serverName)
		server.Graceful = d.Get("graceful").(string)
		hasChange = true
	}
	if d.HasChange("internal") {
		log.Printf("[DEBUG]  netscaler-provider: Internal has changed for server %s, starting update", serverName)
		server.Internal = d.Get("internal").(bool)
		hasChange = true
	}
	if d.HasChange("ipaddress") {
		log.Printf("[DEBUG]  netscaler-provider: Ipaddress has changed for server %s, starting update", serverName)
		server.Ipaddress = d.Get("ipaddress").(string)
		hasChange = true
	}
	if d.HasChange("ipv6address") {
		log.Printf("[DEBUG]  netscaler-provider: Ipv6address has changed for server %s, starting update", serverName)
		server.Ipv6address = d.Get("ipv6address").(string)
		hasChange = true
	}
	if d.HasChange("name") {
		log.Printf("[DEBUG]  netscaler-provider: Name has changed for server %s, starting update", serverName)
		server.Name = d.Get("name").(string)
		hasChange = true
	}
	if d.HasChange("newname") {
		log.Printf("[DEBUG]  netscaler-provider: Newname has changed for server %s, starting update", serverName)
		server.Newname = d.Get("newname").(string)
		hasChange = true
	}
	if d.HasChange("state") {
		log.Printf("[DEBUG]  netscaler-provider: State has changed for server %s, starting update", serverName)
		server.State = d.Get("state").(string)
		hasChange = true
	}
	if d.HasChange("td") {
		log.Printf("[DEBUG]  netscaler-provider: Td has changed for server %s, starting update", serverName)
		server.Td = d.Get("td").(int)
		hasChange = true
	}
	if d.HasChange("translationip") {
		log.Printf("[DEBUG]  netscaler-provider: Translationip has changed for server %s, starting update", serverName)
		server.Translationip = d.Get("translationip").(string)
		hasChange = true
	}
	if d.HasChange("translationmask") {
		log.Printf("[DEBUG]  netscaler-provider: Translationmask has changed for server %s, starting update", serverName)
		server.Translationmask = d.Get("translationmask").(string)
		hasChange = true
	}

	if hasChange {
		_, err := client.UpdateResource(netscaler.Server.Type(), serverName, &server)
		if err != nil {
			return fmt.Errorf("Error updating server %s", serverName)
		}
	}
	return readServerFunc(d, meta)
}

func deleteServerFunc(d *schema.ResourceData, meta interface{}) error {
	log.Printf("[DEBUG]  netscaler-provider: In deleteServerFunc")
	client := meta.(*NetScalerNitroClient).client
	serverName := d.Id()
	err := client.DeleteResource(netscaler.Server.Type(), serverName)
	if err != nil {
		return err
	}

	d.SetId("")

	return nil
}
<commit_msg>Mark server attribute name with ForceNew<commit_after>package netscaler

import (
	"github.com/chiradeep/go-nitro/config/basic"

	"github.com/chiradeep/go-nitro/netscaler"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"fmt"
	"log"
)

func resourceNetScalerServer() *schema.Resource {
	return &schema.Resource{
		SchemaVersion: 1,
		Create:        createServerFunc,
		Read:          readServerFunc,
		Update:        updateServerFunc,
		Delete:        deleteServerFunc,
		Schema: map[string]*schema.Schema{
			"comment": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"delay": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"domain": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"domainresolvenow": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},
			"domainresolveretry": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"graceful": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"internal": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Computed: true,
			},
			"ipaddress": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"ipv6address": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},
			"newname": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"state": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"td": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
			},
			"translationip": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"translationmask": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}

func createServerFunc(d *schema.ResourceData, meta interface{}) error {
	log.Printf("[DEBUG]  netscaler-provider: In createServerFunc")
	client := meta.(*NetScalerNitroClient).client
	var serverName string
	if v, ok := d.GetOk("name"); ok {
		serverName = v.(string)
	} else {
		serverName = resource.PrefixedUniqueId("tf-server-")
		d.Set("name", serverName)
	}
	server := basic.Server{
		Comment:            d.Get("comment").(string),
		Delay:              d.Get("delay").(int),
		Domain:             d.Get("domain").(string),
		Domainresolvenow:   d.Get("domainresolvenow").(bool),
		Domainresolveretry: d.Get("domainresolveretry").(int),
		Graceful:           d.Get("graceful").(string),
		Internal:           d.Get("internal").(bool),
		Ipaddress:          d.Get("ipaddress").(string),
		Ipv6address:        d.Get("ipv6address").(string),
		Name:               d.Get("name").(string),
		Newname:            d.Get("newname").(string),
		State:              d.Get("state").(string),
		Td:                 d.Get("td").(int),
		Translationip:      d.Get("translationip").(string),
		Translationmask:    d.Get("translationmask").(string),
	}

	_, err := client.AddResource(netscaler.Server.Type(), serverName, &server)
	if err != nil {
		return err
	}

	d.SetId(serverName)

	err = readServerFunc(d, meta)
	if err != nil {
		log.Printf("[ERROR] netscaler-provider: ?? we just created this server but we can't read it ?? %s", serverName)
		return nil
	}
	return nil
}

func readServerFunc(d *schema.ResourceData, meta interface{}) error {
	log.Printf("[DEBUG] netscaler-provider:  In readServerFunc")
	client := meta.(*NetScalerNitroClient).client
	serverName := d.Id()
	log.Printf("[DEBUG] netscaler-provider: Reading server state %s", serverName)
	data, err := client.FindResource(netscaler.Server.Type(), serverName)
	if err != nil {
		log.Printf("[WARN] netscaler-provider: Clearing server state %s", serverName)
		d.SetId("")
		return nil
	}
	d.Set("name", data["name"])
	d.Set("comment", data["comment"])
	d.Set("delay", data["delay"])
	d.Set("domain", data["domain"])
	d.Set("domainresolvenow", data["domainresolvenow"])
	d.Set("domainresolveretry", data["domainresolveretry"])
	d.Set("graceful", data["graceful"])
	d.Set("internal", data["internal"])
	d.Set("ipaddress", data["ipaddress"])
	d.Set("ipv6address", data["ipv6address"])
	d.Set("name", data["name"])
	d.Set("newname", data["newname"])
	d.Set("state", data["state"])
	d.Set("td", data["td"])
	d.Set("translationip", data["translationip"])
	d.Set("translationmask", data["translationmask"])

	return nil

}

func updateServerFunc(d *schema.ResourceData, meta interface{}) error {
	log.Printf("[DEBUG]  netscaler-provider: In updateServerFunc")
	client := meta.(*NetScalerNitroClient).client
	serverName := d.Get("name").(string)

	server := basic.Server{
		Name: d.Get("name").(string),
	}
	hasChange := false
	if d.HasChange("comment") {
		log.Printf("[DEBUG]  netscaler-provider: Comment has changed for server %s, starting update", serverName)
		server.Comment = d.Get("comment").(string)
		hasChange = true
	}
	if d.HasChange("delay") {
		log.Printf("[DEBUG]  netscaler-provider: Delay has changed for server %s, starting update", serverName)
		server.Delay = d.Get("delay").(int)
		hasChange = true
	}
	if d.HasChange("domain") {
		log.Printf("[DEBUG]  netscaler-provider: Domain has changed for server %s, starting update", serverName)
		server.Domain = d.Get("domain").(string)
		hasChange = true
	}
	if d.HasChange("domainresolvenow") {
		log.Printf("[DEBUG]  netscaler-provider: Domainresolvenow has changed for server %s, starting update", serverName)
		server.Domainresolvenow = d.Get("domainresolvenow").(bool)
		hasChange = true
	}
	if d.HasChange("domainresolveretry") {
		log.Printf("[DEBUG]  netscaler-provider: Domainresolveretry has changed for server %s, starting update", serverName)
		server.Domainresolveretry = d.Get("domainresolveretry").(int)
		hasChange = true
	}
	if d.HasChange("graceful") {
		log.Printf("[DEBUG]  netscaler-provider: Graceful has changed for server %s, starting update", serverName)
		server.Graceful = d.Get("graceful").(string)
		hasChange = true
	}
	if d.HasChange("internal") {
		log.Printf("[DEBUG]  netscaler-provider: Internal has changed for server %s, starting update", serverName)
		server.Internal = d.Get("internal").(bool)
		hasChange = true
	}
	if d.HasChange("ipaddress") {
		log.Printf("[DEBUG]  netscaler-provider: Ipaddress has changed for server %s, starting update", serverName)
		server.Ipaddress = d.Get("ipaddress").(string)
		hasChange = true
	}
	if d.HasChange("ipv6address") {
		log.Printf("[DEBUG]  netscaler-provider: Ipv6address has changed for server %s, starting update", serverName)
		server.Ipv6address = d.Get("ipv6address").(string)
		hasChange = true
	}
	if d.HasChange("name") {
		log.Printf("[DEBUG]  netscaler-provider: Name has changed for server %s, starting update", serverName)
		server.Name = d.Get("name").(string)
		hasChange = true
	}
	if d.HasChange("newname") {
		log.Printf("[DEBUG]  netscaler-provider: Newname has changed for server %s, starting update", serverName)
		server.Newname = d.Get("newname").(string)
		hasChange = true
	}
	if d.HasChange("state") {
		log.Printf("[DEBUG]  netscaler-provider: State has changed for server %s, starting update", serverName)
		server.State = d.Get("state").(string)
		hasChange = true
	}
	if d.HasChange("td") {
		log.Printf("[DEBUG]  netscaler-provider: Td has changed for server %s, starting update", serverName)
		server.Td = d.Get("td").(int)
		hasChange = true
	}
	if d.HasChange("translationip") {
		log.Printf("[DEBUG]  netscaler-provider: Translationip has changed for server %s, starting update", serverName)
		server.Translationip = d.Get("translationip").(string)
		hasChange = true
	}
	if d.HasChange("translationmask") {
		log.Printf("[DEBUG]  netscaler-provider: Translationmask has changed for server %s, starting update", serverName)
		server.Translationmask = d.Get("translationmask").(string)
		hasChange = true
	}

	if hasChange {
		_, err := client.UpdateResource(netscaler.Server.Type(), serverName, &server)
		if err != nil {
			return fmt.Errorf("Error updating server %s", serverName)
		}
	}
	return readServerFunc(d, meta)
}

func deleteServerFunc(d *schema.ResourceData, meta interface{}) error {
	log.Printf("[DEBUG]  netscaler-provider: In deleteServerFunc")
	client := meta.(*NetScalerNitroClient).client
	serverName := d.Id()
	err := client.DeleteResource(netscaler.Server.Type(), serverName)
	if err != nil {
		return err
	}

	d.SetId("")

	return nil
}
<|endoftext|>


<commit_before>package clui

import (
	"bufio"
	term "github.com/nsf/termbox-go"
	"io/ioutil"
	"os"
	"strings"
)

/*
ThemeManager support for controls.
The current implementation is limited but later the manager will be
able to load a requested theme on demand and use deep inheritance.
Theme 'default' exists always - it is predefinded and always complete.
User-defined themes may omit any theme section, all omitted items
are loaded from parent theme. The only required property that a user-
defined theme must have is a theme name.
*/
type ThemeManager struct {
	// available theme list
	themes map[string]theme
	// name of the current theme
	current   string
	themePath string
	version   string
}

// ThemeInfo is a detailed information about theme:
// title, author, version number
type ThemeInfo struct {
	parent  string
	title   string
	author  string
	version string
}

/*
A theme structure. It keeps all colors, characters for the theme.
Parent property determines a theme name that is used if a requested
theme object is not declared in the current one. If no parent is
defined then the library uses default built-in theme.
*/
type theme struct {
	parent  string
	title   string
	author  string
	version string
	colors  map[string]term.Attribute
	objects map[string]string
}

const defaultTheme = "default"

// NewThemeManager creates a new theme manager
func NewThemeManager() *ThemeManager {
	sm := new(ThemeManager)

	sm.Reset()

	return sm
}

// Reset removes all loaded themes from cache and reinitialize
// the default theme
func (s *ThemeManager) Reset() {
	s.current = defaultTheme
	s.themes = make(map[string]theme, 0)

	defTheme := theme{parent: "", title: "Default Theme", author: "V. Markelov", version: "1.0"}
	defTheme.colors = make(map[string]term.Attribute, 0)
	defTheme.objects = make(map[string]string, 0)

	defTheme.objects[ObjSingleBorder] = "─│┌┐└┘"
	defTheme.objects[ObjDoubleBorder] = "═║╔╗╚╝"
	defTheme.objects[ObjEdit] = "←→V"
	defTheme.objects[ObjScrollBar] = "|O^v"
	defTheme.objects[ObjViewButtons] = "^↓○[]"
	defTheme.objects[ObjCheckBox] = "[] X?"
	defTheme.objects[ObjRadio] = "() *"
	defTheme.objects[ObjProgressBar] = "░▒"

	defTheme.colors[ColorDisabledText] = ColorBlackBold
	defTheme.colors[ColorDisabledBack] = ColorWhite
	defTheme.colors[ColorText] = ColorWhite
	defTheme.colors[ColorBack] = ColorBlackBold
	defTheme.colors[ColorViewBack] = ColorBlackBold
	defTheme.colors[ColorViewText] = ColorWhite

	defTheme.colors[ColorControlText] = ColorWhite
	defTheme.colors[ColorControlBack] = ColorBlack
	defTheme.colors[ColorControlActiveText] = ColorWhite
	defTheme.colors[ColorControlActiveBack] = ColorMagenta
	defTheme.colors[ColorControlShadow] = ColorBlue
	defTheme.colors[ColorControlDisabledText] = ColorWhite
	defTheme.colors[ColorControlDisabledBack] = ColorBlackBold

	defTheme.colors[ColorEditText] = ColorBlack
	defTheme.colors[ColorEditBack] = ColorWhite
	defTheme.colors[ColorEditActiveText] = ColorBlack
	defTheme.colors[ColorEditActiveBack] = ColorWhiteBold
	defTheme.colors[ColorSelectionText] = ColorYellow
	defTheme.colors[ColorSelectionBack] = ColorBlue

	defTheme.colors[ColorScrollBack] = ColorBlackBold
	defTheme.colors[ColorScrollText] = ColorWhite
	defTheme.colors[ColorThumbBack] = ColorBlackBold
	defTheme.colors[ColorThumbText] = ColorWhite

	defTheme.colors[ColorProgressText] = ColorBlue
	defTheme.colors[ColorProgressBack] = ColorBlackBold
	defTheme.colors[ColorProgressActiveText] = ColorBlack
	defTheme.colors[ColorProgressActiveBack] = ColorBlueBold

	s.themes[defaultTheme] = defTheme
}

// SysColor returns attribute by its id for the current theme
func (s *ThemeManager) SysColor(color string) term.Attribute {
	sch, ok := s.themes[s.current]
	if !ok {
		sch = s.themes[defaultTheme]
	}

	clr, okclr := sch.colors[color]
	if !okclr {
		visited := make(map[string]int, 0)
		visited[s.current] = 1
		if !ok {
			visited[defaultTheme] = 1
		}

		for {
			s.LoadTheme(sch.parent)
			sch = s.themes[sch.parent]
			clr, okclr = sch.colors[color]

			if ok {
				break
			} else {
				if _, okSch := visited[sch.parent]; okSch {
					panic("Color + " + color + ". Theme loop detected: " + sch.title + " --> " + sch.parent)
				} else {
					visited[sch.parent] = 1
				}
			}
		}
	}

	return clr
}

// SysObject returns object look by its id for the current
// theme. E.g, border lines for frame or arrows for scrollbar
func (s *ThemeManager) SysObject(object string) string {
	sch, ok := s.themes[s.current]
	if !ok {
		sch = s.themes[defaultTheme]
	}

	obj, okobj := sch.objects[object]
	if !okobj {
		visited := make(map[string]int, 0)
		visited[s.current] = 1
		if !ok {
			visited[defaultTheme] = 1
		}

		for {
			s.LoadTheme(sch.parent)
			sch = s.themes[sch.parent]
			obj, okobj = sch.objects[object]

			if ok {
				break
			} else {
				if _, okSch := visited[sch.parent]; okSch {
					panic("Object: " + object + ". Theme loop detected: " + sch.title + " --> " + sch.parent)
				} else {
					visited[sch.parent] = 1
				}
			}
		}
	}

	return obj
}

// ThemeNames returns the list of short theme names (file names)
func (s *ThemeManager) ThemeNames() []string {
	var str []string
	str = append(str, defaultTheme)

	path := s.themePath
	if path == "" {
		path = "." + string(os.PathSeparator)
	}
	files, err := ioutil.ReadDir(path)
	if err != nil {
		panic("Failed to read theme directory: " + s.themePath)
	}

	for _, f := range files {
		if !f.IsDir() {
			str = append(str, f.Name())
		}
	}

	return str
}

// CurrentTheme returns name of the current theme
func (s *ThemeManager) CurrentTheme() string {
	return s.current
}

// SetCurrentTheme changes the current theme.
// Returns false if changing failed - e.g, theme does not exist
func (s *ThemeManager) SetCurrentTheme(name string) bool {
	if _, ok := s.themes[name]; !ok {
		tnames := s.ThemeNames()
		for _, theme := range tnames {
			if theme == name {
				s.LoadTheme(theme)
				break
			}
		}
	}

	if _, ok := s.themes[name]; ok {
		s.current = name
		return true
	}
	return false
}

// ThemePath returns the current directory with theme inside it
func (s *ThemeManager) ThemePath() string {
	return s.themePath
}

// SetThemePath changes the directory that contains themes.
// If new path does not equal old one, theme list reloads
func (s *ThemeManager) SetThemePath(path string) {
	if path == s.themePath {
		return
	}

	s.themePath = path
	s.Reset()
}

// LoadTheme loads the theme if it is not in the cache already.
// If theme is in the cache LoadTheme does nothing
func (s *ThemeManager) LoadTheme(name string) {
	if _, ok := s.themes[name]; ok {
		return
	}

	theme := theme{parent: "", title: "", author: ""}
	theme.colors = make(map[string]term.Attribute, 0)
	theme.objects = make(map[string]string, 0)

	file, err := os.Open(s.themePath + string(os.PathSeparator) + name)
	if err != nil {
		panic("Failed to open theme " + name + " : " + err.Error())
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		line = strings.Trim(line, " ")

		// skip comments
		if strings.HasPrefix(line, "#") || strings.HasPrefix(line, "/") {
			continue
		}

		// skip invalid lines
		if !strings.Contains(line, "=") {
			continue
		}

		parts := strings.SplitN(line, "=", 2)
		key := strings.Trim(parts[0], " ")
		value := strings.Trim(parts[1], " ")

		low := strings.ToLower(key)
		if low == "parent" {
			theme.parent = value
		} else if low == "author" {
			theme.author = value
		} else if low == "name" || low == "title" {
			theme.title = value
		} else if low == "version" {
			theme.version = value
		} else if strings.HasSuffix(key, "Back") || strings.HasSuffix(key, "Text") {
			c := StringToColor(value)
			if c%32 == 0 {
				panic("Failed to read color: " + value)
			}
			theme.colors[key] = c
		} else {
			theme.objects[key] = value
		}
	}

	if theme.parent == "" {
		theme.parent = "default"
	}

	s.themes[name] = theme
}

// ReLoadTheme refresh cache entry for the theme with new
// data loaded from file. Use it to apply theme changes on
// the fly without resetting manager or restarting application
func (s *ThemeManager) ReLoadTheme(name string) {
	if _, ok := s.themes[name]; ok {
		delete(s.themes, name)
	}

	s.LoadTheme(name)
}

// ThemeInfo returns detailed info about theme
func (s *ThemeManager) ThemeInfo(name string) ThemeInfo {
	s.LoadTheme(name)
	var theme ThemeInfo
	if t, ok := s.themes[name]; !ok {
		theme.parent = t.parent
		theme.title = t.title
		theme.version = t.version
	}
	return theme
}
<commit_msg>closes #26 - load only *.theme files as theme one<commit_after>package clui

import (
	"bufio"
	term "github.com/nsf/termbox-go"
	"io/ioutil"
	"os"
	"strings"
)

/*
ThemeManager support for controls.
The current implementation is limited but later the manager will be
able to load a requested theme on demand and use deep inheritance.
Theme 'default' exists always - it is predefinded and always complete.
User-defined themes may omit any theme section, all omitted items
are loaded from parent theme. The only required property that a user-
defined theme must have is a theme name.
*/
type ThemeManager struct {
	// available theme list
	themes map[string]theme
	// name of the current theme
	current   string
	themePath string
	version   string
}

const themeSuffix = ".theme"

// ThemeInfo is a detailed information about theme:
// title, author, version number
type ThemeInfo struct {
	parent  string
	title   string
	author  string
	version string
}

/*
A theme structure. It keeps all colors, characters for the theme.
Parent property determines a theme name that is used if a requested
theme object is not declared in the current one. If no parent is
defined then the library uses default built-in theme.
*/
type theme struct {
	parent  string
	title   string
	author  string
	version string
	colors  map[string]term.Attribute
	objects map[string]string
}

const defaultTheme = "default"

// NewThemeManager creates a new theme manager
func NewThemeManager() *ThemeManager {
	sm := new(ThemeManager)

	sm.Reset()

	return sm
}

// Reset removes all loaded themes from cache and reinitialize
// the default theme
func (s *ThemeManager) Reset() {
	s.current = defaultTheme
	s.themes = make(map[string]theme, 0)

	defTheme := theme{parent: "", title: "Default Theme", author: "V. Markelov", version: "1.0"}
	defTheme.colors = make(map[string]term.Attribute, 0)
	defTheme.objects = make(map[string]string, 0)

	defTheme.objects[ObjSingleBorder] = "─│┌┐└┘"
	defTheme.objects[ObjDoubleBorder] = "═║╔╗╚╝"
	defTheme.objects[ObjEdit] = "←→V"
	defTheme.objects[ObjScrollBar] = "|O^v"
	defTheme.objects[ObjViewButtons] = "^↓○[]"
	defTheme.objects[ObjCheckBox] = "[] X?"
	defTheme.objects[ObjRadio] = "() *"
	defTheme.objects[ObjProgressBar] = "░▒"

	defTheme.colors[ColorDisabledText] = ColorBlackBold
	defTheme.colors[ColorDisabledBack] = ColorWhite
	defTheme.colors[ColorText] = ColorWhite
	defTheme.colors[ColorBack] = ColorBlackBold
	defTheme.colors[ColorViewBack] = ColorBlackBold
	defTheme.colors[ColorViewText] = ColorWhite

	defTheme.colors[ColorControlText] = ColorWhite
	defTheme.colors[ColorControlBack] = ColorBlack
	defTheme.colors[ColorControlActiveText] = ColorWhite
	defTheme.colors[ColorControlActiveBack] = ColorMagenta
	defTheme.colors[ColorControlShadow] = ColorBlue
	defTheme.colors[ColorControlDisabledText] = ColorWhite
	defTheme.colors[ColorControlDisabledBack] = ColorBlackBold

	defTheme.colors[ColorEditText] = ColorBlack
	defTheme.colors[ColorEditBack] = ColorWhite
	defTheme.colors[ColorEditActiveText] = ColorBlack
	defTheme.colors[ColorEditActiveBack] = ColorWhiteBold
	defTheme.colors[ColorSelectionText] = ColorYellow
	defTheme.colors[ColorSelectionBack] = ColorBlue

	defTheme.colors[ColorScrollBack] = ColorBlackBold
	defTheme.colors[ColorScrollText] = ColorWhite
	defTheme.colors[ColorThumbBack] = ColorBlackBold
	defTheme.colors[ColorThumbText] = ColorWhite

	defTheme.colors[ColorProgressText] = ColorBlue
	defTheme.colors[ColorProgressBack] = ColorBlackBold
	defTheme.colors[ColorProgressActiveText] = ColorBlack
	defTheme.colors[ColorProgressActiveBack] = ColorBlueBold

	s.themes[defaultTheme] = defTheme
}

// SysColor returns attribute by its id for the current theme
func (s *ThemeManager) SysColor(color string) term.Attribute {
	sch, ok := s.themes[s.current]
	if !ok {
		sch = s.themes[defaultTheme]
	}

	clr, okclr := sch.colors[color]
	if !okclr {
		visited := make(map[string]int, 0)
		visited[s.current] = 1
		if !ok {
			visited[defaultTheme] = 1
		}

		for {
			s.LoadTheme(sch.parent)
			sch = s.themes[sch.parent]
			clr, okclr = sch.colors[color]

			if ok {
				break
			} else {
				if _, okSch := visited[sch.parent]; okSch {
					panic("Color + " + color + ". Theme loop detected: " + sch.title + " --> " + sch.parent)
				} else {
					visited[sch.parent] = 1
				}
			}
		}
	}

	return clr
}

// SysObject returns object look by its id for the current
// theme. E.g, border lines for frame or arrows for scrollbar
func (s *ThemeManager) SysObject(object string) string {
	sch, ok := s.themes[s.current]
	if !ok {
		sch = s.themes[defaultTheme]
	}

	obj, okobj := sch.objects[object]
	if !okobj {
		visited := make(map[string]int, 0)
		visited[s.current] = 1
		if !ok {
			visited[defaultTheme] = 1
		}

		for {
			s.LoadTheme(sch.parent)
			sch = s.themes[sch.parent]
			obj, okobj = sch.objects[object]

			if ok {
				break
			} else {
				if _, okSch := visited[sch.parent]; okSch {
					panic("Object: " + object + ". Theme loop detected: " + sch.title + " --> " + sch.parent)
				} else {
					visited[sch.parent] = 1
				}
			}
		}
	}

	return obj
}

// ThemeNames returns the list of short theme names (file names)
func (s *ThemeManager) ThemeNames() []string {
	var str []string
	str = append(str, defaultTheme)

	path := s.themePath
	if path == "" {
		path = "." + string(os.PathSeparator)
	}
	files, err := ioutil.ReadDir(path)
	if err != nil {
		panic("Failed to read theme directory: " + s.themePath)
	}

	for _, f := range files {
		name := f.Name()
		if !f.IsDir() && strings.HasSuffix(name, themeSuffix) {
			str = append(str, strings.TrimSuffix(name, themeSuffix))
		}
	}

	return str
}

// CurrentTheme returns name of the current theme
func (s *ThemeManager) CurrentTheme() string {
	return s.current
}

// SetCurrentTheme changes the current theme.
// Returns false if changing failed - e.g, theme does not exist
func (s *ThemeManager) SetCurrentTheme(name string) bool {
	if _, ok := s.themes[name]; !ok {
		tnames := s.ThemeNames()
		for _, theme := range tnames {
			if theme == name {
				s.LoadTheme(theme)
				break
			}
		}
	}

	if _, ok := s.themes[name]; ok {
		s.current = name
		return true
	}
	return false
}

// ThemePath returns the current directory with theme inside it
func (s *ThemeManager) ThemePath() string {
	return s.themePath
}

// SetThemePath changes the directory that contains themes.
// If new path does not equal old one, theme list reloads
func (s *ThemeManager) SetThemePath(path string) {
	if path == s.themePath {
		return
	}

	s.themePath = path
	s.Reset()
}

// LoadTheme loads the theme if it is not in the cache already.
// If theme is in the cache LoadTheme does nothing
func (s *ThemeManager) LoadTheme(name string) {
	if _, ok := s.themes[name]; ok {
		return
	}

	theme := theme{parent: "", title: "", author: ""}
	theme.colors = make(map[string]term.Attribute, 0)
	theme.objects = make(map[string]string, 0)

	file, err := os.Open(s.themePath + string(os.PathSeparator) + name + themeSuffix)
	if err != nil {
		panic("Failed to open theme " + name + " : " + err.Error())
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		line = strings.Trim(line, " ")

		// skip comments
		if strings.HasPrefix(line, "#") || strings.HasPrefix(line, "/") {
			continue
		}

		// skip invalid lines
		if !strings.Contains(line, "=") {
			continue
		}

		parts := strings.SplitN(line, "=", 2)
		key := strings.Trim(parts[0], " ")
		value := strings.Trim(parts[1], " ")

		low := strings.ToLower(key)
		if low == "parent" {
			theme.parent = value
		} else if low == "author" {
			theme.author = value
		} else if low == "name" || low == "title" {
			theme.title = value
		} else if low == "version" {
			theme.version = value
		} else if strings.HasSuffix(key, "Back") || strings.HasSuffix(key, "Text") {
			c := StringToColor(value)
			if c%32 == 0 {
				panic("Failed to read color: " + value)
			}
			theme.colors[key] = c
		} else {
			theme.objects[key] = value
		}
	}

	if theme.parent == "" {
		theme.parent = "default"
	}

	s.themes[name] = theme
}

// ReLoadTheme refresh cache entry for the theme with new
// data loaded from file. Use it to apply theme changes on
// the fly without resetting manager or restarting application
func (s *ThemeManager) ReLoadTheme(name string) {
	if _, ok := s.themes[name]; ok {
		delete(s.themes, name)
	}

	s.LoadTheme(name)
}

// ThemeInfo returns detailed info about theme
func (s *ThemeManager) ThemeInfo(name string) ThemeInfo {
	s.LoadTheme(name)
	var theme ThemeInfo
	if t, ok := s.themes[name]; !ok {
		theme.parent = t.parent
		theme.title = t.title
		theme.version = t.version
	}
	return theme
}
<|endoftext|>


<commit_before>package boardgame

import (
	"container/heap"
	"time"
)

type Timer struct {
	//Id will be an opaque identifier that is used to keep track of the
	//corresponding underlying Timer object in the game engine. It is not
	//meaningful to inspect yourself and should not be modified.
	Id       int
	statePtr *state
}

func NewTimer() *Timer {
	return &Timer{}
}

func (t *Timer) Copy() *Timer {
	var result Timer
	result = *t
	return &result
}

func (t *Timer) MarshalJSON() ([]byte, error) {
	obj := map[string]interface{}{
		"Id":       t.Id,
		"TimeLeft": t.TimeLeft(),
	}

	return DefaultMarshalJSON(obj)
}

//Active returns true if the timer is active and counting down.
func (t *Timer) Active() bool {
	return t.Id == 0
}

//TimeLeft returns the number of nanoseconds left until this timer fires.
func (t *Timer) TimeLeft() int {
	if !t.Active() {
		return 0
	}

	return t.statePtr.game.manager.getTimer(t.Id)
}

//Start starts the timer. After nanoseconds nanoseconds have passed, the Move
//will be proposed via proposeMove.
func (t *Timer) Start(nanoseconds int, move Move) {
	game := t.statePtr.game
	manager := game.manager

	t.Id = manager.registerTimer(nanoseconds, game, move)
}

//Cancel cancels an active timer. If the timer is not active, it has no
//effect. Returns true if the timer was active and canceled, false if the
//timer was not active.
func (t *Timer) Cancel() bool {
	if !t.Active() {
		return false
	}
	manager := t.statePtr.game.manager

	manager.cancelTimer(t.Id)

	t.Id = 0

	return true
}

/**************************************
 *
 * GameManager timer-related methods
 *
 **************************************/

//registerTimer registers an implementation level-timer, so that after
//nanoseconds have passed the given move will be proposed to the given game.
//Returns an Id that can be used ot cancel or fetch information on the timer
//in the future. Only designed to be called from Timer.Start()
func (g *GameManager) registerTimer(nanoseconds int, game *Game, move Move) int {
	//TOOD: actually implement this
	return 0
}

//cancelTimer cancels the timer with the given Id. IF there there is no such
//timer, or that timer is already over, it is a no-op.
func (g *GameManager) cancelTimer(id int) {
	//TODO: actually implement this.
}

//getTimer returns the number of nanoseconds left for the timer with the given
//id.
func (g *GameManager) getTimer(id int) int {
	//TODO: actually implement this.
	return 0
}

type timerRecord struct {
	id       int
	index    int
	fireTime time.Time
	game     *Game
	move     Move
}

type timerQueue []*timerRecord

type timerManager struct {
	nextId      int
	records     timerQueue
	recordsById map[int]*timerRecord
}

func newTimerManager() *timerManager {
	return &timerManager{
		nextId:      0,
		records:     make(timerQueue, 0),
		recordsById: make(map[int]*timerRecord),
	}
}

func (t *timerManager) RegisterTimer(nanoseconds int, game *Game, move Move) int {
	record := &timerRecord{
		id:       t.nextId,
		index:    -1,
		fireTime: time.Now().Add(time.Duration(nanoseconds)),
		game:     game,
		move:     move,
	}
	t.nextId++

	t.recordsById[record.id] = record

	heap.Push(&t.records, record)

	return record.id
}

func (t *timerManager) GetTimerRemaining(id int) int {
	record := t.recordsById[id]

	if record == nil {
		return 0
	}

	duration := record.fireTime.Sub(time.Now())

	if duration < 0 {
		duration = 0
	}

	return int(duration)
}

//Should be called regularly by the manager to tell this to check and see if
//any timers have fired, and execute them if so.
func (t *timerManager) Tick() {
	for t.nextTimerFired() {
		record := t.popNext()
		if record == nil {
			continue
		}
		if err := <-record.game.ProposeMove(record.move); err != nil {
			//TODO: log the error or something
			panic(err)
		}
	}
}

//Whether the next timer in the queue is already fired
func (t *timerManager) nextTimerFired() bool {
	if len(t.records) == 0 {
		return false
	}

	record := t.records[0]

	if record.fireTime.Sub(time.Now()) < 0 {
		return true
	}

	return false
}

func (t *timerManager) popNext() *timerRecord {
	if !t.nextTimerFired() {
		return nil
	}

	x := heap.Pop(&t.records)

	return x.(*timerRecord)
}

func (t timerQueue) Len() int {
	return len(t)
}

func (t timerQueue) Less(i, j int) bool {
	return t[i].fireTime.Sub(t[j].fireTime) < 0
}

func (t timerQueue) Swap(i, j int) {
	t[i], t[j] = t[j], t[i]
	t[i].index = i
	t[j].index = j
}

//DO NOT USE THIS DIRECTLY. Use heap.Push(t)
func (t *timerQueue) Push(x interface{}) {
	n := len(*t)
	item := x.(*timerRecord)
	item.index = n
	*t = append(*t, item)
}

//DO NOT USE THIS DIRECTLY. Use heap.Pop()
func (t *timerQueue) Pop() interface{} {
	old := *t
	n := len(old)
	item := old[n-1]
	item.index = -1
	*t = old[0 : n-1]
	return item
}
<commit_msg>Factor out the time logic in timerManager to one place. Part of #178.<commit_after>package boardgame

import (
	"container/heap"
	"time"
)

type Timer struct {
	//Id will be an opaque identifier that is used to keep track of the
	//corresponding underlying Timer object in the game engine. It is not
	//meaningful to inspect yourself and should not be modified.
	Id       int
	statePtr *state
}

func NewTimer() *Timer {
	return &Timer{}
}

func (t *Timer) Copy() *Timer {
	var result Timer
	result = *t
	return &result
}

func (t *Timer) MarshalJSON() ([]byte, error) {
	obj := map[string]interface{}{
		"Id":       t.Id,
		"TimeLeft": t.TimeLeft(),
	}

	return DefaultMarshalJSON(obj)
}

//Active returns true if the timer is active and counting down.
func (t *Timer) Active() bool {
	return t.Id == 0
}

//TimeLeft returns the number of nanoseconds left until this timer fires.
func (t *Timer) TimeLeft() int {
	if !t.Active() {
		return 0
	}

	return t.statePtr.game.manager.getTimer(t.Id)
}

//Start starts the timer. After nanoseconds nanoseconds have passed, the Move
//will be proposed via proposeMove.
func (t *Timer) Start(nanoseconds int, move Move) {
	game := t.statePtr.game
	manager := game.manager

	t.Id = manager.registerTimer(nanoseconds, game, move)
}

//Cancel cancels an active timer. If the timer is not active, it has no
//effect. Returns true if the timer was active and canceled, false if the
//timer was not active.
func (t *Timer) Cancel() bool {
	if !t.Active() {
		return false
	}
	manager := t.statePtr.game.manager

	manager.cancelTimer(t.Id)

	t.Id = 0

	return true
}

/**************************************
 *
 * GameManager timer-related methods
 *
 **************************************/

//registerTimer registers an implementation level-timer, so that after
//nanoseconds have passed the given move will be proposed to the given game.
//Returns an Id that can be used ot cancel or fetch information on the timer
//in the future. Only designed to be called from Timer.Start()
func (g *GameManager) registerTimer(nanoseconds int, game *Game, move Move) int {
	//TOOD: actually implement this
	return 0
}

//cancelTimer cancels the timer with the given Id. IF there there is no such
//timer, or that timer is already over, it is a no-op.
func (g *GameManager) cancelTimer(id int) {
	//TODO: actually implement this.
}

//getTimer returns the number of nanoseconds left for the timer with the given
//id.
func (g *GameManager) getTimer(id int) int {
	//TODO: actually implement this.
	return 0
}

type timerRecord struct {
	id       int
	index    int
	fireTime time.Time
	game     *Game
	move     Move
}

func (t *timerRecord) TimeRemaining() int {
	duration := t.fireTime.Sub(time.Now())

	if duration < 0 {
		duration = 0
	}

	return int(duration)
}

type timerQueue []*timerRecord

type timerManager struct {
	nextId      int
	records     timerQueue
	recordsById map[int]*timerRecord
}

func newTimerManager() *timerManager {
	return &timerManager{
		nextId:      0,
		records:     make(timerQueue, 0),
		recordsById: make(map[int]*timerRecord),
	}
}

func (t *timerManager) RegisterTimer(nanoseconds int, game *Game, move Move) int {
	record := &timerRecord{
		id:       t.nextId,
		index:    -1,
		fireTime: time.Now().Add(time.Duration(nanoseconds)),
		game:     game,
		move:     move,
	}
	t.nextId++

	t.recordsById[record.id] = record

	heap.Push(&t.records, record)

	return record.id
}

func (t *timerManager) GetTimerRemaining(id int) int {
	record := t.recordsById[id]

	if record == nil {
		return 0
	}

	return record.TimeRemaining()
}

//Should be called regularly by the manager to tell this to check and see if
//any timers have fired, and execute them if so.
func (t *timerManager) Tick() {
	for t.nextTimerFired() {
		record := t.popNext()
		if record == nil {
			continue
		}
		if err := <-record.game.ProposeMove(record.move); err != nil {
			//TODO: log the error or something
			panic(err)
		}
	}
}

//Whether the next timer in the queue is already fired
func (t *timerManager) nextTimerFired() bool {
	if len(t.records) == 0 {
		return false
	}

	record := t.records[0]

	return record.TimeRemaining() <= 0
}

func (t *timerManager) popNext() *timerRecord {
	if !t.nextTimerFired() {
		return nil
	}

	x := heap.Pop(&t.records)

	return x.(*timerRecord)
}

func (t timerQueue) Len() int {
	return len(t)
}

func (t timerQueue) Less(i, j int) bool {
	return t[i].fireTime.Sub(t[j].fireTime) < 0
}

func (t timerQueue) Swap(i, j int) {
	t[i], t[j] = t[j], t[i]
	t[i].index = i
	t[j].index = j
}

//DO NOT USE THIS DIRECTLY. Use heap.Push(t)
func (t *timerQueue) Push(x interface{}) {
	n := len(*t)
	item := x.(*timerRecord)
	item.index = n
	*t = append(*t, item)
}

//DO NOT USE THIS DIRECTLY. Use heap.Pop()
func (t *timerQueue) Pop() interface{} {
	old := *t
	n := len(old)
	item := old[n-1]
	item.index = -1
	*t = old[0 : n-1]
	return item
}
<|endoftext|>


<commit_before>package ts

import (
	// "fmt"
	//"io/ioutil"
	"log"
	//"os"
	"regifted/util"
	"testing"
)

func awfulStateSetup() {
	DeleteState()
	Init()
}

// const TESTFP = "test1.ts"

// func TestReader(t *testing.T) {
// 	// compare file size from os and then counted from buffer
// 	fp, err := os.Open(TESTFP)
// 	if err != nil {
// 		t.Fatalf("did not open test file\n")
// 	}
// 	fi, err := fp.Stat()
// 	if err != nil {
// 		// Could not obtain stat, handle error
// 	}
// 	fileSizeOS := fi.Size()
// 	fp.Close()

// 	bytes, err := ioutil.ReadFile(TESTFP)
// 	if err != nil {
// 		t.Fatalf("did not open test file\n")
// 	}
// 	fileSizeBuff := len(bytes)
// 	if uint64(fileSizeOS) != uint64(fileSizeBuff) {
// 		t.Error("file size calculated from buffer does not match size returned from OS\n")
// 	}

// 	// t.Error( "print" )
// }

func TestTransportPacketRead(t *testing.T) {
	// ts header
	// n_bits value
	// 8			sync byte is always 'G' 0x47
	// 1			transport error flag
	// 1			payload unit start indicator
	// 1			transport priority indicator
	// 13			pid
	// 2			scrambling control
	// 1			adapaption field exists
	// 1			contains payload
	// 4			continuity counter
	//
	// bars are byte marks. header is 4 bytes total; i.e. 8 hex chars.
	//                     |                    |                    |
	// [sync]                [tei] [psi] [tp] [         pid        ]   [sc] [af] [pf] [cont]

	// bin  	hex
	// 0000 	0
	// 0001 	1
	// 0010 	2
	// 0011 	3
	// 0100 	4
	// 0101 	5
	// 0110 	6
	// 0111 	7
	// 1000 	8
	// 1001 	9
	// 1010 	A
	// 1011 	B
	// 1100 	C
	// 1101 	D
	// 1110 	E
	// 1111 	F

	// could endian-ness matter theoretically?
	var packetBytes []byte
	var packetString string
	var err error

	awfulStateSetup()

	packetString = `
		47 02 62 17 80 70 24 F1 31 D4 33 F3 88 8B 23 5A
		94 13 D4 1D DD CD 61 D8 73 08 82 88 B1 13 85 BB
		D0 93 C4 41 54 BE 68 70 70 EA 3D CF 70 1F 50 CD
		4E 26 A5 00 D2 1C C4 4C EE 71 C4 68 38 2B 91 45
		44 40 F7 50 85 5D 7F DD 62 58 28 72 D6 4F 39 4A
		A1 86 83 28 00 9E 2E 01 F0 AE 79 CC 16 F1 3C 19
		7F FE A3 25 F0 1A 5B 0E CC 45 B6 1C D8 EC 2C E3
		48 E7 78 9F F1 E1 62 30 78 1C 80 D5 D0 CF DA C0
		6A A0 01 F4 03 FA AF 80 B2 34 DE 01 DC 14 46 72
		0F 23 CB 70 21 DE AE 70 1D A0 F1 22 E1 10 79 1C
		58 EA D4 4F 50 07 D1 3E D8 77 E4 63 65 2C E6 D0
		9A 11 82 26 CC 62 D6 2E 00 1F DA C3`
	err = util.GenerateBytesFromString(&packetBytes, &packetString)
	if err != nil {
		log.Printf("EE problem in test suite")
	}
	packet := TsPacket{byteChunk: packetBytes}
	packet.Read()
	if packet.sync != 0X47 { // 71 = 'G' = 0x47
		t.Error("Transport Stream Packet read " +
			"sync byte incorrectly.")
	}
	if packet.transportError != false {
		t.Error("Transport Stream Packet read " +
			"transport error bit incorrectly.")
	}
	if packet.unitStart != false {
		t.Error("Transport Stream Packet read " +
			"unit start incorrectly.")
	}
	if packet.priority != false {
		t.Error("Transport Stream Packet read " +
			"priority incorrectly.")
	}
	if packet.pid != 610 {
		t.Error("Transport Stream Packet read " +
			"packet id incorrectly.")
	}
	if packet.scramble != 0 {
		t.Error("Transport Stream Packet read " +
			"scrambling control incorrectly.")
	}
	if packet.hasAdaptation != false {
		t.Error("Transport Stream Packet read " +
			"adaptation indiciator incorrectly.")
	}
	if packet.hasPayload != true {
		t.Error("Transport Stream Packet read " +
			"payload indicator incorrectly.")
	}
	if packet.continuity != 7 {
		t.Error("Transport Stream Packet read " +
			"continuity incorrectly.")
	}

	awfulStateSetup()
	packetString = `
   47 00 01 17 80 70 24 F1 31 D4 33 F3 88 8B 23 5A
   94 13 D4 1D DD CD 61 D8 73 08 82 88 B1 13 85 BB
   D0 93 C4 41 54 BE 68 70 70 EA 3D CF 70 1F 50 CD
   4E 26 A5 00 D2 1C C4 4C EE 71 C4 68 38 2B 91 45
   44 40 F7 50 85 5D 7F DD 62 58 28 72 D6 4F 39 4A
   A1 86 83 28 00 9E 2E 01 F0 AE 79 CC 16 F1 3C 19
   7F FE A3 25 F0 1A 5B 0E CC 45 B6 1C D8 EC 2C E3
   48 E7 78 9F F1 E1 62 30 78 1C 80 D5 D0 CF DA C0
   6A A0 01 F4 03 FA AF 80 B2 34 DE 01 DC 14 46 72
   0F 23 CB 70 21 DE AE 70 1D A0 F1 22 E1 10 79 1C
   58 EA D4 4F 50 07 D1 3E D8 77 E4 63 65 2C E6 D0
   9A 11 82 26 CC 62 D6 2E 00 1F DA C3`
	err = util.GenerateBytesFromString(&packetBytes, &packetString)
	if err != nil {
		log.Printf("EE problem in test suite")
	}
	packet = TsPacket{byteChunk: packetBytes}
	packet.Read()
	if packet.sync != 0X47 { // 71 = 'G' = 0x47
		t.Error("Transport Stream Packet read " +
			"sync byte incorrectly.")
	}
	if packet.transportError != false {
		t.Error("Transport Stream Packet read " +
			"transport error bit incorrectly.")
	}
	if packet.unitStart != false {
		t.Error("Transport Stream Packet read " +
			"unit start incorrectly.")
	}
	if packet.priority != false {
		t.Error("Transport Stream Packet read " +
			"priority incorrectly.")
	}
	if packet.pid != 1 {
		t.Error("Transport Stream Packet read " +
			"packet id incorrectly.")
	}
	if packet.scramble != 0 {
		t.Error("Transport Stream Packet read " +
			"scrambling control incorrectly.")
	}
	if packet.hasAdaptation != false {
		t.Error("Transport Stream Packet read " +
			"adaptation indiciator incorrectly.")
	}
	if packet.hasPayload != true {
		t.Error("Transport Stream Packet read " +
			"payload indicator incorrectly.")
	}
	if packet.continuity != 7 {
		t.Error("Transport Stream Packet read " +
			"continuity incorrectly.")
	}

	// PAT
	awfulStateSetup()
	packetString = `
  4740 0010 0000 b00d 0001 c100 0000 01f0
  002a b104 b2ff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff`
	err = util.GenerateBytesFromString(&packetBytes, &packetString)
	if err != nil {
		log.Printf("EE problem in test suite")
	}
	packet = TsPacket{byteChunk: packetBytes}
	packet.Read()
	if packet.sync != 0X47 { // 71 = 'G' = 0x47
		t.Error("Transport Stream Packet read " +
			"sync byte incorrectly.")
	}
	if packet.transportError != false {
		t.Error("Transport Stream Packet read " +
			"transport error bit incorrectly.")
	}
	if packet.unitStart != true {
		t.Error("Transport Stream Packet read " +
			"unit start incorrectly.")
	}
	if packet.priority != false {
		t.Error("Transport Stream Packet read " +
			"priority incorrectly.")
	}
	if packet.pid != 0 {
		t.Error("Transport Stream Packet read " +
			"packet id incorrectly.")
	}
	if packet.scramble != 0 {
		t.Error("Transport Stream Packet read " +
			"scrambling control incorrectly.")
	}
	if packet.hasAdaptation != false {
		t.Error("Transport Stream Packet read " +
			"adaptation indiciator incorrectly.")
	}
	if packet.hasPayload != true {
		t.Error("Transport Stream Packet read " +
			"payload indicator incorrectly.")
	}
	if packet.continuity != 0 {
		t.Error("Transport Stream Packet read " +
			"continuity incorrectly.")
	}

	// incorrect sync byte on PAT
	packetString = `
  4640 0010 0000 b00d 0001 c100 0000 01f0
  002a b104 b2ff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff`
	err = util.GenerateBytesFromString(&packetBytes, &packetString)
	if err != nil {
		log.Printf("EE problem in test suite")
	}
	packet = TsPacket{byteChunk: packetBytes}
	packet.Read()
	if packet.sync == 0X47 { // 71 = 'G' = 0x47
		t.Error("Transport Stream Packet read " +
			"sync byte incorrectly.")
	}
	if packet.transportError != false {
		t.Error("Transport Stream Packet read " +
			"transport error bit incorrectly.")
	}
	if packet.unitStart != false {
		t.Error("Transport Stream Packet read " +
			"unit start incorrectly.")
	}
	if packet.priority != false {
		t.Error("Transport Stream Packet read " +
			"priority incorrectly.")
	}
	if packet.pid != 0 {
		t.Error("Transport Stream Packet read " +
			"packet id incorrectly.")
	}
	if packet.scramble != 0 {
		t.Error("Transport Stream Packet read " +
			"scrambling control incorrectly.")
	}
	if packet.hasAdaptation != false {
		t.Error("Transport Stream Packet read " +
			"adaptation indiciator incorrectly.")
	}
	if packet.hasPayload != false {
		t.Error("Transport Stream Packet read " +
			"payload indicator incorrectly.")
	}
	if packet.continuity != 0 {
		t.Error("Transport Stream Packet read " +
			"continuity incorrectly.")
	}

	// empty packet
	packet = TsPacket{}
	packet.Read()
}

func TestInit(t *testing.T) {
	var rc bool
	DeleteState()
	// todo( mathew guest ) assert objects empty
	rc = Init()
	if rc == false {
		t.Error("initial Init() failed")
	}
	rc = Init()
	// todo( mathew guest ) assert objects unchanged
	if rc == true {
		t.Error("secondary Init() returned success when it should have failed")
	}
}
<commit_msg>updated to work with state objects instead of lib-globals<commit_after>package ts

import (
	// "fmt"
	//"io/ioutil"
	"log"
	//"os"
	"regifted/util"
	"testing"
)

func awfulStateSetup() {
	state := Load(nil)
	_ = state
	DeleteState()
	// ts.Init()
}

// const TESTFP = "test1.ts"

// func TestReader(t *testing.T) {
// 	// compare file size from os and then counted from buffer
// 	fp, err := os.Open(TESTFP)
// 	if err != nil {
// 		t.Fatalf("did not open test file\n")
// 	}
// 	fi, err := fp.Stat()
// 	if err != nil {
// 		// Could not obtain stat, handle error
// 	}
// 	fileSizeOS := fi.Size()
// 	fp.Close()

// 	bytes, err := ioutil.ReadFile(TESTFP)
// 	if err != nil {
// 		t.Fatalf("did not open test file\n")
// 	}
// 	fileSizeBuff := len(bytes)
// 	if uint64(fileSizeOS) != uint64(fileSizeBuff) {
// 		t.Error("file size calculated from buffer does not match size returned from OS\n")
// 	}

// 	// t.Error( "print" )
// }

func TestTransportPacketRead(t *testing.T) {
	// ts header
	// n_bits value
	// 8			sync byte is always 'G' 0x47
	// 1			transport error flag
	// 1			payload unit start indicator
	// 1			transport priority indicator
	// 13			pid
	// 2			scrambling control
	// 1			adapaption field exists
	// 1			contains payload
	// 4			continuity counter
	//
	// bars are byte marks. header is 4 bytes total; i.e. 8 hex chars.
	//                     |                    |                    |
	// [sync]                [tei] [psi] [tp] [         pid        ]   [sc] [af] [pf] [cont]

	// bin  	hex
	// 0000 	0
	// 0001 	1
	// 0010 	2
	// 0011 	3
	// 0100 	4
	// 0101 	5
	// 0110 	6
	// 0111 	7
	// 1000 	8
	// 1001 	9
	// 1010 	A
	// 1011 	B
	// 1100 	C
	// 1101 	D
	// 1110 	E
	// 1111 	F

	// could endian-ness matter theoretically?
	var packetBytes []byte
	var packetString string
	var err error

	awfulStateSetup()

	packetString = `
		47 02 62 17 80 70 24 F1 31 D4 33 F3 88 8B 23 5A
		94 13 D4 1D DD CD 61 D8 73 08 82 88 B1 13 85 BB
		D0 93 C4 41 54 BE 68 70 70 EA 3D CF 70 1F 50 CD
		4E 26 A5 00 D2 1C C4 4C EE 71 C4 68 38 2B 91 45
		44 40 F7 50 85 5D 7F DD 62 58 28 72 D6 4F 39 4A
		A1 86 83 28 00 9E 2E 01 F0 AE 79 CC 16 F1 3C 19
		7F FE A3 25 F0 1A 5B 0E CC 45 B6 1C D8 EC 2C E3
		48 E7 78 9F F1 E1 62 30 78 1C 80 D5 D0 CF DA C0
		6A A0 01 F4 03 FA AF 80 B2 34 DE 01 DC 14 46 72
		0F 23 CB 70 21 DE AE 70 1D A0 F1 22 E1 10 79 1C
		58 EA D4 4F 50 07 D1 3E D8 77 E4 63 65 2C E6 D0
		9A 11 82 26 CC 62 D6 2E 00 1F DA C3`
	err = util.GenerateBytesFromString(&packetBytes, &packetString)
	if err != nil {
		log.Printf("EE problem in test suite")
	}
	packet := TsPacket{byteChunk: packetBytes}
	packet.Read()
	if packet.sync != 0X47 { // 71 = 'G' = 0x47
		t.Error("Transport Stream Packet read " +
			"sync byte incorrectly.")
	}
	if packet.transportError != false {
		t.Error("Transport Stream Packet read " +
			"transport error bit incorrectly.")
	}
	if packet.unitStart != false {
		t.Error("Transport Stream Packet read " +
			"unit start incorrectly.")
	}
	if packet.priority != false {
		t.Error("Transport Stream Packet read " +
			"priority incorrectly.")
	}
	if packet.pid != 610 {
		t.Error("Transport Stream Packet read " +
			"packet id incorrectly.")
	}
	if packet.scramble != 0 {
		t.Error("Transport Stream Packet read " +
			"scrambling control incorrectly.")
	}
	if packet.hasAdaptation != false {
		t.Error("Transport Stream Packet read " +
			"adaptation indiciator incorrectly.")
	}
	if packet.hasPayload != true {
		t.Error("Transport Stream Packet read " +
			"payload indicator incorrectly.")
	}
	if packet.continuity != 7 {
		t.Error("Transport Stream Packet read " +
			"continuity incorrectly.")
	}

	awfulStateSetup()
	packetString = `
   47 00 01 17 80 70 24 F1 31 D4 33 F3 88 8B 23 5A
   94 13 D4 1D DD CD 61 D8 73 08 82 88 B1 13 85 BB
   D0 93 C4 41 54 BE 68 70 70 EA 3D CF 70 1F 50 CD
   4E 26 A5 00 D2 1C C4 4C EE 71 C4 68 38 2B 91 45
   44 40 F7 50 85 5D 7F DD 62 58 28 72 D6 4F 39 4A
   A1 86 83 28 00 9E 2E 01 F0 AE 79 CC 16 F1 3C 19
   7F FE A3 25 F0 1A 5B 0E CC 45 B6 1C D8 EC 2C E3
   48 E7 78 9F F1 E1 62 30 78 1C 80 D5 D0 CF DA C0
   6A A0 01 F4 03 FA AF 80 B2 34 DE 01 DC 14 46 72
   0F 23 CB 70 21 DE AE 70 1D A0 F1 22 E1 10 79 1C
   58 EA D4 4F 50 07 D1 3E D8 77 E4 63 65 2C E6 D0
   9A 11 82 26 CC 62 D6 2E 00 1F DA C3`
	err = util.GenerateBytesFromString(&packetBytes, &packetString)
	if err != nil {
		log.Printf("EE problem in test suite")
	}
	packet = TsPacket{byteChunk: packetBytes}
	packet.Read()
	if packet.sync != 0X47 { // 71 = 'G' = 0x47
		t.Error("Transport Stream Packet read " +
			"sync byte incorrectly.")
	}
	if packet.transportError != false {
		t.Error("Transport Stream Packet read " +
			"transport error bit incorrectly.")
	}
	if packet.unitStart != false {
		t.Error("Transport Stream Packet read " +
			"unit start incorrectly.")
	}
	if packet.priority != false {
		t.Error("Transport Stream Packet read " +
			"priority incorrectly.")
	}
	if packet.pid != 1 {
		t.Error("Transport Stream Packet read " +
			"packet id incorrectly.")
	}
	if packet.scramble != 0 {
		t.Error("Transport Stream Packet read " +
			"scrambling control incorrectly.")
	}
	if packet.hasAdaptation != false {
		t.Error("Transport Stream Packet read " +
			"adaptation indiciator incorrectly.")
	}
	if packet.hasPayload != true {
		t.Error("Transport Stream Packet read " +
			"payload indicator incorrectly.")
	}
	if packet.continuity != 7 {
		t.Error("Transport Stream Packet read " +
			"continuity incorrectly.")
	}

	// PAT
	awfulStateSetup()
	packetString = `
  4740 0010 0000 b00d 0001 c100 0000 01f0
  002a b104 b2ff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff`
	err = util.GenerateBytesFromString(&packetBytes, &packetString)
	if err != nil {
		log.Printf("EE problem in test suite")
	}
	packet = TsPacket{byteChunk: packetBytes}
	packet.Read()
	if packet.sync != 0X47 { // 71 = 'G' = 0x47
		t.Error("Transport Stream Packet read " +
			"sync byte incorrectly.")
	}
	if packet.transportError != false {
		t.Error("Transport Stream Packet read " +
			"transport error bit incorrectly.")
	}
	if packet.unitStart != true {
		t.Error("Transport Stream Packet read " +
			"unit start incorrectly.")
	}
	if packet.priority != false {
		t.Error("Transport Stream Packet read " +
			"priority incorrectly.")
	}
	if packet.pid != 0 {
		t.Error("Transport Stream Packet read " +
			"packet id incorrectly.")
	}
	if packet.scramble != 0 {
		t.Error("Transport Stream Packet read " +
			"scrambling control incorrectly.")
	}
	if packet.hasAdaptation != false {
		t.Error("Transport Stream Packet read " +
			"adaptation indiciator incorrectly.")
	}
	if packet.hasPayload != true {
		t.Error("Transport Stream Packet read " +
			"payload indicator incorrectly.")
	}
	if packet.continuity != 0 {
		t.Error("Transport Stream Packet read " +
			"continuity incorrectly.")
	}

	// incorrect sync byte on PAT
	packetString = `
  4640 0010 0000 b00d 0001 c100 0000 01f0
  002a b104 b2ff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff ffff ffff
  ffff ffff ffff ffff ffff ffff`
	err = util.GenerateBytesFromString(&packetBytes, &packetString)
	if err != nil {
		log.Printf("EE problem in test suite")
	}
	packet = TsPacket{byteChunk: packetBytes}
	packet.Read()
	if packet.sync == 0X47 { // 71 = 'G' = 0x47
		t.Error("Transport Stream Packet read " +
			"sync byte incorrectly.")
	}
	if packet.transportError != false {
		t.Error("Transport Stream Packet read " +
			"transport error bit incorrectly.")
	}
	if packet.unitStart != false {
		t.Error("Transport Stream Packet read " +
			"unit start incorrectly.")
	}
	if packet.priority != false {
		t.Error("Transport Stream Packet read " +
			"priority incorrectly.")
	}
	if packet.pid != 0 {
		t.Error("Transport Stream Packet read " +
			"packet id incorrectly.")
	}
	if packet.scramble != 0 {
		t.Error("Transport Stream Packet read " +
			"scrambling control incorrectly.")
	}
	if packet.hasAdaptation != false {
		t.Error("Transport Stream Packet read " +
			"adaptation indiciator incorrectly.")
	}
	if packet.hasPayload != false {
		t.Error("Transport Stream Packet read " +
			"payload indicator incorrectly.")
	}
	if packet.continuity != 0 {
		t.Error("Transport Stream Packet read " +
			"continuity incorrectly.")
	}

	// empty packet
	packet = TsPacket{}
	packet.Read()
}

func TestInit(t *testing.T) {
	var rc bool
	state := Load(nil)

	DeleteState()
	// todo( mathew guest ) assert objects empty
	rc = state.Init()
	if rc == false {
		t.Error("initial Init() failed")
	}
	rc = state.Init()
	// todo( mathew guest ) assert objects unchanged
	if rc == true {
		t.Error("secondary Init() returned success when it should have failed")
	}
}
<|endoftext|>


<commit_before>// Copyright (C) 2017 Google Inc.
//
// 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 trace

import (
	"bytes"
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/golang/protobuf/ptypes"
	"github.com/google/gapid/core/app/crash"
	"github.com/google/gapid/core/app/layout"
	"github.com/google/gapid/core/event/task"
	"github.com/google/gapid/core/log"
	"github.com/google/gapid/core/os/device"
	"github.com/google/gapid/core/os/device/bind"
	"github.com/google/gapid/core/os/file"
	"github.com/google/gapid/core/os/shell"
	"github.com/google/gapid/test/robot/job"
	"github.com/google/gapid/test/robot/job/worker"
	"github.com/google/gapid/test/robot/stash"

	_ "github.com/google/gapid/gapidapk"
)

const (
	traceTimeout = time.Hour
	// this string is returned when GAPIT fails to connect to the GAPIS, particularly due to ETXTBSY
	// look at https://github.com/google/gapid/pull/933 for more information
	retryString = "Failed to connect to the GAPIS server"
	// The value to use for gapit's --observe-frames flag.
	observeEveryNthFrame = 5
)

type client struct {
	store   *stash.Client
	manager Manager
	tempDir file.Path
}

// Run starts new trace client if any hardware is available.
func Run(ctx context.Context, store *stash.Client, manager Manager, tempDir file.Path) error {
	c := &client{store: store, manager: manager, tempDir: tempDir}
	job.OnDeviceAdded(ctx, c.onDeviceAdded)
	return nil
}

// TODO: not assume all android devices are valid trace targets
func (c *client) onDeviceAdded(ctx context.Context, host *device.Instance, target bind.Device) {
	traceOnTarget := func(ctx context.Context, t *Task) error {
		job.LockDevice(ctx, target)
		defer job.UnlockDevice(ctx, target)
		if target.Status(ctx) != bind.Status_Online {
			log.I(ctx, "Trying to trace %s on %s not started, device status %s",
				t.Input.Subject, target.Instance().GetSerial(), target.Status(ctx).String())
			return nil
		}
		return c.trace(ctx, target, t)
	}
	crash.Go(func() {
		if err := c.manager.Register(ctx, host, target.Instance(), traceOnTarget); err != nil {
			log.E(ctx, "Error running trace client: %v", err)
		}
	})
}

func (c *client) trace(ctx context.Context, d bind.Device, t *Task) error {
	if err := c.manager.Update(ctx, t.Action, job.Running, nil); err != nil {
		return err
	}
	var output *Output
	err := worker.RetryFunction(ctx, 4, time.Millisecond*100, func() (err error) {
		ctx, cancel := task.WithTimeout(ctx, traceTimeout)
		defer cancel()
		output, err = doTrace(ctx, t.Action, t.Input, c.store, d, c.tempDir)
		return
	})
	status := job.Succeeded
	if err != nil {
		status = job.Failed
		log.E(ctx, "Error running trace: %v", err)
	} else if output.Err != "" {
		status = job.Failed
		log.E(ctx, "Error during trace: %v", output.Err)
	}

	return c.manager.Update(ctx, t.Action, status, output)
}

// doTrace extracts input files and runs `gapit trace` on them, capturing the output. The output object will
// be partially filled in the event of an upload error from store in order to allow examination of the logs.
func doTrace(ctx context.Context, action string, in *Input, store *stash.Client, d bind.Device, tempDir file.Path) (*Output, error) {
	subject := tempDir.Join(action + ".apk")
	obb := tempDir.Join(action + ".obb")
	tracefile := tempDir.Join(action + ".gfxtrace")
	extractedDir := tempDir.Join(action + "_tools")
	extractedLayout, err := layout.NewPkgLayout(extractedDir, true)
	if err != nil {
		return nil, err
	}

	gapidAPK, err := extractedLayout.GapidApk(ctx, in.GetToolingLayout().GetGapidAbi())
	if err != nil {
		return nil, err
	}

	gapis, err := extractedLayout.Gapis(ctx)
	if err != nil {
		return nil, err
	}

	gapit, err := extractedLayout.Gapit(ctx)
	if err != nil {
		return nil, err
	}

	traceTime, err := ptypes.Duration(in.GetHints().GetTraceTime())
	if err != nil {
		traceTime = 10 * time.Minute // TODO: support Robot-wide override
	}

	defer func() {
		file.Remove(subject)
		file.Remove(tracefile)
		file.RemoveAll(extractedDir)
	}()
	if err := store.GetFile(ctx, in.Subject, subject); err != nil {
		return nil, err
	}
	if err := store.GetFile(ctx, in.Gapis, gapis); err != nil {
		return nil, err
	}
	if err := store.GetFile(ctx, in.Gapit, gapit); err != nil {
		return nil, err
	}
	if err := store.GetFile(ctx, in.GapidApk, gapidAPK); err != nil {
		return nil, err
	}

	params := []string{
		"trace",
		"-out", tracefile.System(),
		"-apk", subject.System(),
		"-for", traceTime.String(),
		"-disable-pcs",
		"-observe-frames", strconv.Itoa(observeEveryNthFrame),
		"-record-errors",
		"-gapii-device", d.Instance().Serial,
		"-api", in.GetHints().GetAPI(),
	}

	if frames := in.GetHints().GetObserveFrames(); frames > 0 {
		params = append(params, "-capture-frames", strconv.Itoa(int(frames*observeEveryNthFrame+1)))
	}
	if in.Obb != "" {
		if err := store.GetFile(ctx, in.Obb, obb); err != nil {
			return nil, err
		}
		defer func() {
			file.Remove(obb)
		}()
		params = append(params, "-obb", obb.System())
	}
	cmd := shell.Command(gapit.System(), params...)
	outBuf := &bytes.Buffer{}
	errBuf := &bytes.Buffer{}
	outputObj := &Output{}
	errs := []string{}
	log.I(ctx, "Running trace action %s", cmd)
	if err := cmd.Capture(outBuf, errBuf).Run(ctx); err != nil {
		if err := worker.NeedsRetry(err.Error()); err != nil {
			return nil, err
		}
		errs = append(errs, err.Error())
	}
	errs = append(errs, strings.TrimSpace(errBuf.String()))
	outputObj.Err = strings.Join(errs, "\n")
	if err := worker.NeedsRetry(outputObj.Err, retryString); err != nil {
		return nil, err
	}

	output := fmt.Sprintf("%s\n\n%s", cmd, strings.TrimSpace(outBuf.String()))
	log.I(ctx, output)
	logID, err := store.UploadString(ctx, stash.Upload{Name: []string{"trace.log"}, Type: []string{"text/plain"}}, output)
	if err != nil {
		return outputObj, err
	}
	outputObj.Log = logID
	traceID, err := store.UploadFile(ctx, tracefile)
	if err != nil {
		return outputObj, err
	}
	outputObj.Trace = traceID
	return outputObj, nil
}

type offlineDevice struct {
	bind.Device
}

func (offlineDevice) Status() bind.Status {
	return bind.Status_Offline
}
<commit_msg>Fix the robot invocation of `gapit trace`.<commit_after>// Copyright (C) 2017 Google Inc.
//
// 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 trace

import (
	"bytes"
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/golang/protobuf/ptypes"
	"github.com/google/gapid/core/app/crash"
	"github.com/google/gapid/core/app/layout"
	"github.com/google/gapid/core/event/task"
	"github.com/google/gapid/core/log"
	"github.com/google/gapid/core/os/device"
	"github.com/google/gapid/core/os/device/bind"
	"github.com/google/gapid/core/os/file"
	"github.com/google/gapid/core/os/shell"
	"github.com/google/gapid/test/robot/job"
	"github.com/google/gapid/test/robot/job/worker"
	"github.com/google/gapid/test/robot/stash"

	_ "github.com/google/gapid/gapidapk"
)

const (
	traceTimeout = time.Hour
	// this string is returned when GAPIT fails to connect to the GAPIS, particularly due to ETXTBSY
	// look at https://github.com/google/gapid/pull/933 for more information
	retryString = "Failed to connect to the GAPIS server"
	// The value to use for gapit's --observe-frames flag.
	observeEveryNthFrame = 5
)

type client struct {
	store   *stash.Client
	manager Manager
	tempDir file.Path
}

// Run starts new trace client if any hardware is available.
func Run(ctx context.Context, store *stash.Client, manager Manager, tempDir file.Path) error {
	c := &client{store: store, manager: manager, tempDir: tempDir}
	job.OnDeviceAdded(ctx, c.onDeviceAdded)
	return nil
}

// TODO: not assume all android devices are valid trace targets
func (c *client) onDeviceAdded(ctx context.Context, host *device.Instance, target bind.Device) {
	traceOnTarget := func(ctx context.Context, t *Task) error {
		job.LockDevice(ctx, target)
		defer job.UnlockDevice(ctx, target)
		if target.Status(ctx) != bind.Status_Online {
			log.I(ctx, "Trying to trace %s on %s not started, device status %s",
				t.Input.Subject, target.Instance().GetSerial(), target.Status(ctx).String())
			return nil
		}
		return c.trace(ctx, target, t)
	}
	crash.Go(func() {
		if err := c.manager.Register(ctx, host, target.Instance(), traceOnTarget); err != nil {
			log.E(ctx, "Error running trace client: %v", err)
		}
	})
}

func (c *client) trace(ctx context.Context, d bind.Device, t *Task) error {
	if err := c.manager.Update(ctx, t.Action, job.Running, nil); err != nil {
		return err
	}
	var output *Output
	err := worker.RetryFunction(ctx, 4, time.Millisecond*100, func() (err error) {
		ctx, cancel := task.WithTimeout(ctx, traceTimeout)
		defer cancel()
		output, err = doTrace(ctx, t.Action, t.Input, c.store, d, c.tempDir)
		return
	})
	status := job.Succeeded
	if err != nil {
		status = job.Failed
		log.E(ctx, "Error running trace: %v", err)
	} else if output.Err != "" {
		status = job.Failed
		log.E(ctx, "Error during trace: %v", output.Err)
	}

	return c.manager.Update(ctx, t.Action, status, output)
}

// doTrace extracts input files and runs `gapit trace` on them, capturing the output. The output object will
// be partially filled in the event of an upload error from store in order to allow examination of the logs.
func doTrace(ctx context.Context, action string, in *Input, store *stash.Client, d bind.Device, tempDir file.Path) (*Output, error) {
	subject := tempDir.Join(action + ".apk")
	obb := tempDir.Join(action + ".obb")
	tracefile := tempDir.Join(action + ".gfxtrace")
	extractedDir := tempDir.Join(action + "_tools")
	extractedLayout, err := layout.NewPkgLayout(extractedDir, true)
	if err != nil {
		return nil, err
	}

	gapidAPK, err := extractedLayout.GapidApk(ctx, in.GetToolingLayout().GetGapidAbi())
	if err != nil {
		return nil, err
	}

	gapis, err := extractedLayout.Gapis(ctx)
	if err != nil {
		return nil, err
	}

	gapit, err := extractedLayout.Gapit(ctx)
	if err != nil {
		return nil, err
	}

	traceTime, err := ptypes.Duration(in.GetHints().GetTraceTime())
	if err != nil {
		traceTime = 10 * time.Minute // TODO: support Robot-wide override
	}

	defer func() {
		file.Remove(subject)
		file.Remove(tracefile)
		file.RemoveAll(extractedDir)
	}()
	if err := store.GetFile(ctx, in.Subject, subject); err != nil {
		return nil, err
	}
	if err := store.GetFile(ctx, in.Gapis, gapis); err != nil {
		return nil, err
	}
	if err := store.GetFile(ctx, in.Gapit, gapit); err != nil {
		return nil, err
	}
	if err := store.GetFile(ctx, in.GapidApk, gapidAPK); err != nil {
		return nil, err
	}

	params := []string{
		"trace",
		"-out", tracefile.System(),
		"-for", traceTime.String(),
		"-disable-pcs",
		"-observe-frames", strconv.Itoa(observeEveryNthFrame),
		"-record-errors",
		"-serial", d.Instance().Serial,
		"-api", in.GetHints().GetAPI(),
	}

	if frames := in.GetHints().GetObserveFrames(); frames > 0 {
		params = append(params, "-capture-frames", strconv.Itoa(int(frames*observeEveryNthFrame+1)))
	}
	if in.Obb != "" {
		if err := store.GetFile(ctx, in.Obb, obb); err != nil {
			return nil, err
		}
		defer func() {
			file.Remove(obb)
		}()
		// TODO fix this
		// params = append(params, "-obb", obb.System())
		return log.Errf(ctx, nil, "OBBs are currently not supported")
	}
	cmd := shell.Command(gapit.System(), append(params, "apk:"+subject.System())...)
	outBuf := &bytes.Buffer{}
	errBuf := &bytes.Buffer{}
	outputObj := &Output{}
	errs := []string{}
	log.I(ctx, "Running trace action %s", cmd)
	if err := cmd.Capture(outBuf, errBuf).Run(ctx); err != nil {
		if err := worker.NeedsRetry(err.Error()); err != nil {
			return nil, err
		}
		errs = append(errs, err.Error())
	}
	errs = append(errs, strings.TrimSpace(errBuf.String()))
	outputObj.Err = strings.Join(errs, "\n")
	if err := worker.NeedsRetry(outputObj.Err, retryString); err != nil {
		return nil, err
	}

	output := fmt.Sprintf("%s\n\n%s", cmd, strings.TrimSpace(outBuf.String()))
	log.I(ctx, output)
	logID, err := store.UploadString(ctx, stash.Upload{Name: []string{"trace.log"}, Type: []string{"text/plain"}}, output)
	if err != nil {
		return outputObj, err
	}
	outputObj.Log = logID
	traceID, err := store.UploadFile(ctx, tracefile)
	if err != nil {
		return outputObj, err
	}
	outputObj.Trace = traceID
	return outputObj, nil
}

type offlineDevice struct {
	bind.Device
}

func (offlineDevice) Status() bind.Status {
	return bind.Status_Offline
}
<|endoftext|>