<commit_before>package main

import (
	"code.google.com/p/go-uuid/uuid"
	iniconf "code.google.com/p/goconf/conf"
	"encoding/json"
	"fmt"
	nsq "github.com/bitly/go-nsq"
	"github.com/gamelost/bot3server/server"
	irc "github.com/gamelost/goirc/client"
	"log"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

const (
	BOT_CONFIG = "bot3.config"
	PRIVMSG    = "PRIVMSG"
)

type Request struct {
	RawLine   *irc.Line
	Timestamp time.Time
}

func main() {

	// the quit channel
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	// read in necessary configuration
	configFile, err := iniconf.ReadConfigFile(BOT_CONFIG)
	if err != nil {
		log.Fatal("Unable to read configuration file. Exiting now.")
	}

	// convert to bot3config
	bot3config, err := Bot3ConfigFromConfigFile(configFile)
	if err != nil {
		log.Fatal("Incomplete or invalid config file for bot3config. Exiting now.")
	}

	// set up Bot3 instnace
	bot3 := &Bot3{}
	bot3.QuitChan = sigChan
	bot3.init(bot3config)
	bot3.connect()

	// receiving quit shuts down
	<-sigChan
}

// struct type for Bot3
type Bot3 struct {
	Config     *Bot3Config
	Connection *irc.Conn
	// NSQ input/output to bot3Server
	BotServerOutputReader    *nsq.Consumer
	BotServerInputWriter     *nsq.Producer
	BotServerHeartbeatReader *nsq.Consumer
	// program quit chan
	QuitChan chan os.Signal
	// bot state
	Silenced                bool
	BotServerOnline         bool
	LastBotServerHeartbeat  *server.Bot3ServerHeartbeat
	Bot3ServerHeartbeatChan chan *server.Bot3ServerHeartbeat
	MagicIdentifier         string
	// messagehandlers
	IRCMessageHandler *MessageHandler
}

func (b *Bot3) init(config *Bot3Config) error {

	b.Config = config
	b.Silenced = false

	// set up magicid
	b.MagicIdentifier = config.MagicIdentifier

	log.Printf("Bot nick will be: %s and will join %s\n", b.Config.BotNick, b.Config.BotChannelToJoin)
	cfg := irc.NewConfig(b.Config.BotNick)
	cfg.SSL = false
	cfg.Server = b.Config.BotIRCServer
	cfg.NewNick = func(n string) string { return n + "^" }
	cfg.Pass = b.Config.BotPass
	c := irc.Client(cfg)

	// assign connection
	b.Connection = c
	b.Bot3ServerHeartbeatChan = make(chan *server.Bot3ServerHeartbeat)
	b.BotServerOnline = true

	// set up listener for heartbeat from bot3server
	heartbeatReader, err := nsq.NewConsumer("bot3server-heartbeat", "main#ephemeral", nsq.NewConfig())
	if err != nil {
		panic(err)
		b.QuitChan <- syscall.SIGINT
	}
	b.BotServerHeartbeatReader = heartbeatReader
	hbmh := &HeartbeatMessageHandler{Bot3ServerHeartbeatChan: b.Bot3ServerHeartbeatChan}
	b.BotServerHeartbeatReader.SetHandler(hbmh)
	b.BotServerHeartbeatReader.ConnectToNSQLookupd("127.0.0.1:4161")

	// set up goroutine to listen for heartbeat
	go func() {
		for {
			select {
			case <-b.Bot3ServerHeartbeatChan:
				// if we're coming back online, broadcast message
				if b.BotServerOnline == false {
					b.BotServerOnline = true
					c.Nick(b.Config.BotNick)
					c.Privmsg(b.Config.BotChannelToJoin, "Wheee.  Restored connection to bot3server!")
				}
				break
			case <-time.After(time.Second * 5):
				// if initially going offline, broadcast message
				if b.BotServerOnline == true {
					c.Privmsg(b.Config.BotChannelToJoin, "Welp! I seem to have lost connection to the bot3server.  Will continue to look for it.")
					c.Nick(b.Config.BotOfflinePrefix + "_SERVEROFFLINE")
					b.BotServerOnline = false
				}
				break
			}
		}
	}()

	// set up reader and message handler for botserver-output
	outputReader, err := nsq.NewConsumer(b.Config.Bot3ServerOutputTopic, "main", nsq.NewConfig())
	if err != nil {
		panic(err)
		b.QuitChan <- syscall.SIGINT
	}
	b.BotServerOutputReader = outputReader
	b.IRCMessageHandler = &MessageHandler{Connection: c, MagicIdentifier: b.Config.MagicIdentifier, Requests: map[string]*Request{}}
	b.BotServerOutputReader.SetHandler(b.IRCMessageHandler)
	b.BotServerOutputReader.ConnectToNSQLookupd("127.0.0.1:4161")

	// set up writer for botserver-input
	writer, err := nsq.NewProducer("127.0.0.1:4150", nsq.NewConfig())
	if err != nil {
		panic(err)
		b.QuitChan <- syscall.SIGINT
	}

	b.BotServerInputWriter = writer

	// Add handlers to do things here!
	// e.g. join a channel on connect.
	c.HandleFunc("connected",
		func(conn *irc.Conn, line *irc.Line) {
			log.Printf("Joining channel %s", b.Config.BotChannelToJoin)
			conn.Join(b.Config.BotChannelToJoin)
		})

	// And a signal on disconnect
	c.HandleFunc("disconnected",
		func(conn *irc.Conn, line *irc.Line) {
			log.Printf("Received quit command")
			b.QuitChan <- syscall.SIGINT
		})

	// hardcoded kill command just in case
	c.HandleFunc(PRIVMSG,
		func(conn *irc.Conn, line *irc.Line) {
			if strings.HasPrefix("!quit "+b.Config.BotNick, line.Text()) {
				if b.Config.IsAdminNick(line.Nick) {
					b.QuitChan <- syscall.SIGINT
				}
			}
		})

	// handle !silent
	c.HandleFunc(PRIVMSG,
		func(conn *irc.Conn, line *irc.Line) {
			if strings.HasPrefix("!silent", line.Text()) {
				if b.Config.IsAdminNick(line.Nick) {
					if !b.Silenced {
						c.Nick(b.Config.BotOfflinePrefix + "_SILENCED")
						b.Silenced = true
					} else {
						c.Nick(b.Config.BotNick)
						b.Silenced = false
					}
				}
			}
		})

	// handle privmsgs
	c.HandleFunc(PRIVMSG,
		func(conn *irc.Conn, line *irc.Line) {
			reqid := uuid.NewUUID().String()
			b.IRCMessageHandler.Requests[reqid] = &Request{RawLine: line, Timestamp: time.Now()}
			botRequest := &server.BotRequest{Identifier: reqid, Nick: line.Nick, Channel: line.Target(), ChatText: line.Text()}
			encodedRequest, err := json.Marshal(botRequest)

			if err != nil {
				log.Printf("Error encoding request: %s\n", err.Error())
			}

			// write to nsq only if not silenced, otherwise drop message
			if !b.Silenced {
				err := b.BotServerInputWriter.Publish(b.Config.Bot3ServerInputTopic, encodedRequest)
				if err != nil {
					panic(err)
				}
			} else {
				log.Printf("Silenced - will not output message.")
			}
		})

	// disabling this for now since it breaks remindme
	// go func() {
	// 	for {
	// 		exp := time.Now()
	// 		<-time.After(time.Second * 5)
	// 		for k, v := range b.IRCMessageHandler.Requests {
	// 			if v.Timestamp.Unix() < exp.Unix() {
	// 				log.Printf("Expiring %+v\n", v)
	// 				delete(b.IRCMessageHandler.Requests, k)
	// 			}
	// 		}
	// 	}
	// }()

	return nil
}

func (b *Bot3) connect() {

	// Tell client to connect.
	if err := b.Connection.Connect(); err != nil {
		fmt.Printf("Connection error: %s\n", err.Error())
		b.QuitChan <- syscall.SIGINT
	} else {
		log.Printf("Successfully connected.")
	}

}
<commit_msg>updates for newer nsq api<commit_after>package main

import (
	"code.google.com/p/go-uuid/uuid"
	iniconf "code.google.com/p/goconf/conf"
	"encoding/json"
	"fmt"
	nsq "github.com/bitly/go-nsq"
	"github.com/gamelost/bot3server/server"
	irc "github.com/gamelost/goirc/client"
	"log"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

const (
	BOT_CONFIG = "bot3.config"
	PRIVMSG    = "PRIVMSG"
)

type Request struct {
	RawLine   *irc.Line
	Timestamp time.Time
}

func main() {

	// the quit channel
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

	// read in necessary configuration
	configFile, err := iniconf.ReadConfigFile(BOT_CONFIG)
	if err != nil {
		log.Fatal("Unable to read configuration file. Exiting now.")
	}

	// convert to bot3config
	bot3config, err := Bot3ConfigFromConfigFile(configFile)
	if err != nil {
		log.Fatal("Incomplete or invalid config file for bot3config. Exiting now.")
	}

	// set up Bot3 instnace
	bot3 := &Bot3{}
	bot3.QuitChan = sigChan
	bot3.init(bot3config)
	bot3.connect()

	// receiving quit shuts down
	<-sigChan
}

// struct type for Bot3
type Bot3 struct {
	Config     *Bot3Config
	Connection *irc.Conn
	// NSQ input/output to bot3Server
	BotServerOutputReader    *nsq.Consumer
	BotServerInputWriter     *nsq.Producer
	BotServerHeartbeatReader *nsq.Consumer
	// program quit chan
	QuitChan chan os.Signal
	// bot state
	Silenced                bool
	BotServerOnline         bool
	LastBotServerHeartbeat  *server.Bot3ServerHeartbeat
	Bot3ServerHeartbeatChan chan *server.Bot3ServerHeartbeat
	MagicIdentifier         string
	// messagehandlers
	IRCMessageHandler *MessageHandler
}

func (b *Bot3) init(config *Bot3Config) error {

	b.Config = config
	b.Silenced = false

	// set up magicid
	b.MagicIdentifier = config.MagicIdentifier

	log.Printf("Bot nick will be: %s and will join %s\n", b.Config.BotNick, b.Config.BotChannelToJoin)
	cfg := irc.NewConfig(b.Config.BotNick)
	cfg.SSL = false
	cfg.Server = b.Config.BotIRCServer
	cfg.NewNick = func(n string) string { return n + "^" }
	cfg.Pass = b.Config.BotPass
	c := irc.Client(cfg)

	// assign connection
	b.Connection = c
	b.Bot3ServerHeartbeatChan = make(chan *server.Bot3ServerHeartbeat)
	b.BotServerOnline = true

	// set up listener for heartbeat from bot3server
	heartbeatReader, err := nsq.NewConsumer("bot3server-heartbeat", "main", nsq.NewConfig())
	if err != nil {
		panic(err)
		b.QuitChan <- syscall.SIGINT
	}
	b.BotServerHeartbeatReader = heartbeatReader
	hbmh := &HeartbeatMessageHandler{Bot3ServerHeartbeatChan: b.Bot3ServerHeartbeatChan}
	b.BotServerHeartbeatReader.AddHandler(hbmh)
	b.BotServerHeartbeatReader.ConnectToNSQLookupd("127.0.0.1:4161")

	// set up goroutine to listen for heartbeat
	go func() {
		for {
			select {
			case <-b.Bot3ServerHeartbeatChan:
				// if we're coming back online, broadcast message
				if b.BotServerOnline == false {
					b.BotServerOnline = true
					c.Nick(b.Config.BotNick)
					c.Privmsg(b.Config.BotChannelToJoin, "Wheee.  Restored connection to bot3server!")
				}
				break
			case <-time.After(time.Second * 5):
				// if initially going offline, broadcast message
				if b.BotServerOnline == true {
					c.Privmsg(b.Config.BotChannelToJoin, "Welp! I seem to have lost connection to the bot3server.  Will continue to look for it.")
					c.Nick(b.Config.BotOfflinePrefix + "_SERVEROFFLINE")
					b.BotServerOnline = false
				}
				break
			}
		}
	}()

	// set up reader and message handler for botserver-output
	outputReader, err := nsq.NewConsumer(b.Config.Bot3ServerOutputTopic, "main", nsq.NewConfig())
	if err != nil {
		panic(err)
		b.QuitChan <- syscall.SIGINT
	}
	b.BotServerOutputReader = outputReader
	b.IRCMessageHandler = &MessageHandler{Connection: c, MagicIdentifier: b.Config.MagicIdentifier, Requests: map[string]*Request{}}
	b.BotServerOutputReader.AddHandler(b.IRCMessageHandler)
	b.BotServerOutputReader.ConnectToNSQLookupd("127.0.0.1:4161")

	// set up writer for botserver-input
	writer, err := nsq.NewProducer("127.0.0.1:4150", nsq.NewConfig())
	if err != nil {
		panic(err)
		b.QuitChan <- syscall.SIGINT
	}

	b.BotServerInputWriter = writer

	// Add handlers to do things here!
	// e.g. join a channel on connect.
	c.HandleFunc("connected",
		func(conn *irc.Conn, line *irc.Line) {
			log.Printf("Joining channel %s", b.Config.BotChannelToJoin)
			conn.Join(b.Config.BotChannelToJoin)
		})

	// And a signal on disconnect
	c.HandleFunc("disconnected",
		func(conn *irc.Conn, line *irc.Line) {
			log.Printf("Received quit command")
			b.QuitChan <- syscall.SIGINT
		})

	// hardcoded kill command just in case
	c.HandleFunc(PRIVMSG,
		func(conn *irc.Conn, line *irc.Line) {
			if strings.HasPrefix("!quit "+b.Config.BotNick, line.Text()) {
				if b.Config.IsAdminNick(line.Nick) {
					b.QuitChan <- syscall.SIGINT
				}
			}
		})

	// handle !silent
	c.HandleFunc(PRIVMSG,
		func(conn *irc.Conn, line *irc.Line) {
			if strings.HasPrefix("!silent", line.Text()) {
				if b.Config.IsAdminNick(line.Nick) {
					if !b.Silenced {
						c.Nick(b.Config.BotOfflinePrefix + "_SILENCED")
						b.Silenced = true
					} else {
						c.Nick(b.Config.BotNick)
						b.Silenced = false
					}
				}
			}
		})

	// handle privmsgs
	c.HandleFunc(PRIVMSG,
		func(conn *irc.Conn, line *irc.Line) {
			reqid := uuid.NewUUID().String()
			b.IRCMessageHandler.Requests[reqid] = &Request{RawLine: line, Timestamp: time.Now()}
			botRequest := &server.BotRequest{Identifier: reqid, Nick: line.Nick, Channel: line.Target(), ChatText: line.Text()}
			encodedRequest, err := json.Marshal(botRequest)

			if err != nil {
				log.Printf("Error encoding request: %s\n", err.Error())
			}

			// write to nsq only if not silenced, otherwise drop message
			if !b.Silenced {
				err := b.BotServerInputWriter.Publish(b.Config.Bot3ServerInputTopic, encodedRequest)
				if err != nil {
					panic(err)
				}
			} else {
				log.Printf("Silenced - will not output message.")
			}
		})

	// disabling this for now since it breaks remindme
	// go func() {
	// 	for {
	// 		exp := time.Now()
	// 		<-time.After(time.Second * 5)
	// 		for k, v := range b.IRCMessageHandler.Requests {
	// 			if v.Timestamp.Unix() < exp.Unix() {
	// 				log.Printf("Expiring %+v\n", v)
	// 				delete(b.IRCMessageHandler.Requests, k)
	// 			}
	// 		}
	// 	}
	// }()

	return nil
}

func (b *Bot3) connect() {

	// Tell client to connect.
	if err := b.Connection.Connect(); err != nil {
		fmt.Printf("Connection error: %s\n", err.Error())
		b.QuitChan <- syscall.SIGINT
	} else {
		log.Printf("Successfully connected.")
	}

}
<|endoftext|>


<commit_before>// Copyright 2014 Mathias Fiedler
//
// 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 gl

// #include <GLES3/gl3.h>
// #include <stdlib.h>
import "C"
import (
	"fmt"
	"reflect"
	"unsafe"
)

type Shader C.GLuint
type ShaderType C.GLenum
type ShaderBinaryFormat C.GLint

const (
	VERTEX_SHADER   ShaderType = C.GL_VERTEX_SHADER
	FRAGMENT_SHADER            = C.GL_FRAGMENT_SHADER
)

type Program struct {
	id C.GLuint

	shadersValid bool
	shaders      []Shader
}

func CreateProgram() Program {
	return Program{id: C.glCreateProgram()}
}

func CreateShader(stype ShaderType) Shader {
	return Shader(C.glCreateShader(C.GLenum(stype)))
}

func (program *Program) Shaders() []Shader {
	if !program.shadersValid {
		nshaders := C.GLint(0)
		C.glGetProgramiv(program.id, C.GL_ATTACHED_SHADERS, &nshaders)
		program.shaders = make([]Shader, nshaders)
		C.glGetAttachedShaders(program.id, C.GLsizei(nshaders), nil, (*C.GLuint)(&program.shaders[0]))
		program.shadersValid = true
	}
	return program.shaders
}

func (program *Program) AttachShader(shader Shader) {
	C.glAttachShader(program.id, C.GLuint(shader))
	program.shadersValid = false
}

func (program *Program) BindAttribLocation(attrib VertexAttrib, name string) {

	cstr := C.CString(name)
	defer C.free(unsafe.Pointer(cstr))

	C.glBindAttribLocation(program.id, attrib.Index(), (*C.GLchar)(cstr))
}

func (program *Program) Delete() {
	C.glDeleteProgram(program.id)
}

func (shader Shader) Delete() {
	C.glDeleteShader(C.GLuint(shader))
}

func (program *Program) DetachShader(shader Shader) {
	C.glDetachShader(program.id, C.GLuint(shader))
	program.shadersValid = false
}

// func (program *Program) GetActiveAttrib(index uint) (size int, atype int, name string) {

// 	nameBuf := make([]byte, MAX_ATTRIBUTE_LENGTH)
// 	sizeBuf := C.GLint(0)
// 	typeBuf := C.GLenum(0)

// 	C.glGetActiveAttrib(program.id, C.GLuint(index), C.GLsizei(len(nameBuf)), C.NULL, &sizeBuf, &typeBuf, (*C.GLchar)(&nameBuf[0]))

// 	size = int(sizeBuf)
// 	atype = int(typeBuf)
// 	name = string(nameBuf)
// }

// func (program *Program) GetActiveUniform(index uint) (size int, utype int, name string) {

// 	nameBuf := make([]byte, MAX_UNIFORM_LENGTH)
// 	sizeBuf := C.GLint(0)
// 	typeBuf := C.GLenum(0)

// 	C.glGetActiveUniform(program.id, C.GLuint(index), C.GLsizei(len(nameBuf)), C.NULL, &sizeBuf, &typeBuf, (*C.GLchar)(&nameBuf[0]))

// 	size = int(sizeBuf)
// 	utype = int(typeBuf)
// 	name = string(nameBuf)
// }

// func (program *Program) GetAttachedShaders(GLsizei maxcount, GLsizei* count) []uint {
// 	C.glGetAttachedShaders(program.id, GLsizei maxcount, GLsizei* count, GLuint* shaders)
// }

// func (program *Program) GetProgramiv(pname int,  params int) {
// 	C.glGetProgramiv(program.id, GLenum pname, GLint* params)
// }

// func (program *Program) InfoLog(GLsizei bufsize, GLsizei* length, GLchar* infolog) {
// 	C.glGetProgramInfoLog(program.id, GLsizei bufsize, GLsizei* length, GLchar* infolog)
// }

// func GetShaderiv(shader Shader, pname int, params int) {
// 	C.glGetShaderiv(C.GLuint(shader), GLenum pname, GLint* params)
// }

// func GetShaderInfoLog(shader Shader, GLsizei bufsize, GLsizei* length, GLchar* infolog) {
// 	C.glGetShaderInfoLog(C.GLuint(shader), GLsizei bufsize, GLsizei* length, GLchar* infolog)
// }

// func GetShaderPrecisionFormat(shadertype ShaderType, precisiontype int, GLint* range,  precision int) {
// 	C.glGetShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision)
// }

// func GetShaderSource(shader Shader, GLsizei bufsize, GLsizei* length, GLchar* source) {
// 	C.glGetShaderSource(C.GLuint(shader), GLsizei bufsize, GLsizei* length, GLchar* source)
// }

// func (program *Program) GetUniformfv(location int, GLfloat* params) {
// 	C.glGetUniformfv(program.id, GLint location, GLfloat* params)
// }

// func (program *Program) GetUniformiv(GLint location,  params int) {
// 	C.glGetUniformiv(program.id, GLint location, GLint* params)
// }

// GL_APICALL int          GL_APIENTRY glGetAttribLocation (GLuint program, const GLchar* name);
// GL_APICALL int          GL_APIENTRY glGetUniformLocation (GLuint program, const GLchar* name);

// func ShaderBinary(GLsizei n, shaders []uint, binaryformat int, const GLvoid* binary, GLsizei length) {
// 	C.glShaderBinary(GLsizei n, const GLuint* shaders, GLenum binaryformat, const GLvoid* binary, GLsizei length)
// }

func GetShaderBinaryFormats() (formats []ShaderBinaryFormat) {
	numFormats := C.GLint(0)
	C.glGetIntegerv(C.GL_NUM_SHADER_BINARY_FORMATS, &numFormats)
	if numFormats > 0 {
		formats = make([]ShaderBinaryFormat, numFormats)
		C.glGetIntegerv(C.GL_SHADER_BINARY_FORMATS, (*C.GLint)(&formats[0]))
	}
	return
}

func ShaderBinary(shaders []Shader, format ShaderBinaryFormat, binary []byte) {
	C.glShaderBinary(C.GLsizei(len(shaders)), (*C.GLuint)(&shaders[0]), C.GLenum(format), unsafe.Pointer(&binary[0]), C.GLsizei(len(binary)))
}

func (shader Shader) Source(src string) {
	cstr := (*C.GLchar)(C.CString(src))
	defer C.free(unsafe.Pointer(cstr))
	slen := C.GLint(len(src))
	C.glShaderSource(C.GLuint(shader), 1, &cstr, &slen)
}

func (shader Shader) Compile() {
	C.glCompileShader(C.GLuint(shader))

	status := C.GLint(0)
	C.glGetShaderiv(C.GLuint(shader), C.GL_COMPILE_STATUS, &status)

	if status != C.GL_TRUE {
		loglen := C.GLint(0)
		C.glGetShaderiv(C.GLuint(shader), C.GL_INFO_LOG_LENGTH, &loglen)
		log := (*C.GLchar)(C.malloc(C.size_t(loglen)))
		defer C.free(unsafe.Pointer(log))
		C.glGetShaderInfoLog(C.GLuint(shader), C.GLsizei(loglen), nil, log)
		panic(fmt.Errorf("Failed to compile shader: %s", C.GoString((*C.char)(log))))
	}
}

func (program *Program) Link() {
	C.glLinkProgram(program.id)

	status := C.GLint(0)
	C.glGetProgramiv(program.id, C.GL_LINK_STATUS, &status)

	if status != C.GL_TRUE {
		loglen := C.GLint(0)
		C.glGetProgramiv(program.id, C.GL_INFO_LOG_LENGTH, &loglen)
		log := (*C.GLchar)(C.malloc(C.size_t(loglen)))
		defer C.free(unsafe.Pointer(log))
		C.glGetProgramInfoLog(program.id, C.GLsizei(loglen), nil, log)
		panic(fmt.Errorf("Failed to link shader: %s", C.GoString((*C.char)(log))))
	}
}

func (program *Program) Use() {
	C.glUseProgram(program.id)
}

func (program *Program) Validate() {
	C.glValidateProgram(program.id)
}

func ReleaseShaderCompiler() {
	C.glReleaseShaderCompiler()
}

func (t ShaderType) String() string {
	switch t {
	case VERTEX_SHADER:
		return "VERTEX_SHADER"
	case FRAGMENT_SHADER:
		return "FRAGMENT_SHADER"
	default:
		return fmt.Sprintf("ShaderType(%#x)", t)
	}
}

func (program *Program) BindVariables(s interface{}) {

	ut := reflect.TypeOf((*Uniform)(nil)).Elem()
	at := reflect.TypeOf((*VertexAttrib)(nil)).Elem()

	v := reflect.ValueOf(s).Elem()
	t := v.Type()

	n := t.NumField()

	for i := 0; i < n; i++ {
		ft := t.Field(i)
		name := ft.Tag.Get("glsl")
		if len(name) > 0 {
			fv := v.Field(i)
			if fv.Type().Implements(ut) {
				fv.Set(reflect.ValueOf(program.GetUniform(name)))
			} else if fv.Type().Implements(at) {
				fv.Set(reflect.ValueOf(program.GetAttrib(name)))
			} else {
				panic(fmt.Errorf("invalid type of '%s': %s", ft.Name, fv.Type().Name()))
			}
		}
	}
}

func (program *Program) GetAttrib(name string) VertexAttrib {

	cname := (*C.GLchar)(C.CString(name))
	defer C.free(unsafe.Pointer(cname))

	iloc := C.glGetAttribLocation(program.id, cname)
	if iloc < 0 {
		panic(fmt.Errorf("not an active uniform: %s", name))
	}

	loc := C.GLuint(iloc)

	datatype := C.GLenum(0)
	C.glGetActiveAttrib(program.id, C.GLuint(loc), 0, nil, nil, &datatype, nil)

	switch datatype {
	case C.GL_FLOAT:
		return FloatAttrib{vattrib{loc}}
	case C.GL_FLOAT_VEC2:
		return Vec2Attrib{FloatAttrib{vattrib{loc}}}
	case C.GL_FLOAT_VEC3:
		return Vec3Attrib{FloatAttrib{vattrib{loc}}}
	case C.GL_FLOAT_VEC4:
		return Vec4Attrib{FloatAttrib{vattrib{loc}}}
	default:
		panic(fmt.Errorf("unsupported attribute type: %#x", datatype))
	}
}

func (program *Program) GetUniform(name string) Uniform {

	cname := (*C.GLchar)(C.CString(name))
	defer C.free(unsafe.Pointer(cname))

	loc := C.GLint(C.glGetUniformLocation(program.id, cname))
	if loc < 0 {
		panic(fmt.Errorf("not an active attribute: %s", name))
	}

	datatype := C.GLenum(0)
	C.glGetActiveUniform(program.id, C.GLuint(loc), 0, nil, nil, &datatype, nil)

	switch datatype {
	case C.GL_FLOAT:
		return Uniform1f{uniformBase{loc}}
	case C.GL_FLOAT_VEC2:
		return Uniform2f{uniformBase{loc}}
	case C.GL_FLOAT_VEC3:
		return Uniform3f{uniformBase{loc}}
	case C.GL_FLOAT_VEC4:
		return Uniform4f{uniformBase{loc}}
	case C.GL_INT:
		return Uniform1i{uniformBase{loc}}
	case C.GL_INT_VEC2:
		return Uniform2i{uniformBase{loc}}
	case C.GL_INT_VEC3:
		return Uniform3i{uniformBase{loc}}
	case C.GL_INT_VEC4:
		return Uniform4i{uniformBase{loc}}
	case C.GL_BOOL:
		return Uniform1i{uniformBase{loc}}
	case C.GL_BOOL_VEC2:
		return Uniform2i{uniformBase{loc}}
	case C.GL_BOOL_VEC3:
		return Uniform3i{uniformBase{loc}}
	case C.GL_BOOL_VEC4:
		return Uniform4i{uniformBase{loc}}
	case C.GL_FLOAT_MAT2:
		return UniformMatrix2f{uniformBase{loc}}
	case C.GL_FLOAT_MAT3:
		return UniformMatrix3f{uniformBase{loc}}
	case C.GL_FLOAT_MAT4:
		return UniformMatrix4f{uniformBase{loc}}
	case C.GL_SAMPLER_2D:
		return Uniform1i{uniformBase{loc}}
	case C.GL_SAMPLER_CUBE:
		return Uniform1i{uniformBase{loc}}
	default:
		panic(fmt.Errorf("unsupported uniform type: %#x", datatype))
	}
}
<commit_msg>fix error messages for inactive uniforms and attributes<commit_after>// Copyright 2014 Mathias Fiedler
//
// 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 gl

// #include <GLES3/gl3.h>
// #include <stdlib.h>
import "C"
import (
	"fmt"
	"reflect"
	"unsafe"
)

type Shader C.GLuint
type ShaderType C.GLenum
type ShaderBinaryFormat C.GLint

const (
	VERTEX_SHADER   ShaderType = C.GL_VERTEX_SHADER
	FRAGMENT_SHADER            = C.GL_FRAGMENT_SHADER
)

type Program struct {
	id C.GLuint

	shadersValid bool
	shaders      []Shader
}

func CreateProgram() Program {
	return Program{id: C.glCreateProgram()}
}

func CreateShader(stype ShaderType) Shader {
	return Shader(C.glCreateShader(C.GLenum(stype)))
}

func (program *Program) Shaders() []Shader {
	if !program.shadersValid {
		nshaders := C.GLint(0)
		C.glGetProgramiv(program.id, C.GL_ATTACHED_SHADERS, &nshaders)
		program.shaders = make([]Shader, nshaders)
		C.glGetAttachedShaders(program.id, C.GLsizei(nshaders), nil, (*C.GLuint)(&program.shaders[0]))
		program.shadersValid = true
	}
	return program.shaders
}

func (program *Program) AttachShader(shader Shader) {
	C.glAttachShader(program.id, C.GLuint(shader))
	program.shadersValid = false
}

func (program *Program) BindAttribLocation(attrib VertexAttrib, name string) {

	cstr := C.CString(name)
	defer C.free(unsafe.Pointer(cstr))

	C.glBindAttribLocation(program.id, attrib.Index(), (*C.GLchar)(cstr))
}

func (program *Program) Delete() {
	C.glDeleteProgram(program.id)
}

func (shader Shader) Delete() {
	C.glDeleteShader(C.GLuint(shader))
}

func (program *Program) DetachShader(shader Shader) {
	C.glDetachShader(program.id, C.GLuint(shader))
	program.shadersValid = false
}

// func (program *Program) GetActiveAttrib(index uint) (size int, atype int, name string) {

// 	nameBuf := make([]byte, MAX_ATTRIBUTE_LENGTH)
// 	sizeBuf := C.GLint(0)
// 	typeBuf := C.GLenum(0)

// 	C.glGetActiveAttrib(program.id, C.GLuint(index), C.GLsizei(len(nameBuf)), C.NULL, &sizeBuf, &typeBuf, (*C.GLchar)(&nameBuf[0]))

// 	size = int(sizeBuf)
// 	atype = int(typeBuf)
// 	name = string(nameBuf)
// }

// func (program *Program) GetActiveUniform(index uint) (size int, utype int, name string) {

// 	nameBuf := make([]byte, MAX_UNIFORM_LENGTH)
// 	sizeBuf := C.GLint(0)
// 	typeBuf := C.GLenum(0)

// 	C.glGetActiveUniform(program.id, C.GLuint(index), C.GLsizei(len(nameBuf)), C.NULL, &sizeBuf, &typeBuf, (*C.GLchar)(&nameBuf[0]))

// 	size = int(sizeBuf)
// 	utype = int(typeBuf)
// 	name = string(nameBuf)
// }

// func (program *Program) GetAttachedShaders(GLsizei maxcount, GLsizei* count) []uint {
// 	C.glGetAttachedShaders(program.id, GLsizei maxcount, GLsizei* count, GLuint* shaders)
// }

// func (program *Program) GetProgramiv(pname int,  params int) {
// 	C.glGetProgramiv(program.id, GLenum pname, GLint* params)
// }

// func (program *Program) InfoLog(GLsizei bufsize, GLsizei* length, GLchar* infolog) {
// 	C.glGetProgramInfoLog(program.id, GLsizei bufsize, GLsizei* length, GLchar* infolog)
// }

// func GetShaderiv(shader Shader, pname int, params int) {
// 	C.glGetShaderiv(C.GLuint(shader), GLenum pname, GLint* params)
// }

// func GetShaderInfoLog(shader Shader, GLsizei bufsize, GLsizei* length, GLchar* infolog) {
// 	C.glGetShaderInfoLog(C.GLuint(shader), GLsizei bufsize, GLsizei* length, GLchar* infolog)
// }

// func GetShaderPrecisionFormat(shadertype ShaderType, precisiontype int, GLint* range,  precision int) {
// 	C.glGetShaderPrecisionFormat(GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision)
// }

// func GetShaderSource(shader Shader, GLsizei bufsize, GLsizei* length, GLchar* source) {
// 	C.glGetShaderSource(C.GLuint(shader), GLsizei bufsize, GLsizei* length, GLchar* source)
// }

// func (program *Program) GetUniformfv(location int, GLfloat* params) {
// 	C.glGetUniformfv(program.id, GLint location, GLfloat* params)
// }

// func (program *Program) GetUniformiv(GLint location,  params int) {
// 	C.glGetUniformiv(program.id, GLint location, GLint* params)
// }

// GL_APICALL int          GL_APIENTRY glGetAttribLocation (GLuint program, const GLchar* name);
// GL_APICALL int          GL_APIENTRY glGetUniformLocation (GLuint program, const GLchar* name);

// func ShaderBinary(GLsizei n, shaders []uint, binaryformat int, const GLvoid* binary, GLsizei length) {
// 	C.glShaderBinary(GLsizei n, const GLuint* shaders, GLenum binaryformat, const GLvoid* binary, GLsizei length)
// }

func GetShaderBinaryFormats() (formats []ShaderBinaryFormat) {
	numFormats := C.GLint(0)
	C.glGetIntegerv(C.GL_NUM_SHADER_BINARY_FORMATS, &numFormats)
	if numFormats > 0 {
		formats = make([]ShaderBinaryFormat, numFormats)
		C.glGetIntegerv(C.GL_SHADER_BINARY_FORMATS, (*C.GLint)(&formats[0]))
	}
	return
}

func ShaderBinary(shaders []Shader, format ShaderBinaryFormat, binary []byte) {
	C.glShaderBinary(C.GLsizei(len(shaders)), (*C.GLuint)(&shaders[0]), C.GLenum(format), unsafe.Pointer(&binary[0]), C.GLsizei(len(binary)))
}

func (shader Shader) Source(src string) {
	cstr := (*C.GLchar)(C.CString(src))
	defer C.free(unsafe.Pointer(cstr))
	slen := C.GLint(len(src))
	C.glShaderSource(C.GLuint(shader), 1, &cstr, &slen)
}

func (shader Shader) Compile() {
	C.glCompileShader(C.GLuint(shader))

	status := C.GLint(0)
	C.glGetShaderiv(C.GLuint(shader), C.GL_COMPILE_STATUS, &status)

	if status != C.GL_TRUE {
		loglen := C.GLint(0)
		C.glGetShaderiv(C.GLuint(shader), C.GL_INFO_LOG_LENGTH, &loglen)
		log := (*C.GLchar)(C.malloc(C.size_t(loglen)))
		defer C.free(unsafe.Pointer(log))
		C.glGetShaderInfoLog(C.GLuint(shader), C.GLsizei(loglen), nil, log)
		panic(fmt.Errorf("Failed to compile shader: %s", C.GoString((*C.char)(log))))
	}
}

func (program *Program) Link() {
	C.glLinkProgram(program.id)

	status := C.GLint(0)
	C.glGetProgramiv(program.id, C.GL_LINK_STATUS, &status)

	if status != C.GL_TRUE {
		loglen := C.GLint(0)
		C.glGetProgramiv(program.id, C.GL_INFO_LOG_LENGTH, &loglen)
		log := (*C.GLchar)(C.malloc(C.size_t(loglen)))
		defer C.free(unsafe.Pointer(log))
		C.glGetProgramInfoLog(program.id, C.GLsizei(loglen), nil, log)
		panic(fmt.Errorf("Failed to link shader: %s", C.GoString((*C.char)(log))))
	}
}

func (program *Program) Use() {
	C.glUseProgram(program.id)
}

func (program *Program) Validate() {
	C.glValidateProgram(program.id)
}

func ReleaseShaderCompiler() {
	C.glReleaseShaderCompiler()
}

func (t ShaderType) String() string {
	switch t {
	case VERTEX_SHADER:
		return "VERTEX_SHADER"
	case FRAGMENT_SHADER:
		return "FRAGMENT_SHADER"
	default:
		return fmt.Sprintf("ShaderType(%#x)", t)
	}
}

func (program *Program) BindVariables(s interface{}) {

	ut := reflect.TypeOf((*Uniform)(nil)).Elem()
	at := reflect.TypeOf((*VertexAttrib)(nil)).Elem()

	v := reflect.ValueOf(s).Elem()
	t := v.Type()

	n := t.NumField()

	for i := 0; i < n; i++ {
		ft := t.Field(i)
		name := ft.Tag.Get("glsl")
		if len(name) > 0 {
			fv := v.Field(i)
			if fv.Type().Implements(ut) {
				fv.Set(reflect.ValueOf(program.GetUniform(name)))
			} else if fv.Type().Implements(at) {
				fv.Set(reflect.ValueOf(program.GetAttrib(name)))
			} else {
				panic(fmt.Errorf("invalid type of '%s': %s", ft.Name, fv.Type().Name()))
			}
		}
	}
}

func (program *Program) GetAttrib(name string) VertexAttrib {

	cname := (*C.GLchar)(C.CString(name))
	defer C.free(unsafe.Pointer(cname))

	iloc := C.glGetAttribLocation(program.id, cname)
	if iloc < 0 {
		panic(fmt.Errorf("not an active attribute: %s", name))
	}

	loc := C.GLuint(iloc)

	datatype := C.GLenum(0)
	C.glGetActiveAttrib(program.id, C.GLuint(loc), 0, nil, nil, &datatype, nil)

	switch datatype {
	case C.GL_FLOAT:
		return FloatAttrib{vattrib{loc}}
	case C.GL_FLOAT_VEC2:
		return Vec2Attrib{FloatAttrib{vattrib{loc}}}
	case C.GL_FLOAT_VEC3:
		return Vec3Attrib{FloatAttrib{vattrib{loc}}}
	case C.GL_FLOAT_VEC4:
		return Vec4Attrib{FloatAttrib{vattrib{loc}}}
	default:
		panic(fmt.Errorf("unsupported attribute type: %#x", datatype))
	}
}

func (program *Program) GetUniform(name string) Uniform {

	cname := (*C.GLchar)(C.CString(name))
	defer C.free(unsafe.Pointer(cname))

	loc := C.GLint(C.glGetUniformLocation(program.id, cname))
	if loc < 0 {
		panic(fmt.Errorf("not an active uniform: %s", name))
	}

	datatype := C.GLenum(0)
	C.glGetActiveUniform(program.id, C.GLuint(loc), 0, nil, nil, &datatype, nil)

	switch datatype {
	case C.GL_FLOAT:
		return Uniform1f{uniformBase{loc}}
	case C.GL_FLOAT_VEC2:
		return Uniform2f{uniformBase{loc}}
	case C.GL_FLOAT_VEC3:
		return Uniform3f{uniformBase{loc}}
	case C.GL_FLOAT_VEC4:
		return Uniform4f{uniformBase{loc}}
	case C.GL_INT:
		return Uniform1i{uniformBase{loc}}
	case C.GL_INT_VEC2:
		return Uniform2i{uniformBase{loc}}
	case C.GL_INT_VEC3:
		return Uniform3i{uniformBase{loc}}
	case C.GL_INT_VEC4:
		return Uniform4i{uniformBase{loc}}
	case C.GL_BOOL:
		return Uniform1i{uniformBase{loc}}
	case C.GL_BOOL_VEC2:
		return Uniform2i{uniformBase{loc}}
	case C.GL_BOOL_VEC3:
		return Uniform3i{uniformBase{loc}}
	case C.GL_BOOL_VEC4:
		return Uniform4i{uniformBase{loc}}
	case C.GL_FLOAT_MAT2:
		return UniformMatrix2f{uniformBase{loc}}
	case C.GL_FLOAT_MAT3:
		return UniformMatrix3f{uniformBase{loc}}
	case C.GL_FLOAT_MAT4:
		return UniformMatrix4f{uniformBase{loc}}
	case C.GL_SAMPLER_2D:
		return Uniform1i{uniformBase{loc}}
	case C.GL_SAMPLER_CUBE:
		return Uniform1i{uniformBase{loc}}
	default:
		panic(fmt.Errorf("unsupported uniform type: %#x", datatype))
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/globalaccelerator"

	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)

func resourceAwsGlobalAcceleratorListener() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsGlobalAcceleratorListenerCreate,
		Read:   resourceAwsGlobalAcceleratorListenerRead,
		Update: resourceAwsGlobalAcceleratorListenerUpdate,
		Delete: resourceAwsGlobalAcceleratorListenerDelete,

		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"accelerator_arn": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"client_affinity": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  globalaccelerator.ClientAffinityNone,
				ValidateFunc: validation.StringInSlice([]string{
					globalaccelerator.ClientAffinityNone,
					globalaccelerator.ClientAffinitySourceIp,
				}, false),
			},
			"protocol": {
				Type:     schema.TypeString,
				Required: true,
				ValidateFunc: validation.StringInSlice([]string{
					globalaccelerator.ProtocolTcp,
					globalaccelerator.ProtocolUdp,
				}, false),
			},
			"port_range": {
				Type:     schema.TypeSet,
				Required: true,
				MinItems: 1,
				MaxItems: 10,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"from_port": {
							Type:         schema.TypeInt,
							Optional:     true,
							ValidateFunc: validation.IntBetween(0, 65535),
						},
						"to_port": {
							Type:         schema.TypeInt,
							Optional:     true,
							ValidateFunc: validation.IntBetween(0, 65535),
						},
					},
				},
			},
		},
	}
}

func resourceAwsGlobalAcceleratorListenerCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).globalacceleratorconn

	opts := &globalaccelerator.CreateListenerInput{
		AcceleratorArn:   aws.String(d.Get("accelerator_arn").(string)),
		ClientAffinity:   aws.String(d.Get("client_affinity").(string)),
		IdempotencyToken: aws.String(resource.UniqueId()),
		Protocol:         aws.String(d.Get("protocol").(string)),
		PortRanges:       resourceAwsGlobalAcceleratorListenerExpandPortRanges(d.Get("port_range").(*schema.Set).List()),
	}

	log.Printf("[DEBUG] Create Global Accelerator listener: %s", opts)

	resp, err := conn.CreateListener(opts)
	if err != nil {
		return fmt.Errorf("Error creating Global Accelerator listener: %s", err)
	}

	d.SetId(*resp.Listener.ListenerArn)

	// Creating a listener triggers the accelerator to change status to InPending
	stateConf := &resource.StateChangeConf{
		Pending: []string{globalaccelerator.AcceleratorStatusInProgress},
		Target:  []string{globalaccelerator.AcceleratorStatusDeployed},
		Refresh: resourceAwsGlobalAcceleratorAcceleratorStateRefreshFunc(conn, d.Get("accelerator_arn").(string)),
		Timeout: 5 * time.Minute,
	}

	log.Printf("[DEBUG] Waiting for Global Accelerator listener (%s) availability", d.Id())
	_, err = stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf("Error waiting for Global Accelerator listener (%s) availability: %s", d.Id(), err)
	}

	return resourceAwsGlobalAcceleratorListenerRead(d, meta)
}

func resourceAwsGlobalAcceleratorListenerRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).globalacceleratorconn

	listener, err := resourceAwsGlobalAcceleratorListenerRetrieve(conn, d.Id())

	if err != nil {
		return fmt.Errorf("Error reading Global Accelerator listener: %s", err)
	}

	if listener == nil {
		log.Printf("[WARN] Global Accelerator listener (%s) not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	acceleratorArn, err := resourceAwsGlobalAcceleratorListenerParseAcceleratorArn(d.Id())

	if err != nil {
		return err
	}

	d.Set("accelerator_arn", acceleratorArn)
	d.Set("client_affinity", listener.ClientAffinity)
	d.Set("protocol", listener.Protocol)
	if err := d.Set("port_range", resourceAwsGlobalAcceleratorListenerFlattenPortRanges(listener.PortRanges)); err != nil {
		return fmt.Errorf("error setting port_range: %s", err)
	}

	return nil
}

func resourceAwsGlobalAcceleratorListenerParseAcceleratorArn(listenerArn string) (string, error) {
	parts := strings.Split(listenerArn, "/")
	if len(parts) < 4 {
		return "", fmt.Errorf("Unable to parse accelerator ARN from %s", listenerArn)
	}
	return strings.Join(parts[0:2], "/"), nil
}

func resourceAwsGlobalAcceleratorListenerExpandPortRanges(portRanges []interface{}) []*globalaccelerator.PortRange {
	out := make([]*globalaccelerator.PortRange, len(portRanges))

	for i, raw := range portRanges {
		portRange := raw.(map[string]interface{})
		m := globalaccelerator.PortRange{}

		m.FromPort = aws.Int64(int64(portRange["from_port"].(int)))
		m.ToPort = aws.Int64(int64(portRange["to_port"].(int)))

		out[i] = &m
	}

	return out
}

func resourceAwsGlobalAcceleratorListenerFlattenPortRanges(portRanges []*globalaccelerator.PortRange) []interface{} {
	out := make([]interface{}, len(portRanges))

	for i, portRange := range portRanges {
		m := make(map[string]interface{})

		m["from_port"] = aws.Int64Value(portRange.FromPort)
		m["to_port"] = aws.Int64Value(portRange.ToPort)

		out[i] = m
	}

	return out
}

func resourceAwsGlobalAcceleratorListenerRetrieve(conn *globalaccelerator.GlobalAccelerator, listenerArn string) (*globalaccelerator.Listener, error) {
	resp, err := conn.DescribeListener(&globalaccelerator.DescribeListenerInput{
		ListenerArn: aws.String(listenerArn),
	})

	if err != nil {
		if isAWSErr(err, globalaccelerator.ErrCodeListenerNotFoundException, "") {
			return nil, nil
		}
		return nil, err
	}

	return resp.Listener, nil
}

func resourceAwsGlobalAcceleratorListenerUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).globalacceleratorconn

	opts := &globalaccelerator.UpdateListenerInput{
		ClientAffinity: aws.String(d.Get("client_affinity").(string)),
		ListenerArn:    aws.String(d.Id()),
		Protocol:       aws.String(d.Get("protocol").(string)),
		PortRanges:     resourceAwsGlobalAcceleratorListenerExpandPortRanges(d.Get("port_range").(*schema.Set).List()),
	}

	log.Printf("[DEBUG] Update Global Accelerator listener: %s", opts)

	_, err := conn.UpdateListener(opts)
	if err != nil {
		return fmt.Errorf("Error updating Global Accelerator listener: %s", err)
	}

	// Creating a listener triggers the accelerator to change status to InPending
	err = resourceAwsGlobalAcceleratorAcceleratorWaitForDeployedState(conn, d.Get("accelerator_arn").(string))
	if err != nil {
		return nil
	}

	return resourceAwsGlobalAcceleratorListenerRead(d, meta)
}

func resourceAwsGlobalAcceleratorListenerDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).globalacceleratorconn

	opts := &globalaccelerator.DeleteListenerInput{
		ListenerArn: aws.String(d.Id()),
	}

	_, err := conn.DeleteListener(opts)
	if err != nil {
		if isAWSErr(err, globalaccelerator.ErrCodeListenerNotFoundException, "") {
			return nil
		}
		return fmt.Errorf("Error deleting Global Accelerator listener: %s", err)
	}

	// Deleting a listener triggers the accelerator to change status to InPending
	// }
	err = resourceAwsGlobalAcceleratorAcceleratorWaitForDeployedState(conn, d.Get("accelerator_arn").(string))
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Returns error where expected<commit_after>package aws

import (
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/globalaccelerator"

	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
)

func resourceAwsGlobalAcceleratorListener() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsGlobalAcceleratorListenerCreate,
		Read:   resourceAwsGlobalAcceleratorListenerRead,
		Update: resourceAwsGlobalAcceleratorListenerUpdate,
		Delete: resourceAwsGlobalAcceleratorListenerDelete,

		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"accelerator_arn": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"client_affinity": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  globalaccelerator.ClientAffinityNone,
				ValidateFunc: validation.StringInSlice([]string{
					globalaccelerator.ClientAffinityNone,
					globalaccelerator.ClientAffinitySourceIp,
				}, false),
			},
			"protocol": {
				Type:     schema.TypeString,
				Required: true,
				ValidateFunc: validation.StringInSlice([]string{
					globalaccelerator.ProtocolTcp,
					globalaccelerator.ProtocolUdp,
				}, false),
			},
			"port_range": {
				Type:     schema.TypeSet,
				Required: true,
				MinItems: 1,
				MaxItems: 10,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"from_port": {
							Type:         schema.TypeInt,
							Optional:     true,
							ValidateFunc: validation.IntBetween(0, 65535),
						},
						"to_port": {
							Type:         schema.TypeInt,
							Optional:     true,
							ValidateFunc: validation.IntBetween(0, 65535),
						},
					},
				},
			},
		},
	}
}

func resourceAwsGlobalAcceleratorListenerCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).globalacceleratorconn

	opts := &globalaccelerator.CreateListenerInput{
		AcceleratorArn:   aws.String(d.Get("accelerator_arn").(string)),
		ClientAffinity:   aws.String(d.Get("client_affinity").(string)),
		IdempotencyToken: aws.String(resource.UniqueId()),
		Protocol:         aws.String(d.Get("protocol").(string)),
		PortRanges:       resourceAwsGlobalAcceleratorListenerExpandPortRanges(d.Get("port_range").(*schema.Set).List()),
	}

	log.Printf("[DEBUG] Create Global Accelerator listener: %s", opts)

	resp, err := conn.CreateListener(opts)
	if err != nil {
		return fmt.Errorf("Error creating Global Accelerator listener: %s", err)
	}

	d.SetId(*resp.Listener.ListenerArn)

	// Creating a listener triggers the accelerator to change status to InPending
	stateConf := &resource.StateChangeConf{
		Pending: []string{globalaccelerator.AcceleratorStatusInProgress},
		Target:  []string{globalaccelerator.AcceleratorStatusDeployed},
		Refresh: resourceAwsGlobalAcceleratorAcceleratorStateRefreshFunc(conn, d.Get("accelerator_arn").(string)),
		Timeout: 5 * time.Minute,
	}

	log.Printf("[DEBUG] Waiting for Global Accelerator listener (%s) availability", d.Id())
	_, err = stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf("Error waiting for Global Accelerator listener (%s) availability: %s", d.Id(), err)
	}

	return resourceAwsGlobalAcceleratorListenerRead(d, meta)
}

func resourceAwsGlobalAcceleratorListenerRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).globalacceleratorconn

	listener, err := resourceAwsGlobalAcceleratorListenerRetrieve(conn, d.Id())

	if err != nil {
		return fmt.Errorf("Error reading Global Accelerator listener: %s", err)
	}

	if listener == nil {
		log.Printf("[WARN] Global Accelerator listener (%s) not found, removing from state", d.Id())
		d.SetId("")
		return nil
	}

	acceleratorArn, err := resourceAwsGlobalAcceleratorListenerParseAcceleratorArn(d.Id())

	if err != nil {
		return err
	}

	d.Set("accelerator_arn", acceleratorArn)
	d.Set("client_affinity", listener.ClientAffinity)
	d.Set("protocol", listener.Protocol)
	if err := d.Set("port_range", resourceAwsGlobalAcceleratorListenerFlattenPortRanges(listener.PortRanges)); err != nil {
		return fmt.Errorf("error setting port_range: %s", err)
	}

	return nil
}

func resourceAwsGlobalAcceleratorListenerParseAcceleratorArn(listenerArn string) (string, error) {
	parts := strings.Split(listenerArn, "/")
	if len(parts) < 4 {
		return "", fmt.Errorf("Unable to parse accelerator ARN from %s", listenerArn)
	}
	return strings.Join(parts[0:2], "/"), nil
}

func resourceAwsGlobalAcceleratorListenerExpandPortRanges(portRanges []interface{}) []*globalaccelerator.PortRange {
	out := make([]*globalaccelerator.PortRange, len(portRanges))

	for i, raw := range portRanges {
		portRange := raw.(map[string]interface{})
		m := globalaccelerator.PortRange{}

		m.FromPort = aws.Int64(int64(portRange["from_port"].(int)))
		m.ToPort = aws.Int64(int64(portRange["to_port"].(int)))

		out[i] = &m
	}

	return out
}

func resourceAwsGlobalAcceleratorListenerFlattenPortRanges(portRanges []*globalaccelerator.PortRange) []interface{} {
	out := make([]interface{}, len(portRanges))

	for i, portRange := range portRanges {
		m := make(map[string]interface{})

		m["from_port"] = aws.Int64Value(portRange.FromPort)
		m["to_port"] = aws.Int64Value(portRange.ToPort)

		out[i] = m
	}

	return out
}

func resourceAwsGlobalAcceleratorListenerRetrieve(conn *globalaccelerator.GlobalAccelerator, listenerArn string) (*globalaccelerator.Listener, error) {
	resp, err := conn.DescribeListener(&globalaccelerator.DescribeListenerInput{
		ListenerArn: aws.String(listenerArn),
	})

	if err != nil {
		if isAWSErr(err, globalaccelerator.ErrCodeListenerNotFoundException, "") {
			return nil, nil
		}
		return nil, err
	}

	return resp.Listener, nil
}

func resourceAwsGlobalAcceleratorListenerUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).globalacceleratorconn

	opts := &globalaccelerator.UpdateListenerInput{
		ClientAffinity: aws.String(d.Get("client_affinity").(string)),
		ListenerArn:    aws.String(d.Id()),
		Protocol:       aws.String(d.Get("protocol").(string)),
		PortRanges:     resourceAwsGlobalAcceleratorListenerExpandPortRanges(d.Get("port_range").(*schema.Set).List()),
	}

	log.Printf("[DEBUG] Update Global Accelerator listener: %s", opts)

	_, err := conn.UpdateListener(opts)
	if err != nil {
		return fmt.Errorf("Error updating Global Accelerator listener: %s", err)
	}

	// Creating a listener triggers the accelerator to change status to InPending
	err = resourceAwsGlobalAcceleratorAcceleratorWaitForDeployedState(conn, d.Get("accelerator_arn").(string))
	if err != nil {
		return err
	}

	return resourceAwsGlobalAcceleratorListenerRead(d, meta)
}

func resourceAwsGlobalAcceleratorListenerDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).globalacceleratorconn

	opts := &globalaccelerator.DeleteListenerInput{
		ListenerArn: aws.String(d.Id()),
	}

	_, err := conn.DeleteListener(opts)
	if err != nil {
		if isAWSErr(err, globalaccelerator.ErrCodeListenerNotFoundException, "") {
			return nil
		}
		return fmt.Errorf("Error deleting Global Accelerator listener: %s", err)
	}

	// Deleting a listener triggers the accelerator to change status to InPending
	// }
	err = resourceAwsGlobalAcceleratorAcceleratorWaitForDeployedState(conn, d.Get("accelerator_arn").(string))
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"fmt"
	"strings"

	"golang.org/x/net/context"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	gregor1 "github.com/keybase/client/go/protocol/gregor1"
)

type ChatUI struct {
	libkb.Contextified
	terminal            libkb.TerminalUI
	noOutput            bool
	lastPercentReported int
}

func (c *ChatUI) ChatAttachmentUploadOutboxID(context.Context, chat1.ChatAttachmentUploadOutboxIDArg) error {
	return nil
}

func (c *ChatUI) ChatAttachmentUploadStart(context.Context, chat1.ChatAttachmentUploadStartArg) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment upload "+ColorString(c.G(), "green", "starting")+"\n")
	return nil
}

func (c *ChatUI) ChatAttachmentUploadProgress(ctx context.Context, arg chat1.ChatAttachmentUploadProgressArg) error {
	if c.noOutput {
		return nil
	}
	percent := int((100 * arg.BytesComplete) / arg.BytesTotal)
	if c.lastPercentReported == 0 || percent == 100 || percent-c.lastPercentReported >= 10 {
		w := c.terminal.ErrorWriter()
		fmt.Fprintf(w, "Attachment upload progress %d%% (%d of %d bytes uploaded)\n", percent, arg.BytesComplete, arg.BytesTotal)
		c.lastPercentReported = percent
	}
	return nil
}

func (c *ChatUI) ChatAttachmentUploadDone(context.Context, int) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment upload "+ColorString(c.G(), "magenta", "finished")+"\n")
	return nil
}

func (c *ChatUI) ChatAttachmentPreviewUploadStart(context.Context, chat1.ChatAttachmentPreviewUploadStartArg) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment preview upload "+ColorString(c.G(), "green", "starting")+"\n")
	return nil
}

func (c *ChatUI) ChatAttachmentPreviewUploadDone(context.Context, int) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment preview upload "+ColorString(c.G(), "magenta", "finished")+"\n")
	return nil
}

func (c *ChatUI) ChatAttachmentDownloadStart(context.Context, int) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment download "+ColorString(c.G(), "green", "starting")+"\n")
	return nil
}

func (c *ChatUI) ChatAttachmentDownloadProgress(ctx context.Context, arg chat1.ChatAttachmentDownloadProgressArg) error {
	if c.noOutput {
		return nil
	}
	percent := int((100 * arg.BytesComplete) / arg.BytesTotal)
	if c.lastPercentReported == 0 || percent == 100 || percent-c.lastPercentReported >= 10 {
		w := c.terminal.ErrorWriter()
		fmt.Fprintf(w, "Attachment download progress %d%% (%d of %d bytes downloaded)\n", percent, arg.BytesComplete, arg.BytesTotal)
		c.lastPercentReported = percent
	}
	return nil
}

func (c *ChatUI) ChatAttachmentDownloadDone(context.Context, int) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment download "+ColorString(c.G(), "magenta", "finished")+"\n")
	return nil
}

func (c *ChatUI) ChatInboxConversation(ctx context.Context, arg chat1.ChatInboxConversationArg) error {
	return nil
}

func (c *ChatUI) ChatInboxFailed(ctx context.Context, arg chat1.ChatInboxFailedArg) error {
	return nil
}

func (c *ChatUI) ChatInboxUnverified(ctx context.Context, arg chat1.ChatInboxUnverifiedArg) error {
	return nil
}

func (c *ChatUI) ChatThreadCached(ctx context.Context, arg chat1.ChatThreadCachedArg) error {
	return nil
}

func (c *ChatUI) ChatThreadFull(ctx context.Context, arg chat1.ChatThreadFullArg) error {
	return nil
}

func (c *ChatUI) ChatConfirmChannelDelete(ctx context.Context, arg chat1.ChatConfirmChannelDeleteArg) (bool, error) {
	term := c.G().UI.GetTerminalUI()
	term.Printf("WARNING: This will destroy this chat channel and remove it from all members' inbox\n\n")
	confirm := fmt.Sprintf("nuke %s", arg.Channel)
	response, err := term.Prompt(PromptDescriptorDeleteRootTeam,
		fmt.Sprintf("** if you are sure, please type: %q > ", confirm))
	if err != nil {
		return false, err
	}
	return strings.TrimSpace(response) == confirm, nil
}

func (c *ChatUI) ChatSearchHit(ctx context.Context, arg chat1.ChatSearchHitArg) error {
	if c.noOutput {
		return nil
	}
	searchHit := arg.SearchHit
	getMsgPrefix := func(uiMsg *chat1.UIMessage) string {
		m := uiMsg.Valid()
		t := gregor1.FromTime(m.Ctime)
		return fmt.Sprintf("[%s %s] ", m.SenderUsername, shortDurationFromNow(t))
	}

	getContext := func(uiMsg *chat1.UIMessage) string {
		if uiMsg != nil && uiMsg.IsValid() && uiMsg.GetMessageType() == chat1.MessageType_TEXT {
			msgBody := uiMsg.Valid().MessageBody.Text().Body
			return getMsgPrefix(uiMsg) + msgBody + "\n"
		}
		return ""
	}

	highlightHits := func(uiMsg *chat1.UIMessage, hits []string) string {
		if uiMsg != nil && uiMsg.IsValid() && uiMsg.GetMessageType() == chat1.MessageType_TEXT {
			msgBody := uiMsg.Valid().MessageBody.Text().Body
			var hitText string
			for _, hit := range hits {
				hitText = strings.Replace(msgBody, hit, ColorString(c.G(), "red", hit), -1)
			}
			return getMsgPrefix(uiMsg) + hitText
		}
		return ""
	}

	// TODO: This should really use chat_cli_rendering.messageView, but we need
	// to refactor for UIMessage
	hitText := highlightHits(searchHit.HitMessage, searchHit.Matches)
	if hitText != "" {
		w := c.terminal.ErrorWriter()
		fmt.Fprintf(w, getContext(searchHit.PrevMessage))
		fmt.Fprintln(w, hitText)
		fmt.Fprintf(w, getContext(searchHit.NextMessage))
		fmt.Fprintln(w, "")
	}
	return nil
}

func (c *ChatUI) ChatSearchDone(ctx context.Context, arg chat1.ChatSearchDoneArg) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Search complete. Found %d results.", arg.NumHits)
	fmt.Fprintln(w, "")
	return nil
}
<commit_msg>Print chat search hits to stdout (#11739)<commit_after>// Copyright 2016 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"fmt"
	"strings"

	"golang.org/x/net/context"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	gregor1 "github.com/keybase/client/go/protocol/gregor1"
)

type ChatUI struct {
	libkb.Contextified
	terminal            libkb.TerminalUI
	noOutput            bool
	lastPercentReported int
}

func (c *ChatUI) ChatAttachmentUploadOutboxID(context.Context, chat1.ChatAttachmentUploadOutboxIDArg) error {
	return nil
}

func (c *ChatUI) ChatAttachmentUploadStart(context.Context, chat1.ChatAttachmentUploadStartArg) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment upload "+ColorString(c.G(), "green", "starting")+"\n")
	return nil
}

func (c *ChatUI) ChatAttachmentUploadProgress(ctx context.Context, arg chat1.ChatAttachmentUploadProgressArg) error {
	if c.noOutput {
		return nil
	}
	percent := int((100 * arg.BytesComplete) / arg.BytesTotal)
	if c.lastPercentReported == 0 || percent == 100 || percent-c.lastPercentReported >= 10 {
		w := c.terminal.ErrorWriter()
		fmt.Fprintf(w, "Attachment upload progress %d%% (%d of %d bytes uploaded)\n", percent, arg.BytesComplete, arg.BytesTotal)
		c.lastPercentReported = percent
	}
	return nil
}

func (c *ChatUI) ChatAttachmentUploadDone(context.Context, int) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment upload "+ColorString(c.G(), "magenta", "finished")+"\n")
	return nil
}

func (c *ChatUI) ChatAttachmentPreviewUploadStart(context.Context, chat1.ChatAttachmentPreviewUploadStartArg) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment preview upload "+ColorString(c.G(), "green", "starting")+"\n")
	return nil
}

func (c *ChatUI) ChatAttachmentPreviewUploadDone(context.Context, int) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment preview upload "+ColorString(c.G(), "magenta", "finished")+"\n")
	return nil
}

func (c *ChatUI) ChatAttachmentDownloadStart(context.Context, int) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment download "+ColorString(c.G(), "green", "starting")+"\n")
	return nil
}

func (c *ChatUI) ChatAttachmentDownloadProgress(ctx context.Context, arg chat1.ChatAttachmentDownloadProgressArg) error {
	if c.noOutput {
		return nil
	}
	percent := int((100 * arg.BytesComplete) / arg.BytesTotal)
	if c.lastPercentReported == 0 || percent == 100 || percent-c.lastPercentReported >= 10 {
		w := c.terminal.ErrorWriter()
		fmt.Fprintf(w, "Attachment download progress %d%% (%d of %d bytes downloaded)\n", percent, arg.BytesComplete, arg.BytesTotal)
		c.lastPercentReported = percent
	}
	return nil
}

func (c *ChatUI) ChatAttachmentDownloadDone(context.Context, int) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Attachment download "+ColorString(c.G(), "magenta", "finished")+"\n")
	return nil
}

func (c *ChatUI) ChatInboxConversation(ctx context.Context, arg chat1.ChatInboxConversationArg) error {
	return nil
}

func (c *ChatUI) ChatInboxFailed(ctx context.Context, arg chat1.ChatInboxFailedArg) error {
	return nil
}

func (c *ChatUI) ChatInboxUnverified(ctx context.Context, arg chat1.ChatInboxUnverifiedArg) error {
	return nil
}

func (c *ChatUI) ChatThreadCached(ctx context.Context, arg chat1.ChatThreadCachedArg) error {
	return nil
}

func (c *ChatUI) ChatThreadFull(ctx context.Context, arg chat1.ChatThreadFullArg) error {
	return nil
}

func (c *ChatUI) ChatConfirmChannelDelete(ctx context.Context, arg chat1.ChatConfirmChannelDeleteArg) (bool, error) {
	term := c.G().UI.GetTerminalUI()
	term.Printf("WARNING: This will destroy this chat channel and remove it from all members' inbox\n\n")
	confirm := fmt.Sprintf("nuke %s", arg.Channel)
	response, err := term.Prompt(PromptDescriptorDeleteRootTeam,
		fmt.Sprintf("** if you are sure, please type: %q > ", confirm))
	if err != nil {
		return false, err
	}
	return strings.TrimSpace(response) == confirm, nil
}

func (c *ChatUI) ChatSearchHit(ctx context.Context, arg chat1.ChatSearchHitArg) error {
	if c.noOutput {
		return nil
	}
	searchHit := arg.SearchHit
	getMsgPrefix := func(uiMsg *chat1.UIMessage) string {
		m := uiMsg.Valid()
		t := gregor1.FromTime(m.Ctime)
		return fmt.Sprintf("[%s %s] ", m.SenderUsername, shortDurationFromNow(t))
	}

	getContext := func(uiMsg *chat1.UIMessage) string {
		if uiMsg != nil && uiMsg.IsValid() && uiMsg.GetMessageType() == chat1.MessageType_TEXT {
			msgBody := uiMsg.Valid().MessageBody.Text().Body
			return getMsgPrefix(uiMsg) + msgBody + "\n"
		}
		return ""
	}

	highlightHits := func(uiMsg *chat1.UIMessage, hits []string) string {
		if uiMsg != nil && uiMsg.IsValid() && uiMsg.GetMessageType() == chat1.MessageType_TEXT {
			msgBody := uiMsg.Valid().MessageBody.Text().Body
			var hitText string
			for _, hit := range hits {
				hitText = strings.Replace(msgBody, hit, ColorString(c.G(), "red", hit), -1)
			}
			return getMsgPrefix(uiMsg) + hitText
		}
		return ""
	}

	// TODO: This should really use chat_cli_rendering.messageView, but we need
	// to refactor for UIMessage
	hitText := highlightHits(searchHit.HitMessage, searchHit.Matches)
	if hitText != "" {
		w := c.terminal.OutputWriter()
		fmt.Fprintf(w, getContext(searchHit.PrevMessage))
		fmt.Fprintln(w, hitText)
		fmt.Fprintf(w, getContext(searchHit.NextMessage))
		fmt.Fprintln(w, "")
	}
	return nil
}

func (c *ChatUI) ChatSearchDone(ctx context.Context, arg chat1.ChatSearchDoneArg) error {
	if c.noOutput {
		return nil
	}
	w := c.terminal.ErrorWriter()
	fmt.Fprintf(w, "Search complete. Found %d results.", arg.NumHits)
	fmt.Fprintln(w, "")
	return nil
}
<|endoftext|>


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

package keyset

import (
	"io"
	"io/ioutil"

	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"

	tinkpb "github.com/google/tink/go/proto/tink_go_proto"
)

// JSONReader deserializes a keyset from json format.
type JSONReader struct {
	r io.Reader
	j *protojson.UnmarshalOptions
}

// NewJSONReader returns new JSONReader that will read from r.
func NewJSONReader(r io.Reader) *JSONReader {
	return &JSONReader{
		r: r,
		j: &protojson.UnmarshalOptions{},
	}
}

// Read parses a (cleartext) keyset from the underlying io.Reader.
func (bkr *JSONReader) Read() (*tinkpb.Keyset, error) {
	keyset := &tinkpb.Keyset{}

	if err := bkr.readJSON(bkr.r, keyset); err != nil {
		return nil, err
	}
	return keyset, nil
}

// ReadEncrypted parses an EncryptedKeyset from the underlying io.Reader.
func (bkr *JSONReader) ReadEncrypted() (*tinkpb.EncryptedKeyset, error) {
	keyset := &tinkpb.EncryptedKeyset{}

	if err := bkr.readJSON(bkr.r, keyset); err != nil {
		return nil, err
	}
	return keyset, nil
}

func (bkr *JSONReader) readJSON(r io.Reader, msg proto.Message) error {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}
	return bkr.j.Unmarshal(b, msg)
}

// JSONWriter serializes a keyset into binary proto format.
type JSONWriter struct {
	w io.Writer
	j *protojson.MarshalOptions
}

// NewJSONWriter returns a new JSONWriter that will write to w.
func NewJSONWriter(w io.Writer) *JSONWriter {
	return &JSONWriter{
		w: w,
		j: &protojson.MarshalOptions{
			EmitUnpopulated: true,
			Indent:          "",
		},
	}
}

// Write writes the keyset to the underlying io.Writer.
func (bkw *JSONWriter) Write(keyset *tinkpb.Keyset) error {
	return bkw.writeJSON(bkw.w, keyset)
}

// WriteEncrypted writes the encrypted keyset to the underlying io.Writer.
func (bkw *JSONWriter) WriteEncrypted(keyset *tinkpb.EncryptedKeyset) error {
	return bkw.writeJSON(bkw.w, keyset)
}

func (bkw *JSONWriter) writeJSON(w io.Writer, msg proto.Message) error {
	b, err := bkw.j.Marshal(msg)
	if err != nil {
		return err
	}
	_, err = w.Write(b)
	return err
}
<commit_msg>Fix typo in JSONWriter's doc comment<commit_after>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

package keyset

import (
	"io"
	"io/ioutil"

	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"

	tinkpb "github.com/google/tink/go/proto/tink_go_proto"
)

// JSONReader deserializes a keyset from json format.
type JSONReader struct {
	r io.Reader
	j *protojson.UnmarshalOptions
}

// NewJSONReader returns new JSONReader that will read from r.
func NewJSONReader(r io.Reader) *JSONReader {
	return &JSONReader{
		r: r,
		j: &protojson.UnmarshalOptions{},
	}
}

// Read parses a (cleartext) keyset from the underlying io.Reader.
func (bkr *JSONReader) Read() (*tinkpb.Keyset, error) {
	keyset := &tinkpb.Keyset{}

	if err := bkr.readJSON(bkr.r, keyset); err != nil {
		return nil, err
	}
	return keyset, nil
}

// ReadEncrypted parses an EncryptedKeyset from the underlying io.Reader.
func (bkr *JSONReader) ReadEncrypted() (*tinkpb.EncryptedKeyset, error) {
	keyset := &tinkpb.EncryptedKeyset{}

	if err := bkr.readJSON(bkr.r, keyset); err != nil {
		return nil, err
	}
	return keyset, nil
}

func (bkr *JSONReader) readJSON(r io.Reader, msg proto.Message) error {
	b, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}
	return bkr.j.Unmarshal(b, msg)
}

// JSONWriter serializes a keyset into json format.
type JSONWriter struct {
	w io.Writer
	j *protojson.MarshalOptions
}

// NewJSONWriter returns a new JSONWriter that will write to w.
func NewJSONWriter(w io.Writer) *JSONWriter {
	return &JSONWriter{
		w: w,
		j: &protojson.MarshalOptions{
			EmitUnpopulated: true,
			Indent:          "",
		},
	}
}

// Write writes the keyset to the underlying io.Writer.
func (bkw *JSONWriter) Write(keyset *tinkpb.Keyset) error {
	return bkw.writeJSON(bkw.w, keyset)
}

// WriteEncrypted writes the encrypted keyset to the underlying io.Writer.
func (bkw *JSONWriter) WriteEncrypted(keyset *tinkpb.EncryptedKeyset) error {
	return bkw.writeJSON(bkw.w, keyset)
}

func (bkw *JSONWriter) writeJSON(w io.Writer, msg proto.Message) error {
	b, err := bkw.j.Marshal(msg)
	if err != nil {
		return err
	}
	_, err = w.Write(b)
	return err
}
<|endoftext|>


<commit_before>package libkb

import (
	"errors"
	"strings"
	"sync"
	"time"

	keybase1 "github.com/keybase/client/go/protocol/keybase1"
)

type Feature string
type FeatureFlags []Feature

const (
	EnvironmentFeatureAllowHighSkips   = Feature("env_allow_high_skips")
	EnvironmentFeatureMerkleCheckpoint = Feature("merkle_checkpoint")
)

// StringToFeatureFlags returns a set of feature flags
func StringToFeatureFlags(s string) (ret FeatureFlags) {
	s = strings.TrimSpace(s)
	if len(s) == 0 {
		return ret
	}
	v := strings.Split(s, ",")
	for _, f := range v {
		ret = append(ret, Feature(strings.TrimSpace(f)))
	}
	return ret
}

// Admin returns true if the admin feature set is on or the user is a keybase
// admin.
func (set FeatureFlags) Admin(uid keybase1.UID) bool {
	for _, f := range set {
		if f == Feature("admin") {
			return true
		}
	}
	return IsKeybaseAdmin(uid)
}

func (set FeatureFlags) HasFeature(feature Feature) bool {
	for _, f := range set {
		if f == feature {
			return true
		}
	}
	return false
}

func (set FeatureFlags) Empty() bool {
	return len(set) == 0
}

type featureSlot struct {
	sync.Mutex
	on         bool
	cacheUntil time.Time
}

// FeatureFlagSet is a set of feature flags for a given user. It will keep track
// of whether a feature is on or off, and how long until we should check to
// update
type FeatureFlagSet struct {
	sync.Mutex
	features map[Feature]*featureSlot
}

const (
	FeatureFTL                        = Feature("ftl")
	FeatureBoxAuditor                 = Feature("box_auditor3")
	ExperimentalGenericProofs         = Feature("experimental_generic_proofs")
	FeatureCheckForHiddenChainSupport = Feature("check_for_hidden_chain_support")

	// Show journeycards. This 'preview' flag is for development and admin testing.
	// This 'preview' flag is known to clients with old buggy journeycard code. For that reason, don't enable it for external users.
	FeatureJourneycardPreview = Feature("journeycard_preview")
	FeatureJourneycard        = Feature("journeycard")
)

// NewFeatureFlagSet makes a new set of feature flags.
func NewFeatureFlagSet() *FeatureFlagSet {
	return &FeatureFlagSet{
		features: make(map[Feature]*featureSlot),
	}
}

func (s *FeatureFlagSet) getOrMakeSlot(f Feature) *featureSlot {
	s.Lock()
	defer s.Unlock()
	ret := s.features[f]
	if ret != nil {
		return ret
	}
	ret = &featureSlot{}
	s.features[f] = ret
	return ret
}

type rawFeatureSlot struct {
	Value    bool `json:"value"`
	CacheSec int  `json:"cache_sec"`
}

type rawFeatures struct {
	Status   AppStatus                 `json:"status"`
	Features map[string]rawFeatureSlot `json:"features"`
}

func (r *rawFeatures) GetAppStatus() *AppStatus {
	return &r.Status
}

func (f *featureSlot) readFrom(m MetaContext, r rawFeatureSlot) {
	f.on = r.Value
	f.cacheUntil = m.G().Clock().Now().Add(time.Duration(r.CacheSec) * time.Second)
}

func (s *FeatureFlagSet) InvalidateCache(m MetaContext, f Feature) {
	featureSlot := s.getOrMakeSlot(f)
	featureSlot.Lock()
	defer featureSlot.Unlock()
	featureSlot.cacheUntil = m.G().Clock().Now().Add(time.Duration(-1) * time.Second)
}

func (s *FeatureFlagSet) EnableImmediately(m MetaContext, f Feature) error {
	if m.G().Env.GetRunMode() == ProductionRunMode {
		return errors.New("EnableImmediately is a dev/test-only path")
	}
	s.InvalidateCache(m, f)
	_, err := m.G().API.Post(m, APIArg{
		Endpoint:    "test/feature",
		SessionType: APISessionTypeREQUIRED,
		Args: HTTPArgs{
			"feature":   S{Val: string(f)},
			"value":     I{Val: 1},
			"cache_sec": I{Val: 100},
		},
	})
	return err
}

// EnabledWithError returns if the given feature is enabled, it will return true if it's
// enabled, and an error if one occurred.
func (s *FeatureFlagSet) EnabledWithError(m MetaContext, f Feature) (on bool, err error) {
	m = m.WithLogTag("FEAT")
	slot := s.getOrMakeSlot(f)
	slot.Lock()
	defer slot.Unlock()
	if m.G().Clock().Now().Before(slot.cacheUntil) {
		m.Debug("Feature (cached) %q -> %v", f, slot.on)
		return slot.on, nil
	}
	var raw rawFeatures
	arg := NewAPIArg("user/features")
	arg.SessionType = APISessionTypeREQUIRED
	arg.Args = HTTPArgs{
		"features": S{Val: string(f)},
	}
	err = m.G().API.GetDecode(m, arg, &raw)
	switch err.(type) {
	case nil:
	case LoginRequiredError:
		// No features for logged-out users
		return false, nil
	default:
		return false, err
	}
	if err != nil {
		return false, err
	}
	rawFeature, ok := raw.Features[string(f)]
	if !ok {
		m.Info("Feature %q wasn't returned from server", f)
		return false, nil
	}
	slot.readFrom(m, rawFeature)
	m.Debug("Feature (fetched) %q -> %v (will cache for %ds)", f, slot.on, rawFeature.CacheSec)
	return slot.on, nil
}

// Enabled returns if the feature flag is enabled. It ignore errors and just acts
// as if the feature is off.
func (s *FeatureFlagSet) Enabled(m MetaContext, f Feature) (on bool) {
	on, err := s.EnabledWithError(m, f)
	if err != nil {
		m.Info("Error checking feature %q: %v", f, err)
		return false
	}
	return on
}

// Clear clears out the cached feature flags, for instance if the user
// is going to logout.
func (s *FeatureFlagSet) Clear() {
	s.Lock()
	defer s.Unlock()
	s.features = make(map[Feature]*featureSlot)
}

// FeatureFlagGate allows the server to disable certain features by replying with a
// FEATURE_FLAG API status code, which is then translated into a FeatureFlagError.
// We cache these errors for a given amount of time, so we're not spamming the
// same attempt over and over again.
type FeatureFlagGate struct {
	sync.Mutex
	lastCheck time.Time
	lastError error
	feature   Feature
	cacheFor  time.Duration
}

// NewFeatureFlagGate makes a gate for the given feature that will cache for the given
// duration.
func NewFeatureFlagGate(f Feature, d time.Duration) *FeatureFlagGate {
	return &FeatureFlagGate{
		feature:  f,
		cacheFor: d,
	}
}

// DigestError should be called on the result of an API call. It will allow this gate
// to digest the error and maybe set up its internal caching for when to retry this
// feature.
func (f *FeatureFlagGate) DigestError(m MetaContext, err error) {
	if err == nil {
		return
	}
	ffe, ok := err.(FeatureFlagError)
	if !ok {
		return
	}
	if ffe.Feature() != f.feature {
		m.Debug("Got feature flag error for wrong feature: %v", err)
		return
	}

	m.Debug("Server reports feature %q is flagged off", f.feature)

	f.Lock()
	defer f.Unlock()
	f.lastCheck = m.G().Clock().Now()
	f.lastError = err
}

// ErrorIfFlagged should be called to avoid a feature if it's recently
// been feature-flagged "off" by the server.  In that case, it will return
// the error that was originally returned by the server.
func (f *FeatureFlagGate) ErrorIfFlagged(m MetaContext) (err error) {
	f.Lock()
	defer f.Unlock()
	if f.lastError == nil {
		return nil
	}
	diff := m.G().Clock().Now().Sub(f.lastCheck)
	if diff > f.cacheFor {
		m.Debug("Feature flag %q expired %d ago, let's give it another try", f.feature, diff)
		f.lastError = nil
		f.lastCheck = time.Time{}
	}
	return f.lastError
}

func (f *FeatureFlagGate) Clear() {
	f.Lock()
	defer f.Unlock()
	f.lastError = nil
}
<commit_msg>downgrade log level (#22859)<commit_after>package libkb

import (
	"errors"
	"strings"
	"sync"
	"time"

	keybase1 "github.com/keybase/client/go/protocol/keybase1"
)

type Feature string
type FeatureFlags []Feature

const (
	EnvironmentFeatureAllowHighSkips   = Feature("env_allow_high_skips")
	EnvironmentFeatureMerkleCheckpoint = Feature("merkle_checkpoint")
)

// StringToFeatureFlags returns a set of feature flags
func StringToFeatureFlags(s string) (ret FeatureFlags) {
	s = strings.TrimSpace(s)
	if len(s) == 0 {
		return ret
	}
	v := strings.Split(s, ",")
	for _, f := range v {
		ret = append(ret, Feature(strings.TrimSpace(f)))
	}
	return ret
}

// Admin returns true if the admin feature set is on or the user is a keybase
// admin.
func (set FeatureFlags) Admin(uid keybase1.UID) bool {
	for _, f := range set {
		if f == Feature("admin") {
			return true
		}
	}
	return IsKeybaseAdmin(uid)
}

func (set FeatureFlags) HasFeature(feature Feature) bool {
	for _, f := range set {
		if f == feature {
			return true
		}
	}
	return false
}

func (set FeatureFlags) Empty() bool {
	return len(set) == 0
}

type featureSlot struct {
	sync.Mutex
	on         bool
	cacheUntil time.Time
}

// FeatureFlagSet is a set of feature flags for a given user. It will keep track
// of whether a feature is on or off, and how long until we should check to
// update
type FeatureFlagSet struct {
	sync.Mutex
	features map[Feature]*featureSlot
}

const (
	FeatureFTL                        = Feature("ftl")
	FeatureBoxAuditor                 = Feature("box_auditor3")
	ExperimentalGenericProofs         = Feature("experimental_generic_proofs")
	FeatureCheckForHiddenChainSupport = Feature("check_for_hidden_chain_support")

	// Show journeycards. This 'preview' flag is for development and admin testing.
	// This 'preview' flag is known to clients with old buggy journeycard code. For that reason, don't enable it for external users.
	FeatureJourneycardPreview = Feature("journeycard_preview")
	FeatureJourneycard        = Feature("journeycard")
)

// NewFeatureFlagSet makes a new set of feature flags.
func NewFeatureFlagSet() *FeatureFlagSet {
	return &FeatureFlagSet{
		features: make(map[Feature]*featureSlot),
	}
}

func (s *FeatureFlagSet) getOrMakeSlot(f Feature) *featureSlot {
	s.Lock()
	defer s.Unlock()
	ret := s.features[f]
	if ret != nil {
		return ret
	}
	ret = &featureSlot{}
	s.features[f] = ret
	return ret
}

type rawFeatureSlot struct {
	Value    bool `json:"value"`
	CacheSec int  `json:"cache_sec"`
}

type rawFeatures struct {
	Status   AppStatus                 `json:"status"`
	Features map[string]rawFeatureSlot `json:"features"`
}

func (r *rawFeatures) GetAppStatus() *AppStatus {
	return &r.Status
}

func (f *featureSlot) readFrom(m MetaContext, r rawFeatureSlot) {
	f.on = r.Value
	f.cacheUntil = m.G().Clock().Now().Add(time.Duration(r.CacheSec) * time.Second)
}

func (s *FeatureFlagSet) InvalidateCache(m MetaContext, f Feature) {
	featureSlot := s.getOrMakeSlot(f)
	featureSlot.Lock()
	defer featureSlot.Unlock()
	featureSlot.cacheUntil = m.G().Clock().Now().Add(time.Duration(-1) * time.Second)
}

func (s *FeatureFlagSet) EnableImmediately(m MetaContext, f Feature) error {
	if m.G().Env.GetRunMode() == ProductionRunMode {
		return errors.New("EnableImmediately is a dev/test-only path")
	}
	s.InvalidateCache(m, f)
	_, err := m.G().API.Post(m, APIArg{
		Endpoint:    "test/feature",
		SessionType: APISessionTypeREQUIRED,
		Args: HTTPArgs{
			"feature":   S{Val: string(f)},
			"value":     I{Val: 1},
			"cache_sec": I{Val: 100},
		},
	})
	return err
}

// EnabledWithError returns if the given feature is enabled, it will return true if it's
// enabled, and an error if one occurred.
func (s *FeatureFlagSet) EnabledWithError(m MetaContext, f Feature) (on bool, err error) {
	m = m.WithLogTag("FEAT")
	slot := s.getOrMakeSlot(f)
	slot.Lock()
	defer slot.Unlock()
	if m.G().Clock().Now().Before(slot.cacheUntil) {
		m.Debug("Feature (cached) %q -> %v", f, slot.on)
		return slot.on, nil
	}
	var raw rawFeatures
	arg := NewAPIArg("user/features")
	arg.SessionType = APISessionTypeREQUIRED
	arg.Args = HTTPArgs{
		"features": S{Val: string(f)},
	}
	err = m.G().API.GetDecode(m, arg, &raw)
	switch err.(type) {
	case nil:
	case LoginRequiredError:
		// No features for logged-out users
		return false, nil
	default:
		return false, err
	}
	if err != nil {
		return false, err
	}
	rawFeature, ok := raw.Features[string(f)]
	if !ok {
		m.Debug("Feature %q wasn't returned from server", f)
		return false, nil
	}
	slot.readFrom(m, rawFeature)
	m.Debug("Feature (fetched) %q -> %v (will cache for %ds)", f, slot.on, rawFeature.CacheSec)
	return slot.on, nil
}

// Enabled returns if the feature flag is enabled. It ignore errors and just acts
// as if the feature is off.
func (s *FeatureFlagSet) Enabled(m MetaContext, f Feature) (on bool) {
	on, err := s.EnabledWithError(m, f)
	if err != nil {
		m.Debug("Error checking feature %q: %v", f, err)
		return false
	}
	return on
}

// Clear clears out the cached feature flags, for instance if the user
// is going to logout.
func (s *FeatureFlagSet) Clear() {
	s.Lock()
	defer s.Unlock()
	s.features = make(map[Feature]*featureSlot)
}

// FeatureFlagGate allows the server to disable certain features by replying with a
// FEATURE_FLAG API status code, which is then translated into a FeatureFlagError.
// We cache these errors for a given amount of time, so we're not spamming the
// same attempt over and over again.
type FeatureFlagGate struct {
	sync.Mutex
	lastCheck time.Time
	lastError error
	feature   Feature
	cacheFor  time.Duration
}

// NewFeatureFlagGate makes a gate for the given feature that will cache for the given
// duration.
func NewFeatureFlagGate(f Feature, d time.Duration) *FeatureFlagGate {
	return &FeatureFlagGate{
		feature:  f,
		cacheFor: d,
	}
}

// DigestError should be called on the result of an API call. It will allow this gate
// to digest the error and maybe set up its internal caching for when to retry this
// feature.
func (f *FeatureFlagGate) DigestError(m MetaContext, err error) {
	if err == nil {
		return
	}
	ffe, ok := err.(FeatureFlagError)
	if !ok {
		return
	}
	if ffe.Feature() != f.feature {
		m.Debug("Got feature flag error for wrong feature: %v", err)
		return
	}

	m.Debug("Server reports feature %q is flagged off", f.feature)

	f.Lock()
	defer f.Unlock()
	f.lastCheck = m.G().Clock().Now()
	f.lastError = err
}

// ErrorIfFlagged should be called to avoid a feature if it's recently
// been feature-flagged "off" by the server.  In that case, it will return
// the error that was originally returned by the server.
func (f *FeatureFlagGate) ErrorIfFlagged(m MetaContext) (err error) {
	f.Lock()
	defer f.Unlock()
	if f.lastError == nil {
		return nil
	}
	diff := m.G().Clock().Now().Sub(f.lastCheck)
	if diff > f.cacheFor {
		m.Debug("Feature flag %q expired %d ago, let's give it another try", f.feature, diff)
		f.lastError = nil
		f.lastCheck = time.Time{}
	}
	return f.lastError
}

func (f *FeatureFlagGate) Clear() {
	f.Lock()
	defer f.Unlock()
	f.lastError = nil
}
<|endoftext|>


<commit_before>package storage

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"sync"
)

const (
	SuperBlockSize = 8
)

type SuperBlock struct {
	Version     Version
	ReplicaType ReplicationType
}

func (s *SuperBlock) Bytes() []byte {
	header := make([]byte, SuperBlockSize)
	header[0] = byte(s.Version)
	header[1] = s.ReplicaType.Byte()
	return header
}

type Volume struct {
	Id       VolumeId
	dir      string
	dataFile *os.File
	nm       NeedleMapper
	readOnly bool

	SuperBlock

	accessLock sync.Mutex
}

func NewVolume(dirname string, id VolumeId, replicationType ReplicationType) (v *Volume, e error) {
	v = &Volume{dir: dirname, Id: id}
	v.SuperBlock = SuperBlock{ReplicaType: replicationType}
	e = v.load(true)
	return
}
func LoadVolumeOnly(dirname string, id VolumeId) (v *Volume, e error) {
	v = &Volume{dir: dirname, Id: id}
	v.SuperBlock = SuperBlock{ReplicaType: CopyNil}
	e = v.load(false)
	return
}
func (v *Volume) load(alsoLoadIndex bool) error {
	var e error
	fileName := path.Join(v.dir, v.Id.String())
	v.dataFile, e = os.OpenFile(fileName+".dat", os.O_RDWR|os.O_CREATE, 0644)
	if e != nil {
		if !os.IsPermission(e) {
			return fmt.Errorf("cannot create Volume Data %s.dat: %s", fileName, e)
		}
		if v.dataFile, e = os.Open(fileName + ".dat"); e != nil {
			return fmt.Errorf("cannot open Volume Data %s.dat: %s", fileName, e)
		}
		log.Printf("opening " + fileName + ".dat in READONLY mode")
		v.readOnly = true
	}
	if v.ReplicaType == CopyNil {
		e = v.readSuperBlock()
	} else {
		e = v.maybeWriteSuperBlock()
	}
	if e == nil && alsoLoadIndex {
		var indexFile *os.File
		if v.readOnly {
			if indexFile, e = os.Open(fileName + ".idx"); e != nil && !os.IsNotExist(e) {
				return fmt.Errorf("cannot open index file %s.idx: %s", fileName, e)
			}
			if indexFile != nil {
				log.Printf("converting %s.idx to %s.cdb", fileName, fileName)
				if e = ConvertIndexToCdb(fileName+".cdb", indexFile); e != nil {
					log.Printf("error converting %s.idx to %s.cdb: %s", fileName, fileName)
				} else {
					indexFile.Close()
					os.Remove(indexFile.Name())
					indexFile = nil
				}
			}
			v.nm, e = OpenCdbMap(fileName + ".cdb")
			return e
		} else {
			indexFile, e = os.OpenFile(fileName+".idx", os.O_RDWR|os.O_CREATE, 0644)
			if e != nil {
				return fmt.Errorf("cannot create Volume Data %s.dat: %s", fileName, e)
			}
		}
		v.nm, e = LoadNeedleMap(indexFile)
	}
	return e
}
func (v *Volume) Version() Version {
	return v.SuperBlock.Version
}
func (v *Volume) Size() int64 {
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	stat, e := v.dataFile.Stat()
	if e == nil {
		return stat.Size()
	}
	fmt.Printf("Failed to read file size %s %s\n", v.dataFile.Name(), e.Error())
	return -1
}
func (v *Volume) Close() {
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	v.nm.Close()
	_ = v.dataFile.Close()
}
func (v *Volume) maybeWriteSuperBlock() error {
	stat, e := v.dataFile.Stat()
	if e != nil {
		fmt.Printf("failed to stat datafile %s: %s", v.dataFile, e)
		return e
	}
	if stat.Size() == 0 {
		v.SuperBlock.Version = CurrentVersion
		_, e = v.dataFile.Write(v.SuperBlock.Bytes())
		if e != nil && os.IsPermission(e) {
			//read-only, but zero length - recreate it!
			if v.dataFile, e = os.Create(v.dataFile.Name()); e == nil {
				if _, e = v.dataFile.Write(v.SuperBlock.Bytes()); e == nil {
					v.readOnly = false
				}
			}
		}
	}
	return e
}
func (v *Volume) readSuperBlock() (err error) {
	if _, err = v.dataFile.Seek(0, 0); err != nil {
		return fmt.Errorf("cannot seek to the beginning of %s: %s", v.dataFile, err)
	}
	header := make([]byte, SuperBlockSize)
	if _, e := v.dataFile.Read(header); e != nil {
		return fmt.Errorf("cannot read superblock: %s", e)
	}
	v.SuperBlock, err = ParseSuperBlock(header)
	return err
}
func ParseSuperBlock(header []byte) (superBlock SuperBlock, err error) {
	superBlock.Version = Version(header[0])
	if superBlock.ReplicaType, err = NewReplicationTypeFromByte(header[1]); err != nil {
		err = fmt.Errorf("cannot read replica type: %s", err)
	}
	return
}
func (v *Volume) NeedToReplicate() bool {
	return v.ReplicaType.GetCopyCount() > 1
}

func (v *Volume) isFileUnchanged(n *Needle) bool {
	nv, ok := v.nm.Get(n.Id)
	if ok && nv.Offset > 0 {
		if _, err := v.dataFile.Seek(int64(nv.Offset)*NeedlePaddingSize, 0); err != nil {
			return false
		}
		oldNeedle := new(Needle)
		oldNeedle.Read(v.dataFile, nv.Size, v.Version())
		if len(oldNeedle.Data) == len(n.Data) && oldNeedle.Checksum == n.Checksum {
			length := len(n.Data)
			for i := 0; i < length; i++ {
				if n.Data[i] != oldNeedle.Data[i] {
					return false
				}
			}
			n.Size = oldNeedle.Size
			return true
		}
	}
	return false
}
func (v *Volume) write(n *Needle) (size uint32, err error) {
	if v.readOnly {
		err = fmt.Errorf("%s is read-only", v.dataFile)
		return
	}
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	if v.isFileUnchanged(n) {
		size = n.Size
		return
	}
	var offset int64
	if offset, err = v.dataFile.Seek(0, 2); err != nil {
		return
	}

	//ensure file writing starting from aligned positions
	if offset%NeedlePaddingSize != 0 {
		offset = offset + (NeedlePaddingSize - offset%NeedlePaddingSize)
		if offset, err = v.dataFile.Seek(offset, 0); err != nil {
			return
		}
	}

	if size, err = n.Append(v.dataFile, v.Version()); err != nil {
		if e := v.dataFile.Truncate(offset); e != nil {
			err = fmt.Errorf("%s\ncannot truncate %s: %s", err, v.dataFile, e)
		}
		return
	}
	nv, ok := v.nm.Get(n.Id)
	if !ok || int64(nv.Offset)*NeedlePaddingSize < offset {
		_, err = v.nm.Put(n.Id, uint32(offset/NeedlePaddingSize), n.Size)
	}
	return
}

func (v *Volume) delete(n *Needle) (uint32, error) {
	if v.readOnly {
		return 0, fmt.Errorf("%s is read-only", v.dataFile)
	}
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	nv, ok := v.nm.Get(n.Id)
	//fmt.Println("key", n.Id, "volume offset", nv.Offset, "data_size", n.Size, "cached size", nv.Size)
	if ok {
		var err error
		if err = v.nm.Delete(n.Id); err != nil {
			return nv.Size, err
		}
		if _, err = v.dataFile.Seek(0, 2); err != nil {
			return nv.Size, err
		}
		n.Data = make([]byte, 0)
		_, err = n.Append(v.dataFile, v.Version())
		return nv.Size, err
	}
	return 0, nil
}

func (v *Volume) read(n *Needle) (int, error) {
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	nv, ok := v.nm.Get(n.Id)
	if ok && nv.Offset > 0 {
		if _, err := v.dataFile.Seek(int64(nv.Offset)*NeedlePaddingSize, 0); err != nil {
			return -1, err
		}
		return n.Read(v.dataFile, nv.Size, v.Version())
	}
	return -1, errors.New("Not Found")
}

func (v *Volume) garbageLevel() float64 {
	return float64(v.nm.DeletedSize()) / float64(v.ContentSize())
}

func (v *Volume) compact() error {
	v.accessLock.Lock()
	defer v.accessLock.Unlock()

	filePath := path.Join(v.dir, v.Id.String())
	return v.copyDataAndGenerateIndexFile(filePath+".cpd", filePath+".cpx")
}
func (v *Volume) commitCompact() error {
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	_ = v.dataFile.Close()
	var e error
	if e = os.Rename(path.Join(v.dir, v.Id.String()+".cpd"), path.Join(v.dir, v.Id.String()+".dat")); e != nil {
		return e
	}
	if e = os.Rename(path.Join(v.dir, v.Id.String()+".cpx"), path.Join(v.dir, v.Id.String()+".idx")); e != nil {
		return e
	}
	if e = v.load(true); e != nil {
		return e
	}
	return nil
}
func (v *Volume) freeze() error {
	if v.readOnly {
		return nil
	}
	nm, ok := v.nm.(*NeedleMap)
	if !ok {
		return nil
	}
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	bn, _ := nakeFilename(v.dataFile.Name())
	cdbFn := bn + ".cdb"
	log.Printf("converting %s to %s", nm.indexFile.Name(), cdbFn)
	err := DumpNeedleMapToCdb(cdbFn, nm)
	if err != nil {
		return err
	}
	if v.nm, err = OpenCdbMap(cdbFn); err != nil {
		return err
	}
	nm.indexFile.Close()
	os.Remove(nm.indexFile.Name())
	v.readOnly = true
	return nil
}

func ScanVolumeFile(dirname string, id VolumeId,
	visitSuperBlock func(SuperBlock) error,
	visitNeedle func(n *Needle, offset uint32) error) (err error) {
	var v *Volume
	if v, err = LoadVolumeOnly(dirname, id); err != nil {
		return
	}
	if err = visitSuperBlock(v.SuperBlock); err != nil {
		return
	}

	version := v.Version()

	offset := uint32(SuperBlockSize)
	n, rest, e := ReadNeedleHeader(v.dataFile, version)
	if e != nil {
		err = fmt.Errorf("cannot read needle header: %s", e)
		return
	}
	for n != nil {
		if err = n.ReadNeedleBody(v.dataFile, version, rest); err != nil {
			err = fmt.Errorf("cannot read needle body: %s", err)
			return
		}
		if err = visitNeedle(n, offset); err != nil {
			return
		}
		offset += NeedleHeaderSize + rest
		if n, rest, err = ReadNeedleHeader(v.dataFile, version); err != nil {
			if err == io.EOF {
				return nil
			}
			return fmt.Errorf("cannot read needle header: %s", err)
		}
	}

	return
}

func (v *Volume) copyDataAndGenerateIndexFile(dstName, idxName string) (err error) {
	var (
		dst, idx *os.File
	)
	if dst, err = os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
		return
	}
	defer dst.Close()

	if idx, err = os.OpenFile(idxName, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
		return
	}
	defer idx.Close()

	nm := NewNeedleMap(idx)
	new_offset := uint32(SuperBlockSize)

	err = ScanVolumeFile(v.dir, v.Id, func(superBlock SuperBlock) error {
		_, err = dst.Write(superBlock.Bytes())
		return err
	}, func(n *Needle, offset uint32) error {
		nv, ok := v.nm.Get(n.Id)
		//log.Println("file size is", n.Size, "rest", rest)
		if ok && nv.Offset*NeedlePaddingSize == offset {
			if nv.Size > 0 {
				if _, err = nm.Put(n.Id, new_offset/NeedlePaddingSize, n.Size); err != nil {
					return fmt.Errorf("cannot put needle: %s", err)
				}
				if _, err = n.Append(dst, v.Version()); err != nil {
					return fmt.Errorf("cannot append needle: %s", err)
				}
				new_offset += n.DiskSize()
				//log.Println("saving key", n.Id, "volume offset", old_offset, "=>", new_offset, "data_size", n.Size, "rest", rest)
			}
		}
		return nil
	})

	return
}
func (v *Volume) ContentSize() uint64 {
	return v.nm.ContentSize()
}
<commit_msg>use bytes.Equal() instead, Thanks for Thomas' suggestion<commit_after>package storage

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"sync"
)

const (
	SuperBlockSize = 8
)

type SuperBlock struct {
	Version     Version
	ReplicaType ReplicationType
}

func (s *SuperBlock) Bytes() []byte {
	header := make([]byte, SuperBlockSize)
	header[0] = byte(s.Version)
	header[1] = s.ReplicaType.Byte()
	return header
}

type Volume struct {
	Id       VolumeId
	dir      string
	dataFile *os.File
	nm       NeedleMapper
	readOnly bool

	SuperBlock

	accessLock sync.Mutex
}

func NewVolume(dirname string, id VolumeId, replicationType ReplicationType) (v *Volume, e error) {
	v = &Volume{dir: dirname, Id: id}
	v.SuperBlock = SuperBlock{ReplicaType: replicationType}
	e = v.load(true)
	return
}
func LoadVolumeOnly(dirname string, id VolumeId) (v *Volume, e error) {
	v = &Volume{dir: dirname, Id: id}
	v.SuperBlock = SuperBlock{ReplicaType: CopyNil}
	e = v.load(false)
	return
}
func (v *Volume) load(alsoLoadIndex bool) error {
	var e error
	fileName := path.Join(v.dir, v.Id.String())
	v.dataFile, e = os.OpenFile(fileName+".dat", os.O_RDWR|os.O_CREATE, 0644)
	if e != nil {
		if !os.IsPermission(e) {
			return fmt.Errorf("cannot create Volume Data %s.dat: %s", fileName, e)
		}
		if v.dataFile, e = os.Open(fileName + ".dat"); e != nil {
			return fmt.Errorf("cannot open Volume Data %s.dat: %s", fileName, e)
		}
		log.Printf("opening " + fileName + ".dat in READONLY mode")
		v.readOnly = true
	}
	if v.ReplicaType == CopyNil {
		e = v.readSuperBlock()
	} else {
		e = v.maybeWriteSuperBlock()
	}
	if e == nil && alsoLoadIndex {
		var indexFile *os.File
		if v.readOnly {
			if indexFile, e = os.Open(fileName + ".idx"); e != nil && !os.IsNotExist(e) {
				return fmt.Errorf("cannot open index file %s.idx: %s", fileName, e)
			}
			if indexFile != nil {
				log.Printf("converting %s.idx to %s.cdb", fileName, fileName)
				if e = ConvertIndexToCdb(fileName+".cdb", indexFile); e != nil {
					log.Printf("error converting %s.idx to %s.cdb: %s", fileName, fileName)
				} else {
					indexFile.Close()
					os.Remove(indexFile.Name())
					indexFile = nil
				}
			}
			v.nm, e = OpenCdbMap(fileName + ".cdb")
			return e
		} else {
			indexFile, e = os.OpenFile(fileName+".idx", os.O_RDWR|os.O_CREATE, 0644)
			if e != nil {
				return fmt.Errorf("cannot create Volume Data %s.dat: %s", fileName, e)
			}
		}
		v.nm, e = LoadNeedleMap(indexFile)
	}
	return e
}
func (v *Volume) Version() Version {
	return v.SuperBlock.Version
}
func (v *Volume) Size() int64 {
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	stat, e := v.dataFile.Stat()
	if e == nil {
		return stat.Size()
	}
	fmt.Printf("Failed to read file size %s %s\n", v.dataFile.Name(), e.Error())
	return -1
}
func (v *Volume) Close() {
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	v.nm.Close()
	_ = v.dataFile.Close()
}
func (v *Volume) maybeWriteSuperBlock() error {
	stat, e := v.dataFile.Stat()
	if e != nil {
		fmt.Printf("failed to stat datafile %s: %s", v.dataFile, e)
		return e
	}
	if stat.Size() == 0 {
		v.SuperBlock.Version = CurrentVersion
		_, e = v.dataFile.Write(v.SuperBlock.Bytes())
		if e != nil && os.IsPermission(e) {
			//read-only, but zero length - recreate it!
			if v.dataFile, e = os.Create(v.dataFile.Name()); e == nil {
				if _, e = v.dataFile.Write(v.SuperBlock.Bytes()); e == nil {
					v.readOnly = false
				}
			}
		}
	}
	return e
}
func (v *Volume) readSuperBlock() (err error) {
	if _, err = v.dataFile.Seek(0, 0); err != nil {
		return fmt.Errorf("cannot seek to the beginning of %s: %s", v.dataFile, err)
	}
	header := make([]byte, SuperBlockSize)
	if _, e := v.dataFile.Read(header); e != nil {
		return fmt.Errorf("cannot read superblock: %s", e)
	}
	v.SuperBlock, err = ParseSuperBlock(header)
	return err
}
func ParseSuperBlock(header []byte) (superBlock SuperBlock, err error) {
	superBlock.Version = Version(header[0])
	if superBlock.ReplicaType, err = NewReplicationTypeFromByte(header[1]); err != nil {
		err = fmt.Errorf("cannot read replica type: %s", err)
	}
	return
}
func (v *Volume) NeedToReplicate() bool {
	return v.ReplicaType.GetCopyCount() > 1
}

func (v *Volume) isFileUnchanged(n *Needle) bool {
	nv, ok := v.nm.Get(n.Id)
	if ok && nv.Offset > 0 {
		if _, err := v.dataFile.Seek(int64(nv.Offset)*NeedlePaddingSize, 0); err != nil {
			return false
		}
		oldNeedle := new(Needle)
		oldNeedle.Read(v.dataFile, nv.Size, v.Version())
		if oldNeedle.Checksum == n.Checksum && bytes.Equal(oldNeedle.Data, n.Data) {
			n.Size = oldNeedle.Size
			return true
		}
	}
	return false
}
func (v *Volume) write(n *Needle) (size uint32, err error) {
	if v.readOnly {
		err = fmt.Errorf("%s is read-only", v.dataFile)
		return
	}
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	if v.isFileUnchanged(n) {
		size = n.Size
		return
	}
	var offset int64
	if offset, err = v.dataFile.Seek(0, 2); err != nil {
		return
	}

	//ensure file writing starting from aligned positions
	if offset%NeedlePaddingSize != 0 {
		offset = offset + (NeedlePaddingSize - offset%NeedlePaddingSize)
		if offset, err = v.dataFile.Seek(offset, 0); err != nil {
			return
		}
	}

	if size, err = n.Append(v.dataFile, v.Version()); err != nil {
		if e := v.dataFile.Truncate(offset); e != nil {
			err = fmt.Errorf("%s\ncannot truncate %s: %s", err, v.dataFile, e)
		}
		return
	}
	nv, ok := v.nm.Get(n.Id)
	if !ok || int64(nv.Offset)*NeedlePaddingSize < offset {
		_, err = v.nm.Put(n.Id, uint32(offset/NeedlePaddingSize), n.Size)
	}
	return
}

func (v *Volume) delete(n *Needle) (uint32, error) {
	if v.readOnly {
		return 0, fmt.Errorf("%s is read-only", v.dataFile)
	}
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	nv, ok := v.nm.Get(n.Id)
	//fmt.Println("key", n.Id, "volume offset", nv.Offset, "data_size", n.Size, "cached size", nv.Size)
	if ok {
		var err error
		if err = v.nm.Delete(n.Id); err != nil {
			return nv.Size, err
		}
		if _, err = v.dataFile.Seek(0, 2); err != nil {
			return nv.Size, err
		}
		n.Data = make([]byte, 0)
		_, err = n.Append(v.dataFile, v.Version())
		return nv.Size, err
	}
	return 0, nil
}

func (v *Volume) read(n *Needle) (int, error) {
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	nv, ok := v.nm.Get(n.Id)
	if ok && nv.Offset > 0 {
		if _, err := v.dataFile.Seek(int64(nv.Offset)*NeedlePaddingSize, 0); err != nil {
			return -1, err
		}
		return n.Read(v.dataFile, nv.Size, v.Version())
	}
	return -1, errors.New("Not Found")
}

func (v *Volume) garbageLevel() float64 {
	return float64(v.nm.DeletedSize()) / float64(v.ContentSize())
}

func (v *Volume) compact() error {
	v.accessLock.Lock()
	defer v.accessLock.Unlock()

	filePath := path.Join(v.dir, v.Id.String())
	return v.copyDataAndGenerateIndexFile(filePath+".cpd", filePath+".cpx")
}
func (v *Volume) commitCompact() error {
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	_ = v.dataFile.Close()
	var e error
	if e = os.Rename(path.Join(v.dir, v.Id.String()+".cpd"), path.Join(v.dir, v.Id.String()+".dat")); e != nil {
		return e
	}
	if e = os.Rename(path.Join(v.dir, v.Id.String()+".cpx"), path.Join(v.dir, v.Id.String()+".idx")); e != nil {
		return e
	}
	if e = v.load(true); e != nil {
		return e
	}
	return nil
}
func (v *Volume) freeze() error {
	if v.readOnly {
		return nil
	}
	nm, ok := v.nm.(*NeedleMap)
	if !ok {
		return nil
	}
	v.accessLock.Lock()
	defer v.accessLock.Unlock()
	bn, _ := nakeFilename(v.dataFile.Name())
	cdbFn := bn + ".cdb"
	log.Printf("converting %s to %s", nm.indexFile.Name(), cdbFn)
	err := DumpNeedleMapToCdb(cdbFn, nm)
	if err != nil {
		return err
	}
	if v.nm, err = OpenCdbMap(cdbFn); err != nil {
		return err
	}
	nm.indexFile.Close()
	os.Remove(nm.indexFile.Name())
	v.readOnly = true
	return nil
}

func ScanVolumeFile(dirname string, id VolumeId,
	visitSuperBlock func(SuperBlock) error,
	visitNeedle func(n *Needle, offset uint32) error) (err error) {
	var v *Volume
	if v, err = LoadVolumeOnly(dirname, id); err != nil {
		return
	}
	if err = visitSuperBlock(v.SuperBlock); err != nil {
		return
	}

	version := v.Version()

	offset := uint32(SuperBlockSize)
	n, rest, e := ReadNeedleHeader(v.dataFile, version)
	if e != nil {
		err = fmt.Errorf("cannot read needle header: %s", e)
		return
	}
	for n != nil {
		if err = n.ReadNeedleBody(v.dataFile, version, rest); err != nil {
			err = fmt.Errorf("cannot read needle body: %s", err)
			return
		}
		if err = visitNeedle(n, offset); err != nil {
			return
		}
		offset += NeedleHeaderSize + rest
		if n, rest, err = ReadNeedleHeader(v.dataFile, version); err != nil {
			if err == io.EOF {
				return nil
			}
			return fmt.Errorf("cannot read needle header: %s", err)
		}
	}

	return
}

func (v *Volume) copyDataAndGenerateIndexFile(dstName, idxName string) (err error) {
	var (
		dst, idx *os.File
	)
	if dst, err = os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
		return
	}
	defer dst.Close()

	if idx, err = os.OpenFile(idxName, os.O_WRONLY|os.O_CREATE, 0644); err != nil {
		return
	}
	defer idx.Close()

	nm := NewNeedleMap(idx)
	new_offset := uint32(SuperBlockSize)

	err = ScanVolumeFile(v.dir, v.Id, func(superBlock SuperBlock) error {
		_, err = dst.Write(superBlock.Bytes())
		return err
	}, func(n *Needle, offset uint32) error {
		nv, ok := v.nm.Get(n.Id)
		//log.Println("file size is", n.Size, "rest", rest)
		if ok && nv.Offset*NeedlePaddingSize == offset {
			if nv.Size > 0 {
				if _, err = nm.Put(n.Id, new_offset/NeedlePaddingSize, n.Size); err != nil {
					return fmt.Errorf("cannot put needle: %s", err)
				}
				if _, err = n.Append(dst, v.Version()); err != nil {
					return fmt.Errorf("cannot append needle: %s", err)
				}
				new_offset += n.DiskSize()
				//log.Println("saving key", n.Id, "volume offset", old_offset, "=>", new_offset, "data_size", n.Size, "rest", rest)
			}
		}
		return nil
	})

	return
}
func (v *Volume) ContentSize() uint64 {
	return v.nm.ContentSize()
}
<|endoftext|>