<commit_before>// go-ari-proxy is an implementation of the go-ari-library used to
// connect to the Asterisk REST Interface for delivery of Events and
// Commands across a message bus.
package main

import (
	"bytes"
	"golang.org/x/net/websocket"
	"encoding/json"
	"flag"
	"github.com/nvisibleinc/go-ari-library"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

// Var config contains a Config struct to hold the proxy configuration file.
var (
	config         Config            // main proxy configuration structure
	client         = &http.Client{}  // connection for Commands to ARI
	proxyInstances *proxyInstanceMap // maps the per-dialog proxy instances
	Debug          *log.Logger
	Info           *log.Logger
	Warning        *log.Logger
	Error          *log.Logger
)

// signalCatcher is a function to allows us to stop the application through an
// operating system signal.
func signalCatcher() {
	ch := make(chan os.Signal)
	signal.Notify(ch, syscall.SIGINT)
	sig := <-ch
	log.Printf("Signal received: %v", sig)
	os.Exit(0)
}

// Init parses the configuration file by unmarshaling it into a Config struct.
func init() {
	var err error

	// Setup our logging interfaces
	Debug = ari.InitLogger(os.Stdout, "DEBUG")
	Info = ari.InitLogger(os.Stdout, "INFO")
	Warning = ari.InitLogger(os.Stdout, "WARNING")
	Error = ari.InitLogger(os.Stderr, "ERROR")

	// parse the configuration file and get data from it
	Info.Println("Loading configuration for proxy.")
	configpath := flag.String("config", "./config.json", "Path to config file")

	Debug.Println("Parsing the configuration file.")
	flag.Parse()
	Debug.Println("Reading in the configuration from the file.")
	configfile, err := ioutil.ReadFile(*configpath)
	if err != nil {
		Error.Fatal(err)
	}
	// read in the configuration file and unmarshal the json, storing it in 'config'
	Debug.Println("Unmarshaling proxy configuration.")
	json.Unmarshal(configfile, &config)
	Debug.Println(&config)
	Debug.Println("Initialize the proxy instance map.")
	proxyInstances = NewproxyInstanceMap() // initialize a new proxy instance map
}

func main() {
	// Setup a new Event producer and Command consumer for every application
	// we've configured in the configuration file.
	Info.Println("Initializing the message bus.")
	ari.InitBus(config.MessageBus, config.BusConfig)
	for _, app := range config.Applications {
		/*
			Create a new producer which is responsible for the initial topic on the message bus which is used
			to signal the setup of per-application instances. All applications listen to this topic in order to
			be provided the information to setup the ownership of per dialog application instances.
		*/
		Info.Printf("Initializing signalling bus for application %s", app)
		producer := ari.InitProducer(app) // Initialize a new producer channel using the ari.InitProducer function.
		Info.Printf("Starting event handler for application %s", app)
		go runEventHandler(app, producer) // create new websocket connection for every application and pass the producer channel
	}

	go signalCatcher() // listen for os signal to stop the application
	select {}
}

// runEventHandler sets up a websocket connection to an ARI application.
func runEventHandler(s string, producer chan []byte) {
	// Connect to the websocket backend (ARI)
	var ariMessage string
	url := strings.Join([]string{config.WebsocketURL, "?app=", s, "&api_key=", config.WSUser, ":", config.WSPassword}, "")

	Info.Printf("Attempting to connect to ARI websocket at: %s", url)
	ws, err := websocket.Dial(url, "ari", config.Origin)
	if err != nil {
		log.Fatal(err)
	}

	// Start the producer loop. Every message received from the websocket is
	// passed to the PublishMessage() function.
	Info.Printf("Starting producer loop for application %s", s)
	for {
		err = websocket.Message.Receive(ws, &ariMessage) // accept the message from the websocket
		if err != nil {
			log.Fatal(err)
		}
		go PublishMessage(ariMessage, producer) // publish message to the producer channel
	}
}

// PublishMessage takes an ARI event from the websocket and places it on the
// producer channel.
// Accepts two arguments:
// * a string containing the ARI message
// * a producer channel
func PublishMessage(ariMessage string, producer chan []byte) {
	// unmarshal into an ari.Event so we can append some extra information
	var info eventInfo
	var message ari.Event
	var pi *proxyInstance
	var exists bool = false
	json.Unmarshal([]byte(ariMessage), &message)
	json.Unmarshal([]byte(ariMessage), &info)
	message.ServerID = config.ServerID
	message.Timestamp = time.Now()
	message.ARI_Body = ariMessage

	switch {
	case info.Type == "StasisStart":

		// Check to see if the new channel was already in the map, which means it
		// was created by an originate with ID
		pi, exists = proxyInstances.Get(info.Channel.ID)
		if exists {
			break
		}
		// since we're starting a new application instance, create the proxy side
		dialogID := ari.UUID()
		Info.Println("New StasisStart found. Created new dialogID of ", dialogID)
		as, err := json.Marshal(ari.AppStart{Application: info.Application, DialogID: dialogID, ServerID: config.ServerID})
		producer <- as

		// TODO: this sleep is required to allow the application time to spin up. In the future we likely want
		// to implement some sort of feedback mechanism in order to remove this sleep timer.
		time.Sleep(50 * time.Millisecond)
		if err != nil {
			return
		}

		Info.Printf("Created new proxy instance mapping for dialog '%s' and channel '%s'", dialogID, info.Channel.ID)
		pi = NewProxyInstance(dialogID)         // create new proxy instance for the dialog
		proxyInstances.Add(info.Channel.ID, pi) // add the dialog to the proxyInstances map to track its life
		exists = true

	case info.Type == "StasisEnd":
		Info.Printf("Ending application instance for channel '%s'", info.Channel.ID)
		// on application end, perform clean up checks
		pi, exists = proxyInstances.Get(info.Channel.ID)
		if exists {
			pi.removeAllObjects()
		}

	case info.Type == "BridgeDestroyed":
		pi, exists = proxyInstances.Get(info.Bridge.ID)
		if exists {
			pi.removeObject(info.Bridge.ID)
		}

	case info.Type == "ChannelDestroyed":
		pi, exists = proxyInstances.Get(info.Channel.ID)
		if exists {
			pi.removeObject(info.Channel.ID)
		}

	// check if prefix is part of the minChan{} struct
	case strings.HasPrefix(info.Type, "Channel"):
		pi, exists = proxyInstances.Get(info.Channel.ID)

	// check if prefix is part of the minBridge{} struct
	case strings.HasPrefix(message.Type, "Bridge"):
		pi, exists = proxyInstances.Get(info.Bridge.ID)

	// check if prefix is part of the minPlay{} struct
	case strings.HasPrefix(message.Type, "Playback"):
		pi, exists = proxyInstances.Get(info.Playback.ID)

	// check if prefix is part of the minRec{} struct (this one uses a Name instead of ID for some reason)
	case strings.HasPrefix(message.Type, "Recording"):
		pi, exists = proxyInstances.Get(info.Recording.Name)

	default:
		Warning.Println("No handler for event type")
		//pi, exists = proxyInstances[]
		// if not matching, then we need to perform checks against the
		// existing map to determine where to send this ARI message.
	}

	// marshal the message back into a string
	busMessage, err := json.Marshal(message)
	if err != nil {
		Error.Println(err)
		return
	}
	Debug.Printf("Bus Data:\n%s\n", busMessage)

	// push the busMessage onto the producer channel
	if exists {
		pi.Events <- busMessage
	}
}

// Add inserts a proxy instance into the global map of active proxy instances.
func (p *proxyInstanceMap) Add(id string, pi *proxyInstance) {
	p.mapLock.Lock()
	defer p.mapLock.Unlock()
	p.instanceMap[id] = pi
}

// Get returns a proxy instance from the global map of active proxy instances.
// returns nil if not found.
func (p *proxyInstanceMap) Get(id string) (*proxyInstance, bool) {
	p.mapLock.RLock()
	defer p.mapLock.RUnlock()
	pi, ok := p.instanceMap[id]
	if !ok {
		return nil, false
	}
	return pi, true
}

// Remove deletes an entry in the global proxyInstanceMap
func (p *proxyInstanceMap) Remove(id string) {
	p.mapLock.Lock()
	defer p.mapLock.Unlock()
	delete(p.instanceMap, id)
}

// shutDown closes the quit channel to signal all of a ProxyInstance's goroutines
// to return
func (p *proxyInstance) shutDown() {
	select {
	case _, ok := (<-p.quit):
		if !ok {
			return
		}
	default:
		close(p.quit)
	}
}

// addObject adds an object reference to the proxyInstance mapping
func (p *proxyInstance) addObject(id string) {
	for i := range p.ariObjects {
		if p.ariObjects[i] == id {
			//object already is associated with this proxyInstance
			return
		}
	}
	p.ariObjects = append(p.ariObjects, id)
	proxyInstances.Add(id, p)

}

// removeObject removes an object reference from the proxyInstance mapping
func (p *proxyInstance) removeObject(id string) {
	// remove an object from the map.
	for i := range p.ariObjects {
		if p.ariObjects[i] == id {
			// rewrite the p.ariObjects string slice to append all values up to
			// the index value of 'i', and all values of 'i'+1 and later.
			p.ariObjects = append(p.ariObjects[:i], p.ariObjects[i+1:]...)
		}
	}
	// remove the instance from our tracking map
	proxyInstances.Remove(id)

	// if there are no more objects, shut'rdown
	if len(p.ariObjects) == 0 {
		p.shutDown()
	}
}

// removeAllObjects will remove all object references from the proxyInstance mapping
func (p *proxyInstance) removeAllObjects() {
	// remove all objects from the map as our application is shutting down.
	for _, obj := range p.ariObjects {
		proxyInstances.Remove(obj)
	}
	p.shutDown() // destroy the application / proxy instance
}

// runCommandConsumer starts the consumer for accepting Commands from
// applications.
func (p *proxyInstance) runCommandConsumer(dialogID string) {
	commandTopic := strings.Join([]string{"commands", dialogID}, "_")
	responseTopic := strings.Join([]string{"responses", dialogID}, "_")
	Debug.Println("Topics are:", commandTopic, " ", responseTopic)
	p.responseChannel = ari.InitProducer(responseTopic)

	// waits for the TopicExists function to return a channel
	select {
	case <-ari.TopicExists(commandTopic):
		p.commandChannel = ari.InitConsumer(commandTopic)
	case <-time.After(10 * time.Second):
		// if the application instance hasn't come up after a period of time, gracefully end the proxy instance
		p.removeAllObjects()
		return
	}

	for {
		select {
		case jsonCommand := <-p.commandChannel:
			go p.processCommand(jsonCommand, p.responseChannel)
		case <-p.quit:
			return
		}
	}
}

// processCommand processes commands from applications and submits them to the
// REST interface.
func (p *proxyInstance) processCommand(jsonCommand []byte, responseProducer chan []byte) {
	var c ari.Command
	var r ari.CommandResponse
	i := ID{ID: "", Name: ""}
	Debug.Printf("jsonCommand is %s\n", string(jsonCommand))
	json.Unmarshal(jsonCommand, &c)

	//TODO:  Try to come up with something that makes me feel less dirty
	if c.Method == "POST" && strings.Contains(c.URL, "/channels/") && strings.Count(c.URL, "/") == 2 {
		chanID := strings.TrimPrefix(c.URL, "/channels/")
		if chanID != "" {
			p.addObject(chanID)
		}
	}
	//ENDTODO

	fullURL := strings.Join([]string{config.StasisURL, c.URL, "?api_key=", config.WSUser, ":", config.WSPassword}, "")

	Debug.Printf("fullURL is %s\n", fullURL)
	req, err := http.NewRequest(c.Method, fullURL, bytes.NewBufferString(c.Body))
	req.Header.Set("Content-Type", "application/json")
	res, err := client.Do(req)
	buf := new(bytes.Buffer)
	buf.ReadFrom(res.Body)
	Debug.Printf("Response body is %s\n", buf.String())
	json.Unmarshal(buf.Bytes(), &i)
	if i.ID != "" {
		p.addObject(i.ID)
	} else if i.Name != "" {
		p.addObject(i.Name)
	}
	r.ResponseBody = buf.String()
	r.StatusCode = res.StatusCode
	r.UniqueID = c.UniqueID // return the Command UID in the response
	sendJSON, err := json.Marshal(r)
	if err != nil {
		Error.Println(err)
	}
	Debug.Printf("sendJSON is %s\n", string(sendJSON))
	responseProducer <- sendJSON
}

// vim: tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab tw=72
<commit_msg>Format go-ari-proxy with gofmt -s<commit_after>// go-ari-proxy is an implementation of the go-ari-library used to
// connect to the Asterisk REST Interface for delivery of Events and
// Commands across a message bus.
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"github.com/nvisibleinc/go-ari-library"
	"golang.org/x/net/websocket"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"
)

// Var config contains a Config struct to hold the proxy configuration file.
var (
	config         Config            // main proxy configuration structure
	client         = &http.Client{}  // connection for Commands to ARI
	proxyInstances *proxyInstanceMap // maps the per-dialog proxy instances
	Debug          *log.Logger
	Info           *log.Logger
	Warning        *log.Logger
	Error          *log.Logger
)

// signalCatcher is a function to allows us to stop the application through an
// operating system signal.
func signalCatcher() {
	ch := make(chan os.Signal)
	signal.Notify(ch, syscall.SIGINT)
	sig := <-ch
	log.Printf("Signal received: %v", sig)
	os.Exit(0)
}

// Init parses the configuration file by unmarshaling it into a Config struct.
func init() {
	var err error

	// Setup our logging interfaces
	Debug = ari.InitLogger(os.Stdout, "DEBUG")
	Info = ari.InitLogger(os.Stdout, "INFO")
	Warning = ari.InitLogger(os.Stdout, "WARNING")
	Error = ari.InitLogger(os.Stderr, "ERROR")

	// parse the configuration file and get data from it
	Info.Println("Loading configuration for proxy.")
	configpath := flag.String("config", "./config.json", "Path to config file")

	Debug.Println("Parsing the configuration file.")
	flag.Parse()
	Debug.Println("Reading in the configuration from the file.")
	configfile, err := ioutil.ReadFile(*configpath)
	if err != nil {
		Error.Fatal(err)
	}
	// read in the configuration file and unmarshal the json, storing it in 'config'
	Debug.Println("Unmarshaling proxy configuration.")
	json.Unmarshal(configfile, &config)
	Debug.Println(&config)
	Debug.Println("Initialize the proxy instance map.")
	proxyInstances = NewproxyInstanceMap() // initialize a new proxy instance map
}

func main() {
	// Setup a new Event producer and Command consumer for every application
	// we've configured in the configuration file.
	Info.Println("Initializing the message bus.")
	ari.InitBus(config.MessageBus, config.BusConfig)
	for _, app := range config.Applications {
		/*
			Create a new producer which is responsible for the initial topic on the message bus which is used
			to signal the setup of per-application instances. All applications listen to this topic in order to
			be provided the information to setup the ownership of per dialog application instances.
		*/
		Info.Printf("Initializing signalling bus for application %s", app)
		producer := ari.InitProducer(app) // Initialize a new producer channel using the ari.InitProducer function.
		Info.Printf("Starting event handler for application %s", app)
		go runEventHandler(app, producer) // create new websocket connection for every application and pass the producer channel
	}

	go signalCatcher() // listen for os signal to stop the application
	select {}
}

// runEventHandler sets up a websocket connection to an ARI application.
func runEventHandler(s string, producer chan []byte) {
	// Connect to the websocket backend (ARI)
	var ariMessage string
	url := strings.Join([]string{config.WebsocketURL, "?app=", s, "&api_key=", config.WSUser, ":", config.WSPassword}, "")

	Info.Printf("Attempting to connect to ARI websocket at: %s", url)
	ws, err := websocket.Dial(url, "ari", config.Origin)
	if err != nil {
		log.Fatal(err)
	}

	// Start the producer loop. Every message received from the websocket is
	// passed to the PublishMessage() function.
	Info.Printf("Starting producer loop for application %s", s)
	for {
		err = websocket.Message.Receive(ws, &ariMessage) // accept the message from the websocket
		if err != nil {
			log.Fatal(err)
		}
		go PublishMessage(ariMessage, producer) // publish message to the producer channel
	}
}

// PublishMessage takes an ARI event from the websocket and places it on the
// producer channel.
// Accepts two arguments:
// * a string containing the ARI message
// * a producer channel
func PublishMessage(ariMessage string, producer chan []byte) {
	// unmarshal into an ari.Event so we can append some extra information
	var info eventInfo
	var message ari.Event
	var pi *proxyInstance
	var exists bool = false
	json.Unmarshal([]byte(ariMessage), &message)
	json.Unmarshal([]byte(ariMessage), &info)
	message.ServerID = config.ServerID
	message.Timestamp = time.Now()
	message.ARI_Body = ariMessage

	switch {
	case info.Type == "StasisStart":

		// Check to see if the new channel was already in the map, which means it
		// was created by an originate with ID
		pi, exists = proxyInstances.Get(info.Channel.ID)
		if exists {
			break
		}
		// since we're starting a new application instance, create the proxy side
		dialogID := ari.UUID()
		Info.Println("New StasisStart found. Created new dialogID of ", dialogID)
		as, err := json.Marshal(ari.AppStart{Application: info.Application, DialogID: dialogID, ServerID: config.ServerID})
		producer <- as

		// TODO: this sleep is required to allow the application time to spin up. In the future we likely want
		// to implement some sort of feedback mechanism in order to remove this sleep timer.
		time.Sleep(50 * time.Millisecond)
		if err != nil {
			return
		}

		Info.Printf("Created new proxy instance mapping for dialog '%s' and channel '%s'", dialogID, info.Channel.ID)
		pi = NewProxyInstance(dialogID)         // create new proxy instance for the dialog
		proxyInstances.Add(info.Channel.ID, pi) // add the dialog to the proxyInstances map to track its life
		exists = true

	case info.Type == "StasisEnd":
		Info.Printf("Ending application instance for channel '%s'", info.Channel.ID)
		// on application end, perform clean up checks
		pi, exists = proxyInstances.Get(info.Channel.ID)
		if exists {
			pi.removeAllObjects()
		}

	case info.Type == "BridgeDestroyed":
		pi, exists = proxyInstances.Get(info.Bridge.ID)
		if exists {
			pi.removeObject(info.Bridge.ID)
		}

	case info.Type == "ChannelDestroyed":
		pi, exists = proxyInstances.Get(info.Channel.ID)
		if exists {
			pi.removeObject(info.Channel.ID)
		}

	// check if prefix is part of the minChan{} struct
	case strings.HasPrefix(info.Type, "Channel"):
		pi, exists = proxyInstances.Get(info.Channel.ID)

	// check if prefix is part of the minBridge{} struct
	case strings.HasPrefix(message.Type, "Bridge"):
		pi, exists = proxyInstances.Get(info.Bridge.ID)

	// check if prefix is part of the minPlay{} struct
	case strings.HasPrefix(message.Type, "Playback"):
		pi, exists = proxyInstances.Get(info.Playback.ID)

	// check if prefix is part of the minRec{} struct (this one uses a Name instead of ID for some reason)
	case strings.HasPrefix(message.Type, "Recording"):
		pi, exists = proxyInstances.Get(info.Recording.Name)

	default:
		Warning.Println("No handler for event type")
		//pi, exists = proxyInstances[]
		// if not matching, then we need to perform checks against the
		// existing map to determine where to send this ARI message.
	}

	// marshal the message back into a string
	busMessage, err := json.Marshal(message)
	if err != nil {
		Error.Println(err)
		return
	}
	Debug.Printf("Bus Data:\n%s\n", busMessage)

	// push the busMessage onto the producer channel
	if exists {
		pi.Events <- busMessage
	}
}

// Add inserts a proxy instance into the global map of active proxy instances.
func (p *proxyInstanceMap) Add(id string, pi *proxyInstance) {
	p.mapLock.Lock()
	defer p.mapLock.Unlock()
	p.instanceMap[id] = pi
}

// Get returns a proxy instance from the global map of active proxy instances.
// returns nil if not found.
func (p *proxyInstanceMap) Get(id string) (*proxyInstance, bool) {
	p.mapLock.RLock()
	defer p.mapLock.RUnlock()
	pi, ok := p.instanceMap[id]
	if !ok {
		return nil, false
	}
	return pi, true
}

// Remove deletes an entry in the global proxyInstanceMap
func (p *proxyInstanceMap) Remove(id string) {
	p.mapLock.Lock()
	defer p.mapLock.Unlock()
	delete(p.instanceMap, id)
}

// shutDown closes the quit channel to signal all of a ProxyInstance's goroutines
// to return
func (p *proxyInstance) shutDown() {
	select {
	case _, ok := (<-p.quit):
		if !ok {
			return
		}
	default:
		close(p.quit)
	}
}

// addObject adds an object reference to the proxyInstance mapping
func (p *proxyInstance) addObject(id string) {
	for i := range p.ariObjects {
		if p.ariObjects[i] == id {
			//object already is associated with this proxyInstance
			return
		}
	}
	p.ariObjects = append(p.ariObjects, id)
	proxyInstances.Add(id, p)

}

// removeObject removes an object reference from the proxyInstance mapping
func (p *proxyInstance) removeObject(id string) {
	// remove an object from the map.
	for i := range p.ariObjects {
		if p.ariObjects[i] == id {
			// rewrite the p.ariObjects string slice to append all values up to
			// the index value of 'i', and all values of 'i'+1 and later.
			p.ariObjects = append(p.ariObjects[:i], p.ariObjects[i+1:]...)
		}
	}
	// remove the instance from our tracking map
	proxyInstances.Remove(id)

	// if there are no more objects, shut'rdown
	if len(p.ariObjects) == 0 {
		p.shutDown()
	}
}

// removeAllObjects will remove all object references from the proxyInstance mapping
func (p *proxyInstance) removeAllObjects() {
	// remove all objects from the map as our application is shutting down.
	for _, obj := range p.ariObjects {
		proxyInstances.Remove(obj)
	}
	p.shutDown() // destroy the application / proxy instance
}

// runCommandConsumer starts the consumer for accepting Commands from
// applications.
func (p *proxyInstance) runCommandConsumer(dialogID string) {
	commandTopic := strings.Join([]string{"commands", dialogID}, "_")
	responseTopic := strings.Join([]string{"responses", dialogID}, "_")
	Debug.Println("Topics are:", commandTopic, " ", responseTopic)
	p.responseChannel = ari.InitProducer(responseTopic)

	// waits for the TopicExists function to return a channel
	select {
	case <-ari.TopicExists(commandTopic):
		p.commandChannel = ari.InitConsumer(commandTopic)
	case <-time.After(10 * time.Second):
		// if the application instance hasn't come up after a period of time, gracefully end the proxy instance
		p.removeAllObjects()
		return
	}

	for {
		select {
		case jsonCommand := <-p.commandChannel:
			go p.processCommand(jsonCommand, p.responseChannel)
		case <-p.quit:
			return
		}
	}
}

// processCommand processes commands from applications and submits them to the
// REST interface.
func (p *proxyInstance) processCommand(jsonCommand []byte, responseProducer chan []byte) {
	var c ari.Command
	var r ari.CommandResponse
	i := ID{ID: "", Name: ""}
	Debug.Printf("jsonCommand is %s\n", string(jsonCommand))
	json.Unmarshal(jsonCommand, &c)

	//TODO:  Try to come up with something that makes me feel less dirty
	if c.Method == "POST" && strings.Contains(c.URL, "/channels/") && strings.Count(c.URL, "/") == 2 {
		chanID := strings.TrimPrefix(c.URL, "/channels/")
		if chanID != "" {
			p.addObject(chanID)
		}
	}
	//ENDTODO

	fullURL := strings.Join([]string{config.StasisURL, c.URL, "?api_key=", config.WSUser, ":", config.WSPassword}, "")

	Debug.Printf("fullURL is %s\n", fullURL)
	req, err := http.NewRequest(c.Method, fullURL, bytes.NewBufferString(c.Body))
	req.Header.Set("Content-Type", "application/json")
	res, err := client.Do(req)
	buf := new(bytes.Buffer)
	buf.ReadFrom(res.Body)
	Debug.Printf("Response body is %s\n", buf.String())
	json.Unmarshal(buf.Bytes(), &i)
	if i.ID != "" {
		p.addObject(i.ID)
	} else if i.Name != "" {
		p.addObject(i.Name)
	}
	r.ResponseBody = buf.String()
	r.StatusCode = res.StatusCode
	r.UniqueID = c.UniqueID // return the Command UID in the response
	sendJSON, err := json.Marshal(r)
	if err != nil {
		Error.Println(err)
	}
	Debug.Printf("sendJSON is %s\n", string(sendJSON))
	responseProducer <- sendJSON
}

// vim: tabstop=4 softtabstop=4 shiftwidth=4 noexpandtab tw=72
<|endoftext|>


<commit_before>// Copyright (c) 2018 Iori Mizutani
//
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.

package main

import (
	"encoding/csv"
	"io"
	"log"
	"os"
	"strconv"

	"github.com/iomz/go-llrp/binutil"
	"github.com/iomz/golemu"
	"gopkg.in/alecthomas/kingpin.v2"
)

var (
	// kingpin app
	app     = kingpin.New("gobtags", "Read pcbits and ids from csv and save them in a gob file.")
	inFile  = app.Flag("in", "A source csv file contains tags.").Short('i').Default("tags.csv").String()
	outFile = app.Flag("out", "A destination gob file contains tags.").Short('o').Default("tags.gob").String()
)

func makeByteID(s string) ([]byte, error) {
	id, err := binutil.ParseBinRuneSliceToUint8Slice([]rune(s))
	if err != nil {
		return []byte{}, nil
	}
	return binutil.Pack([]interface{}{id}), err
}

func makeUint16PC(s string) (uint16, error) {
	pc64, err := strconv.ParseUint(s, 16, 16)
	if err != nil {
		return uint16(0), err
	}
	return uint16(pc64), err
}

func readTagsFromCSV(inputFile string) *golemu.Tags {
	// Check inputFile
	fp, err := os.Open(inputFile)
	if err != nil {
		panic(err)
	}
	defer fp.Close()

	// Read CSV and store in [][]byte
	var tags golemu.Tags
	reader := csv.NewReader(fp)
	reader.Comma = ','
	reader.LazyQuotes = true
	for {
		record, err := reader.Read()
		if err == io.EOF {
			break
		} else if err != nil {
			panic(err)
		}
		if len(record) == 2 {
			tagRecord := &golemu.TagRecord{
				PCBits: record[0],
				EPC:    record[1],
			} // PCbits, EPC
			tag, err := golemu.NewTag(tagRecord)
			if err != nil {
				continue
			}
			tags = append(tags, tag)
		}
	}
	return &tags
}

func main() {
	kingpin.MustParse(app.Parse(os.Args[1:]))
	tags := readTagsFromCSV(*inFile)
	binutil.Save(*outFile, tags)
	/*
		var tagBuf bytes.Buffer
		enc := gob.NewEncoder(&tagBuf)
		err := enc.Encode(tags)
		if err != nil {
			log.Fatal(err)
		}
		file, err := os.Create(*outFile)
		if err != nil {
			log.Fatal(err)
		}
		file.Write(tagBuf.Bytes())
		file.Close()
	*/
	log.Printf("Saved %v tags in %v\n", len(*tags), *outFile)
}
<commit_msg>change outfile from a flag to an argument in gobtags<commit_after>// Copyright (c) 2018 Iori Mizutani
//
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.

package main

import (
	"encoding/csv"
	"io"
	"log"
	"os"
	"strconv"

	"github.com/iomz/go-llrp/binutil"
	"github.com/iomz/golemu"
	"gopkg.in/alecthomas/kingpin.v2"
)

var (
	// kingpin app
	app     = kingpin.New("gobtags", "Read pcbits and ids from csv and save them in a gob file.")
	inFile  = app.Flag("in", "A source csv file contains tags.").Short('i').Default("tags.csv").String()
	outFile = app.Arg("out", "A destination gob file contains tags.").Required().String()
)

func makeByteID(s string) ([]byte, error) {
	id, err := binutil.ParseBinRuneSliceToUint8Slice([]rune(s))
	if err != nil {
		return []byte{}, nil
	}
	return binutil.Pack([]interface{}{id}), err
}

func makeUint16PC(s string) (uint16, error) {
	pc64, err := strconv.ParseUint(s, 16, 16)
	if err != nil {
		return uint16(0), err
	}
	return uint16(pc64), err
}

func readTagsFromCSV(inputFile string) *golemu.Tags {
	// Check inputFile
	fp, err := os.Open(inputFile)
	if err != nil {
		panic(err)
	}
	defer fp.Close()

	// Read CSV and store in [][]byte
	var tags golemu.Tags
	reader := csv.NewReader(fp)
	reader.Comma = ','
	reader.LazyQuotes = true
	for {
		record, err := reader.Read()
		if err == io.EOF {
			break
		} else if err != nil {
			panic(err)
		}
		if len(record) == 2 {
			tagRecord := &golemu.TagRecord{
				PCBits: record[0],
				EPC:    record[1],
			} // PCbits, EPC
			tag, err := golemu.NewTag(tagRecord)
			if err != nil {
				continue
			}
			tags = append(tags, tag)
		}
	}
	return &tags
}

func main() {
	kingpin.MustParse(app.Parse(os.Args[1:]))
	tags := readTagsFromCSV(*inFile)
	binutil.Save(*outFile, tags)
	/*
		var tagBuf bytes.Buffer
		enc := gob.NewEncoder(&tagBuf)
		err := enc.Encode(tags)
		if err != nil {
			log.Fatal(err)
		}
		file, err := os.Create(*outFile)
		if err != nil {
			log.Fatal(err)
		}
		file.Write(tagBuf.Bytes())
		file.Close()
	*/
	log.Printf("Saved %v tags in %v\n", len(*tags), *outFile)
}
<|endoftext|>


<commit_before>package gomud

import (
	"log"
	"math/rand"
	"net"
	"strconv"
	"time"
)

type Event string

const (
	tick  Event = "tick"
	pulse Event = "pulse"
)

const tickLength int = 15

type Message struct {
	client *Client
	message string
}

func (m *Message) Process() bool {
	if (m.client.mob.Delay == 0) {
		m.client.Write(m.client.mob.Act(m.message))
		return true
	}
	return false
}

type Server struct {
	clients []*Client
	listener net.Listener
	messages []*Message
	port    int
}

func NewServer(port int) *Server {
	return &Server{port: port}
}

func (s *Server) Run() {
	s.connect()
	newClientListener := make(chan *Client)
	pulseListener := make(chan Event)
	tickListener := make(chan Event)
	bufListener := make(chan *Message)
	// Listen for new clients
	go func() {
		for {
			conn, err := s.listener.Accept()
			if err != nil {
				log.Fatalln(err)
			}
			newClientListener <- NewClient(conn)
		}
	}()
	// Timing
	go func() {
		nt := rand.Intn(tickLength) + tickLength
		p := 0
		for {
			time.Sleep(time.Second)
			p += 1
			pulseListener <- pulse
			if p >= nt {
				tickListener <- tick
				p = 0
				nt = rand.Intn(tickLength) + tickLength
				log.Println("Next tick in " + strconv.Itoa(nt) + " seconds")
			}
		}
	}()
	for {
		select {
		case client := <-newClientListener:
			s.clients = append(s.clients, client)
			go client.Listen(bufListener)
			log.Println("Client connected, " + strconv.Itoa(len(s.clients)) + " active clients")
		case <-pulseListener:
			for _, m := range mobs {
				m.Pulse()
			}
			for _, cl := range s.clients {
				cl.Pulse()
			}
		case <-tickListener:
			for _, m := range mobs {
				m.Tick()
			}
			for _, cl := range s.clients {
				cl.Tick()
			}
		case message := <-bufListener:
			s.messages = append(s.messages, message)
		}
		s.processMessages()
	}
}

func (s *Server) processMessages() {
	var unprocessed []*Message
	for _, m := range s.messages {
		if (!m.Process()) {
			unprocessed = append(unprocessed, m)
		}
	}
	s.messages = unprocessed
}

func (s *Server) connect() {
	ln, err := net.Listen("tcp", ":"+strconv.Itoa(s.port))
	if err != nil {
		log.Fatalln(err)
	} else {
		s.listener = ln
		log.Println("Listening on port :" + strconv.Itoa(s.port))
	}
}

func (s *Server) removeClient(c *Client) {
	c.conn.Close()
	for i, cl := range s.clients {
		if cl == c {
			s.clients = append(s.clients[0:i], s.clients[i+1:]...)
			log.Println("Client disconnected, " + strconv.Itoa(len(s.clients)) + " active clients")
			return
		}
	}
}
<commit_msg>make server work<commit_after>package gomud

import (
	"log"
	"math/rand"
	"net"
	"strconv"
	"time"
)

const tickLength int64 = 15

type Message struct {
	client *Client
	message string
}

func (m *Message) Process() bool {
	if (m.client.mob.Delay == 0) {
		m.client.Write(m.client.mob.Act(m.message))
		return true
	}
	return false
}

type Server struct {
	clients []*Client
	listener net.Listener
	messages []*Message
	port    int
	nextTick int64
	lastPulse int64
}

func NewServer(port int) *Server {
	return &Server{port: port}
}

func (s *Server) Run() {
	s.connect()
	newClientListener := make(chan *Client)
	bufListener := make(chan *Message)
	go s.newClientListener(newClientListener)
	for {
		select {
		case client := <-newClientListener:
			s.clients = append(s.clients, client)
			go client.Listen(bufListener)
			log.Println("Client connected, " + strconv.Itoa(len(s.clients)) + " active clients")
		case message := <-bufListener:
			s.messages = append(s.messages, message)
		default:
			s.timing()
			s.processMessages()
		}
	}
}

func (s *Server) newClientListener(newClientListener chan<-*Client) {
	for {
		conn, err := s.listener.Accept()
		if err != nil {
			log.Fatalln(err)
		}
		newClientListener <- NewClient(conn)
	}
}

func (s *Server) timing() {
	t := time.Now().Unix()
	if t > s.lastPulse {
		s.lastPulse = t
		for _, m := range mobs {
			m.Pulse()
		}
		for _, cl := range s.clients {
			cl.Pulse()
		}
	}
	if t > s.nextTick {
		s.nextTick = t + rand.Int63n(tickLength) + tickLength
		for _, m := range mobs {
			m.Tick()
		}
		for _, cl := range s.clients {
			cl.Tick()
		}
	}
}

func (s *Server) processMessages() {
	for i, m := range s.messages {
		if (m.Process()) {
			s.messages = append(s.messages[0:i], s.messages[i+1:]...)
		}
	}
}

func (s *Server) connect() {
	ln, err := net.Listen("tcp", ":"+strconv.Itoa(s.port))
	if err != nil {
		log.Fatalln(err)
	} else {
		s.listener = ln
		log.Println("Listening on port :" + strconv.Itoa(s.port))
	}
}

func (s *Server) removeClient(c *Client) {
	c.conn.Close()
	for i, cl := range s.clients {
		if cl == c {
			s.clients = append(s.clients[0:i], s.clients[i+1:]...)
			log.Println("Client disconnected, " + strconv.Itoa(len(s.clients)) + " active clients")
			return
		}
	}
}
<|endoftext|>


<commit_before>package googleimages

import (
	"encoding/json"
	"io"
	"net/url"
	"os"

	behavior "github.com/ikeikeikeike/gopkg/net/http"
)

const EndPoint = "http://ajax.googleapis.com/ajax/services/search/images"

func tee(r io.Reader, debug bool) io.Reader {
	if !debug {
		return r
	}
	return io.TeeReader(r, os.Stdout)
}

type Client struct {
	*behavior.UserBehavior
	Debug bool
}

func NewClient() *Client {
	return &Client{
		UserBehavior: behavior.NewUserBehavior(),
		Debug:        false,
	}
}

type Result struct {
	GsearchResultClass  string `json:"GsearchResultClass"`
	Width               string `json:"width"`
	Height              string `json:"height"`
	ImageId             string `json:"imageId"`
	TbWidth             string `json:"tbWidth"`
	TbHeight            string `json:"tbHeight"`
	UnescapedUrl        string `json:"unescapedUrl"`
	Url                 string `json:"url"`
	VisibleUrl          string `json:"visibleUrl"`
	Title               string `json:"title"`
	TitleNoFormatting   string `json:"titleNoFormatting"`
	OriginalContextUrl  string `json:"originalContextUrl"`
	Content             string `json:"content"`
	ContentNoFormatting string `json:"contentNoFormatting"`
	TbUrl               string `json:"tbUrl"`
}

type Page struct {
	Start string `json:"start`
	Label int    `json:"label"`
}

type Cursor struct {
	ResultCount          string  `json:"resultCount"`
	Pages                []*Page `json:"pages"`
	EstimatedResultCount string  `json:"estimatedResultCount"`
	CurrentPageIndex     int     `json:"currentPageIndex"`
	MoreResultsUrl       string  `json:"moreResultsUrl"`
	SearchResultTime     string  `json:"searchResultTime"`
}

type ResponseData struct {
	Cursor  *Cursor   `json:"cursor"`
	Results []*Result `json:"results"`
}

type Googleimages struct {
	ResponseData    *ResponseData
	ResponseStatus  int    `json:"responseStatus"`
	ResponseDetails string `json:"responseDetails"`
}

func (c *Client) Fetch(query string) (*Googleimages, error) {
	res, err := c.Get(EndPoint + "?v=1.0&q=" + url.QueryEscape(query))
	if err != nil {
		return nil, err
	}

	defer res.Body.Close()

	r := new(Googleimages)
	err = json.NewDecoder(tee(res.Body, c.Debug)).Decode(&r)
	if err != nil {
		return nil, err
	}

	return r, nil
}

// Set Cursor object, And return operate iterate object with google image api.
func (c *Client) Do(query string) (err error) { return }
<commit_msg>Fixed api parameter for japanese<commit_after>package googleimages

import (
	"encoding/json"
	"io"
	"net/url"
	"os"

	behavior "github.com/ikeikeikeike/gopkg/net/http"
)

const EndPoint = "http://ajax.googleapis.com/ajax/services/search/images"

func tee(r io.Reader, debug bool) io.Reader {
	if !debug {
		return r
	}
	return io.TeeReader(r, os.Stdout)
}

type Client struct {
	*behavior.UserBehavior
	Debug bool
}

func NewClient() *Client {
	return &Client{
		UserBehavior: behavior.NewUserBehavior(),
		Debug:        false,
	}
}

type Result struct {
	GsearchResultClass  string `json:"GsearchResultClass"`
	Width               string `json:"width"`
	Height              string `json:"height"`
	ImageId             string `json:"imageId"`
	TbWidth             string `json:"tbWidth"`
	TbHeight            string `json:"tbHeight"`
	UnescapedUrl        string `json:"unescapedUrl"`
	Url                 string `json:"url"`
	VisibleUrl          string `json:"visibleUrl"`
	Title               string `json:"title"`
	TitleNoFormatting   string `json:"titleNoFormatting"`
	OriginalContextUrl  string `json:"originalContextUrl"`
	Content             string `json:"content"`
	ContentNoFormatting string `json:"contentNoFormatting"`
	TbUrl               string `json:"tbUrl"`
}

type Page struct {
	Start string `json:"start`
	Label int    `json:"label"`
}

type Cursor struct {
	ResultCount          string  `json:"resultCount"`
	Pages                []*Page `json:"pages"`
	EstimatedResultCount string  `json:"estimatedResultCount"`
	CurrentPageIndex     int     `json:"currentPageIndex"`
	MoreResultsUrl       string  `json:"moreResultsUrl"`
	SearchResultTime     string  `json:"searchResultTime"`
}

type ResponseData struct {
	Cursor  *Cursor   `json:"cursor"`
	Results []*Result `json:"results"`
}

type Googleimages struct {
	ResponseData    *ResponseData
	ResponseStatus  int    `json:"responseStatus"`
	ResponseDetails string `json:"responseDetails"`
}

func (c *Client) Fetch(query string) (*Googleimages, error) {
	res, err := c.Get(EndPoint + "?hl=ja&v=1.0&q=" + url.QueryEscape(query))
	if err != nil {
		return nil, err
	}

	defer res.Body.Close()

	r := new(Googleimages)
	err = json.NewDecoder(tee(res.Body, c.Debug)).Decode(&r)
	if err != nil {
		return nil, err
	}

	return r, nil
}

// Set Cursor object, And return operate iterate object with google image api.
func (c *Client) Do(query string) (err error) { return }
<|endoftext|>


<commit_before>package gorush

const (
	// Version is gorush server version.
	Version = "1.1.0"
)

const (
	// PlatFormIos constant is 1 for iOS
	PlatFormIos = iota + 1
	// PlatFormAndroid constant is 2 for Android
	PlatFormAndroid
)

const (
	// SucceededPush is log block
	SucceededPush = "succeeded-push"
	// FailedPush is log block
	FailedPush = "failed-push"
)

// Stat variable for redis
const (
	gorushTotalCount     = "gorush-total-count"
	gorushIosSuccess     = "gorush-ios-success-count"
	gorushIosError       = "gorush-ios-error-count"
	gorushAndroidSuccess = "gorush-android-success-count"
	gorushAndroidError   = "gorush-android-error-count"
)
<commit_msg>bump version.<commit_after>package gorush

const (
	// Version is gorush server version.
	Version = "1.2.1"
)

const (
	// PlatFormIos constant is 1 for iOS
	PlatFormIos = iota + 1
	// PlatFormAndroid constant is 2 for Android
	PlatFormAndroid
)

const (
	// SucceededPush is log block
	SucceededPush = "succeeded-push"
	// FailedPush is log block
	FailedPush = "failed-push"
)

// Stat variable for redis
const (
	gorushTotalCount     = "gorush-total-count"
	gorushIosSuccess     = "gorush-ios-success-count"
	gorushIosError       = "gorush-ios-error-count"
	gorushAndroidSuccess = "gorush-android-success-count"
	gorushAndroidError   = "gorush-android-error-count"
)
<|endoftext|>


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

package gosh

import (
	"os/exec"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

const (
	/**
	 * 'Unstarted' is the state of a command that has been constructed, but execution has not yet begun.
	 */
	UNSTARTED int32 = iota

	/**
	 * 'Running' is the state of a command that has begun execution, but not yet finished.
	 */
	RUNNING

	/**
	 * 'Finished' is the state of a command that has finished normally.
	 *
	 * The exit code may or may not have been success, but at the very least we
	 * successfully observed that exit code.
	 */
	FINISHED

	/**
	 * 'Panicked' is the state of a command that at some point began execution, but has encountered
	 * serious problems.
	 *
	 * It may not be clear whether or not the command is still running, since a panic implies we no
	 * longer have completely clear visibility to the command on the underlying system.  The exit
	 * code may not be reliably known.
	 */
	PANICKED
)

func NewRunningCommand(cmd *exec.Cmd) *RunningCommand {
	return &RunningCommand{
		cmd:      cmd,
		state:    UNSTARTED,
		exitCh:   make(chan bool),
		exitCode: -1,
	}
}

type RunningCommand struct {
	mutex sync.Mutex

	/** Always access this with functions from the atomic package, and when
	* transitioning states set the status after all other fields are mutated,
	* so that checks of State() serve as a memory barrier for all. */
	state int32

	cmd *exec.Cmd
	//TODO: or: callback func() int

	/** If this is set, game over. */
	err error

	/** Wait for this to close in order to wait for the process to return. */
	exitCh chan bool

	/** Exit code if we're state==FINISHED and exit codes are possible on this platform, or
	 * -1 if we're not there yet.  Will not change after exitCh has closed. */
	exitCode int

	/** Functions to call back when the command has exited. */
	exitListeners []func(*RunningCommand)
}

func (cmd *RunningCommand) State() int32 {
	return atomic.LoadInt32(&cmd.state)
}

/** Returns true if the command is current running. */
func (cmd *RunningCommand) IsRunning() bool {
	state := cmd.State()
	return state == RUNNING
}

/** Returns true if the command has ever been started (including if the command is already finished). */
func (cmd *RunningCommand) IsStarted() bool {
	state := cmd.State()
	return state == RUNNING || state == FINISHED || state == PANICKED
}

/** Returns true if the command is finished (either gracefully, or with internal errors). */
func (cmd *RunningCommand) IsDone() bool {
	state := cmd.State()
	return state == FINISHED || state == PANICKED
}

/** Returns true if the command is finished gracefully.  (A nonzero exit code may still be set.) */
func (cmd *RunningCommand) IsFinishedGracefully() bool {
	state := cmd.State()
	return state == FINISHED
}

func (cmd *RunningCommand) Start() *RunningCommand {
	if err := cmd.startCalmly(); err != nil {
		panic(err)
	}
	return cmd
}

func (cmd *RunningCommand) startCalmly() error {
	cmd.mutex.Lock()
	defer cmd.mutex.Unlock()

	if cmd.IsStarted() {
		return nil
	}

	atomic.StoreInt32(&cmd.state, RUNNING)
	if err := cmd.cmd.Start(); err != nil {
		cmd.finalState(CommandStartError{cause: err})
		return cmd.err
	}

	go cmd.waitAndHandleExit()
	return nil
}

func (cmd *RunningCommand) waitAndHandleExit() {
	err := cmd.cmd.Wait()

	cmd.mutex.Lock()
	defer cmd.mutex.Unlock()

	if err == nil {
		cmd.exitCode = 0
	} else if exitError, ok := err.(*exec.ExitError); ok {
		if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
			cmd.exitCode = waitStatus.ExitStatus()
		} else {
			panic(exitError) //TODO: damage control better.  consider setting some kind of CommandMonitorError.
		}
	} else {
		panic(err) //TODO: damage control better.  consider setting some kind of CommandMonitorError.
	}
	cmd.finalState(nil)
}

func (cmd *RunningCommand) finalState(err error) {
	// must hold cmd.mutex before calling this
	// golang is an epic troll: claims to be best buddy for concurrent code, SYNC PACKAGE DOES NOT HAVE REENTRANT LOCKS
	if cmd.IsRunning() {
		if err == nil {
			atomic.StoreInt32(&cmd.state, FINISHED)
		} else {
			cmd.err = err
			atomic.StoreInt32(&cmd.state, PANICKED)
		}
		//TODO iterate over exit listeners
		for _, cb := range cmd.exitListeners {
			func() {
				defer recover()
				cb(cmd)
			}()
		}
	}
	close(cmd.exitCh)
}

/**
 * Add a function to be called when this command completes.
 *
 * These listener functions will be invoked after the exit code and other command
 * state is final, but before other Wait() methods unblock.
 * (This means if you want for example to log a message that a process exited, and
 * your main function is Wait()'ing for that process... if you use AddExitListener()
 * to invoke your log function then you will always get the log.)
 *
 * The listener function should complete quickly and not try to perform other blocking
 * operations or locks, since other actions are waiting until the listeners have all
 * been called.  Panics that escape the function will be silently discarded; do not
 * panic in a listener.
 *
 * If the command is already in the state FINISHED or PANICKED, the callback function
 * will be invoked immediately in the current goroutine.
 */
func (cmd *RunningCommand) AddExitListener(callback func(*RunningCommand)) {
	cmd.mutex.Lock()
	defer cmd.mutex.Unlock()

	if cmd.IsDone() {
		func() {
			defer recover()
			callback(cmd)
		}()
	} else {
		cmd.exitListeners = append(cmd.exitListeners, callback)
	}
}

/**
 * Returns a channel that will be open until the command is complete.
 * This is suitable for use in a select block.
 */
func (cmd *RunningCommand) GetExitChannel() <-chan bool {
	return cmd.exitCh
}

/**
 * Waits for the command to exit before returning.
 *
 * There are no consequences to waiting on a single command repeatedly;
 * all wait calls will return normally when the command completes.  The order
 * in which multiple wait calls will return is undefined.  Similarly, there
 * are no consequences to waiting on a command that has not yet started;
 * the function will still wait without error until the command finishes.
 * (Much friendlier than os.exec.Cmd.Wait(), neh?)
 */
func (cmd *RunningCommand) Wait() {
	<-cmd.GetExitChannel()
}

/**
 * Waits for the command to exit before returning, or for the specified duration.
 * Returns true if the return was due to the command finishing, or false if the
 * return was due to timeout.
 */
func (cmd *RunningCommand) WaitSoon(d time.Duration) bool {
	select {
	case <-time.After(d):
		return false
	case <-cmd.GetExitChannel():
		return true
	}
}

/**
 * Waits for the command to exit if it has not already, then returns the exit code.
 */
func (cmd *RunningCommand) GetExitCode() int {
	if !cmd.IsDone() {
		cmd.Wait()
	}
	return cmd.exitCode
}

/**
 * Waits for the command to exit if it has not already, or for the specified duration,
 * then either returns the exit code, or -1 if the duration expired and the command
 * still hasn't returned.
 */
func (cmd *RunningCommand) GetExitCodeSoon(d time.Duration) int {
	if cmd.WaitSoon(d) {
		return cmd.exitCode
	} else {
		return -1
	}
}
<commit_msg>Remove comical todo that's long since done.<commit_after>// Copyright 2013 Eric Myhre
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gosh

import (
	"os/exec"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

const (
	/**
	 * 'Unstarted' is the state of a command that has been constructed, but execution has not yet begun.
	 */
	UNSTARTED int32 = iota

	/**
	 * 'Running' is the state of a command that has begun execution, but not yet finished.
	 */
	RUNNING

	/**
	 * 'Finished' is the state of a command that has finished normally.
	 *
	 * The exit code may or may not have been success, but at the very least we
	 * successfully observed that exit code.
	 */
	FINISHED

	/**
	 * 'Panicked' is the state of a command that at some point began execution, but has encountered
	 * serious problems.
	 *
	 * It may not be clear whether or not the command is still running, since a panic implies we no
	 * longer have completely clear visibility to the command on the underlying system.  The exit
	 * code may not be reliably known.
	 */
	PANICKED
)

func NewRunningCommand(cmd *exec.Cmd) *RunningCommand {
	return &RunningCommand{
		cmd:      cmd,
		state:    UNSTARTED,
		exitCh:   make(chan bool),
		exitCode: -1,
	}
}

type RunningCommand struct {
	mutex sync.Mutex

	/** Always access this with functions from the atomic package, and when
	* transitioning states set the status after all other fields are mutated,
	* so that checks of State() serve as a memory barrier for all. */
	state int32

	cmd *exec.Cmd
	//TODO: or: callback func() int

	/** If this is set, game over. */
	err error

	/** Wait for this to close in order to wait for the process to return. */
	exitCh chan bool

	/** Exit code if we're state==FINISHED and exit codes are possible on this platform, or
	 * -1 if we're not there yet.  Will not change after exitCh has closed. */
	exitCode int

	/** Functions to call back when the command has exited. */
	exitListeners []func(*RunningCommand)
}

func (cmd *RunningCommand) State() int32 {
	return atomic.LoadInt32(&cmd.state)
}

/** Returns true if the command is current running. */
func (cmd *RunningCommand) IsRunning() bool {
	state := cmd.State()
	return state == RUNNING
}

/** Returns true if the command has ever been started (including if the command is already finished). */
func (cmd *RunningCommand) IsStarted() bool {
	state := cmd.State()
	return state == RUNNING || state == FINISHED || state == PANICKED
}

/** Returns true if the command is finished (either gracefully, or with internal errors). */
func (cmd *RunningCommand) IsDone() bool {
	state := cmd.State()
	return state == FINISHED || state == PANICKED
}

/** Returns true if the command is finished gracefully.  (A nonzero exit code may still be set.) */
func (cmd *RunningCommand) IsFinishedGracefully() bool {
	state := cmd.State()
	return state == FINISHED
}

func (cmd *RunningCommand) Start() *RunningCommand {
	if err := cmd.startCalmly(); err != nil {
		panic(err)
	}
	return cmd
}

func (cmd *RunningCommand) startCalmly() error {
	cmd.mutex.Lock()
	defer cmd.mutex.Unlock()

	if cmd.IsStarted() {
		return nil
	}

	atomic.StoreInt32(&cmd.state, RUNNING)
	if err := cmd.cmd.Start(); err != nil {
		cmd.finalState(CommandStartError{cause: err})
		return cmd.err
	}

	go cmd.waitAndHandleExit()
	return nil
}

func (cmd *RunningCommand) waitAndHandleExit() {
	err := cmd.cmd.Wait()

	cmd.mutex.Lock()
	defer cmd.mutex.Unlock()

	if err == nil {
		cmd.exitCode = 0
	} else if exitError, ok := err.(*exec.ExitError); ok {
		if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
			cmd.exitCode = waitStatus.ExitStatus()
		} else {
			panic(exitError) //TODO: damage control better.  consider setting some kind of CommandMonitorError.
		}
	} else {
		panic(err) //TODO: damage control better.  consider setting some kind of CommandMonitorError.
	}
	cmd.finalState(nil)
}

func (cmd *RunningCommand) finalState(err error) {
	// must hold cmd.mutex before calling this
	// golang is an epic troll: claims to be best buddy for concurrent code, SYNC PACKAGE DOES NOT HAVE REENTRANT LOCKS
	if cmd.IsRunning() {
		if err == nil {
			atomic.StoreInt32(&cmd.state, FINISHED)
		} else {
			cmd.err = err
			atomic.StoreInt32(&cmd.state, PANICKED)
		}
		// iterate over exit listeners
		for _, cb := range cmd.exitListeners {
			func() {
				defer recover()
				cb(cmd)
			}()
		}
	}
	close(cmd.exitCh)
}

/**
 * Add a function to be called when this command completes.
 *
 * These listener functions will be invoked after the exit code and other command
 * state is final, but before other Wait() methods unblock.
 * (This means if you want for example to log a message that a process exited, and
 * your main function is Wait()'ing for that process... if you use AddExitListener()
 * to invoke your log function then you will always get the log.)
 *
 * The listener function should complete quickly and not try to perform other blocking
 * operations or locks, since other actions are waiting until the listeners have all
 * been called.  Panics that escape the function will be silently discarded; do not
 * panic in a listener.
 *
 * If the command is already in the state FINISHED or PANICKED, the callback function
 * will be invoked immediately in the current goroutine.
 */
func (cmd *RunningCommand) AddExitListener(callback func(*RunningCommand)) {
	cmd.mutex.Lock()
	defer cmd.mutex.Unlock()

	if cmd.IsDone() {
		func() {
			defer recover()
			callback(cmd)
		}()
	} else {
		cmd.exitListeners = append(cmd.exitListeners, callback)
	}
}

/**
 * Returns a channel that will be open until the command is complete.
 * This is suitable for use in a select block.
 */
func (cmd *RunningCommand) GetExitChannel() <-chan bool {
	return cmd.exitCh
}

/**
 * Waits for the command to exit before returning.
 *
 * There are no consequences to waiting on a single command repeatedly;
 * all wait calls will return normally when the command completes.  The order
 * in which multiple wait calls will return is undefined.  Similarly, there
 * are no consequences to waiting on a command that has not yet started;
 * the function will still wait without error until the command finishes.
 * (Much friendlier than os.exec.Cmd.Wait(), neh?)
 */
func (cmd *RunningCommand) Wait() {
	<-cmd.GetExitChannel()
}

/**
 * Waits for the command to exit before returning, or for the specified duration.
 * Returns true if the return was due to the command finishing, or false if the
 * return was due to timeout.
 */
func (cmd *RunningCommand) WaitSoon(d time.Duration) bool {
	select {
	case <-time.After(d):
		return false
	case <-cmd.GetExitChannel():
		return true
	}
}

/**
 * Waits for the command to exit if it has not already, then returns the exit code.
 */
func (cmd *RunningCommand) GetExitCode() int {
	if !cmd.IsDone() {
		cmd.Wait()
	}
	return cmd.exitCode
}

/**
 * Waits for the command to exit if it has not already, or for the specified duration,
 * then either returns the exit code, or -1 if the duration expired and the command
 * still hasn't returned.
 */
func (cmd *RunningCommand) GetExitCodeSoon(d time.Duration) int {
	if cmd.WaitSoon(d) {
		return cmd.exitCode
	} else {
		return -1
	}
}
<|endoftext|>


<commit_before>package kutil

import (
	"fmt"
	"os"
	"os/exec"
	"text/template"
)

const (
	SystemV = 1
	Upstart = 2
	Systemd = 3
)

type LinuxService struct {
	itype                          int
	name, displayName, description string
	exePath                        string
}

func NewService(itype int, name, desc, exePath string) *LinuxService {
	return &LinuxService{itype, name, fmt.Sprintf("%v service", name), desc, exePath}
}

func isUpstart() bool {
	if _, err := os.Stat("/sbin/initctl"); err == nil {
		return true
	}
	return false
}

func isSystemd() bool {
	if _, err := os.Stat("/run/systemd/system"); err == nil {
		return true
	}
	return false
}

func SupportInitType(itype int) bool {
	if itype == SystemV {
		return true
	}
	if itype == Upstart {
		return isUpstart()
	}
	if itype == Systemd {
		return isSystemd()
	}
	return false
}

func GetPreferInitType() int {
	if isSystemd() {
		return Systemd
	}
	if isUpstart() {
		return Upstart
	}
	return SystemV
}

func configPath(itype int, name string) string {
	switch itype {
	case SystemV:
		return "/etc/init.d/" + name
	case Upstart:
		return "/etc/init/" + name + ".conf"
	case Systemd:
		return "/etc/systemd/system/" + name + ".service"
	default:
		return ""
	}
}

func (s *LinuxService) Install() error {
	confPath := configPath(s.itype, s.name)
	_, err := os.Stat(confPath)
	if err == nil {
		return fmt.Errorf("Init already exists: %s", confPath)
	}

	f, err := os.Create(confPath)
	if err != nil {
		return err
	}
	defer f.Close()

	path := s.exePath

	var to = &struct {
		Display     string
		Description string
		Path        string
	}{
		s.displayName,
		s.description,
		path,
	}

	var templ string
	switch s.itype {
	case SystemV:
		templ = systemVScript
	case Upstart:
		templ = upstartScript
	case Systemd:
		templ = systemdScript
	}
	mytemp := template.Must(template.New("Script").Parse(templ))
	err = mytemp.Execute(f, to)
	if err != nil {
		return err
	}

	if s.itype == SystemV {
		if err = os.Chmod(confPath, 0755); err != nil {
			return err
		}
		for _, i := range [...]string{"2", "3", "4", "5"} {
			if err = os.Symlink(confPath, "/etc/rc"+i+".d/S50"+s.name); err != nil {
				continue
			}
		}
		for _, i := range [...]string{"0", "1", "6"} {
			if err = os.Symlink(confPath, "/etc/rc"+i+".d/K02"+s.name); err != nil {
				continue
			}
		}
	}

	if s.itype == Systemd {
		return exec.Command("systemctl", "daemon-reload").Run()
	}

	return nil
}

func (s *LinuxService) Remove() error {
	if s.itype == Systemd {
		exec.Command("systemctl", "disable", s.name+".service").Run()
	}
	if err := os.Remove(configPath(s.itype, s.name)); err != nil {
		return err
	}
	return nil
}

func (s *LinuxService) Start() error {
	if s.itype == Upstart {
		return exec.Command("initctl", "start", s.name).Run()
	}
	if s.itype == Systemd {
		return exec.Command("systemctl", "start", s.name+".service").Run()
	}
	return exec.Command("service", s.name, "start").Run()
}

func (s *LinuxService) Stop() error {
	if s.itype == Upstart {
		return exec.Command("initctl", "stop", s.name).Run()
	}
	if s.itype == Systemd {
		return exec.Command("systemctl", "stop", s.name+".service").Run()
	}
	return exec.Command("service", s.name, "stop").Run()
}

const systemVScript = `#!/bin/sh
# For RedHat and cousins:
# chkconfig: - 99 01
# description: {{.Description}}
# processname: {{.Path}}

### BEGIN INIT INFO
# Provides:          {{.Path}}
# Required-Start:
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: {{.Display}}
# Description:       {{.Description}}
### END INIT INFO

cmd="{{.Path}}"

name=$(basename $0)
pid_file="/var/run/$name.pid"
stdout_log="/var/log/$name.log"
stderr_log="/var/log/$name.err"

get_pid() {
    cat "$pid_file"
}

is_running() {
    [ -f "$pid_file" ] && ps $(get_pid) > /dev/null 2>&1
}

case "$1" in
    start)
        if is_running; then
            echo "Already started"
        else
            echo "Starting $name"
            $cmd >> "$stdout_log" 2>> "$stderr_log" &
            echo $! > "$pid_file"
            if ! is_running; then
                echo "Unable to start, see $stdout_log and $stderr_log"
                exit 1
            fi
        fi
    ;;
    stop)
        if is_running; then
            echo -n "Stopping $name.."
            kill $(get_pid)
            for i in {1..10}
            do
                if ! is_running; then
                    break
                fi
                echo -n "."
                sleep 1
            done
            echo
            if is_running; then
                echo "Not stopped; may still be shutting down or shutdown may have failed"
                exit 1
            else
                echo "Stopped"
                if [ -f "$pid_file" ]; then
                    rm "$pid_file"
                fi
            fi
        else
            echo "Not running"
        fi
    ;;
    restart)
        $0 stop
        if is_running; then
            echo "Unable to stop, will not attempt to start"
            exit 1
        fi
        $0 start
    ;;
    status)
        if is_running; then
            echo "Running"
        else
            echo "Stopped"
            exit 1
        fi
    ;;
    *)
    echo "Usage: $0 {start|stop|restart|status}"
    exit 1
    ;;
esac
exit 0`

const upstartScript = `# {{.Description}}

description     "{{.Display}}"

start on filesystem or runlevel [2345]
stop on runlevel [!2345]

#setuid username

respawn
respawn limit 10 5
umask 022

console none

pre-start script
    test -x {{.Path}} || { stop; exit 0; }
end script

# Start
exec {{.Path}}
`

const systemdScript = `[Unit]
Description={{.Description}}
ConditionFileIsExecutable={{.Path}}

[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart={{.Path}}

[Install]
WantedBy=multi-user.target
`
<commit_msg>使生成的PID文件恒为进程名名。<commit_after>package kutil

import (
	"fmt"
	"os"
	"os/exec"
	"text/template"
)

const (
	SystemV = 1
	Upstart = 2
	Systemd = 3
)

type LinuxService struct {
	itype                          int
	name, displayName, description string
	exePath                        string
}

func NewService(itype int, name, desc, exePath string) *LinuxService {
	return &LinuxService{itype, name, fmt.Sprintf("%v service", name), desc, exePath}
}

func isUpstart() bool {
	if _, err := os.Stat("/sbin/initctl"); err == nil {
		return true
	}
	return false
}

func isSystemd() bool {
	if _, err := os.Stat("/run/systemd/system"); err == nil {
		return true
	}
	return false
}

func SupportInitType(itype int) bool {
	if itype == SystemV {
		return true
	}
	if itype == Upstart {
		return isUpstart()
	}
	if itype == Systemd {
		return isSystemd()
	}
	return false
}

func GetPreferInitType() int {
	if isSystemd() {
		return Systemd
	}
	if isUpstart() {
		return Upstart
	}
	return SystemV
}

func configPath(itype int, name string) string {
	switch itype {
	case SystemV:
		return "/etc/init.d/" + name
	case Upstart:
		return "/etc/init/" + name + ".conf"
	case Systemd:
		return "/etc/systemd/system/" + name + ".service"
	default:
		return ""
	}
}

func (s *LinuxService) Install() error {
	confPath := configPath(s.itype, s.name)
	_, err := os.Stat(confPath)
	if err == nil {
		return fmt.Errorf("Init already exists: %s", confPath)
	}

	f, err := os.Create(confPath)
	if err != nil {
		return err
	}
	defer f.Close()

	path := s.exePath

	var to = &struct {
		Display     string
		Description string
		Path        string
		Name		string
	}{
		s.displayName,
		s.description,
		path,
		s.name
	}

	var templ string
	switch s.itype {
	case SystemV:
		templ = systemVScript
	case Upstart:
		templ = upstartScript
	case Systemd:
		templ = systemdScript
	}
	mytemp := template.Must(template.New("Script").Parse(templ))
	err = mytemp.Execute(f, to)
	if err != nil {
		return err
	}

	if s.itype == SystemV {
		if err = os.Chmod(confPath, 0755); err != nil {
			return err
		}
		for _, i := range [...]string{"2", "3", "4", "5"} {
			if err = os.Symlink(confPath, "/etc/rc"+i+".d/S50"+s.name); err != nil {
				continue
			}
		}
		for _, i := range [...]string{"0", "1", "6"} {
			if err = os.Symlink(confPath, "/etc/rc"+i+".d/K02"+s.name); err != nil {
				continue
			}
		}
	}

	if s.itype == Systemd {
		return exec.Command("systemctl", "daemon-reload").Run()
	}

	return nil
}

func (s *LinuxService) Remove() error {
	if s.itype == Systemd {
		exec.Command("systemctl", "disable", s.name+".service").Run()
	}
	if err := os.Remove(configPath(s.itype, s.name)); err != nil {
		return err
	}
	return nil
}

func (s *LinuxService) Start() error {
	if s.itype == Upstart {
		return exec.Command("initctl", "start", s.name).Run()
	}
	if s.itype == Systemd {
		return exec.Command("systemctl", "start", s.name+".service").Run()
	}
	return exec.Command("service", s.name, "start").Run()
}

func (s *LinuxService) Stop() error {
	if s.itype == Upstart {
		return exec.Command("initctl", "stop", s.name).Run()
	}
	if s.itype == Systemd {
		return exec.Command("systemctl", "stop", s.name+".service").Run()
	}
	return exec.Command("service", s.name, "stop").Run()
}

const systemVScript = `#!/bin/sh
# For RedHat and cousins:
# chkconfig: - 99 01
# description: {{.Description}}
# processname: {{.Path}}

### BEGIN INIT INFO
# Provides:          {{.Path}}
# Required-Start:
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: {{.Display}}
# Description:       {{.Description}}
### END INIT INFO

cmd="{{.Path}}"

name="{{.Name}}"
pid_file="/var/run/$name.pid"
stdout_log="/var/log/$name.log"
stderr_log="/var/log/$name.err"

get_pid() {
    cat "$pid_file"
}

is_running() {
    [ -f "$pid_file" ] && ps $(get_pid) > /dev/null 2>&1
}

case "$1" in
    start)
        if is_running; then
            echo "Already started"
        else
            echo "Starting $name"
            $cmd >> "$stdout_log" 2>> "$stderr_log" &
            echo $! > "$pid_file"
            if ! is_running; then
                echo "Unable to start, see $stdout_log and $stderr_log"
                exit 1
            fi
        fi
    ;;
    stop)
        if is_running; then
            echo -n "Stopping $name.."
            kill $(get_pid)
            for i in {1..10}
            do
                if ! is_running; then
                    break
                fi
                echo -n "."
                sleep 1
            done
            echo
            if is_running; then
                echo "Not stopped; may still be shutting down or shutdown may have failed"
                exit 1
            else
                echo "Stopped"
                if [ -f "$pid_file" ]; then
                    rm "$pid_file"
                fi
            fi
        else
            echo "Not running"
        fi
    ;;
    restart)
        $0 stop
        if is_running; then
            echo "Unable to stop, will not attempt to start"
            exit 1
        fi
        $0 start
    ;;
    status)
        if is_running; then
            echo "Running"
        else
            echo "Stopped"
            exit 1
        fi
    ;;
    *)
    echo "Usage: $0 {start|stop|restart|status}"
    exit 1
    ;;
esac
exit 0`

const upstartScript = `# {{.Description}}

description     "{{.Display}}"

start on filesystem or runlevel [2345]
stop on runlevel [!2345]

#setuid username

respawn
respawn limit 10 5
umask 022

console none

pre-start script
    test -x {{.Path}} || { stop; exit 0; }
end script

# Start
exec {{.Path}}
`

const systemdScript = `[Unit]
Description={{.Description}}
ConditionFileIsExecutable={{.Path}}

[Service]
StartLimitInterval=5
StartLimitBurst=10
ExecStart={{.Path}}

[Install]
WantedBy=multi-user.target
`
<|endoftext|>


<commit_before>package gottyclient

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
)

// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
func GetWebsocketURL(httpURL string) (*url.URL, error) {
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, err
	}

	if target.Scheme == "https" {
		target.Scheme = "wss"
	} else {
		target.Scheme = "ws"
	}

	target.Path = strings.TrimLeft(target.Path+"ws", "/")

	return target, nil
}

type Client struct {
	Dialer    *websocket.Dialer
	Conn      *websocket.Conn
	Headers   http.Header
	Target    string
	Connected bool
}

// Connect tries to dial a websocket server
func (c *Client) Connect() error {
	conn, _, err := c.Dialer.Dial(c.Target, c.Headers)
	if err != nil {
		return err
	}
	c.Conn = conn
	return nil
}

// Close will nicely close the dialer
func (c *Client) Close() {
	c.Conn.Close()
}

// Loop will look indefinitely for new messages
func (c *Client) Loop() error {
	if !c.Connected {
		err := c.Connect()
		if err != nil {
			return err
		}
	}

	for {
		_, data, err := c.Conn.ReadMessage()
		if err != nil {
			return err
		}

		switch data[0] {
		case '0': // data
			fmt.Print(string(data[1:]))
		case '1': // new title
			newTitle := string(data[1:])
			fmt.Printf("\033]0;%s\007", newTitle)
		case '2': // json prefs
			logrus.Warnf("Unhandled protocol message: json pref: %s", string(data))
		default:
			logrus.Warnf("Unhandled protocol message: %s", string(data))
		}
	}
	return nil
}

// NewClient returns a GoTTY client object
func NewClient(httpURL string) (*Client, error) {
	target, err := GetWebsocketURL(httpURL)
	if err != nil {
		return nil, err
	}

	return &Client{
		Dialer: &websocket.Dialer{},
		Target: target.String(),
	}, nil
}
<commit_msg>Added write support<commit_after>package gottyclient

import (
	"bufio"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strings"
	"unicode/utf8"

	"github.com/Sirupsen/logrus"
	"github.com/gorilla/websocket"
)

// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
func GetWebsocketURL(httpURL string) (*url.URL, error) {
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, err
	}

	if target.Scheme == "https" {
		target.Scheme = "wss"
	} else {
		target.Scheme = "ws"
	}

	target.Path = strings.TrimLeft(target.Path+"ws", "/")

	return target, nil
}

type Client struct {
	Dialer    *websocket.Dialer
	Conn      *websocket.Conn
	Headers   http.Header
	Target    string
	Connected bool
}

// Connect tries to dial a websocket server
func (c *Client) Connect() error {
	conn, _, err := c.Dialer.Dial(c.Target, c.Headers)
	if err != nil {
		return err
	}
	c.Conn = conn
	return nil
}

// Close will nicely close the dialer
func (c *Client) Close() {
	c.Conn.Close()
}

// Loop will look indefinitely for new messages
func (c *Client) Loop() error {
	if !c.Connected {
		err := c.Connect()
		if err != nil {
			return err
		}
	}

	done := make(chan bool)
	go c.readLoop(done)
	go c.writeLoop(done)
	<-done
	return nil
}

func (c *Client) writeLoop(done chan bool) {
	reader := bufio.NewReader(os.Stdin)
	for {
		x, size, err := reader.ReadRune()
		if size <= 0 || err != nil {
			done <- true
			return
		}

		p := make([]byte, size)
		utf8.EncodeRune(p, x)

		err = c.Conn.WriteMessage(websocket.TextMessage, append([]byte("0"), p...))
		if err != nil {
			done <- true
			return
		}
	}
}

func (c *Client) readLoop(done chan bool) {
	for {
		_, data, err := c.Conn.ReadMessage()
		if err != nil {
			done <- true
			logrus.Warnf("c.Conn.ReadMessage: %v", err)
			return
		}

		switch data[0] {
		case '0': // data
			fmt.Print(string(data[1:]))
		case '1': // new title
			newTitle := string(data[1:])
			fmt.Printf("\033]0;%s\007", newTitle)
		case '2': // json prefs
			logrus.Warnf("Unhandled protocol message: json pref: %s", string(data))
		default:
			logrus.Warnf("Unhandled protocol message: %s", string(data))
		}
	}
}

// NewClient returns a GoTTY client object
func NewClient(httpURL string) (*Client, error) {
	target, err := GetWebsocketURL(httpURL)
	if err != nil {
		return nil, err
	}

	return &Client{
		Dialer: &websocket.Dialer{},
		Target: target.String(),
	}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/csv"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"time"

	"github.com/line/line-bot-sdk-go/linebot"

	"golang.org/x/net/context"
	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
)

// 調整さんの開催日ごとの集計エントリ
type schedule struct {
	Date             time.Time // 開催日（時間は00:00:00JST）
	DateString       string    // 日程欄（文字列）
	Present          int       // ◯
	Absent           int       // ×
	Unknown          int       // △および未入力
	ParticipantsName string    // 参加者の名前を列挙したもの
	UnknownName      string    // △および未入力の名前を列挙したもの
}

// 送信メッセージ用のサマリを組み立てて返す
func (s *schedule) constructSummaryBody() string {
	return s.DateString + "の出欠状況をお知らせします\n\n" +
		"参加: " + strconv.Itoa(s.Present) + "名" + s.ParticipantsName +
		"\n不参加: " + strconv.Itoa(s.Absent) + "名\n" +
		"不明/未入力: " + strconv.Itoa(s.Unknown) + "名" + s.UnknownName
}

// 送信メッセージ用のサマリを組み立てて返す
func (s *schedule) constructSummary(hash string) string {
	return s.constructSummaryBody() +
		"\n\n詳細および出欠変更は「調整さん」へ\nhttps://chouseisan.com/s?h=" + hash
}

// 調整さんスケジュールのMap型
type scheduleMap map[string]schedule

/**
 * 調整さんcsvをパースして、参加人数などを集計する
 */
func parseCsv(c context.Context, csvBody io.ReadCloser, today time.Time) (m scheduleMap) {
	var (
		names    []string
		rowCount = 0
	)
	m = make(scheduleMap)

	reader := csv.NewReader(transform.NewReader(csvBody, japanese.ShiftJIS.NewDecoder()))
	for {
		row, err := reader.Read()
		if err == io.EOF {
			break
		} else if e2, ok := err.(*csv.ParseError); ok && e2.Err == csv.ErrFieldCount {
			//フィールド数エラーは無視
		} else if err != nil {
			log.Errorf(c, "Read chouseisan's csv failed. err: %v", err)
			return nil
		}

		if rowCount < 2 {
			//イベント名、詳細説明文はスキップ

		} else if rowCount == 2 {
			//名前行
			for i, v := range row {
				if i > 0 {
					names = append(names, v)
				}
			}

		} else {
			//データ行（最終のコメント行も含む）
			s := schedule{}
			for i, v := range row {
				if i == 0 {
					//日付カラムはパースしてキーにする
					tz, _ := time.LoadLocation("Asia/Tokyo")
					year := today.Year()
					r := regexp.MustCompile(`^(\d{1,2})/(\d{1,2}).*$`)
					md := r.FindAllStringSubmatch(v, -1)
					if len(md) == 0 {
						log.Debugf(c, "Month and day parse error. col:%v", v)
						continue
					}
					month, merr := strconv.Atoi(md[0][1])
					day, derr := strconv.Atoi(md[0][2])
					if merr != nil {
						log.Debugf(c, "Month parse error. col:%v error:%v", v, merr)
						continue
					} else if derr != nil {
						log.Debugf(c, "Month parse error. col:%v error:%v", v, merr)
						continue
					} else {
						//日付パース成功
						if time.Month(month) < today.Month() {
							year++ //来年として扱う
						}
						s.Date = time.Date(year, time.Month(month), day, 0, 0, 0, 0, tz)
						s.DateString = v
						s.Present = 0
						s.Absent = 0
						s.Unknown = 0
						s.ParticipantsName = ""
						s.UnknownName = ""
					}

				} else if len(names[i-1]) > 0 {
					//出欠カラムの内容を、scheduleに足しこむ
					if v == "○" {
						s.Present++
						if len(s.ParticipantsName) > 0 {
							s.ParticipantsName += ","
						}
						s.ParticipantsName += names[i-1]
					} else if v == "×" {
						s.Absent++
					} else {
						s.Unknown++
						if len(s.UnknownName) > 0 {
							s.UnknownName += ","
						}
						s.UnknownName += names[i-1]
					}
				}
			}
			if len(s.DateString) > 0 {
				if len(s.ParticipantsName) > 0 {
					s.ParticipantsName = "(" + s.ParticipantsName + ")"
				}
				if len(s.UnknownName) > 0 {
					s.UnknownName = "(" + s.UnknownName + ")"
				}
				m[s.Date.String()] = s
			}
		}
		rowCount++
	}
	return m
}

/**
 * 購読者ごとのイテレーション処理。調整さんをクロールして通知対象があれば集計して返す
 */
func chouseisanIterator(current *subscriber, c context.Context, client *http.Client, w http.ResponseWriter, r *http.Request) []schedule {
	result := []schedule{}

	//調整さんの"出欠表をダウンロード"リンクからcsv形式で取得
	url := "https://chouseisan.com/schedule/List/createCsv?h=" + current.ChouseisanHash
	res, err := client.Get(url)
	if err != nil {
		log.Errorf(c, "Get chouseisan's csv failed. err: %v", err)
		return result
	} else if res.StatusCode != 200 {
		log.Errorf(c, "Get chouseisan's csv failed. StatusCode: %v", res.StatusCode)
		return result
	}

	//csvをパース
	tz, _ := time.LoadLocation("Asia/Tokyo")
	today := time.Now().In(tz)
	m := parseCsv(c, res.Body, today)

	//当日の予定をピック
	targetDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, tz)
	obj, exist := m[targetDate.String()]
	if exist {
		result = append(result, obj)
	} else {
		log.Debugf(c, "Not found schedule at today.")
	}

	//3日後の予定をピック
	targetDate = targetDate.AddDate(0, 0, 3)
	obj, exist = m[targetDate.String()]
	if exist {
		result = append(result, obj)
	} else {
		log.Debugf(c, "Not found schedule at 3 days after.")
	}

	return result
}

/**
 * 調整さんをクロールして出欠を通知
 *
 * 引数にContextとhttp.Clientを取るインナーメソッド
 */
func crawlChouseisanWithContext(c context.Context, client *http.Client, w http.ResponseWriter, r *http.Request) {
	bot, err := createBotClient(c, client)
	if err != nil {
		return
	}

	//hashの入ってるエンティティを抽出してループ
	ite := datastore.NewQuery("Subscriber").Run(c)
	for {
		var cSubscriber subscriber
		_, err = ite.Next(&cSubscriber)
		if err == datastore.Done {
			break
		} else if err != nil {
			log.Errorf(c, "Error occurred at fetch Subscriber. err:%v", err)
			break
		}

		if cSubscriber.ChouseisanHash != "" {
			// ハッシュが設定されていれば、調整さんイベントをクロール
			log.Infof(c, "Crawl chouseisan! subscriber:%v hash:%v", cSubscriber.DisplayName, cSubscriber.ChouseisanHash)
			result := chouseisanIterator(&cSubscriber, c, client, w, r)

			// リマインド対象イベントがあれば、Push Messageを送信
			for _, v := range result {
				log.Infof(c, "Remind event! subscriber:%v date:%v", cSubscriber.DisplayName, v.DateString)
				template := linebot.NewButtonsTemplate(
					"", //サムネイル
					"", //タイトル
					v.constructSummaryBody(), //画像もタイトルも指定しない場合：160文字以内
					linebot.NewURITemplateAction("出欠を登録（変更）する", "https://chouseisan.com/s?h="+cSubscriber.ChouseisanHash),
				)
				altText := "このメッセージが見えている人は、お使いのLINEアプリのバージョンおよび機種名を教えてください"
				if _, err = bot.PushMessage(cSubscriber.MID, linebot.NewTemplateMessage(altText, template)).Do(); err != nil {
					log.Errorf(c, "Error occurred at crawl chouseisan. subscriber:%v, date:%v, err: %v", cSubscriber.DisplayName, v.DateString, err)
				}
			}
		}
	}
}

/**
 * 調整さんをクロールして出欠を通知（cronからキックされる）
 */
func crawlChouseisan(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	crawlChouseisanWithContext(c, urlfetch.Client(c), w, r)
}
<commit_msg>Fix ButtonTemplate alt text<commit_after>package main

import (
	"encoding/csv"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"time"

	"github.com/line/line-bot-sdk-go/linebot"

	"golang.org/x/net/context"
	"golang.org/x/text/encoding/japanese"
	"golang.org/x/text/transform"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
)

// 調整さんの開催日ごとの集計エントリ
type schedule struct {
	Date             time.Time // 開催日（時間は00:00:00JST）
	DateString       string    // 日程欄（文字列）
	Present          int       // ◯
	Absent           int       // ×
	Unknown          int       // △および未入力
	ParticipantsName string    // 参加者の名前を列挙したもの
	UnknownName      string    // △および未入力の名前を列挙したもの
}

// 送信メッセージ用のサマリを組み立てて返す
func (s *schedule) constructSummaryBody() string {
	return s.DateString + "の出欠状況をお知らせします\n\n" +
		"参加: " + strconv.Itoa(s.Present) + "名" + s.ParticipantsName +
		"\n不参加: " + strconv.Itoa(s.Absent) + "名\n" +
		"不明/未入力: " + strconv.Itoa(s.Unknown) + "名" + s.UnknownName
}

// 送信メッセージ用のサマリを組み立てて返す
func (s *schedule) constructSummary(hash string) string {
	return s.constructSummaryBody() +
		"\n\n詳細および出欠変更は「調整さん」へ\nhttps://chouseisan.com/s?h=" + hash
}

// 調整さんスケジュールのMap型
type scheduleMap map[string]schedule

/**
 * 調整さんcsvをパースして、参加人数などを集計する
 */
func parseCsv(c context.Context, csvBody io.ReadCloser, today time.Time) (m scheduleMap) {
	var (
		names    []string
		rowCount = 0
	)
	m = make(scheduleMap)

	reader := csv.NewReader(transform.NewReader(csvBody, japanese.ShiftJIS.NewDecoder()))
	for {
		row, err := reader.Read()
		if err == io.EOF {
			break
		} else if e2, ok := err.(*csv.ParseError); ok && e2.Err == csv.ErrFieldCount {
			//フィールド数エラーは無視
		} else if err != nil {
			log.Errorf(c, "Read chouseisan's csv failed. err: %v", err)
			return nil
		}

		if rowCount < 2 {
			//イベント名、詳細説明文はスキップ

		} else if rowCount == 2 {
			//名前行
			for i, v := range row {
				if i > 0 {
					names = append(names, v)
				}
			}

		} else {
			//データ行（最終のコメント行も含む）
			s := schedule{}
			for i, v := range row {
				if i == 0 {
					//日付カラムはパースしてキーにする
					tz, _ := time.LoadLocation("Asia/Tokyo")
					year := today.Year()
					r := regexp.MustCompile(`^(\d{1,2})/(\d{1,2}).*$`)
					md := r.FindAllStringSubmatch(v, -1)
					if len(md) == 0 {
						log.Debugf(c, "Month and day parse error. col:%v", v)
						continue
					}
					month, merr := strconv.Atoi(md[0][1])
					day, derr := strconv.Atoi(md[0][2])
					if merr != nil {
						log.Debugf(c, "Month parse error. col:%v error:%v", v, merr)
						continue
					} else if derr != nil {
						log.Debugf(c, "Month parse error. col:%v error:%v", v, merr)
						continue
					} else {
						//日付パース成功
						if time.Month(month) < today.Month() {
							year++ //来年として扱う
						}
						s.Date = time.Date(year, time.Month(month), day, 0, 0, 0, 0, tz)
						s.DateString = v
						s.Present = 0
						s.Absent = 0
						s.Unknown = 0
						s.ParticipantsName = ""
						s.UnknownName = ""
					}

				} else if len(names[i-1]) > 0 {
					//出欠カラムの内容を、scheduleに足しこむ
					if v == "○" {
						s.Present++
						if len(s.ParticipantsName) > 0 {
							s.ParticipantsName += ","
						}
						s.ParticipantsName += names[i-1]
					} else if v == "×" {
						s.Absent++
					} else {
						s.Unknown++
						if len(s.UnknownName) > 0 {
							s.UnknownName += ","
						}
						s.UnknownName += names[i-1]
					}
				}
			}
			if len(s.DateString) > 0 {
				if len(s.ParticipantsName) > 0 {
					s.ParticipantsName = "(" + s.ParticipantsName + ")"
				}
				if len(s.UnknownName) > 0 {
					s.UnknownName = "(" + s.UnknownName + ")"
				}
				m[s.Date.String()] = s
			}
		}
		rowCount++
	}
	return m
}

/**
 * 購読者ごとのイテレーション処理。調整さんをクロールして通知対象があれば集計して返す
 */
func chouseisanIterator(current *subscriber, c context.Context, client *http.Client, w http.ResponseWriter, r *http.Request) []schedule {
	result := []schedule{}

	//調整さんの"出欠表をダウンロード"リンクからcsv形式で取得
	url := "https://chouseisan.com/schedule/List/createCsv?h=" + current.ChouseisanHash
	res, err := client.Get(url)
	if err != nil {
		log.Errorf(c, "Get chouseisan's csv failed. err: %v", err)
		return result
	} else if res.StatusCode != 200 {
		log.Errorf(c, "Get chouseisan's csv failed. StatusCode: %v", res.StatusCode)
		return result
	}

	//csvをパース
	tz, _ := time.LoadLocation("Asia/Tokyo")
	today := time.Now().In(tz)
	m := parseCsv(c, res.Body, today)

	//当日の予定をピック
	targetDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, tz)
	obj, exist := m[targetDate.String()]
	if exist {
		result = append(result, obj)
	} else {
		log.Debugf(c, "Not found schedule at today.")
	}

	//3日後の予定をピック
	targetDate = targetDate.AddDate(0, 0, 3)
	obj, exist = m[targetDate.String()]
	if exist {
		result = append(result, obj)
	} else {
		log.Debugf(c, "Not found schedule at 3 days after.")
	}

	return result
}

/**
 * 調整さんをクロールして出欠を通知
 *
 * 引数にContextとhttp.Clientを取るインナーメソッド
 */
func crawlChouseisanWithContext(c context.Context, client *http.Client, w http.ResponseWriter, r *http.Request) {
	bot, err := createBotClient(c, client)
	if err != nil {
		return
	}

	//hashの入ってるエンティティを抽出してループ
	ite := datastore.NewQuery("Subscriber").Run(c)
	for {
		var cSubscriber subscriber
		_, err = ite.Next(&cSubscriber)
		if err == datastore.Done {
			break
		} else if err != nil {
			log.Errorf(c, "Error occurred at fetch Subscriber. err:%v", err)
			break
		}

		if cSubscriber.ChouseisanHash != "" {
			// ハッシュが設定されていれば、調整さんイベントをクロール
			log.Infof(c, "Crawl chouseisan! subscriber:%v hash:%v", cSubscriber.DisplayName, cSubscriber.ChouseisanHash)
			result := chouseisanIterator(&cSubscriber, c, client, w, r)

			// リマインド対象イベントがあれば、Push Messageを送信
			for _, v := range result {
				log.Infof(c, "Remind event! subscriber:%v date:%v", cSubscriber.DisplayName, v.DateString)
				template := linebot.NewButtonsTemplate(
					"", //サムネイル
					"", //タイトル
					v.constructSummaryBody(), //画像もタイトルも指定しない場合：160文字以内
					linebot.NewURITemplateAction("出欠を登録（変更）する", "https://chouseisan.com/s?h="+cSubscriber.ChouseisanHash),
				)
				altText := v.DateString + "の出欠状況をお知らせします" +
					"\n（トークルームでもこのメッセージが見えている人は、お使いのLINEアプリのバージョンおよび機種名を教えてください）"
				if _, err = bot.PushMessage(cSubscriber.MID, linebot.NewTemplateMessage(altText, template)).Do(); err != nil {
					log.Errorf(c, "Error occurred at crawl chouseisan. subscriber:%v, date:%v, err: %v", cSubscriber.DisplayName, v.DateString, err)
				}
			}
		}
	}
}

/**
 * 調整さんをクロールして出欠を通知（cronからキックされる）
 */
func crawlChouseisan(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	crawlChouseisanWithContext(c, urlfetch.Client(c), w, r)
}
<|endoftext|>