<commit_before>package application

import (
	"github.com/ovh/cds/engine/api/database"
	"github.com/ovh/cds/engine/api/environment"
	"github.com/ovh/cds/engine/api/group"
	"github.com/ovh/cds/engine/api/hook"
	"github.com/ovh/cds/engine/api/msg"
	"github.com/ovh/cds/engine/api/pipeline"
	"github.com/ovh/cds/engine/api/repositoriesmanager"
	"github.com/ovh/cds/engine/api/trigger"
	"github.com/ovh/cds/engine/log"
	"github.com/ovh/cds/sdk"
)

//Import is able to create a new application and all its components
func Import(db database.QueryExecuter, proj *sdk.Project, app *sdk.Application, repomanager *sdk.RepositoriesManager, user *sdk.User, msgChan chan<- msg.Message) error {
	//Save application in database
	if err := InsertApplication(db, proj, app); err != nil {
		return err
	}

	if msgChan != nil {
		msgChan <- msg.New(msg.AppCreated, app.Name)
	}

	//Inherit project groups if not provided
	if app.ApplicationGroups == nil {
		if msgChan != nil {
			msgChan <- msg.New(msg.AppGroupInheritPermission, app.Name)
		}
		app.ApplicationGroups = proj.ProjectGroups
	}

	//Insert group permission on application
	for i := range app.ApplicationGroups {
		//Load the group by name
		g, err := group.LoadGroup(db, app.ApplicationGroups[i].Group.Name)
		if err != nil {
			return err
		}
		log.Debug("application.Import> Insert group %d in application", g.ID)
		if err := group.InsertGroupInApplication(db, app.ID, g.ID, app.ApplicationGroups[i].Permission); err != nil {
			return err
		}
		if msgChan != nil {
			msgChan <- msg.New(msg.AppGroupSetPermission, g.Name, app.Name)
		}
	}

	if err := importVariables(db, proj, app, user, msgChan); err != nil {
		return err
	}

	if err := ImportPipelines(db, proj, app, msgChan); err != nil {
		return err
	}

	//Set repositories manager
	app.RepositoriesManager = repomanager
	if app.RepositoriesManager != nil && app.RepositoryFullname != "" && len(app.Pipelines) > 0 {
		if err := repositoriesmanager.InsertForApplication(db, app, proj.Key); err != nil {
			return err
		}
		//Manage hook
		if _, err := hook.CreateHook(db, proj.Key, repomanager, app.RepositoryFullname, app, &app.Pipelines[0].Pipeline); err != nil {
			return err
		}
		if msgChan != nil {
			msgChan <- msg.New(msg.HookCreated, app.RepositoryFullname, app.Pipelines[0].Pipeline.Name)
		}
	}

	return nil
}

//importVariables is able to create variable on an existing application
func importVariables(db database.QueryExecuter, proj *sdk.Project, app *sdk.Application, user *sdk.User, msgChan chan<- msg.Message) error {

	for _, newVar := range app.Variable {
		var errCreate error
		switch newVar.Type {
		case sdk.KeyVariable:
			errCreate = AddKeyPairToApplication(db, app, newVar.Name)
			break
		default:
			errCreate = InsertVariable(db, app, newVar)
			break
		}
		if errCreate != nil {
			log.Warning("importVariables> Cannot add variable %s in application %s:  %s\n", newVar.Name, app.Name, errCreate)
			return errCreate
		}
		if err := CreateAudit(db, newVar.Name, app, user); err != nil {
			log.Warning("importVariabitles> Cannot create variable audit for application %s:  %s\n", app.Name, err)
			return err
		}
	}

	if msgChan != nil {
		msgChan <- msg.New(msg.AppVariablesCreated, app.Name)
	}

	return nil
}

//ImportPipelines is able to create pipelines on an existing application
func ImportPipelines(db database.QueryExecuter, proj *sdk.Project, app *sdk.Application, msgChan chan<- msg.Message) error {
	//Import pipelines
	for i := range app.Pipelines {
		//Import pipeline
		log.Debug("application.Import> Import pipeline %s", app.Pipelines[i].Pipeline.Name)
		if err := pipeline.Import(db, proj, &app.Pipelines[i].Pipeline, msgChan); err != nil {
			return err
		}

		//Check if application is attached to the pipeline
		attached, err := IsAttached(db, proj.ID, app.ID, app.Pipelines[i].Pipeline.Name)
		if err != nil {
			return err
		}

		//Attach pipeline
		if !attached {
			log.Debug("application.Import> Attach pipeline %s", app.Pipelines[i].Pipeline.Name)
			if err := AttachPipeline(db, app.ID, app.Pipelines[i].Pipeline.ID); err != nil {
				return err
			}
			if msgChan != nil {
				msgChan <- msg.New(msg.PipelineAttached, app.Pipelines[i].Pipeline.Name, app.Name)
			}
		}
	}

	//Insert triggers
	for i := range app.Pipelines {
		for j := range app.Pipelines[i].Triggers {
			t := &app.Pipelines[i].Triggers[j]
			//Source pipeline is always the current pipeline
			t.SrcPipeline = app.Pipelines[i].Pipeline

			//Source application is always the current application
			t.SrcApplication = *app

			//Load destination App
			if t.DestApplication.Name == "" {
				t.DestApplication = *app
			} else {
				dest, err := LoadApplicationByName(db, proj.Key, t.DestApplication.Name)
				if err != nil {
					return err
				}
				t.DestApplication = *dest
			}

			//Load or import source environmment
			if t.SrcEnvironment.Name == "" {
				t.SrcEnvironment = sdk.DefaultEnv
			} else {
				if err := environment.Import(db, proj, &t.SrcEnvironment, msgChan); err != nil {
					return err
				}
			}

			//Load or import destination environment
			if t.DestEnvironment.Name == "" {
				t.DestEnvironment = sdk.DefaultEnv
			} else {
				if err := environment.Import(db, proj, &t.DestEnvironment, msgChan); err != nil {
					return err
				}
			}

			//Load dest pipeline
			destPipeline, err := pipeline.LoadPipeline(db, proj.Key, t.DestPipeline.Name, false)
			if err != nil {
				return err
			}
			t.DestPipeline = *destPipeline

			//Check if environment and pipeline type are compatible
			if t.DestEnvironment.ID == sdk.DefaultEnv.ID && t.DestPipeline.Type == sdk.DeploymentPipeline {
				return sdk.ErrNoEnvironmentProvided
			}

			log.Debug("application.Import> creating trigger SrcApp=%d SrpPip=%d SrcEnv=%d DestApp=%d DestPip=%d DestEnv=%d", t.SrcApplication.ID, t.SrcPipeline.ID, t.SrcEnvironment.ID, t.DestApplication.ID, t.DestPipeline.ID, t.DestEnvironment.ID)

			//Check if trigger exists
			exists, err := trigger.Exists(db, t.SrcApplication.ID, t.SrcPipeline.ID, t.SrcEnvironment.ID, t.DestApplication.ID, t.DestPipeline.ID, t.DestEnvironment.ID)
			if err != nil {
				return err
			}
			if !exists {
				//Insert trigger
				if err := trigger.InsertTrigger(db, t); err != nil {
					return err
				}
				if msgChan != nil {
					msgChan <- msg.New(msg.PipelineTriggerCreated, t.SrcPipeline.Name, t.SrcApplication.Name, t.DestPipeline.Name, t.DestApplication.Name)
				}

			}
		}
	}
	return nil
}
<commit_msg>feat: allow adding a trigger in pipeline child (and not in parent) (#81)<commit_after>package application

import (
	"github.com/ovh/cds/engine/api/database"
	"github.com/ovh/cds/engine/api/environment"
	"github.com/ovh/cds/engine/api/group"
	"github.com/ovh/cds/engine/api/hook"
	"github.com/ovh/cds/engine/api/msg"
	"github.com/ovh/cds/engine/api/pipeline"
	"github.com/ovh/cds/engine/api/repositoriesmanager"
	"github.com/ovh/cds/engine/api/trigger"
	"github.com/ovh/cds/engine/log"
	"github.com/ovh/cds/sdk"
)

//Import is able to create a new application and all its components
func Import(db database.QueryExecuter, proj *sdk.Project, app *sdk.Application, repomanager *sdk.RepositoriesManager, user *sdk.User, msgChan chan<- msg.Message) error {
	//Save application in database
	if err := InsertApplication(db, proj, app); err != nil {
		return err
	}

	if msgChan != nil {
		msgChan <- msg.New(msg.AppCreated, app.Name)
	}

	//Inherit project groups if not provided
	if app.ApplicationGroups == nil {
		if msgChan != nil {
			msgChan <- msg.New(msg.AppGroupInheritPermission, app.Name)
		}
		app.ApplicationGroups = proj.ProjectGroups
	}

	//Insert group permission on application
	for i := range app.ApplicationGroups {
		//Load the group by name
		g, err := group.LoadGroup(db, app.ApplicationGroups[i].Group.Name)
		if err != nil {
			return err
		}
		log.Debug("application.Import> Insert group %d in application", g.ID)
		if err := group.InsertGroupInApplication(db, app.ID, g.ID, app.ApplicationGroups[i].Permission); err != nil {
			return err
		}
		if msgChan != nil {
			msgChan <- msg.New(msg.AppGroupSetPermission, g.Name, app.Name)
		}
	}

	if err := importVariables(db, proj, app, user, msgChan); err != nil {
		return err
	}

	if err := ImportPipelines(db, proj, app, msgChan); err != nil {
		return err
	}

	//Set repositories manager
	app.RepositoriesManager = repomanager
	if app.RepositoriesManager != nil && app.RepositoryFullname != "" && len(app.Pipelines) > 0 {
		if err := repositoriesmanager.InsertForApplication(db, app, proj.Key); err != nil {
			return err
		}
		//Manage hook
		if _, err := hook.CreateHook(db, proj.Key, repomanager, app.RepositoryFullname, app, &app.Pipelines[0].Pipeline); err != nil {
			return err
		}
		if msgChan != nil {
			msgChan <- msg.New(msg.HookCreated, app.RepositoryFullname, app.Pipelines[0].Pipeline.Name)
		}
	}

	return nil
}

//importVariables is able to create variable on an existing application
func importVariables(db database.QueryExecuter, proj *sdk.Project, app *sdk.Application, user *sdk.User, msgChan chan<- msg.Message) error {

	for _, newVar := range app.Variable {
		var errCreate error
		switch newVar.Type {
		case sdk.KeyVariable:
			errCreate = AddKeyPairToApplication(db, app, newVar.Name)
			break
		default:
			errCreate = InsertVariable(db, app, newVar)
			break
		}
		if errCreate != nil {
			log.Warning("importVariables> Cannot add variable %s in application %s:  %s\n", newVar.Name, app.Name, errCreate)
			return errCreate
		}
		if err := CreateAudit(db, newVar.Name, app, user); err != nil {
			log.Warning("importVariabitles> Cannot create variable audit for application %s:  %s\n", app.Name, err)
			return err
		}
	}

	if msgChan != nil {
		msgChan <- msg.New(msg.AppVariablesCreated, app.Name)
	}

	return nil
}

//ImportPipelines is able to create pipelines on an existing application
func ImportPipelines(db database.QueryExecuter, proj *sdk.Project, app *sdk.Application, msgChan chan<- msg.Message) error {
	//Import pipelines
	for i := range app.Pipelines {
		//Import pipeline
		log.Debug("application.Import> Import pipeline %s", app.Pipelines[i].Pipeline.Name)
		if err := pipeline.Import(db, proj, &app.Pipelines[i].Pipeline, msgChan); err != nil {
			return err
		}

		//Check if application is attached to the pipeline
		attached, err := IsAttached(db, proj.ID, app.ID, app.Pipelines[i].Pipeline.Name)
		if err != nil {
			return err
		}

		//Attach pipeline
		if !attached {
			log.Debug("application.Import> Attach pipeline %s", app.Pipelines[i].Pipeline.Name)
			if err := AttachPipeline(db, app.ID, app.Pipelines[i].Pipeline.ID); err != nil {
				return err
			}
			if msgChan != nil {
				msgChan <- msg.New(msg.PipelineAttached, app.Pipelines[i].Pipeline.Name, app.Name)
			}
		}
	}

	//Insert triggers
	for i := range app.Pipelines {
		for j := range app.Pipelines[i].Triggers {
			t := &app.Pipelines[i].Triggers[j]

			// You have an existing build pipeline. You want to create a template
			// for create a deploy package, and this template add trigger with only srcApp.
			// so, if SrcApplication.Name != "" -> load existing application.
			if t.SrcApplication.Name == "" {
				//Source application is the current application
				t.SrcApplication = *app
				log.Debug("ImportPipelines> current app")
			} else {
				log.Debug("Load t.SrcApplication.Name:%s", t.SrcApplication.Name)
				srcApp, err := LoadApplicationByName(db, proj.Key, t.SrcApplication.Name)
				if err != nil {
					return err
				}
				t.SrcApplication = *srcApp
			}

			// Same explanation for pipeline
			if t.SrcPipeline.Name == "" {
				//Source pipeline is the current pipeline
				t.SrcPipeline = app.Pipelines[i].Pipeline
				log.Debug("ImportPipelines> current pipeline")
			} else {
				log.Debug("ImportPipelines> Load t.SrcPipeline.Name:%s", t.SrcApplication.Name)
				srcPipeline, err := pipeline.LoadPipeline(db, proj.Key, t.SrcPipeline.Name, false)
				if err != nil {
					return err
				}
				t.SrcPipeline = *srcPipeline
			}

			//Load destination App
			if t.DestApplication.Name == "" {
				t.DestApplication = *app
			} else {
				dest, err := LoadApplicationByName(db, proj.Key, t.DestApplication.Name)
				if err != nil {
					return err
				}
				t.DestApplication = *dest
			}

			//Load dest pipeline
			if t.DestPipeline.Name == "" {
				t.DestPipeline = app.Pipelines[i].Pipeline
			} else {
				destPipeline, err := pipeline.LoadPipeline(db, proj.Key, t.DestPipeline.Name, false)
				if err != nil {
					return err
				}
				t.DestPipeline = *destPipeline
			}

			//Load or import source environmment
			if t.SrcEnvironment.Name == "" {
				t.SrcEnvironment = sdk.DefaultEnv
			} else {
				if err := environment.Import(db, proj, &t.SrcEnvironment, msgChan); err != nil {
					return err
				}
			}

			//Load or import destination environment
			if t.DestEnvironment.Name == "" {
				t.DestEnvironment = sdk.DefaultEnv
			} else {
				if err := environment.Import(db, proj, &t.DestEnvironment, msgChan); err != nil {
					return err
				}
			}

			//Check if environment and pipeline type are compatible
			if t.DestEnvironment.ID == sdk.DefaultEnv.ID && t.DestPipeline.Type == sdk.DeploymentPipeline {
				return sdk.ErrNoEnvironmentProvided
			}

			log.Debug("application.Import> creating trigger SrcApp=%d SrpPip=%d SrcEnv=%d DestApp=%d DestPip=%d DestEnv=%d", t.SrcApplication.ID, t.SrcPipeline.ID, t.SrcEnvironment.ID, t.DestApplication.ID, t.DestPipeline.ID, t.DestEnvironment.ID)

			//Check if trigger exists
			exists, err := trigger.Exists(db, t.SrcApplication.ID, t.SrcPipeline.ID, t.SrcEnvironment.ID, t.DestApplication.ID, t.DestPipeline.ID, t.DestEnvironment.ID)
			if err != nil {
				return err
			}
			if !exists {
				//Insert trigger
				if err := trigger.InsertTrigger(db, t); err != nil {
					return err
				}
				if msgChan != nil {
					msgChan <- msg.New(msg.PipelineTriggerCreated, t.SrcPipeline.Name, t.SrcApplication.Name, t.DestPipeline.Name, t.DestApplication.Name)
				}

			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package msg

import (
	"time"

	"github.com/elemchat/elemchat/magic"
)

var (
	_ Message = &Init{}
	_ Message = &WaitChat{}
	_ Message = &WaitMagic{}
	_ Message = &Chat{}
	_ Message = &Magic{}
	_ Message = &Effect{}
	_ Message = &Dualover{}
)

type Message interface {
	Type() Type
}

type Init struct {
}

type WaitChat struct {
	Deadline time.Time
}

type WaitMagic struct {
	Deadline time.Time
}

type Chat struct {
	Text string
}

type Magic struct {
	Magic magic.Magic
}

type Effect struct {
}

type Dualover struct {
}

func (_ *Init) Type() Type {
	return INIT
}
func (_ *WaitChat) Type() Type {
	return WAIT_CHAT
}
func (_ *WaitMagic) Type() Type {
	return WAIT_MAGIC
}
func (_ *Chat) Type() Type {
	return CHAT
}
func (_ *Magic) Type() Type {
	return MAGIC
}
func (_ *Effect) Type() Type {
	return EFFECT
}
func (_ *Dualover) Type() Type {
	return DUALOVER
}
<commit_msg>add message structs tag for json serialization<commit_after>package msg

import (
	"time"

	"github.com/elemchat/elemchat/magic"
)

var (
	_ Message = &Init{}
	_ Message = &WaitChat{}
	_ Message = &WaitMagic{}
	_ Message = &Chat{}
	_ Message = &Magic{}
	_ Message = &Effect{}
	_ Message = &Dualover{}
)

type Message interface {
	Type() Type
}

type Init struct {
}

type WaitChat struct {
	Deadline time.Time `json:"deadline"`
}

type WaitMagic struct {
	Deadline time.Time `json:"deadline"`
}

type Chat struct {
	Text string `json:"text"`
}

type Magic struct {
	Magic magic.Magic `json:"magic"`
}

type Effect struct {
}

type Dualover struct {
}

func (_ *Init) Type() Type {
	return INIT
}
func (_ *WaitChat) Type() Type {
	return WAIT_CHAT
}
func (_ *WaitMagic) Type() Type {
	return WAIT_MAGIC
}
func (_ *Chat) Type() Type {
	return CHAT
}
func (_ *Magic) Type() Type {
	return MAGIC
}
func (_ *Effect) Type() Type {
	return EFFECT
}
func (_ *Dualover) Type() Type {
	return DUALOVER
}
<|endoftext|>


<commit_before>package lncfg

import (
	"crypto/tls"
	"encoding/hex"
	"fmt"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/btcsuite/btcd/btcec"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/lightningnetwork/lnd/tor"
)

var (
	loopBackAddrs = []string{"localhost", "127.0.0.1", "[::1]"}
)

type tcpResolver = func(network, addr string) (*net.TCPAddr, error)

// NormalizeAddresses returns a new slice with all the passed addresses
// normalized with the given default port and all duplicates removed.
func NormalizeAddresses(addrs []string, defaultPort string,
	tcpResolver tcpResolver) ([]net.Addr, error) {

	result := make([]net.Addr, 0, len(addrs))
	seen := map[string]struct{}{}

	for _, addr := range addrs {
		parsedAddr, err := ParseAddressString(
			addr, defaultPort, tcpResolver,
		)
		if err != nil {
			return nil, err
		}

		if _, ok := seen[parsedAddr.String()]; !ok {
			result = append(result, parsedAddr)
			seen[parsedAddr.String()] = struct{}{}
		}
	}

	return result, nil
}

// EnforceSafeAuthentication enforces "safe" authentication taking into account
// the interfaces that the RPC servers are listening on, and if macaroons are
// activated or not. To protect users from using dangerous config combinations,
// we'll prevent disabling authentication if the server is listening on a public
// interface.
func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive bool) error {
	// We'll now examine all addresses that this RPC server is listening
	// on. If it's a localhost address, we'll skip it, otherwise, we'll
	// return an error if macaroons are inactive.
	for _, addr := range addrs {
		if IsLoopback(addr.String()) || IsUnix(addr) {
			continue
		}

		if !macaroonsActive {
			return fmt.Errorf("Detected RPC server listening on "+
				"publicly reachable interface %v with "+
				"authentication disabled! Refusing to start "+
				"with --no-macaroons specified.", addr)
		}
	}

	return nil
}

// parseNetwork parses the network type of the given address.
func parseNetwork(addr net.Addr) string {
	switch addr := addr.(type) {
	// TCP addresses resolved through net.ResolveTCPAddr give a default
	// network of "tcp", so we'll map back the correct network for the given
	// address. This ensures that we can listen on the correct interface
	// (IPv4 vs IPv6).
	case *net.TCPAddr:
		if addr.IP.To4() != nil {
			return "tcp4"
		}
		return "tcp6"

	default:
		return addr.Network()
	}
}

// ListenOnAddress creates a listener that listens on the given address.
func ListenOnAddress(addr net.Addr) (net.Listener, error) {
	return net.Listen(parseNetwork(addr), addr.String())
}

// TLSListenOnAddress creates a TLS listener that listens on the given address.
func TLSListenOnAddress(addr net.Addr,
	config *tls.Config) (net.Listener, error) {
	return tls.Listen(parseNetwork(addr), addr.String(), config)
}

// IsLoopback returns true if an address describes a loopback interface.
func IsLoopback(addr string) bool {
	for _, loopback := range loopBackAddrs {
		if strings.Contains(addr, loopback) {
			return true
		}
	}

	return false
}

// IsUnix returns true if an address describes an Unix socket address.
func IsUnix(addr net.Addr) bool {
	return strings.HasPrefix(addr.Network(), "unix")
}

// ParseAddressString converts an address in string format to a net.Addr that is
// compatible with lnd. UDP is not supported because lnd needs reliable
// connections. We accept a custom function to resolve any TCP addresses so
// that caller is able control exactly how resolution is performed.
func ParseAddressString(strAddress string, defaultPort string,
	tcpResolver tcpResolver) (net.Addr, error) {

	var parsedNetwork, parsedAddr string

	// Addresses can either be in network://address:port format,
	// network:address:port, address:port, or just port. We want to support
	// all possible types.
	if strings.Contains(strAddress, "://") {
		parts := strings.Split(strAddress, "://")
		parsedNetwork, parsedAddr = parts[0], parts[1]
	} else if strings.Contains(strAddress, ":") {
		parts := strings.Split(strAddress, ":")
		parsedNetwork = parts[0]
		parsedAddr = strings.Join(parts[1:], ":")
	}

	// Only TCP and Unix socket addresses are valid. We can't use IP or
	// UDP only connections for anything we do in lnd.
	switch parsedNetwork {
	case "unix", "unixpacket":
		return net.ResolveUnixAddr(parsedNetwork, parsedAddr)

	case "tcp", "tcp4", "tcp6":
		return tcpResolver(
			parsedNetwork, verifyPort(parsedAddr, defaultPort),
		)

	case "ip", "ip4", "ip6", "udp", "udp4", "udp6", "unixgram":
		return nil, fmt.Errorf("only TCP or unix socket "+
			"addresses are supported: %s", parsedAddr)

	default:
		// We'll now possibly apply the default port, use the local
		// host short circuit, or parse out an all interfaces listen.
		addrWithPort := verifyPort(strAddress, defaultPort)
		rawHost, rawPort, _ := net.SplitHostPort(addrWithPort)

		// If we reach this point, then we'll check to see if we have
		// an onion addresses, if so, we can directly pass the raw
		// address and port to create the proper address.
		if tor.IsOnionHost(rawHost) {
			portNum, err := strconv.Atoi(rawPort)
			if err != nil {
				return nil, err
			}

			return &tor.OnionAddr{
				OnionService: rawHost,
				Port:         portNum,
			}, nil
		}

		// Otherwise, we'll attempt the resolve the host. The Tor
		// resolver is unable to resolve local addresses, so we'll use
		// the system resolver instead.
		if rawHost == "" || IsLoopback(rawHost) {
			return net.ResolveTCPAddr("tcp", addrWithPort)
		}

		return tcpResolver("tcp", addrWithPort)
	}
}

// ParseLNAddressString converts a string of the form <pubkey>@<addr> into an
// lnwire.NetAddress. The <pubkey> must be presented in hex, and result in a
// 33-byte, compressed public key that lies on the secp256k1 curve. The <addr>
// may be any address supported by ParseAddressString. If no port is specified,
// the defaultPort will be used. Any tcp addresses that need resolving will be
// resolved using the custom tcpResolver.
func ParseLNAddressString(strAddress string, defaultPort string,
	tcpResolver tcpResolver) (*lnwire.NetAddress, error) {

	// Split the address string around the @ sign.
	parts := strings.Split(strAddress, "@")

	// The string is malformed if there are not exactly two parts.
	if len(parts) != 2 {
		return nil, fmt.Errorf("invalid lightning address %s: "+
			"must be of the form <pubkey-hex>@<addr>", strAddress)
	}

	// Now, take the first portion as the hex pubkey, and the latter as the
	// address string.
	parsedPubKey, parsedAddr := parts[0], parts[1]

	// Decode the hex pubkey to get the raw compressed pubkey bytes.
	pubKeyBytes, err := hex.DecodeString(parsedPubKey)
	if err != nil {
		return nil, fmt.Errorf("invalid lightning address pubkey: %v", err)
	}

	// The compressed pubkey should have a length of exactly 33 bytes.
	if len(pubKeyBytes) != 33 {
		return nil, fmt.Errorf("invalid lightning address pubkey: "+
			"length must be 33 bytes, found %d", len(pubKeyBytes))
	}

	// Parse the pubkey bytes to verify that it corresponds to valid public
	// key on the secp256k1 curve.
	pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256())
	if err != nil {
		return nil, fmt.Errorf("invalid lightning address pubkey: %v", err)
	}

	// Finally, parse the address string using our generic address parser.
	addr, err := ParseAddressString(parsedAddr, defaultPort, tcpResolver)
	if err != nil {
		return nil, fmt.Errorf("invalid lightning address address: %v", err)
	}

	return &lnwire.NetAddress{
		IdentityKey: pubKey,
		Address:     addr,
	}, nil
}

// verifyPort makes sure that an address string has both a host and a port. If
// there is no port found, the default port is appended. If the address is just
// a port, then we'll assume that the user is using the short cut to specify a
// localhost:port address.
func verifyPort(address string, defaultPort string) string {
	host, port, err := net.SplitHostPort(address)
	if err != nil {
		// If the address itself is just an integer, then we'll assume
		// that we're mapping this directly to a localhost:port pair.
		// This ensures we maintain the legacy behavior.
		if _, err := strconv.Atoi(address); err == nil {
			return net.JoinHostPort("localhost", address)
		}

		// Otherwise, we'll assume that the address just failed to
		// attach its own port, so we'll use the default port. In the
		// case of IPv6 addresses, if the host is already surrounded by
		// brackets, then we'll avoid using the JoinHostPort function,
		// since it will always add a pair of brackets.
		if strings.HasPrefix(address, "[") {
			return address + ":" + defaultPort
		}
		return net.JoinHostPort(address, defaultPort)
	}

	// In the case that both the host and port are empty, we'll use the
	// default port.
	if host == "" && port == "" {
		return ":" + defaultPort
	}

	return address
}

// ClientAddressDialer creates a gRPC dialer that can also dial unix socket
// addresses instead of just TCP addresses.
func ClientAddressDialer(defaultPort string) func(string, time.Duration) (net.Conn, error) {
	return func(addr string, timeout time.Duration) (net.Conn, error) {
		parsedAddr, err := ParseAddressString(
			addr, defaultPort, net.ResolveTCPAddr,
		)
		if err != nil {
			return nil, err
		}

		return net.DialTimeout(
			parsedAddr.Network(), parsedAddr.String(), timeout,
		)
	}
}
<commit_msg>lncfg/address: expose TCPResolver<commit_after>package lncfg

import (
	"crypto/tls"
	"encoding/hex"
	"fmt"
	"net"
	"strconv"
	"strings"
	"time"

	"github.com/btcsuite/btcd/btcec"
	"github.com/lightningnetwork/lnd/lnwire"
	"github.com/lightningnetwork/lnd/tor"
)

var (
	loopBackAddrs = []string{"localhost", "127.0.0.1", "[::1]"}
)

// TCPResolver is a function signature that resolves an address on a given
// network.
type TCPResolver = func(network, addr string) (*net.TCPAddr, error)

// NormalizeAddresses returns a new slice with all the passed addresses
// normalized with the given default port and all duplicates removed.
func NormalizeAddresses(addrs []string, defaultPort string,
	tcpResolver TCPResolver) ([]net.Addr, error) {

	result := make([]net.Addr, 0, len(addrs))
	seen := map[string]struct{}{}

	for _, addr := range addrs {
		parsedAddr, err := ParseAddressString(
			addr, defaultPort, tcpResolver,
		)
		if err != nil {
			return nil, err
		}

		if _, ok := seen[parsedAddr.String()]; !ok {
			result = append(result, parsedAddr)
			seen[parsedAddr.String()] = struct{}{}
		}
	}

	return result, nil
}

// EnforceSafeAuthentication enforces "safe" authentication taking into account
// the interfaces that the RPC servers are listening on, and if macaroons are
// activated or not. To protect users from using dangerous config combinations,
// we'll prevent disabling authentication if the server is listening on a public
// interface.
func EnforceSafeAuthentication(addrs []net.Addr, macaroonsActive bool) error {
	// We'll now examine all addresses that this RPC server is listening
	// on. If it's a localhost address, we'll skip it, otherwise, we'll
	// return an error if macaroons are inactive.
	for _, addr := range addrs {
		if IsLoopback(addr.String()) || IsUnix(addr) {
			continue
		}

		if !macaroonsActive {
			return fmt.Errorf("Detected RPC server listening on "+
				"publicly reachable interface %v with "+
				"authentication disabled! Refusing to start "+
				"with --no-macaroons specified.", addr)
		}
	}

	return nil
}

// parseNetwork parses the network type of the given address.
func parseNetwork(addr net.Addr) string {
	switch addr := addr.(type) {
	// TCP addresses resolved through net.ResolveTCPAddr give a default
	// network of "tcp", so we'll map back the correct network for the given
	// address. This ensures that we can listen on the correct interface
	// (IPv4 vs IPv6).
	case *net.TCPAddr:
		if addr.IP.To4() != nil {
			return "tcp4"
		}
		return "tcp6"

	default:
		return addr.Network()
	}
}

// ListenOnAddress creates a listener that listens on the given address.
func ListenOnAddress(addr net.Addr) (net.Listener, error) {
	return net.Listen(parseNetwork(addr), addr.String())
}

// TLSListenOnAddress creates a TLS listener that listens on the given address.
func TLSListenOnAddress(addr net.Addr,
	config *tls.Config) (net.Listener, error) {
	return tls.Listen(parseNetwork(addr), addr.String(), config)
}

// IsLoopback returns true if an address describes a loopback interface.
func IsLoopback(addr string) bool {
	for _, loopback := range loopBackAddrs {
		if strings.Contains(addr, loopback) {
			return true
		}
	}

	return false
}

// IsUnix returns true if an address describes an Unix socket address.
func IsUnix(addr net.Addr) bool {
	return strings.HasPrefix(addr.Network(), "unix")
}

// ParseAddressString converts an address in string format to a net.Addr that is
// compatible with lnd. UDP is not supported because lnd needs reliable
// connections. We accept a custom function to resolve any TCP addresses so
// that caller is able control exactly how resolution is performed.
func ParseAddressString(strAddress string, defaultPort string,
	tcpResolver TCPResolver) (net.Addr, error) {

	var parsedNetwork, parsedAddr string

	// Addresses can either be in network://address:port format,
	// network:address:port, address:port, or just port. We want to support
	// all possible types.
	if strings.Contains(strAddress, "://") {
		parts := strings.Split(strAddress, "://")
		parsedNetwork, parsedAddr = parts[0], parts[1]
	} else if strings.Contains(strAddress, ":") {
		parts := strings.Split(strAddress, ":")
		parsedNetwork = parts[0]
		parsedAddr = strings.Join(parts[1:], ":")
	}

	// Only TCP and Unix socket addresses are valid. We can't use IP or
	// UDP only connections for anything we do in lnd.
	switch parsedNetwork {
	case "unix", "unixpacket":
		return net.ResolveUnixAddr(parsedNetwork, parsedAddr)

	case "tcp", "tcp4", "tcp6":
		return tcpResolver(
			parsedNetwork, verifyPort(parsedAddr, defaultPort),
		)

	case "ip", "ip4", "ip6", "udp", "udp4", "udp6", "unixgram":
		return nil, fmt.Errorf("only TCP or unix socket "+
			"addresses are supported: %s", parsedAddr)

	default:
		// We'll now possibly apply the default port, use the local
		// host short circuit, or parse out an all interfaces listen.
		addrWithPort := verifyPort(strAddress, defaultPort)
		rawHost, rawPort, _ := net.SplitHostPort(addrWithPort)

		// If we reach this point, then we'll check to see if we have
		// an onion addresses, if so, we can directly pass the raw
		// address and port to create the proper address.
		if tor.IsOnionHost(rawHost) {
			portNum, err := strconv.Atoi(rawPort)
			if err != nil {
				return nil, err
			}

			return &tor.OnionAddr{
				OnionService: rawHost,
				Port:         portNum,
			}, nil
		}

		// Otherwise, we'll attempt the resolve the host. The Tor
		// resolver is unable to resolve local addresses, so we'll use
		// the system resolver instead.
		if rawHost == "" || IsLoopback(rawHost) {
			return net.ResolveTCPAddr("tcp", addrWithPort)
		}

		return tcpResolver("tcp", addrWithPort)
	}
}

// ParseLNAddressString converts a string of the form <pubkey>@<addr> into an
// lnwire.NetAddress. The <pubkey> must be presented in hex, and result in a
// 33-byte, compressed public key that lies on the secp256k1 curve. The <addr>
// may be any address supported by ParseAddressString. If no port is specified,
// the defaultPort will be used. Any tcp addresses that need resolving will be
// resolved using the custom TCPResolver.
func ParseLNAddressString(strAddress string, defaultPort string,
	tcpResolver TCPResolver) (*lnwire.NetAddress, error) {

	// Split the address string around the @ sign.
	parts := strings.Split(strAddress, "@")

	// The string is malformed if there are not exactly two parts.
	if len(parts) != 2 {
		return nil, fmt.Errorf("invalid lightning address %s: "+
			"must be of the form <pubkey-hex>@<addr>", strAddress)
	}

	// Now, take the first portion as the hex pubkey, and the latter as the
	// address string.
	parsedPubKey, parsedAddr := parts[0], parts[1]

	// Decode the hex pubkey to get the raw compressed pubkey bytes.
	pubKeyBytes, err := hex.DecodeString(parsedPubKey)
	if err != nil {
		return nil, fmt.Errorf("invalid lightning address pubkey: %v", err)
	}

	// The compressed pubkey should have a length of exactly 33 bytes.
	if len(pubKeyBytes) != 33 {
		return nil, fmt.Errorf("invalid lightning address pubkey: "+
			"length must be 33 bytes, found %d", len(pubKeyBytes))
	}

	// Parse the pubkey bytes to verify that it corresponds to valid public
	// key on the secp256k1 curve.
	pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256())
	if err != nil {
		return nil, fmt.Errorf("invalid lightning address pubkey: %v", err)
	}

	// Finally, parse the address string using our generic address parser.
	addr, err := ParseAddressString(parsedAddr, defaultPort, tcpResolver)
	if err != nil {
		return nil, fmt.Errorf("invalid lightning address address: %v", err)
	}

	return &lnwire.NetAddress{
		IdentityKey: pubKey,
		Address:     addr,
	}, nil
}

// verifyPort makes sure that an address string has both a host and a port. If
// there is no port found, the default port is appended. If the address is just
// a port, then we'll assume that the user is using the short cut to specify a
// localhost:port address.
func verifyPort(address string, defaultPort string) string {
	host, port, err := net.SplitHostPort(address)
	if err != nil {
		// If the address itself is just an integer, then we'll assume
		// that we're mapping this directly to a localhost:port pair.
		// This ensures we maintain the legacy behavior.
		if _, err := strconv.Atoi(address); err == nil {
			return net.JoinHostPort("localhost", address)
		}

		// Otherwise, we'll assume that the address just failed to
		// attach its own port, so we'll use the default port. In the
		// case of IPv6 addresses, if the host is already surrounded by
		// brackets, then we'll avoid using the JoinHostPort function,
		// since it will always add a pair of brackets.
		if strings.HasPrefix(address, "[") {
			return address + ":" + defaultPort
		}
		return net.JoinHostPort(address, defaultPort)
	}

	// In the case that both the host and port are empty, we'll use the
	// default port.
	if host == "" && port == "" {
		return ":" + defaultPort
	}

	return address
}

// ClientAddressDialer creates a gRPC dialer that can also dial unix socket
// addresses instead of just TCP addresses.
func ClientAddressDialer(defaultPort string) func(string, time.Duration) (net.Conn, error) {
	return func(addr string, timeout time.Duration) (net.Conn, error) {
		parsedAddr, err := ParseAddressString(
			addr, defaultPort, net.ResolveTCPAddr,
		)
		if err != nil {
			return nil, err
		}

		return net.DialTimeout(
			parsedAddr.Network(), parsedAddr.String(), timeout,
		)
	}
}
<|endoftext|>


<commit_before>package systray

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"syscall"
	"unsafe"
)

var (
	iconFiles = make([]*os.File, 0)
	dllDir    = path.Join(os.Getenv("APPDATA"), "systray")
	dllFile   = path.Join(dllDir, "systray.dll")

	mod                      = syscall.NewLazyDLL(dllFile)
	_nativeLoop              = mod.NewProc("nativeLoop")
	_quit                    = mod.NewProc("quit")
	_setIcon                 = mod.NewProc("setIcon")
	_setTitle                = mod.NewProc("setTitle")
	_setTooltip              = mod.NewProc("setTooltip")
	_add_or_update_menu_item = mod.NewProc("add_or_update_menu_item")
)

func init() {
	// Write DLL to file
	b, err := Asset("systray.dll")
	if err != nil {
		panic(fmt.Errorf("Unable to read systray.dll: %v", err))
	}

	err = os.MkdirAll(dllDir, 755)
	if err != nil {
		panic(fmt.Errorf("Unable to create directory %v to hold systray.dll: %v", dllDir, err))
	}

	err = ioutil.WriteFile(dllFile, b, 0644)
	if err != nil {
		panic(fmt.Errorf("Unable to save systray.dll to %v: %v", dllFile, err))
	}
}

func nativeLoop() {
	_nativeLoop.Call(
		syscall.NewCallback(systray_ready),
		syscall.NewCallback(systray_menu_item_selected))
}

func quit() {
	_quit.Call()
	for _, f := range iconFiles {
		err := os.Remove(f.Name())
		if err != nil {
			log.Debugf("Unable to delete temporary icon file %v: %v", f.Name(), err)
		}
	}
}

// SetIcon sets the systray icon.
// iconBytes should be the content of .ico for windows and .ico/.jpg/.png
// for other platforms.
func SetIcon(iconBytes []byte) {
	f, err := ioutil.TempFile("", "systray_temp_icon")
	if err != nil {
		log.Errorf("Unable to create temp icon: %v", err)
		return
	}
	defer f.Close()
	_, err = f.Write(iconBytes)
	if err != nil {
		log.Errorf("Unable to write icon to temp file %v: %v", f.Name(), f)
		return
	}
	f.Close()
	name, err := strPtr(f.Name())
	if err != nil {
		log.Errorf("Unable to convert name to string pointer: %v", err)
		return
	}
	_setIcon.Call(name)
}

// SetTitle sets the systray title, only available on Mac.
func SetTitle(title string) {
	// do nothing
}

// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon,
// only available on Mac and Windows.
func SetTooltip(tooltip string) {
	t, err := strPtr(tooltip)
	if err != nil {
		log.Errorf("Unable to convert tooltip to string pointer: %v", err)
		return
	}
	_setTooltip.Call(t)
}

func addOrUpdateMenuItem(item *MenuItem) {
	var disabled = 0
	if item.disabled {
		disabled = 1
	}
	var checked = 0
	if item.checked {
		checked = 1
	}
	title, err := strPtr(item.title)
	if err != nil {
		log.Errorf("Unable to convert title to string pointer: %v", err)
		return
	}
	tooltip, err := strPtr(item.tooltip)
	if err != nil {
		log.Errorf("Unable to convert tooltip to string pointer: %v", err)
		return
	}
	_add_or_update_menu_item.Call(
		uintptr(item.id),
		title,
		tooltip,
		uintptr(disabled),
		uintptr(checked),
	)
}

func strPtr(s string) (uintptr, error) {
	bp, err := syscall.BytePtrFromString(s)
	if err != nil {
		return 0, err
	}
	return uintptr(unsafe.Pointer(bp)), nil
}

func systray_ready() uintptr {
	systrayReady()
	return 0
}

func systray_menu_item_selected(id uintptr) uintptr {
	systrayMenuItemSelected(int32(id))
	return 0
}
<commit_msg>Fixed mode on directory containing dll<commit_after>package systray

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"syscall"
	"unsafe"
)

var (
	iconFiles = make([]*os.File, 0)
	dllDir    = path.Join(os.Getenv("APPDATA"), "systray")
	dllFile   = path.Join(dllDir, "systray.dll")

	mod                      = syscall.NewLazyDLL(dllFile)
	_nativeLoop              = mod.NewProc("nativeLoop")
	_quit                    = mod.NewProc("quit")
	_setIcon                 = mod.NewProc("setIcon")
	_setTitle                = mod.NewProc("setTitle")
	_setTooltip              = mod.NewProc("setTooltip")
	_add_or_update_menu_item = mod.NewProc("add_or_update_menu_item")
)

func init() {
	// Write DLL to file
	b, err := Asset("systray.dll")
	if err != nil {
		panic(fmt.Errorf("Unable to read systray.dll: %v", err))
	}

	err = os.MkdirAll(dllDir, 0755)
	if err != nil {
		panic(fmt.Errorf("Unable to create directory %v to hold systray.dll: %v", dllDir, err))
	}

	err = ioutil.WriteFile(dllFile, b, 0644)
	if err != nil {
		panic(fmt.Errorf("Unable to save systray.dll to %v: %v", dllFile, err))
	}
}

func nativeLoop() {
	_nativeLoop.Call(
		syscall.NewCallback(systray_ready),
		syscall.NewCallback(systray_menu_item_selected))
}

func quit() {
	_quit.Call()
	for _, f := range iconFiles {
		err := os.Remove(f.Name())
		if err != nil {
			log.Debugf("Unable to delete temporary icon file %v: %v", f.Name(), err)
		}
	}
}

// SetIcon sets the systray icon.
// iconBytes should be the content of .ico for windows and .ico/.jpg/.png
// for other platforms.
func SetIcon(iconBytes []byte) {
	f, err := ioutil.TempFile("", "systray_temp_icon")
	if err != nil {
		log.Errorf("Unable to create temp icon: %v", err)
		return
	}
	defer f.Close()
	_, err = f.Write(iconBytes)
	if err != nil {
		log.Errorf("Unable to write icon to temp file %v: %v", f.Name(), f)
		return
	}
	f.Close()
	name, err := strPtr(f.Name())
	if err != nil {
		log.Errorf("Unable to convert name to string pointer: %v", err)
		return
	}
	_setIcon.Call(name)
}

// SetTitle sets the systray title, only available on Mac.
func SetTitle(title string) {
	// do nothing
}

// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon,
// only available on Mac and Windows.
func SetTooltip(tooltip string) {
	t, err := strPtr(tooltip)
	if err != nil {
		log.Errorf("Unable to convert tooltip to string pointer: %v", err)
		return
	}
	_setTooltip.Call(t)
}

func addOrUpdateMenuItem(item *MenuItem) {
	var disabled = 0
	if item.disabled {
		disabled = 1
	}
	var checked = 0
	if item.checked {
		checked = 1
	}
	title, err := strPtr(item.title)
	if err != nil {
		log.Errorf("Unable to convert title to string pointer: %v", err)
		return
	}
	tooltip, err := strPtr(item.tooltip)
	if err != nil {
		log.Errorf("Unable to convert tooltip to string pointer: %v", err)
		return
	}
	_add_or_update_menu_item.Call(
		uintptr(item.id),
		title,
		tooltip,
		uintptr(disabled),
		uintptr(checked),
	)
}

func strPtr(s string) (uintptr, error) {
	bp, err := syscall.BytePtrFromString(s)
	if err != nil {
		return 0, err
	}
	return uintptr(unsafe.Pointer(bp)), nil
}

func systray_ready() uintptr {
	systrayReady()
	return 0
}

func systray_menu_item_selected(id uintptr) uintptr {
	systrayMenuItemSelected(int32(id))
	return 0
}
<|endoftext|>


<commit_before>// Package strmangle is a collection of string manipulation functions.
// Primarily used by boil and templates for code generation.
// Because it is focused on pipelining inside templates
// you will see some odd parameter ordering.
package strmangle

import (
	"fmt"
	"math"
	"regexp"
	"strings"
	"sync"
)

var (
	idAlphabet    = []byte("abcdefghijklmnopqrstuvwxyz")
	smartQuoteRgx = regexp.MustCompile(`^(?i)"?[a-z_][_a-z0-9]*"?(\."?[_a-z][_a-z0-9]*"?)*(\.\*)?$`)
)

var uppercaseWords = map[string]struct{}{
	"guid": struct{}{},
	"id":   struct{}{},
	"uid":  struct{}{},
	"uuid": struct{}{},
}

func init() {
	// Our Boil inflection Ruleset does not include uncountable inflections.
	// This way, people using words like Sheep will not have
	// collisions with their model name (Sheep) and their
	// function name (Sheep()). Instead, it will
	// use the regular inflection rules: Sheep, Sheeps().
	boilRuleset = newBoilRuleset()
}

// IdentQuote attempts to quote simple identifiers in SQL tatements
func IdentQuote(s string) string {
	if strings.ToLower(s) == "null" {
		return s
	}

	if m := smartQuoteRgx.MatchString(s); m != true {
		return s
	}

	buf := GetBuffer()
	defer PutBuffer(buf)

	splits := strings.Split(s, ".")
	for i, split := range splits {
		if i != 0 {
			buf.WriteByte('.')
		}

		if strings.HasPrefix(split, `"`) || strings.HasSuffix(split, `"`) || split == "*" {
			buf.WriteString(split)
			continue
		}

		buf.WriteByte('"')
		buf.WriteString(split)
		buf.WriteByte('"')
	}

	return buf.String()
}

// IdentQuoteSlice applies IdentQuote to a slice.
func IdentQuoteSlice(s []string) []string {
	if len(s) == 0 {
		return s
	}

	strs := make([]string, len(s))
	for i, str := range s {
		strs[i] = IdentQuote(str)
	}

	return strs
}

// Identifier is a base conversion from Base 10 integers to Base 26
// integers that are represented by an alphabet from a-z
// See tests for example outputs.
func Identifier(in int) string {
	ln := len(idAlphabet)
	var n int
	if in == 0 {
		n = 1
	} else {
		n = 1 + int(math.Log(float64(in))/math.Log(float64(ln)))
	}

	cols := GetBuffer()
	defer PutBuffer(cols)

	for i := 0; i < n; i++ {
		divisor := int(math.Pow(float64(ln), float64(n-i-1)))
		rem := in / divisor
		cols.WriteByte(idAlphabet[rem])

		in -= rem * divisor
	}

	return cols.String()
}

// Plural converts singular words to plural words (eg: person to people)
func Plural(name string) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	splits := strings.Split(name, "_")

	for i := 0; i < len(splits); i++ {
		if i != 0 {
			buf.WriteByte('_')
		}

		if i == len(splits)-1 {
			buf.WriteString(boilRuleset.Pluralize(splits[len(splits)-1]))
			break
		}

		buf.WriteString(splits[i])
	}

	return buf.String()
}

// Singular converts plural words to singular words (eg: people to person)
func Singular(name string) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	splits := strings.Split(name, "_")

	for i := 0; i < len(splits); i++ {
		if i != 0 {
			buf.WriteByte('_')
		}

		if i == len(splits)-1 {
			buf.WriteString(boilRuleset.Singularize(splits[len(splits)-1]))
			break
		}

		buf.WriteString(splits[i])
	}

	return buf.String()
}

// titleCaseCache holds the mapping of title cases.
// Example: map["MyWord"] == "my_word"
var (
	mut            sync.RWMutex
	titleCaseCache = map[string]string{}
)

// TitleCase changes a snake-case variable name
// into a go styled object variable name of "ColumnName".
// titleCase also fully uppercases "ID" components of names, for example
// "column_name_id" to "ColumnNameID".
//
// Note: This method is ugly because it has been highly optimized,
// we found that it was a fairly large bottleneck when we were using regexp.
func TitleCase(n string) string {
	// Attempt to fetch from cache
	mut.RLock()
	val, ok := titleCaseCache[n]
	mut.RUnlock()
	if ok {
		return val
	}

	ln := len(n)
	name := []byte(n)
	buf := GetBuffer()

	start := 0
	end := 0
	for start < ln {
		// Find the start and end of the underscores to account
		// for the possibility of being multiple underscores in a row.
		if end < ln {
			if name[start] == '_' {
				start++
				end++
				continue
				// Once we have found the end of the underscores, we can
				// find the end of the first full word.
			} else if name[end] != '_' {
				end++
				continue
			}
		}

		word := name[start:end]
		wordLen := len(word)
		var vowels bool

		numStart := wordLen
		for i, c := range word {
			vowels = vowels || (c == 97 || c == 101 || c == 105 || c == 111 || c == 117 || c == 121)

			if c > 47 && c < 58 && numStart == wordLen {
				numStart = i
			}
		}

		_, match := uppercaseWords[string(word[:numStart])]

		if match || !vowels {
			// Uppercase all a-z characters
			for _, c := range word {
				if c > 96 && c < 123 {
					buf.WriteByte(c - 32)
				} else {
					buf.WriteByte(c)
				}
			}
		} else {
			if c := word[0]; c > 96 && c < 123 {
				buf.WriteByte(word[0] - 32)
				buf.Write(word[1:])
			} else {
				buf.Write(word)
			}
		}

		start = end + 1
		end = start
	}

	ret := buf.String()
	PutBuffer(buf)

	// Cache the title case result
	mut.Lock()
	titleCaseCache[n] = ret
	mut.Unlock()

	return ret
}

// CamelCase takes a variable name in the format of "var_name" and converts
// it into a go styled variable name of "varName".
// camelCase also fully uppercases "ID" components of names, for example
// "var_name_id" to "varNameID".
func CamelCase(name string) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	index := -1
	for i := 0; i < len(name); i++ {
		if name[i] != '_' {
			index = i
			break
		}
	}

	if index != -1 {
		name = name[index:]
	} else {
		return ""
	}

	index = -1
	for i := 0; i < len(name); i++ {
		if name[i] == '_' {
			index = i
			break
		}
	}

	if index == -1 {
		buf.WriteString(name)
	} else {
		buf.WriteString(name[:index])
		buf.WriteString(TitleCase(name[index+1:]))
	}

	return buf.String()
}

// TitleCaseIdentifier splits on dots and then titlecases each fragment.
// map titleCase (split c ".")
func TitleCaseIdentifier(id string) string {
	nextDot := strings.IndexByte(id, '.')
	if nextDot < 0 {
		return TitleCase(id)
	}

	buf := GetBuffer()
	lastDot := 0
	ln := len(id)
	addDots := false

	for i := 0; nextDot >= 0; i++ {
		fmt.Println(lastDot, nextDot)
		fragment := id[lastDot:nextDot]

		titled := TitleCase(fragment)

		if addDots {
			buf.WriteByte('.')
		}
		buf.WriteString(titled)
		addDots = true

		if nextDot == ln {
			break
		}

		lastDot = nextDot + 1
		if nextDot = strings.IndexByte(id[lastDot:], '.'); nextDot >= 0 {
			nextDot += lastDot
		} else {
			nextDot = ln
		}
	}

	PutBuffer(buf)
	return buf.String()
}

// MakeStringMap converts a map[string]string into the format:
// "key": "value", "key": "value"
func MakeStringMap(types map[string]string) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	c := 0
	for k, v := range types {
		buf.WriteString(fmt.Sprintf(`"%s": "%s"`, k, v))
		if c < len(types)-1 {
			buf.WriteString(", ")
		}

		c++
	}

	return buf.String()
}

// StringMap maps a function over a slice of strings.
func StringMap(modifier func(string) string, strs []string) []string {
	ret := make([]string, len(strs))

	for i, str := range strs {
		ret[i] = modifier(str)
	}

	return ret
}

// PrefixStringSlice with the given str.
func PrefixStringSlice(str string, strs []string) []string {
	ret := make([]string, len(strs))

	for i, s := range strs {
		ret[i] = fmt.Sprintf("%s%s", str, s)
	}

	return ret
}

// Placeholders generates the SQL statement placeholders for in queries.
// For example, ($1,$2,$3),($4,$5,$6) etc.
// It will start counting placeholders at "start".
func Placeholders(count int, start int, group int) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	if start == 0 || group == 0 {
		panic("Invalid start or group numbers supplied.")
	}

	if group > 1 {
		buf.WriteByte('(')
	}
	for i := 0; i < count; i++ {
		if i != 0 {
			if group > 1 && i%group == 0 {
				buf.WriteString("),(")
			} else {
				buf.WriteByte(',')
			}
		}
		buf.WriteString(fmt.Sprintf("$%d", start+i))
	}
	if group > 1 {
		buf.WriteByte(')')
	}

	return buf.String()
}

// SetParamNames takes a slice of columns and returns a comma separated
// list of parameter names for a template statement SET clause.
// eg: "col1"=$1, "col2"=$2, "col3"=$3
func SetParamNames(columns []string) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	for i, c := range columns {
		buf.WriteString(fmt.Sprintf(`"%s"=$%d`, c, i+1))
		if i < len(columns)-1 {
			buf.WriteString(", ")
		}
	}

	return buf.String()
}

// WhereClause returns the where clause using start as the $ flag index
// For example, if start was 2 output would be: "colthing=$2 AND colstuff=$3"
func WhereClause(start int, cols []string) string {
	if start == 0 {
		panic("0 is not a valid start number for whereClause")
	}

	buf := GetBuffer()
	defer PutBuffer(buf)

	for i, c := range cols {
		buf.WriteString(fmt.Sprintf(`"%s"=$%d`, c, start+i))
		if i < len(cols)-1 {
			buf.WriteString(" AND ")
		}
	}

	return buf.String()
}

// JoinSlices merges two string slices of equal length
func JoinSlices(sep string, a, b []string) []string {
	lna, lnb := len(a), len(b)
	if lna != lnb {
		panic("joinSlices: can only merge slices of same length")
	} else if lna == 0 {
		return nil
	}

	ret := make([]string, len(a))
	for i, elem := range a {
		ret[i] = fmt.Sprintf("%s%s%s", elem, sep, b[i])
	}

	return ret
}

// StringSliceMatch returns true if the length of both
// slices is the same, and the elements of both slices are the same.
// The elements can be in any order.
func StringSliceMatch(a []string, b []string) bool {
	if len(a) != len(b) {
		return false
	}

	for _, aval := range a {
		found := false
		for _, bval := range b {
			if bval == aval {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}

	return true
}

// ContainsAny returns true if any of the passed in strings are
// found in the passed in string slice
func ContainsAny(a []string, finds ...string) bool {
	for _, s := range a {
		for _, find := range finds {
			if s == find {
				return true
			}
		}
	}

	return false
}
<commit_msg>Remove titlecaseidentifier printf spam<commit_after>// Package strmangle is a collection of string manipulation functions.
// Primarily used by boil and templates for code generation.
// Because it is focused on pipelining inside templates
// you will see some odd parameter ordering.
package strmangle

import (
	"fmt"
	"math"
	"regexp"
	"strings"
	"sync"
)

var (
	idAlphabet    = []byte("abcdefghijklmnopqrstuvwxyz")
	smartQuoteRgx = regexp.MustCompile(`^(?i)"?[a-z_][_a-z0-9]*"?(\."?[_a-z][_a-z0-9]*"?)*(\.\*)?$`)
)

var uppercaseWords = map[string]struct{}{
	"guid": struct{}{},
	"id":   struct{}{},
	"uid":  struct{}{},
	"uuid": struct{}{},
}

func init() {
	// Our Boil inflection Ruleset does not include uncountable inflections.
	// This way, people using words like Sheep will not have
	// collisions with their model name (Sheep) and their
	// function name (Sheep()). Instead, it will
	// use the regular inflection rules: Sheep, Sheeps().
	boilRuleset = newBoilRuleset()
}

// IdentQuote attempts to quote simple identifiers in SQL tatements
func IdentQuote(s string) string {
	if strings.ToLower(s) == "null" {
		return s
	}

	if m := smartQuoteRgx.MatchString(s); m != true {
		return s
	}

	buf := GetBuffer()
	defer PutBuffer(buf)

	splits := strings.Split(s, ".")
	for i, split := range splits {
		if i != 0 {
			buf.WriteByte('.')
		}

		if strings.HasPrefix(split, `"`) || strings.HasSuffix(split, `"`) || split == "*" {
			buf.WriteString(split)
			continue
		}

		buf.WriteByte('"')
		buf.WriteString(split)
		buf.WriteByte('"')
	}

	return buf.String()
}

// IdentQuoteSlice applies IdentQuote to a slice.
func IdentQuoteSlice(s []string) []string {
	if len(s) == 0 {
		return s
	}

	strs := make([]string, len(s))
	for i, str := range s {
		strs[i] = IdentQuote(str)
	}

	return strs
}

// Identifier is a base conversion from Base 10 integers to Base 26
// integers that are represented by an alphabet from a-z
// See tests for example outputs.
func Identifier(in int) string {
	ln := len(idAlphabet)
	var n int
	if in == 0 {
		n = 1
	} else {
		n = 1 + int(math.Log(float64(in))/math.Log(float64(ln)))
	}

	cols := GetBuffer()
	defer PutBuffer(cols)

	for i := 0; i < n; i++ {
		divisor := int(math.Pow(float64(ln), float64(n-i-1)))
		rem := in / divisor
		cols.WriteByte(idAlphabet[rem])

		in -= rem * divisor
	}

	return cols.String()
}

// Plural converts singular words to plural words (eg: person to people)
func Plural(name string) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	splits := strings.Split(name, "_")

	for i := 0; i < len(splits); i++ {
		if i != 0 {
			buf.WriteByte('_')
		}

		if i == len(splits)-1 {
			buf.WriteString(boilRuleset.Pluralize(splits[len(splits)-1]))
			break
		}

		buf.WriteString(splits[i])
	}

	return buf.String()
}

// Singular converts plural words to singular words (eg: people to person)
func Singular(name string) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	splits := strings.Split(name, "_")

	for i := 0; i < len(splits); i++ {
		if i != 0 {
			buf.WriteByte('_')
		}

		if i == len(splits)-1 {
			buf.WriteString(boilRuleset.Singularize(splits[len(splits)-1]))
			break
		}

		buf.WriteString(splits[i])
	}

	return buf.String()
}

// titleCaseCache holds the mapping of title cases.
// Example: map["MyWord"] == "my_word"
var (
	mut            sync.RWMutex
	titleCaseCache = map[string]string{}
)

// TitleCase changes a snake-case variable name
// into a go styled object variable name of "ColumnName".
// titleCase also fully uppercases "ID" components of names, for example
// "column_name_id" to "ColumnNameID".
//
// Note: This method is ugly because it has been highly optimized,
// we found that it was a fairly large bottleneck when we were using regexp.
func TitleCase(n string) string {
	// Attempt to fetch from cache
	mut.RLock()
	val, ok := titleCaseCache[n]
	mut.RUnlock()
	if ok {
		return val
	}

	ln := len(n)
	name := []byte(n)
	buf := GetBuffer()

	start := 0
	end := 0
	for start < ln {
		// Find the start and end of the underscores to account
		// for the possibility of being multiple underscores in a row.
		if end < ln {
			if name[start] == '_' {
				start++
				end++
				continue
				// Once we have found the end of the underscores, we can
				// find the end of the first full word.
			} else if name[end] != '_' {
				end++
				continue
			}
		}

		word := name[start:end]
		wordLen := len(word)
		var vowels bool

		numStart := wordLen
		for i, c := range word {
			vowels = vowels || (c == 97 || c == 101 || c == 105 || c == 111 || c == 117 || c == 121)

			if c > 47 && c < 58 && numStart == wordLen {
				numStart = i
			}
		}

		_, match := uppercaseWords[string(word[:numStart])]

		if match || !vowels {
			// Uppercase all a-z characters
			for _, c := range word {
				if c > 96 && c < 123 {
					buf.WriteByte(c - 32)
				} else {
					buf.WriteByte(c)
				}
			}
		} else {
			if c := word[0]; c > 96 && c < 123 {
				buf.WriteByte(word[0] - 32)
				buf.Write(word[1:])
			} else {
				buf.Write(word)
			}
		}

		start = end + 1
		end = start
	}

	ret := buf.String()
	PutBuffer(buf)

	// Cache the title case result
	mut.Lock()
	titleCaseCache[n] = ret
	mut.Unlock()

	return ret
}

// CamelCase takes a variable name in the format of "var_name" and converts
// it into a go styled variable name of "varName".
// camelCase also fully uppercases "ID" components of names, for example
// "var_name_id" to "varNameID".
func CamelCase(name string) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	index := -1
	for i := 0; i < len(name); i++ {
		if name[i] != '_' {
			index = i
			break
		}
	}

	if index != -1 {
		name = name[index:]
	} else {
		return ""
	}

	index = -1
	for i := 0; i < len(name); i++ {
		if name[i] == '_' {
			index = i
			break
		}
	}

	if index == -1 {
		buf.WriteString(name)
	} else {
		buf.WriteString(name[:index])
		buf.WriteString(TitleCase(name[index+1:]))
	}

	return buf.String()
}

// TitleCaseIdentifier splits on dots and then titlecases each fragment.
// map titleCase (split c ".")
func TitleCaseIdentifier(id string) string {
	nextDot := strings.IndexByte(id, '.')
	if nextDot < 0 {
		return TitleCase(id)
	}

	buf := GetBuffer()
	lastDot := 0
	ln := len(id)
	addDots := false

	for i := 0; nextDot >= 0; i++ {
		fragment := id[lastDot:nextDot]

		titled := TitleCase(fragment)

		if addDots {
			buf.WriteByte('.')
		}
		buf.WriteString(titled)
		addDots = true

		if nextDot == ln {
			break
		}

		lastDot = nextDot + 1
		if nextDot = strings.IndexByte(id[lastDot:], '.'); nextDot >= 0 {
			nextDot += lastDot
		} else {
			nextDot = ln
		}
	}

	PutBuffer(buf)
	return buf.String()
}

// MakeStringMap converts a map[string]string into the format:
// "key": "value", "key": "value"
func MakeStringMap(types map[string]string) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	c := 0
	for k, v := range types {
		buf.WriteString(fmt.Sprintf(`"%s": "%s"`, k, v))
		if c < len(types)-1 {
			buf.WriteString(", ")
		}

		c++
	}

	return buf.String()
}

// StringMap maps a function over a slice of strings.
func StringMap(modifier func(string) string, strs []string) []string {
	ret := make([]string, len(strs))

	for i, str := range strs {
		ret[i] = modifier(str)
	}

	return ret
}

// PrefixStringSlice with the given str.
func PrefixStringSlice(str string, strs []string) []string {
	ret := make([]string, len(strs))

	for i, s := range strs {
		ret[i] = fmt.Sprintf("%s%s", str, s)
	}

	return ret
}

// Placeholders generates the SQL statement placeholders for in queries.
// For example, ($1,$2,$3),($4,$5,$6) etc.
// It will start counting placeholders at "start".
func Placeholders(count int, start int, group int) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	if start == 0 || group == 0 {
		panic("Invalid start or group numbers supplied.")
	}

	if group > 1 {
		buf.WriteByte('(')
	}
	for i := 0; i < count; i++ {
		if i != 0 {
			if group > 1 && i%group == 0 {
				buf.WriteString("),(")
			} else {
				buf.WriteByte(',')
			}
		}
		buf.WriteString(fmt.Sprintf("$%d", start+i))
	}
	if group > 1 {
		buf.WriteByte(')')
	}

	return buf.String()
}

// SetParamNames takes a slice of columns and returns a comma separated
// list of parameter names for a template statement SET clause.
// eg: "col1"=$1, "col2"=$2, "col3"=$3
func SetParamNames(columns []string) string {
	buf := GetBuffer()
	defer PutBuffer(buf)

	for i, c := range columns {
		buf.WriteString(fmt.Sprintf(`"%s"=$%d`, c, i+1))
		if i < len(columns)-1 {
			buf.WriteString(", ")
		}
	}

	return buf.String()
}

// WhereClause returns the where clause using start as the $ flag index
// For example, if start was 2 output would be: "colthing=$2 AND colstuff=$3"
func WhereClause(start int, cols []string) string {
	if start == 0 {
		panic("0 is not a valid start number for whereClause")
	}

	buf := GetBuffer()
	defer PutBuffer(buf)

	for i, c := range cols {
		buf.WriteString(fmt.Sprintf(`"%s"=$%d`, c, start+i))
		if i < len(cols)-1 {
			buf.WriteString(" AND ")
		}
	}

	return buf.String()
}

// JoinSlices merges two string slices of equal length
func JoinSlices(sep string, a, b []string) []string {
	lna, lnb := len(a), len(b)
	if lna != lnb {
		panic("joinSlices: can only merge slices of same length")
	} else if lna == 0 {
		return nil
	}

	ret := make([]string, len(a))
	for i, elem := range a {
		ret[i] = fmt.Sprintf("%s%s%s", elem, sep, b[i])
	}

	return ret
}

// StringSliceMatch returns true if the length of both
// slices is the same, and the elements of both slices are the same.
// The elements can be in any order.
func StringSliceMatch(a []string, b []string) bool {
	if len(a) != len(b) {
		return false
	}

	for _, aval := range a {
		found := false
		for _, bval := range b {
			if bval == aval {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}

	return true
}

// ContainsAny returns true if any of the passed in strings are
// found in the passed in string slice
func ContainsAny(a []string, finds ...string) bool {
	for _, s := range a {
		for _, find := range finds {
			if s == find {
				return true
			}
		}
	}

	return false
}
<|endoftext|>


<commit_before>// Copyright 2017 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains an http.Handler implementation that serves Upspin release
// archives (tar.gz and zip files).

package frontend

import (
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"fmt"
	"net/http"
	"regexp"
	"sort"
	"strings"
	"sync"
	"time"

	"upspin.io/client"
	"upspin.io/log"
	"upspin.io/path"
	"upspin.io/upspin"
)

// osArchHuman describes operating system and processor architectures
// in human-readable form.
var osArchHuman = map[string]string{
	"darwin_amd64":  "macOS 64-bit x86",
	"linux_amd64":   "Linux 64-bit x86",
	"windows_amd64": "Windows 64-bit x86",
}

// osArchFormat associates archive formats with os/arch combinations.
var osArchFormat = map[string]string{
	"darwin_amd64":  "tar.gz",
	"linux_amd64":   "tar.gz",
	"windows_amd64": "zip",
}

const (
	// updateDownloadsInterval is the interval between refreshing the list
	// of available binary releases.
	updateDownloadsInterval = 1 * time.Minute

	// releaseUser is the tree in which the releases are kept.
	releaseUser = "release@upspin.io"

	// releasePath is the path to the latest releases.
	releasePath = releaseUser + "/latest/"

	// downloadPath is the HTTP base path for the download handler.
	downloadPath = "/dl/"

	// archiveExpr defines the file name for the release archives.
	archiveExpr = `^upspin\.([a-z0-9]+_[a-z0-9]+).(tar\.gz|zip)$`

	// archiveFormat is a format string that formats a release archives file
	// name. Its arguments are the os_arch combination and archive format.
	archiveFormat = "upspin.%s.%s"
)

var archiveRE = regexp.MustCompile(archiveExpr)

// newDownloadHandler initializes and returns a new downloadHandler.
func newDownloadHandler(cfg upspin.Config) http.Handler {
	h := &downloadHandler{
		client:  client.New(cfg),
		latest:  make(map[string]time.Time),
		archive: make(map[string]*archive),
	}
	go func() {
		for {
			err := h.updateArchives()
			if err != nil {
				log.Error.Printf("download: error updating archives: %v", err)
			}
			time.Sleep(updateDownloadsInterval)
		}
	}()
	return h
}

// downloadHandler is an http.Handler that serves a directory of available
// Upspin release binaries and archives of those binaries.
// It keeps the latest archives bytes for each os-arch combination in memory.
type downloadHandler struct {
	client upspin.Client

	mu      sync.RWMutex
	latest  map[string]time.Time // [os_arch]last-update-time
	archive map[string]*archive  // [os_arch]archive
}

func (h *downloadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	p := strings.TrimPrefix(r.URL.Path, downloadPath)

	if p == "" {
		// Show listing of available releases.
		var archives []*archive
		h.mu.RLock()
		for _, a := range h.archive {
			archives = append(archives, a)
		}
		h.mu.RUnlock()
		sort.Slice(archives, func(i, j int) bool {
			return archives[i].osArch < archives[j].osArch
		})

		err := downloadTmpl.Execute(w, pageData{
			Content: archives,
		})
		if err != nil {
			log.Error.Printf("download: rendering downloadTmpl: %v", err)
		}
		return
	}

	// Parse the request path to see if it's an archive.
	m := archiveRE.FindStringSubmatch(p)
	if m == nil {
		http.NotFound(w, r)
		return
	}
	osArch := m[1]

	h.mu.RLock()
	a := h.archive[osArch]
	h.mu.RUnlock()
	if a == nil || p != a.FileName() {
		http.NotFound(w, r)
		return
	}
	w.Write(a.data)
}

// updateArchives refreshes the list of release binaries in releasePath
// and updates the latest and archive maps appropriately.
func (h *downloadHandler) updateArchives() error {
	// Fetch the available os_arch combinations.
	des, err := h.client.Glob(releasePath + "/*")
	if err != nil {
		return err
	}

	// For each os_arch, check for new releases and build archives.
	for _, de := range des {
		p, _ := path.Parse(de.Name)
		osArch := p.Elem(p.NElem() - 1)

		des, err := h.client.Glob(releasePath + osArch + "/*")
		if err != nil {
			return err
		}

		h.mu.RLock()
		latest := h.latest[osArch]
		h.mu.RUnlock()

		updated := false
		for _, de := range des {
			if t := de.Time.Go(); t.After(latest) {
				latest = t
				updated = true
			}
		}
		if !updated {
			continue
		}

		h.mu.Lock()
		h.latest[osArch] = latest
		h.mu.Unlock()

		// Build the archive in the background.
		log.Info.Printf("download: building archive for %v released at %v", osArch, latest)
		go h.buildArchive(osArch, des)
	}

	return nil
}

// buildArchive builds an archive for the given osArch containing the given
// Upspin commands. If it succeeds it adds the archive to the list of downloads.
func (h *downloadHandler) buildArchive(osArch string, des []*upspin.DirEntry) {
	a, err := newArchive(osArch, h.client, des)
	if err != nil {
		log.Error.Printf("download: error building archive for %v: %v", osArch, err)

		// Rebuild on the next update.
		h.mu.Lock()
		h.latest[osArch] = time.Time{}
		h.mu.Unlock()
	} else {
		log.Info.Printf("download: built new archive for %v", osArch)

		// Add the archive to the list.
		h.mu.Lock()
		h.archive[osArch] = a
		h.mu.Unlock()
	}
}

// archive represents a release archive and its contents.
type archive struct {
	osArch string
	data   []byte
}

// FileName returns the file name of the archive.
func (a *archive) FileName() string {
	return fmt.Sprintf(archiveFormat, a.osArch, osArchFormat[a.osArch])
}

// OSArch returns the operating system and processor architecture for this
// archive in human-readable form.
func (a *archive) OSArch() string {
	return osArchHuman[a.osArch]
}

// SizeMB returns the size of the archive in human-readable form.
func (a *archive) Size() string {
	return fmt.Sprintf("%.1fMB", float64(len(a.data))/(1<<20))
}

// newArchive fetches DirEntries using the given Client and assembles a gzipped
// tar file containing those files, and returns the resulting archive.
func newArchive(osArch string, c upspin.Client, des []*upspin.DirEntry) (*archive, error) {
	var buf bytes.Buffer

	// The Write and Close methods of tar, gzip and zip should not return
	// errors, as they cannnot fail when writing to a bytes.Buffer.
	switch osArchFormat[osArch] {
	case "tar.gz":
		zw := gzip.NewWriter(&buf)
		tw := tar.NewWriter(zw)
		for _, de := range des {
			b, err := c.Get(de.Name)
			if err != nil {
				return nil, err
			}

			p, _ := path.Parse(de.Name)
			tw.WriteHeader(&tar.Header{
				Name:    p.Elem(p.NElem() - 1),
				Mode:    0755,
				Size:    int64(len(b)),
				ModTime: de.Time.Go(),
			})
			tw.Write(b)
		}
		tw.Close()
		zw.Close()
	case "zip":
		zw := zip.NewWriter(&buf)
		for _, de := range des {
			b, err := c.Get(de.Name)
			if err != nil {
				return nil, err
			}

			p, _ := path.Parse(de.Name)
			w, _ := zw.Create(p.Elem(p.NElem() - 1))
			w.Write(b)
		}
		zw.Close()
	default:
		return nil, fmt.Errorf("no known archive format %v", osArch)
	}

	return &archive{osArch: osArch, data: buf.Bytes()}, nil
}
<commit_msg>serverutil/frontend: use upspin.AllFilesGlob where appropriate<commit_after>// Copyright 2017 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains an http.Handler implementation that serves Upspin release
// archives (tar.gz and zip files).

package frontend

import (
	"archive/tar"
	"archive/zip"
	"bytes"
	"compress/gzip"
	"fmt"
	"net/http"
	"regexp"
	"sort"
	"strings"
	"sync"
	"time"

	"upspin.io/client"
	"upspin.io/log"
	"upspin.io/path"
	"upspin.io/upspin"
)

// osArchHuman describes operating system and processor architectures
// in human-readable form.
var osArchHuman = map[string]string{
	"darwin_amd64":  "macOS 64-bit x86",
	"linux_amd64":   "Linux 64-bit x86",
	"windows_amd64": "Windows 64-bit x86",
}

// osArchFormat associates archive formats with os/arch combinations.
var osArchFormat = map[string]string{
	"darwin_amd64":  "tar.gz",
	"linux_amd64":   "tar.gz",
	"windows_amd64": "zip",
}

const (
	// updateDownloadsInterval is the interval between refreshing the list
	// of available binary releases.
	updateDownloadsInterval = 1 * time.Minute

	// releaseUser is the tree in which the releases are kept.
	releaseUser = "release@upspin.io"

	// releasePath is the path to the latest releases.
	releasePath = releaseUser + "/latest/"

	// downloadPath is the HTTP base path for the download handler.
	downloadPath = "/dl/"

	// archiveExpr defines the file name for the release archives.
	archiveExpr = `^upspin\.([a-z0-9]+_[a-z0-9]+).(tar\.gz|zip)$`

	// archiveFormat is a format string that formats a release archives file
	// name. Its arguments are the os_arch combination and archive format.
	archiveFormat = "upspin.%s.%s"
)

var archiveRE = regexp.MustCompile(archiveExpr)

// newDownloadHandler initializes and returns a new downloadHandler.
func newDownloadHandler(cfg upspin.Config) http.Handler {
	h := &downloadHandler{
		client:  client.New(cfg),
		latest:  make(map[string]time.Time),
		archive: make(map[string]*archive),
	}
	go func() {
		for {
			err := h.updateArchives()
			if err != nil {
				log.Error.Printf("download: error updating archives: %v", err)
			}
			time.Sleep(updateDownloadsInterval)
		}
	}()
	return h
}

// downloadHandler is an http.Handler that serves a directory of available
// Upspin release binaries and archives of those binaries.
// It keeps the latest archives bytes for each os-arch combination in memory.
type downloadHandler struct {
	client upspin.Client

	mu      sync.RWMutex
	latest  map[string]time.Time // [os_arch]last-update-time
	archive map[string]*archive  // [os_arch]archive
}

func (h *downloadHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	p := strings.TrimPrefix(r.URL.Path, downloadPath)

	if p == "" {
		// Show listing of available releases.
		var archives []*archive
		h.mu.RLock()
		for _, a := range h.archive {
			archives = append(archives, a)
		}
		h.mu.RUnlock()
		sort.Slice(archives, func(i, j int) bool {
			return archives[i].osArch < archives[j].osArch
		})

		err := downloadTmpl.Execute(w, pageData{
			Content: archives,
		})
		if err != nil {
			log.Error.Printf("download: rendering downloadTmpl: %v", err)
		}
		return
	}

	// Parse the request path to see if it's an archive.
	m := archiveRE.FindStringSubmatch(p)
	if m == nil {
		http.NotFound(w, r)
		return
	}
	osArch := m[1]

	h.mu.RLock()
	a := h.archive[osArch]
	h.mu.RUnlock()
	if a == nil || p != a.FileName() {
		http.NotFound(w, r)
		return
	}
	w.Write(a.data)
}

// updateArchives refreshes the list of release binaries in releasePath
// and updates the latest and archive maps appropriately.
func (h *downloadHandler) updateArchives() error {
	// Fetch the available os_arch combinations.
	des, err := h.client.Glob(upspin.AllFilesGlob(releasePath))
	if err != nil {
		return err
	}

	// For each os_arch, check for new releases and build archives.
	for _, de := range des {
		p, _ := path.Parse(de.Name)
		osArch := p.Elem(p.NElem() - 1)

		des, err := h.client.Glob(upspin.AllFilesGlob(upspin.PathName(releasePath + osArch)))
		if err != nil {
			return err
		}

		h.mu.RLock()
		latest := h.latest[osArch]
		h.mu.RUnlock()

		updated := false
		for _, de := range des {
			if t := de.Time.Go(); t.After(latest) {
				latest = t
				updated = true
			}
		}
		if !updated {
			continue
		}

		h.mu.Lock()
		h.latest[osArch] = latest
		h.mu.Unlock()

		// Build the archive in the background.
		log.Info.Printf("download: building archive for %v released at %v", osArch, latest)
		go h.buildArchive(osArch, des)
	}

	return nil
}

// buildArchive builds an archive for the given osArch containing the given
// Upspin commands. If it succeeds it adds the archive to the list of downloads.
func (h *downloadHandler) buildArchive(osArch string, des []*upspin.DirEntry) {
	a, err := newArchive(osArch, h.client, des)
	if err != nil {
		log.Error.Printf("download: error building archive for %v: %v", osArch, err)

		// Rebuild on the next update.
		h.mu.Lock()
		h.latest[osArch] = time.Time{}
		h.mu.Unlock()
	} else {
		log.Info.Printf("download: built new archive for %v", osArch)

		// Add the archive to the list.
		h.mu.Lock()
		h.archive[osArch] = a
		h.mu.Unlock()
	}
}

// archive represents a release archive and its contents.
type archive struct {
	osArch string
	data   []byte
}

// FileName returns the file name of the archive.
func (a *archive) FileName() string {
	return fmt.Sprintf(archiveFormat, a.osArch, osArchFormat[a.osArch])
}

// OSArch returns the operating system and processor architecture for this
// archive in human-readable form.
func (a *archive) OSArch() string {
	return osArchHuman[a.osArch]
}

// SizeMB returns the size of the archive in human-readable form.
func (a *archive) Size() string {
	return fmt.Sprintf("%.1fMB", float64(len(a.data))/(1<<20))
}

// newArchive fetches DirEntries using the given Client and assembles a gzipped
// tar file containing those files, and returns the resulting archive.
func newArchive(osArch string, c upspin.Client, des []*upspin.DirEntry) (*archive, error) {
	var buf bytes.Buffer

	// The Write and Close methods of tar, gzip and zip should not return
	// errors, as they cannnot fail when writing to a bytes.Buffer.
	switch osArchFormat[osArch] {
	case "tar.gz":
		zw := gzip.NewWriter(&buf)
		tw := tar.NewWriter(zw)
		for _, de := range des {
			b, err := c.Get(de.Name)
			if err != nil {
				return nil, err
			}

			p, _ := path.Parse(de.Name)
			tw.WriteHeader(&tar.Header{
				Name:    p.Elem(p.NElem() - 1),
				Mode:    0755,
				Size:    int64(len(b)),
				ModTime: de.Time.Go(),
			})
			tw.Write(b)
		}
		tw.Close()
		zw.Close()
	case "zip":
		zw := zip.NewWriter(&buf)
		for _, de := range des {
			b, err := c.Get(de.Name)
			if err != nil {
				return nil, err
			}

			p, _ := path.Parse(de.Name)
			w, _ := zw.Create(p.Elem(p.NElem() - 1))
			w.Write(b)
		}
		zw.Close()
	default:
		return nil, fmt.Errorf("no known archive format %v", osArch)
	}

	return &archive{osArch: osArch, data: buf.Bytes()}, nil
}
<|endoftext|>


<commit_before>package server

import (
	"context"
	"fmt"
	"io"
	"reflect"
	"strings"
	"sync"
	"time"

	gostore "github.com/micro/go-micro/v3/store"
	"github.com/micro/micro/v3/internal/namespace"
	"github.com/micro/micro/v3/service/errors"
	"github.com/micro/micro/v3/service/store"
	pb "github.com/micro/micro/v3/service/store/proto"
)

const (
	defaultDatabase = namespace.DefaultNamespace
	defaultTable    = namespace.DefaultNamespace
	internalTable   = "store"
)

type handler struct {
	// local stores cache
	sync.RWMutex
	stores map[string]bool
}

// List all the keys in a table
func (h *handler) List(ctx context.Context, req *pb.ListRequest, stream pb.Store_ListStream) error {
	// set defaults
	if req.Options == nil {
		req.Options = &pb.ListOptions{}
	}
	if len(req.Options.Database) == 0 {
		req.Options.Database = defaultDatabase
	}
	if len(req.Options.Table) == 0 {
		req.Options.Table = defaultTable
	}

	// authorize the request
	if err := namespace.Authorize(ctx, req.Options.Database); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.List", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.List", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.List", err.Error())
	}

	// setup the store
	if err := h.setupTable(req.Options.Database, req.Options.Table); err != nil {
		return errors.InternalServerError("store.Store.List", err.Error())
	}

	// setup the options
	opts := []gostore.ListOption{
		gostore.ListFrom(req.Options.Database, req.Options.Table),
	}
	if len(req.Options.Prefix) > 0 {
		opts = append(opts, gostore.ListPrefix(req.Options.Prefix))
	}
	if req.Options.Offset > 0 {
		opts = append(opts, gostore.ListOffset(uint(req.Options.Offset)))
	}
	if req.Options.Limit > 0 {
		opts = append(opts, gostore.ListLimit(uint(req.Options.Limit)))
	}

	// list from the store
	vals, err := store.List(opts...)
	if err != nil && err == gostore.ErrNotFound {
		return errors.NotFound("store.Store.List", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.List", err.Error())
	}

	// serialize the response
	// TODO: batch sync
	rsp := new(pb.ListResponse)
	for _, val := range vals {
		rsp.Keys = append(rsp.Keys, val)
	}

	err = stream.Send(rsp)
	if err == io.EOF {
		return nil
	}
	if err != nil {
		return errors.InternalServerError("store.Store.List", err.Error())
	}
	return nil
}

// Read records from the store
func (h *handler) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
	// set defaults
	if req.Options == nil {
		req.Options = &pb.ReadOptions{}
	}
	if len(req.Options.Database) == 0 {
		req.Options.Database = defaultDatabase
	}
	if len(req.Options.Table) == 0 {
		req.Options.Table = defaultTable
	}

	// authorize the request
	if err := namespace.Authorize(ctx, req.Options.Database); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.Read", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.Read", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Read", err.Error())
	}

	// setup the store
	if err := h.setupTable(req.Options.Database, req.Options.Table); err != nil {
		return errors.InternalServerError("store.Store.Read", err.Error())
	}

	// setup the options
	opts := []gostore.ReadOption{
		gostore.ReadFrom(req.Options.Database, req.Options.Table),
	}
	if req.Options.Prefix {
		opts = append(opts, gostore.ReadPrefix())
	}

	// read from the database
	vals, err := store.Read(req.Key, opts...)
	if err != nil && err == gostore.ErrNotFound {
		return errors.NotFound("store.Store.Read", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Read", err.Error())
	}

	// serialize the result
	for _, val := range vals {
		metadata := make(map[string]*pb.Field)
		for k, v := range val.Metadata {
			metadata[k] = &pb.Field{
				Type:  reflect.TypeOf(v).String(),
				Value: fmt.Sprintf("%v", v),
			}
		}
		rsp.Records = append(rsp.Records, &pb.Record{
			Key:      val.Key,
			Value:    val.Value,
			Expiry:   int64(val.Expiry.Seconds()),
			Metadata: metadata,
		})
	}
	return nil
}

// Write to the store
func (h *handler) Write(ctx context.Context, req *pb.WriteRequest, rsp *pb.WriteResponse) error {
	// validate the request
	if req.Record == nil {
		return errors.BadRequest("store.Store.Write", "no record specified")
	}

	// set defaults
	if req.Options == nil {
		req.Options = &pb.WriteOptions{}
	}
	if len(req.Options.Database) == 0 {
		req.Options.Database = defaultDatabase
	}
	if len(req.Options.Table) == 0 {
		req.Options.Table = defaultTable
	}

	// authorize the request
	if err := namespace.Authorize(ctx, req.Options.Database); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.Write", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.Write", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Write", err.Error())
	}

	// setup the store
	if err := h.setupTable(req.Options.Database, req.Options.Table); err != nil {
		return errors.InternalServerError("store.Store.Write", err.Error())
	}

	// setup the options
	opts := []gostore.WriteOption{
		gostore.WriteTo(req.Options.Database, req.Options.Table),
	}

	// construct the record
	metadata := make(map[string]interface{})
	for k, v := range req.Record.Metadata {
		metadata[k] = v.Value
	}
	record := &gostore.Record{
		Key:      req.Record.Key,
		Value:    req.Record.Value,
		Expiry:   time.Duration(req.Record.Expiry) * time.Second,
		Metadata: metadata,
	}

	// write to the store
	err := store.Write(record, opts...)
	if err != nil && err == gostore.ErrNotFound {
		return errors.NotFound("store.Store.Write", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Write", err.Error())
	}

	return nil
}

func (h *handler) Delete(ctx context.Context, req *pb.DeleteRequest, rsp *pb.DeleteResponse) error {
	// set defaults
	if req.Options == nil {
		req.Options = &pb.DeleteOptions{}
	}
	if len(req.Options.Database) == 0 {
		req.Options.Database = defaultDatabase
	}
	if len(req.Options.Table) == 0 {
		req.Options.Table = defaultTable
	}

	// authorize the request
	if err := namespace.Authorize(ctx, req.Options.Database); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.Delete", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.Delete", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Delete", err.Error())
	}

	// setup the store
	if err := h.setupTable(req.Options.Database, req.Options.Table); err != nil {
		return errors.InternalServerError("store.Store.Delete", err.Error())
	}

	// setup the options
	opts := []gostore.DeleteOption{
		gostore.DeleteFrom(req.Options.Database, req.Options.Table),
	}

	// delete from the store
	if err := store.Delete(req.Key, opts...); err == gostore.ErrNotFound {
		return errors.NotFound("store.Store.Delete", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Delete", err.Error())
	}

	return nil
}

// Databases lists all the databases
func (h *handler) Databases(ctx context.Context, req *pb.DatabasesRequest, rsp *pb.DatabasesResponse) error {
	// authorize the request
	if err := namespace.Authorize(ctx, defaultDatabase); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.Databases", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.Databases", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Databases", err.Error())
	}

	// read the databases from the store
	opts := []gostore.ReadOption{
		gostore.ReadPrefix(),
		gostore.ReadFrom(defaultDatabase, internalTable),
	}
	recs, err := store.Read("databases/", opts...)
	if err != nil {
		return errors.InternalServerError("store.Store.Databases", err.Error())
	}

	// serialize the response
	rsp.Databases = make([]string, len(recs))
	for i, r := range recs {
		rsp.Databases[i] = strings.TrimPrefix(r.Key, "databases/")
	}
	return nil
}

// Tables returns all the tables in a database
func (h *handler) Tables(ctx context.Context, req *pb.TablesRequest, rsp *pb.TablesResponse) error {
	// set defaults
	if len(req.Database) == 0 {
		req.Database = defaultDatabase
	}

	// authorize the request
	if err := namespace.Authorize(ctx, req.Database); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.Tables", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.Tables", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Tables", err.Error())
	}

	// construct the options
	opts := []gostore.ReadOption{
		gostore.ReadPrefix(),
		gostore.ReadFrom(defaultDatabase, internalTable),
	}

	// perform the query
	query := fmt.Sprintf("tables/%v/", req.Database)
	recs, err := store.Read(query, opts...)
	if err != nil {
		return errors.InternalServerError("store.Store.Tables", err.Error())
	}

	// serialize the response
	rsp.Tables = make([]string, len(recs))
	for i, r := range recs {
		rsp.Tables[i] = strings.TrimPrefix(r.Key, "tables/"+req.Database+"/")
	}
	return nil
}

func (h *handler) setupTable(database, table string) error {
	// lock (might be a race)
	h.Lock()
	defer h.Unlock()

	// attempt to get the database
	if _, ok := h.stores[database+":"+table]; ok {
		return nil
	}

	// record the new database in the internal store
	opt := gostore.WriteTo(defaultDatabase, internalTable)
	dbRecord := &gostore.Record{Key: "databases/" + database, Value: []byte{}}
	if err := store.Write(dbRecord, opt); err != nil {
		return fmt.Errorf("Error writing new database to internal table: %v", err)
	}

	// record the new table in the internal store
	tableRecord := &gostore.Record{Key: "tables/" + database + "/" + table, Value: []byte{}}
	if err := store.Write(tableRecord, opt); err != nil {
		return fmt.Errorf("Error writing new table to internal table: %v", err)
	}

	// write to the cache
	h.stores[database+":"+table] = true
	return nil
}
<commit_msg>service/store: fix ignored options in Store.Read (#1242)<commit_after>package server

import (
	"context"
	"fmt"
	"io"
	"reflect"
	"strings"
	"sync"
	"time"

	gostore "github.com/micro/go-micro/v3/store"
	"github.com/micro/micro/v3/internal/namespace"
	"github.com/micro/micro/v3/service/errors"
	"github.com/micro/micro/v3/service/store"
	pb "github.com/micro/micro/v3/service/store/proto"
)

const (
	defaultDatabase = namespace.DefaultNamespace
	defaultTable    = namespace.DefaultNamespace
	internalTable   = "store"
)

type handler struct {
	// local stores cache
	sync.RWMutex
	stores map[string]bool
}

// List all the keys in a table
func (h *handler) List(ctx context.Context, req *pb.ListRequest, stream pb.Store_ListStream) error {
	// set defaults
	if req.Options == nil {
		req.Options = &pb.ListOptions{}
	}
	if len(req.Options.Database) == 0 {
		req.Options.Database = defaultDatabase
	}
	if len(req.Options.Table) == 0 {
		req.Options.Table = defaultTable
	}

	// authorize the request
	if err := namespace.Authorize(ctx, req.Options.Database); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.List", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.List", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.List", err.Error())
	}

	// setup the store
	if err := h.setupTable(req.Options.Database, req.Options.Table); err != nil {
		return errors.InternalServerError("store.Store.List", err.Error())
	}

	// setup the options
	opts := []gostore.ListOption{
		gostore.ListFrom(req.Options.Database, req.Options.Table),
	}
	if len(req.Options.Prefix) > 0 {
		opts = append(opts, gostore.ListPrefix(req.Options.Prefix))
	}
	if req.Options.Offset > 0 {
		opts = append(opts, gostore.ListOffset(uint(req.Options.Offset)))
	}
	if req.Options.Limit > 0 {
		opts = append(opts, gostore.ListLimit(uint(req.Options.Limit)))
	}

	// list from the store
	vals, err := store.List(opts...)
	if err != nil && err == gostore.ErrNotFound {
		return errors.NotFound("store.Store.List", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.List", err.Error())
	}

	// serialize the response
	// TODO: batch sync
	rsp := new(pb.ListResponse)
	for _, val := range vals {
		rsp.Keys = append(rsp.Keys, val)
	}

	err = stream.Send(rsp)
	if err == io.EOF {
		return nil
	}
	if err != nil {
		return errors.InternalServerError("store.Store.List", err.Error())
	}
	return nil
}

// Read records from the store
func (h *handler) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadResponse) error {
	// set defaults
	if req.Options == nil {
		req.Options = &pb.ReadOptions{}
	}
	if len(req.Options.Database) == 0 {
		req.Options.Database = defaultDatabase
	}
	if len(req.Options.Table) == 0 {
		req.Options.Table = defaultTable
	}

	// authorize the request
	if err := namespace.Authorize(ctx, req.Options.Database); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.Read", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.Read", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Read", err.Error())
	}

	// setup the store
	if err := h.setupTable(req.Options.Database, req.Options.Table); err != nil {
		return errors.InternalServerError("store.Store.Read", err.Error())
	}

	// setup the options
	opts := []gostore.ReadOption{
		gostore.ReadFrom(req.Options.Database, req.Options.Table),
	}
	if req.Options.Prefix {
		opts = append(opts, gostore.ReadPrefix())
	}
	if req.Options.Limit > 0 {
		opts = append(opts, gostore.ReadLimit(uint(req.Options.Limit)))
	}
	if req.Options.Offset > 0 {
		opts = append(opts, gostore.ReadOffset(uint(req.Options.Offset)))
	}

	// read from the database
	vals, err := store.Read(req.Key, opts...)
	if err != nil && err == gostore.ErrNotFound {
		return errors.NotFound("store.Store.Read", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Read", err.Error())
	}

	// serialize the result
	for _, val := range vals {
		metadata := make(map[string]*pb.Field)
		for k, v := range val.Metadata {
			metadata[k] = &pb.Field{
				Type:  reflect.TypeOf(v).String(),
				Value: fmt.Sprintf("%v", v),
			}
		}
		rsp.Records = append(rsp.Records, &pb.Record{
			Key:      val.Key,
			Value:    val.Value,
			Expiry:   int64(val.Expiry.Seconds()),
			Metadata: metadata,
		})
	}
	return nil
}

// Write to the store
func (h *handler) Write(ctx context.Context, req *pb.WriteRequest, rsp *pb.WriteResponse) error {
	// validate the request
	if req.Record == nil {
		return errors.BadRequest("store.Store.Write", "no record specified")
	}

	// set defaults
	if req.Options == nil {
		req.Options = &pb.WriteOptions{}
	}
	if len(req.Options.Database) == 0 {
		req.Options.Database = defaultDatabase
	}
	if len(req.Options.Table) == 0 {
		req.Options.Table = defaultTable
	}

	// authorize the request
	if err := namespace.Authorize(ctx, req.Options.Database); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.Write", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.Write", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Write", err.Error())
	}

	// setup the store
	if err := h.setupTable(req.Options.Database, req.Options.Table); err != nil {
		return errors.InternalServerError("store.Store.Write", err.Error())
	}

	// setup the options
	opts := []gostore.WriteOption{
		gostore.WriteTo(req.Options.Database, req.Options.Table),
	}

	// construct the record
	metadata := make(map[string]interface{})
	for k, v := range req.Record.Metadata {
		metadata[k] = v.Value
	}
	record := &gostore.Record{
		Key:      req.Record.Key,
		Value:    req.Record.Value,
		Expiry:   time.Duration(req.Record.Expiry) * time.Second,
		Metadata: metadata,
	}

	// write to the store
	err := store.Write(record, opts...)
	if err != nil && err == gostore.ErrNotFound {
		return errors.NotFound("store.Store.Write", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Write", err.Error())
	}

	return nil
}

func (h *handler) Delete(ctx context.Context, req *pb.DeleteRequest, rsp *pb.DeleteResponse) error {
	// set defaults
	if req.Options == nil {
		req.Options = &pb.DeleteOptions{}
	}
	if len(req.Options.Database) == 0 {
		req.Options.Database = defaultDatabase
	}
	if len(req.Options.Table) == 0 {
		req.Options.Table = defaultTable
	}

	// authorize the request
	if err := namespace.Authorize(ctx, req.Options.Database); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.Delete", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.Delete", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Delete", err.Error())
	}

	// setup the store
	if err := h.setupTable(req.Options.Database, req.Options.Table); err != nil {
		return errors.InternalServerError("store.Store.Delete", err.Error())
	}

	// setup the options
	opts := []gostore.DeleteOption{
		gostore.DeleteFrom(req.Options.Database, req.Options.Table),
	}

	// delete from the store
	if err := store.Delete(req.Key, opts...); err == gostore.ErrNotFound {
		return errors.NotFound("store.Store.Delete", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Delete", err.Error())
	}

	return nil
}

// Databases lists all the databases
func (h *handler) Databases(ctx context.Context, req *pb.DatabasesRequest, rsp *pb.DatabasesResponse) error {
	// authorize the request
	if err := namespace.Authorize(ctx, defaultDatabase); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.Databases", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.Databases", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Databases", err.Error())
	}

	// read the databases from the store
	opts := []gostore.ReadOption{
		gostore.ReadPrefix(),
		gostore.ReadFrom(defaultDatabase, internalTable),
	}
	recs, err := store.Read("databases/", opts...)
	if err != nil {
		return errors.InternalServerError("store.Store.Databases", err.Error())
	}

	// serialize the response
	rsp.Databases = make([]string, len(recs))
	for i, r := range recs {
		rsp.Databases[i] = strings.TrimPrefix(r.Key, "databases/")
	}
	return nil
}

// Tables returns all the tables in a database
func (h *handler) Tables(ctx context.Context, req *pb.TablesRequest, rsp *pb.TablesResponse) error {
	// set defaults
	if len(req.Database) == 0 {
		req.Database = defaultDatabase
	}

	// authorize the request
	if err := namespace.Authorize(ctx, req.Database); err == namespace.ErrForbidden {
		return errors.Forbidden("store.Store.Tables", err.Error())
	} else if err == namespace.ErrUnauthorized {
		return errors.Unauthorized("store.Store.Tables", err.Error())
	} else if err != nil {
		return errors.InternalServerError("store.Store.Tables", err.Error())
	}

	// construct the options
	opts := []gostore.ReadOption{
		gostore.ReadPrefix(),
		gostore.ReadFrom(defaultDatabase, internalTable),
	}

	// perform the query
	query := fmt.Sprintf("tables/%v/", req.Database)
	recs, err := store.Read(query, opts...)
	if err != nil {
		return errors.InternalServerError("store.Store.Tables", err.Error())
	}

	// serialize the response
	rsp.Tables = make([]string, len(recs))
	for i, r := range recs {
		rsp.Tables[i] = strings.TrimPrefix(r.Key, "tables/"+req.Database+"/")
	}
	return nil
}

func (h *handler) setupTable(database, table string) error {
	// lock (might be a race)
	h.Lock()
	defer h.Unlock()

	// attempt to get the database
	if _, ok := h.stores[database+":"+table]; ok {
		return nil
	}

	// record the new database in the internal store
	opt := gostore.WriteTo(defaultDatabase, internalTable)
	dbRecord := &gostore.Record{Key: "databases/" + database, Value: []byte{}}
	if err := store.Write(dbRecord, opt); err != nil {
		return fmt.Errorf("Error writing new database to internal table: %v", err)
	}

	// record the new table in the internal store
	tableRecord := &gostore.Record{Key: "tables/" + database + "/" + table, Value: []byte{}}
	if err := store.Write(tableRecord, opt); err != nil {
		return fmt.Errorf("Error writing new table to internal table: %v", err)
	}

	// write to the cache
	h.stores[database+":"+table] = true
	return nil
}
<|endoftext|>