<commit_before>package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"net"
	"math/rand"
	"regexp"
	"sort"
	"strings"
	"time"
)

type Server struct {
	eventChan  chan Event
	running    bool

	name string

	clientMap  map[string]*Client  //Map of nicks → clients
	channelMap map[string]*Channel //Map of channel names → channels
}

type Client struct {
	server     *Server
	connection net.Conn
	signalChan chan int
	outputChan chan string
	nick       string
	channelMap map[string]*Channel
}

type Channel struct {
	name      string
	topic     string
	clientMap map[string]*Client
}

type Event struct {
	client *Client
	input  string
}

const (
	signalStop int = iota
)

const (
	rplWelcome int = iota
	rplJoin
	rplPart
	rplTopic
	rplNoTopic
	rplNames
	rplNickChange
	errMoreArgs
	errNoNick
	errInvalidNick
	errNickInUse
	errAlreadyReg
	errNoSuchNick
)

var (
	nickRegexp    = regexp.MustCompile(`^[a-zA-Z\[\]_^{|}][a-zA-Z0-9\[\]_^{|}]*$`)
	channelRegexp = regexp.MustCompile(`^#[a-z0-9_\-]+$`)
)

func NewServer() (*Server, error) {
	return &Server{eventChan:  make(chan Event),
		name:       "rosella",
		clientMap:  make(map[string]*Client),
		channelMap: make(map[string]*Channel)}, nil
}

func (s *Server) Start() {
	if s.running == false {
		s.running = true
		go s.serverThread()
	}
}

func (s *Server) HandleConnection(conn net.Conn) {

	client := &Client{server: s,
		connection: conn,
		outputChan: make(chan string),
		signalChan: make(chan int),
		channelMap: make(map[string]*Channel)}

	go client.clientThread()
}

func (s *Server) serverThread() {
	for {
		select {
		case e := <-s.eventChan:
			s.handleEvent(e)
		}
	}
}

func (s *Server) handleEvent(e Event) {
	fields := strings.Fields(e.input)

	if len(fields) < 1 {
		return
	}

	if strings.HasPrefix(fields[0], ":") {
		fields = fields[1:]
	}

	command := strings.ToUpper(fields[0])
	args := fields[1:]

	switch {
	case command == "NICK":
		if len(args) < 1 {
			e.client.reply(errNoNick)
			return
		}

		newNick := args[0]

		//Check newNick is of valid formatting (regex)
		if nickRegexp.MatchString(newNick) == false {
			e.client.reply(errInvalidNick)
			return
		}

		if _, exists := s.clientMap[newNick]; exists {
			e.client.reply(errNickInUse)
			return
		}

		e.client.setNick(newNick)

	case command == "USER":
		if e.client.nick == "" {
			//Give them a unique Guest nick
			newNick := fmt.Sprintf("Guest%d", rand.Int())
			for s.clientMap[newNick] != nil {
				newNick = fmt.Sprintf("Guest%d", rand.Int())
			}
		}
		e.client.reply(rplWelcome)

	case command == "JOIN":
		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		if args[0] == "0" {
			//Quit all channels
			for channel := range e.client.channelMap {
				s.partChannel(e.client, channel)
			}
			return
		}

		channels := strings.Split(args[0], ",")
		for _, channel := range channels {
			//Join the channel if it's valid
			if channelRegexp.Match([]byte(channel)) {
				s.joinChannel(e.client, channel)
			}
		}

	case command == "PART":
		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		channels := strings.Split(args[0], ",")
		for _, channel := range channels {
			//Part the channel if it's valid
			if channelRegexp.Match([]byte(channel)) {
				s.partChannel(e.client, channel)
			}
		}

	case command == "PRIVMSG":
		if len(args) < 2 {
			e.client.reply(errMoreArgs)
			return
		}

		message := strings.Join(args[1:], " ")

		channel, chanExists := s.channelMap[args[0]]
		client, clientExists := s.clientMap[args[0]]

		if chanExists {
			for _, c := range channel.clientMap {
				if c != e.client {
					c.outputChan <- fmt.Sprintf(":%s PRIVMSG %s %s", e.client.nick, args[0], message)
				}
			}
		} else if clientExists {
			client.outputChan <- fmt.Sprintf(":%s PRIVMSG %s %s", e.client.nick, client.nick, message)
		} else {
			e.client.reply(errNoSuchNick)
		}

	case command == "QUIT":
		//Stop the client, which will auto part channels and quit
		e.client.signalChan <- signalStop
	case command == "TOPIC":
		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		channel, exists := s.channelMap[args[0]]
		if exists == false {
			e.client.reply(errNoSuchNick)
			return
		}

		channelName := args[0]

		if len(args) == 1 {
			e.client.reply(rplTopic, channelName, channel.topic)
			return
		}

		if args[1] == ":" {
			channel.topic = ""
			for _, client := range channel.clientMap {
				client.reply(rplNoTopic, channelName)
			}
		} else {
			topic := strings.Join(args[1:], " ")
			topic = strings.TrimPrefix(topic, ":")
			channel.topic = topic

			for _, client := range channel.clientMap {
				client.reply(rplTopic, channelName, channel.topic)
			}
		}

	default:
		log.Printf("Unknown command: %q", fields[0])
	}
}

func (s *Server) joinChannel(client *Client, channelName string) {
	channel, exists := s.channelMap[channelName]
	if exists == false {
		channel = &Channel{name: channelName,
			topic:     "",
			clientMap: make(map[string]*Client)}
		s.channelMap[channelName] = channel
	}

	channel.clientMap[client.nick] = client
	client.channelMap[channelName] = channel

	for _, c := range channel.clientMap {
		c.reply(rplJoin, client.nick, channelName)
	}

	if channel.topic != "" {
		client.reply(rplTopic, channelName, channel.topic)
	} else {
		client.reply(rplNoTopic, channelName)
	}

	nicks := make([]string, 0, 100)
	for nick := range channel.clientMap {
		nicks = append(nicks, nick)
	}

	client.reply(rplNames, channelName, strings.Join(nicks, " "))
}

func (s *Server) partChannel(client *Client, channelName string) {
	channel, exists := s.channelMap[channelName]
	if exists == false {
		return
	}

	//Notify clients of the part
	for _, c := range channel.clientMap {
		c.reply(rplPart, client.nick, channelName)
	}

	delete(channel.clientMap, client.nick)
	delete(client.channelMap, channelName)
}

func (c *Client) clientThread() {
	defer c.connection.Close()

	readSignalChan := make(chan int, 1)
	writeSignalChan := make(chan int, 1)
	writeChan := make(chan string, 100)

	go c.readThread(readSignalChan)
	go c.writeThread(writeSignalChan, writeChan)

	for {
		select {
		case signal := <-c.signalChan:
			//Do stuff
			if signal == signalStop {
				readSignalChan <- signalStop
				writeSignalChan <- signalStop
				break
			}
		case line := <-c.outputChan:
			select {
			case writeChan <- line:
				//It worked
			default:
				log.Printf("Dropped a line for client: %q", c.nick)
				//Do nothing, dropping the line
			}
		}
	}

	//Part from all channels
	for channelName := range c.channelMap {
		c.server.partChannel(c, channelName)
	}

	//Remove from client list
	delete(c.server.clientMap, c.nick)
}

func (c *Client) readThread(signalChan chan int) {
	for {
		select {
		case signal := <-signalChan:
			if signal == signalStop {
				return
			}
		default:
			c.connection.SetReadDeadline(time.Now().Add(time.Second * 3))
			buf := make([]byte, 512)
			ln, err := c.connection.Read(buf)
			if err != nil {
				if err == io.EOF {
					//They must have dc'd
					c.signalChan <- signalStop
					return
				}
				continue
			}

			rawLines := buf[:ln]
			lines := bytes.Split(rawLines, []byte("\r\n"))
			for _, line := range lines {
				if len(line) > 0 {
					c.server.eventChan <- Event{client: c, input: string(line)}
				}
			}
		}
	}
}

func (c *Client) writeThread(signalChan chan int, outputChan chan string) {
	for {
		select {
		case signal := <-signalChan:
			if signal == signalStop {
				return
			}
		case output := <-outputChan:
			line := []byte(fmt.Sprintf("%s\r\n", output))

			c.connection.SetWriteDeadline(time.Now().Add(time.Second * 30))
			_, err := c.connection.Write(line)
			if err != nil {
				log.Printf("Write err: %q", err.Error())
				c.signalChan <- signalStop
				return
			}
		}
	}
}

//Send a reply to a user with the code specified
func (c *Client) reply(code int, args ...string) {
	switch code {
	case rplWelcome:
		c.outputChan <- fmt.Sprintf(":%s 001 %s :Welcome to %s", c.server.name, c.nick, c.server.name)
	case rplJoin:
		c.outputChan <- fmt.Sprintf(":%s JOIN %s", args[0], args[1])
	case rplPart:
		c.outputChan <- fmt.Sprintf(":%s PART %s", args[0], args[1])
	case rplTopic:
		c.outputChan <- fmt.Sprintf(":%s 332 %s %s :%s", c.server.name, c.nick, args[0], args[1])
	case rplNoTopic:
		c.outputChan <- fmt.Sprintf(":%s 331 %s %s :No topic is set", c.server.name, c.nick, args[0])
	case rplNames:
		//TODO: break long lists up into multiple messages
		c.outputChan <- fmt.Sprintf(":%s 353 %s = %s :%s", c.server.name, c.nick, args[0], args[1])
		c.outputChan <- fmt.Sprintf(":%s 366", c.server.name)
	case rplNickChange:
		c.outputChan <- fmt.Sprintf(":%s NICK %s", args[0], args[1])
	case errMoreArgs:
		c.outputChan <- fmt.Sprintf(":%s 461 :Not enough params", c.server.name)
	case errNoNick:
		c.outputChan <- fmt.Sprintf(":%s 431 :No nickanme given", c.server.name)
	case errInvalidNick:
		c.outputChan <- fmt.Sprintf(":%s 432 :Erronenous nickname", c.server.name)
	case errNickInUse:
		c.outputChan <- fmt.Sprintf(":%s 433 :Nick already in use", c.server.name)
	case errAlreadyReg:
		c.outputChan <- fmt.Sprintf(":%s 462 :You need a valid nick first", c.server.name)
	case errNoSuchNick:
		c.outputChan <- fmt.Sprintf("%s 401 :No such nick/channel", c.server.name)
	default:
		log.Printf("Client.reply() with unknown reply code: %d", code)
	}
}

func (c *Client) setNick(nick string) {
	if c.nick != "" {
		delete(c.server.clientMap, c.nick)
		for _, channel := range c.channelMap {
			delete(channel.clientMap, c.nick)
		}
	}

	//Set up new nick
	oldNick := c.nick
	c.nick = nick
	c.server.clientMap[c.nick] = c

	clients := make([]string, 0, 100)

	for _, channel := range c.channelMap {
		channel.clientMap[c.nick] = c

		//Collect list of client nicks who can see us
		for client := range channel.clientMap {
			clients = append(clients, client)
		}
	}

	//By sorting the nicks and skipping duplicates we send each client one message
	sort.Strings(clients)
	prevNick := ""
	for _, nick := range clients {
		if nick == prevNick {
			continue
		}
		prevNick = nick

		client, exists := c.server.clientMap[nick]
		if exists {
			client.reply(rplNickChange, oldNick, c.nick)
		}
	}
}
<commit_msg>Ran go fmt<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"regexp"
	"sort"
	"strings"
	"time"
)

type Server struct {
	eventChan chan Event
	running   bool

	name string

	clientMap  map[string]*Client  //Map of nicks → clients
	channelMap map[string]*Channel //Map of channel names → channels
}

type Client struct {
	server     *Server
	connection net.Conn
	signalChan chan int
	outputChan chan string
	nick       string
	channelMap map[string]*Channel
}

type Channel struct {
	name      string
	topic     string
	clientMap map[string]*Client
}

type Event struct {
	client *Client
	input  string
}

const (
	signalStop int = iota
)

const (
	rplWelcome int = iota
	rplJoin
	rplPart
	rplTopic
	rplNoTopic
	rplNames
	rplNickChange
	errMoreArgs
	errNoNick
	errInvalidNick
	errNickInUse
	errAlreadyReg
	errNoSuchNick
)

var (
	nickRegexp    = regexp.MustCompile(`^[a-zA-Z\[\]_^{|}][a-zA-Z0-9\[\]_^{|}]*$`)
	channelRegexp = regexp.MustCompile(`^#[a-z0-9_\-]+$`)
)

func NewServer() (*Server, error) {
	return &Server{eventChan: make(chan Event),
		name:       "rosella",
		clientMap:  make(map[string]*Client),
		channelMap: make(map[string]*Channel)}, nil
}

func (s *Server) Start() {
	if s.running == false {
		s.running = true
		go s.serverThread()
	}
}

func (s *Server) HandleConnection(conn net.Conn) {

	client := &Client{server: s,
		connection: conn,
		outputChan: make(chan string),
		signalChan: make(chan int),
		channelMap: make(map[string]*Channel)}

	go client.clientThread()
}

func (s *Server) serverThread() {
	for {
		select {
		case e := <-s.eventChan:
			s.handleEvent(e)
		}
	}
}

func (s *Server) handleEvent(e Event) {
	fields := strings.Fields(e.input)

	if len(fields) < 1 {
		return
	}

	if strings.HasPrefix(fields[0], ":") {
		fields = fields[1:]
	}

	command := strings.ToUpper(fields[0])
	args := fields[1:]

	switch {
	case command == "NICK":
		if len(args) < 1 {
			e.client.reply(errNoNick)
			return
		}

		newNick := args[0]

		//Check newNick is of valid formatting (regex)
		if nickRegexp.MatchString(newNick) == false {
			e.client.reply(errInvalidNick)
			return
		}

		if _, exists := s.clientMap[newNick]; exists {
			e.client.reply(errNickInUse)
			return
		}

		e.client.setNick(newNick)

	case command == "USER":
		if e.client.nick == "" {
			//Give them a unique Guest nick
			newNick := fmt.Sprintf("Guest%d", rand.Int())
			for s.clientMap[newNick] != nil {
				newNick = fmt.Sprintf("Guest%d", rand.Int())
			}
		}
		e.client.reply(rplWelcome)

	case command == "JOIN":
		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		if args[0] == "0" {
			//Quit all channels
			for channel := range e.client.channelMap {
				s.partChannel(e.client, channel)
			}
			return
		}

		channels := strings.Split(args[0], ",")
		for _, channel := range channels {
			//Join the channel if it's valid
			if channelRegexp.Match([]byte(channel)) {
				s.joinChannel(e.client, channel)
			}
		}

	case command == "PART":
		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		channels := strings.Split(args[0], ",")
		for _, channel := range channels {
			//Part the channel if it's valid
			if channelRegexp.Match([]byte(channel)) {
				s.partChannel(e.client, channel)
			}
		}

	case command == "PRIVMSG":
		if len(args) < 2 {
			e.client.reply(errMoreArgs)
			return
		}

		message := strings.Join(args[1:], " ")

		channel, chanExists := s.channelMap[args[0]]
		client, clientExists := s.clientMap[args[0]]

		if chanExists {
			for _, c := range channel.clientMap {
				if c != e.client {
					c.outputChan <- fmt.Sprintf(":%s PRIVMSG %s %s", e.client.nick, args[0], message)
				}
			}
		} else if clientExists {
			client.outputChan <- fmt.Sprintf(":%s PRIVMSG %s %s", e.client.nick, client.nick, message)
		} else {
			e.client.reply(errNoSuchNick)
		}

	case command == "QUIT":
		//Stop the client, which will auto part channels and quit
		e.client.signalChan <- signalStop
	case command == "TOPIC":
		if len(args) < 1 {
			e.client.reply(errMoreArgs)
			return
		}

		channel, exists := s.channelMap[args[0]]
		if exists == false {
			e.client.reply(errNoSuchNick)
			return
		}

		channelName := args[0]

		if len(args) == 1 {
			e.client.reply(rplTopic, channelName, channel.topic)
			return
		}

		if args[1] == ":" {
			channel.topic = ""
			for _, client := range channel.clientMap {
				client.reply(rplNoTopic, channelName)
			}
		} else {
			topic := strings.Join(args[1:], " ")
			topic = strings.TrimPrefix(topic, ":")
			channel.topic = topic

			for _, client := range channel.clientMap {
				client.reply(rplTopic, channelName, channel.topic)
			}
		}

	default:
		log.Printf("Unknown command: %q", fields[0])
	}
}

func (s *Server) joinChannel(client *Client, channelName string) {
	channel, exists := s.channelMap[channelName]
	if exists == false {
		channel = &Channel{name: channelName,
			topic:     "",
			clientMap: make(map[string]*Client)}
		s.channelMap[channelName] = channel
	}

	channel.clientMap[client.nick] = client
	client.channelMap[channelName] = channel

	for _, c := range channel.clientMap {
		c.reply(rplJoin, client.nick, channelName)
	}

	if channel.topic != "" {
		client.reply(rplTopic, channelName, channel.topic)
	} else {
		client.reply(rplNoTopic, channelName)
	}

	nicks := make([]string, 0, 100)
	for nick := range channel.clientMap {
		nicks = append(nicks, nick)
	}

	client.reply(rplNames, channelName, strings.Join(nicks, " "))
}

func (s *Server) partChannel(client *Client, channelName string) {
	channel, exists := s.channelMap[channelName]
	if exists == false {
		return
	}

	//Notify clients of the part
	for _, c := range channel.clientMap {
		c.reply(rplPart, client.nick, channelName)
	}

	delete(channel.clientMap, client.nick)
	delete(client.channelMap, channelName)
}

func (c *Client) clientThread() {
	defer c.connection.Close()

	readSignalChan := make(chan int, 1)
	writeSignalChan := make(chan int, 1)
	writeChan := make(chan string, 100)

	go c.readThread(readSignalChan)
	go c.writeThread(writeSignalChan, writeChan)

	for {
		select {
		case signal := <-c.signalChan:
			//Do stuff
			if signal == signalStop {
				readSignalChan <- signalStop
				writeSignalChan <- signalStop
				break
			}
		case line := <-c.outputChan:
			select {
			case writeChan <- line:
				//It worked
			default:
				log.Printf("Dropped a line for client: %q", c.nick)
				//Do nothing, dropping the line
			}
		}
	}

	//Part from all channels
	for channelName := range c.channelMap {
		c.server.partChannel(c, channelName)
	}

	//Remove from client list
	delete(c.server.clientMap, c.nick)
}

func (c *Client) readThread(signalChan chan int) {
	for {
		select {
		case signal := <-signalChan:
			if signal == signalStop {
				return
			}
		default:
			c.connection.SetReadDeadline(time.Now().Add(time.Second * 3))
			buf := make([]byte, 512)
			ln, err := c.connection.Read(buf)
			if err != nil {
				if err == io.EOF {
					//They must have dc'd
					c.signalChan <- signalStop
					return
				}
				continue
			}

			rawLines := buf[:ln]
			lines := bytes.Split(rawLines, []byte("\r\n"))
			for _, line := range lines {
				if len(line) > 0 {
					c.server.eventChan <- Event{client: c, input: string(line)}
				}
			}
		}
	}
}

func (c *Client) writeThread(signalChan chan int, outputChan chan string) {
	for {
		select {
		case signal := <-signalChan:
			if signal == signalStop {
				return
			}
		case output := <-outputChan:
			line := []byte(fmt.Sprintf("%s\r\n", output))

			c.connection.SetWriteDeadline(time.Now().Add(time.Second * 30))
			_, err := c.connection.Write(line)
			if err != nil {
				log.Printf("Write err: %q", err.Error())
				c.signalChan <- signalStop
				return
			}
		}
	}
}

//Send a reply to a user with the code specified
func (c *Client) reply(code int, args ...string) {
	switch code {
	case rplWelcome:
		c.outputChan <- fmt.Sprintf(":%s 001 %s :Welcome to %s", c.server.name, c.nick, c.server.name)
	case rplJoin:
		c.outputChan <- fmt.Sprintf(":%s JOIN %s", args[0], args[1])
	case rplPart:
		c.outputChan <- fmt.Sprintf(":%s PART %s", args[0], args[1])
	case rplTopic:
		c.outputChan <- fmt.Sprintf(":%s 332 %s %s :%s", c.server.name, c.nick, args[0], args[1])
	case rplNoTopic:
		c.outputChan <- fmt.Sprintf(":%s 331 %s %s :No topic is set", c.server.name, c.nick, args[0])
	case rplNames:
		//TODO: break long lists up into multiple messages
		c.outputChan <- fmt.Sprintf(":%s 353 %s = %s :%s", c.server.name, c.nick, args[0], args[1])
		c.outputChan <- fmt.Sprintf(":%s 366", c.server.name)
	case rplNickChange:
		c.outputChan <- fmt.Sprintf(":%s NICK %s", args[0], args[1])
	case errMoreArgs:
		c.outputChan <- fmt.Sprintf(":%s 461 :Not enough params", c.server.name)
	case errNoNick:
		c.outputChan <- fmt.Sprintf(":%s 431 :No nickanme given", c.server.name)
	case errInvalidNick:
		c.outputChan <- fmt.Sprintf(":%s 432 :Erronenous nickname", c.server.name)
	case errNickInUse:
		c.outputChan <- fmt.Sprintf(":%s 433 :Nick already in use", c.server.name)
	case errAlreadyReg:
		c.outputChan <- fmt.Sprintf(":%s 462 :You need a valid nick first", c.server.name)
	case errNoSuchNick:
		c.outputChan <- fmt.Sprintf("%s 401 :No such nick/channel", c.server.name)
	default:
		log.Printf("Client.reply() with unknown reply code: %d", code)
	}
}

func (c *Client) setNick(nick string) {
	if c.nick != "" {
		delete(c.server.clientMap, c.nick)
		for _, channel := range c.channelMap {
			delete(channel.clientMap, c.nick)
		}
	}

	//Set up new nick
	oldNick := c.nick
	c.nick = nick
	c.server.clientMap[c.nick] = c

	clients := make([]string, 0, 100)

	for _, channel := range c.channelMap {
		channel.clientMap[c.nick] = c

		//Collect list of client nicks who can see us
		for client := range channel.clientMap {
			clients = append(clients, client)
		}
	}

	//By sorting the nicks and skipping duplicates we send each client one message
	sort.Strings(clients)
	prevNick := ""
	for _, nick := range clients {
		if nick == prevNick {
			continue
		}
		prevNick = nick

		client, exists := c.server.clientMap[nick]
		if exists {
			client.reply(rplNickChange, oldNick, c.nick)
		}
	}
}
<|endoftext|>


<commit_before>package errors

import (
	"net/http"
	"os"

	log "github.com/Sirupsen/logrus"
	"github.com/cozy/cozy-stack/config"
	"github.com/cozy/cozy-stack/couchdb"
	"github.com/cozy/cozy-stack/web/jsonapi"
	"github.com/labstack/echo"
)

// ErrorHandler is the default error handler of our server. It always write a
// jsonapi compatible error.
func ErrorHandler(err error, c echo.Context) {
	var je *jsonapi.Error
	var ce *couchdb.Error
	var he *echo.HTTPError
	var ok bool

	res := c.Response()
	req := c.Request()

	if he, ok = err.(*echo.HTTPError); ok {
		if !res.Committed {
			if c.Request().Method == http.MethodHead {
				c.NoContent(he.Code)
			} else {
				c.String(he.Code, he.Message)
			}
		}
		if config.IsDevRelease() {
			log.Errorf("[HTTP %s %s] %s", req.Method, req.URL.Path, err)
		}
		return
	}

	if os.IsExist(err) {
		je = jsonapi.Conflict(err)
	} else if os.IsNotExist(err) {
		je = jsonapi.NotFound(err)
	} else if ce, ok = err.(*couchdb.Error); ok {
		je = &jsonapi.Error{
			Status: ce.StatusCode,
			Title:  ce.Name,
			Detail: ce.Reason,
		}
	} else if je, ok = err.(*jsonapi.Error); !ok {
		je = &jsonapi.Error{
			Status: http.StatusInternalServerError,
			Title:  "Unqualified error",
			Detail: err.Error(),
		}
	}

	if !res.Committed {
		if c.Request().Method == http.MethodHead {
			c.NoContent(je.Status)
		} else {
			jsonapi.DataError(c, je)
		}
	}

	if config.IsDevRelease() {
		log.Errorf("[HTTP %s %s] %s", req.Method, req.URL.Path, err)
	}
}
<commit_msg>Hotfix: echo HTTPError message is interface{} now<commit_after>package errors

import (
	"fmt"
	"net/http"
	"os"

	log "github.com/Sirupsen/logrus"
	"github.com/cozy/cozy-stack/config"
	"github.com/cozy/cozy-stack/couchdb"
	"github.com/cozy/cozy-stack/web/jsonapi"
	"github.com/labstack/echo"
)

// ErrorHandler is the default error handler of our server. It always write a
// jsonapi compatible error.
func ErrorHandler(err error, c echo.Context) {
	var je *jsonapi.Error
	var ce *couchdb.Error
	var he *echo.HTTPError
	var ok bool

	res := c.Response()
	req := c.Request()

	if he, ok = err.(*echo.HTTPError); ok {
		if !res.Committed {
			if c.Request().Method == http.MethodHead {
				c.NoContent(he.Code)
			} else {
				c.String(he.Code, fmt.Sprintf("%v", he.Message))
			}
		}
		if config.IsDevRelease() {
			log.Errorf("[HTTP %s %s] %s", req.Method, req.URL.Path, err)
		}
		return
	}

	if os.IsExist(err) {
		je = jsonapi.Conflict(err)
	} else if os.IsNotExist(err) {
		je = jsonapi.NotFound(err)
	} else if ce, ok = err.(*couchdb.Error); ok {
		je = &jsonapi.Error{
			Status: ce.StatusCode,
			Title:  ce.Name,
			Detail: ce.Reason,
		}
	} else if je, ok = err.(*jsonapi.Error); !ok {
		je = &jsonapi.Error{
			Status: http.StatusInternalServerError,
			Title:  "Unqualified error",
			Detail: err.Error(),
		}
	}

	if !res.Committed {
		if c.Request().Method == http.MethodHead {
			c.NoContent(je.Status)
		} else {
			jsonapi.DataError(c, je)
		}
	}

	if config.IsDevRelease() {
		log.Errorf("[HTTP %s %s] %s", req.Method, req.URL.Path, err)
	}
}
<|endoftext|>


<commit_before>package gpio

import (
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
)

type RpiGPIO struct {
	pin        int
	isOutput   bool
	isExported bool
	baseDir    string
}

func write(file string, data string) error {
	f, err := os.OpenFile(file, os.O_WRONLY, 0644)
	defer f.Close()

	if err == nil {
		_, err = f.WriteString(data)
	}

	return err
}

func exportPin(baseDir string, pin int) error {
	return write(fmt.Sprintf("%s/export", baseDir),
		fmt.Sprintf("%d", pin))
}

func unexportPin(baseDir string, pin int) error {
	return write(fmt.Sprintf("%s/unexport", baseDir),
		fmt.Sprintf("%d", pin))
}

func setDirection(baseDir string, pin int, direction int) error {
	pinDirection := map[int]string{1: "out", 0: "in"} // should/could be a const?

	return write(fmt.Sprintf("%s/gpio%d/direction", baseDir, pin),
		pinDirection[direction])
}

func writeValue(baseDir string, pin int, value int) error {
	return write(fmt.Sprintf("%s/gpio%d/value", baseDir, pin),
		fmt.Sprintf("%d", value))
}

func (g *RpiGPIO) Close() error {
	return unexportPin(g.baseDir, g.pin)
}

func (g *RpiGPIO) MakeOutput() error {
	if !g.isExported {
		err := exportPin(g.baseDir, g.pin)
		if err != nil {
			return attachErrorCause(fmt.Sprintf("Failed to export pin %d", g.pin), err)
		}
		g.isExported = true
	}

	err := setDirection(g.baseDir, g.pin, 1)
	if err != nil {
		return attachErrorCause(fmt.Sprintf("Failed to set pin %d direction", g.pin), err)
	}

	g.isOutput = true

	return nil
}

func (g *RpiGPIO) MakeInput() error {
	if !g.isExported {
		err := exportPin(g.baseDir, g.pin)
		if err != nil {
			return attachErrorCause(fmt.Sprintf("Failed to export pin %d", g.pin), err)
		}
		g.isExported = true
	}

	err := setDirection(g.baseDir, g.pin, 0)
	if err != nil {
		return attachErrorCause(fmt.Sprintf("Failed to set pin %d direction", g.pin), err)
	}
	g.isOutput = false

	return nil
}

func (g *RpiGPIO) WriteValue(val int) error {
	if !g.isOutput {
		return &RpiGPIOError{msg: fmt.Sprintf("Pin %d is not an output pin", g.pin)}
	}

	err := writeValue(g.baseDir, g.pin, val)
	if err != nil {
		return attachErrorCause(fmt.Sprintf("Failed to set output to %d on pin %d", val, g.pin), err)
	}

	return nil
}

func (g *RpiGPIO) ReadValue() (int, error) {
	if g.isOutput {
		return 0, &RpiGPIOError{msg: fmt.Sprintf("Pin %d is not an input pin", g.pin)}
	}

	data, err := ioutil.ReadFile(fmt.Sprintf("%s/gpio%d/value", g.baseDir, g.pin))

	if err != nil {
		return 0, attachErrorCause(fmt.Sprintf("Failed to read value from pin %d", g.pin), err)
	}

	return strconv.Atoi(string(data))
}

func NewRpiOutput(pin int) (*RpiGPIO, error) {
	r := &RpiGPIO{pin: pin,
		baseDir: "/sys/class/gpio"}

	return r, r.MakeOutput()
}

func NewRpiInput(pin int) (*RpiGPIO, error) {
	r := &RpiGPIO{pin: pin,
		baseDir: "/sys/class/gpio"}

	return r, r.MakeInput()
}
<commit_msg>Gotta trim that insane input<commit_after>package gpio

import (
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
)

type RpiGPIO struct {
	pin        int
	isOutput   bool
	isExported bool
	baseDir    string
}

func write(file string, data string) error {
	f, err := os.OpenFile(file, os.O_WRONLY, 0644)
	defer f.Close()

	if err == nil {
		_, err = f.WriteString(data)
	}

	return err
}

func exportPin(baseDir string, pin int) error {
	return write(fmt.Sprintf("%s/export", baseDir),
		fmt.Sprintf("%d", pin))
}

func unexportPin(baseDir string, pin int) error {
	return write(fmt.Sprintf("%s/unexport", baseDir),
		fmt.Sprintf("%d", pin))
}

func setDirection(baseDir string, pin int, direction int) error {
	pinDirection := map[int]string{1: "out", 0: "in"} // should/could be a const?

	return write(fmt.Sprintf("%s/gpio%d/direction", baseDir, pin),
		pinDirection[direction])
}

func writeValue(baseDir string, pin int, value int) error {
	return write(fmt.Sprintf("%s/gpio%d/value", baseDir, pin),
		fmt.Sprintf("%d", value))
}

func (g *RpiGPIO) Close() error {
	return unexportPin(g.baseDir, g.pin)
}

func (g *RpiGPIO) MakeOutput() error {
	if !g.isExported {
		err := exportPin(g.baseDir, g.pin)
		if err != nil {
			return attachErrorCause(fmt.Sprintf("Failed to export pin %d", g.pin), err)
		}
		g.isExported = true
	}

	err := setDirection(g.baseDir, g.pin, 1)
	if err != nil {
		return attachErrorCause(fmt.Sprintf("Failed to set pin %d direction", g.pin), err)
	}

	g.isOutput = true

	return nil
}

func (g *RpiGPIO) MakeInput() error {
	if !g.isExported {
		err := exportPin(g.baseDir, g.pin)
		if err != nil {
			return attachErrorCause(fmt.Sprintf("Failed to export pin %d", g.pin), err)
		}
		g.isExported = true
	}

	err := setDirection(g.baseDir, g.pin, 0)
	if err != nil {
		return attachErrorCause(fmt.Sprintf("Failed to set pin %d direction", g.pin), err)
	}
	g.isOutput = false

	return nil
}

func (g *RpiGPIO) WriteValue(val int) error {
	if !g.isOutput {
		return &RpiGPIOError{msg: fmt.Sprintf("Pin %d is not an output pin", g.pin)}
	}

	err := writeValue(g.baseDir, g.pin, val)
	if err != nil {
		return attachErrorCause(fmt.Sprintf("Failed to set output to %d on pin %d", val, g.pin), err)
	}

	return nil
}

func (g *RpiGPIO) ReadValue() (int, error) {
	if g.isOutput {
		return 0, &RpiGPIOError{msg: fmt.Sprintf("Pin %d is not an input pin", g.pin)}
	}

	data, err := ioutil.ReadFile(fmt.Sprintf("%s/gpio%d/value", g.baseDir, g.pin))

	if err != nil {
		return 0, attachErrorCause(fmt.Sprintf("Failed to read value from pin %d", g.pin), err)
	}

	return strconv.Atoi(strings.TrimSpace(string(data)))
}

func NewRpiOutput(pin int) (*RpiGPIO, error) {
	r := &RpiGPIO{pin: pin,
		baseDir: "/sys/class/gpio"}

	return r, r.MakeOutput()
}

func NewRpiInput(pin int) (*RpiGPIO, error) {
	r := &RpiGPIO{pin: pin,
		baseDir: "/sys/class/gpio"}

	return r, r.MakeInput()
}
<|endoftext|>


<commit_before>package docker

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestNewImage(t *testing.T) {
	tcs := map[string]struct {
		image    string
		registry string
		name     string
		tag      string
	}{
		"full": {
			image:    "docker-registry.domain.com:8080/nginx:1b29e1531c",
			registry: "https://docker-registry.domain.com:8080/v2",
			name:     "nginx",
			tag:      "1b29e1531c",
		},
		"regular": {
			image:    "docker-registry.domain.com/nginx:1b29e1531c",
			registry: "https://docker-registry.domain.com/v2",
			name:     "nginx",
			tag:      "1b29e1531c",
		},
		"regular_extended": {
			image:    "docker-registry.domain.com/skynetservices/skydns:2.3",
			registry: "https://docker-registry.domain.com/v2",
			name:     "skynetservices/skydns",
			tag:      "2.3",
		},
		"no_tag": {
			image:    "docker-registry.domain.com/nginx",
			registry: "https://docker-registry.domain.com/v2",
			name:     "nginx",
			tag:      "latest",
		},
		"no_tag_with_port": {
			image:    "docker-registry.domain.com:8080/nginx",
			registry: "https://docker-registry.domain.com:8080/v2",
			name:     "nginx",
			tag:      "latest",
		},

		"no_registry": {
			image:    "skynetservices/skydns:2.3",
			registry: "https://registry-1.docker.io/v2",
			name:     "skynetservices/skydns",
			tag:      "2.3",
		},
		"no_registry_root": {
			image:    "postgres:9.5.1",
			registry: "https://registry-1.docker.io/v2",
			name:     "library/postgres",
			tag:      "9.5.1",
		},
		"digest": {
			image:    "postgres@sha256:f6a2b81d981ace74aeafb2ed2982d52984d82958bfe836b82cbe4bf1ba440999",
			registry: "https://registry-1.docker.io/v2",
			name:     "library/postgres",
			tag:      "sha256:f6a2b81d981ace74aeafb2ed2982d52984d82958bfe836b82cbe4bf1ba440999",
		},
		"localhost_no_tag": {
			image:    "localhost/nginx",
			registry: "https://localhost/v2",
			name:     "nginx",
			tag:      "latest",
		},
		"localhost_tag_with_port": {
			image:    "localhost:8080/nginx:xxx",
			registry: "https://localhost:8080/v2",
			name:     "nginx",
			tag:      "xxx",
		},
	}
	for name, tc := range tcs {

		image, err := NewImage(&Config{ImageName: tc.image})
		if err != nil {
			t.Fatalf("%s: Can't parse image name: %s", name, err)
		}
		if image.Registry != tc.registry {
			t.Fatalf("%s: Expected registry name %s, got %s", name, tc.registry, image.Registry)
		}
		if image.Name != tc.name {
			t.Fatalf("%s: Expected image name %s, got %s", name, tc.name, image.Name)
		}
		if image.Tag != tc.tag {
			t.Fatalf("%s: Expected image tag %s, got %s", name, tc.tag, image.Tag)
		}
	}

}

func TestPullManifestSchemaV1(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		resp, err := ioutil.ReadFile("testdata/registry-response.json")
		if err != nil {
			t.Fatalf("Can't load registry test response %s", err.Error())
		}
		fmt.Fprintln(w, string(resp))
	}))
	defer ts.Close()

	image, err := NewImage(&Config{ImageName: "docker-registry.domain.com/nginx:1b29e1531ci"})
	image.Registry = ts.URL
	err = image.Pull()
	if err != nil {
		t.Fatalf("Can't pull image: %s", err)
	}
	if len(image.FsLayers) == 0 {
		t.Fatal("Can't pull fsLayers")
	}
}

func TestPullManifestSchemaV2(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		resp, err := ioutil.ReadFile("testdata/registry-response-schemav2.json")
		w.Header().Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
		if err != nil {
			t.Fatalf("Can't load registry test response %s", err.Error())
		}
		fmt.Fprintln(w, string(resp))
	}))
	defer ts.Close()

	image, err := NewImage(&Config{ImageName: "docker-registry.domain.com/nginx:1b29e1531c"})
	image.Registry = ts.URL
	err = image.Pull()
	if err != nil {
		t.Fatalf("Can't pull image: %s", err)
	}
	if len(image.FsLayers) == 0 {
		t.Fatal("Can't pull fsLayers")
	}
}
<commit_msg>Fix unit test<commit_after>package docker

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestNewImage(t *testing.T) {
	tcs := map[string]struct {
		image    string
		registry string
		name     string
		tag      string
	}{
		"full": {
			image:    "docker-registry.domain.com:8080/nginx:1b29e1531c",
			registry: "https://docker-registry.domain.com:8080/v2",
			name:     "nginx",
			tag:      "1b29e1531c",
		},
		"regular": {
			image:    "docker-registry.domain.com/nginx:1b29e1531c",
			registry: "https://docker-registry.domain.com/v2",
			name:     "nginx",
			tag:      "1b29e1531c",
		},
		"regular_extended": {
			image:    "docker-registry.domain.com/skynetservices/skydns:2.3",
			registry: "https://docker-registry.domain.com/v2",
			name:     "skynetservices/skydns",
			tag:      "2.3",
		},
		"no_tag": {
			image:    "docker-registry.domain.com/nginx",
			registry: "https://docker-registry.domain.com/v2",
			name:     "nginx",
			tag:      "latest",
		},
		"no_tag_with_port": {
			image:    "docker-registry.domain.com:8080/nginx",
			registry: "https://docker-registry.domain.com:8080/v2",
			name:     "nginx",
			tag:      "latest",
		},

		"no_registry": {
			image:    "skynetservices/skydns:2.3",
			registry: "https://registry-1.docker.io/v2",
			name:     "skynetservices/skydns",
			tag:      "2.3",
		},
		"no_registry_root": {
			image:    "postgres:9.5.1",
			registry: "https://registry-1.docker.io/v2",
			name:     "library/postgres",
			tag:      "9.5.1",
		},
		"digest": {
			image:    "postgres@sha256:f6a2b81d981ace74aeafb2ed2982d52984d82958bfe836b82cbe4bf1ba440999",
			registry: "https://registry-1.docker.io/v2",
			name:     "library/postgres",
			tag:      "sha256:f6a2b81d981ace74aeafb2ed2982d52984d82958bfe836b82cbe4bf1ba440999",
		},
		"localhost_no_tag": {
			image:    "localhost/nginx",
			registry: "https://localhost/v2",
			name:     "nginx",
			tag:      "latest",
		},
		"localhost_tag_with_port": {
			image:    "localhost:8080/nginx:xxx",
			registry: "https://localhost:8080/v2",
			name:     "nginx",
			tag:      "xxx",
		},
	}
	for name, tc := range tcs {

		image, err := NewImage(&Config{ImageName: tc.image})
		if err != nil {
			t.Fatalf("%s: Can't parse image name: %s", name, err)
		}
		if image.Registry != tc.registry {
			t.Fatalf("%s: Expected registry name %s, got %s", name, tc.registry, image.Registry)
		}
		if image.Name != tc.name {
			t.Fatalf("%s: Expected image name %s, got %s", name, tc.name, image.Name)
		}
		if image.Tag != tc.tag {
			t.Fatalf("%s: Expected image tag %s, got %s", name, tc.tag, image.Tag)
		}
	}

}

func TestPullManifestSchemaV1(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Add("Content-Type", "application/vnd.docker.distribution.manifest.v1+prettyjws")
		resp, err := ioutil.ReadFile("testdata/registry-response.json")
		if err != nil {
			t.Fatalf("Can't load registry test response %s", err.Error())
		}
		fmt.Fprintln(w, string(resp))
	}))
	defer ts.Close()

	image, err := NewImage(&Config{ImageName: "docker-registry.domain.com/nginx:1b29e1531ci"})
	image.Registry = ts.URL
	err = image.Pull()
	if err != nil {
		t.Fatalf("Can't pull image: %s", err)
	}
	if len(image.FsLayers) == 0 {
		t.Fatal("Can't pull fsLayers")
	}
}

func TestPullManifestSchemaV2(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		resp, err := ioutil.ReadFile("testdata/registry-response-schemav2.json")
		w.Header().Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
		if err != nil {
			t.Fatalf("Can't load registry test response %s", err.Error())
		}
		fmt.Fprintln(w, string(resp))
	}))
	defer ts.Close()

	image, err := NewImage(&Config{ImageName: "docker-registry.domain.com/nginx:1b29e1531c"})
	image.Registry = ts.URL
	err = image.Pull()
	if err != nil {
		t.Fatalf("Can't pull image: %s", err)
	}
	if len(image.FsLayers) == 0 {
		t.Fatal("Can't pull fsLayers")
	}
}
<|endoftext|>


<commit_before>package connector

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/Dataman-Cloud/swan/src/manager/framework/event"
	"github.com/Dataman-Cloud/swan/src/mesosproto/mesos"
	"github.com/Dataman-Cloud/swan/src/mesosproto/sched"
	"github.com/Dataman-Cloud/swan/src/swancontext"
	"github.com/Dataman-Cloud/swan/src/utils"

	"github.com/Sirupsen/logrus"
	"github.com/andygrunwald/megos"
	"github.com/golang/protobuf/proto"
	"github.com/samuel/go-zookeeper/zk"
	"golang.org/x/net/context"
)

var SPECIAL_CHARACTER = regexp.MustCompile("([\\-\\.\\$\\*\\+\\?\\{\\}\\(\\)\\[\\]\\|]+)")

var instance *Connector
var once sync.Once

type Connector struct {
	ClusterID             string
	MesosLeader           string
	MesosLeaderHttpClient *HttpClient

	SendChan    chan *sched.Call
	ReceiveChan chan *event.MesosEvent

	mesosFailureChan chan error

	FrameworkInfo *mesos.FrameworkInfo

	StreamCtx       context.Context
	StreamCancelFun context.CancelFunc
}

func NewConnector() *Connector {
	return Instance() // call initialize method
}

func Instance() *Connector {
	once.Do(
		func() {
			hostname, _ := os.Hostname()
			info := &mesos.FrameworkInfo{
				User:      proto.String(swancontext.Instance().Config.Scheduler.MesosFrameworkUser),
				Name:      proto.String("swan"),
				Principal: proto.String("swan"),

				FailoverTimeout: proto.Float64(60),
				Checkpoint:      proto.Bool(false),
				Hostname:        proto.String(hostname),
				Capabilities: []*mesos.FrameworkInfo_Capability{
					&mesos.FrameworkInfo_Capability{Type: mesos.FrameworkInfo_Capability_PARTITION_AWARE.Enum()},
					&mesos.FrameworkInfo_Capability{Type: mesos.FrameworkInfo_Capability_TASK_KILLING_STATE.Enum()},
				},
			}

			instance = &Connector{
				ReceiveChan:   make(chan *event.MesosEvent, 1024), // make this unbound in future
				SendChan:      make(chan *sched.Call, 1024),
				FrameworkInfo: info,
			}
		})

	return instance
}

func (s *Connector) Subscribe(ctx context.Context) {
	logrus.Infof("subscribe to mesos leader: %s", s.MesosLeader)

	call := &sched.Call{
		Type: sched.Call_SUBSCRIBE.Enum(),
		Subscribe: &sched.Call_Subscribe{
			FrameworkInfo: s.FrameworkInfo,
		},
	}

	if s.FrameworkInfo.Id != nil {
		call.FrameworkId = &mesos.FrameworkID{
			Value: proto.String(s.FrameworkInfo.Id.GetValue()),
		}
	}

	resp, err := s.Send(call)
	if err != nil {
		logrus.Errorf("send subscribe call got err: %d", err)
		s.mesosFailureChan <- err

		logrus.Error("exiting Subscribe")
		return
	}

	if resp.StatusCode != http.StatusOK {
		logrus.Errorf("subscribe got http response status code: %d", resp.StatusCode)
		s.mesosFailureChan <- errors.New(fmt.Sprintf("subscribe with unexpected response status: %d", resp.StatusCode))

		logrus.Error("exiting Subscribe")
		return
	}

	s.handleEvents(ctx, resp)
}

func (s *Connector) handleEvents(ctx context.Context, resp *http.Response) {
	defer func() {
		resp.Body.Close()
	}()

	r := NewReader(resp.Body)
	dec := json.NewDecoder(r)

	for {
		select {
		case <-ctx.Done():
			logrus.Infof("goroutine handleEvents cancelled %s", ctx.Err())
			return
		default:
			event := new(sched.Event)
			if err := dec.Decode(event); err != nil {
				logrus.Errorf("handleEvents goroutine decode response got err: %s", err)
				s.mesosFailureChan <- utils.NewError(utils.SeverityLow, err)

				logrus.Error("goroutine handleEvents exiting")
				return
			}

			switch event.GetType() {
			case sched.Event_SUBSCRIBED:
				logrus.Infof("subscribed successful with ID %s", event.GetSubscribed().FrameworkId.GetValue())
				s.addEvent(sched.Event_SUBSCRIBED, event)
			case sched.Event_OFFERS:
				s.addEvent(sched.Event_OFFERS, event)
			case sched.Event_RESCIND:
				s.addEvent(sched.Event_RESCIND, event)
			case sched.Event_UPDATE:
				s.addEvent(sched.Event_UPDATE, event)
			case sched.Event_MESSAGE:
				s.addEvent(sched.Event_MESSAGE, event)
			case sched.Event_FAILURE:
				s.addEvent(sched.Event_FAILURE, event)
			case sched.Event_ERROR:
				s.addEvent(sched.Event_ERROR, event)
			case sched.Event_HEARTBEAT:
				s.addEvent(sched.Event_HEARTBEAT, event)
			}
		}
	}
}

func (s *Connector) SetFrameworkInfoId(id string) {
	s.FrameworkInfo.Id = &mesos.FrameworkID{Value: proto.String(id)}
}

func getMastersFromZK(zkPath string) ([]string, error) {
	masterInfo := new(mesos.MasterInfo)

	if !strings.HasPrefix(zkPath, "zk://") {
		zkPath = fmt.Sprintf("zk://%s", zkPath)
	}
	url, err := url.Parse(zkPath)
	if err != nil {
		return nil, err
	}

	conn, _, err := zk.Connect(strings.Split(url.Host, ","), time.Second)
	defer conn.Close()
	if err != nil {
		return nil, err
	}

	children, _, err := conn.Children(url.Path)
	if err != nil {
		return nil, err
	}

	masters := make([]string, 0)
	for _, node := range children {
		if strings.HasPrefix(node, "json.info") {
			data, _, _ := conn.Get(url.Path + "/" + node)
			err := json.Unmarshal(data, masterInfo)
			if err != nil {
				return nil, err
			}
			masters = append(masters, fmt.Sprintf("%s:%d", *masterInfo.GetAddress().Ip, *masterInfo.GetAddress().Port))
		}
	}

	return masters, nil
}

func stateFromMasters(masters []string) (*megos.State, error) {
	masterUrls := make([]*url.URL, 0)
	for _, master := range masters {
		masterUrl, _ := url.Parse(fmt.Sprintf("http://%s", master))
		masterUrls = append(masterUrls, masterUrl)
	}

	mesos := megos.NewClient(masterUrls, nil)
	return mesos.GetStateFromCluster()
}

func (s *Connector) Send(call *sched.Call) (*http.Response, error) {
	payload, err := proto.Marshal(call)
	if err != nil {
		return nil, err
	}
	return s.MesosLeaderHttpClient.Send(payload)
}

func (s *Connector) addEvent(eventType sched.Event_Type, e *sched.Event) {
	s.ReceiveChan <- &event.MesosEvent{EventType: eventType, Event: e}
}

func (s *Connector) Reregister() error {
	logrus.Infof("register to mesos now")

	if s.StreamCancelFun != nil {
		s.StreamCancelFun()
	}

	err := s.LeaderDetect()
	if err != nil { // if leader detect encounter any error
		logrus.Errorf("exiting reregister due to err: %s", err)
		return err
	}

	s.StreamCtx, s.StreamCancelFun = context.WithCancel(context.Background())
	go s.Subscribe(s.StreamCtx)
	return nil
}

func (s *Connector) Start(ctx context.Context, errorChan chan error) {
	s.mesosFailureChan = errorChan
	err := s.LeaderDetect()
	if err != nil {
		logrus.Errorf("start mesos connector got error: %s", err)
		s.mesosFailureChan <- utils.NewError(utils.SeverityHigh, err) // set SeverityHigh when first start
		return
	}

	s.StreamCtx, s.StreamCancelFun = context.WithCancel(context.Background())
	go s.Subscribe(s.StreamCtx)

	for {
		select {
		case <-ctx.Done():
			logrus.Infof("connector got done signal: %s", ctx.Err())
			s.StreamCancelFun() // stop stream goroutine
			return
		case call := <-s.SendChan:
			logrus.WithFields(logrus.Fields{"sending-call": sched.Call_Type_name[int32(*call.Type)]}).Debugf("%+v", call)
			resp, err := s.Send(call)
			if err != nil {
				logrus.Errorf("send call to master got err: %s", err)
				s.mesosFailureChan <- utils.NewError(utils.SeverityLow, err)
			}
			if resp != nil && resp.StatusCode != 202 {
				logrus.Errorf("send call to master response not valie: %d", resp.StatusCode)
				s.mesosFailureChan <- utils.NewError(utils.SeverityLow, errors.New("sending call response status code not 202"))
			}
		}
	}
}

func (s *Connector) LeaderDetect() error {
	masters, err := getMastersFromZK(swancontext.Instance().Config.Scheduler.ZkPath)
	if err != nil {
		return err
	}

	state, err := stateFromMasters(masters)
	if err != nil {
		return err
	}

	s.MesosLeaderHttpClient = NewHTTPClient(state.Leader, "/api/v1/scheduler")
	s.MesosLeader = state.Leader

	if len(strings.TrimSpace(state.Cluster)) == 0 {
		s.ClusterID = "cluster"
	} else {
		s.ClusterID = state.Cluster
	}

	if SPECIAL_CHARACTER.MatchString(s.ClusterID) {
		logrus.Warnf(`Swan do not work with mesos cluster name(%s) with special characters "-.$*+?{}()[]|".`, s.ClusterID)
		s.ClusterID = SPECIAL_CHARACTER.ReplaceAllString(s.ClusterID, "")
	}

	return nil
}
<commit_msg>convert more default errors to user defined errors, help categories error when handling<commit_after>package connector

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/Dataman-Cloud/swan/src/manager/framework/event"
	"github.com/Dataman-Cloud/swan/src/mesosproto/mesos"
	"github.com/Dataman-Cloud/swan/src/mesosproto/sched"
	"github.com/Dataman-Cloud/swan/src/swancontext"
	"github.com/Dataman-Cloud/swan/src/utils"

	"github.com/Sirupsen/logrus"
	"github.com/andygrunwald/megos"
	"github.com/golang/protobuf/proto"
	"github.com/samuel/go-zookeeper/zk"
	"golang.org/x/net/context"
)

var SPECIAL_CHARACTER = regexp.MustCompile("([\\-\\.\\$\\*\\+\\?\\{\\}\\(\\)\\[\\]\\|]+)")

var instance *Connector
var once sync.Once

type Connector struct {
	ClusterID             string
	MesosLeader           string
	MesosLeaderHttpClient *HttpClient

	SendChan    chan *sched.Call
	ReceiveChan chan *event.MesosEvent

	mesosFailureChan chan error

	FrameworkInfo *mesos.FrameworkInfo

	StreamCtx       context.Context
	StreamCancelFun context.CancelFunc
}

func NewConnector() *Connector {
	return Instance() // call initialize method
}

func Instance() *Connector {
	once.Do(
		func() {
			hostname, _ := os.Hostname()
			info := &mesos.FrameworkInfo{
				User:      proto.String(swancontext.Instance().Config.Scheduler.MesosFrameworkUser),
				Name:      proto.String("swan"),
				Principal: proto.String("swan"),

				FailoverTimeout: proto.Float64(60),
				Checkpoint:      proto.Bool(false),
				Hostname:        proto.String(hostname),
				Capabilities: []*mesos.FrameworkInfo_Capability{
					&mesos.FrameworkInfo_Capability{Type: mesos.FrameworkInfo_Capability_PARTITION_AWARE.Enum()},
					&mesos.FrameworkInfo_Capability{Type: mesos.FrameworkInfo_Capability_TASK_KILLING_STATE.Enum()},
				},
			}

			instance = &Connector{
				ReceiveChan:   make(chan *event.MesosEvent, 1024), // make this unbound in future
				SendChan:      make(chan *sched.Call, 1024),
				FrameworkInfo: info,
			}
		})

	return instance
}

func (s *Connector) Subscribe(ctx context.Context) {
	logrus.Infof("subscribe to mesos leader: %s", s.MesosLeader)

	call := &sched.Call{
		Type: sched.Call_SUBSCRIBE.Enum(),
		Subscribe: &sched.Call_Subscribe{
			FrameworkInfo: s.FrameworkInfo,
		},
	}

	if s.FrameworkInfo.Id != nil {
		call.FrameworkId = &mesos.FrameworkID{
			Value: proto.String(s.FrameworkInfo.Id.GetValue()),
		}
	}

	resp, err := s.Send(call)
	if err != nil {
		logrus.Errorf("send subscribe call got err: %d", err)
		s.mesosFailureChan <- utils.NewError(utils.SeverityLow, err)

		logrus.Error("exiting Subscribe")
		return
	}

	if resp.StatusCode != http.StatusOK {
		logrus.Errorf("subscribe got http response status code: %d", resp.StatusCode)
		s.mesosFailureChan <- utils.NewError(utils.SeverityLow,
			errors.New(fmt.Sprintf("subscribe with unexpected response status: %d", resp.StatusCode)))

		logrus.Error("exiting Subscribe")
		return
	}

	s.handleEvents(ctx, resp)
}

func (s *Connector) handleEvents(ctx context.Context, resp *http.Response) {
	defer func() {
		resp.Body.Close()
	}()

	r := NewReader(resp.Body)
	dec := json.NewDecoder(r)

	for {
		select {
		case <-ctx.Done():
			logrus.Infof("goroutine handleEvents cancelled %s", ctx.Err())
			return
		default:
			event := new(sched.Event)
			if err := dec.Decode(event); err != nil {
				logrus.Errorf("handleEvents goroutine decode response got err: %s", err)
				s.mesosFailureChan <- utils.NewError(utils.SeverityLow, err)

				logrus.Error("goroutine handleEvents exiting")
				return
			}

			switch event.GetType() {
			case sched.Event_SUBSCRIBED:
				logrus.Infof("subscribed successful with ID %s", event.GetSubscribed().FrameworkId.GetValue())
				s.addEvent(sched.Event_SUBSCRIBED, event)
			case sched.Event_OFFERS:
				s.addEvent(sched.Event_OFFERS, event)
			case sched.Event_RESCIND:
				s.addEvent(sched.Event_RESCIND, event)
			case sched.Event_UPDATE:
				s.addEvent(sched.Event_UPDATE, event)
			case sched.Event_MESSAGE:
				s.addEvent(sched.Event_MESSAGE, event)
			case sched.Event_FAILURE:
				s.addEvent(sched.Event_FAILURE, event)
			case sched.Event_ERROR:
				s.addEvent(sched.Event_ERROR, event)
			case sched.Event_HEARTBEAT:
				s.addEvent(sched.Event_HEARTBEAT, event)
			}
		}
	}
}

func (s *Connector) SetFrameworkInfoId(id string) {
	s.FrameworkInfo.Id = &mesos.FrameworkID{Value: proto.String(id)}
}

func getMastersFromZK(zkPath string) ([]string, error) {
	masterInfo := new(mesos.MasterInfo)

	if !strings.HasPrefix(zkPath, "zk://") {
		zkPath = fmt.Sprintf("zk://%s", zkPath)
	}
	url, err := url.Parse(zkPath)
	if err != nil {
		return nil, err
	}

	conn, _, err := zk.Connect(strings.Split(url.Host, ","), time.Second)
	defer conn.Close()
	if err != nil {
		return nil, err
	}

	children, _, err := conn.Children(url.Path)
	if err != nil {
		return nil, err
	}

	masters := make([]string, 0)
	for _, node := range children {
		if strings.HasPrefix(node, "json.info") {
			data, _, _ := conn.Get(url.Path + "/" + node)
			err := json.Unmarshal(data, masterInfo)
			if err != nil {
				return nil, err
			}
			masters = append(masters, fmt.Sprintf("%s:%d", *masterInfo.GetAddress().Ip, *masterInfo.GetAddress().Port))
		}
	}

	return masters, nil
}

func stateFromMasters(masters []string) (*megos.State, error) {
	masterUrls := make([]*url.URL, 0)
	for _, master := range masters {
		masterUrl, _ := url.Parse(fmt.Sprintf("http://%s", master))
		masterUrls = append(masterUrls, masterUrl)
	}

	mesos := megos.NewClient(masterUrls, nil)
	return mesos.GetStateFromCluster()
}

func (s *Connector) Send(call *sched.Call) (*http.Response, error) {
	payload, err := proto.Marshal(call)
	if err != nil {
		return nil, err
	}
	return s.MesosLeaderHttpClient.Send(payload)
}

func (s *Connector) addEvent(eventType sched.Event_Type, e *sched.Event) {
	s.ReceiveChan <- &event.MesosEvent{EventType: eventType, Event: e}
}

func (s *Connector) Reregister() error {
	logrus.Infof("register to mesos now")

	if s.StreamCancelFun != nil {
		s.StreamCancelFun()
	}

	err := s.LeaderDetect()
	if err != nil { // if leader detect encounter any error
		logrus.Errorf("exiting reregister due to err: %s", err)
		return err
	}

	s.StreamCtx, s.StreamCancelFun = context.WithCancel(context.Background())
	go s.Subscribe(s.StreamCtx)
	return nil
}

func (s *Connector) Start(ctx context.Context, errorChan chan error) {
	s.mesosFailureChan = errorChan
	err := s.LeaderDetect()
	if err != nil {
		logrus.Errorf("start mesos connector got error: %s", err)
		s.mesosFailureChan <- utils.NewError(utils.SeverityHigh, err) // set SeverityHigh when first start
		return
	}

	s.StreamCtx, s.StreamCancelFun = context.WithCancel(context.Background())
	go s.Subscribe(s.StreamCtx)

	for {
		select {
		case <-ctx.Done():
			logrus.Infof("connector got done signal: %s", ctx.Err())
			s.StreamCancelFun() // stop stream goroutine
			return
		case call := <-s.SendChan:
			logrus.WithFields(logrus.Fields{"sending-call": sched.Call_Type_name[int32(*call.Type)]}).Debugf("%+v", call)
			resp, err := s.Send(call)
			if err != nil {
				logrus.Errorf("send call to master got err: %s", err)
				s.mesosFailureChan <- utils.NewError(utils.SeverityLow, err)
			}
			if resp != nil && resp.StatusCode != 202 {
				logrus.Errorf("send call to master response not valie: %d", resp.StatusCode)
				s.mesosFailureChan <- utils.NewError(utils.SeverityLow, errors.New("sending call response status code not 202"))
			}
		}
	}
}

func (s *Connector) LeaderDetect() error {
	masters, err := getMastersFromZK(swancontext.Instance().Config.Scheduler.ZkPath)
	if err != nil {
		return err
	}

	state, err := stateFromMasters(masters)
	if err != nil {
		return err
	}

	s.MesosLeaderHttpClient = NewHTTPClient(state.Leader, "/api/v1/scheduler")
	s.MesosLeader = state.Leader

	if len(strings.TrimSpace(state.Cluster)) == 0 {
		s.ClusterID = "cluster"
	} else {
		s.ClusterID = state.Cluster
	}

	if SPECIAL_CHARACTER.MatchString(s.ClusterID) {
		logrus.Warnf(`Swan do not work with mesos cluster name(%s) with special characters "-.$*+?{}()[]|".`, s.ClusterID)
		s.ClusterID = SPECIAL_CHARACTER.ReplaceAllString(s.ClusterID, "")
	}

	return nil
}
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0
// Details: https://raw.githubusercontent.com/maniksurtani/quotaservice/master/LICENSE

package main

import (
	"net/http"
	"os"
	"os/signal"
	"sync"
	"syscall"

	"github.com/maniksurtani/quotaservice"
	"github.com/maniksurtani/quotaservice/buckets/memory"
	"github.com/maniksurtani/quotaservice/config"
	"github.com/maniksurtani/quotaservice/rpc/grpc"
	"github.com/maniksurtani/quotaservice/stats"
)

func main() {
	cfg := config.NewDefaultServiceConfig()
	ns := config.NewDefaultNamespaceConfig("test.namespace")
	ns.DynamicBucketTemplate = config.NewDefaultBucketConfig(config.DynamicBucketTemplateName)
	ns.DynamicBucketTemplate.Size = 100000000000
	ns.DynamicBucketTemplate.FillRate = 100000000
	b := config.NewDefaultBucketConfig("xyz")
	config.AddBucket(ns, b)
	config.AddNamespace(cfg, ns)

	ns = config.NewDefaultNamespaceConfig("test.namespace2")
	ns.DefaultBucket = config.NewDefaultBucketConfig(config.DefaultBucketName)
	b = config.NewDefaultBucketConfig("xyz")
	config.AddBucket(ns, b)
	config.AddNamespace(cfg, ns)

	server := quotaservice.New(memory.NewBucketFactory(),
		config.NewMemoryConfigPersister(),
		cfg,
		grpc.New("localhost:10990"))
	server.SetStatsListener(stats.NewMemoryStatsListener())
	server.Start()

	// Serve Admin Console
	sm := http.NewServeMux()
	server.ServeAdminConsole(sm, "admin/public", true)
	go func() { http.ListenAndServe("localhost:8080", sm) }()

	// Block until SIGTERM, SIGKILL or SIGINT
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGINT)

	var shutdown sync.WaitGroup
	shutdown.Add(1)

	go func() {
		<-sigs
		shutdown.Done()
	}()

	shutdown.Wait()
	server.Stop()
}
<commit_msg>Log admin host and port when running test server (#28)<commit_after>// Licensed under the Apache License, Version 2.0
// Details: https://raw.githubusercontent.com/maniksurtani/quotaservice/master/LICENSE

package main

import (
	"net/http"
	"os"
	"os/signal"
	"sync"
	"syscall"

	"github.com/maniksurtani/quotaservice"
	"github.com/maniksurtani/quotaservice/buckets/memory"
	"github.com/maniksurtani/quotaservice/config"
	"github.com/maniksurtani/quotaservice/logging"
	"github.com/maniksurtani/quotaservice/rpc/grpc"
	"github.com/maniksurtani/quotaservice/stats"
)

const (
	ADMIN_SERVER = "localhost:8080"
	GRPC_SERVER  = "localhost:10990"
)

func main() {
	cfg := config.NewDefaultServiceConfig()
	ns := config.NewDefaultNamespaceConfig("test.namespace")
	ns.DynamicBucketTemplate = config.NewDefaultBucketConfig(config.DynamicBucketTemplateName)
	ns.DynamicBucketTemplate.Size = 100000000000
	ns.DynamicBucketTemplate.FillRate = 100000000
	b := config.NewDefaultBucketConfig("xyz")
	config.AddBucket(ns, b)
	config.AddNamespace(cfg, ns)

	ns = config.NewDefaultNamespaceConfig("test.namespace2")
	ns.DefaultBucket = config.NewDefaultBucketConfig(config.DefaultBucketName)
	b = config.NewDefaultBucketConfig("xyz")
	config.AddBucket(ns, b)
	config.AddNamespace(cfg, ns)

	server := quotaservice.New(memory.NewBucketFactory(),
		config.NewMemoryConfigPersister(),
		cfg,
		grpc.New(GRPC_SERVER))
	server.SetStatsListener(stats.NewMemoryStatsListener())
	server.Start()

	// Serve Admin Console
	logging.Printf("Starting admin server on %v\n", ADMIN_SERVER)
	sm := http.NewServeMux()
	server.ServeAdminConsole(sm, "admin/public", true)
	go func() {
		http.ListenAndServe(ADMIN_SERVER, sm)
	}()

	// Block until SIGTERM, SIGKILL or SIGINT
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGINT)

	var shutdown sync.WaitGroup
	shutdown.Add(1)

	go func() {
		<-sigs
		shutdown.Done()
	}()

	shutdown.Wait()
	server.Stop()
}
<|endoftext|>


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

package ddl

import (
	"fmt"
	"math"
	"os"
	"strconv"
	"sync/atomic"
	"time"

	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/clientv3/concurrency"
	"github.com/coreos/etcd/mvcc/mvccpb"
	"github.com/juju/errors"
	"github.com/ngaut/log"
	goctx "golang.org/x/net/context"
)

// OwnerManager is used to campaign the owner and manage the owner information.
type OwnerManager interface {
	// ID returns the ID of DDL.
	ID() string
	// IsOwner returns whether the ownerManager is the DDL owner.
	IsOwner() bool
	// SetOwner sets whether the ownerManager is the DDL owner.
	SetOwner(isOwner bool)
	// IsOwner returns whether the ownerManager is the background owner.
	IsBgOwner() bool
	// SetOwner sets whether the ownerManager is the background owner.
	SetBgOwner(isOwner bool)
	// GetOwnerID gets the owner ID.
	GetOwnerID(ctx goctx.Context, ownerKey string) (string, error)
	// CampaignOwners campaigns the DDL owner and the background owner.
	CampaignOwners(ctx goctx.Context) error
	// Cancel cancels this etcd ownerManager campaign.
	Cancel()
}

const (
	// DDLOwnerKey is the ddl owner path that is saved to etcd, and it's exported for testing.
	DDLOwnerKey = "/tidb/ddl/fg/owner"
	// BgOwnerKey is the background owner path that is saved to etcd, and it's exported for testing.
	BgOwnerKey                = "/tidb/ddl/bg/owner"
	newSessionDefaultRetryCnt = 3
	newSessionRetryUnlimited  = math.MaxInt64
)

// ownerManager represents the structure which is used for electing owner.
type ownerManager struct {
	ddlOwner int32
	bgOwner  int32
	ddlID    string // id is the ID of DDL.
	etcdCli  *clientv3.Client
	cancel   goctx.CancelFunc
}

// NewOwnerManager creates a new OwnerManager.
func NewOwnerManager(etcdCli *clientv3.Client, id string, cancel goctx.CancelFunc) OwnerManager {
	return &ownerManager{
		etcdCli: etcdCli,
		ddlID:   id,
		cancel:  cancel,
	}
}

// ID implements OwnerManager.ID interface.
func (m *ownerManager) ID() string {
	return m.ddlID
}

// IsOwner implements OwnerManager.IsOwner interface.
func (m *ownerManager) IsOwner() bool {
	return atomic.LoadInt32(&m.ddlOwner) == 1
}

// SetOwner implements OwnerManager.SetOwner interface.
func (m *ownerManager) SetOwner(isOwner bool) {
	if isOwner {
		atomic.StoreInt32(&m.ddlOwner, 1)
	} else {
		atomic.StoreInt32(&m.ddlOwner, 0)
	}
}

// Cancel implements OwnerManager.Cancel interface.
func (m *ownerManager) Cancel() {
	m.cancel()
}

// IsBgOwner implements OwnerManager.IsBgOwner interface.
func (m *ownerManager) IsBgOwner() bool {
	return atomic.LoadInt32(&m.bgOwner) == 1
}

// SetBgOwner implements OwnerManager.SetBgOwner interface.
func (m *ownerManager) SetBgOwner(isOwner bool) {
	if isOwner {
		atomic.StoreInt32(&m.bgOwner, 1)
	} else {
		atomic.StoreInt32(&m.bgOwner, 0)
	}
}

// ManagerSessionTTL is the etcd session's TTL in seconds. It's exported for testing.
var ManagerSessionTTL = 60

// setManagerSessionTTL sets the ManagerSessionTTL value, it's used for testing.
func setManagerSessionTTL() error {
	ttlStr := os.Getenv("tidb_manager_ttl")
	if len(ttlStr) == 0 {
		return nil
	}
	ttl, err := strconv.Atoi(ttlStr)
	if err != nil {
		return errors.Trace(err)
	}
	ManagerSessionTTL = ttl
	return nil
}

func newSession(ctx goctx.Context, flag string, etcdCli *clientv3.Client, retryCnt, ttl int) (*concurrency.Session, error) {
	var err error
	var etcdSession *concurrency.Session
	for i := 0; i < retryCnt; i++ {
		etcdSession, err = concurrency.NewSession(etcdCli,
			concurrency.WithTTL(ttl), concurrency.WithContext(ctx))
		if err == nil {
			break
		}
		log.Warnf("[ddl] %s failed to new session, err %v", flag, err)
		if isContextFinished(err) {
			break
		}
		time.Sleep(200 * time.Millisecond)
		continue
	}
	return etcdSession, errors.Trace(err)
}

// CampaignOwners implements OwnerManager.CampaignOwners interface.
func (m *ownerManager) CampaignOwners(ctx goctx.Context) error {
	ddlSession, err := newSession(ctx, DDLOwnerKey, m.etcdCli, newSessionDefaultRetryCnt, ManagerSessionTTL)
	if err != nil {
		return errors.Trace(err)
	}
	bgSession, err := newSession(ctx, BgOwnerKey, m.etcdCli, newSessionDefaultRetryCnt, ManagerSessionTTL)
	if err != nil {
		return errors.Trace(err)
	}

	ddlCtx, _ := goctx.WithCancel(ctx)
	go m.campaignLoop(ddlCtx, ddlSession, DDLOwnerKey)

	bgCtx, _ := goctx.WithCancel(ctx)
	go m.campaignLoop(bgCtx, bgSession, BgOwnerKey)
	return nil
}

func (m *ownerManager) campaignLoop(ctx goctx.Context, etcdSession *concurrency.Session, key string) {
	idInfo := fmt.Sprintf("%s ownerManager %s", key, m.ddlID)
	var err error
	for {
		select {
		case <-etcdSession.Done():
			log.Infof("[ddl] %s etcd session is done, creates a new one", idInfo)
			etcdSession, err = newSession(ctx, idInfo, m.etcdCli, newSessionRetryUnlimited, ManagerSessionTTL)
			if err != nil {
				log.Infof("[ddl] %s break campaign loop, err %v", idInfo, err)
				return
			}
		case <-ctx.Done():
			// Revoke the session lease.
			// If revoke takes longer than the ttl, lease is expired anyway.
			cancelCtx, cancel := goctx.WithTimeout(goctx.Background(),
				time.Duration(ManagerSessionTTL)*time.Second)
			_, err = m.etcdCli.Revoke(cancelCtx, etcdSession.Lease())
			cancel()
			log.Infof("[ddl] %s break campaign loop err %v", idInfo, err)
			return
		default:
		}

		elec := concurrency.NewElection(etcdSession, key)
		err = elec.Campaign(ctx, m.ddlID)
		if err != nil {
			log.Infof("[ddl] %s failed to campaign, err %v", idInfo, err)
			if isContextFinished(err) {
				log.Warnf("[ddl] %s campaign loop, err %v", idInfo, err)
				return
			}
			continue
		}

		ownerKey, err := GetOwnerInfo(ctx, elec, key, m.ddlID)
		if err != nil {
			continue
		}
		m.setOwnerVal(key, true)

		m.watchOwner(ctx, etcdSession, ownerKey)
		m.setOwnerVal(key, false)
	}
}

// GetOwnerID implements OwnerManager.GetOwnerID interface.
func (m *ownerManager) GetOwnerID(ctx goctx.Context, key string) (string, error) {
	resp, err := m.etcdCli.Get(ctx, key, clientv3.WithFirstCreate()...)
	if err != nil {
		return "", errors.Trace(err)
	}
	if len(resp.Kvs) == 0 {
		return "", concurrency.ErrElectionNoLeader
	}
	return string(resp.Kvs[0].Value), nil
}

// GetOwnerInfo gets the owner information.
func GetOwnerInfo(ctx goctx.Context, elec *concurrency.Election, key, id string) (string, error) {
	resp, err := elec.Leader(ctx)
	if err != nil {
		// If no leader elected currently, it returns ErrElectionNoLeader.
		log.Infof("[ddl] %s ownerManager %s failed to get leader, err %v", key, id, err)
		return "", errors.Trace(err)
	}
	ownerID := string(resp.Kvs[0].Value)
	log.Infof("[ddl] %s ownerManager is %s, owner is %v", key, id, ownerID)
	if ownerID != id {
		log.Warnf("[ddl] %s ownerManager %s isn't the owner", key, id)
		return "", errors.New("ownerInfoNotMatch")
	}

	return string(resp.Kvs[0].Key), nil
}

func (m *ownerManager) setOwnerVal(key string, val bool) {
	if key == DDLOwnerKey {
		m.SetOwner(val)
	} else {
		m.SetBgOwner(val)
	}
}

func (m *ownerManager) watchOwner(ctx goctx.Context, etcdSession *concurrency.Session, key string) {
	log.Debugf("[ddl] ownerManager %s watch owner key %v", m.ddlID, key)
	watchCh := m.etcdCli.Watch(ctx, key)
	for {
		select {
		case resp := <-watchCh:
			if resp.Canceled {
				log.Infof("[ddl] ownerManager %s watch owner key %v failed, no owner",
					m.ddlID, key)
				return
			}

			for _, ev := range resp.Events {
				if ev.Type == mvccpb.DELETE {
					log.Infof("[ddl] ownerManager %s watch owner key %v failed, owner is deleted", m.ddlID, key)
					return
				}
			}
		case <-etcdSession.Done():
			return
		case <-ctx.Done():
			return
		}
	}
}

func init() {
	err := setManagerSessionTTL()
	if err != nil {
		log.Warnf("[ddl] set manager session TTL failed %v", err)
	}
}
<commit_msg>ddl: Add a safeguard for lease not found error (#3869)<commit_after>// Copyright 2017 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package ddl

import (
	"fmt"
	"math"
	"os"
	"strconv"
	"sync/atomic"
	"time"

	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/clientv3/concurrency"
	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
	"github.com/coreos/etcd/mvcc/mvccpb"
	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/tidb/terror"
	goctx "golang.org/x/net/context"
)

// OwnerManager is used to campaign the owner and manage the owner information.
type OwnerManager interface {
	// ID returns the ID of DDL.
	ID() string
	// IsOwner returns whether the ownerManager is the DDL owner.
	IsOwner() bool
	// SetOwner sets whether the ownerManager is the DDL owner.
	SetOwner(isOwner bool)
	// IsOwner returns whether the ownerManager is the background owner.
	IsBgOwner() bool
	// SetOwner sets whether the ownerManager is the background owner.
	SetBgOwner(isOwner bool)
	// GetOwnerID gets the owner ID.
	GetOwnerID(ctx goctx.Context, ownerKey string) (string, error)
	// CampaignOwners campaigns the DDL owner and the background owner.
	CampaignOwners(ctx goctx.Context) error
	// Cancel cancels this etcd ownerManager campaign.
	Cancel()
}

const (
	// DDLOwnerKey is the ddl owner path that is saved to etcd, and it's exported for testing.
	DDLOwnerKey = "/tidb/ddl/fg/owner"
	// BgOwnerKey is the background owner path that is saved to etcd, and it's exported for testing.
	BgOwnerKey                = "/tidb/ddl/bg/owner"
	newSessionDefaultRetryCnt = 3
	newSessionRetryUnlimited  = math.MaxInt64
)

// ownerManager represents the structure which is used for electing owner.
type ownerManager struct {
	ddlOwner int32
	bgOwner  int32
	ddlID    string // id is the ID of DDL.
	etcdCli  *clientv3.Client
	cancel   goctx.CancelFunc
}

// NewOwnerManager creates a new OwnerManager.
func NewOwnerManager(etcdCli *clientv3.Client, id string, cancel goctx.CancelFunc) OwnerManager {
	return &ownerManager{
		etcdCli: etcdCli,
		ddlID:   id,
		cancel:  cancel,
	}
}

// ID implements OwnerManager.ID interface.
func (m *ownerManager) ID() string {
	return m.ddlID
}

// IsOwner implements OwnerManager.IsOwner interface.
func (m *ownerManager) IsOwner() bool {
	return atomic.LoadInt32(&m.ddlOwner) == 1
}

// SetOwner implements OwnerManager.SetOwner interface.
func (m *ownerManager) SetOwner(isOwner bool) {
	if isOwner {
		atomic.StoreInt32(&m.ddlOwner, 1)
	} else {
		atomic.StoreInt32(&m.ddlOwner, 0)
	}
}

// Cancel implements OwnerManager.Cancel interface.
func (m *ownerManager) Cancel() {
	m.cancel()
}

// IsBgOwner implements OwnerManager.IsBgOwner interface.
func (m *ownerManager) IsBgOwner() bool {
	return atomic.LoadInt32(&m.bgOwner) == 1
}

// SetBgOwner implements OwnerManager.SetBgOwner interface.
func (m *ownerManager) SetBgOwner(isOwner bool) {
	if isOwner {
		atomic.StoreInt32(&m.bgOwner, 1)
	} else {
		atomic.StoreInt32(&m.bgOwner, 0)
	}
}

// ManagerSessionTTL is the etcd session's TTL in seconds. It's exported for testing.
var ManagerSessionTTL = 60

// setManagerSessionTTL sets the ManagerSessionTTL value, it's used for testing.
func setManagerSessionTTL() error {
	ttlStr := os.Getenv("tidb_manager_ttl")
	if len(ttlStr) == 0 {
		return nil
	}
	ttl, err := strconv.Atoi(ttlStr)
	if err != nil {
		return errors.Trace(err)
	}
	ManagerSessionTTL = ttl
	return nil
}

func newSession(ctx goctx.Context, flag string, etcdCli *clientv3.Client, retryCnt, ttl int) (*concurrency.Session, error) {
	var err error
	var etcdSession *concurrency.Session
	for i := 0; i < retryCnt; i++ {
		etcdSession, err = concurrency.NewSession(etcdCli,
			concurrency.WithTTL(ttl), concurrency.WithContext(ctx))
		if err == nil {
			break
		}
		log.Warnf("[ddl] %s failed to new session, err %v", flag, err)
		if isContextFinished(err) {
			break
		}
		time.Sleep(200 * time.Millisecond)
		continue
	}
	return etcdSession, errors.Trace(err)
}

// CampaignOwners implements OwnerManager.CampaignOwners interface.
func (m *ownerManager) CampaignOwners(ctx goctx.Context) error {
	ddlSession, err := newSession(ctx, DDLOwnerKey, m.etcdCli, newSessionDefaultRetryCnt, ManagerSessionTTL)
	if err != nil {
		return errors.Trace(err)
	}
	bgSession, err := newSession(ctx, BgOwnerKey, m.etcdCli, newSessionDefaultRetryCnt, ManagerSessionTTL)
	if err != nil {
		return errors.Trace(err)
	}

	ddlCtx, _ := goctx.WithCancel(ctx)
	go m.campaignLoop(ddlCtx, ddlSession, DDLOwnerKey)

	bgCtx, _ := goctx.WithCancel(ctx)
	go m.campaignLoop(bgCtx, bgSession, BgOwnerKey)
	return nil
}

func (m *ownerManager) campaignLoop(ctx goctx.Context, etcdSession *concurrency.Session, key string) {
	idInfo := fmt.Sprintf("%s ownerManager %s", key, m.ddlID)
	var err error
	for {
		select {
		case <-etcdSession.Done():
			log.Infof("[ddl] %s etcd session is done, creates a new one", idInfo)
			etcdSession, err = newSession(ctx, idInfo, m.etcdCli, newSessionRetryUnlimited, ManagerSessionTTL)
			if err != nil {
				log.Infof("[ddl] %s break campaign loop, err %v", idInfo, err)
				return
			}
		case <-ctx.Done():
			// Revoke the session lease.
			// If revoke takes longer than the ttl, lease is expired anyway.
			cancelCtx, cancel := goctx.WithTimeout(goctx.Background(),
				time.Duration(ManagerSessionTTL)*time.Second)
			_, err = m.etcdCli.Revoke(cancelCtx, etcdSession.Lease())
			cancel()
			log.Infof("[ddl] %s break campaign loop err %v", idInfo, err)
			return
		default:
		}
		// If the etcd server turns clocks forward，the following case may occur.
		// The etcd server deletes this session's lease ID, but etcd session doesn't find it.
		// In this time if we do the campaign operation, the etcd server will return ErrLeaseNotFound.
		if terror.ErrorEqual(err, rpctypes.ErrLeaseNotFound) {
			if etcdSession != nil {
				err = etcdSession.Close()
				log.Infof("[ddl] %s etcd session encounters the error of lease not found, closes it err %s", idInfo, err)
			}
			continue
		}

		elec := concurrency.NewElection(etcdSession, key)
		err = elec.Campaign(ctx, m.ddlID)
		if err != nil {
			log.Infof("[ddl] %s failed to campaign, err %v", idInfo, err)
			if isContextFinished(err) {
				log.Warnf("[ddl] %s campaign loop, err %v", idInfo, err)
				return
			}
			continue
		}

		ownerKey, err := GetOwnerInfo(ctx, elec, key, m.ddlID)
		if err != nil {
			continue
		}
		m.setOwnerVal(key, true)

		m.watchOwner(ctx, etcdSession, ownerKey)
		m.setOwnerVal(key, false)
	}
}

// GetOwnerID implements OwnerManager.GetOwnerID interface.
func (m *ownerManager) GetOwnerID(ctx goctx.Context, key string) (string, error) {
	resp, err := m.etcdCli.Get(ctx, key, clientv3.WithFirstCreate()...)
	if err != nil {
		return "", errors.Trace(err)
	}
	if len(resp.Kvs) == 0 {
		return "", concurrency.ErrElectionNoLeader
	}
	return string(resp.Kvs[0].Value), nil
}

// GetOwnerInfo gets the owner information.
func GetOwnerInfo(ctx goctx.Context, elec *concurrency.Election, key, id string) (string, error) {
	resp, err := elec.Leader(ctx)
	if err != nil {
		// If no leader elected currently, it returns ErrElectionNoLeader.
		log.Infof("[ddl] %s ownerManager %s failed to get leader, err %v", key, id, err)
		return "", errors.Trace(err)
	}
	ownerID := string(resp.Kvs[0].Value)
	log.Infof("[ddl] %s ownerManager is %s, owner is %v", key, id, ownerID)
	if ownerID != id {
		log.Warnf("[ddl] %s ownerManager %s isn't the owner", key, id)
		return "", errors.New("ownerInfoNotMatch")
	}

	return string(resp.Kvs[0].Key), nil
}

func (m *ownerManager) setOwnerVal(key string, val bool) {
	if key == DDLOwnerKey {
		m.SetOwner(val)
	} else {
		m.SetBgOwner(val)
	}
}

func (m *ownerManager) watchOwner(ctx goctx.Context, etcdSession *concurrency.Session, key string) {
	log.Debugf("[ddl] ownerManager %s watch owner key %v", m.ddlID, key)
	watchCh := m.etcdCli.Watch(ctx, key)
	for {
		select {
		case resp := <-watchCh:
			if resp.Canceled {
				log.Infof("[ddl] ownerManager %s watch owner key %v failed, no owner",
					m.ddlID, key)
				return
			}

			for _, ev := range resp.Events {
				if ev.Type == mvccpb.DELETE {
					log.Infof("[ddl] ownerManager %s watch owner key %v failed, owner is deleted", m.ddlID, key)
					return
				}
			}
		case <-etcdSession.Done():
			return
		case <-ctx.Done():
			return
		}
	}
}

func init() {
	err := setManagerSessionTTL()
	if err != nil {
		log.Warnf("[ddl] set manager session TTL failed %v", err)
	}
}
<|endoftext|>


<commit_before>package sftp_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"restic/backend"
	"restic/backend/sftp"
	"restic/backend/test"

	. "restic/test"
)

var tempBackendDir string

//go:generate go run ../test/generate_backend_tests.go

func createTempdir() error {
	if tempBackendDir != "" {
		return nil
	}

	tempdir, err := ioutil.TempDir("", "restic-local-test-")
	if err != nil {
		return err
	}

	fmt.Printf("created new test backend at %v\n", tempdir)
	tempBackendDir = tempdir
	return nil
}

func init() {
	sftpserver := ""

	for _, dir := range strings.Split(TestSFTPPath, ":") {
		testpath := filepath.Join(dir, "sftp-server")
		_, err := os.Stat(testpath)
		if !os.IsNotExist(err) {
			sftpserver = testpath
			break
		}
	}

	if sftpserver == "" {
		SkipMessage = "sftp server binary not found, skipping tests"
		return
	}

	args := []string{"-e"}

	test.CreateFn = func() (backend.Backend, error) {
		err := createTempdir()
		if err != nil {
			return nil, err
		}

		return sftp.Create(tempBackendDir, sftpserver, args...)
	}

	test.OpenFn = func() (backend.Backend, error) {
		err := createTempdir()
		if err != nil {
			return nil, err
		}
		return sftp.Open(tempBackendDir, sftpserver, args...)
	}

	test.CleanupFn = func() error {
		if tempBackendDir == "" {
			return nil
		}

		fmt.Printf("removing test backend at %v\n", tempBackendDir)
		err := os.RemoveAll(tempBackendDir)
		tempBackendDir = ""
		return err
	}
}
<commit_msg>Remove debug output for tests<commit_after>package sftp_test

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"restic/backend"
	"restic/backend/sftp"
	"restic/backend/test"

	. "restic/test"
)

var tempBackendDir string

//go:generate go run ../test/generate_backend_tests.go

func createTempdir() error {
	if tempBackendDir != "" {
		return nil
	}

	tempdir, err := ioutil.TempDir("", "restic-local-test-")
	if err != nil {
		return err
	}

	tempBackendDir = tempdir
	return nil
}

func init() {
	sftpserver := ""

	for _, dir := range strings.Split(TestSFTPPath, ":") {
		testpath := filepath.Join(dir, "sftp-server")
		_, err := os.Stat(testpath)
		if !os.IsNotExist(err) {
			sftpserver = testpath
			break
		}
	}

	if sftpserver == "" {
		SkipMessage = "sftp server binary not found, skipping tests"
		return
	}

	args := []string{"-e"}

	test.CreateFn = func() (backend.Backend, error) {
		err := createTempdir()
		if err != nil {
			return nil, err
		}

		return sftp.Create(tempBackendDir, sftpserver, args...)
	}

	test.OpenFn = func() (backend.Backend, error) {
		err := createTempdir()
		if err != nil {
			return nil, err
		}
		return sftp.Open(tempBackendDir, sftpserver, args...)
	}

	test.CleanupFn = func() error {
		if tempBackendDir == "" {
			return nil
		}

		err := os.RemoveAll(tempBackendDir)
		tempBackendDir = ""
		return err
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/flowcommerce/tools/executor"
)

func main() {
	executor := executor.Create("lib-event")
	executor = executor.Add("dev tag --label micro")
	executor = executor.Add("sbt publish")
	executor.Run()
}
<commit_msg>Remove explicit label (no longer needed in dev)<commit_after>package main

import (
	"github.com/flowcommerce/tools/executor"
)

func main() {
	executor := executor.Create("lib-event")
	executor = executor.Add("dev tag")
	executor = executor.Add("sbt publish")
	executor.Run()
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Michael Wendland
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * 	Authors:
 * 		Michael Wendland <michael@michiwend.com>
 */

package gomusicbrainz

import "encoding/xml"

// Release represents a unique release (i.e. issuing) of a product on a
// specific date with specific release information such as the country, label,
// barcode, packaging, etc. More information at https://musicbrainz.org/doc/Release
type Release struct {
	ID                 MBID               `xml:"id,attr"`
	Title              string             `xml:"title"`
	Status             string             `xml:"status"`
	Disambiguation     string             `xml:"disambiguation"`
	TextRepresentation TextRepresentation `xml:"text-representation"`
	ArtistCredit       ArtistCredit       `xml:"artist-credit"`
	ReleaseGroup       ReleaseGroup       `xml:"release-group"`
	Date               BrainzTime         `xml:"date"`
	CountryCode        string             `xml:"country"`
	Barcode            string             `xml:"barcode"`
	Asin               string             `xml:"asin"`
	Quality            string             `xml:"quality"`
	LabelInfos         []LabelInfo        `xml:"label-info-list>label-info"`
	Mediums            []*Medium          `xml:"medium-list>medium"`
	Relations          TargetRelationsMap `xml:"relation-list"`
}

func (mbe *Release) lookupResult() interface{} {
	var res struct {
		XMLName xml.Name `xml:"metadata"`
		Ptr     *Release `xml:"release"`
	}
	res.Ptr = mbe
	return &res
}

func (mbe *Release) apiEndpoint() string {
	return "/release"
}

func (mbe *Release) Id() MBID {
	return mbe.ID
}

// LookupRelease performs a release lookup request for the given MBID.
func (c *WS2Client) LookupRelease(id MBID, inc ...string) (*Release, error) {
	a := &Release{ID: id}
	err := c.Lookup(a, inc...)

	return a, err
}

// SearchRelease queries MusicBrainz´ Search Server for Releases.
//
// Possible search fields to provide in searchTerm are:
//
//	arid           artist id
//	artist         complete artist name(s) as it appears on the release
//	artistname     an artist on the release, each artist added as a separate field
//	asin           the Amazon ASIN for this release
//	barcode        The barcode of this release
//	catno          The catalog number for this release, can have multiples when major using an imprint
//	comment        Disambiguation comment
//	country        The two letter country code for the release country
//	creditname     name credit on the release, each artist added as a separate field
//	date           The release date (format: YYYY-MM-DD)
//	discids        total number of cd ids over all mediums for the release
//	discidsmedium  number of cd ids for the release on a medium in the release
//	format         release format
//	laid           The label id for this release, a release can have multiples when major using an imprint
//	label          The name of the label for this release, can have multiples when major using an imprint
//	lang           The language for this release. Use the three character ISO 639 codes to search for a specific language. (e.g. lang:eng)
//	mediums        number of mediums in the release
//	primarytype    primary type of the release group (album, single, ep, other)
//	puid           The release contains recordings with these puids
//	quality        The quality of the release (low, normal, high)
//	reid           release id
//	release        release name
//	releaseaccent  name of the release with any accent characters retained
//	rgid           release group id
//	script         The 4 character script code (e.g. latn) used for this release
//	secondarytype  secondary type of the release group (audiobook, compilation, interview, live, remix, soundtrack, spokenword)
//	status         release status (e.g official)
//	tag            a tag that appears on the release
//	tracks         total number of tracks over all mediums on the release
//	tracksmedium   number of tracks on a medium in the release
//	type           type of the release group, old type mapping for when we did not have separate primary and secondary types
//
// With no fields specified searchTerm searches the release field only. For
// more information visit
// https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search#Release
func (c *WS2Client) SearchRelease(searchTerm string, limit, offset int) (*ReleaseSearchResponse, error) {

	result := releaseListResult{}
	err := c.searchRequest("/release", &result, searchTerm, limit, offset)

	rsp := ReleaseSearchResponse{}
	rsp.WS2ListResponse = result.ReleaseList.WS2ListResponse
	rsp.Scores = make(ScoreMap)

	for i, v := range result.ReleaseList.Releases {
		rsp.Releases = append(rsp.Releases, v.Release)
		rsp.Scores[rsp.Releases[i]] = v.Score
	}

	return &rsp, err
}

// ReleaseSearchResponse is the response type returned by the SearchRelease method.
type ReleaseSearchResponse struct {
	WS2ListResponse
	Releases []*Release
	Scores   ScoreMap
}

// ResultsWithScore returns a slice of Releases with a specific score.
func (r *ReleaseSearchResponse) ResultsWithScore(score int) []*Release {
	var res []*Release
	for _, v := range r.Releases {
		if r.Scores[v] == score {
			res = append(res, v)
		}
	}
	return res
}

// OriginalRelease is a helper function that returns the earliest release of
// a release array with the most accurate date. It can be used to determine
// the original/first release from releases of a release group.
func OriginalRelease(releases []*Release) *Release {

	original := releases[0] // fall back on the first item

	for _, release := range releases {

		if !release.Date.IsZero() {

			if release.Date.Year() < original.Date.Year() {
				original = release
			} else if release.Date.Year() == original.Date.Year() &&
				release.Date.Accuracy > Year {

				if original.Date.Accuracy == Year ||
					release.Date.Month() < original.Date.Month() {

					original = release

				} else if release.Date.Month() == original.Date.Month() &&
					release.Date.Accuracy > Month {

					if original.Date.Accuracy == Month ||
						release.Date.Day() < original.Date.Day() {
						original = release
					}
				}
			}
		}
	}

	return original
}

type releaseListResult struct {
	ReleaseList struct {
		WS2ListResponse
		Releases []struct {
			*Release
			Score int `xml:"http://musicbrainz.org/ns/ext#-2.0 score,attr"`
		} `xml:"release"`
	} `xml:"release-list"`
}
<commit_msg>Adds missing check whether slice is empty.<commit_after>/*
 * Copyright (c) 2014 Michael Wendland
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * 	Authors:
 * 		Michael Wendland <michael@michiwend.com>
 */

package gomusicbrainz

import "encoding/xml"

// Release represents a unique release (i.e. issuing) of a product on a
// specific date with specific release information such as the country, label,
// barcode, packaging, etc. More information at https://musicbrainz.org/doc/Release
type Release struct {
	ID                 MBID               `xml:"id,attr"`
	Title              string             `xml:"title"`
	Status             string             `xml:"status"`
	Disambiguation     string             `xml:"disambiguation"`
	TextRepresentation TextRepresentation `xml:"text-representation"`
	ArtistCredit       ArtistCredit       `xml:"artist-credit"`
	ReleaseGroup       ReleaseGroup       `xml:"release-group"`
	Date               BrainzTime         `xml:"date"`
	CountryCode        string             `xml:"country"`
	Barcode            string             `xml:"barcode"`
	Asin               string             `xml:"asin"`
	Quality            string             `xml:"quality"`
	LabelInfos         []LabelInfo        `xml:"label-info-list>label-info"`
	Mediums            []*Medium          `xml:"medium-list>medium"`
	Relations          TargetRelationsMap `xml:"relation-list"`
}

func (mbe *Release) lookupResult() interface{} {
	var res struct {
		XMLName xml.Name `xml:"metadata"`
		Ptr     *Release `xml:"release"`
	}
	res.Ptr = mbe
	return &res
}

func (mbe *Release) apiEndpoint() string {
	return "/release"
}

func (mbe *Release) Id() MBID {
	return mbe.ID
}

// LookupRelease performs a release lookup request for the given MBID.
func (c *WS2Client) LookupRelease(id MBID, inc ...string) (*Release, error) {
	a := &Release{ID: id}
	err := c.Lookup(a, inc...)

	return a, err
}

// SearchRelease queries MusicBrainz´ Search Server for Releases.
//
// Possible search fields to provide in searchTerm are:
//
//	arid           artist id
//	artist         complete artist name(s) as it appears on the release
//	artistname     an artist on the release, each artist added as a separate field
//	asin           the Amazon ASIN for this release
//	barcode        The barcode of this release
//	catno          The catalog number for this release, can have multiples when major using an imprint
//	comment        Disambiguation comment
//	country        The two letter country code for the release country
//	creditname     name credit on the release, each artist added as a separate field
//	date           The release date (format: YYYY-MM-DD)
//	discids        total number of cd ids over all mediums for the release
//	discidsmedium  number of cd ids for the release on a medium in the release
//	format         release format
//	laid           The label id for this release, a release can have multiples when major using an imprint
//	label          The name of the label for this release, can have multiples when major using an imprint
//	lang           The language for this release. Use the three character ISO 639 codes to search for a specific language. (e.g. lang:eng)
//	mediums        number of mediums in the release
//	primarytype    primary type of the release group (album, single, ep, other)
//	puid           The release contains recordings with these puids
//	quality        The quality of the release (low, normal, high)
//	reid           release id
//	release        release name
//	releaseaccent  name of the release with any accent characters retained
//	rgid           release group id
//	script         The 4 character script code (e.g. latn) used for this release
//	secondarytype  secondary type of the release group (audiobook, compilation, interview, live, remix, soundtrack, spokenword)
//	status         release status (e.g official)
//	tag            a tag that appears on the release
//	tracks         total number of tracks over all mediums on the release
//	tracksmedium   number of tracks on a medium in the release
//	type           type of the release group, old type mapping for when we did not have separate primary and secondary types
//
// With no fields specified searchTerm searches the release field only. For
// more information visit
// https://musicbrainz.org/doc/Development/XML_Web_Service/Version_2/Search#Release
func (c *WS2Client) SearchRelease(searchTerm string, limit, offset int) (*ReleaseSearchResponse, error) {

	result := releaseListResult{}
	err := c.searchRequest("/release", &result, searchTerm, limit, offset)

	rsp := ReleaseSearchResponse{}
	rsp.WS2ListResponse = result.ReleaseList.WS2ListResponse
	rsp.Scores = make(ScoreMap)

	for i, v := range result.ReleaseList.Releases {
		rsp.Releases = append(rsp.Releases, v.Release)
		rsp.Scores[rsp.Releases[i]] = v.Score
	}

	return &rsp, err
}

// ReleaseSearchResponse is the response type returned by the SearchRelease method.
type ReleaseSearchResponse struct {
	WS2ListResponse
	Releases []*Release
	Scores   ScoreMap
}

// ResultsWithScore returns a slice of Releases with a specific score.
func (r *ReleaseSearchResponse) ResultsWithScore(score int) []*Release {
	var res []*Release
	for _, v := range r.Releases {
		if r.Scores[v] == score {
			res = append(res, v)
		}
	}
	return res
}

// OriginalRelease is a helper function that returns the earliest release of
// a release array with the most accurate date. It can be used to determine
// the original/first release from releases of a release group.
func OriginalRelease(releases []*Release) *Release {

	if len(releases) == 0 {
		return nil
	}
	original := releases[0] // fall back on the first item

	for _, release := range releases {

		if !release.Date.IsZero() {

			if release.Date.Year() < original.Date.Year() {
				original = release
			} else if release.Date.Year() == original.Date.Year() &&
				release.Date.Accuracy > Year {

				if original.Date.Accuracy == Year ||
					release.Date.Month() < original.Date.Month() {

					original = release

				} else if release.Date.Month() == original.Date.Month() &&
					release.Date.Accuracy > Month {

					if original.Date.Accuracy == Month ||
						release.Date.Day() < original.Date.Day() {
						original = release
					}
				}
			}
		}
	}

	return original
}

type releaseListResult struct {
	ReleaseList struct {
		WS2ListResponse
		Releases []struct {
			*Release
			Score int `xml:"http://musicbrainz.org/ns/ext#-2.0 score,attr"`
		} `xml:"release"`
	} `xml:"release-list"`
}
<|endoftext|>


<commit_before>package deferrer

// Deferrer allows for collecting many defer statements for deferring later on.
type Deferrer struct {
	funcs []func()
}

// Cleanup calls the statements that were added to the Deferrer's stack, in reverse order (the defer
// statement works like a stack: https://tour.golang.org/flowcontrol/13)
func (d *Deferrer) Cleanup() {
	for i := len(d.funcs) - 1; i >= 0; i-- {
		d.funcs[i]()
	}

	d.funcs = []func(){}
}

// Defer adds a function to the Deferrer's stack.
func (d *Deferrer) Defer(f func()) {
	d.funcs = append(d.funcs, f)
}

// New creates a new Deferrer for defering actions on *your* timetable.
func New() Deferrer {
	return Deferrer{
		funcs: make([]func(), 0),
	}
}
<commit_msg>Deferrer: simplify some expressions since they're the defaults<commit_after>package deferrer

// Deferrer allows for collecting many defer statements for deferring later on.
type Deferrer struct {
	funcs []func()
}

// Cleanup calls the statements that were added to the Deferrer's stack, in reverse order (the defer
// statement works like a stack: https://tour.golang.org/flowcontrol/13)
func (d *Deferrer) Cleanup() {
	for i := len(d.funcs) - 1; i >= 0; i-- {
		d.funcs[i]()
	}

	d.funcs = nil
}

// Defer adds a function to the Deferrer's stack.
func (d *Deferrer) Defer(f func()) {
	d.funcs = append(d.funcs, f)
}

// New creates a new Deferrer for defering actions on *your* timetable.
func New() Deferrer {
	return Deferrer{}
}
<|endoftext|>


<commit_before>package immortal

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

// ScanDir struct
type ScanDir struct {
	scandir string
}

// NewScanDir returns ScanDir struct
func NewScanDir(path string) (*ScanDir, error) {
	if info, err := os.Stat(path); os.IsNotExist(err) {
		return nil, fmt.Errorf("%q no such file or directory.", path)
	} else if !info.IsDir() {
		return nil, fmt.Errorf("%q is not a directory.", path)
	}

	dir, err := filepath.EvalSymlinks(path)
	if err != nil {
		return nil, err
	}

	dir, err = filepath.Abs(filepath.Clean(dir))
	if err != nil {
		return nil, err
	}

	d, err := os.Open(dir)
	if err != nil {
		if os.IsPermission(err) {
			return nil, os.ErrPermission
		}
		return nil, err
	}
	defer d.Close()

	return &ScanDir{
		scandir: dir,
	}, nil
}

// Start scans directory every 5 seconds
func (s *ScanDir) Start() {
	log.Printf("immortal scandir: %s", s.scandir)
	s.Scaner()
	ticker := time.NewTicker(5 * time.Second)
	for {
		select {
		case <-ticker.C:
			s.Scaner()
		}
	}
}

// Scaner searchs for run.yml and based on the perms start/stops the process
func (s *ScanDir) Scaner() {
	time := time.Now()
	find := func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if strings.HasSuffix(f.Name(), ".yml") {
			name := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
			refresh := (time.Unix() - f.ModTime().Unix()) <= 5
			log.Printf("name: %s  refresh: %v", name, refresh)
			if refresh {
				cmd := exec.Command("immortal", "-c", path, "-ctl", name)
				stdoutStderr, err := cmd.CombinedOutput()
				if err != nil {
					return err
				}
				log.Printf("%s\n", stdoutStderr)
			}
		}
		return nil
	}
	err := filepath.Walk(s.scandir, find)
	if err != nil {
		log.Println(err)
	}
	return
}
<commit_msg>	modified:   scandir.go<commit_after>package immortal

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
)

// ScanDir struct
type ScanDir struct {
	scandir string
}

// NewScanDir returns ScanDir struct
func NewScanDir(path string) (*ScanDir, error) {
	if info, err := os.Stat(path); os.IsNotExist(err) {
		return nil, fmt.Errorf("%q no such file or directory.", path)
	} else if !info.IsDir() {
		return nil, fmt.Errorf("%q is not a directory.", path)
	}

	dir, err := filepath.EvalSymlinks(path)
	if err != nil {
		return nil, err
	}

	dir, err = filepath.Abs(filepath.Clean(dir))
	if err != nil {
		return nil, err
	}

	d, err := os.Open(dir)
	if err != nil {
		if os.IsPermission(err) {
			return nil, os.ErrPermission
		}
		return nil, err
	}
	defer d.Close()

	return &ScanDir{
		scandir: dir,
	}, nil
}

// Start scans directory every 5 seconds
func (s *ScanDir) Start() {
	log.Printf("immortal scandir: %s", s.scandir)
	s.Scaner()
	ticker := time.NewTicker(5 * time.Second)
	for {
		select {
		case <-ticker.C:
			s.Scaner()
		}
	}
}

// Scaner searchs for run.yml and based on the perms start/stops the process
func (s *ScanDir) Scaner() {
	time := time.Now()
	find := func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if strings.HasSuffix(f.Name(), ".yml") {
			name := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
			refresh := (time.Unix() - f.ModTime().Unix()) <= 5
			log.Printf("name: %s  refresh: %v", name, refresh)
			if refresh {
				cmd := exec.Command("immortal", "-c", path, "-ctl", name)
				cmd.Env = os.Environ()
				stdoutStderr, err := cmd.CombinedOutput()
				if err != nil {
					return err
				}
				log.Printf("%s\n", stdoutStderr)
			}
		}
		return nil
	}
	err := filepath.Walk(s.scandir, find)
	if err != nil {
		log.Println(err)
	}
	return
}
<|endoftext|>


<commit_before>package main

func a(c int) int {
	return 3
}

func b() int {
	return 0
}

func c(a, b int) int {
	return 1
}

func main() {
	var (
		q int
		t string
	)
	x := a(1)
	y := b()
	z := c(a(q), b())

	a(c(b(), b()))
}
<commit_msg>Add a bit more golang test code<commit_after>package main

func a(c int) int {
	return 3
}

func b() int {
	return 0
}

func c(a, b int) int {
	return 1
}

func ttt() (int, int) {
	return 1, 2
}

func main() {
	var (
		q int
		t string
	)
	x := a(1)
	y := b()
	z := c(a(q), b())
	n, m := ttt()
	m, x = ttt()

	a(c(b(), b()))
}
<|endoftext|>