<commit_before>/*
   Copyright 2014 Outbrain Inc.

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

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

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

package inst

import (
	"net"

	"github.com/github/orchestrator/go/config"
	"github.com/github/orchestrator/go/db"
	"github.com/openark/golib/log"
	"github.com/openark/golib/sqlutils"
	"github.com/rcrowley/go-metrics"
)

var writeResolvedHostnameCounter = metrics.NewCounter()
var writeUnresolvedHostnameCounter = metrics.NewCounter()
var readResolvedHostnameCounter = metrics.NewCounter()
var readUnresolvedHostnameCounter = metrics.NewCounter()
var readAllResolvedHostnamesCounter = metrics.NewCounter()

func init() {
	metrics.Register("resolve.write_resolved", writeResolvedHostnameCounter)
	metrics.Register("resolve.write_unresolved", writeUnresolvedHostnameCounter)
	metrics.Register("resolve.read_resolved", readResolvedHostnameCounter)
	metrics.Register("resolve.read_unresolved", readUnresolvedHostnameCounter)
	metrics.Register("resolve.read_resolved_all", readAllResolvedHostnamesCounter)
}

// WriteResolvedHostname stores a hostname and the resolved hostname to backend database
func WriteResolvedHostname(hostname string, resolvedHostname string) error {
	writeFunc := func() error {
		_, err := db.ExecOrchestrator(`
			insert into
					hostname_resolve (hostname, resolved_hostname, resolved_timestamp)
				values
					(?, ?, NOW())
				on duplicate key update
					resolved_hostname = VALUES(resolved_hostname),
					resolved_timestamp = VALUES(resolved_timestamp)
			`,
			hostname,
			resolvedHostname)
		if err != nil {
			return log.Errore(err)
		}
		if hostname != resolvedHostname {
			// history is only interesting when there's actually something to resolve...
			_, err = db.ExecOrchestrator(`
			insert into
					hostname_resolve_history (hostname, resolved_hostname, resolved_timestamp)
				values
					(?, ?, NOW())
				on duplicate key update
					hostname=values(hostname),
					resolved_timestamp=values(resolved_timestamp)
			`,
				hostname,
				resolvedHostname)
		}
		log.Debugf("WriteResolvedHostname: resolved %s to %s", hostname, resolvedHostname)
		writeResolvedHostnameCounter.Inc(1)
		return nil
	}
	return ExecDBWriteFunc(writeFunc)
}

// ReadResolvedHostname returns the resolved hostname given a hostname, or empty if not exists
func ReadResolvedHostname(hostname string) (string, error) {
	var resolvedHostname string = ""

	query := `
		select
			resolved_hostname
		from
			hostname_resolve
		where
			hostname = ?
		`

	err := db.QueryOrchestrator(query, sqlutils.Args(hostname), func(m sqlutils.RowMap) error {
		resolvedHostname = m.GetString("resolved_hostname")
		return nil
	})
	readResolvedHostnameCounter.Inc(1)

	if err != nil {
		log.Errore(err)
	}
	return resolvedHostname, err
}

func ReadAllHostnameResolves() ([]HostnameResolve, error) {
	res := []HostnameResolve{}
	query := `
		select
			hostname,
			resolved_hostname
		from
			hostname_resolve
		`
	err := db.QueryOrchestratorRowsMap(query, func(m sqlutils.RowMap) error {
		hostnameResolve := HostnameResolve{hostname: m.GetString("hostname"), resolvedHostname: m.GetString("resolved_hostname")}

		res = append(res, hostnameResolve)
		return nil
	})
	readAllResolvedHostnamesCounter.Inc(1)

	if err != nil {
		log.Errore(err)
	}
	return res, err
}

// ReadAllHostnameUnresolves returns the content of the hostname_unresolve table
func ReadAllHostnameUnresolves() ([]HostnameUnresolve, error) {
	unres := []HostnameUnresolve{}
	query := `
		select
			hostname,
			unresolved_hostname
		from
			hostname_unresolve
		`
	err := db.QueryOrchestratorRowsMap(query, func(m sqlutils.RowMap) error {
		hostnameUnresolve := HostnameUnresolve{hostname: m.GetString("hostname"), unresolvedHostname: m.GetString("unresolved_hostname")}

		unres = append(unres, hostnameUnresolve)
		return nil
	})

	return unres, log.Errore(err)
}

// ReadAllHostnameUnresolves returns the content of the hostname_unresolve table
func ReadAllHostnameUnresolvesRegistrations() (registrations []HostnameRegistration, err error) {
	unresolves, err := ReadAllHostnameUnresolves()
	if err != nil {
		return registrations, err
	}
	for _, unresolve := range unresolves {
		registration := NewHostnameRegistration(&InstanceKey{Hostname: unresolve.hostname}, unresolve.unresolvedHostname)
		registrations = append(registrations, *registration)
	}
	return registrations, nil
}

// readUnresolvedHostname reverse-reads hostname resolve. It returns a hostname which matches given pattern and resovles to resolvedHostname,
// or, in the event no such hostname is found, the given resolvedHostname, unchanged.
func readUnresolvedHostname(hostname string) (string, error) {
	unresolvedHostname := hostname

	query := `
	   		select
	   			unresolved_hostname
	   		from
	   			hostname_unresolve
	   		where
	   			hostname = ?
	   		`

	err := db.QueryOrchestrator(query, sqlutils.Args(hostname), func(m sqlutils.RowMap) error {
		unresolvedHostname = m.GetString("unresolved_hostname")
		return nil
	})
	readUnresolvedHostnameCounter.Inc(1)

	if err != nil {
		log.Errore(err)
	}
	return unresolvedHostname, err
}

// readMissingHostnamesToResolve gets those (unresolved, e.g. VIP) hostnames that *should* be present in
// the hostname_resolve table, but aren't.
func readMissingKeysToResolve() (result InstanceKeyMap, err error) {
	query := `
   		select
   				hostname_unresolve.unresolved_hostname,
   				database_instance.port
   			from
   				database_instance
   				join hostname_unresolve on (database_instance.hostname = hostname_unresolve.hostname)
   				left join hostname_resolve on (database_instance.hostname = hostname_resolve.resolved_hostname)
   			where
   				hostname_resolve.hostname is null
	   		`

	err = db.QueryOrchestratorRowsMap(query, func(m sqlutils.RowMap) error {
		instanceKey := InstanceKey{Hostname: m.GetString("unresolved_hostname"), Port: m.GetInt("port")}
		result.AddKey(instanceKey)
		return nil
	})

	if err != nil {
		log.Errore(err)
	}
	return result, err
}

// WriteHostnameUnresolve upserts an entry in hostname_unresolve
func WriteHostnameUnresolve(instanceKey *InstanceKey, unresolvedHostname string) error {
	writeFunc := func() error {
		_, err := db.ExecOrchestrator(`
        	insert into hostname_unresolve (
        		hostname,
        		unresolved_hostname,
        		last_registered)
        	values (?, ?, NOW())
        	on duplicate key update
        		unresolved_hostname=values(unresolved_hostname),
        		last_registered=now()
				`, instanceKey.Hostname, unresolvedHostname,
		)
		if err != nil {
			return log.Errore(err)
		}
		_, err = db.ExecOrchestrator(`
        	replace into hostname_unresolve_history (
        		hostname,
        		unresolved_hostname,
        		last_registered)
        	values (?, ?, NOW())
				`, instanceKey.Hostname, unresolvedHostname,
		)
		writeUnresolvedHostnameCounter.Inc(1)
		return nil
	}
	return ExecDBWriteFunc(writeFunc)
}

// DeleteHostnameUnresolve removes an unresolve entry
func DeleteHostnameUnresolve(instanceKey *InstanceKey) error {
	writeFunc := func() error {
		_, err := db.ExecOrchestrator(`
      	delete from hostname_unresolve
				where hostname=?
				`, instanceKey.Hostname,
		)
		return log.Errore(err)
	}
	return ExecDBWriteFunc(writeFunc)
}

// ExpireHostnameUnresolve expires hostname_unresolve entries that haven't been updated recently.
func ExpireHostnameUnresolve() error {
	writeFunc := func() error {
		_, err := db.ExecOrchestrator(`
      	delete from hostname_unresolve
				where last_registered < NOW() - INTERVAL ? MINUTE
				`, config.Config.ExpiryHostnameResolvesMinutes,
		)
		return log.Errore(err)
	}
	return ExecDBWriteFunc(writeFunc)
}

// ForgetExpiredHostnameResolves
func ForgetExpiredHostnameResolves() error {
	_, err := db.ExecOrchestrator(`
			delete
				from hostname_resolve
			where
				resolved_timestamp < NOW() - interval ? minute`,
		2*config.Config.ExpiryHostnameResolvesMinutes,
	)
	return err
}

// DeleteInvalidHostnameResolves removes invalid resolves. At this time these are:
// - infinite loop resolves (A->B and B->A), remove earlier mapping
func DeleteInvalidHostnameResolves() error {
	var invalidHostnames []string

	query := `
		select
		    early.hostname
		  from
		    hostname_resolve as latest
		    join hostname_resolve early on (latest.resolved_hostname = early.hostname and latest.hostname = early.resolved_hostname)
		  where
		    latest.hostname != latest.resolved_hostname
		    and latest.resolved_timestamp > early.resolved_timestamp
	   	`

	err := db.QueryOrchestratorRowsMap(query, func(m sqlutils.RowMap) error {
		invalidHostnames = append(invalidHostnames, m.GetString("hostname"))
		return nil
	})
	if err != nil {
		return err
	}

	for _, invalidHostname := range invalidHostnames {
		_, err = db.ExecOrchestrator(`
			delete
				from hostname_resolve
			where
				hostname = ?`,
			invalidHostname,
		)
		log.Errore(err)
	}
	return err
}

// deleteHostnameResolves compeltely erases the database cache
func deleteHostnameResolves() error {
	_, err := db.ExecOrchestrator(`
			delete
				from hostname_resolve`,
	)
	return err
}

// writeHostnameIPs stroes an ipv4 and ipv6 associated witha hostname, if available
func writeHostnameIPs(hostname string, ips []net.IP) error {
	ipv4String := ""
	ipv6String := ""
	for _, ip := range ips {
		if ip4 := ip.To4(); ip4 != nil {
			ipv4String = ip.String()
		} else {
			ipv6String = ip.String()
		}
	}
	writeFunc := func() error {
		_, err := db.ExecOrchestrator(`
			insert into
					hostname_ips (hostname, ipv4, ipv6, last_updated)
				values
					(?, ?, ?, NOW())
				on duplicate key update
					ipv4 = VALUES(ipv4),
					ipv6 = VALUES(ipv6),
					last_updated = VALUES(last_updated)
			`,
			hostname,
			ipv4String,
			ipv6String,
		)
		return log.Errore(err)
	}
	return ExecDBWriteFunc(writeFunc)
}

// readUnresolvedHostname reverse-reads hostname resolve. It returns a hostname which matches given pattern and resovles to resolvedHostname,
// or, in the event no such hostname is found, the given resolvedHostname, unchanged.
func readHostnameIPs(hostname string) (ipv4 string, ipv6 string, err error) {
	query := `
		select
			ipv4, ipv6
		from
			hostname_ips
		where
			hostname = ?
	`
	err = db.QueryOrchestrator(query, sqlutils.Args(hostname), func(m sqlutils.RowMap) error {
		ipv4 = m.GetString("ipv4")
		ipv6 = m.GetString("ipv6")
		return nil
	})
	return ipv4, ipv6, log.Errore(err)
}
<commit_msg>reduced WriteResolvedHostname logging<commit_after>/*
   Copyright 2014 Outbrain Inc.

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

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

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

package inst

import (
	"net"

	"github.com/github/orchestrator/go/config"
	"github.com/github/orchestrator/go/db"
	"github.com/openark/golib/log"
	"github.com/openark/golib/sqlutils"
	"github.com/rcrowley/go-metrics"
)

var writeResolvedHostnameCounter = metrics.NewCounter()
var writeUnresolvedHostnameCounter = metrics.NewCounter()
var readResolvedHostnameCounter = metrics.NewCounter()
var readUnresolvedHostnameCounter = metrics.NewCounter()
var readAllResolvedHostnamesCounter = metrics.NewCounter()

func init() {
	metrics.Register("resolve.write_resolved", writeResolvedHostnameCounter)
	metrics.Register("resolve.write_unresolved", writeUnresolvedHostnameCounter)
	metrics.Register("resolve.read_resolved", readResolvedHostnameCounter)
	metrics.Register("resolve.read_unresolved", readUnresolvedHostnameCounter)
	metrics.Register("resolve.read_resolved_all", readAllResolvedHostnamesCounter)
}

// WriteResolvedHostname stores a hostname and the resolved hostname to backend database
func WriteResolvedHostname(hostname string, resolvedHostname string) error {
	writeFunc := func() error {
		_, err := db.ExecOrchestrator(`
			insert into
					hostname_resolve (hostname, resolved_hostname, resolved_timestamp)
				values
					(?, ?, NOW())
				on duplicate key update
					resolved_hostname = VALUES(resolved_hostname),
					resolved_timestamp = VALUES(resolved_timestamp)
			`,
			hostname,
			resolvedHostname)
		if err != nil {
			return log.Errore(err)
		}
		if hostname != resolvedHostname {
			// history is only interesting when there's actually something to resolve...
			_, err = db.ExecOrchestrator(`
			insert into
					hostname_resolve_history (hostname, resolved_hostname, resolved_timestamp)
				values
					(?, ?, NOW())
				on duplicate key update
					hostname=values(hostname),
					resolved_timestamp=values(resolved_timestamp)
			`,
				hostname,
				resolvedHostname)
		}
		writeResolvedHostnameCounter.Inc(1)
		return nil
	}
	return ExecDBWriteFunc(writeFunc)
}

// ReadResolvedHostname returns the resolved hostname given a hostname, or empty if not exists
func ReadResolvedHostname(hostname string) (string, error) {
	var resolvedHostname string = ""

	query := `
		select
			resolved_hostname
		from
			hostname_resolve
		where
			hostname = ?
		`

	err := db.QueryOrchestrator(query, sqlutils.Args(hostname), func(m sqlutils.RowMap) error {
		resolvedHostname = m.GetString("resolved_hostname")
		return nil
	})
	readResolvedHostnameCounter.Inc(1)

	if err != nil {
		log.Errore(err)
	}
	return resolvedHostname, err
}

func ReadAllHostnameResolves() ([]HostnameResolve, error) {
	res := []HostnameResolve{}
	query := `
		select
			hostname,
			resolved_hostname
		from
			hostname_resolve
		`
	err := db.QueryOrchestratorRowsMap(query, func(m sqlutils.RowMap) error {
		hostnameResolve := HostnameResolve{hostname: m.GetString("hostname"), resolvedHostname: m.GetString("resolved_hostname")}

		res = append(res, hostnameResolve)
		return nil
	})
	readAllResolvedHostnamesCounter.Inc(1)

	if err != nil {
		log.Errore(err)
	}
	return res, err
}

// ReadAllHostnameUnresolves returns the content of the hostname_unresolve table
func ReadAllHostnameUnresolves() ([]HostnameUnresolve, error) {
	unres := []HostnameUnresolve{}
	query := `
		select
			hostname,
			unresolved_hostname
		from
			hostname_unresolve
		`
	err := db.QueryOrchestratorRowsMap(query, func(m sqlutils.RowMap) error {
		hostnameUnresolve := HostnameUnresolve{hostname: m.GetString("hostname"), unresolvedHostname: m.GetString("unresolved_hostname")}

		unres = append(unres, hostnameUnresolve)
		return nil
	})

	return unres, log.Errore(err)
}

// ReadAllHostnameUnresolves returns the content of the hostname_unresolve table
func ReadAllHostnameUnresolvesRegistrations() (registrations []HostnameRegistration, err error) {
	unresolves, err := ReadAllHostnameUnresolves()
	if err != nil {
		return registrations, err
	}
	for _, unresolve := range unresolves {
		registration := NewHostnameRegistration(&InstanceKey{Hostname: unresolve.hostname}, unresolve.unresolvedHostname)
		registrations = append(registrations, *registration)
	}
	return registrations, nil
}

// readUnresolvedHostname reverse-reads hostname resolve. It returns a hostname which matches given pattern and resovles to resolvedHostname,
// or, in the event no such hostname is found, the given resolvedHostname, unchanged.
func readUnresolvedHostname(hostname string) (string, error) {
	unresolvedHostname := hostname

	query := `
	   		select
	   			unresolved_hostname
	   		from
	   			hostname_unresolve
	   		where
	   			hostname = ?
	   		`

	err := db.QueryOrchestrator(query, sqlutils.Args(hostname), func(m sqlutils.RowMap) error {
		unresolvedHostname = m.GetString("unresolved_hostname")
		return nil
	})
	readUnresolvedHostnameCounter.Inc(1)

	if err != nil {
		log.Errore(err)
	}
	return unresolvedHostname, err
}

// readMissingHostnamesToResolve gets those (unresolved, e.g. VIP) hostnames that *should* be present in
// the hostname_resolve table, but aren't.
func readMissingKeysToResolve() (result InstanceKeyMap, err error) {
	query := `
   		select
   				hostname_unresolve.unresolved_hostname,
   				database_instance.port
   			from
   				database_instance
   				join hostname_unresolve on (database_instance.hostname = hostname_unresolve.hostname)
   				left join hostname_resolve on (database_instance.hostname = hostname_resolve.resolved_hostname)
   			where
   				hostname_resolve.hostname is null
	   		`

	err = db.QueryOrchestratorRowsMap(query, func(m sqlutils.RowMap) error {
		instanceKey := InstanceKey{Hostname: m.GetString("unresolved_hostname"), Port: m.GetInt("port")}
		result.AddKey(instanceKey)
		return nil
	})

	if err != nil {
		log.Errore(err)
	}
	return result, err
}

// WriteHostnameUnresolve upserts an entry in hostname_unresolve
func WriteHostnameUnresolve(instanceKey *InstanceKey, unresolvedHostname string) error {
	writeFunc := func() error {
		_, err := db.ExecOrchestrator(`
        	insert into hostname_unresolve (
        		hostname,
        		unresolved_hostname,
        		last_registered)
        	values (?, ?, NOW())
        	on duplicate key update
        		unresolved_hostname=values(unresolved_hostname),
        		last_registered=now()
				`, instanceKey.Hostname, unresolvedHostname,
		)
		if err != nil {
			return log.Errore(err)
		}
		_, err = db.ExecOrchestrator(`
        	replace into hostname_unresolve_history (
        		hostname,
        		unresolved_hostname,
        		last_registered)
        	values (?, ?, NOW())
				`, instanceKey.Hostname, unresolvedHostname,
		)
		writeUnresolvedHostnameCounter.Inc(1)
		return nil
	}
	return ExecDBWriteFunc(writeFunc)
}

// DeleteHostnameUnresolve removes an unresolve entry
func DeleteHostnameUnresolve(instanceKey *InstanceKey) error {
	writeFunc := func() error {
		_, err := db.ExecOrchestrator(`
      	delete from hostname_unresolve
				where hostname=?
				`, instanceKey.Hostname,
		)
		return log.Errore(err)
	}
	return ExecDBWriteFunc(writeFunc)
}

// ExpireHostnameUnresolve expires hostname_unresolve entries that haven't been updated recently.
func ExpireHostnameUnresolve() error {
	writeFunc := func() error {
		_, err := db.ExecOrchestrator(`
      	delete from hostname_unresolve
				where last_registered < NOW() - INTERVAL ? MINUTE
				`, config.Config.ExpiryHostnameResolvesMinutes,
		)
		return log.Errore(err)
	}
	return ExecDBWriteFunc(writeFunc)
}

// ForgetExpiredHostnameResolves
func ForgetExpiredHostnameResolves() error {
	_, err := db.ExecOrchestrator(`
			delete
				from hostname_resolve
			where
				resolved_timestamp < NOW() - interval ? minute`,
		2*config.Config.ExpiryHostnameResolvesMinutes,
	)
	return err
}

// DeleteInvalidHostnameResolves removes invalid resolves. At this time these are:
// - infinite loop resolves (A->B and B->A), remove earlier mapping
func DeleteInvalidHostnameResolves() error {
	var invalidHostnames []string

	query := `
		select
		    early.hostname
		  from
		    hostname_resolve as latest
		    join hostname_resolve early on (latest.resolved_hostname = early.hostname and latest.hostname = early.resolved_hostname)
		  where
		    latest.hostname != latest.resolved_hostname
		    and latest.resolved_timestamp > early.resolved_timestamp
	   	`

	err := db.QueryOrchestratorRowsMap(query, func(m sqlutils.RowMap) error {
		invalidHostnames = append(invalidHostnames, m.GetString("hostname"))
		return nil
	})
	if err != nil {
		return err
	}

	for _, invalidHostname := range invalidHostnames {
		_, err = db.ExecOrchestrator(`
			delete
				from hostname_resolve
			where
				hostname = ?`,
			invalidHostname,
		)
		log.Errore(err)
	}
	return err
}

// deleteHostnameResolves compeltely erases the database cache
func deleteHostnameResolves() error {
	_, err := db.ExecOrchestrator(`
			delete
				from hostname_resolve`,
	)
	return err
}

// writeHostnameIPs stroes an ipv4 and ipv6 associated witha hostname, if available
func writeHostnameIPs(hostname string, ips []net.IP) error {
	ipv4String := ""
	ipv6String := ""
	for _, ip := range ips {
		if ip4 := ip.To4(); ip4 != nil {
			ipv4String = ip.String()
		} else {
			ipv6String = ip.String()
		}
	}
	writeFunc := func() error {
		_, err := db.ExecOrchestrator(`
			insert into
					hostname_ips (hostname, ipv4, ipv6, last_updated)
				values
					(?, ?, ?, NOW())
				on duplicate key update
					ipv4 = VALUES(ipv4),
					ipv6 = VALUES(ipv6),
					last_updated = VALUES(last_updated)
			`,
			hostname,
			ipv4String,
			ipv6String,
		)
		return log.Errore(err)
	}
	return ExecDBWriteFunc(writeFunc)
}

// readUnresolvedHostname reverse-reads hostname resolve. It returns a hostname which matches given pattern and resovles to resolvedHostname,
// or, in the event no such hostname is found, the given resolvedHostname, unchanged.
func readHostnameIPs(hostname string) (ipv4 string, ipv6 string, err error) {
	query := `
		select
			ipv4, ipv6
		from
			hostname_ips
		where
			hostname = ?
	`
	err = db.QueryOrchestrator(query, sqlutils.Args(hostname), func(m sqlutils.RowMap) error {
		ipv4 = m.GetString("ipv4")
		ipv6 = m.GetString("ipv6")
		return nil
	})
	return ipv4, ipv6, log.Errore(err)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/samalba/dockerclient"
	"io"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

var DOCKER_CLIENT *dockerclient.DockerClient

// Callback used to listen to Docker's events
func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) {

	fmt.Println("---")
	fmt.Printf("%+v\n", *event)

	client := &http.Client{}

	id := event.Id

	switch event.Status {
	case "create":
		fmt.Println("create event")

		repo, tag := splitRepoAndTag(event.From)

		containerName := "<name>"

		containerInfo, err := DOCKER_CLIENT.InspectContainer(id)

		if err != nil {
			fmt.Print("InspectContainer error:", err.Error())
		} else {
			containerName = containerInfo.Name
		}

		data := url.Values{
			"action":    {"createContainer"},
			"id":        {id},
			"name":      {containerName},
			"imageRepo": {repo},
			"imageTag":  {tag}}

		MCServerRequest(data, client)

	case "start":
		fmt.Println("start event")

		repo, tag := splitRepoAndTag(event.From)

		containerName := "<name>"

		containerInfo, err := DOCKER_CLIENT.InspectContainer(id)

		if err != nil {
			fmt.Print("InspectContainer error:", err.Error())
		} else {
			containerName = containerInfo.Name
		}

		data := url.Values{
			"action":    {"startContainer"},
			"id":        {id},
			"name":      {containerName},
			"imageRepo": {repo},
			"imageTag":  {tag}}

		// Monitor stats
		DOCKER_CLIENT.StartMonitorStats(id, statCallback, nil)

		MCServerRequest(data, client)

	case "stop":
		fmt.Println("stop event")

		repo, tag := splitRepoAndTag(event.From)

		containerName := "<name>"

		containerInfo, err := DOCKER_CLIENT.InspectContainer(id)

		if err != nil {
			fmt.Print("InspectContainer error:", err.Error())
		} else {
			containerName = containerInfo.Name
		}

		data := url.Values{
			"action":    {"stopContainer"},
			"id":        {id},
			"name":      {containerName},
			"imageRepo": {repo},
			"imageTag":  {tag}}

		MCServerRequest(data, client)

	case "restart":
		fmt.Println("restart event")
		//  same as start event
		repo, tag := splitRepoAndTag(event.From)

		containerName := "<name>"

		containerInfo, err := DOCKER_CLIENT.InspectContainer(id)

		if err != nil {
			fmt.Print("InspectContainer error:", err.Error())
		} else {
			containerName = containerInfo.Name
		}

		data := url.Values{
			"action":    {"startContainer"},
			"id":        {id},
			"name":      {containerName},
			"imageRepo": {repo},
			"imageTag":  {tag}}

		MCServerRequest(data, client)

	case "kill":
		fmt.Println("kill event")
		// same as stop event
		repo, tag := splitRepoAndTag(event.From)

		containerName := "<name>"

		containerInfo, err := DOCKER_CLIENT.InspectContainer(id)

		if err != nil {
			fmt.Print("InspectContainer error:", err.Error())
		} else {
			containerName = containerInfo.Name
		}

		data := url.Values{
			"action":    {"stopContainer"},
			"id":        {id},
			"name":      {containerName},
			"imageRepo": {repo},
			"imageTag":  {tag}}

		MCServerRequest(data, client)

	case "die":
		fmt.Println("die event")
		// same as stop event
		repo, tag := splitRepoAndTag(event.From)

		containerName := "<name>"

		containerInfo, err := DOCKER_CLIENT.InspectContainer(id)

		if err != nil {
			fmt.Print("InspectContainer error:", err.Error())
		} else {
			containerName = containerInfo.Name
		}

		data := url.Values{
			"action":    {"stopContainer"},
			"id":        {id},
			"name":      {containerName},
			"imageRepo": {repo},
			"imageTag":  {tag}}

		MCServerRequest(data, client)

	case "destroy":
		fmt.Println("destroy event")

		data := url.Values{
			"action": {"destroyContainer"},
			"id":     {id},
		}

		MCServerRequest(data, client)
	}
}

func statCallback(id string, stat *dockerclient.Stats, ec chan error, args ...interface{}) {

	//fmt.Println("STATS", id, stat)

	// fmt.Println("---")
	// fmt.Println("cpu :", float64(stat.CpuStats.CpuUsage.TotalUsage)/float64(stat.CpuStats.SystemUsage))
	// fmt.Println("ram :", stat.MemoryStats.Usage)

	client := &http.Client{}

	memPercent := float64(stat.MemoryStats.Usage) / float64(stat.MemoryStats.Limit) * 100.0

	var cpuPercent float64 = 0.0

	if preCPUStats, exists := previousCPUStats[id]; exists {

		cpuPercent = calculateCPUPercent(preCPUStats, &stat.CpuStats)

	}

	previousCPUStats[id] = &CPUStats{TotalUsage: stat.CpuStats.CpuUsage.TotalUsage, SystemUsage: stat.CpuStats.SystemUsage}

	data := url.Values{
		"action": {"stats"},
		"id":     {id},
		"cpu":    {strconv.FormatFloat(cpuPercent, 'f', 2, 64) + "%"},
		"ram":    {strconv.FormatFloat(memPercent, 'f', 2, 64) + "%"}}

	MCServerRequest(data, client)
}

func execCmd(w http.ResponseWriter, r *http.Request) {

	fmt.Println("*** execCmd (1)")

	io.WriteString(w, "OK")

	go func() {

		fmt.Println("*** execCmd")

		cmd := r.URL.Query().Get("cmd")

		fmt.Println("*** cmd:", cmd)

		cmd, _ = url.QueryUnescape(cmd)

		fmt.Println("*** cmd (unescape):", cmd)

		arr := strings.Split(cmd, " ")

		fmt.Println("*** arr:", arr)

		if len(arr) > 0 {
			cmd := exec.Command(arr[0], arr[1:]...)

			// Stdout buffer
			// cmdOutput := &bytes.Buffer{}
			// Attach buffer to command
			// cmd.Stdout = cmdOutput

			// Execute command
			// printCommand(cmd)
			err := cmd.Run() // will wait for command to return

			if err != nil {
				fmt.Println("Error:", err.Error())
			}

		}
	}()

}

func listContainers(w http.ResponseWriter, r *http.Request) {

	// answer right away to avoid dead locks in LUA
	io.WriteString(w, "OK")

	go func() {
		containers, err := DOCKER_CLIENT.ListContainers(true, false, "")

		if err != nil {
			fmt.Println(err.Error())
			return
		}

		images, err := DOCKER_CLIENT.ListImages()

		if err != nil {
			fmt.Println(err.Error())
			return
		}

		client := &http.Client{}

		for i := 0; i < len(containers); i++ {

			id := containers[i].Id
			info, _ := DOCKER_CLIENT.InspectContainer(id)
			name := info.Name[1:]
			imageRepo := ""
			imageTag := ""

			for _, image := range images {
				if image.Id == info.Image {
					if len(image.RepoTags) > 0 {
						imageRepo, imageTag = splitRepoAndTag(image.RepoTags[0])
					}
					break
				}
			}

			data := url.Values{
				"action":    {"containerInfos"},
				"id":        {id},
				"name":      {name},
				"imageRepo": {imageRepo},
				"imageTag":  {imageTag},
				"running":   {strconv.FormatBool(info.State.Running)},
			}

			MCServerRequest(data, client)

			if info.State.Running {
				// Monitor stats
				DOCKER_CLIENT.StartMonitorStats(id, statCallback, nil)
			}
		}
	}()
}

func calculateCPUPercent(previousCPUStats *CPUStats, newCPUStats *dockerclient.CpuStats) float64 {
	var (
		cpuPercent = 0.0
		// calculate the change for the cpu usage of the container in between readings
		cpuDelta = float64(newCPUStats.CpuUsage.TotalUsage - previousCPUStats.TotalUsage)
		// calculate the change for the entire system between readings
		systemDelta = float64(newCPUStats.SystemUsage - previousCPUStats.SystemUsage)
	)

	if systemDelta > 0.0 && cpuDelta > 0.0 {
		cpuPercent = (cpuDelta / systemDelta) * float64(len(newCPUStats.CpuUsage.PercpuUsage)) * 100.0
	}
	return cpuPercent
}

type CPUStats struct {
	TotalUsage  uint64
	SystemUsage uint64
}

var previousCPUStats map[string]*CPUStats

func main() {

	previousCPUStats = make(map[string]*CPUStats)

	if len(os.Args) > 1 {

		// If there's an argument
		// It will be considered as a path for an HTTP GET request
		// That's a way to communicate with goproxy daemon

		if len(os.Args) == 2 {
			reqPath := "http://127.0.0.1:8000/" + os.Args[1]

			resp, err := http.Get(reqPath)
			if err != nil {
				fmt.Println("Error on request:", reqPath, "ERROR:", err.Error())
			} else {
				fmt.Println("Request sent", reqPath, "StatusCode:", resp.StatusCode)
			}
		}

		// os.exec in lua will block the script execution
		// it's better to do it in goproxy
		// in lua: `os.exec("goproxy exec PLAYER_NAME docker run...)`
		if len(os.Args) >= 4 && os.Args[1] == "exec" {

			reqPath := "http://127.0.0.1:8000/exec?cmd=" + strings.Join(os.Args[3:], "+")

			resp, err := http.Get(reqPath)
			if err != nil {
				fmt.Println("Error on request:", reqPath, "ERROR:", err.Error())
			} else {
				fmt.Println("Request sent", reqPath, "StatusCode:", resp.StatusCode)
			}
		}

		return
	}

	// Init the client
	DOCKER_CLIENT, _ = dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil)

	// Monitor events
	DOCKER_CLIENT.StartMonitorEvents(eventCallback, nil)

	go func() {
		http.HandleFunc("/containers", listContainers)
		http.HandleFunc("/exec", execCmd)
		http.ListenAndServe(":8000", nil)
	}()

	// wait for interruption
	<-make(chan int)
}

func splitRepoAndTag(repoTag string) (string, string) {

	repo := ""
	tag := ""

	repoAndTag := strings.Split(repoTag, ":")

	if len(repoAndTag) > 0 {
		repo = repoAndTag[0]
	}

	if len(repoAndTag) > 1 {
		tag = repoAndTag[1]
	}

	return repo, tag
}

// MCServerRequest send a POST request that will be handled
// by our MCServer Docker plugin.
func MCServerRequest(data url.Values, client *http.Client) {

	if client == nil {
		client = &http.Client{}
	}

	req, _ := http.NewRequest("POST", "http://127.0.0.1:8080/webadmin/Docker/Docker", strings.NewReader(data.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.SetBasicAuth("admin", "admin")
	client.Do(req)
}
<commit_msg>Some events don't need to be transmitted<commit_after>package main

import (
	"fmt"
	"github.com/samalba/dockerclient"
	"io"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

var DOCKER_CLIENT *dockerclient.DockerClient

// Callback used to listen to Docker's events
func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) {

	fmt.Println("---")
	fmt.Printf("%+v\n", *event)

	client := &http.Client{}

	id := event.Id

	switch event.Status {
	case "create":
		fmt.Println("create event")

		repo, tag := splitRepoAndTag(event.From)

		containerName := "<name>"

		containerInfo, err := DOCKER_CLIENT.InspectContainer(id)

		if err != nil {
			fmt.Print("InspectContainer error:", err.Error())
		} else {
			containerName = containerInfo.Name
		}

		data := url.Values{
			"action":    {"createContainer"},
			"id":        {id},
			"name":      {containerName},
			"imageRepo": {repo},
			"imageTag":  {tag}}

		MCServerRequest(data, client)

	case "start":
		fmt.Println("start event")

		repo, tag := splitRepoAndTag(event.From)

		containerName := "<name>"

		containerInfo, err := DOCKER_CLIENT.InspectContainer(id)

		if err != nil {
			fmt.Print("InspectContainer error:", err.Error())
		} else {
			containerName = containerInfo.Name
		}

		data := url.Values{
			"action":    {"startContainer"},
			"id":        {id},
			"name":      {containerName},
			"imageRepo": {repo},
			"imageTag":  {tag}}

		// Monitor stats
		DOCKER_CLIENT.StartMonitorStats(id, statCallback, nil)

		MCServerRequest(data, client)

	case "stop":
		// die event is enough
		// http://docs.docker.com/reference/api/docker_remote_api/#docker-events

	case "restart":
		// start event is enough
		// http://docs.docker.com/reference/api/docker_remote_api/#docker-events

	case "kill":
		// die event is enough
		// http://docs.docker.com/reference/api/docker_remote_api/#docker-events

	case "die":
		fmt.Println("die event")
		// same as stop event
		repo, tag := splitRepoAndTag(event.From)

		containerName := "<name>"

		containerInfo, err := DOCKER_CLIENT.InspectContainer(id)

		if err != nil {
			fmt.Print("InspectContainer error:", err.Error())
		} else {
			containerName = containerInfo.Name
		}

		data := url.Values{
			"action":    {"stopContainer"},
			"id":        {id},
			"name":      {containerName},
			"imageRepo": {repo},
			"imageTag":  {tag}}

		MCServerRequest(data, client)

	case "destroy":
		fmt.Println("destroy event")

		data := url.Values{
			"action": {"destroyContainer"},
			"id":     {id},
		}

		MCServerRequest(data, client)
	}
}

func statCallback(id string, stat *dockerclient.Stats, ec chan error, args ...interface{}) {

	//fmt.Println("STATS", id, stat)

	// fmt.Println("---")
	// fmt.Println("cpu :", float64(stat.CpuStats.CpuUsage.TotalUsage)/float64(stat.CpuStats.SystemUsage))
	// fmt.Println("ram :", stat.MemoryStats.Usage)

	client := &http.Client{}

	memPercent := float64(stat.MemoryStats.Usage) / float64(stat.MemoryStats.Limit) * 100.0

	var cpuPercent float64 = 0.0

	if preCPUStats, exists := previousCPUStats[id]; exists {

		cpuPercent = calculateCPUPercent(preCPUStats, &stat.CpuStats)

	}

	previousCPUStats[id] = &CPUStats{TotalUsage: stat.CpuStats.CpuUsage.TotalUsage, SystemUsage: stat.CpuStats.SystemUsage}

	data := url.Values{
		"action": {"stats"},
		"id":     {id},
		"cpu":    {strconv.FormatFloat(cpuPercent, 'f', 2, 64) + "%"},
		"ram":    {strconv.FormatFloat(memPercent, 'f', 2, 64) + "%"}}

	MCServerRequest(data, client)
}

func execCmd(w http.ResponseWriter, r *http.Request) {

	fmt.Println("*** execCmd (1)")

	io.WriteString(w, "OK")

	go func() {

		fmt.Println("*** execCmd")

		cmd := r.URL.Query().Get("cmd")

		fmt.Println("*** cmd:", cmd)

		cmd, _ = url.QueryUnescape(cmd)

		fmt.Println("*** cmd (unescape):", cmd)

		arr := strings.Split(cmd, " ")

		fmt.Println("*** arr:", arr)

		if len(arr) > 0 {
			cmd := exec.Command(arr[0], arr[1:]...)

			// Stdout buffer
			// cmdOutput := &bytes.Buffer{}
			// Attach buffer to command
			// cmd.Stdout = cmdOutput

			// Execute command
			// printCommand(cmd)
			err := cmd.Run() // will wait for command to return

			if err != nil {
				fmt.Println("Error:", err.Error())
			}

		}
	}()

}

func listContainers(w http.ResponseWriter, r *http.Request) {

	// answer right away to avoid dead locks in LUA
	io.WriteString(w, "OK")

	go func() {
		containers, err := DOCKER_CLIENT.ListContainers(true, false, "")

		if err != nil {
			fmt.Println(err.Error())
			return
		}

		images, err := DOCKER_CLIENT.ListImages()

		if err != nil {
			fmt.Println(err.Error())
			return
		}

		client := &http.Client{}

		for i := 0; i < len(containers); i++ {

			id := containers[i].Id
			info, _ := DOCKER_CLIENT.InspectContainer(id)
			name := info.Name[1:]
			imageRepo := ""
			imageTag := ""

			for _, image := range images {
				if image.Id == info.Image {
					if len(image.RepoTags) > 0 {
						imageRepo, imageTag = splitRepoAndTag(image.RepoTags[0])
					}
					break
				}
			}

			data := url.Values{
				"action":    {"containerInfos"},
				"id":        {id},
				"name":      {name},
				"imageRepo": {imageRepo},
				"imageTag":  {imageTag},
				"running":   {strconv.FormatBool(info.State.Running)},
			}

			MCServerRequest(data, client)

			if info.State.Running {
				// Monitor stats
				DOCKER_CLIENT.StartMonitorStats(id, statCallback, nil)
			}
		}
	}()
}

func calculateCPUPercent(previousCPUStats *CPUStats, newCPUStats *dockerclient.CpuStats) float64 {
	var (
		cpuPercent = 0.0
		// calculate the change for the cpu usage of the container in between readings
		cpuDelta = float64(newCPUStats.CpuUsage.TotalUsage - previousCPUStats.TotalUsage)
		// calculate the change for the entire system between readings
		systemDelta = float64(newCPUStats.SystemUsage - previousCPUStats.SystemUsage)
	)

	if systemDelta > 0.0 && cpuDelta > 0.0 {
		cpuPercent = (cpuDelta / systemDelta) * float64(len(newCPUStats.CpuUsage.PercpuUsage)) * 100.0
	}
	return cpuPercent
}

type CPUStats struct {
	TotalUsage  uint64
	SystemUsage uint64
}

var previousCPUStats map[string]*CPUStats

func main() {

	previousCPUStats = make(map[string]*CPUStats)

	if len(os.Args) > 1 {

		// If there's an argument
		// It will be considered as a path for an HTTP GET request
		// That's a way to communicate with goproxy daemon

		if len(os.Args) == 2 {
			reqPath := "http://127.0.0.1:8000/" + os.Args[1]

			resp, err := http.Get(reqPath)
			if err != nil {
				fmt.Println("Error on request:", reqPath, "ERROR:", err.Error())
			} else {
				fmt.Println("Request sent", reqPath, "StatusCode:", resp.StatusCode)
			}
		}

		// os.exec in lua will block the script execution
		// it's better to do it in goproxy
		// in lua: `os.exec("goproxy exec PLAYER_NAME docker run...)`
		if len(os.Args) >= 4 && os.Args[1] == "exec" {

			reqPath := "http://127.0.0.1:8000/exec?cmd=" + strings.Join(os.Args[3:], "+")

			resp, err := http.Get(reqPath)
			if err != nil {
				fmt.Println("Error on request:", reqPath, "ERROR:", err.Error())
			} else {
				fmt.Println("Request sent", reqPath, "StatusCode:", resp.StatusCode)
			}
		}

		return
	}

	// Init the client
	DOCKER_CLIENT, _ = dockerclient.NewDockerClient("unix:///var/run/docker.sock", nil)

	// Monitor events
	DOCKER_CLIENT.StartMonitorEvents(eventCallback, nil)

	go func() {
		http.HandleFunc("/containers", listContainers)
		http.HandleFunc("/exec", execCmd)
		http.ListenAndServe(":8000", nil)
	}()

	// wait for interruption
	<-make(chan int)
}

func splitRepoAndTag(repoTag string) (string, string) {

	repo := ""
	tag := ""

	repoAndTag := strings.Split(repoTag, ":")

	if len(repoAndTag) > 0 {
		repo = repoAndTag[0]
	}

	if len(repoAndTag) > 1 {
		tag = repoAndTag[1]
	}

	return repo, tag
}

// MCServerRequest send a POST request that will be handled
// by our MCServer Docker plugin.
func MCServerRequest(data url.Values, client *http.Client) {

	if client == nil {
		client = &http.Client{}
	}

	req, _ := http.NewRequest("POST", "http://127.0.0.1:8080/webadmin/Docker/Docker", strings.NewReader(data.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.SetBasicAuth("admin", "admin")
	client.Do(req)
}
<|endoftext|>


<commit_before>package model

type TradingDAO interface {
	GetListByUser(userId string) ([]*Trading, error)
	GetById(id, userId string) (*Trading, error)
	Create(date, companyId, subject string, titleType int, workFrom, workTo, quotationDate, billDate int64, taxRate float32, assignee, product string) (*Trading, error)
	Update(id, companyId, subject string, titleType int, workFrom, workTo, quotationDate, billDate int64, taxRate float32, assignee, product string) (*Trading, error)

	// Gets all trading items by trading ID
	GetItemsById(tradingId string) ([]*TradingItem, error)
	CreateItem(tradingId, subject, degree, memo string, sortOrder, unitPrice, Amount, taxType int) (*TradingItem, error)
	// Updates specified trading Item
	UpdateItem(id, tradingId, subject, degree, memo string, sortOrder, unitPrice, Amount, taxType int) (*TradingItem, error)

	// Deletes specified trading Item
	SoftDeleteItem(id, tradingId string) error
}

type Trading struct {
	Id            string
	CompanyId     string
	Subject       string
	TitleType     int
	WorkFrom      int64
	WorkTo        int64
	QuotationDate int64
	BillDate      int64
	TaxRate       float32
	AssigneeId    string
	Product       string
	CreatedTime   int64
	ModifiedTime  int64
}

type TradingItem struct {
	Id        string
	TradingId string
	SortOrder int
	Subject   string
	UnitPrice int
	Amount    int
	Degree    string
	TaxType   int
	Memo      string
}
<commit_msg>Add fields<commit_after>package model

type TradingDAO interface {
	GetListByUser(userId string) ([]*Trading, error)
	GetById(id, userId string) (*Trading, error)
	Create(date, companyId, subject string, titleType int, workFrom, workTo, quotationDate, billDate int64, taxRate float32, assignee, product string) (*Trading, error)
	Update(id, companyId, subject string, titleType int, workFrom, workTo, quotationDate, billDate int64, taxRate float32, assignee, product string) (*Trading, error)

	// Gets all trading items by trading ID
	GetItemsById(tradingId string) ([]*TradingItem, error)
	CreateItem(tradingId, subject, degree, memo string, sortOrder, unitPrice, Amount, taxType int) (*TradingItem, error)
	// Updates specified trading Item
	UpdateItem(id, tradingId, subject, degree, memo string, sortOrder, unitPrice, Amount, taxType int) (*TradingItem, error)

	// Deletes specified trading Item
	SoftDeleteItem(id, tradingId string) error
}

type Trading struct {
	Id              string
	CompanyId       string
	Subject         string
	TitleType       int
	WorkFrom        int64
	WorkTo          int64
	Total           int64
	QuotationDate   int64
	QuotationNumber string
	BillDate        int64
	BillNumber      string
	TaxRate         float32
	AssigneeId      string
	Product         string
	CreatedTime     int64
	ModifiedTime    int64
}

type TradingItem struct {
	Id        string
	TradingId string
	SortOrder int
	Subject   string
	UnitPrice int
	Amount    int
	Degree    string
	TaxType   int
	Memo      string
}
<|endoftext|>


<commit_before>package store

import (
	"encoding/gob"
	"io"
	"log"
	"os"
	"strconv"
	"sync"
)

type URLStore struct {
	urls map[string]string
	lock sync.RWMutex
	file *os.File
}

type record struct {
	Key, Url string
}

func NewURLStore(filename string) *URLStore {
	s := &URLStore{urls: make(map[string]string, 100)}
	f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644)
	if err != nil {
		log.Fatal(err)
	}
	s.file = f
	if s.load() != nil {
		log.Fatal("Load Error:", err)
	}
	return s
}

func (u *URLStore) load() error {
	if _, err := u.file.Seek(0, 0); err != nil {
		return err
	}
	de := gob.NewDecoder(u.file)
	var err error
	for err != io.EOF {
		var r record
		if err = de.Decode(&r); err == nil {
			u.set(r.Key, r.Url)
		}
	}
	return nil
}

func (u *URLStore) Get(key string) string {
	u.lock.RLock()
	defer u.lock.RUnlock()
	return u.urls[key]
}

func (u *URLStore) set(key, url string) bool {
	u.lock.Lock()
	defer u.lock.Unlock()
	if _, present := u.urls[key]; present {
		return false
	}
	u.urls[key] = url
	return true
}

func (u *URLStore) Count() int {
	u.lock.RLock()
	defer u.lock.RUnlock()
	return len(u.urls)
}

func (u *URLStore) Put(url string) string {
	for {
		key := genKey(u.Count())
		if u.set(key, url) {
			if err := u.save(key, url); err != nil {
				log.Fatal("save url error:", err)
			}
			return key
		}
		return ""
	}
}

func (u *URLStore) save(key, url string) error {
	e := gob.NewEncoder(u.file)
	return e.Encode(record{key, url})
}

func genKey(v int) string {
	return strconv.Itoa(v) + "a"
}
<commit_msg>Using goroutines for performance<commit_after>package store

import (
	"encoding/gob"
	"io"
	"log"
	"os"
	"strconv"
	"sync"
)

type URLStore struct {
	urls map[string]string
	lock sync.RWMutex
	ch   chan record
}

type record struct {
	Key, Url string
}

func NewURLStore(filename string) *URLStore {
	s := &URLStore{
		urls: make(map[string]string, 100),
		ch:   make(chan record, 1000),
	}

	if err := s.load(filename); err != nil {
		log.Fatal("Load Error:", err)
	}

	go s.saveLoop(filename)
	return s
}

func (u *URLStore) load(filename string) error {
	file, err := os.OpenFile(filename, os.O_RDONLY|os.O_CREATE, 0644)
	if err != nil {
		return err
	}
	defer file.Close()
	if _, err := file.Seek(0, 0); err != nil {
		return err
	}
	de := gob.NewDecoder(file)
	for err != io.EOF {
		var r record
		if err = de.Decode(&r); err == nil {
			u.set(r.Key, r.Url)
		}
	}
	return nil
}

func (u *URLStore) Get(key string) string {
	u.lock.RLock()
	defer u.lock.RUnlock()
	return u.urls[key]
}

func (u *URLStore) set(key, url string) bool {
	u.lock.Lock()
	defer u.lock.Unlock()
	if _, present := u.urls[key]; present {
		return false
	}
	u.urls[key] = url
	return true
}

func (u *URLStore) Count() int {
	u.lock.RLock()
	defer u.lock.RUnlock()
	return len(u.urls)
}

func (u *URLStore) Put(url string) string {
	for {
		key := genKey(u.Count())
		if u.set(key, url) {
			u.ch <- record{key, url}
			return key
		}
		return ""
	}
}

func (u *URLStore) saveLoop(filename string) {
	var file *os.File
	var err error
	file, err = os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatal("URLStore:", err)
	}
	defer file.Close()

	encode := gob.NewEncoder(file)
	for {
		r := <-u.ch
		if err = encode.Encode(r); err != nil {
			log.Println("URLStore:", err)
		}
	}
}

func genKey(v int) string {
	return strconv.Itoa(v) + "a"
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"encoding/binary"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"regexp"
	"runtime"
	"time"

	"github.com/lomik/graphite-clickhouse/autocomplete"
	"github.com/lomik/graphite-clickhouse/config"
	"github.com/lomik/graphite-clickhouse/find"
	"github.com/lomik/graphite-clickhouse/helper/version"
	"github.com/lomik/graphite-clickhouse/index"
	"github.com/lomik/graphite-clickhouse/prometheus"
	"github.com/lomik/graphite-clickhouse/render"
	"github.com/lomik/graphite-clickhouse/tagger"
	"github.com/lomik/zapwriter"
	"go.uber.org/zap"

	_ "net/http/pprof"
)

// Version of graphite-clickhouse
const Version = "0.11.2"

func init() {
	version.Version = Version
}

type LogResponseWriter struct {
	http.ResponseWriter
	status int
}

func (w *LogResponseWriter) WriteHeader(status int) {
	w.status = status
	w.ResponseWriter.WriteHeader(status)
}

func (w *LogResponseWriter) Status() int {
	if w.status == 0 {
		return http.StatusOK
	}
	return w.status
}

func WrapResponseWriter(w http.ResponseWriter) *LogResponseWriter {
	if wrapped, ok := w.(*LogResponseWriter); ok {
		return wrapped
	}
	return &LogResponseWriter{ResponseWriter: w}
}

var requestIdRegexp *regexp.Regexp = regexp.MustCompile("^[a-zA-Z0-9_.-]+$")

func Handler(logger *zap.Logger, handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		writer := WrapResponseWriter(w)

		requestID := r.Header.Get("X-Request-Id")
		if requestID == "" || !requestIdRegexp.MatchString(requestID) {
			var b [16]byte
			binary.LittleEndian.PutUint64(b[:], rand.Uint64())
			binary.LittleEndian.PutUint64(b[8:], rand.Uint64())
			requestID = fmt.Sprintf("%x", b)
		}

		logger := logger.With(zap.String("request_id", requestID))

		r = r.WithContext(
			context.WithValue(
				context.WithValue(
					r.Context(),
					"logger",
					logger,
				),
				"requestID",
				requestID,
			),
		)

		start := time.Now()
		handler.ServeHTTP(writer, r)
		d := time.Since(start)
		logger.Info("access",
			zap.Duration("time", d),
			zap.String("method", r.Method),
			zap.String("url", r.URL.String()),
			zap.String("peer", r.RemoteAddr),
			zap.Int("status", writer.Status()),
		)
	})
}

func main() {
	rand.Seed(time.Now().UnixNano())

	var err error

	/* CONFIG start */

	configFile := flag.String("config", "/etc/graphite-clickhouse/graphite-clickhouse.conf", "Filename of config")
	printDefaultConfig := flag.Bool("config-print-default", false, "Print default config")
	checkConfig := flag.Bool("check-config", false, "Check config and exit")
	buildTags := flag.Bool("tags", false, "Build tags table")
	pprof := flag.String("pprof", "", "Additional pprof listen addr for non-server modes (tagger, etc..)")

	printVersion := flag.Bool("version", false, "Print version")

	flag.Parse()

	if *printVersion {
		fmt.Print(Version)
		return
	}

	if *printDefaultConfig {
		if err = config.PrintDefaultConfig(); err != nil {
			log.Fatal(err)
		}
		return
	}

	cfg, err := config.ReadConfig(*configFile)
	if err != nil {
		log.Fatal(err)
	}

	// config parsed successfully. Exit in check-only mode
	if *checkConfig {
		return
	}

	if err = zapwriter.ApplyConfig(cfg.Logging); err != nil {
		log.Fatal(err)
	}

	runtime.GOMAXPROCS(cfg.Common.MaxCPU)

	/* CONFIG end */

	if pprof != nil && *pprof != "" {
		go func() { log.Fatal(http.ListenAndServe(*pprof, nil)) }()
	}

	/* CONSOLE COMMANDS start */
	if *buildTags {
		if err := tagger.Make(cfg); err != nil {
			log.Fatal(err)
		}
		return
	}

	/* CONSOLE COMMANDS end */

	http.Handle("/metrics/find/", Handler(zapwriter.Default(), find.NewHandler(cfg)))
	http.Handle("/metrics/index.json", Handler(zapwriter.Default(), index.NewHandler(cfg)))
	http.Handle("/render/", Handler(zapwriter.Default(), render.NewHandler(cfg)))
	http.Handle("/tags/autoComplete/tags", Handler(zapwriter.Default(), autocomplete.NewTags(cfg)))
	http.Handle("/tags/autoComplete/values", Handler(zapwriter.Default(), autocomplete.NewValues(cfg)))
	http.HandleFunc("/debug/config", func(w http.ResponseWriter, r *http.Request) {
		b, err := json.MarshalIndent(cfg, "", "  ")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(b)
	})

	http.Handle("/", Handler(zapwriter.Default(), prometheus.NewHandler(cfg)))

	log.Fatal(http.ListenAndServe(cfg.Common.Listen, nil))
}
<commit_msg>version 0.11.3<commit_after>package main

import (
	"context"
	"encoding/binary"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"regexp"
	"runtime"
	"time"

	"github.com/lomik/graphite-clickhouse/autocomplete"
	"github.com/lomik/graphite-clickhouse/config"
	"github.com/lomik/graphite-clickhouse/find"
	"github.com/lomik/graphite-clickhouse/helper/version"
	"github.com/lomik/graphite-clickhouse/index"
	"github.com/lomik/graphite-clickhouse/prometheus"
	"github.com/lomik/graphite-clickhouse/render"
	"github.com/lomik/graphite-clickhouse/tagger"
	"github.com/lomik/zapwriter"
	"go.uber.org/zap"

	_ "net/http/pprof"
)

// Version of graphite-clickhouse
const Version = "0.11.3"

func init() {
	version.Version = Version
}

type LogResponseWriter struct {
	http.ResponseWriter
	status int
}

func (w *LogResponseWriter) WriteHeader(status int) {
	w.status = status
	w.ResponseWriter.WriteHeader(status)
}

func (w *LogResponseWriter) Status() int {
	if w.status == 0 {
		return http.StatusOK
	}
	return w.status
}

func WrapResponseWriter(w http.ResponseWriter) *LogResponseWriter {
	if wrapped, ok := w.(*LogResponseWriter); ok {
		return wrapped
	}
	return &LogResponseWriter{ResponseWriter: w}
}

var requestIdRegexp *regexp.Regexp = regexp.MustCompile("^[a-zA-Z0-9_.-]+$")

func Handler(logger *zap.Logger, handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		writer := WrapResponseWriter(w)

		requestID := r.Header.Get("X-Request-Id")
		if requestID == "" || !requestIdRegexp.MatchString(requestID) {
			var b [16]byte
			binary.LittleEndian.PutUint64(b[:], rand.Uint64())
			binary.LittleEndian.PutUint64(b[8:], rand.Uint64())
			requestID = fmt.Sprintf("%x", b)
		}

		logger := logger.With(zap.String("request_id", requestID))

		r = r.WithContext(
			context.WithValue(
				context.WithValue(
					r.Context(),
					"logger",
					logger,
				),
				"requestID",
				requestID,
			),
		)

		start := time.Now()
		handler.ServeHTTP(writer, r)
		d := time.Since(start)
		logger.Info("access",
			zap.Duration("time", d),
			zap.String("method", r.Method),
			zap.String("url", r.URL.String()),
			zap.String("peer", r.RemoteAddr),
			zap.Int("status", writer.Status()),
		)
	})
}

func main() {
	rand.Seed(time.Now().UnixNano())

	var err error

	/* CONFIG start */

	configFile := flag.String("config", "/etc/graphite-clickhouse/graphite-clickhouse.conf", "Filename of config")
	printDefaultConfig := flag.Bool("config-print-default", false, "Print default config")
	checkConfig := flag.Bool("check-config", false, "Check config and exit")
	buildTags := flag.Bool("tags", false, "Build tags table")
	pprof := flag.String("pprof", "", "Additional pprof listen addr for non-server modes (tagger, etc..)")

	printVersion := flag.Bool("version", false, "Print version")

	flag.Parse()

	if *printVersion {
		fmt.Print(Version)
		return
	}

	if *printDefaultConfig {
		if err = config.PrintDefaultConfig(); err != nil {
			log.Fatal(err)
		}
		return
	}

	cfg, err := config.ReadConfig(*configFile)
	if err != nil {
		log.Fatal(err)
	}

	// config parsed successfully. Exit in check-only mode
	if *checkConfig {
		return
	}

	if err = zapwriter.ApplyConfig(cfg.Logging); err != nil {
		log.Fatal(err)
	}

	runtime.GOMAXPROCS(cfg.Common.MaxCPU)

	/* CONFIG end */

	if pprof != nil && *pprof != "" {
		go func() { log.Fatal(http.ListenAndServe(*pprof, nil)) }()
	}

	/* CONSOLE COMMANDS start */
	if *buildTags {
		if err := tagger.Make(cfg); err != nil {
			log.Fatal(err)
		}
		return
	}

	/* CONSOLE COMMANDS end */

	http.Handle("/metrics/find/", Handler(zapwriter.Default(), find.NewHandler(cfg)))
	http.Handle("/metrics/index.json", Handler(zapwriter.Default(), index.NewHandler(cfg)))
	http.Handle("/render/", Handler(zapwriter.Default(), render.NewHandler(cfg)))
	http.Handle("/tags/autoComplete/tags", Handler(zapwriter.Default(), autocomplete.NewTags(cfg)))
	http.Handle("/tags/autoComplete/values", Handler(zapwriter.Default(), autocomplete.NewValues(cfg)))
	http.HandleFunc("/debug/config", func(w http.ResponseWriter, r *http.Request) {
		b, err := json.MarshalIndent(cfg, "", "  ")
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(b)
	})

	http.Handle("/", Handler(zapwriter.Default(), prometheus.NewHandler(cfg)))

	log.Fatal(http.ListenAndServe(cfg.Common.Listen, nil))
}
<|endoftext|>


<commit_before>package treeprint

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestOneNode(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddNode("hello")
	actual := tree.String()
	expected := `.
└── hello
`
	assert.Equal(expected, actual)
}

func TestMetaNode(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddMetaNode(123, "hello")
	tree.AddMetaNode([]struct{}{}, "world")
	actual := tree.String()
	expected := `.
├── [123]  hello
└── [[]]  world
`
	assert.Equal(expected, actual)
}

func TestTwoNodes(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddNode("hello")
	tree.AddNode("world")
	actual := tree.String()
	expected := `.
├── hello
└── world
`
	assert.Equal(expected, actual)
}

func TestLevel(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddBranch("hello").AddNode("my friend").AddNode("lol")
	tree.AddNode("world")
	actual := tree.String()
	expected := `.
├── hello
│   ├── my friend
│   └── lol
└── world
`
	assert.Equal(expected, actual)
}

func TestDeepLevel(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	one := tree.AddBranch("one")
	one.AddNode("subnode1").AddNode("subnode2")
	one.AddBranch("two").
		AddNode("subnode1").AddNode("subnode2").
		AddBranch("three").
		AddNode("subnode1").AddNode("subnode2")
	one.AddNode("subnode3")
	tree.AddNode("outernode")

	actual := tree.String()
	expected := `.
├── one
│   ├── subnode1
│   ├── subnode2
│   ├── two
│   │   ├── subnode1
│   │   ├── subnode2
│   │   └── three
│   │       ├── subnode1
│   │       └── subnode2
│   └── subnode3
└── outernode
`
	assert.Equal(expected, actual)
}

func TestComplex(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddNode("Dockerfile")
	tree.AddNode("Makefile")
	tree.AddNode("aws.sh")
	tree.AddMetaBranch(" 204", "bin").
		AddNode("dbmaker").AddNode("someserver").AddNode("testtool")
	tree.AddMetaBranch(" 374", "deploy").
		AddNode("Makefile").AddNode("bootstrap.sh")
	tree.AddMetaNode("122K", "testtool.a")

	actual := tree.String()
	expected := `.
├── Dockerfile
├── Makefile
├── aws.sh
├── [ 204]  bin
│   ├── dbmaker
│   ├── someserver
│   └── testtool
├── [ 374]  deploy
│   ├── Makefile
│   └── bootstrap.sh
└── [122K]  testtool.a
`
	assert.Equal(expected, actual)
}

func TestIndirectOrder(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddBranch("one").AddNode("two")
	foo := tree.AddBranch("foo")
	foo.AddBranch("bar").AddNode("a").AddNode("b").AddNode("c")
	foo.AddNode("end")

	actual := tree.String()
	expected := `.
├── one
│   └── two
└── foo
    ├── bar
    │   ├── a
    │   ├── b
    │   └── c
    └── end
`
	assert.Equal(expected, actual)
}
<commit_msg>test for a named root<commit_after>package treeprint

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestOneNode(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddNode("hello")
	actual := tree.String()
	expected := `.
└── hello
`
	assert.Equal(expected, actual)
}

func TestMetaNode(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddMetaNode(123, "hello")
	tree.AddMetaNode([]struct{}{}, "world")
	actual := tree.String()
	expected := `.
├── [123]  hello
└── [[]]  world
`
	assert.Equal(expected, actual)
}

func TestTwoNodes(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddNode("hello")
	tree.AddNode("world")
	actual := tree.String()
	expected := `.
├── hello
└── world
`
	assert.Equal(expected, actual)
}

func TestLevel(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddBranch("hello").AddNode("my friend").AddNode("lol")
	tree.AddNode("world")
	actual := tree.String()
	expected := `.
├── hello
│   ├── my friend
│   └── lol
└── world
`
	assert.Equal(expected, actual)
}

func TestNamedRoot(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddBranch("hello").AddNode("my friend").AddNode("lol")
	tree.AddNode("world")
	tree.SetValue("friends")
	actual := tree.String()
	expected := `friends
├── hello
│   ├── my friend
│   └── lol
└── world
`
	assert.Equal(expected, actual)
}

func TestDeepLevel(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	one := tree.AddBranch("one")
	one.AddNode("subnode1").AddNode("subnode2")
	one.AddBranch("two").
		AddNode("subnode1").AddNode("subnode2").
		AddBranch("three").
		AddNode("subnode1").AddNode("subnode2")
	one.AddNode("subnode3")
	tree.AddNode("outernode")

	actual := tree.String()
	expected := `.
├── one
│   ├── subnode1
│   ├── subnode2
│   ├── two
│   │   ├── subnode1
│   │   ├── subnode2
│   │   └── three
│   │       ├── subnode1
│   │       └── subnode2
│   └── subnode3
└── outernode
`
	assert.Equal(expected, actual)
}

func TestComplex(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddNode("Dockerfile")
	tree.AddNode("Makefile")
	tree.AddNode("aws.sh")
	tree.AddMetaBranch(" 204", "bin").
		AddNode("dbmaker").AddNode("someserver").AddNode("testtool")
	tree.AddMetaBranch(" 374", "deploy").
		AddNode("Makefile").AddNode("bootstrap.sh")
	tree.AddMetaNode("122K", "testtool.a")

	actual := tree.String()
	expected := `.
├── Dockerfile
├── Makefile
├── aws.sh
├── [ 204]  bin
│   ├── dbmaker
│   ├── someserver
│   └── testtool
├── [ 374]  deploy
│   ├── Makefile
│   └── bootstrap.sh
└── [122K]  testtool.a
`
	assert.Equal(expected, actual)
}

func TestIndirectOrder(t *testing.T) {
	assert := assert.New(t)

	tree := New()
	tree.AddBranch("one").AddNode("two")
	foo := tree.AddBranch("foo")
	foo.AddBranch("bar").AddNode("a").AddNode("b").AddNode("c")
	foo.AddNode("end")

	actual := tree.String()
	expected := `.
├── one
│   └── two
└── foo
    ├── bar
    │   ├── a
    │   ├── b
    │   └── c
    └── end
`
	assert.Equal(expected, actual)
}
<|endoftext|>


<commit_before>// This is free and unencumbered software released into the public
// domain.  For more information, see <http://unlicense.org> or the
// accompanying UNLICENSE file.

package editor

import (
	"fmt"
	"log"

	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/nelsam/gxui"
	"github.com/nelsam/gxui/math"
	"github.com/nelsam/gxui/mixins"
	"github.com/nelsam/gxui/mixins/outer"
	"github.com/nelsam/gxui/themes/basic"
	"github.com/nelsam/vidar/theme"
)

var (
	splitterBG = gxui.Color{
		R: .05,
		G: .05,
		B: .05,
		A: 1,
	}
	splitterFG = gxui.Color{
		R: .2,
		G: .2,
		B: .2,
		A: 1,
	}
)

type Orienter interface {
	SetOrientation(gxui.Orientation)
}

type Splitter interface {
	Split(orientation gxui.Orientation)
}

type MultiEditor interface {
	gxui.Control
	outer.LayoutChildren
	Focus()
	Has(hiddenPrefix, path string) bool
	Open(hiddenPrefix, path, headerText string, environ []string) (editor *CodeEditor, existed bool)
	Editors() uint
	CurrentEditor() *CodeEditor
	CurrentFile() string
	CloseCurrentEditor() (name string, editor *CodeEditor)
	Add(name string, editor *CodeEditor)
	SaveAll()
}

type Direction int

const (
	Up Direction = 1 + iota
	Right
	Down
	Left
)

type SplitEditor struct {
	mixins.SplitterLayout

	driver      gxui.Driver
	theme       *basic.Theme
	syntaxTheme theme.Theme
	font        gxui.Font
	window      gxui.Window

	current MultiEditor
}

func NewSplitEditor(driver gxui.Driver, window gxui.Window, theme *basic.Theme, syntaxTheme theme.Theme, font gxui.Font) *SplitEditor {
	editor := &SplitEditor{
		driver:      driver,
		theme:       theme,
		syntaxTheme: syntaxTheme,
		font:        font,
		window:      window,
	}
	editor.SplitterLayout.Init(editor, theme)
	return editor
}

func (e *SplitEditor) Elements() []interface{} {
	return []interface{}{e.current}
}

func (e *SplitEditor) Split(orientation gxui.Orientation) {
	if e.current.Editors() <= 1 {
		return
	}
	if splitter, ok := e.current.(Splitter); ok {
		splitter.Split(orientation)
		return
	}
	name, editor := e.current.CloseCurrentEditor()
	newSplit := NewTabbedEditor(e.driver, e.theme, e.syntaxTheme, e.font)
	defer func() {
		newSplit.Add(name, editor)
		newSplit.Focus()
	}()
	if e.Orientation() == orientation {
		e.AddChild(newSplit)
		return
	}
	newSplitter := NewSplitEditor(e.driver, e.window, e.theme, e.syntaxTheme, e.font)
	newSplitter.SetOrientation(orientation)
	var (
		index       int
		searchChild *gxui.Child
	)
	for index, searchChild = range e.Children() {
		if e.current == searchChild.Control {
			break
		}
	}
	e.RemoveChildAt(index)
	newSplitter.AddChild(e.current)
	e.current = newSplitter
	e.AddChildAt(index, e.current)
	newSplitter.AddChild(newSplit)
}

func (e *SplitEditor) Editors() (count uint) {
	for _, child := range e.Children() {
		editor, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		count += editor.Editors()
	}
	return count
}

func (e *SplitEditor) CloseCurrentEditor() (name string, editor *CodeEditor) {
	name, editor = e.current.CloseCurrentEditor()
	if e.current.Editors() == 0 && len(e.Children()) > 1 {
		e.RemoveChild(e.current)
		e.current = e.Children()[0].Control.(MultiEditor)
		e.current.Focus()
	}
	return name, editor
}

func (e *SplitEditor) Add(name string, editor *CodeEditor) {
	e.current.Add(name, editor)
}

func (e *SplitEditor) AddChild(child gxui.Control) *gxui.Child {
	editor, ok := child.(MultiEditor)
	if !ok {
		panic(fmt.Errorf("SplitEditor: Non-MultiEditor type %T sent to AddChild", child))
	}
	if e.current == nil {
		e.current = editor
	}
	return e.SplitterLayout.AddChild(child)
}

func (e *SplitEditor) Focus() {
	e.current.Focus()
}

func (l *SplitEditor) CreateSplitterBar() gxui.Control {
	b := NewSplitterBar(l.window.Viewport(), l.theme)
	b.SetOrientation(l.Orientation())
	b.OnSplitterDragged(func(wndPnt math.Point) { l.SplitterDragged(b, wndPnt) })
	return b
}

func (e *SplitEditor) SetOrientation(o gxui.Orientation) {
	e.SplitterLayout.SetOrientation(o)
	for _, child := range e.Children() {
		if orienter, ok := child.Control.(Orienter); ok {
			orienter.SetOrientation(o)
		}
	}
}

func (e *SplitEditor) MouseUp(event gxui.MouseEvent) {
	for _, child := range e.Children() {
		offsetPoint := event.Point.AddX(-child.Offset.X).AddY(-child.Offset.Y)
		if !child.Control.ContainsPoint(offsetPoint) {
			continue
		}
		newFocus, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		e.current = newFocus
		e.current.Focus()
		break
	}
	e.SplitterLayout.MouseUp(event)
}

func (e *SplitEditor) Has(hiddenPrefix, path string) bool {
	for _, child := range e.Children() {
		if child.Control.(MultiEditor).Has(hiddenPrefix, path) {
			return true
		}
	}
	return false
}

func (e *SplitEditor) Open(hiddenPrefix, path, headerText string, environ []string) (editor *CodeEditor, existed bool) {
	for _, child := range e.Children() {
		me := child.Control.(MultiEditor)
		if me.Has(hiddenPrefix, path) {
			e.current = me
			return me.Open(hiddenPrefix, path, headerText, environ)
		}
	}
	return e.current.Open(hiddenPrefix, path, headerText, environ)
}

func (e *SplitEditor) CurrentEditor() *CodeEditor {
	return e.current.CurrentEditor()
}

func (e *SplitEditor) CurrentFile() string {
	return e.current.CurrentFile()
}

func (e *SplitEditor) ChildIndex(c gxui.Control) int {
	if c == nil {
		return -1
	}
	for i, child := range e.Children() {
		if child.Control == c {
			return i
		}
	}
	return -1
}

func (e *SplitEditor) NextEditor(direction Direction) *CodeEditor {
	editor, _ := e.nextEditor(direction)
	return editor
}

func (e *SplitEditor) nextEditor(direction Direction) (editor *CodeEditor, wrapped bool) {
	switch direction {
	case Up, Down:
		if e.Orientation().Horizontal() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.nextEditor(direction)
			}
			return nil, false
		}
	case Left, Right:
		if e.Orientation().Vertical() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.nextEditor(direction)
			}
			return nil, false
		}
	}

	if splitter, ok := e.current.(*SplitEditor); ok {
		// Special case - there could be another split editor with our orientation
		// as a child, and *that* is the editor that needs the split moved.
		ed, wrapped := splitter.nextEditor(direction)
		if e != nil && !wrapped {
			return ed, false
		}
	}

	children := e.Children()
	i := children.IndexOf(e.current)
	if i < 0 {
		log.Printf("Error: Current editor is not part of the splitter's layout")
		return nil, false
	}
	switch direction {
	case Up, Left:
		i--
		if i < 0 {
			wrapped = true
			i = len(children) - 1
		}
	case Down, Right:
		i++
		if i == len(children) {
			wrapped = true
			i = 0
		}
	}
	me := children[i].Control.(MultiEditor)
	if splitter, ok := me.(*SplitEditor); ok {
		return splitter.first(direction), wrapped
	}

	return me.CurrentEditor(), wrapped
}

func (e *SplitEditor) first(d Direction) *CodeEditor {
	switch d {
	case Up, Down:
		if e.Orientation().Horizontal() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.first(d)
			}
			return e.current.CurrentEditor()
		}
	case Left, Right:
		if e.Orientation().Vertical() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.first(d)
			}
			return e.current.CurrentEditor()
		}
	}

	var first *gxui.Child
	switch d {
	case Up, Left:
		first = e.Children()[0]
	case Down, Right:
		first = e.Children()[len(e.Children())-1]
	}

	switch src := first.Control.(type) {
	case *SplitEditor:
		return src.first(d)
	case MultiEditor:
		return src.CurrentEditor()
	default:
		log.Printf("Error: first editor is not an editor")
		return nil
	}
}

func (e *SplitEditor) SaveAll() {
	for _, child := range e.Children() {
		editor, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		editor.SaveAll()
	}
}

type SplitterBar struct {
	mixins.SplitterBar
	viewport    gxui.Viewport
	orientation gxui.Orientation

	arrow, horizResize, vertResize *glfw.Cursor
}

func NewSplitterBar(viewport gxui.Viewport, theme gxui.Theme) *SplitterBar {
	s := &SplitterBar{
		viewport:    viewport,
		arrow:       glfw.CreateStandardCursor(glfw.ArrowCursor),
		horizResize: glfw.CreateStandardCursor(glfw.HResizeCursor),
		vertResize:  glfw.CreateStandardCursor(glfw.VResizeCursor),
	}
	s.SplitterBar.Init(s, theme)
	s.SetBackgroundColor(splitterBG)
	s.SetForegroundColor(splitterFG)
	return s
}

func (s *SplitterBar) SetOrientation(o gxui.Orientation) {
	s.orientation = o
}

func (s *SplitterBar) MouseEnter(gxui.MouseEvent) {
	switch s.orientation {
	case gxui.Vertical:
		s.viewport.SetCursor(s.vertResize)
	case gxui.Horizontal:
		s.viewport.SetCursor(s.horizResize)
	}
}

func (s *SplitterBar) MouseExit(gxui.MouseEvent) {
	s.viewport.SetCursor(s.arrow)
}
<commit_msg>Fix type assertion panic when looking for an already-open file<commit_after>// This is free and unencumbered software released into the public
// domain.  For more information, see <http://unlicense.org> or the
// accompanying UNLICENSE file.

package editor

import (
	"fmt"
	"log"

	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/nelsam/gxui"
	"github.com/nelsam/gxui/math"
	"github.com/nelsam/gxui/mixins"
	"github.com/nelsam/gxui/mixins/outer"
	"github.com/nelsam/gxui/themes/basic"
	"github.com/nelsam/vidar/theme"
)

var (
	splitterBG = gxui.Color{
		R: .05,
		G: .05,
		B: .05,
		A: 1,
	}
	splitterFG = gxui.Color{
		R: .2,
		G: .2,
		B: .2,
		A: 1,
	}
)

type Orienter interface {
	SetOrientation(gxui.Orientation)
}

type Splitter interface {
	Split(orientation gxui.Orientation)
}

type MultiEditor interface {
	gxui.Control
	outer.LayoutChildren
	Focus()
	Has(hiddenPrefix, path string) bool
	Open(hiddenPrefix, path, headerText string, environ []string) (editor *CodeEditor, existed bool)
	Editors() uint
	CurrentEditor() *CodeEditor
	CurrentFile() string
	CloseCurrentEditor() (name string, editor *CodeEditor)
	Add(name string, editor *CodeEditor)
	SaveAll()
}

type Direction int

const (
	Up Direction = 1 + iota
	Right
	Down
	Left
)

type SplitEditor struct {
	mixins.SplitterLayout

	driver      gxui.Driver
	theme       *basic.Theme
	syntaxTheme theme.Theme
	font        gxui.Font
	window      gxui.Window

	current MultiEditor
}

func NewSplitEditor(driver gxui.Driver, window gxui.Window, theme *basic.Theme, syntaxTheme theme.Theme, font gxui.Font) *SplitEditor {
	editor := &SplitEditor{
		driver:      driver,
		theme:       theme,
		syntaxTheme: syntaxTheme,
		font:        font,
		window:      window,
	}
	editor.SplitterLayout.Init(editor, theme)
	return editor
}

func (e *SplitEditor) Elements() []interface{} {
	return []interface{}{e.current}
}

func (e *SplitEditor) Split(orientation gxui.Orientation) {
	if e.current.Editors() <= 1 {
		return
	}
	if splitter, ok := e.current.(Splitter); ok {
		splitter.Split(orientation)
		return
	}
	name, editor := e.current.CloseCurrentEditor()
	newSplit := NewTabbedEditor(e.driver, e.theme, e.syntaxTheme, e.font)
	defer func() {
		newSplit.Add(name, editor)
		newSplit.Focus()
	}()
	if e.Orientation() == orientation {
		e.AddChild(newSplit)
		return
	}
	newSplitter := NewSplitEditor(e.driver, e.window, e.theme, e.syntaxTheme, e.font)
	newSplitter.SetOrientation(orientation)
	var (
		index       int
		searchChild *gxui.Child
	)
	for index, searchChild = range e.Children() {
		if e.current == searchChild.Control {
			break
		}
	}
	e.RemoveChildAt(index)
	newSplitter.AddChild(e.current)
	e.current = newSplitter
	e.AddChildAt(index, e.current)
	newSplitter.AddChild(newSplit)
}

func (e *SplitEditor) Editors() (count uint) {
	for _, child := range e.Children() {
		editor, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		count += editor.Editors()
	}
	return count
}

func (e *SplitEditor) CloseCurrentEditor() (name string, editor *CodeEditor) {
	name, editor = e.current.CloseCurrentEditor()
	if e.current.Editors() == 0 && len(e.Children()) > 1 {
		e.RemoveChild(e.current)
		e.current = e.Children()[0].Control.(MultiEditor)
		e.current.Focus()
	}
	return name, editor
}

func (e *SplitEditor) Add(name string, editor *CodeEditor) {
	e.current.Add(name, editor)
}

func (e *SplitEditor) AddChild(child gxui.Control) *gxui.Child {
	editor, ok := child.(MultiEditor)
	if !ok {
		panic(fmt.Errorf("SplitEditor: Non-MultiEditor type %T sent to AddChild", child))
	}
	if e.current == nil {
		e.current = editor
	}
	return e.SplitterLayout.AddChild(child)
}

func (e *SplitEditor) Focus() {
	e.current.Focus()
}

func (l *SplitEditor) CreateSplitterBar() gxui.Control {
	b := NewSplitterBar(l.window.Viewport(), l.theme)
	b.SetOrientation(l.Orientation())
	b.OnSplitterDragged(func(wndPnt math.Point) { l.SplitterDragged(b, wndPnt) })
	return b
}

func (e *SplitEditor) SetOrientation(o gxui.Orientation) {
	e.SplitterLayout.SetOrientation(o)
	for _, child := range e.Children() {
		if orienter, ok := child.Control.(Orienter); ok {
			orienter.SetOrientation(o)
		}
	}
}

func (e *SplitEditor) MouseUp(event gxui.MouseEvent) {
	for _, child := range e.Children() {
		offsetPoint := event.Point.AddX(-child.Offset.X).AddY(-child.Offset.Y)
		if !child.Control.ContainsPoint(offsetPoint) {
			continue
		}
		newFocus, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		e.current = newFocus
		e.current.Focus()
		break
	}
	e.SplitterLayout.MouseUp(event)
}

func (e *SplitEditor) Has(hiddenPrefix, path string) bool {
	for _, child := range e.Children() {
		if child.Control.(MultiEditor).Has(hiddenPrefix, path) {
			return true
		}
	}
	return false
}

func (e *SplitEditor) Open(hiddenPrefix, path, headerText string, environ []string) (editor *CodeEditor, existed bool) {
	for _, child := range e.Children() {
		if me, ok := child.Control.(MultiEditor); ok && me.Has(hiddenPrefix, path) {
			e.current = me
			return me.Open(hiddenPrefix, path, headerText, environ)
		}
	}
	return e.current.Open(hiddenPrefix, path, headerText, environ)
}

func (e *SplitEditor) CurrentEditor() *CodeEditor {
	return e.current.CurrentEditor()
}

func (e *SplitEditor) CurrentFile() string {
	return e.current.CurrentFile()
}

func (e *SplitEditor) ChildIndex(c gxui.Control) int {
	if c == nil {
		return -1
	}
	for i, child := range e.Children() {
		if child.Control == c {
			return i
		}
	}
	return -1
}

func (e *SplitEditor) NextEditor(direction Direction) *CodeEditor {
	editor, _ := e.nextEditor(direction)
	return editor
}

func (e *SplitEditor) nextEditor(direction Direction) (editor *CodeEditor, wrapped bool) {
	switch direction {
	case Up, Down:
		if e.Orientation().Horizontal() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.nextEditor(direction)
			}
			return nil, false
		}
	case Left, Right:
		if e.Orientation().Vertical() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.nextEditor(direction)
			}
			return nil, false
		}
	}

	if splitter, ok := e.current.(*SplitEditor); ok {
		// Special case - there could be another split editor with our orientation
		// as a child, and *that* is the editor that needs the split moved.
		ed, wrapped := splitter.nextEditor(direction)
		if e != nil && !wrapped {
			return ed, false
		}
	}

	children := e.Children()
	i := children.IndexOf(e.current)
	if i < 0 {
		log.Printf("Error: Current editor is not part of the splitter's layout")
		return nil, false
	}
	switch direction {
	case Up, Left:
		i--
		if i < 0 {
			wrapped = true
			i = len(children) - 1
		}
	case Down, Right:
		i++
		if i == len(children) {
			wrapped = true
			i = 0
		}
	}
	me := children[i].Control.(MultiEditor)
	if splitter, ok := me.(*SplitEditor); ok {
		return splitter.first(direction), wrapped
	}

	return me.CurrentEditor(), wrapped
}

func (e *SplitEditor) first(d Direction) *CodeEditor {
	switch d {
	case Up, Down:
		if e.Orientation().Horizontal() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.first(d)
			}
			return e.current.CurrentEditor()
		}
	case Left, Right:
		if e.Orientation().Vertical() {
			if splitter, ok := e.current.(*SplitEditor); ok {
				return splitter.first(d)
			}
			return e.current.CurrentEditor()
		}
	}

	var first *gxui.Child
	switch d {
	case Up, Left:
		first = e.Children()[0]
	case Down, Right:
		first = e.Children()[len(e.Children())-1]
	}

	switch src := first.Control.(type) {
	case *SplitEditor:
		return src.first(d)
	case MultiEditor:
		return src.CurrentEditor()
	default:
		log.Printf("Error: first editor is not an editor")
		return nil
	}
}

func (e *SplitEditor) SaveAll() {
	for _, child := range e.Children() {
		editor, ok := child.Control.(MultiEditor)
		if !ok {
			continue
		}
		editor.SaveAll()
	}
}

type SplitterBar struct {
	mixins.SplitterBar
	viewport    gxui.Viewport
	orientation gxui.Orientation

	arrow, horizResize, vertResize *glfw.Cursor
}

func NewSplitterBar(viewport gxui.Viewport, theme gxui.Theme) *SplitterBar {
	s := &SplitterBar{
		viewport:    viewport,
		arrow:       glfw.CreateStandardCursor(glfw.ArrowCursor),
		horizResize: glfw.CreateStandardCursor(glfw.HResizeCursor),
		vertResize:  glfw.CreateStandardCursor(glfw.VResizeCursor),
	}
	s.SplitterBar.Init(s, theme)
	s.SetBackgroundColor(splitterBG)
	s.SetForegroundColor(splitterFG)
	return s
}

func (s *SplitterBar) SetOrientation(o gxui.Orientation) {
	s.orientation = o
}

func (s *SplitterBar) MouseEnter(gxui.MouseEvent) {
	switch s.orientation {
	case gxui.Vertical:
		s.viewport.SetCursor(s.vertResize)
	case gxui.Horizontal:
		s.viewport.SetCursor(s.horizResize)
	}
}

func (s *SplitterBar) MouseExit(gxui.MouseEvent) {
	s.viewport.SetCursor(s.arrow)
}
<|endoftext|>


<commit_before>package tunnel

import (
	"crypto/sha256"
	"errors"
	"fmt"
	"sync"

	"github.com/google/uuid"
	"github.com/micro/go-micro/transport"
	"github.com/micro/go-micro/util/log"
)

// tun represents a network tunnel
type tun struct {
	options Options

	sync.RWMutex

	// to indicate if we're connected or not
	connected bool

	// the send channel for all messages
	send chan *message

	// close channel
	closed chan bool

	// a map of sockets based on Micro-Tunnel-Id
	sockets map[string]*socket

	// outbound links
	links map[string]*link

	// listener
	listener transport.Listener
}

type link struct {
	transport.Socket
	id string
}

// create new tunnel on top of a link
func newTunnel(opts ...Option) *tun {
	options := DefaultOptions()
	for _, o := range opts {
		o(&options)
	}

	return &tun{
		options: options,
		send:    make(chan *message, 128),
		closed:  make(chan bool),
		sockets: make(map[string]*socket),
		links:   make(map[string]*link),
	}
}

// getSocket returns a socket from the internal socket map.
// It does this based on the Micro-Tunnel-Id and Micro-Tunnel-Session
func (t *tun) getSocket(id, session string) (*socket, bool) {
	// get the socket
	t.RLock()
	s, ok := t.sockets[id+session]
	t.RUnlock()
	return s, ok
}

// newSocket creates a new socket and saves it
func (t *tun) newSocket(id, session string) (*socket, bool) {
	// hash the id
	h := sha256.New()
	h.Write([]byte(id))
	id = fmt.Sprintf("%x", h.Sum(nil))

	// new socket
	s := &socket{
		id:      id,
		session: session,
		closed:  make(chan bool),
		recv:    make(chan *message, 128),
		send:    t.send,
		wait:    make(chan bool),
	}

	// save socket
	t.Lock()
	_, ok := t.sockets[id+session]
	if ok {
		// socket already exists
		t.Unlock()
		return nil, false
	}
	t.sockets[id+session] = s
	t.Unlock()

	// return socket
	return s, true
}

// TODO: use tunnel id as part of the session
func (t *tun) newSession() string {
	return uuid.New().String()
}

// process outgoing messages sent by all local sockets
func (t *tun) process() {
	// manage the send buffer
	// all pseudo sockets throw everything down this
	for {
		select {
		case msg := <-t.send:
			nmsg := &transport.Message{
				Header: msg.data.Header,
				Body:   msg.data.Body,
			}

			if nmsg.Header == nil {
				nmsg.Header = make(map[string]string)
			}

			// set the tunnel id on the outgoing message
			nmsg.Header["Micro-Tunnel-Id"] = msg.id

			// set the session id
			nmsg.Header["Micro-Tunnel-Session"] = msg.session

			// send the message via the interface
			t.RLock()
			for _, link := range t.links {
				link.Send(nmsg)
			}
			t.RUnlock()
		case <-t.closed:
			return
		}
	}
}

// process incoming messages
func (t *tun) listen(link transport.Socket, listener bool) {
	for {
		// process anything via the net interface
		msg := new(transport.Message)
		err := link.Recv(msg)
		if err != nil {
			return
		}

		switch msg.Header["Micro-Tunnel"] {
		case "connect", "close":
			continue
		}

		// the tunnel id
		id := msg.Header["Micro-Tunnel-Id"]

		// the session id
		session := msg.Header["Micro-Tunnel-Session"]

		// if the session id is blank there's nothing we can do
		// TODO: check this is the case, is there any reason
		// why we'd have a blank session? Is the tunnel
		// used for some other purpose?
		if len(id) == 0 || len(session) == 0 {
			continue
		}

		var s *socket
		var exists bool

		// if its a local listener then we use that as the session id
		// e.g we're using a loopback connecting to ourselves
		if listener {
			s, exists = t.getSocket(id, "listener")
		} else {
			// get the socket based on the tunnel id and session
			// this could be something we dialed in which case
			// we have a session for it otherwise its a listener
			s, exists = t.getSocket(id, session)
			if !exists {
				// try get it based on just the tunnel id
				// the assumption here is that a listener
				// has no session but its set a listener session
				s, exists = t.getSocket(id, "listener")
			}
		}

		// no socket in existence
		if !exists {
			// drop it, we don't care about
			// messages we don't know about
			continue
		}

		// is the socket closed?
		select {
		case <-s.closed:
			// closed
			delete(t.sockets, id)
			continue
		default:
			// process
		}

		// is the socket new?
		select {
		// if its new the socket is actually blocked waiting
		// for a connection. so we check if its waiting.
		case <-s.wait:
		// if its waiting e.g its new then we close it
		default:
			// set remote address of the socket
			s.remote = msg.Header["Remote"]
			close(s.wait)
		}

		// construct a new transport message
		tmsg := &transport.Message{
			Header: msg.Header,
			Body:   msg.Body,
		}

		// construct the internal message
		imsg := &message{
			id:      id,
			session: session,
			data:    tmsg,
		}

		// append to recv backlog
		// we don't block if we can't pass it on
		select {
		case s.recv <- imsg:
		default:
		}
	}
}

func (t *tun) connect() error {
	l, err := t.options.Transport.Listen(t.options.Address)
	if err != nil {
		return err
	}

	// save the listener
	t.listener = l

	go func() {
		// accept inbound connections
		err := l.Accept(func(sock transport.Socket) {
			// save the link
			id := uuid.New().String()
			t.Lock()
			t.links[id] = &link{
				Socket: sock,
				id:     id,
			}
			t.Unlock()

			// delete the link
			defer func() {
				t.Lock()
				delete(t.links, id)
				t.Unlock()
			}()

			// listen for inbound messages
			t.listen(sock, true)
		})

		t.Lock()
		defer t.Unlock()

		// still connected but the tunnel died
		if err != nil && t.connected {
			log.Logf("Tunnel listener died: %v", err)
		}
	}()

	for _, node := range t.options.Nodes {
		c, err := t.options.Transport.Dial(node)
		if err != nil {
			log.Debugf("Tunnel failed to connect to %s: %v", node, err)
			continue
		}

		if err := c.Send(&transport.Message{
			Header: map[string]string{
				"Micro-Tunnel": "connect",
			},
		}); err != nil {
			continue
		}

		// process incoming messages
		go t.listen(c, false)

		// save the link
		id := uuid.New().String()
		t.links[id] = &link{
			Socket: c,
			id:     id,
		}
	}

	// process outbound messages to be sent
	// process sends to all links
	go t.process()

	return nil
}

func (t *tun) close() error {
	// close all the links
	for id, link := range t.links {
		link.Send(&transport.Message{
			Header: map[string]string{
				"Micro-Tunnel": "close",
			},
		})
		link.Close()
		delete(t.links, id)
	}

	// close the listener
	return t.listener.Close()
}

// Close the tunnel
func (t *tun) Close() error {
	t.Lock()
	defer t.Unlock()

	if !t.connected {
		return nil
	}

	select {
	case <-t.closed:
		return nil
	default:
		// close all the sockets
		for _, s := range t.sockets {
			s.Close()
		}
		// close the connection
		close(t.closed)
		t.connected = false

		// send a close message
		// we don't close the link
		// just the tunnel
		return t.close()
	}

	return nil
}

// Connect the tunnel
func (t *tun) Connect() error {
	t.Lock()
	defer t.Unlock()

	// already connected
	if t.connected {
		return nil
	}

	// send the connect message
	if err := t.connect(); err != nil {
		return err
	}

	// set as connected
	t.connected = true
	// create new close channel
	t.closed = make(chan bool)

	return nil
}

func (t *tun) Init(opts ...Option) error {
	for _, o := range opts {
		o(&t.options)
	}
	return nil
}

// Dial an address
func (t *tun) Dial(addr string) (Conn, error) {
	c, ok := t.newSocket(addr, t.newSession())
	if !ok {
		return nil, errors.New("error dialing " + addr)
	}

	// set remote
	c.remote = addr
	// set local
	c.local = "local"

	return c, nil
}

// Accept a connection on the address
func (t *tun) Listen(addr string) (Listener, error) {
	// create a new socket by hashing the address
	c, ok := t.newSocket(addr, "listener")
	if !ok {
		return nil, errors.New("already listening on " + addr)
	}

	// set remote. it will be replaced by the first message received
	c.remote = "remote"
	// set local
	c.local = addr

	tl := &tunListener{
		addr: addr,
		// the accept channel
		accept: make(chan *socket, 128),
		// the channel to close
		closed: make(chan bool),
		// the connection
		conn: c,
		// the listener socket
		socket: c,
	}

	// this kicks off the internal message processor
	// for the listener so it can create pseudo sockets
	// per session if they do not exist or pass messages
	// to the existign sessions
	go tl.process()

	// return the listener
	return tl, nil
}
<commit_msg>more tunnel logging<commit_after>package tunnel

import (
	"crypto/sha256"
	"errors"
	"fmt"
	"sync"

	"github.com/google/uuid"
	"github.com/micro/go-micro/transport"
	"github.com/micro/go-micro/util/log"
)

// tun represents a network tunnel
type tun struct {
	options Options

	sync.RWMutex

	// to indicate if we're connected or not
	connected bool

	// the send channel for all messages
	send chan *message

	// close channel
	closed chan bool

	// a map of sockets based on Micro-Tunnel-Id
	sockets map[string]*socket

	// outbound links
	links map[string]*link

	// listener
	listener transport.Listener
}

type link struct {
	transport.Socket
	id string
}

// create new tunnel on top of a link
func newTunnel(opts ...Option) *tun {
	options := DefaultOptions()
	for _, o := range opts {
		o(&options)
	}

	return &tun{
		options: options,
		send:    make(chan *message, 128),
		closed:  make(chan bool),
		sockets: make(map[string]*socket),
		links:   make(map[string]*link),
	}
}

// getSocket returns a socket from the internal socket map.
// It does this based on the Micro-Tunnel-Id and Micro-Tunnel-Session
func (t *tun) getSocket(id, session string) (*socket, bool) {
	// get the socket
	t.RLock()
	s, ok := t.sockets[id+session]
	t.RUnlock()
	return s, ok
}

// newSocket creates a new socket and saves it
func (t *tun) newSocket(id, session string) (*socket, bool) {
	// hash the id
	h := sha256.New()
	h.Write([]byte(id))
	id = fmt.Sprintf("%x", h.Sum(nil))

	// new socket
	s := &socket{
		id:      id,
		session: session,
		closed:  make(chan bool),
		recv:    make(chan *message, 128),
		send:    t.send,
		wait:    make(chan bool),
	}

	// save socket
	t.Lock()
	_, ok := t.sockets[id+session]
	if ok {
		// socket already exists
		t.Unlock()
		return nil, false
	}
	t.sockets[id+session] = s
	t.Unlock()

	// return socket
	return s, true
}

// TODO: use tunnel id as part of the session
func (t *tun) newSession() string {
	return uuid.New().String()
}

// process outgoing messages sent by all local sockets
func (t *tun) process() {
	// manage the send buffer
	// all pseudo sockets throw everything down this
	for {
		select {
		case msg := <-t.send:
			nmsg := &transport.Message{
				Header: msg.data.Header,
				Body:   msg.data.Body,
			}

			if nmsg.Header == nil {
				nmsg.Header = make(map[string]string)
			}

			// set the tunnel id on the outgoing message
			nmsg.Header["Micro-Tunnel-Id"] = msg.id

			// set the session id
			nmsg.Header["Micro-Tunnel-Session"] = msg.session

			// send the message via the interface
			t.RLock()
			for _, link := range t.links {
				link.Send(nmsg)
			}
			t.RUnlock()
		case <-t.closed:
			return
		}
	}
}

// process incoming messages
func (t *tun) listen(link transport.Socket, listener bool) {
	for {
		// process anything via the net interface
		msg := new(transport.Message)
		err := link.Recv(msg)
		if err != nil {
			return
		}

		switch msg.Header["Micro-Tunnel"] {
		case "connect", "close":
			continue
		}

		// the tunnel id
		id := msg.Header["Micro-Tunnel-Id"]

		// the session id
		session := msg.Header["Micro-Tunnel-Session"]

		// if the session id is blank there's nothing we can do
		// TODO: check this is the case, is there any reason
		// why we'd have a blank session? Is the tunnel
		// used for some other purpose?
		if len(id) == 0 || len(session) == 0 {
			continue
		}

		var s *socket
		var exists bool

		// if its a local listener then we use that as the session id
		// e.g we're using a loopback connecting to ourselves
		if listener {
			s, exists = t.getSocket(id, "listener")
		} else {
			// get the socket based on the tunnel id and session
			// this could be something we dialed in which case
			// we have a session for it otherwise its a listener
			s, exists = t.getSocket(id, session)
			if !exists {
				// try get it based on just the tunnel id
				// the assumption here is that a listener
				// has no session but its set a listener session
				s, exists = t.getSocket(id, "listener")
			}
		}

		// no socket in existence
		if !exists {
			// drop it, we don't care about
			// messages we don't know about
			continue
		}

		// is the socket closed?
		select {
		case <-s.closed:
			// closed
			delete(t.sockets, id)
			continue
		default:
			// process
		}

		// is the socket new?
		select {
		// if its new the socket is actually blocked waiting
		// for a connection. so we check if its waiting.
		case <-s.wait:
		// if its waiting e.g its new then we close it
		default:
			// set remote address of the socket
			s.remote = msg.Header["Remote"]
			close(s.wait)
		}

		// construct a new transport message
		tmsg := &transport.Message{
			Header: msg.Header,
			Body:   msg.Body,
		}

		// construct the internal message
		imsg := &message{
			id:      id,
			session: session,
			data:    tmsg,
		}

		// append to recv backlog
		// we don't block if we can't pass it on
		select {
		case s.recv <- imsg:
		default:
		}
	}
}

func (t *tun) connect() error {
	l, err := t.options.Transport.Listen(t.options.Address)
	if err != nil {
		return err
	}

	// save the listener
	t.listener = l

	go func() {
		// accept inbound connections
		err := l.Accept(func(sock transport.Socket) {
			log.Debugf("Accepted connection from %s", sock.Remote())
			// save the link
			id := uuid.New().String()
			t.Lock()
			t.links[id] = &link{
				Socket: sock,
				id:     id,
			}
			t.Unlock()

			// delete the link
			defer func() {
				t.Lock()
				delete(t.links, id)
				t.Unlock()
			}()

			// listen for inbound messages
			t.listen(sock, true)
		})

		t.Lock()
		defer t.Unlock()

		// still connected but the tunnel died
		if err != nil && t.connected {
			log.Logf("Tunnel listener died: %v", err)
		}
	}()

	for _, node := range t.options.Nodes {
		log.Debugf("Dialing %s", node)
		c, err := t.options.Transport.Dial(node)
		if err != nil {
			log.Debugf("Tunnel failed to connect to %s: %v", node, err)
			continue
		}
		log.Debugf("Connected to %s", node)

		if err := c.Send(&transport.Message{
			Header: map[string]string{
				"Micro-Tunnel": "connect",
			},
		}); err != nil {
			continue
		}

		// process incoming messages
		go t.listen(c, false)

		// save the link
		id := uuid.New().String()
		t.links[id] = &link{
			Socket: c,
			id:     id,
		}
	}

	// process outbound messages to be sent
	// process sends to all links
	go t.process()

	return nil
}

func (t *tun) close() error {
	// close all the links
	for id, link := range t.links {
		link.Send(&transport.Message{
			Header: map[string]string{
				"Micro-Tunnel": "close",
			},
		})
		link.Close()
		delete(t.links, id)
	}

	// close the listener
	return t.listener.Close()
}

// Close the tunnel
func (t *tun) Close() error {
	t.Lock()
	defer t.Unlock()

	if !t.connected {
		return nil
	}

	select {
	case <-t.closed:
		return nil
	default:
		// close all the sockets
		for _, s := range t.sockets {
			s.Close()
		}
		// close the connection
		close(t.closed)
		t.connected = false

		// send a close message
		// we don't close the link
		// just the tunnel
		return t.close()
	}

	return nil
}

// Connect the tunnel
func (t *tun) Connect() error {
	t.Lock()
	defer t.Unlock()

	// already connected
	if t.connected {
		return nil
	}

	// send the connect message
	if err := t.connect(); err != nil {
		return err
	}

	// set as connected
	t.connected = true
	// create new close channel
	t.closed = make(chan bool)

	return nil
}

func (t *tun) Init(opts ...Option) error {
	for _, o := range opts {
		o(&t.options)
	}
	return nil
}

// Dial an address
func (t *tun) Dial(addr string) (Conn, error) {
	c, ok := t.newSocket(addr, t.newSession())
	if !ok {
		return nil, errors.New("error dialing " + addr)
	}

	// set remote
	c.remote = addr
	// set local
	c.local = "local"

	return c, nil
}

// Accept a connection on the address
func (t *tun) Listen(addr string) (Listener, error) {
	// create a new socket by hashing the address
	c, ok := t.newSocket(addr, "listener")
	if !ok {
		return nil, errors.New("already listening on " + addr)
	}

	// set remote. it will be replaced by the first message received
	c.remote = "remote"
	// set local
	c.local = addr

	tl := &tunListener{
		addr: addr,
		// the accept channel
		accept: make(chan *socket, 128),
		// the channel to close
		closed: make(chan bool),
		// the connection
		conn: c,
		// the listener socket
		socket: c,
	}

	// this kicks off the internal message processor
	// for the listener so it can create pseudo sockets
	// per session if they do not exist or pass messages
	// to the existign sessions
	go tl.process()

	// return the listener
	return tl, nil
}
<|endoftext|>