<commit_before>package dataProcess

import (
	"database/sql"
	"github.com/gin-gonic/gin"
	"net/http"
	"time"

	"../autils"
	"regexp"
)

// 组件概况数据处理
func UpdateArrival(c *gin.Context, db *sql.DB) {
	dateReg := regexp.MustCompile("(\\d{4})(\\d{2})(\\d{2})")
	data := c.Query("data")
	date := c.Query("date")

	count := autils.CheckSql(data)

	if date == "" {
		date = autils.GetCurrentData(time.Now().AddDate(0, 0, -1))
	} else {
		dateArr := dateReg.FindAllStringSubmatch(date, -1)
		if len(dateArr) > 0 && len(dateArr[0]) > 3 {
			year, month, day := dateArr[0][1], dateArr[0][2], dateArr[0][3]
			date = year + "-" + month + "-" + day
		}
	}

	rsText := "ok"
	_, err := db.Exec("update all_flow set wb_pv =  '" + count + "' where date = '" + date + "'")

	if err != nil {
		rsText = "failed"
	}

	c.JSON(http.StatusOK, gin.H{
		"status": 0,
		"msg":    rsText,
	})
}
<commit_msg>add arrival rate data.<commit_after>package dataProcess

import (
	"database/sql"
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"regexp"
	"strconv"
	"time"

	"../autils"
)

// 组件概况数据处理
func UpdateArrival(c *gin.Context, db *sql.DB) {
	dateReg := regexp.MustCompile("(\\d{4})(\\d{2})(\\d{2})")
	data := c.Query("data")
	date := c.Query("date")

	count := autils.CheckSql(data)

	if date == "" {
		date = autils.GetCurrentData(time.Now().AddDate(0, 0, -1))
	} else {
		dateArr := dateReg.FindAllStringSubmatch(date, -1)
		if len(dateArr) > 0 && len(dateArr[0]) > 3 {
			year, month, day := dateArr[0][1], dateArr[0][2], dateArr[0][3]
			date = year + "-" + month + "-" + day
		}
	}

	pv := getPv(date, db)
	intPv, _ := strconv.Atoi(count)
	rsPv := "0"
	if pv != 0 {
		rsPv = fmt.Sprintf("%.2f", float32(intPv)/float32(pv)*100)
	}

	rsText := "ok"
	_, err := db.Exec("update all_flow set wb_pv =  '" + count + "', arrival_rate = '" + rsPv + "' where date = '" + date + "'")

	if err != nil {
		rsText = "failed"
	}

	c.JSON(http.StatusOK, gin.H{
		"status": 0,
		"msg":    rsText,
	})
}

func getPv(date string, db *sql.DB) int {
	var pv int
	rows, err := db.Query("select pv from site_detail where date = '" + date + "' and domain = '总和'")

	autils.ErrHadle(err)

	for rows.Next() {
		err := rows.Scan(&pv)
		autils.ErrHadle(err)
	}
	err = rows.Err()
	autils.ErrHadle(err)
	return pv
}
<|endoftext|>


<commit_before>package collector

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"

	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
	"github.com/karimra/gnmic/outputs"
	"github.com/openconfig/gnmi/proto/gnmi"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/encoding/prototext"
)

const (
	defaultTargetReceivebuffer = 1000
)

// Config is the collector config
type Config struct {
	PrometheusAddress   string
	Debug               bool
	Format              string
	TargetReceiveBuffer uint
}

// Collector //
type Collector struct {
	Config        *Config
	Subscriptions map[string]*SubscriptionConfig
	Outputs       map[string][]outputs.Output
	DialOpts      []grpc.DialOption
	//
	m          *sync.Mutex
	Targets    map[string]*Target
	Logger     *log.Logger
	httpServer *http.Server

	ctx      context.Context
	cancelFn context.CancelFunc
}

// NewCollector //
func NewCollector(ctx context.Context,
	config *Config,
	targetConfigs map[string]*TargetConfig,
	subscriptions map[string]*SubscriptionConfig,
	outputs map[string][]outputs.Output,
	dialOpts []grpc.DialOption,
	logger *log.Logger,
) *Collector {
	nctx, cancel := context.WithCancel(ctx)
	grpcMetrics := grpc_prometheus.NewClientMetrics()
	reg := prometheus.NewRegistry()
	reg.MustRegister(prometheus.NewGoCollector())
	reg.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
	grpcMetrics.EnableClientHandlingTimeHistogram()
	reg.MustRegister(grpcMetrics)
	handler := http.NewServeMux()
	handler.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
	httpServer := &http.Server{
		Handler: handler,
		Addr:    config.PrometheusAddress,
	}
	dialOpts = append(dialOpts, grpc.WithStreamInterceptor(grpcMetrics.StreamClientInterceptor()))
	if config.TargetReceiveBuffer == 0 {
		config.TargetReceiveBuffer = defaultTargetReceivebuffer
	}
	c := &Collector{
		Config:        config,
		Subscriptions: subscriptions,
		Outputs:       outputs,
		DialOpts:      dialOpts,
		m:             new(sync.Mutex),
		Targets:       make(map[string]*Target),
		Logger:        logger,
		httpServer:    httpServer,
		ctx:           nctx,
		cancelFn:      cancel,
	}

	for _, tc := range targetConfigs {
		c.InitTarget(tc)
	}
	return c
}

// InitTarget initializes a target based on *TargetConfig
func (c *Collector) InitTarget(tc *TargetConfig) {
	if tc.BufferSize == 0 {
		tc.BufferSize = c.Config.TargetReceiveBuffer
	}
	t := NewTarget(tc)
	//
	t.Subscriptions = make([]*SubscriptionConfig, 0, len(tc.Subscriptions))
	for _, subName := range tc.Subscriptions {
		if sub, ok := c.Subscriptions[subName]; ok {
			t.Subscriptions = append(t.Subscriptions, sub)
		}
	}
	if len(t.Subscriptions) == 0 {
		t.Subscriptions = make([]*SubscriptionConfig, 0, len(c.Subscriptions))
		for _, sub := range c.Subscriptions {
			t.Subscriptions = append(t.Subscriptions, sub)
		}
	}
	//
	t.Outputs = make([]outputs.Output, 0, len(tc.Outputs))
	for _, outName := range tc.Outputs {
		if outs, ok := c.Outputs[outName]; ok {
			t.Outputs = append(t.Outputs, outs...)
		}
	}
	if len(t.Outputs) == 0 {
		t.Outputs = make([]outputs.Output, 0, len(c.Outputs))
		for _, o := range c.Outputs {
			t.Outputs = append(t.Outputs, o...)
		}
	}
	c.m.Lock()
	defer c.m.Unlock()
	c.Targets[t.Config.Name] = t
}

// Subscribe //
func (c *Collector) Subscribe(tName string) error {
	if t, ok := c.Targets[tName]; ok {
		if err := t.CreateGNMIClient(c.ctx, c.DialOpts...); err != nil {
			return err
		}
		c.Logger.Printf("target '%s' gNMI client created", t.Config.Name)
		for _, sc := range t.Subscriptions {
			req, err := sc.CreateSubscribeRequest()
			if err != nil {
				return err
			}
			c.Logger.Printf("sending gNMI SubscribeRequest: subscribe='%+v', mode='%+v', encoding='%+v', to %s",
				req, req.GetSubscribe().GetMode(), req.GetSubscribe().GetEncoding(), t.Config.Name)
			go t.Subscribe(c.ctx, req, sc.Name)
		}
		return nil
	}
	return fmt.Errorf("unknown target name: %s", tName)
}

// Start start the prometheus server as well as a goroutine per target selecting on the response chan, the error chan and the ctx.Done() chan
func (c *Collector) Start() {
	go func() {
		if err := c.httpServer.ListenAndServe(); err != nil {
			c.Logger.Printf("Unable to start prometheus http server: %v", err)
			return
		}
	}()
	defer func() {
		for _, outputs := range c.Outputs {
			for _, o := range outputs {
				o.Close()
			}
		}
	}()
	wg := new(sync.WaitGroup)
	wg.Add(len(c.Targets))
	for _, t := range c.Targets {
		go func(t *Target) {
			defer wg.Done()
			numOnceSubscriptions := t.numberOfOnceSubscriptions()
			remainingOnceSubscriptions := numOnceSubscriptions
			numSubscriptions := len(t.Subscriptions)
			for {
				select {
				case rsp := <-t.SubscribeResponses:
					if c.Config.Debug {
						c.Logger.Printf("received gNMI Subscribe Response: %+v", rsp)
					}
					m := make(map[string]interface{})
					m["subscription-name"] = rsp.SubscriptionName
					m["source"] = t.Config.Name
					b, err := c.FormatMsg(m, rsp.Response)
					if err != nil {
						c.Logger.Printf("failed formatting msg from target '%s': %v", t.Config.Name, err)
						continue
					}
					if c.subscriptionMode(rsp.SubscriptionName) == "ONCE" {
						t.Export(b, outputs.Meta{"source": t.Config.Name, "format": c.Config.Format, "subscription-name": rsp.SubscriptionName})
					} else {
						go t.Export(b, outputs.Meta{"source": t.Config.Name, "format": c.Config.Format, "subscription-name": rsp.SubscriptionName})
					}
					if remainingOnceSubscriptions > 0 {
						if c.subscriptionMode(rsp.SubscriptionName) == "ONCE" {
							switch rsp.Response.Response.(type) {
							case *gnmi.SubscribeResponse_SyncResponse:
								remainingOnceSubscriptions--
							}
						}
					}
					if remainingOnceSubscriptions == 0 && numSubscriptions == numOnceSubscriptions {
						return
					}
				case err := <-t.Errors:
					if err == io.EOF {
						c.Logger.Printf("target '%s' closed stream(EOF)", t.Config.Name)
						return
					}
					c.Logger.Printf("target '%s' error: %v", t.Config.Name, err)
				}
			}
		}(t)
	}
	wg.Wait()
}

// FormatMsg formats the gnmi.SubscribeResponse and returns a []byte and an error
func (c *Collector) FormatMsg(meta map[string]interface{}, rsp *gnmi.SubscribeResponse) ([]byte, error) {
	if rsp == nil {
		return nil, nil
	}
	if c.Config.Format == "textproto" {
		return []byte(prototext.Format(rsp)), nil
	}
	switch rsp := rsp.Response.(type) {
	case *gnmi.SubscribeResponse_Update:
		msg := new(msg)
		msg.Timestamp = rsp.Update.Timestamp
		t := time.Unix(0, rsp.Update.Timestamp)
		msg.Time = &t
		if meta == nil {
			meta = make(map[string]interface{})
		}
		msg.Prefix = gnmiPathToXPath(rsp.Update.Prefix)
		var ok bool
		if _, ok = meta["source"]; ok {
			msg.Source = fmt.Sprintf("%s", meta["source"])
		}
		if _, ok = meta["system-name"]; ok {
			msg.SystemName = fmt.Sprintf("%s", meta["system-name"])
		}
		if _, ok = meta["subscription-name"]; ok {
			msg.SubscriptionName = fmt.Sprintf("%s", meta["subscription-name"])
		}
		for i, upd := range rsp.Update.Update {
			pathElems := make([]string, 0, len(upd.Path.Elem))
			for _, pElem := range upd.Path.Elem {
				pathElems = append(pathElems, pElem.GetName())
			}
			value, err := getValue(upd.Val)
			if err != nil {
				c.Logger.Println(err)
			}
			msg.Updates = append(msg.Updates,
				&update{
					Path:   gnmiPathToXPath(upd.Path),
					Values: make(map[string]interface{}),
				})
			msg.Updates[i].Values[strings.Join(pathElems, "/")] = value
		}
		for _, del := range rsp.Update.Delete {
			msg.Deletes = append(msg.Deletes, gnmiPathToXPath(del))
		}
		data, err := json.Marshal(msg)
		if err != nil {
			return nil, err
		}
		return data, nil
	}
	return nil, nil
}

// TargetPoll sends a gnmi.SubscribeRequest_Poll to targetName and returns the response and an error,
// it uses the targetName and the subscriptionName strings to find the gnmi.GNMI_SubscribeClient
func (c *Collector) TargetPoll(targetName, subscriptionName string) (*gnmi.SubscribeResponse, error) {
	if sub, ok := c.Subscriptions[subscriptionName]; ok {
		if strings.ToUpper(sub.Mode) != "POLL" {
			return nil, fmt.Errorf("subscription '%s' is not a POLL subscription", subscriptionName)
		}
		if t, ok := c.Targets[targetName]; ok {
			if subClient, ok := t.SubscribeClients[subscriptionName]; ok {
				err := subClient.Send(&gnmi.SubscribeRequest{
					Request: &gnmi.SubscribeRequest_Poll{
						Poll: &gnmi.Poll{},
					},
				})
				if err != nil {
					return nil, err
				}
				return subClient.Recv()
			}
		}
		return nil, fmt.Errorf("unknown target name '%s'", targetName)
	}
	return nil, fmt.Errorf("unknown subscription name '%s'", subscriptionName)
}

// PolledSubscriptionsTargets returns a map of target name to a list of subscription names that have Mode == POLL
func (c *Collector) PolledSubscriptionsTargets() map[string][]string {
	result := make(map[string][]string)
	for tn, target := range c.Targets {
		for _, sub := range target.Subscriptions {
			if strings.ToUpper(sub.Mode) == "POLL" {
				if result[tn] == nil {
					result[tn] = make([]string, 0)
				}
				result[tn] = append(result[tn], sub.Name)
			}
		}
	}
	return result
}

func (c *Collector) subscriptionMode(name string) string {
	if sub, ok := c.Subscriptions[name]; ok {
		return strings.ToUpper(sub.Mode)
	}
	return ""
}
<commit_msg>update collector.Start to match ouput interface<commit_after>package collector

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"

	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
	"github.com/karimra/gnmic/outputs"
	"github.com/openconfig/gnmi/proto/gnmi"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"google.golang.org/grpc"
	"google.golang.org/protobuf/encoding/prototext"
)

const (
	defaultTargetReceivebuffer = 1000
)

// Config is the collector config
type Config struct {
	PrometheusAddress   string
	Debug               bool
	Format              string
	TargetReceiveBuffer uint
}

// Collector //
type Collector struct {
	Config        *Config
	Subscriptions map[string]*SubscriptionConfig
	Outputs       map[string][]outputs.Output
	DialOpts      []grpc.DialOption
	//
	m          *sync.Mutex
	Targets    map[string]*Target
	Logger     *log.Logger
	httpServer *http.Server

	ctx      context.Context
	cancelFn context.CancelFunc
}

// NewCollector //
func NewCollector(ctx context.Context,
	config *Config,
	targetConfigs map[string]*TargetConfig,
	subscriptions map[string]*SubscriptionConfig,
	outputs map[string][]outputs.Output,
	dialOpts []grpc.DialOption,
	logger *log.Logger,
) *Collector {
	nctx, cancel := context.WithCancel(ctx)
	grpcMetrics := grpc_prometheus.NewClientMetrics()
	reg := prometheus.NewRegistry()
	reg.MustRegister(prometheus.NewGoCollector())
	reg.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
	grpcMetrics.EnableClientHandlingTimeHistogram()
	reg.MustRegister(grpcMetrics)
	handler := http.NewServeMux()
	handler.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
	httpServer := &http.Server{
		Handler: handler,
		Addr:    config.PrometheusAddress,
	}
	dialOpts = append(dialOpts, grpc.WithStreamInterceptor(grpcMetrics.StreamClientInterceptor()))
	if config.TargetReceiveBuffer == 0 {
		config.TargetReceiveBuffer = defaultTargetReceivebuffer
	}
	c := &Collector{
		Config:        config,
		Subscriptions: subscriptions,
		Outputs:       outputs,
		DialOpts:      dialOpts,
		m:             new(sync.Mutex),
		Targets:       make(map[string]*Target),
		Logger:        logger,
		httpServer:    httpServer,
		ctx:           nctx,
		cancelFn:      cancel,
	}

	for _, tc := range targetConfigs {
		c.InitTarget(tc)
	}
	return c
}

// InitTarget initializes a target based on *TargetConfig
func (c *Collector) InitTarget(tc *TargetConfig) {
	if tc.BufferSize == 0 {
		tc.BufferSize = c.Config.TargetReceiveBuffer
	}
	t := NewTarget(tc)
	//
	t.Subscriptions = make([]*SubscriptionConfig, 0, len(tc.Subscriptions))
	for _, subName := range tc.Subscriptions {
		if sub, ok := c.Subscriptions[subName]; ok {
			t.Subscriptions = append(t.Subscriptions, sub)
		}
	}
	if len(t.Subscriptions) == 0 {
		t.Subscriptions = make([]*SubscriptionConfig, 0, len(c.Subscriptions))
		for _, sub := range c.Subscriptions {
			t.Subscriptions = append(t.Subscriptions, sub)
		}
	}
	//
	t.Outputs = make([]outputs.Output, 0, len(tc.Outputs))
	for _, outName := range tc.Outputs {
		if outs, ok := c.Outputs[outName]; ok {
			t.Outputs = append(t.Outputs, outs...)
		}
	}
	if len(t.Outputs) == 0 {
		t.Outputs = make([]outputs.Output, 0, len(c.Outputs))
		for _, o := range c.Outputs {
			t.Outputs = append(t.Outputs, o...)
		}
	}
	c.m.Lock()
	defer c.m.Unlock()
	c.Targets[t.Config.Name] = t
}

// Subscribe //
func (c *Collector) Subscribe(tName string) error {
	if t, ok := c.Targets[tName]; ok {
		if err := t.CreateGNMIClient(c.ctx, c.DialOpts...); err != nil {
			return err
		}
		c.Logger.Printf("target '%s' gNMI client created", t.Config.Name)
		for _, sc := range t.Subscriptions {
			req, err := sc.CreateSubscribeRequest()
			if err != nil {
				return err
			}
			c.Logger.Printf("sending gNMI SubscribeRequest: subscribe='%+v', mode='%+v', encoding='%+v', to %s",
				req, req.GetSubscribe().GetMode(), req.GetSubscribe().GetEncoding(), t.Config.Name)
			go t.Subscribe(c.ctx, req, sc.Name)
		}
		return nil
	}
	return fmt.Errorf("unknown target name: %s", tName)
}

// Start start the prometheus server as well as a goroutine per target selecting on the response chan, the error chan and the ctx.Done() chan
func (c *Collector) Start() {
	go func() {
		if err := c.httpServer.ListenAndServe(); err != nil {
			c.Logger.Printf("Unable to start prometheus http server: %v", err)
			return
		}
	}()
	defer func() {
		for _, outputs := range c.Outputs {
			for _, o := range outputs {
				o.Close()
			}
		}
	}()
	wg := new(sync.WaitGroup)
	wg.Add(len(c.Targets))
	for _, t := range c.Targets {
		go func(t *Target) {
			defer wg.Done()
			numOnceSubscriptions := t.numberOfOnceSubscriptions()
			remainingOnceSubscriptions := numOnceSubscriptions
			numSubscriptions := len(t.Subscriptions)
			for {
				select {
				case rsp := <-t.SubscribeResponses:
					if c.Config.Debug {
						c.Logger.Printf("received gNMI Subscribe Response: %+v", rsp)
					}
					if c.subscriptionMode(rsp.SubscriptionName) == "ONCE" {
						t.Export(rsp.Response, outputs.Meta{"source": t.Config.Name, "format": c.Config.Format, "subscription-name": rsp.SubscriptionName})
					} else {
						go t.Export(rsp.Response, outputs.Meta{"source": t.Config.Name, "format": c.Config.Format, "subscription-name": rsp.SubscriptionName})
					}
					if remainingOnceSubscriptions > 0 {
						if c.subscriptionMode(rsp.SubscriptionName) == "ONCE" {
							switch rsp.Response.Response.(type) {
							case *gnmi.SubscribeResponse_SyncResponse:
								remainingOnceSubscriptions--
							}
						}
					}
					if remainingOnceSubscriptions == 0 && numSubscriptions == numOnceSubscriptions {
						return
					}
				case err := <-t.Errors:
					if err == io.EOF {
						c.Logger.Printf("target '%s' closed stream(EOF)", t.Config.Name)
						return
					}
					c.Logger.Printf("target '%s' error: %v", t.Config.Name, err)
				}
			}
		}(t)
	}
	wg.Wait()
}

// FormatMsg formats the gnmi.SubscribeResponse and returns a []byte and an error
func (c *Collector) FormatMsg(meta map[string]interface{}, rsp *gnmi.SubscribeResponse) ([]byte, error) {
	if rsp == nil {
		return nil, nil
	}
	if c.Config.Format == "textproto" {
		return []byte(prototext.Format(rsp)), nil
	}
	switch rsp := rsp.Response.(type) {
	case *gnmi.SubscribeResponse_Update:
		msg := new(msg)
		msg.Timestamp = rsp.Update.Timestamp
		t := time.Unix(0, rsp.Update.Timestamp)
		msg.Time = &t
		if meta == nil {
			meta = make(map[string]interface{})
		}
		msg.Prefix = gnmiPathToXPath(rsp.Update.Prefix)
		var ok bool
		if _, ok = meta["source"]; ok {
			msg.Source = fmt.Sprintf("%s", meta["source"])
		}
		if _, ok = meta["system-name"]; ok {
			msg.SystemName = fmt.Sprintf("%s", meta["system-name"])
		}
		if _, ok = meta["subscription-name"]; ok {
			msg.SubscriptionName = fmt.Sprintf("%s", meta["subscription-name"])
		}
		for i, upd := range rsp.Update.Update {
			pathElems := make([]string, 0, len(upd.Path.Elem))
			for _, pElem := range upd.Path.Elem {
				pathElems = append(pathElems, pElem.GetName())
			}
			value, err := getValue(upd.Val)
			if err != nil {
				c.Logger.Println(err)
			}
			msg.Updates = append(msg.Updates,
				&update{
					Path:   gnmiPathToXPath(upd.Path),
					Values: make(map[string]interface{}),
				})
			msg.Updates[i].Values[strings.Join(pathElems, "/")] = value
		}
		for _, del := range rsp.Update.Delete {
			msg.Deletes = append(msg.Deletes, gnmiPathToXPath(del))
		}
		data, err := json.Marshal(msg)
		if err != nil {
			return nil, err
		}
		return data, nil
	}
	return nil, nil
}

// TargetPoll sends a gnmi.SubscribeRequest_Poll to targetName and returns the response and an error,
// it uses the targetName and the subscriptionName strings to find the gnmi.GNMI_SubscribeClient
func (c *Collector) TargetPoll(targetName, subscriptionName string) (*gnmi.SubscribeResponse, error) {
	if sub, ok := c.Subscriptions[subscriptionName]; ok {
		if strings.ToUpper(sub.Mode) != "POLL" {
			return nil, fmt.Errorf("subscription '%s' is not a POLL subscription", subscriptionName)
		}
		if t, ok := c.Targets[targetName]; ok {
			if subClient, ok := t.SubscribeClients[subscriptionName]; ok {
				err := subClient.Send(&gnmi.SubscribeRequest{
					Request: &gnmi.SubscribeRequest_Poll{
						Poll: &gnmi.Poll{},
					},
				})
				if err != nil {
					return nil, err
				}
				return subClient.Recv()
			}
		}
		return nil, fmt.Errorf("unknown target name '%s'", targetName)
	}
	return nil, fmt.Errorf("unknown subscription name '%s'", subscriptionName)
}

// PolledSubscriptionsTargets returns a map of target name to a list of subscription names that have Mode == POLL
func (c *Collector) PolledSubscriptionsTargets() map[string][]string {
	result := make(map[string][]string)
	for tn, target := range c.Targets {
		for _, sub := range target.Subscriptions {
			if strings.ToUpper(sub.Mode) == "POLL" {
				if result[tn] == nil {
					result[tn] = make([]string, 0)
				}
				result[tn] = append(result[tn], sub.Name)
			}
		}
	}
	return result
}

func (c *Collector) subscriptionMode(name string) string {
	if sub, ok := c.Subscriptions[name]; ok {
		return strings.ToUpper(sub.Mode)
	}
	return ""
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package definition

import (
	"errors"
	"fmt"
	"net"
	"unicode/utf8"
)

// Instance ...
type Instance struct {
	Name           string   `json:"name"`
	Type           string   `json:"type"`
	Image          string   `json:"image"`
	Count          int      `json:"count"`
	Network        string   `json:"network"`
	StartIP        net.IP   `json:"start_ip"`
	SecurityGroups []string `json:"secuirty_groups"`
}

// Validate : Validates the instance returning true or false if is valid or not
func (i *Instance) Validate(network *Network) error {
	if i.Name == "" {
		return errors.New("Instance name should not be null")
	}

	if utf8.RuneCountInString(i.Name) > AWSMAXNAME {
		return fmt.Errorf("Instance name can't be greater than %d characters", AWSMAXNAME)
	}

	if i.Type == "" {
		return errors.New("Instance type should not be null")
	}

	if i.Image == "" {
		return errors.New("Instance image should not be null")
	}

	if i.Count < 1 {
		return errors.New("Instance count should not be < 1")
	}

	if i.Network == "" {
		return errors.New("Instance network should not be null")
	}

	// Validate IP addresses
	if network != nil {
		_, nw, err := net.ParseCIDR(network.Subnet)
		if err != nil {
			return errors.New("Could not process network")
		}

		startIP := net.ParseIP(i.StartIP.String()).To4()
		ip := make(net.IP, net.IPv4len)
		copy(ip, i.StartIP.To4())

		for x := 0; x < i.Count; x++ {
			if !nw.Contains(ip) {
				return errors.New("Instance IP invalid. IP must be a valid IP in the same range as it's network")
			}

			// Check IP is greater than Start IP (Bounds checking)
			if ip[3] < startIP[3] {
				return errors.New("Instance IP invalid. Allocated IP is lower than Start IP")
			}

			ip[3]++
		}
	}

	return nil
}
<commit_msg>fixed input instance security group json tag<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package definition

import (
	"errors"
	"fmt"
	"net"
	"unicode/utf8"
)

// Instance ...
type Instance struct {
	Name           string   `json:"name"`
	Type           string   `json:"type"`
	Image          string   `json:"image"`
	Count          int      `json:"count"`
	Network        string   `json:"network"`
	StartIP        net.IP   `json:"start_ip"`
	SecurityGroups []string `json:"security_groups"`
}

// Validate : Validates the instance returning true or false if is valid or not
func (i *Instance) Validate(network *Network) error {
	if i.Name == "" {
		return errors.New("Instance name should not be null")
	}

	if utf8.RuneCountInString(i.Name) > AWSMAXNAME {
		return fmt.Errorf("Instance name can't be greater than %d characters", AWSMAXNAME)
	}

	if i.Type == "" {
		return errors.New("Instance type should not be null")
	}

	if i.Image == "" {
		return errors.New("Instance image should not be null")
	}

	if i.Count < 1 {
		return errors.New("Instance count should not be < 1")
	}

	if i.Network == "" {
		return errors.New("Instance network should not be null")
	}

	// Validate IP addresses
	if network != nil {
		_, nw, err := net.ParseCIDR(network.Subnet)
		if err != nil {
			return errors.New("Could not process network")
		}

		startIP := net.ParseIP(i.StartIP.String()).To4()
		ip := make(net.IP, net.IPv4len)
		copy(ip, i.StartIP.To4())

		for x := 0; x < i.Count; x++ {
			if !nw.Contains(ip) {
				return errors.New("Instance IP invalid. IP must be a valid IP in the same range as it's network")
			}

			// Check IP is greater than Start IP (Bounds checking)
			if ip[3] < startIP[3] {
				return errors.New("Instance IP invalid. Allocated IP is lower than Start IP")
			}

			ip[3]++
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"

	"github.com/krasoffski/goplts/gopl/ch04/github"
)

func main() {
	result, err := github.SearchIssues(os.Args[1:])
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%d issues:\n", result.TotalCount)
	for _, item := range result.Items {
		fmt.Printf("#%-5d %9.9s %.55s\n",
			item.Number, item.User.Login, item.Title)
	}

}
<commit_msg>Added quick solution for task 4.10<commit_after>package main

import (
	"fmt"
	"os"
	"time"

	"github.com/krasoffski/goplts/gopl/ch04/github"
)

func main() {

	var month, year, older []*github.Issue

	result, err := github.SearchIssues(os.Args[1:])
	if err != nil {
		fmt.Fprintf(os.Stderr, "issue error: %s", err)
		os.Exit(1)
	}
	fmt.Printf("TOTAL ISSUES:  %d\n", result.TotalCount)
	for _, item := range result.Items {
		// TODO: add timzone aware time calculation here.
		ts := int(time.Since(item.CreatedAt).Hours() / 24)
		switch {
		case ts < 31:
			month = append(month, item)
		case ts < 365:
			year = append(year, item)
		default:
			older = append(older, item)
		}

	}
	fmt.Println("\nNOT OLDER THEN A MONTH")
	for _, item := range month {
		fmt.Printf("#%-5d %9.9s %.55s\n",
			item.Number, item.User.Login, item.Title)
	}
	fmt.Println("\nNOT OLDER THEN A YEAR")
	for _, item := range year {
		fmt.Printf("#%-5d %9.9s %.55s\n",
			item.Number, item.User.Login, item.Title)
	}
	fmt.Println("\nMORE THAN ONE YEAR")
	for _, item := range older {
		fmt.Printf("#%-5d %9.9s %.55s\n",
			item.Number, item.User.Login, item.Title)
	}

}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package apimachinery

import (
	"context"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/kubernetes/test/e2e/framework"

	"github.com/onsi/ginkgo"
)

var _ = SIGDescribe("get-apigroup-list", func() {
	f := framework.NewDefaultFramework("get-apigroup-list")
	ginkgo.It("should create pods, set the deletionTimestamp and deletionGracePeriodSeconds of the pod", func() {

		// TEST BEGINS HERE
		ginkgo.By("[status] begin")

		// get list of APIGroup endpoints
		list := &metav1.APIGroupList{}
		err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/apis/").Do(context.TODO()).Into(list)

		framework.ExpectNoError(err, "Failed to find /apis/")

		for _, group := range list.Groups {
			framework.Logf("Checking APIGroup:", group.Name)

			// hit APIGroup endpoint
			checkGroup := &metav1.APIGroup{}
			apiPath := "/apis/" + group.Name + "/"
			err = f.ClientSet.Discovery().RESTClient().Get().AbsPath(apiPath).Do(context.TODO()).Into(checkGroup)

			framework.ExpectNoError(err, "Fail to access: %s", apiPath)

			// get PreferredVersion for endpoint
			framework.Logf("PreferredVersion:", checkGroup.PreferredVersion)
		}
	})
})
<commit_msg>Fix test name and log formatting<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package apimachinery

import (
	"context"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/kubernetes/test/e2e/framework"

	"github.com/onsi/ginkgo"
)

var _ = SIGDescribe("get-apigroup-list", func() {
	f := framework.NewDefaultFramework("get-apigroup-list")
	ginkgo.It("should locate PreferredVersion for each APIGroup", func() {

		// TEST BEGINS HERE
		ginkgo.By("[status] begin")

		// get list of APIGroup endpoints
		list := &metav1.APIGroupList{}
		err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/apis/").Do(context.TODO()).Into(list)

		framework.ExpectNoError(err, "Failed to find /apis/")

		for _, group := range list.Groups {
			framework.Logf("Checking APIGroup: %v", group.Name)

			// hit APIGroup endpoint
			checkGroup := &metav1.APIGroup{}
			apiPath := "/apis/" + group.Name + "/"
			err = f.ClientSet.Discovery().RESTClient().Get().AbsPath(apiPath).Do(context.TODO()).Into(checkGroup)

			framework.ExpectNoError(err, "Fail to access: %s", apiPath)

			// get PreferredVersion for endpoint
			framework.Logf("PreferredVersion: %v", checkGroup.PreferredVersion)
		}
	})
})
<|endoftext|>


<commit_before>package testing

import (
	"time"
	"gopkg.in/check.v1"
)

func ParseTime(c *check.C, timeAsString string) time.Time {
	timeValue, err := time.Parse(time.RFC3339, timeAsString)
	c.Assert(err, check.IsNil)

	return timeValue
}
<commit_msg>[OWL-1167] Add function for parsing time to JsonTime<commit_after>package testing

import (
	"time"
	ojson "github.com/Cepave/open-falcon-backend/common/json"
	"gopkg.in/check.v1"
)

func ParseTime(c *check.C, timeAsString string) time.Time {
	timeValue, err := time.Parse(time.RFC3339, timeAsString)
	c.Assert(err, check.IsNil)

	return timeValue
}
func ParseTimeToJsonTime(c *check.C, timeAsString string) ojson.JsonTime {
	return ojson.JsonTime(ParseTime(c, timeAsString))
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package common

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"sync"
	"time"

	"sigs.k8s.io/yaml"
)

const (
	// Busy state defines a resource being used.
	Busy = "busy"
	// Cleaning state defines a resource being cleaned
	Cleaning = "cleaning"
	// Dirty state defines a resource that needs cleaning
	Dirty = "dirty"
	// Free state defines a resource that is usable
	Free = "free"
	// Leased state defines a resource being leased in order to make a new resource
	Leased = "leased"
	// ToBeDeleted is used for resources about to be deleted, they will be verified by a cleaner which mark them as tombstone
	ToBeDeleted = "toBeDeleted"
	// Tombstone is the state in which a resource can safely be deleted
	Tombstone = "tombstone"
	// Other is used to agglomerate unspecified states for metrics reporting
	Other = "other"
)

var (
	// KnownStates is the set of all known states, excluding "other".
	KnownStates = []string{
		Busy,
		Cleaning,
		Dirty,
		Free,
		Leased,
		ToBeDeleted,
		Tombstone,
	}
)

// UserData is a map of Name to user defined interface, serialized into a string
type UserData struct {
	sync.Map
}

// UserDataMap is the standard Map version of UserMap, it is used to ease UserMap creation.
type UserDataMap map[string]string

// LeasedResources is a list of resources name that used in order to create another resource by Mason
type LeasedResources []string

// Duration is a wrapper around time.Duration that parses times in either
// 'integer number of nanoseconds' or 'duration string' formats and serializes
// to 'duration string' format.
type Duration struct {
	*time.Duration
}

// UnmarshalJSON implement the JSON Unmarshaler interface in order to be able parse string to time.Duration.
func (d *Duration) UnmarshalJSON(b []byte) error {
	if err := json.Unmarshal(b, &d.Duration); err == nil {
		// b was an integer number of nanoseconds.
		return nil
	}
	// b was not an integer. Assume that it is a duration string.

	var str string
	err := json.Unmarshal(b, &str)
	if err != nil {
		return err
	}

	pd, err := time.ParseDuration(str)
	if err != nil {
		return err
	}
	d.Duration = &pd
	return nil
}

// Resource abstracts any resource type that can be tracked by boskos
type Resource struct {
	Type       string    `json:"type"`
	Name       string    `json:"name"`
	State      string    `json:"state"`
	Owner      string    `json:"owner"`
	LastUpdate time.Time `json:"lastupdate"`
	// Customized UserData
	UserData *UserData `json:"userdata"`
	// Used to clean up dynamic resources
	ExpirationDate *time.Time `json:"expiration-date,omitempty"`
}

// ResourceEntry is resource config format defined from config.yaml
type ResourceEntry struct {
	Type     string        `json:"type"`
	State    string        `json:"state"`
	Names    []string      `json:"names,flow"`
	MaxCount int           `json:"max-count,omitempty"`
	MinCount int           `json:"min-count,omitempty"`
	LifeSpan *Duration     `json:"lifespan,omitempty"`
	Config   ConfigType    `json:"config,omitempty"`
	Needs    ResourceNeeds `json:"needs,omitempty"`
}

func (re *ResourceEntry) IsDRLC() bool {
	return len(re.Names) == 0
}

// BoskosConfig defines config used by boskos server
type BoskosConfig struct {
	Resources []ResourceEntry `json:"resources,flow"`
}

// Metric contains analytics about a specific resource type
type Metric struct {
	Type    string         `json:"type"`
	Current map[string]int `json:"current"`
	Owners  map[string]int `json:"owner"`
	// TODO: implements state transition metrics
}

// NewMetric returns a new Metric struct.
func NewMetric(rtype string) Metric {
	return Metric{
		Type:    rtype,
		Current: map[string]int{},
		Owners:  map[string]int{},
	}
}

// NewResource creates a new Boskos Resource.
func NewResource(name, rtype, state, owner string, t time.Time) Resource {
	// If no state defined, mark as Free
	if state == "" {
		state = Free
	}
	return Resource{
		Name:       name,
		Type:       rtype,
		State:      state,
		Owner:      owner,
		LastUpdate: t,
	}
}

// NewResourcesFromConfig parse the a ResourceEntry into a list of resources
func NewResourcesFromConfig(e ResourceEntry) []Resource {
	var resources []Resource
	for _, name := range e.Names {
		resources = append(resources, NewResource(name, e.Type, e.State, "", time.Time{}))
	}
	return resources
}

// UserDataFromMap returns a UserData from a map
func UserDataFromMap(m UserDataMap) *UserData {
	ud := &UserData{}
	for k, v := range m {
		ud.Store(k, v)
	}
	return ud
}

// UserDataNotFound will be returned if requested resource does not exist.
type UserDataNotFound struct {
	ID string
}

func (ud *UserDataNotFound) Error() string {
	return fmt.Sprintf("user data ID %s does not exist", ud.ID)
}

// ResourceByName helps sorting resources by name
type ResourceByName []Resource

func (ut ResourceByName) Len() int           { return len(ut) }
func (ut ResourceByName) Swap(i, j int)      { ut[i], ut[j] = ut[j], ut[i] }
func (ut ResourceByName) Less(i, j int) bool { return ut[i].Name < ut[j].Name }

// CommaSeparatedStrings is used to parse comma separated string flag into a list of strings
type CommaSeparatedStrings []string

func (r *CommaSeparatedStrings) String() string {
	return fmt.Sprint(*r)
}

// Set parses the flag value into a CommaSeparatedStrings
func (r *CommaSeparatedStrings) Set(value string) error {
	if len(*r) > 0 {
		return errors.New("resTypes flag already set")
	}
	for _, rtype := range strings.Split(value, ",") {
		*r = append(*r, rtype)
	}
	return nil
}

func (r *CommaSeparatedStrings) Type() string {
	return "commaSeparatedStrings"
}

// UnmarshalJSON implements JSON Unmarshaler interface
func (ud *UserData) UnmarshalJSON(data []byte) error {
	tmpMap := UserDataMap{}
	if err := json.Unmarshal(data, &tmpMap); err != nil {
		return err
	}
	ud.FromMap(tmpMap)
	return nil
}

// MarshalJSON implements JSON Marshaler interface
func (ud *UserData) MarshalJSON() ([]byte, error) {
	return json.Marshal(ud.ToMap())
}

// Extract unmarshalls a string a given struct if it exists
func (ud *UserData) Extract(id string, out interface{}) error {
	content, ok := ud.Load(id)
	if !ok {
		return &UserDataNotFound{id}
	}
	return yaml.Unmarshal([]byte(content.(string)), out)
}

// User Data are used to store custom information mainly by Mason and Masonable implementation.
// Mason used a LeasedResource keys to store information about other resources that used to
// create the given resource.

// Set marshalls a struct to a string into the UserData
func (ud *UserData) Set(id string, in interface{}) error {
	b, err := yaml.Marshal(in)
	if err != nil {
		return err
	}
	ud.Store(id, string(b))
	return nil
}

// Update updates existing UserData with new UserData.
// If a key as an empty string, the key will be deleted
func (ud *UserData) Update(new *UserData) *UserData {
	if new == nil {
		return ud
	}
	new.Range(func(key, value interface{}) bool {
		if value.(string) != "" {
			ud.Store(key, value)
		} else {
			ud.Delete(key)
		}
		return true
	})
	return ud
}

// ToMap converts a UserData to UserDataMap
func (ud *UserData) ToMap() UserDataMap {
	if ud == nil {
		return nil
	}
	m := UserDataMap{}
	ud.Range(func(key, value interface{}) bool {
		m[key.(string)] = value.(string)
		return true
	})
	return m
}

// FromMap feels updates user data from a map
func (ud *UserData) FromMap(m UserDataMap) {
	for key, value := range m {
		ud.Store(key, value)
	}
}

func ResourceTypeNotFoundMessage(rType string) string {
	return fmt.Sprintf("resource type %q does not exist", rType)
}
<commit_msg>Remove 'flow' JSON field tag<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package common

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"sync"
	"time"

	"sigs.k8s.io/yaml"
)

const (
	// Busy state defines a resource being used.
	Busy = "busy"
	// Cleaning state defines a resource being cleaned
	Cleaning = "cleaning"
	// Dirty state defines a resource that needs cleaning
	Dirty = "dirty"
	// Free state defines a resource that is usable
	Free = "free"
	// Leased state defines a resource being leased in order to make a new resource
	Leased = "leased"
	// ToBeDeleted is used for resources about to be deleted, they will be verified by a cleaner which mark them as tombstone
	ToBeDeleted = "toBeDeleted"
	// Tombstone is the state in which a resource can safely be deleted
	Tombstone = "tombstone"
	// Other is used to agglomerate unspecified states for metrics reporting
	Other = "other"
)

var (
	// KnownStates is the set of all known states, excluding "other".
	KnownStates = []string{
		Busy,
		Cleaning,
		Dirty,
		Free,
		Leased,
		ToBeDeleted,
		Tombstone,
	}
)

// UserData is a map of Name to user defined interface, serialized into a string
type UserData struct {
	sync.Map
}

// UserDataMap is the standard Map version of UserMap, it is used to ease UserMap creation.
type UserDataMap map[string]string

// LeasedResources is a list of resources name that used in order to create another resource by Mason
type LeasedResources []string

// Duration is a wrapper around time.Duration that parses times in either
// 'integer number of nanoseconds' or 'duration string' formats and serializes
// to 'duration string' format.
type Duration struct {
	*time.Duration
}

// UnmarshalJSON implement the JSON Unmarshaler interface in order to be able parse string to time.Duration.
func (d *Duration) UnmarshalJSON(b []byte) error {
	if err := json.Unmarshal(b, &d.Duration); err == nil {
		// b was an integer number of nanoseconds.
		return nil
	}
	// b was not an integer. Assume that it is a duration string.

	var str string
	err := json.Unmarshal(b, &str)
	if err != nil {
		return err
	}

	pd, err := time.ParseDuration(str)
	if err != nil {
		return err
	}
	d.Duration = &pd
	return nil
}

// Resource abstracts any resource type that can be tracked by boskos
type Resource struct {
	Type       string    `json:"type"`
	Name       string    `json:"name"`
	State      string    `json:"state"`
	Owner      string    `json:"owner"`
	LastUpdate time.Time `json:"lastupdate"`
	// Customized UserData
	UserData *UserData `json:"userdata"`
	// Used to clean up dynamic resources
	ExpirationDate *time.Time `json:"expiration-date,omitempty"`
}

// ResourceEntry is resource config format defined from config.yaml
type ResourceEntry struct {
	Type     string        `json:"type"`
	State    string        `json:"state"`
	Names    []string      `json:"names"`
	MaxCount int           `json:"max-count,omitempty"`
	MinCount int           `json:"min-count,omitempty"`
	LifeSpan *Duration     `json:"lifespan,omitempty"`
	Config   ConfigType    `json:"config,omitempty"`
	Needs    ResourceNeeds `json:"needs,omitempty"`
}

func (re *ResourceEntry) IsDRLC() bool {
	return len(re.Names) == 0
}

// BoskosConfig defines config used by boskos server
type BoskosConfig struct {
	Resources []ResourceEntry `json:"resources"`
}

// Metric contains analytics about a specific resource type
type Metric struct {
	Type    string         `json:"type"`
	Current map[string]int `json:"current"`
	Owners  map[string]int `json:"owner"`
	// TODO: implements state transition metrics
}

// NewMetric returns a new Metric struct.
func NewMetric(rtype string) Metric {
	return Metric{
		Type:    rtype,
		Current: map[string]int{},
		Owners:  map[string]int{},
	}
}

// NewResource creates a new Boskos Resource.
func NewResource(name, rtype, state, owner string, t time.Time) Resource {
	// If no state defined, mark as Free
	if state == "" {
		state = Free
	}
	return Resource{
		Name:       name,
		Type:       rtype,
		State:      state,
		Owner:      owner,
		LastUpdate: t,
	}
}

// NewResourcesFromConfig parse the a ResourceEntry into a list of resources
func NewResourcesFromConfig(e ResourceEntry) []Resource {
	var resources []Resource
	for _, name := range e.Names {
		resources = append(resources, NewResource(name, e.Type, e.State, "", time.Time{}))
	}
	return resources
}

// UserDataFromMap returns a UserData from a map
func UserDataFromMap(m UserDataMap) *UserData {
	ud := &UserData{}
	for k, v := range m {
		ud.Store(k, v)
	}
	return ud
}

// UserDataNotFound will be returned if requested resource does not exist.
type UserDataNotFound struct {
	ID string
}

func (ud *UserDataNotFound) Error() string {
	return fmt.Sprintf("user data ID %s does not exist", ud.ID)
}

// ResourceByName helps sorting resources by name
type ResourceByName []Resource

func (ut ResourceByName) Len() int           { return len(ut) }
func (ut ResourceByName) Swap(i, j int)      { ut[i], ut[j] = ut[j], ut[i] }
func (ut ResourceByName) Less(i, j int) bool { return ut[i].Name < ut[j].Name }

// CommaSeparatedStrings is used to parse comma separated string flag into a list of strings
type CommaSeparatedStrings []string

func (r *CommaSeparatedStrings) String() string {
	return fmt.Sprint(*r)
}

// Set parses the flag value into a CommaSeparatedStrings
func (r *CommaSeparatedStrings) Set(value string) error {
	if len(*r) > 0 {
		return errors.New("resTypes flag already set")
	}
	for _, rtype := range strings.Split(value, ",") {
		*r = append(*r, rtype)
	}
	return nil
}

func (r *CommaSeparatedStrings) Type() string {
	return "commaSeparatedStrings"
}

// UnmarshalJSON implements JSON Unmarshaler interface
func (ud *UserData) UnmarshalJSON(data []byte) error {
	tmpMap := UserDataMap{}
	if err := json.Unmarshal(data, &tmpMap); err != nil {
		return err
	}
	ud.FromMap(tmpMap)
	return nil
}

// MarshalJSON implements JSON Marshaler interface
func (ud *UserData) MarshalJSON() ([]byte, error) {
	return json.Marshal(ud.ToMap())
}

// Extract unmarshalls a string a given struct if it exists
func (ud *UserData) Extract(id string, out interface{}) error {
	content, ok := ud.Load(id)
	if !ok {
		return &UserDataNotFound{id}
	}
	return yaml.Unmarshal([]byte(content.(string)), out)
}

// User Data are used to store custom information mainly by Mason and Masonable implementation.
// Mason used a LeasedResource keys to store information about other resources that used to
// create the given resource.

// Set marshalls a struct to a string into the UserData
func (ud *UserData) Set(id string, in interface{}) error {
	b, err := yaml.Marshal(in)
	if err != nil {
		return err
	}
	ud.Store(id, string(b))
	return nil
}

// Update updates existing UserData with new UserData.
// If a key as an empty string, the key will be deleted
func (ud *UserData) Update(new *UserData) *UserData {
	if new == nil {
		return ud
	}
	new.Range(func(key, value interface{}) bool {
		if value.(string) != "" {
			ud.Store(key, value)
		} else {
			ud.Delete(key)
		}
		return true
	})
	return ud
}

// ToMap converts a UserData to UserDataMap
func (ud *UserData) ToMap() UserDataMap {
	if ud == nil {
		return nil
	}
	m := UserDataMap{}
	ud.Range(func(key, value interface{}) bool {
		m[key.(string)] = value.(string)
		return true
	})
	return m
}

// FromMap feels updates user data from a map
func (ud *UserData) FromMap(m UserDataMap) {
	for key, value := range m {
		ud.Store(key, value)
	}
}

func ResourceTypeNotFoundMessage(rType string) string {
	return fmt.Sprintf("resource type %q does not exist", rType)
}
<|endoftext|>


<commit_before>package commands

import (
	"regexp"
	"rollcage/core"
	"strings"

	"github.com/cactus/cobra"
	"github.com/cactus/gologit"
)

var snapremoveRegex bool

func snapremoveCmdRun(cmd *cobra.Command, args []string) {
	// requires root
	if !core.IsRoot() {
		gologit.Fatalf("Must be root to snapremove\n")
	}

	jailpath := core.GetJailByTagOrUUID(args[0])
	if jailpath == "" {
		gologit.Fatalf("No jail found by '%s'\n", args[0])
	}

	matchers := args[1:]

	zfsArgs := []string{"list", "-Hrt", "snapshot",
		"-o", "name", "-d2", jailpath}
	lines := core.SplitOutput(core.ZFSMust(zfsArgs...))

	rmlist := []string{}
	for _, l := range lines {
		line := l[0]
		for _, m := range matchers {
			if snapremoveRegex {
				matched, err := regexp.MatchString(m, line)
				if err != nil {
					gologit.Fatalf("Regex error: %s", err)
				}
				if matched {
					rmlist = append(rmlist, line)
					continue
				}
			} else {
				if m == line {
					rmlist = append(rmlist, line)
					continue
				}
			}
		}
	}
	gologit.Debugf("match list: %#v\n", rmlist)

	for _, snap := range rmlist {
		gologit.Printf("Removing snapshot: %s", strings.Split(snap, "@")[1])
		core.ZFSMust("destroy", "-r", snap)
	}
}

func init() {
	cmd := &cobra.Command{
		Use:   "snapremove UUID|TAG snapshotname [snapshotname ...]",
		Short: "Remove snapshots belonging to jail",
		Run:   snapremoveCmdRun,
		PreRun: func(cmd *cobra.Command, args []string) {
			if len(args) == 0 {
				gologit.Fatalln("Required UUID|TAG not provided")
			} else if len(args) == 1 {
				gologit.Fatalln("Required snapshotname not provided")
			}
		},
	}

	cmd.Flags().BoolVarP(
		&snapremoveRegex, "regex", "x", false,
		"snapshotname becomes a match regex")

	RootCmd.AddCommand(cmd)
}
<commit_msg>fix snapremove<commit_after>package commands

import (
	"regexp"
	"rollcage/core"
	"strings"

	"github.com/cactus/cobra"
	"github.com/cactus/gologit"
)

var snapremoveRegex bool

func snapremoveCmdRun(cmd *cobra.Command, args []string) {
	// requires root
	if !core.IsRoot() {
		gologit.Fatalf("Must be root to snapremove\n")
	}

	jailpath := core.GetJailByTagOrUUID(args[0])
	if jailpath == "" {
		gologit.Fatalf("No jail found by '%s'\n", args[0])
	}

	matchers := args[1:]
	gologit.Debugf("matchers: %#v\n", matchers)

	zfsArgs := []string{"list", "-Hrt", "snapshot",
		"-o", "name", "-d2", jailpath}
	lines := core.SplitOutput(core.ZFSMust(zfsArgs...))

	rmlist := []string{}
	for _, line := range lines {
		if len(line) == 0 || len(line[0]) == 0 {
			continue
		}
		snapname := strings.SplitN(line[0], "@", 2)[1]
		gologit.Debugf("source snapname: %#v\n", snapname)
		for _, m := range matchers {
			if snapremoveRegex {
				matched, err := regexp.MatchString(m, snapname)
				if err != nil {
					gologit.Fatalf("Regex error: %s", err)
				}
				if matched {
					rmlist = append(rmlist, line[0])
					continue
				}
			} else {
				if m == snapname {
					rmlist = append(rmlist, line[0])
					continue
				}
			}
		}
	}
	gologit.Debugf("match list: %#v\n", rmlist)

	for _, snap := range rmlist {
		gologit.Printf("Removing snapshot: %s", strings.SplitN(snap, "@", 2)[1])
		core.ZFSMust("destroy", "-r", snap)
	}
}

func init() {
	cmd := &cobra.Command{
		Use:   "snapremove UUID|TAG snapshotname [snapshotname ...]",
		Short: "Remove snapshots belonging to jail",
		Run:   snapremoveCmdRun,
		PreRun: func(cmd *cobra.Command, args []string) {
			if len(args) == 0 {
				gologit.Fatalln("Required UUID|TAG not provided")
			} else if len(args) == 1 {
				gologit.Fatalln("Required snapshotname not provided")
			}
		},
	}

	cmd.Flags().BoolVarP(
		&snapremoveRegex, "regex", "x", false,
		"snapshotname becomes a match regex")

	RootCmd.AddCommand(cmd)
}
<|endoftext|>


<commit_before>package instancewriter

import (
	"archive/tar"
	"io"
	"os"
	"strings"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/idmap"
	"github.com/lxc/lxd/shared/logger"
)

// InstanceTarWriter provides a TarWriter implementation that handles ID shifting and hardlink tracking.
type InstanceTarWriter struct {
	tarWriter *tar.Writer
	idmapSet  *idmap.IdmapSet
	linkMap   map[uint64]string
}

// NewInstanceTarWriter returns a ContainerTarWriter for the provided target Writer and id map.
func NewInstanceTarWriter(writer io.Writer, idmapSet *idmap.IdmapSet) *InstanceTarWriter {
	ctw := new(InstanceTarWriter)
	ctw.tarWriter = tar.NewWriter(writer)
	ctw.idmapSet = idmapSet
	ctw.linkMap = map[uint64]string{}
	return ctw
}

// ResetHardLinkMap resets the hard link map. Use when copying multiple instances (or snapshots) into a tarball.
// So that the hard link map doesn't work across different instances/snapshots.
func (ctw *InstanceTarWriter) ResetHardLinkMap() {
	ctw.linkMap = map[uint64]string{}
}

// WriteFile adds a file to the tarball with the specified name using the srcPath file as the contents of the file.
// The ignoreGrowth argument indicates whether to error if the srcPath file increases in size beyond the size in fi
// during the write. If false the write will return an error. If true, no error is returned, instead only the size
// specified in fi is written to the tarball. This can be used when you don't need a consistent copy of the file.
func (ctw *InstanceTarWriter) WriteFile(name string, srcPath string, fi os.FileInfo, ignoreGrowth bool) error {
	var err error
	var major, minor uint32
	var nlink int
	var ino uint64

	link := ""
	if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
		link, err = os.Readlink(srcPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to resolve symlink for %q", srcPath)
		}
	}

	// Sockets cannot be stored in tarballs, just skip them (consistent with tar).
	if fi.Mode()&os.ModeSocket == os.ModeSocket {
		return nil
	}

	hdr, err := tar.FileInfoHeader(fi, link)
	if err != nil {
		return errors.Wrap(err, "Failed to create tar info header")
	}

	hdr.Name = name
	if fi.IsDir() || fi.Mode()&os.ModeSymlink == os.ModeSymlink {
		hdr.Size = 0
	} else {
		hdr.Size = fi.Size()
	}

	hdr.Uid, hdr.Gid, major, minor, ino, nlink, err = shared.GetFileStat(srcPath)
	if err != nil {
		return errors.Wrapf(err, "Failed to get file stat %q", srcPath)
	}

	// Unshift the id under rootfs/ for unpriv containers.
	if strings.HasPrefix(hdr.Name, "rootfs") && ctw.idmapSet != nil {
		hUID, hGID := ctw.idmapSet.ShiftFromNs(int64(hdr.Uid), int64(hdr.Gid))
		hdr.Uid = int(hUID)
		hdr.Gid = int(hGID)
		if hdr.Uid == -1 || hdr.Gid == -1 {
			return nil
		}
	}

	hdr.Devmajor = int64(major)
	hdr.Devminor = int64(minor)

	// If it's a hardlink we've already seen use the old name.
	if fi.Mode().IsRegular() && nlink > 1 {
		if firstPath, found := ctw.linkMap[ino]; found {
			hdr.Typeflag = tar.TypeLink
			hdr.Linkname = firstPath
			hdr.Size = 0
		} else {
			ctw.linkMap[ino] = hdr.Name
		}
	}

	// Handle xattrs (for real files only).
	if link == "" {
		xattrs, err := shared.GetAllXattr(srcPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to read xattr for %q", srcPath)
		}

		hdr.PAXRecords = make(map[string]string, len(xattrs))
		for key, val := range xattrs {
			if key == "system.posix_acl_access" {
				aclAccess, err := idmap.UnshiftACL(val, ctw.idmapSet)
				if err != nil {
					logger.Debugf("%s - Failed to unshift ACL access permissions", err)
					continue
				}
				hdr.PAXRecords["SCHILY.acl.access"] = aclAccess
			} else if key == "system.posix_acl_default" {
				aclDefault, err := idmap.UnshiftACL(val, ctw.idmapSet)
				if err != nil {
					logger.Debugf("%s - Failed to unshift ACL default permissions", err)
					continue
				}
				hdr.PAXRecords["SCHILY.acl.default"] = aclDefault
			} else if key == "security.capability" {
				vfsCaps, err := idmap.UnshiftCaps(val, ctw.idmapSet)
				if err != nil {
					logger.Debugf("%s - Failed to unshift vfs capabilities", err)
					continue
				}
				hdr.PAXRecords["SCHILY.xattr."+key] = vfsCaps
			} else {
				hdr.PAXRecords["SCHILY.xattr."+key] = val
			}
		}
	}

	err = ctw.tarWriter.WriteHeader(hdr)
	if err != nil {
		return errors.Wrap(err, "Failed to write tar header")
	}

	if hdr.Typeflag == tar.TypeReg {
		f, err := os.Open(srcPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to open file %q", srcPath)
		}
		defer f.Close()

		r := io.Reader(f)
		if ignoreGrowth {
			r = io.LimitReader(r, fi.Size())
		}

		_, err = io.Copy(ctw.tarWriter, r)
		if err != nil {
			return errors.Wrapf(err, "Failed to copy file content %q", srcPath)
		}
	}

	return nil
}

// WriteFileFromReader streams a file into the tarball using the src reader.
// A manually generated os.FileInfo should be supplied so that the tar header can be added before streaming starts.
func (ctw *InstanceTarWriter) WriteFileFromReader(src io.Reader, fi os.FileInfo) error {
	hdr, err := tar.FileInfoHeader(fi, "")
	if err != nil {
		return errors.Wrap(err, "Failed to create tar info header")
	}

	err = ctw.tarWriter.WriteHeader(hdr)
	if err != nil {
		return errors.Wrap(err, "Failed to write tar header")
	}

	_, err = io.Copy(ctw.tarWriter, src)
	return err
}

// Close finishes writing the tarball.
func (ctw *InstanceTarWriter) Close() error {
	err := ctw.tarWriter.Close()
	if err != nil {
		return errors.Wrap(err, "Failed to close tar writer")
	}
	return nil
}
<commit_msg>shared/instancewriter/instance/tar/writer: Handle nil idmapSet and log shifting errors in WriteFile<commit_after>package instancewriter

import (
	"archive/tar"
	"io"
	"os"
	"strings"

	"github.com/pkg/errors"

	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/idmap"
	"github.com/lxc/lxd/shared/logger"
)

// InstanceTarWriter provides a TarWriter implementation that handles ID shifting and hardlink tracking.
type InstanceTarWriter struct {
	tarWriter *tar.Writer
	idmapSet  *idmap.IdmapSet
	linkMap   map[uint64]string
}

// NewInstanceTarWriter returns a ContainerTarWriter for the provided target Writer and id map.
func NewInstanceTarWriter(writer io.Writer, idmapSet *idmap.IdmapSet) *InstanceTarWriter {
	ctw := new(InstanceTarWriter)
	ctw.tarWriter = tar.NewWriter(writer)
	ctw.idmapSet = idmapSet
	ctw.linkMap = map[uint64]string{}
	return ctw
}

// ResetHardLinkMap resets the hard link map. Use when copying multiple instances (or snapshots) into a tarball.
// So that the hard link map doesn't work across different instances/snapshots.
func (ctw *InstanceTarWriter) ResetHardLinkMap() {
	ctw.linkMap = map[uint64]string{}
}

// WriteFile adds a file to the tarball with the specified name using the srcPath file as the contents of the file.
// The ignoreGrowth argument indicates whether to error if the srcPath file increases in size beyond the size in fi
// during the write. If false the write will return an error. If true, no error is returned, instead only the size
// specified in fi is written to the tarball. This can be used when you don't need a consistent copy of the file.
func (ctw *InstanceTarWriter) WriteFile(name string, srcPath string, fi os.FileInfo, ignoreGrowth bool) error {
	var err error
	var major, minor uint32
	var nlink int
	var ino uint64

	link := ""
	if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
		link, err = os.Readlink(srcPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to resolve symlink for %q", srcPath)
		}
	}

	// Sockets cannot be stored in tarballs, just skip them (consistent with tar).
	if fi.Mode()&os.ModeSocket == os.ModeSocket {
		return nil
	}

	hdr, err := tar.FileInfoHeader(fi, link)
	if err != nil {
		return errors.Wrap(err, "Failed to create tar info header")
	}

	hdr.Name = name
	if fi.IsDir() || fi.Mode()&os.ModeSymlink == os.ModeSymlink {
		hdr.Size = 0
	} else {
		hdr.Size = fi.Size()
	}

	hdr.Uid, hdr.Gid, major, minor, ino, nlink, err = shared.GetFileStat(srcPath)
	if err != nil {
		return errors.Wrapf(err, "Failed to get file stat %q", srcPath)
	}

	// Unshift the id under rootfs/ for unpriv containers.
	if strings.HasPrefix(hdr.Name, "rootfs") && ctw.idmapSet != nil {
		hUID, hGID := ctw.idmapSet.ShiftFromNs(int64(hdr.Uid), int64(hdr.Gid))
		hdr.Uid = int(hUID)
		hdr.Gid = int(hGID)
		if hdr.Uid == -1 || hdr.Gid == -1 {
			return nil
		}
	}

	hdr.Devmajor = int64(major)
	hdr.Devminor = int64(minor)

	// If it's a hardlink we've already seen use the old name.
	if fi.Mode().IsRegular() && nlink > 1 {
		if firstPath, found := ctw.linkMap[ino]; found {
			hdr.Typeflag = tar.TypeLink
			hdr.Linkname = firstPath
			hdr.Size = 0
		} else {
			ctw.linkMap[ino] = hdr.Name
		}
	}

	// Handle xattrs (for real files only).
	if link == "" {
		xattrs, err := shared.GetAllXattr(srcPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to read xattr for %q", srcPath)
		}

		hdr.PAXRecords = make(map[string]string, len(xattrs))
		for key, val := range xattrs {
			if key == "system.posix_acl_access" && ctw.idmapSet != nil {
				aclAccess, err := idmap.UnshiftACL(val, ctw.idmapSet)
				if err != nil {
					logger.Debugf("Failed to unshift ACL access permissions of %q: %v", srcPath, err)
					continue
				}
				hdr.PAXRecords["SCHILY.acl.access"] = aclAccess
			} else if key == "system.posix_acl_default" && ctw.idmapSet != nil {
				aclDefault, err := idmap.UnshiftACL(val, ctw.idmapSet)
				if err != nil {
					logger.Debugf("Failed to unshift ACL default permissions of %q: %v", srcPath, err)
					continue
				}
				hdr.PAXRecords["SCHILY.acl.default"] = aclDefault
			} else if key == "security.capability" && ctw.idmapSet != nil {
				vfsCaps, err := idmap.UnshiftCaps(val, ctw.idmapSet)
				if err != nil {
					logger.Debugf("Failed to unshift VFS capabilities of %q: %v", srcPath, err)
					continue
				}
				hdr.PAXRecords["SCHILY.xattr."+key] = vfsCaps
			} else {
				hdr.PAXRecords["SCHILY.xattr."+key] = val
			}
		}
	}

	err = ctw.tarWriter.WriteHeader(hdr)
	if err != nil {
		return errors.Wrap(err, "Failed to write tar header")
	}

	if hdr.Typeflag == tar.TypeReg {
		f, err := os.Open(srcPath)
		if err != nil {
			return errors.Wrapf(err, "Failed to open file %q", srcPath)
		}
		defer f.Close()

		r := io.Reader(f)
		if ignoreGrowth {
			r = io.LimitReader(r, fi.Size())
		}

		_, err = io.Copy(ctw.tarWriter, r)
		if err != nil {
			return errors.Wrapf(err, "Failed to copy file content %q", srcPath)
		}
	}

	return nil
}

// WriteFileFromReader streams a file into the tarball using the src reader.
// A manually generated os.FileInfo should be supplied so that the tar header can be added before streaming starts.
func (ctw *InstanceTarWriter) WriteFileFromReader(src io.Reader, fi os.FileInfo) error {
	hdr, err := tar.FileInfoHeader(fi, "")
	if err != nil {
		return errors.Wrap(err, "Failed to create tar info header")
	}

	err = ctw.tarWriter.WriteHeader(hdr)
	if err != nil {
		return errors.Wrap(err, "Failed to write tar header")
	}

	_, err = io.Copy(ctw.tarWriter, src)
	return err
}

// Close finishes writing the tarball.
func (ctw *InstanceTarWriter) Close() error {
	err := ctw.tarWriter.Close()
	if err != nil {
		return errors.Wrap(err, "Failed to close tar writer")
	}
	return nil
}
<|endoftext|>


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

package main

import (
	"flag"
	"fmt"
	"log"
	"net/url"
	"os"
	"regexp"
	"sort"
	"strconv"
)

var version string

var ARG_MMC bool
var ARG_VERBOSE bool
var ARG_SKIPMODS bool

type command struct {
	Fn        func() error
	Desc      string
	ArgsCount int
	Args      string
}

var gCommands = map[string]command{
	"pack.create": command{
		Fn:        cmdPackCreate,
		Desc:      "Create a new mod pack",
		ArgsCount: 2,
		Args:      "<directory> <minecraft version> [<forge version>]",
	},
	"pack.install": command{
		Fn:        cmdPackInstall,
		Desc:      "Install a mod pack, optionally using a URL to download",
		ArgsCount: 1,
		Args:      "<directory> [<url>]",
	},
	"info": command{
		Fn:        cmdInfo,
		Desc:      "Show runtime info",
		ArgsCount: 0,
	},
	"mod.list": command{
		Fn:        cmdModList,
		Desc:      "List mods matching a name and Minecraft version",
		ArgsCount: 1,
		Args:      "<mod name> [<minecraft version>]",
	},
	"mod.select": command{
		Fn:        cmdModSelect,
		Desc:      "Select a mod to include in the specified pack",
		ArgsCount: 2,
		Args:      "<directory> <mod name or URL> [<tag>]",
	},
	"mod.select.client": command{
		Fn:        cmdModSelectClient,
		Desc:      "Select a client-side only mod to include in the specified pack",
		ArgsCount: 2,
		Args:      "<directory> <mod name or URL> [<tag>]",
	},
	"mod.update.all": command{
		Fn:        cmdModUpdateAll,
		Desc:      "Update all mods entries to latest available file",
		ArgsCount: 1,
		Args:      "<directory>",
	},
	"server.install": command{
		Fn:        cmdServerInstall,
		Desc:      "Install a Minecraft server using an existing pack",
		ArgsCount: 1,
		Args:      "<directory>",
	},
	"db.update": command{
		Fn:        cmdDBUpdate,
		Desc:      "Update local database of available mods",
		ArgsCount: 0,
	},
	"forge.list": command{
		Fn:        cmdForgeList,
		Desc:      "List available versions of Forge",
		ArgsCount: 1,
		Args:      "<minecraft version>",
	},
}

func cmdPackCreate() error {
	dir := flag.Arg(1)
	minecraftVsn := flag.Arg(2)
	forgeVsn := flag.Arg(3)

	// If no forge version was specified, open the database and find
	// a recommended one
	if forgeVsn == "" {
		db, err := OpenDatabase()
		if err != nil {
			return err
		}

		forgeVsn, err = db.lookupForgeVsn(minecraftVsn)
		if err != nil {
			return err
		}
	}

	// Create a new pack directory
	cp, err := NewModPack(dir, false, ARG_MMC)
	if err != nil {
		return err
	}

	// Create the manifest for this new pack
	err = cp.createManifest(dir, minecraftVsn, forgeVsn)
	if err != nil {
		return err
	}

	// Create the launcher profile (and install forge if necessary)
	err = cp.createLauncherProfile()
	if err != nil {
		return err
	}

	return nil
}

func cmdPackInstall() error {
	dir := flag.Arg(1)
	url := flag.Arg(2)

	// Only require a manifest if we're not installing from a URL
	requireManifest := (url == "")

	cp, err := NewModPack(dir, requireManifest, ARG_MMC)
	if err != nil {
		return err
	}

	if url != "" {
		// Download the pack
		err = cp.download(url)
		if err != nil {
			return err
		}

		// Process manifest
		err = cp.processManifest()
		if err != nil {
			return err
		}

		// Install overrides from the modpack; this is a bit of a misnomer since
		// under usual circumstances there are no mods in the modpack file that
		// will be also be downloaded
		err = cp.installOverrides()
		if err != nil {
			return err
		}
	}

	// If the -mmc flag is provided, don't create a launcher profile; just generate
	// an instance.cfg for MultiMC to use
	if ARG_MMC == true {
		err = cp.generateMMCConfig()
		if err != nil {
			return err
		}
	} else {
		// Create launcher profile
		err = cp.createLauncherProfile()
		if err != nil {
			return err
		}
	}

	if ARG_SKIPMODS == false {
		// Install mods (include client-side only mods)
		err = cp.installMods(true)
		if err != nil {
			return err
		}
	}

	return nil
}

func cmdInfo() error {
	// Try to retrieve the latest available version info
	publishedVsn, err := getLatestVersion("release")

	if err != nil && ARG_VERBOSE {
		fmt.Printf("%s\n", err)
	}

	if err == nil && publishedVsn != "" && version != publishedVsn {
		fmt.Printf("Version: %s (%s is available for download)\n", version, publishedVsn)
	} else {
		fmt.Printf("Version: %s\n", version)
	}

	// Print the environment
	fmt.Printf("Environment:\n")
	fmt.Printf("* Minecraft dir: %s\n", env().MinecraftDir)
	fmt.Printf("* mcdex dir: %s\n", env().McdexDir)
	fmt.Printf("* Java dir: %s\n", env().JavaDir)
	return nil
}

func cmdModSelect() error {
	return _modSelect(false)
}

func cmdModSelectClient() error {
	return _modSelect(true)
}

var curseForgeRegex = regexp.MustCompile("/projects/([\\w-]*)(/files/(\\d+))?")

func _modSelect(clientOnly bool) error {
	dir := flag.Arg(1)
	mod := flag.Arg(2)
	tag := flag.Arg(3)

	// Try to open the mod pack
	cp, err := NewModPack(dir, true, ARG_MMC)
	if err != nil {
		return err
	}

	db, err := OpenDatabase()
	if err != nil {
		return err
	}

	var modID int
	var fileID int

	// Try to parse the mod as a URL
	url, err := url.Parse(mod)
	if err == nil {
		// We have a URL; if it's not a CurseForge URL, treat it as an external file
		if url.Host != "minecraft.curseforge.com" {
			return cp.selectModURL(mod, tag, clientOnly)
		}

		// Otherwise, try to parse the project name & file ID out of the URL path
		parts := curseForgeRegex.FindStringSubmatch(url.Path)
		if len(parts) < 4 {
			// Unknown structure on the CurseForge path; bail
			return fmt.Errorf("invalid CurseForge URL")
		}

		modSlug := parts[1]
		fileID, _ = strconv.Atoi(parts[3])

		// Lookup the modID using the slug in a URL
		modID, err = db.findModByURL("https://minecraft.curseforge.com/projects/" + modSlug)
		if err != nil {
			return err
		}
	} else {
		// Try to lookup the mod ID by name
		modID, err = db.findModByName(mod)
		if err != nil {
			return err
		}
	}

	// At this point, we should have a modID and we may have a fileID. We want to walk major.minor.[patch]
	// versions, and find either the latest file for our version of minecraft or verify that the fileID
	// we have will work on this version
	major, minor, patch, err := parseVersion(cp.minecraftVersion())
	if err != nil {
		// Invalid version string?!
		return err
	}

	// Walk down patch versions, looking for our mod + file (or latest file if no fileID available)
	for i := patch; i > -1; i-- {
		var vsn string
		if i > 0 {
			vsn = fmt.Sprintf("%d.%d.%d", major, minor, i)
		} else {
			vsn = fmt.Sprintf("%d.%d", major, minor)
		}

		modFile, err := db.findModFile(modID, fileID, vsn)
		if err == nil {
			return cp.selectModFile(modFile, clientOnly)
		}
	}

	// Didn't find a file that matches :(
	return fmt.Errorf("No compatible file found for %s", mod)
}

func cmdModList() error {
	name := flag.Arg(1)
	mcvsn := flag.Arg(2)

	db, err := OpenDatabase()
	if err != nil {
		return err
	}

	return db.listMods(name, mcvsn)
}

func cmdModUpdateAll() error {
	dir := flag.Arg(1)

	cp, err := NewModPack(dir, true, ARG_MMC)
	if err != nil {
		return err
	}

	db, err := OpenDatabase()
	if err != nil {
		return err
	}

	err = cp.updateMods(db)
	if err != nil {
		return err
	}

	return nil
}

func cmdForgeList() error {
	mcvsn := flag.Arg(1)

	db, err := OpenDatabase()
	if err != nil {
		return err
	}

	return db.listForge(mcvsn, ARG_VERBOSE)
}

func cmdServerInstall() error {
	dir := flag.Arg(1)

	if ARG_MMC == true {
		return fmt.Errorf("-mmc arg not supported when installing a server")
	}

	// Open the pack; we require the manifest and any
	// config files to already be present
	cp, err := NewModPack(dir, true, false)
	if err != nil {
		return err
	}

	// Install the server jar, Forge and dependencies
	err = cp.installServer()
	if err != nil {
		return err
	}

	// Make sure all mods are installed (do NOT include client-side only)
	err = cp.installMods(false)
	if err != nil {
		return err
	}

	return nil
	// Setup the command-line
	// java -jar <forge.jar>
}

func cmdDBUpdate() error {
	return InstallDatabase()
}

func console(f string, args ...interface{}) {
	fmt.Printf(f, args...)
}

func usage() {
	console("usage: mcdex [<options>] <command> [<args>]\n")
	console(" commands:\n")

	// Sort the list of keys in gCommands
	keys := []string{}
	for k := range gCommands {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, cmd := range keys {
		console(" - %s: %s\n", cmd, gCommands[cmd].Desc)
	}
}

func main() {
	// Register
	flag.BoolVar(&ARG_MMC, "mmc", false, "Generate MultiMC instance.cfg when installing a pack")
	flag.BoolVar(&ARG_VERBOSE, "v", false, "Enable verbose logging of operations")
	flag.BoolVar(&ARG_SKIPMODS, "skipmods", false, "Skip download of mods when installing a pack")

	// Process command-line args
	flag.Parse()
	if !flag.Parsed() || flag.NArg() < 1 {
		usage()
		os.Exit(-1)
	}

	// Initialize our environment
	err := initEnv()
	if err != nil {
		log.Fatalf("Failed to initialize: %s\n", err)
	}

	commandName := flag.Arg(0)
	command, exists := gCommands[commandName]
	if !exists {
		console("ERROR: unknown command '%s'\n", commandName)
		usage()
		os.Exit(-1)
	}

	// Check that the required number of arguments is present
	if flag.NArg() < command.ArgsCount+1 {
		console("ERROR: insufficient arguments for %s\n", commandName)
		console("usage: mcdex %s %s\n", commandName, command.Args)
		os.Exit(-1)
	}

	err = command.Fn()
	if err != nil {
		log.Fatalf("%+v\n", err)
	}
}
<commit_msg>Fix stupid URL parsing bug<commit_after>// ***************************************************************************
//
//  Copyright 2017 David (Dizzy) Smith, dizzyd@dizzyd.com
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
// ***************************************************************************

package main

import (
	"flag"
	"fmt"
	"log"
	"net/url"
	"os"
	"regexp"
	"sort"
	"strconv"
)

var version string

var ARG_MMC bool
var ARG_VERBOSE bool
var ARG_SKIPMODS bool

type command struct {
	Fn        func() error
	Desc      string
	ArgsCount int
	Args      string
}

var gCommands = map[string]command{
	"pack.create": command{
		Fn:        cmdPackCreate,
		Desc:      "Create a new mod pack",
		ArgsCount: 2,
		Args:      "<directory> <minecraft version> [<forge version>]",
	},
	"pack.install": command{
		Fn:        cmdPackInstall,
		Desc:      "Install a mod pack, optionally using a URL to download",
		ArgsCount: 1,
		Args:      "<directory> [<url>]",
	},
	"info": command{
		Fn:        cmdInfo,
		Desc:      "Show runtime info",
		ArgsCount: 0,
	},
	"mod.list": command{
		Fn:        cmdModList,
		Desc:      "List mods matching a name and Minecraft version",
		ArgsCount: 1,
		Args:      "<mod name> [<minecraft version>]",
	},
	"mod.select": command{
		Fn:        cmdModSelect,
		Desc:      "Select a mod to include in the specified pack",
		ArgsCount: 2,
		Args:      "<directory> <mod name or URL> [<tag>]",
	},
	"mod.select.client": command{
		Fn:        cmdModSelectClient,
		Desc:      "Select a client-side only mod to include in the specified pack",
		ArgsCount: 2,
		Args:      "<directory> <mod name or URL> [<tag>]",
	},
	"mod.update.all": command{
		Fn:        cmdModUpdateAll,
		Desc:      "Update all mods entries to latest available file",
		ArgsCount: 1,
		Args:      "<directory>",
	},
	"server.install": command{
		Fn:        cmdServerInstall,
		Desc:      "Install a Minecraft server using an existing pack",
		ArgsCount: 1,
		Args:      "<directory>",
	},
	"db.update": command{
		Fn:        cmdDBUpdate,
		Desc:      "Update local database of available mods",
		ArgsCount: 0,
	},
	"forge.list": command{
		Fn:        cmdForgeList,
		Desc:      "List available versions of Forge",
		ArgsCount: 1,
		Args:      "<minecraft version>",
	},
}

func cmdPackCreate() error {
	dir := flag.Arg(1)
	minecraftVsn := flag.Arg(2)
	forgeVsn := flag.Arg(3)

	// If no forge version was specified, open the database and find
	// a recommended one
	if forgeVsn == "" {
		db, err := OpenDatabase()
		if err != nil {
			return err
		}

		forgeVsn, err = db.lookupForgeVsn(minecraftVsn)
		if err != nil {
			return err
		}
	}

	// Create a new pack directory
	cp, err := NewModPack(dir, false, ARG_MMC)
	if err != nil {
		return err
	}

	// Create the manifest for this new pack
	err = cp.createManifest(dir, minecraftVsn, forgeVsn)
	if err != nil {
		return err
	}

	// Create the launcher profile (and install forge if necessary)
	err = cp.createLauncherProfile()
	if err != nil {
		return err
	}

	return nil
}

func cmdPackInstall() error {
	dir := flag.Arg(1)
	url := flag.Arg(2)

	// Only require a manifest if we're not installing from a URL
	requireManifest := (url == "")

	cp, err := NewModPack(dir, requireManifest, ARG_MMC)
	if err != nil {
		return err
	}

	if url != "" {
		// Download the pack
		err = cp.download(url)
		if err != nil {
			return err
		}

		// Process manifest
		err = cp.processManifest()
		if err != nil {
			return err
		}

		// Install overrides from the modpack; this is a bit of a misnomer since
		// under usual circumstances there are no mods in the modpack file that
		// will be also be downloaded
		err = cp.installOverrides()
		if err != nil {
			return err
		}
	}

	// If the -mmc flag is provided, don't create a launcher profile; just generate
	// an instance.cfg for MultiMC to use
	if ARG_MMC == true {
		err = cp.generateMMCConfig()
		if err != nil {
			return err
		}
	} else {
		// Create launcher profile
		err = cp.createLauncherProfile()
		if err != nil {
			return err
		}
	}

	if ARG_SKIPMODS == false {
		// Install mods (include client-side only mods)
		err = cp.installMods(true)
		if err != nil {
			return err
		}
	}

	return nil
}

func cmdInfo() error {
	// Try to retrieve the latest available version info
	publishedVsn, err := getLatestVersion("release")

	if err != nil && ARG_VERBOSE {
		fmt.Printf("%s\n", err)
	}

	if err == nil && publishedVsn != "" && version != publishedVsn {
		fmt.Printf("Version: %s (%s is available for download)\n", version, publishedVsn)
	} else {
		fmt.Printf("Version: %s\n", version)
	}

	// Print the environment
	fmt.Printf("Environment:\n")
	fmt.Printf("* Minecraft dir: %s\n", env().MinecraftDir)
	fmt.Printf("* mcdex dir: %s\n", env().McdexDir)
	fmt.Printf("* Java dir: %s\n", env().JavaDir)
	return nil
}

func cmdModSelect() error {
	return _modSelect(false)
}

func cmdModSelectClient() error {
	return _modSelect(true)
}

var curseForgeRegex = regexp.MustCompile("/projects/([\\w-]*)(/files/(\\d+))?")

func _modSelect(clientOnly bool) error {
	dir := flag.Arg(1)
	mod := flag.Arg(2)
	tag := flag.Arg(3)

	// Try to open the mod pack
	cp, err := NewModPack(dir, true, ARG_MMC)
	if err != nil {
		return err
	}

	db, err := OpenDatabase()
	if err != nil {
		return err
	}

	var modID int
	var fileID int

	// Try to parse the mod as a URL
	url, err := url.Parse(mod)
	if err == nil && (url.Scheme == "http" || url.Scheme == "https") {
		// We have a URL; if it's not a CurseForge URL, treat it as an external file
		if url.Host != "minecraft.curseforge.com" {
			return cp.selectModURL(mod, tag, clientOnly)
		}

		// Otherwise, try to parse the project name & file ID out of the URL path
		parts := curseForgeRegex.FindStringSubmatch(url.Path)
		if len(parts) < 4 {
			// Unknown structure on the CurseForge path; bail
			return fmt.Errorf("invalid CurseForge URL")
		}

		modSlug := parts[1]
		fileID, _ = strconv.Atoi(parts[3])

		// Lookup the modID using the slug in a URL
		modID, err = db.findModByURL("https://minecraft.curseforge.com/projects/" + modSlug)
		if err != nil {
			return err
		}
	} else {
		// Try to lookup the mod ID by name
		modID, err = db.findModByName(mod)
		if err != nil {
			return err
		}
	}

	// At this point, we should have a modID and we may have a fileID. We want to walk major.minor.[patch]
	// versions, and find either the latest file for our version of minecraft or verify that the fileID
	// we have will work on this version
	major, minor, patch, err := parseVersion(cp.minecraftVersion())
	if err != nil {
		// Invalid version string?!
		return err
	}

	// Walk down patch versions, looking for our mod + file (or latest file if no fileID available)
	for i := patch; i > -1; i-- {
		var vsn string
		if i > 0 {
			vsn = fmt.Sprintf("%d.%d.%d", major, minor, i)
		} else {
			vsn = fmt.Sprintf("%d.%d", major, minor)
		}

		modFile, err := db.findModFile(modID, fileID, vsn)
		if err == nil {
			return cp.selectModFile(modFile, clientOnly)
		}
	}

	// Didn't find a file that matches :(
	return fmt.Errorf("No compatible file found for %s", mod)
}

func cmdModList() error {
	name := flag.Arg(1)
	mcvsn := flag.Arg(2)

	db, err := OpenDatabase()
	if err != nil {
		return err
	}

	return db.listMods(name, mcvsn)
}

func cmdModUpdateAll() error {
	dir := flag.Arg(1)

	cp, err := NewModPack(dir, true, ARG_MMC)
	if err != nil {
		return err
	}

	db, err := OpenDatabase()
	if err != nil {
		return err
	}

	err = cp.updateMods(db)
	if err != nil {
		return err
	}

	return nil
}

func cmdForgeList() error {
	mcvsn := flag.Arg(1)

	db, err := OpenDatabase()
	if err != nil {
		return err
	}

	return db.listForge(mcvsn, ARG_VERBOSE)
}

func cmdServerInstall() error {
	dir := flag.Arg(1)

	if ARG_MMC == true {
		return fmt.Errorf("-mmc arg not supported when installing a server")
	}

	// Open the pack; we require the manifest and any
	// config files to already be present
	cp, err := NewModPack(dir, true, false)
	if err != nil {
		return err
	}

	// Install the server jar, Forge and dependencies
	err = cp.installServer()
	if err != nil {
		return err
	}

	// Make sure all mods are installed (do NOT include client-side only)
	err = cp.installMods(false)
	if err != nil {
		return err
	}

	return nil
	// Setup the command-line
	// java -jar <forge.jar>
}

func cmdDBUpdate() error {
	return InstallDatabase()
}

func console(f string, args ...interface{}) {
	fmt.Printf(f, args...)
}

func usage() {
	console("usage: mcdex [<options>] <command> [<args>]\n")
	console(" commands:\n")

	// Sort the list of keys in gCommands
	keys := []string{}
	for k := range gCommands {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, cmd := range keys {
		console(" - %s: %s\n", cmd, gCommands[cmd].Desc)
	}
}

func main() {
	// Register
	flag.BoolVar(&ARG_MMC, "mmc", false, "Generate MultiMC instance.cfg when installing a pack")
	flag.BoolVar(&ARG_VERBOSE, "v", false, "Enable verbose logging of operations")
	flag.BoolVar(&ARG_SKIPMODS, "skipmods", false, "Skip download of mods when installing a pack")

	// Process command-line args
	flag.Parse()
	if !flag.Parsed() || flag.NArg() < 1 {
		usage()
		os.Exit(-1)
	}

	// Initialize our environment
	err := initEnv()
	if err != nil {
		log.Fatalf("Failed to initialize: %s\n", err)
	}

	commandName := flag.Arg(0)
	command, exists := gCommands[commandName]
	if !exists {
		console("ERROR: unknown command '%s'\n", commandName)
		usage()
		os.Exit(-1)
	}

	// Check that the required number of arguments is present
	if flag.NArg() < command.ArgsCount+1 {
		console("ERROR: insufficient arguments for %s\n", commandName)
		console("usage: mcdex %s %s\n", commandName, command.Args)
		os.Exit(-1)
	}

	err = command.Fn()
	if err != nil {
		log.Fatalf("%+v\n", err)
	}
}
<|endoftext|>


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

package view

import (
	"github.com/google/shenzhen-go/dev/dom"
	"golang.org/x/net/context"
)

const (
	nametagRectStyle = "fill: #efe; stroke: #353; stroke-width:1"
	nametagTextStyle = "font-family:Go; font-size:16; user-select:none; pointer-events:none"
)

// Pin represents a node pin visually, and has enough information to know
// if it is validly connected.
type Pin struct {
	Group                          // Container for all the pin elements.
	Shape              dom.Element // The pin itself.
	Nametag            *TextBox    // Temporarily visible on hover.
	dragLine, dragCirc dom.Element // Temporary elements when dragging from unattached pin.

	// Computed, absolute coordinates (not relative to node).
	x, y float64

	pc PinController

	node *Node    // owner.
	ch   *Channel // attached to this channel, is often nil
}

func (p *Pin) reallyConnect() {
	// Attach to the existing channel
	if err := p.pc.Attach(context.TODO(), p.ch.cc); err != nil {
		p.node.view.setError("Couldn't connect: " + err.Error())
	}
}

func (p *Pin) disconnect() {
	if p.ch == nil {
		return
	}
	go p.reallyDisconnect()
	delete(p.ch.Pins, p)
	p.ch.setColour(normalColour)
	p.ch.reposition(nil)
	if len(p.ch.Pins) < 2 {
		// Delete the channel
		for q := range p.ch.Pins {
			q.ch = nil
		}
		delete(p.node.view.graph.Channels, p.ch.cc.Name())
	}
	p.ch = nil
}

func (p *Pin) reallyDisconnect() {
	if err := p.pc.Detach(context.TODO()); err != nil {
		p.node.view.setError("Couldn't disconnect: " + err.Error())
	}
}

// MoveTo moves the pin (relatively).
func (p *Pin) MoveTo(rx, ry float64) {
	p.Group.MoveTo(rx, ry)
	p.x, p.y = rx+p.node.x, ry+p.node.y
}

// Pt returns the diagram coordinate of the pin, for nearest-neighbor purposes.
func (p *Pin) Pt() (x, y float64) { return p.x, p.y }

func (p *Pin) String() string { return p.node.nc.Name() + "." + p.pc.Name() }

func (p *Pin) connectTo(q Point) {
	switch q := q.(type) {
	case *Pin:
		if p.ch != nil && p.ch != q.ch {
			p.disconnect()
		}
		if q.ch != nil {
			p.connectTo(q.ch)
			return
		}

		// Create a new channel to connect to
		ch := p.node.view.createChannel(p, q)
		ch.reposition(nil)

	case *Channel:
		if p.ch != nil && p.ch != q {
			p.disconnect()
		}

		p.ch = q
		q.Pins[p] = &Route{}
		q.reposition(nil)
	}
	return
}

func (p *Pin) dragStart(e dom.Object) {
	// If a channel is attached, detach and drag from that instead.
	if p.ch != nil {
		p.disconnect()
		p.ch.dragStart(e)
		return
	}

	// Not attached, so the pin is the drag item and show the temporary line and circle.
	p.node.view.dragItem = p
	x, y := p.node.view.diagramCursorPos(e)

	// Start with errorColour because we're probably only in range of ourself.
	p.dragTo(x, y, errorColour)
}

func (p *Pin) drag(e dom.Object) {
	x, y := p.node.view.diagramCursorPos(e)
	colour := activeColour

	d, q := p.node.view.graph.nearestPoint(x, y)

	// Don't connect P to itself, don't connect if nearest is far away.
	if p == q || d >= snapQuad {
		p.node.view.clearError()
		if p.ch != nil {
			p.ch.setColour(normalColour)
			p.disconnect()
		}
		colour = errorColour
		p.Shape.SetAttribute("fill", colour)
		p.dragTo(x-p.x, y-p.y, colour)
		return
	}

	// Make the connection - this is the responsibility of the channel.
	p.node.view.clearError()
	colour = activeColour
	p.connectTo(q)
	p.ch.setColour(colour)
	p.hideDrag()
}

func (p *Pin) drop(e dom.Object) {
	p.node.view.clearError()
	p.Shape.SetAttribute("fill", normalColour)
	p.hideDrag()
	if p.ch == nil {
		go p.reallyDisconnect()
		return
	}
	if p.ch.created {
		go p.reallyConnect()
	}
	p.ch.setColour(normalColour)
	p.ch.commit()
}

// Show the temporary drag elements with a specific colour.
// Coordinates are pin relative.
func (p *Pin) dragTo(rx, ry float64, stroke string) {
	p.dragLine.
		SetAttribute("x2", rx).
		SetAttribute("y2", ry).
		SetAttribute("stroke", stroke).
		SetAttribute("stroke-width", lineWidth).
		Show()
	p.dragCirc.
		SetAttribute("cx", rx).
		SetAttribute("cy", ry).
		SetAttribute("stroke", stroke).
		SetAttribute("stroke-width", lineWidth).
		Show()
}

func (p *Pin) hideDrag() {
	p.dragLine.Hide()
	p.dragCirc.Hide()
}

func (p *Pin) mouseEnter(dom.Object) {
	x, y := 8.0, 0.0
	if p.pc.IsInput() {
		y -= 38
	} else {
		y += 8
	}
	p.Nametag.MoveTo(x, y).Show()
}

func (p *Pin) mouseLeave(dom.Object) {
	p.Nametag.Hide()
}

// MakeElements creates elements associated with this pin.
func (p *Pin) MakeElements(doc dom.Document, parent dom.Element) *Pin {
	// Container for the pin elements.
	p.Group = NewGroup(doc, parent)

	// The pin itself, visually
	p.Shape = doc.MakeSVGElement("circle").
		SetAttribute("r", pinRadius).
		SetAttribute("fill", normalColour).
		AddEventListener("mousedown", p.dragStart).
		AddEventListener("mouseenter", p.mouseEnter).
		AddEventListener("mouseleave", p.mouseLeave)

	// Nametag textbox.
	p.Nametag = &TextBox{Margin: 20, TextOffsetY: 5}
	p.Nametag.
		MakeElements(doc, p.Group).
		SetHeight(30).
		SetTextStyle(nametagTextStyle).
		SetRectStyle(nametagRectStyle).
		SetText(p.pc.Name() + " (" + p.pc.Type() + ")")
	p.Nametag.RecomputeWidth()
	p.Nametag.Hide()

	// Temporarily-visible elements when dragging from an unattached pin.
	p.dragLine = doc.MakeSVGElement("line").
		SetAttribute("stroke", normalColour).
		Hide()
	p.dragCirc = doc.MakeSVGElement("circ").
		SetAttribute("r", pinRadius).
		SetAttribute("stroke", normalColour).
		Hide()

	p.Group.AddChildren(p.Shape, p.dragLine, p.dragCirc)
	return p
}

// AddTo adds the pin's group as a child to the given parent.
func (p *Pin) AddTo(parent dom.Element) *Pin {
	parent.AddChildren(p.Group)
	return p
}

// Remove removes the group from its parent.
func (p *Pin) Remove() {
	p.Group.Parent().RemoveChildren(p.Group)
}
<commit_msg>Shorten<commit_after>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package view

import (
	"github.com/google/shenzhen-go/dev/dom"
	"golang.org/x/net/context"
)

const (
	nametagRectStyle = "fill: #efe; stroke: #353; stroke-width:1"
	nametagTextStyle = "font-family:Go; font-size:16; user-select:none; pointer-events:none"
)

// Pin represents a node pin visually, and has enough information to know
// if it is validly connected.
type Pin struct {
	Group                          // Container for all the pin elements.
	Shape              dom.Element // The pin itself.
	Nametag            *TextBox    // Temporarily visible on hover.
	dragLine, dragCirc dom.Element // Temporary elements when dragging from unattached pin.

	// Computed, absolute coordinates (not relative to node).
	x, y float64

	pc PinController

	node *Node    // owner.
	ch   *Channel // attached to this channel, is often nil
}

func (p *Pin) reallyConnect() {
	// Attach to the existing channel
	if err := p.pc.Attach(context.TODO(), p.ch.cc); err != nil {
		p.node.view.setError("Couldn't connect: " + err.Error())
	}
}

func (p *Pin) disconnect() {
	if p.ch == nil {
		return
	}
	go p.reallyDisconnect()
	delete(p.ch.Pins, p)
	p.ch.setColour(normalColour)
	p.ch.reposition(nil)
	if len(p.ch.Pins) < 2 {
		// Delete the channel
		for q := range p.ch.Pins {
			q.ch = nil
		}
		delete(p.node.view.graph.Channels, p.ch.cc.Name())
	}
	p.ch = nil
}

func (p *Pin) reallyDisconnect() {
	if err := p.pc.Detach(context.TODO()); err != nil {
		p.node.view.setError("Couldn't disconnect: " + err.Error())
	}
}

// MoveTo moves the pin (relatively).
func (p *Pin) MoveTo(rx, ry float64) {
	p.Group.MoveTo(rx, ry)
	p.x, p.y = rx+p.node.x, ry+p.node.y
}

// Pt returns the diagram coordinate of the pin, for nearest-neighbor purposes.
func (p *Pin) Pt() (x, y float64) { return p.x, p.y }

func (p *Pin) String() string { return p.node.nc.Name() + "." + p.pc.Name() }

func (p *Pin) connectTo(q Point) {
	switch q := q.(type) {
	case *Pin:
		if p.ch != nil && p.ch != q.ch {
			p.disconnect()
		}
		if q.ch != nil {
			p.connectTo(q.ch)
			return
		}

		// Create a new channel to connect to
		ch := p.node.view.createChannel(p, q)
		ch.reposition(nil)

	case *Channel:
		if p.ch != nil && p.ch != q {
			p.disconnect()
		}

		p.ch = q
		q.Pins[p] = &Route{}
		q.reposition(nil)
	}
	return
}

func (p *Pin) dragStart(e dom.Object) {
	// If a channel is attached, detach and drag from that instead.
	if p.ch != nil {
		p.disconnect()
		p.ch.dragStart(e)
		return
	}

	// Not attached, so the pin is the drag item and show the temporary line and circle.
	p.node.view.dragItem = p
	x, y := p.node.view.diagramCursorPos(e)

	// Start with errorColour because we're probably only in range of ourself.
	p.dragTo(x, y, errorColour)
}

func (p *Pin) drag(e dom.Object) {
	x, y := p.node.view.diagramCursorPos(e)
	colour := activeColour

	d, q := p.node.view.graph.nearestPoint(x, y)

	// Don't connect P to itself, don't connect if nearest is far away.
	if p == q || d >= snapQuad {
		p.node.view.clearError()
		if p.ch != nil {
			p.ch.setColour(normalColour)
			p.disconnect()
		}
		colour = errorColour
		p.Shape.SetAttribute("fill", colour)
		p.dragTo(x-p.x, y-p.y, colour)
		return
	}

	// Make the connection - this is the responsibility of the channel.
	p.node.view.clearError()
	colour = activeColour
	p.connectTo(q)
	p.ch.setColour(colour)
	p.hideDrag()
}

func (p *Pin) drop(e dom.Object) {
	p.node.view.clearError()
	p.Shape.SetAttribute("fill", normalColour)
	p.hideDrag()
	if p.ch == nil {
		go p.reallyDisconnect()
		return
	}
	if p.ch.created {
		go p.reallyConnect()
	}
	p.ch.setColour(normalColour)
	p.ch.commit()
}

// Show the temporary drag elements with a specific colour.
// Coordinates are pin relative.
func (p *Pin) dragTo(rx, ry float64, stroke string) {
	p.dragLine.
		SetAttribute("x2", rx).
		SetAttribute("y2", ry).
		SetAttribute("stroke", stroke).
		SetAttribute("stroke-width", lineWidth).
		Show()
	p.dragCirc.
		SetAttribute("cx", rx).
		SetAttribute("cy", ry).
		SetAttribute("stroke", stroke).
		SetAttribute("stroke-width", lineWidth).
		Show()
}

func (p *Pin) hideDrag() {
	p.dragLine.Hide()
	p.dragCirc.Hide()
}

func (p *Pin) mouseEnter(dom.Object) {
	x, y := 8.0, 8.0
	if p.pc.IsInput() {
		y = -38
	}
	p.Nametag.MoveTo(x, y).Show()
}

func (p *Pin) mouseLeave(dom.Object) {
	p.Nametag.Hide()
}

// MakeElements creates elements associated with this pin.
func (p *Pin) MakeElements(doc dom.Document, parent dom.Element) *Pin {
	// Container for the pin elements.
	p.Group = NewGroup(doc, parent)

	// The pin itself, visually
	p.Shape = doc.MakeSVGElement("circle").
		SetAttribute("r", pinRadius).
		SetAttribute("fill", normalColour).
		AddEventListener("mousedown", p.dragStart).
		AddEventListener("mouseenter", p.mouseEnter).
		AddEventListener("mouseleave", p.mouseLeave)

	// Nametag textbox.
	p.Nametag = &TextBox{Margin: 20, TextOffsetY: 5}
	p.Nametag.
		MakeElements(doc, p.Group).
		SetHeight(30).
		SetTextStyle(nametagTextStyle).
		SetRectStyle(nametagRectStyle).
		SetText(p.pc.Name() + " (" + p.pc.Type() + ")")
	p.Nametag.RecomputeWidth()
	p.Nametag.Hide()

	// Temporarily-visible elements when dragging from an unattached pin.
	p.dragLine = doc.MakeSVGElement("line").
		SetAttribute("stroke", normalColour).
		Hide()
	p.dragCirc = doc.MakeSVGElement("circ").
		SetAttribute("r", pinRadius).
		SetAttribute("stroke", normalColour).
		Hide()

	p.Group.AddChildren(p.Shape, p.dragLine, p.dragCirc)
	return p
}

// AddTo adds the pin's group as a child to the given parent.
func (p *Pin) AddTo(parent dom.Element) *Pin {
	parent.AddChildren(p.Group)
	return p
}

// Remove removes the group from its parent.
func (p *Pin) Remove() {
	p.Group.Parent().RemoveChildren(p.Group)
}
<|endoftext|>


<commit_before>package net

import (
	"io"
	"log"
	"net"
	"sync"

	"../redlot"
)

var info struct {
	sync.RWMutex
	ConnCounter uint64
	TotalCalls  uint64
	reply       Reply
}

func init() {
	// Register commands.
	// system info
	REG("INFO", STATUS_REPLY, Info)

	// KV type
	REG("GET", BULK_REPLY, redlot.Get)
	REG("SET", STATUS_REPLY, redlot.Set)
	REG("DEL", STATUS_REPLY, redlot.Del)
	REG("EXISTS", INT_REPLY, redlot.Exists)
	REG("SETX", STATUS_REPLY, redlot.Setx)
	REG("SETEX", STATUS_REPLY, redlot.Setx) // Alias of SETX
	REG("TTL", INT_REPLY, redlot.Ttl)

}

func Serve(addr string, options *redlot.Options) {
	// Open LevelDB with options.
	redlot.Open(options)

	// Create sockets listener.
	l, err := net.Listen("tcp4", addr)
	if err != nil {
		log.Fatalf("Listen error: %v\n", err.Error())
	}
	defer l.Close()

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Fatalf("Wait for a connection error: %s\n", err.Error())
		}

		// Count connecion
		info.Lock()
		info.ConnCounter++
		info.Unlock()

		go func(c net.Conn) {
			for {
				req, err := newRequset(c)
				if err == io.EOF {
					break
				}
				if err != nil {
					continue
				}

				info.Lock()
				info.TotalCalls++
				info.Unlock()

				reply := RUN(req.Cmd, req.Args)
				reply.WriteTo(c)
			}

			c.Close()
			info.Lock()
			info.ConnCounter--
			info.Unlock()

		}(conn)

	}
}
<commit_msg>Register EXPIRE command.<commit_after>package net

import (
	"io"
	"log"
	"net"
	"sync"

	"../redlot"
)

var info struct {
	sync.RWMutex
	ConnCounter uint64
	TotalCalls  uint64
	reply       Reply
}

func init() {
	// Register commands.
	// system info
	REG("INFO", STATUS_REPLY, Info)

	// KV type
	REG("GET", BULK_REPLY, redlot.Get)
	REG("SET", STATUS_REPLY, redlot.Set)
	REG("DEL", STATUS_REPLY, redlot.Del)
	REG("EXISTS", INT_REPLY, redlot.Exists)
	REG("SETX", STATUS_REPLY, redlot.Setx)
	REG("SETEX", STATUS_REPLY, redlot.Setx) // Alias of SETX
	REG("TTL", INT_REPLY, redlot.Ttl)
	REG("EXPIRE", INT_REPLY, redlot.Expire)

}

func Serve(addr string, options *redlot.Options) {
	// Open LevelDB with options.
	redlot.Open(options)

	// Create sockets listener.
	l, err := net.Listen("tcp4", addr)
	if err != nil {
		log.Fatalf("Listen error: %v\n", err.Error())
	}
	defer l.Close()

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Fatalf("Wait for a connection error: %s\n", err.Error())
		}

		// Count connecion
		info.Lock()
		info.ConnCounter++
		info.Unlock()

		go func(c net.Conn) {
			for {
				req, err := newRequset(c)
				if err == io.EOF {
					break
				}
				if err != nil {
					continue
				}

				info.Lock()
				info.TotalCalls++
				info.Unlock()

				reply := RUN(req.Cmd, req.Args)
				reply.WriteTo(c)
			}

			c.Close()
			info.Lock()
			info.ConnCounter--
			info.Unlock()

		}(conn)

	}
}
<|endoftext|>