<commit_before>package main

type Menu struct {
	disp Display
	g    [2]Gauge
	sel  byte
	cnt  byte
}

func (m *Menu) Display() *Display {
	return &m.disp
}

const (
	SetInRPM = iota
	SetOutRPM
	ShowRPM
	DispOff
	Init
)

func (m *Menu) SetMaxRPM(rpm int) {
	for i := range m.g {
		m.g[i].SetMax(rpm)
	}
}

func (m *Menu) clearDisp() {
	if m.sel == DispOff {
		for i := 0; i < 8; i++ {
			m.disp.Clear(i)
		}
	} else {
		m.disp.WriteString(0, 0, 8, "")
	}
}

func (m *Menu) Select(item byte) {
	m.sel = item
	m.clearDisp()
}

func (m *Menu) Next() {
	m.sel = (m.sel + 1) & 3
	m.clearDisp()
}

func (m *Menu) printDec(row, val int) {
	addr := 4 * row
	m.disp.WriteDec(addr, addr+3, 4, val)
}

func (m *Menu) printStr(row int, s string) {
	addr := 4 * row
	m.disp.WriteString(addr, addr, 4, s)
}

func (m *Menu) clearRow(row int) {
	addr := 4 * row
	m.disp.WriteString(addr, addr, 4, "")
}

func (m *Menu) RTCISR() {
	if m.disp.RTCISR() != 7 {
		return // Wait for the frame to be completed.
	}
	switch {
	case m.sel <= SetOutRPM:
		set := int(m.sel & 1)
		if m.cnt += 8; m.cnt > 80 {
			if rpm := fc.TargetRPM(set); rpm < 0 {
				m.printStr(set, "IErr") // Identification error.
			} else {
				m.printDec(set, rpm)
			}
		} else {
			m.clearRow(set)
		}
		show := set ^ 1
		m.printDec(show, fc.RPM(show))
	case m.sel == ShowRPM:
		m.printDec(0, fc.RPM(0))
		m.printDec(1, fc.RPM(1))
	case m.sel == Init:
		if m.cnt += 8; m.cnt >= 80 {
			m.printStr(0, "Idnt") // Identification.
			m.printDec(1, fc.IdentProgress())
		} else {
			m.clearRow(0)
		}
	}
}

func (m *Menu) HandleEncoder(change int) (n, rpm int) {
	if m.sel > SetOutRPM {
		return -1, 0
	}
	g := &m.g[m.sel]
	g.AddCube(change)
	return int(m.sel), g.Val()
}
<commit_msg>/examples/core51822/ventilation: Better menu blinking.<commit_after>package main

type Menu struct {
	disp Display
	g    [2]Gauge
	sel  byte
	cnt  byte
}

func (m *Menu) Display() *Display {
	return &m.disp
}

const (
	SetInRPM = iota
	SetOutRPM
	ShowRPM
	DispOff
	Init
)

func (m *Menu) SetMaxRPM(rpm int) {
	for i := range m.g {
		m.g[i].SetMax(rpm)
	}
}

func (m *Menu) clearDisp() {
	if m.sel == DispOff {
		for i := 0; i < 8; i++ {
			m.disp.Clear(i)
		}
	} else {
		m.disp.WriteString(0, 0, 8, "")
	}
}

func (m *Menu) Select(item byte) {
	m.sel = item
	m.clearDisp()
}

func (m *Menu) Next() {
	m.sel = (m.sel + 1) & 3
	m.clearDisp()
}

func (m *Menu) printDec(row, val int) {
	addr := 4 * row
	m.disp.WriteDec(addr, addr+3, 4, val)
}

func (m *Menu) printStr(row int, s string) {
	addr := 4 * row
	m.disp.WriteString(addr, addr, 4, s)
}

func (m *Menu) clearRow(row int) {
	addr := 4 * row
	m.disp.WriteString(addr, addr, 4, "")
}

func (m *Menu) RTCISR() {
	if m.disp.RTCISR() != 7 {
		return // Wait for the frame to be completed.
	}
	switch {
	case m.sel <= SetOutRPM:
		set := int(m.sel & 1)
		switch m.cnt += 8; m.cnt {
		case 0:
			if rpm := fc.TargetRPM(set); rpm < 0 {
				m.printStr(set, "IErr") // Identification error.
			} else {
				m.printDec(set, rpm)
			}
		case 200:
			m.clearRow(set)
		}
		show := set ^ 1
		m.printDec(show, fc.RPM(show))
	case m.sel == ShowRPM:
		m.printDec(0, fc.RPM(0))
		m.printDec(1, fc.RPM(1))
	case m.sel == Init:
		switch m.cnt += 8; m.cnt {
		case 0:
			m.printStr(0, "Idnt") // Identification.
		case 160:
			m.clearRow(0)
		}
		m.printDec(1, fc.IdentProgress())
	}
}

func (m *Menu) HandleEncoder(change int) (n, rpm int) {
	if m.sel > SetOutRPM {
		return -1, 0
	}
	g := &m.g[m.sel]
	g.AddCube(change)
	return int(m.sel), g.Val()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"sync"

	"github.com/peterstace/grayt/grayt"
)

const listenAddrEnv = "GRAYT_SCENELIB_LISTEN_ADDR"

func main() {
	listenAddr, ok := os.LookupEnv(listenAddrEnv)
	if !ok {
		log.Fatalf("%s not set", listenAddrEnv)
	}

	s := Server{
		sceneCache: make(map[string]grayt.Scene),
		registry:   make(map[string]func() grayt.Scene),
	}
	/*
		s.Register("cornellbox_classic", classic.CameraFn(), classic.ObjectsFn)
		s.Register("cornellbox_reflections", reflections.CameraFn(), reflections.ObjectsFn)
		s.Register("spheretree", spheretree.CameraFn(), spheretree.ObjectsFn)
		s.Register("splitbox", splitbox.CameraFn(), splitbox.ObjectsFn)
	*/

	http.HandleFunc("/scene", s.HandleScene)
	log.Printf("serving on %v", listenAddr)
	log.Fatal(http.ListenAndServe(listenAddr, nil))
}

type Server struct {
	mu         sync.Mutex
	sceneCache map[string]grayt.Scene

	registry map[string]func() grayt.Scene
}

func (s *Server) Register(
	name string,
	cam grayt.CameraBlueprint,
	objs func() grayt.ObjectList,
) {
	s.registry[name] = func() grayt.Scene {
		return grayt.Scene{
			Camera:  cam,
			Objects: objs(),
		}
	}
}

func (s *Server) HandleScene(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodGet {
		http.Error(w, "method must be GET", http.StatusBadRequest)
		return
	}
	name := req.URL.Query().Get("name")
	if name == "" {
		http.Error(w, "query parameter 'name' not set", http.StatusBadRequest)
		return
	}

	s.mu.Lock()
	scene, ok := s.sceneCache[name]
	if ok {
		s.mu.Unlock()
	} else {
		sceneFn, ok := s.registry[name]
		if !ok {
			http.Error(w, "unknown scene name", http.StatusBadRequest)
			s.mu.Unlock()
			return
		}
		scene = sceneFn()
		s.sceneCache[name] = scene
		s.mu.Unlock()
	}

	if err := json.NewEncoder(w).Encode(scene); err != nil {
		http.Error(w, "couldn't write scene: "+err.Error(), http.StatusInternalServerError)
		return
	}
}
<commit_msg>Allow cornell classic to be served by grayt.scenelib<commit_after>package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"sync"

	"github.com/peterstace/grayt/examples/cornellbox/classic"
	"github.com/peterstace/grayt/protocol"
)

const listenAddrEnv = "GRAYT_SCENELIB_LISTEN_ADDR"

func main() {
	listenAddr, ok := os.LookupEnv(listenAddrEnv)
	if !ok {
		log.Fatalf("%s not set", listenAddrEnv)
	}

	s := Server{
		sceneCache: make(map[string]protocol.Scene),
		registry:   make(map[string]func() protocol.Scene),
	}
	s.Register("cornellbox_classic", classic.Scene)
	/*
		s.Register("cornellbox_reflections", reflections.CameraFn(), reflections.ObjectsFn)
		s.Register("spheretree", spheretree.CameraFn(), spheretree.ObjectsFn)
		s.Register("splitbox", splitbox.CameraFn(), splitbox.ObjectsFn)
	*/

	http.HandleFunc("/scene", s.HandleScene)
	log.Printf("serving on %v", listenAddr)
	log.Fatal(http.ListenAndServe(listenAddr, nil))
}

type Server struct {
	mu         sync.Mutex
	sceneCache map[string]protocol.Scene

	registry map[string]func() protocol.Scene
}

func (s *Server) Register(
	name string,
	sceneFn func() protocol.Scene,
) {
	s.registry[name] = sceneFn
}

func (s *Server) HandleScene(w http.ResponseWriter, req *http.Request) {
	if req.Method != http.MethodGet {
		http.Error(w, "method must be GET", http.StatusBadRequest)
		return
	}
	name := req.URL.Query().Get("name")
	if name == "" {
		http.Error(w, "query parameter 'name' not set", http.StatusBadRequest)
		return
	}

	s.mu.Lock()
	scene, ok := s.sceneCache[name]
	if ok {
		s.mu.Unlock()
	} else {
		sceneFn, ok := s.registry[name]
		if !ok {
			http.Error(w, "unknown scene name", http.StatusBadRequest)
			s.mu.Unlock()
			return
		}
		scene = sceneFn()
		s.sceneCache[name] = scene
		s.mu.Unlock()
	}

	if err := json.NewEncoder(w).Encode(scene); err != nil {
		http.Error(w, "couldn't write scene: "+err.Error(), http.StatusInternalServerError)
		return
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 main

import (
	"bytes"
	"fmt"
	"github.com/golang/glog"
	"github.com/spf13/cobra"
	"io"
	"io/ioutil"
	"k8s.io/kops/cmd/kops/util"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/apis/kops/registry"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup"
	"k8s.io/kops/upup/pkg/fi/utils"
	"k8s.io/kops/upup/pkg/kutil"
	k8sapi "k8s.io/kubernetes/pkg/api"
	"strings"
	"time"
)

type UpdateClusterOptions struct {
	Yes             bool
	Target          string
	Models          string
	OutDir          string
	SSHPublicKey    string
	MaxTaskDuration time.Duration
	CreateKubecfg   bool
}

func (o *UpdateClusterOptions) InitDefaults() {
	o.Yes = false
	o.Target = "direct"
	o.Models = strings.Join(cloudup.CloudupModels, ",")
	o.SSHPublicKey = ""
	o.OutDir = ""
	o.MaxTaskDuration = cloudup.DefaultMaxTaskDuration
	o.CreateKubecfg = true
}

func NewCmdUpdateCluster(f *util.Factory, out io.Writer) *cobra.Command {
	options := &UpdateClusterOptions{}
	options.InitDefaults()

	cmd := &cobra.Command{
		Use:   "cluster",
		Short: "Update cluster",
		Long:  `Updates a k8s cluster.`,
		Run: func(cmd *cobra.Command, args []string) {
			err := rootCommand.ProcessArgs(args)
			if err != nil {
				exitWithError(err)
			}

			clusterName := rootCommand.ClusterName()

			err = RunUpdateCluster(f, clusterName, out, options)
			if err != nil {
				exitWithError(err)
			}
		},
	}

	cmd.Flags().BoolVar(&options.Yes, "yes", options.Yes, "Actually create cloud resources")
	cmd.Flags().StringVar(&options.Target, "target", options.Target, "Target - direct, terraform")
	cmd.Flags().StringVar(&options.Models, "model", options.Models, "Models to apply (separate multiple models with commas)")
	cmd.Flags().StringVar(&options.SSHPublicKey, "ssh-public-key", options.SSHPublicKey, "SSH public key to use (deprecated: use kops create secret instead)")
	cmd.Flags().StringVar(&options.OutDir, "out", options.OutDir, "Path to write any local output")

	return cmd
}

func RunUpdateCluster(f *util.Factory, clusterName string, out io.Writer, c *UpdateClusterOptions) error {
	isDryrun := false
	targetName := c.Target

	// direct requires --yes (others do not, because they don't do anything!)
	if c.Target == cloudup.TargetDirect {
		if !c.Yes {
			isDryrun = true
			targetName = cloudup.TargetDryRun
		}
	}
	if c.Target == cloudup.TargetDryRun {
		isDryrun = true
		targetName = cloudup.TargetDryRun
	}

	if c.OutDir == "" {
		if c.Target == cloudup.TargetTerraform {
			c.OutDir = "out/terraform"
		} else {
			c.OutDir = "out"
		}
	}

	cluster, err := GetCluster(f, clusterName)
	if err != nil {
		return err
	}

	keyStore, err := registry.KeyStore(cluster)
	if err != nil {
		return err
	}

	secretStore, err := registry.SecretStore(cluster)
	if err != nil {
		return err
	}

	clientset, err := f.Clientset()
	if err != nil {
		return err
	}

	if c.SSHPublicKey != "" {
		fmt.Fprintf(out, "--ssh-public-key on update is deprecated - please use `kops create secret --name %s sshpublickey admin -i ~/.ssh/id_rsa.pub` instead\n", cluster.ObjectMeta.Name)

		c.SSHPublicKey = utils.ExpandPath(c.SSHPublicKey)
		authorized, err := ioutil.ReadFile(c.SSHPublicKey)
		if err != nil {
			return fmt.Errorf("error reading SSH key file %q: %v", c.SSHPublicKey, err)
		}
		err = keyStore.AddSSHPublicKey(fi.SecretNameSSHPrimary, authorized)
		if err != nil {
			return fmt.Errorf("error addding SSH public key: %v", err)
		}

		glog.Infof("Using SSH public key: %v\n", c.SSHPublicKey)
	}

	var instanceGroups []*kops.InstanceGroup
	{
		list, err := clientset.InstanceGroups(cluster.ObjectMeta.Name).List(k8sapi.ListOptions{})
		if err != nil {
			return err
		}
		for i := range list.Items {
			instanceGroups = append(instanceGroups, &list.Items[i])
		}
	}

	applyCmd := &cloudup.ApplyClusterCmd{
		Cluster:         cluster,
		Models:          strings.Split(c.Models, ","),
		Clientset:       clientset,
		TargetName:      targetName,
		OutDir:          c.OutDir,
		DryRun:          isDryrun,
		MaxTaskDuration: c.MaxTaskDuration,
		InstanceGroups:  instanceGroups,
	}

	err = applyCmd.Run()
	if err != nil {
		return err
	}

	if isDryrun {
		target := applyCmd.Target.(*fi.DryRunTarget)
		if target.HasChanges() {
			fmt.Fprintf(out, "Must specify --yes to apply changes\n")
		} else {
			fmt.Fprintf(out, "No changes need to be applied\n")
		}
		return nil
	}

	// TODO: Only if not yet set?
	if !isDryrun && c.CreateKubecfg {
		hasKubecfg, err := hasKubecfg(cluster.ObjectMeta.Name)
		if err != nil {
			glog.Warningf("error reading kubecfg: %v", err)
			hasKubecfg = true
		}

		kubecfgCert, err := keyStore.FindCert("kubecfg")
		if err != nil {
			// This is only a convenience; don't error because of it
			glog.Warningf("Ignoring error trying to fetch kubecfg cert - won't export kubecfg: %v", err)
			kubecfgCert = nil
		}
		if kubecfgCert != nil {
			glog.Infof("Exporting kubecfg for cluster")
			x := &kutil.CreateKubecfg{
				ContextName:  cluster.ObjectMeta.Name,
				KeyStore:     keyStore,
				SecretStore:  secretStore,
				KubeMasterIP: cluster.Spec.MasterPublicName,
			}

			err = x.WriteKubecfg()
			if err != nil {
				return err
			}
		} else {
			glog.Infof("kubecfg cert not found; won't export kubecfg")
		}

		if !hasKubecfg {
			sb := new(bytes.Buffer)

			// Assume initial creation
			if c.Target == cloudup.TargetTerraform {
				fmt.Fprintf(sb, "\n")
				fmt.Fprintf(sb, "Terraform output has been placed into %s\n", c.OutDir)
				fmt.Fprintf(sb, "Run these commands to apply the configuration:\n")
				fmt.Fprintf(sb, "   cd %s\n", c.OutDir)
				fmt.Fprintf(sb, "   terraform plan\n")
				fmt.Fprintf(sb, "   terraform apply\n")
				fmt.Fprintf(sb, "\n")
			} else {
				fmt.Fprintf(sb, "\n")
				fmt.Fprintf(sb, "Cluster is starting.  It should be ready in a few minutes.\n")
				fmt.Fprintf(sb, "\n")
			}
			fmt.Fprintf(sb, "Suggestions:\n")
			fmt.Fprintf(sb, " * list nodes: kubectl get nodes --show-labels\n")
			if !usesBastion(instanceGroups) {
				fmt.Fprintf(sb, " * ssh to the master: ssh -i ~/.ssh/id_rsa admin@%s\n", cluster.Spec.MasterPublicName)
			} else {
				bastionPublicName := findBastionPublicName(cluster)
				if bastionPublicName != "" {
					fmt.Fprintf(sb, " * ssh to the bastion: ssh -i ~/.ssh/id_rsa admin@%s\n", bastionPublicName)
				} else {
					fmt.Fprintf(sb, " * to ssh to the bastion, you probably want to configure a bastionPublicName")
				}
			}
			fmt.Fprintf(sb, " * read about installing addons: https://github.com/kubernetes/kops/blob/master/docs/addons.md\n")
			fmt.Fprintf(sb, "\n")

			_, err := out.Write(sb.Bytes())
			if err != nil {
				return fmt.Errorf("error writing to output: %v", err)
			}
		}
	}

	return nil
}

func usesBastion(instanceGroups []*kops.InstanceGroup) bool {
	for _, ig := range instanceGroups {
		if ig.Spec.Role == kops.InstanceGroupRoleBastion {
			return true
		}
	}

	return false
}

func findBastionPublicName(c *kops.Cluster) string {
	topology := c.Spec.Topology
	if topology == nil {
		return ""
	}
	bastion := topology.Bastion
	if bastion == nil {
		return ""
	}
	return bastion.BastionPublicName
}

func hasKubecfg(contextName string) (bool, error) {
	kubectl := &kutil.Kubectl{}

	config, err := kubectl.GetConfig(false)
	if err != nil {
		return false, fmt.Errorf("error getting config from kubectl: %v", err)
	}

	for _, context := range config.Contexts {
		if context.Name == contextName {
			return true, nil
		}
	}
	return false, nil
}
<commit_msg>Clearer 'all done' message on kops update cluster<commit_after>/*
Copyright 2016 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 main

import (
	"bytes"
	"fmt"
	"github.com/golang/glog"
	"github.com/spf13/cobra"
	"io"
	"io/ioutil"
	"k8s.io/kops/cmd/kops/util"
	"k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/apis/kops/registry"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kops/upup/pkg/fi/cloudup"
	"k8s.io/kops/upup/pkg/fi/utils"
	"k8s.io/kops/upup/pkg/kutil"
	k8sapi "k8s.io/kubernetes/pkg/api"
	"strings"
	"time"
)

type UpdateClusterOptions struct {
	Yes             bool
	Target          string
	Models          string
	OutDir          string
	SSHPublicKey    string
	MaxTaskDuration time.Duration
	CreateKubecfg   bool
}

func (o *UpdateClusterOptions) InitDefaults() {
	o.Yes = false
	o.Target = "direct"
	o.Models = strings.Join(cloudup.CloudupModels, ",")
	o.SSHPublicKey = ""
	o.OutDir = ""
	o.MaxTaskDuration = cloudup.DefaultMaxTaskDuration
	o.CreateKubecfg = true
}

func NewCmdUpdateCluster(f *util.Factory, out io.Writer) *cobra.Command {
	options := &UpdateClusterOptions{}
	options.InitDefaults()

	cmd := &cobra.Command{
		Use:   "cluster",
		Short: "Update cluster",
		Long:  `Updates a k8s cluster.`,
		Run: func(cmd *cobra.Command, args []string) {
			err := rootCommand.ProcessArgs(args)
			if err != nil {
				exitWithError(err)
			}

			clusterName := rootCommand.ClusterName()

			err = RunUpdateCluster(f, clusterName, out, options)
			if err != nil {
				exitWithError(err)
			}
		},
	}

	cmd.Flags().BoolVar(&options.Yes, "yes", options.Yes, "Actually create cloud resources")
	cmd.Flags().StringVar(&options.Target, "target", options.Target, "Target - direct, terraform")
	cmd.Flags().StringVar(&options.Models, "model", options.Models, "Models to apply (separate multiple models with commas)")
	cmd.Flags().StringVar(&options.SSHPublicKey, "ssh-public-key", options.SSHPublicKey, "SSH public key to use (deprecated: use kops create secret instead)")
	cmd.Flags().StringVar(&options.OutDir, "out", options.OutDir, "Path to write any local output")

	return cmd
}

func RunUpdateCluster(f *util.Factory, clusterName string, out io.Writer, c *UpdateClusterOptions) error {
	isDryrun := false
	targetName := c.Target

	// direct requires --yes (others do not, because they don't do anything!)
	if c.Target == cloudup.TargetDirect {
		if !c.Yes {
			isDryrun = true
			targetName = cloudup.TargetDryRun
		}
	}
	if c.Target == cloudup.TargetDryRun {
		isDryrun = true
		targetName = cloudup.TargetDryRun
	}

	if c.OutDir == "" {
		if c.Target == cloudup.TargetTerraform {
			c.OutDir = "out/terraform"
		} else {
			c.OutDir = "out"
		}
	}

	cluster, err := GetCluster(f, clusterName)
	if err != nil {
		return err
	}

	keyStore, err := registry.KeyStore(cluster)
	if err != nil {
		return err
	}

	secretStore, err := registry.SecretStore(cluster)
	if err != nil {
		return err
	}

	clientset, err := f.Clientset()
	if err != nil {
		return err
	}

	if c.SSHPublicKey != "" {
		fmt.Fprintf(out, "--ssh-public-key on update is deprecated - please use `kops create secret --name %s sshpublickey admin -i ~/.ssh/id_rsa.pub` instead\n", cluster.ObjectMeta.Name)

		c.SSHPublicKey = utils.ExpandPath(c.SSHPublicKey)
		authorized, err := ioutil.ReadFile(c.SSHPublicKey)
		if err != nil {
			return fmt.Errorf("error reading SSH key file %q: %v", c.SSHPublicKey, err)
		}
		err = keyStore.AddSSHPublicKey(fi.SecretNameSSHPrimary, authorized)
		if err != nil {
			return fmt.Errorf("error addding SSH public key: %v", err)
		}

		glog.Infof("Using SSH public key: %v\n", c.SSHPublicKey)
	}

	var instanceGroups []*kops.InstanceGroup
	{
		list, err := clientset.InstanceGroups(cluster.ObjectMeta.Name).List(k8sapi.ListOptions{})
		if err != nil {
			return err
		}
		for i := range list.Items {
			instanceGroups = append(instanceGroups, &list.Items[i])
		}
	}

	applyCmd := &cloudup.ApplyClusterCmd{
		Cluster:         cluster,
		Models:          strings.Split(c.Models, ","),
		Clientset:       clientset,
		TargetName:      targetName,
		OutDir:          c.OutDir,
		DryRun:          isDryrun,
		MaxTaskDuration: c.MaxTaskDuration,
		InstanceGroups:  instanceGroups,
	}

	err = applyCmd.Run()
	if err != nil {
		return err
	}

	if isDryrun {
		target := applyCmd.Target.(*fi.DryRunTarget)
		if target.HasChanges() {
			fmt.Fprintf(out, "Must specify --yes to apply changes\n")
		} else {
			fmt.Fprintf(out, "No changes need to be applied\n")
		}
		return nil
	}

	firstRun := false

	if !isDryrun && c.CreateKubecfg {
		hasKubecfg, err := hasKubecfg(cluster.ObjectMeta.Name)
		if err != nil {
			glog.Warningf("error reading kubecfg: %v", err)
			hasKubecfg = true
		}
		firstRun = !hasKubecfg

		kubecfgCert, err := keyStore.FindCert("kubecfg")
		if err != nil {
			// This is only a convenience; don't error because of it
			glog.Warningf("Ignoring error trying to fetch kubecfg cert - won't export kubecfg: %v", err)
			kubecfgCert = nil
		}
		if kubecfgCert != nil {
			glog.Infof("Exporting kubecfg for cluster")
			x := &kutil.CreateKubecfg{
				ContextName:  cluster.ObjectMeta.Name,
				KeyStore:     keyStore,
				SecretStore:  secretStore,
				KubeMasterIP: cluster.Spec.MasterPublicName,
			}

			err = x.WriteKubecfg()
			if err != nil {
				return err
			}
		} else {
			glog.Infof("kubecfg cert not found; won't export kubecfg")
		}
	}

	if !isDryrun {
		sb := new(bytes.Buffer)

		if c.Target == cloudup.TargetTerraform {
			fmt.Fprintf(sb, "\n")
			fmt.Fprintf(sb, "Terraform output has been placed into %s\n", c.OutDir)

			if firstRun {
				fmt.Fprintf(sb, "Run these commands to apply the configuration:\n")
				fmt.Fprintf(sb, "   cd %s\n", c.OutDir)
				fmt.Fprintf(sb, "   terraform plan\n")
				fmt.Fprintf(sb, "   terraform apply\n")
				fmt.Fprintf(sb, "\n")
			}
		} else if firstRun {
			fmt.Fprintf(sb, "\n")
			fmt.Fprintf(sb, "Cluster is starting.  It should be ready in a few minutes.\n")
			fmt.Fprintf(sb, "\n")
		} else {
			// TODO: Different message if no changes were needed
			fmt.Fprintf(sb, "\n")
			fmt.Fprintf(sb, "Cluster changes have been applied to the cloud.\n")
			fmt.Fprintf(sb, "\n")
		}

		// More suggestions on first run
		if firstRun {
			fmt.Fprintf(sb, "Suggestions:\n")
			fmt.Fprintf(sb, " * list nodes: kubectl get nodes --show-labels\n")
			if !usesBastion(instanceGroups) {
				fmt.Fprintf(sb, " * ssh to the master: ssh -i ~/.ssh/id_rsa admin@%s\n", cluster.Spec.MasterPublicName)
			} else {
				bastionPublicName := findBastionPublicName(cluster)
				if bastionPublicName != "" {
					fmt.Fprintf(sb, " * ssh to the bastion: ssh -i ~/.ssh/id_rsa admin@%s\n", bastionPublicName)
				} else {
					fmt.Fprintf(sb, " * to ssh to the bastion, you probably want to configure a bastionPublicName")
				}
			}
			fmt.Fprintf(sb, " * read about installing addons: https://github.com/kubernetes/kops/blob/master/docs/addons.md\n")
			fmt.Fprintf(sb, "\n")
		}

		if !firstRun {
			// TODO: Detect if rolling-update is needed
			fmt.Fprintf(sb, "\n")
			fmt.Fprintf(sb, "Changes may require instances to restart: kops rolling-update cluster\n")
			fmt.Fprintf(sb, "\n")
		}

		_, err := out.Write(sb.Bytes())
		if err != nil {
			return fmt.Errorf("error writing to output: %v", err)
		}
	}

	return nil
}

func usesBastion(instanceGroups []*kops.InstanceGroup) bool {
	for _, ig := range instanceGroups {
		if ig.Spec.Role == kops.InstanceGroupRoleBastion {
			return true
		}
	}

	return false
}

func findBastionPublicName(c *kops.Cluster) string {
	topology := c.Spec.Topology
	if topology == nil {
		return ""
	}
	bastion := topology.Bastion
	if bastion == nil {
		return ""
	}
	return bastion.BastionPublicName
}

func hasKubecfg(contextName string) (bool, error) {
	kubectl := &kutil.Kubectl{}

	config, err := kubectl.GetConfig(false)
	if err != nil {
		return false, fmt.Errorf("error getting config from kubectl: %v", err)
	}

	for _, context := range config.Contexts {
		if context.Name == contextName {
			return true, nil
		}
	}
	return false, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"log"
	"os"

	"github.com/jecolon/fn"
	flag "github.com/ogier/pflag"
)

type config struct {
	dir    string
	mv     bool
	out    string
	report bool
	input  []string
	output []string
}

var dir = flag.StringP("dir", "d", ".", "Directory to process. Default is current directory.")
var mv = flag.BoolP("move", "m", false, "Move (rename) instead of copy files.")
var out = flag.StringP("out", "o", ".", "Output directory for copies. Default is --dir flag's value.")
var report = flag.BoolP("report", "r", false, "Just report the filename changes that would occur.")

func main() {
	flag.Parse()
	process(&config{
		dir:    *dir,
		mv:     *mv,
		out:    *out,
		report: *report,
	})
}

func process(conf *config) {
	// Input slice
	var names []string
	// For tests
	if conf.dir == "TEST" {
		names = conf.input
	} else {
		// Open source dir
		d, err := os.Open(conf.dir)
		check(err)
		defer d.Close()

		// Read filenames
		names, err = d.Readdirnames(0)
		check(err)
	}

	// Fix filenames for shell
	conf.output = make([]string, len(names))
	for i, n := range names {
		conf.output[i] = fn.FixForShell(n)
	}

	// Testing ends here... for now
	if conf.dir == "TEST" {
		return
	}

	// If just reporting
	if conf.report {
		fmt.Printf("\nfn fix filenames report for %q directory:\n", conf.dir)
		fmt.Printf("Move (rename):\t%t\n", conf.mv)
		fmt.Printf("Output dir:\t%q\n\n", conf.dir+"/"+conf.out)
		for i, n := range names {
			fmt.Printf("%q -> %q\n", n, conf.output[i])
		}
		fmt.Println()
		return

	}

	// Copy or move files
	err := os.Chdir(conf.dir)
	check(err)
	for i, n := range names {
		// Skip non-fixed names
		if n == conf.output[i] {
			continue
		}
		// Skip directories
		si, err := os.Stat(n)
		check(err)
		if si.IsDir() {
			continue
		}
		// Open files
		src, err := os.Open(n)
		check(err)
		defer src.Close()

		var dst *os.File
		// Output dir
		if conf.mv {
			dst, err = os.Create(conf.output[i])
			check(err)
		} else {
			err = os.MkdirAll(conf.out, 0750)
			if err != nil && !os.IsExist(err) {
				check(err)
			}
			dst, err = os.Create(conf.out + "/" + conf.output[i])
			check(err)
		}

		defer dst.Close()
		// Copy
		_, err = io.Copy(dst, src)
		check(err)
		// Close now; maybe many files to process
		src.Close()
		dst.Close()
		// Move is really a copy + delete source
		if conf.mv {
			err = os.Remove(n)
			check(err)
		}
	}
}

func check(err error) {
	if err != nil {
		log.Fatalln(err)
	}
}
<commit_msg>Fixed directory path handling.<commit_after>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"path"

	"github.com/jecolon/fn"
	flag "github.com/ogier/pflag"
)

type config struct {
	dir    string
	mv     bool
	out    string
	report bool
	input  []string
	output []string
}

var dir = flag.StringP("dir", "d", ".", "Directory to process. Default is current directory.")
var mv = flag.BoolP("move", "m", false, "Move (rename) instead of copy files.")
var out = flag.StringP("out", "o", ".", "Output directory (relative to --dir) to save copies.")
var report = flag.BoolP("report", "r", false, "Just report the filename changes that would occur.")

func main() {
	flag.Parse()
	process(&config{
		dir:    *dir,
		mv:     *mv,
		out:    *out,
		report: *report,
	})
}

func process(conf *config) {
	// Input slice
	var names []string
	// For tests
	if conf.dir == "TEST" {
		names = conf.input
	} else {
		// Prepare path
		conf.dir = path.Clean(conf.dir)
		// Open source dir
		d, err := os.Open(conf.dir)
		check(err)
		defer d.Close()

		// Read filenames
		names, err = d.Readdirnames(0)
		check(err)
	}

	// Fix filenames for shell
	conf.output = make([]string, len(names))
	for i, n := range names {
		conf.output[i] = fn.FixForShell(n)
	}

	// Testing ends here... for now
	if conf.dir == "TEST" {
		return
	}

	// If just reporting
	if conf.report {
		fmt.Printf("\nfn fix filenames report for %q directory:\n", conf.dir)
		fmt.Printf("Move (rename):\t%t\n", conf.mv)
		fmt.Printf("Output dir:\t%q\n\n", path.Join(conf.dir, conf.out))
		for i, n := range names {
			fmt.Printf("%q -> %q\n", n, conf.output[i])
		}
		fmt.Println()
		return

	}

	// Copy or move files
	err := os.Chdir(conf.dir)
	check(err)
	for i, n := range names {
		// Skip non-fixed names
		if n == conf.output[i] {
			continue
		}
		// Skip directories
		si, err := os.Stat(n)
		check(err)
		if si.IsDir() {
			continue
		}
		// Open files
		src, err := os.Open(n)
		check(err)
		defer src.Close()

		var dst *os.File
		// Output dir
		if conf.mv {
			// Move (rename) stays in source directory
			dst, err = os.Create(conf.output[i])
			check(err)
		} else {
			// Copy uses --out directory (which could be same as source)
			err = os.Mkdir(conf.out, 0750)
			// If --out exists, carry on, stop on other errors
			if err != nil && !os.IsExist(err) {
				check(err)
			}
			dst, err = os.Create(path.Join(conf.out, conf.output[i]))
			check(err)
		}

		defer dst.Close()
		// Copy
		_, err = io.Copy(dst, src)
		check(err)
		// Close now; maybe many files to process
		src.Close()
		dst.Close()
		// Move is really a copy + delete source
		if conf.mv {
			err = os.Remove(n)
			check(err)
		}
	}
}

func check(err error) {
	if err != nil {
		log.Fatalln(err)
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"bytes"
	"fmt"
	"io"

	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/dustin/go-humanize"

	key "github.com/ipfs/go-ipfs/blocks/key"
	cmds "github.com/ipfs/go-ipfs/commands"
	bitswap "github.com/ipfs/go-ipfs/exchange/bitswap"
	peer "github.com/ipfs/go-ipfs/p2p/peer"
	u "github.com/ipfs/go-ipfs/util"
)

var BitswapCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline:          "A set of commands to manipulate the bitswap agent",
		ShortDescription: ``,
	},
	Subcommands: map[string]*cmds.Command{
		"wantlist": showWantlistCmd,
		"stat":     bitswapStatCmd,
		"unwant":   unwantCmd,
	},
}

var unwantCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Remove a given block from your wantlist",
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("key", true, true, "key to remove from your wantlist").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if !nd.OnlineMode() {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		bs, ok := nd.Exchange.(*bitswap.Bitswap)
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

		var ks []key.Key
		for _, arg := range req.Arguments() {
			dec := key.B58KeyDecode(arg)
			if dec == "" {
				res.SetError(fmt.Errorf("incorrectly formatted key: %s", arg), cmds.ErrNormal)
				return
			}

			ks = append(ks, dec)
		}

		bs.CancelWants(ks)
	},
}

var showWantlistCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Show blocks currently on the wantlist",
		ShortDescription: `
Print out all blocks currently on the bitswap wantlist for the local peer`,
	},
	Options: []cmds.Option{
		cmds.StringOption("peer", "p", "specify which peer to show wantlist for (default self)"),
	},
	Type: KeyList{},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if !nd.OnlineMode() {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		bs, ok := nd.Exchange.(*bitswap.Bitswap)
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

		pstr, found, err := req.Option("peer").String()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		if found {
			pid, err := peer.IDB58Decode(pstr)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			res.SetOutput(&KeyList{bs.WantlistForPeer(pid)})
		} else {
			res.SetOutput(&KeyList{bs.GetWantlist()})
		}
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: KeyListTextMarshaler,
	},
}

var bitswapStatCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline:          "show some diagnostic information on the bitswap agent",
		ShortDescription: ``,
	},
	Type: bitswap.Stat{},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if !nd.OnlineMode() {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		bs, ok := nd.Exchange.(*bitswap.Bitswap)
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

		st, err := bs.Stat()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		res.SetOutput(st)
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			out, ok := res.Output().(*bitswap.Stat)
			if !ok {
				return nil, u.ErrCast()
			}
			buf := new(bytes.Buffer)
			fmt.Fprintln(buf, "bitswap status")
			fmt.Fprintf(buf, "\tprovides buffer: %d / %d\n", out.ProvideBufLen, bitswap.HasBlockBufferSize)
			fmt.Fprintf(buf, "\tblocks received: %d\n", out.BlocksReceived)
			fmt.Fprintf(buf, "\tdup blocks received: %d\n", out.DupBlksReceived)
			fmt.Fprintf(buf, "\tdup data received: %s\n", humanize.Bytes(out.DupDataReceived))
			fmt.Fprintf(buf, "\twantlist [%d keys]\n", len(out.Wantlist))
			for _, k := range out.Wantlist {
				fmt.Fprintf(buf, "\t\t%s\n", k.B58String())
			}
			fmt.Fprintf(buf, "\tpartners [%d]\n", len(out.Peers))
			for _, p := range out.Peers {
				fmt.Fprintf(buf, "\t\t%s\n", p)
			}
			return buf, nil
		},
	},
}
<commit_msg>Fixed up docs for bitswap<commit_after>package commands

import (
	"bytes"
	"fmt"
	"io"

	"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/dustin/go-humanize"

	key "github.com/ipfs/go-ipfs/blocks/key"
	cmds "github.com/ipfs/go-ipfs/commands"
	bitswap "github.com/ipfs/go-ipfs/exchange/bitswap"
	peer "github.com/ipfs/go-ipfs/p2p/peer"
	u "github.com/ipfs/go-ipfs/util"
)

var BitswapCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline:          "A set of commands to manipulate the bitswap agent.",
		ShortDescription: ``,
	},
	Subcommands: map[string]*cmds.Command{
		"wantlist": showWantlistCmd,
		"stat":     bitswapStatCmd,
		"unwant":   unwantCmd,
	},
}

var unwantCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Remove a given block from your wantlist.",
	},
	Arguments: []cmds.Argument{
		cmds.StringArg("key", true, true, "Key to remove from your wantlist.").EnableStdin(),
	},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if !nd.OnlineMode() {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		bs, ok := nd.Exchange.(*bitswap.Bitswap)
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

		var ks []key.Key
		for _, arg := range req.Arguments() {
			dec := key.B58KeyDecode(arg)
			if dec == "" {
				res.SetError(fmt.Errorf("Incorrectly formatted key: %s", arg), cmds.ErrNormal)
				return
			}

			ks = append(ks, dec)
		}

		bs.CancelWants(ks)
	},
}

var showWantlistCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Show blocks currently on the wantlist.",
		ShortDescription: `
Print out all blocks currently on the bitswap wantlist for the local peer.`,
	},
	Options: []cmds.Option{
		cmds.StringOption("peer", "p", "Specify which peer to show wantlist for. Default: self."),
	},
	Type: KeyList{},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if !nd.OnlineMode() {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		bs, ok := nd.Exchange.(*bitswap.Bitswap)
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

		pstr, found, err := req.Option("peer").String()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		if found {
			pid, err := peer.IDB58Decode(pstr)
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			res.SetOutput(&KeyList{bs.WantlistForPeer(pid)})
		} else {
			res.SetOutput(&KeyList{bs.GetWantlist()})
		}
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: KeyListTextMarshaler,
	},
}

var bitswapStatCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline:          "Show some diagnostic information on the bitswap agent.",
		ShortDescription: ``,
	},
	Type: bitswap.Stat{},
	Run: func(req cmds.Request, res cmds.Response) {
		nd, err := req.InvocContext().GetNode()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		if !nd.OnlineMode() {
			res.SetError(errNotOnline, cmds.ErrClient)
			return
		}

		bs, ok := nd.Exchange.(*bitswap.Bitswap)
		if !ok {
			res.SetError(u.ErrCast(), cmds.ErrNormal)
			return
		}

		st, err := bs.Stat()
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}

		res.SetOutput(st)
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			out, ok := res.Output().(*bitswap.Stat)
			if !ok {
				return nil, u.ErrCast()
			}
			buf := new(bytes.Buffer)
			fmt.Fprintln(buf, "bitswap status")
			fmt.Fprintf(buf, "\tprovides buffer: %d / %d\n", out.ProvideBufLen, bitswap.HasBlockBufferSize)
			fmt.Fprintf(buf, "\tblocks received: %d\n", out.BlocksReceived)
			fmt.Fprintf(buf, "\tdup blocks received: %d\n", out.DupBlksReceived)
			fmt.Fprintf(buf, "\tdup data received: %s\n", humanize.Bytes(out.DupDataReceived))
			fmt.Fprintf(buf, "\twantlist [%d keys]\n", len(out.Wantlist))
			for _, k := range out.Wantlist {
				fmt.Fprintf(buf, "\t\t%s\n", k.B58String())
			}
			fmt.Fprintf(buf, "\tpartners [%d]\n", len(out.Peers))
			for _, p := range out.Peers {
				fmt.Fprintf(buf, "\t\t%s\n", p)
			}
			return buf, nil
		},
	},
}
<|endoftext|>


<commit_before>// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package discovery

import (
	"fmt"

	pb "github.com/TheThingsNetwork/api/discovery"
	"github.com/TheThingsNetwork/go-account-lib/rights"
	"github.com/TheThingsNetwork/go-utils/grpc/ttnctx"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/gogo/protobuf/types"
	"golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711"
	"google.golang.org/grpc"
)

type discoveryServer struct {
	discovery *discovery
}

func errPermissionDeniedf(format string, args ...interface{}) error {
	return errors.NewErrPermissionDenied(fmt.Sprintf("Discovery:"+format, args...))
}

func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.MetadataRequest) error {
	claims, err := d.discovery.ValidateTTNAuthContext(ctx)
	if err != nil {
		return err
	}

	appEUI := in.Metadata.GetAppEUI()
	appID := in.Metadata.GetAppID()
	prefix := in.Metadata.GetDevAddrPrefix()
	gatewayID := in.Metadata.GetGatewayID()

	if appEUI == nil && appID == "" && prefix == nil && gatewayID == "" {
		return errPermissionDeniedf("Unknown Metadata type")
	}

	// AppEUI and AppID can only be added to Handlers
	if (appEUI != nil || appID != "") && in.ServiceName != "handler" {
		return errPermissionDeniedf("Announcement service type should be \"handler\"")
	}

	// DevAddrPrefix can only be added to Brokers
	if prefix != nil && in.ServiceName != "broker" {
		return errPermissionDeniedf("Announcement service type should be \"broker\"")
	}

	// GatewayID can only be added to Routers
	if gatewayID != "" && in.ServiceName != "router" {
		return errPermissionDeniedf("Announcement service type should be \"router\"")
	}

	// DevAddrPrefix and AppEUI are network level changes
	if prefix != nil || appEUI != nil {

		// If not in develop mode
		if d.discovery.Component.Identity.ID != "dev" {

			// We require a signature from a master auth server
			if !d.discovery.IsMasterAuthServer(claims.Issuer) {
				return errPermissionDeniedf("Token issuer \"%s\" is not allowed to make changes to the network settings", claims.Issuer)
			}

			// TODO: Check if claims allow DevAddrPrefix to be announced

			// AppEUI can not be announced yet
			if appEUI != nil {
				return errPermissionDeniedf("Can not announce AppEUIs at this time")
			}
		}

		// Can only be announced to "self"
		if claims.Type != in.ServiceName {
			return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName)
		}
		if claims.Subject != in.ID {
			return errPermissionDeniedf("Token subject %s does not correspond with announcement id %s", claims.Subject, in.ID)
		}
	}

	// Check claims for AppID
	if appID != "" {
		if !claims.AppRight(appID, rights.AppDelete) {
			return errPermissionDeniedf(`No "%s" rights to Application "%s"`, rights.AppDelete, appID)
		}
	}

	// Check claims for GatewayID
	if gatewayID != "" {
		if !claims.GatewayRight(gatewayID, rights.GatewayDelete) || (claims.Type == "gateway" && claims.Subject == gatewayID) {
			return errPermissionDeniedf(`No "%s" rights to Gateway "%s"`, rights.GatewayDelete, gatewayID)
		}
	}
	return nil
}

func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*types.Empty, error) {
	claims, err := d.discovery.ValidateTTNAuthContext(ctx)
	if err != nil {
		return nil, err
	}

	// If not in development mode
	if d.discovery.Component.Identity.ID != "dev" {
		if !d.discovery.IsMasterAuthServer(claims.Issuer) {
			return nil, errPermissionDeniedf("Token issuer %s is not allowed to make changes to the network settings", claims.Issuer)
		}

		// Can't announce development components
		if claims.Subject == "dev" {
			return nil, errPermissionDeniedf("Can't announce development components to production networks")
		}
	}

	if claims.Subject != announcement.ID {
		return nil, errPermissionDeniedf("Token subject %s does not correspond with announcement ID %s", claims.Subject, announcement.ID)
	}
	if claims.Type != announcement.ServiceName {
		return nil, errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, announcement.ServiceName)
	}
	announcementCopy := *announcement
	announcement.Metadata = []*pb.Metadata{} // This will be taken from existing announcement
	err = d.discovery.Announce(&announcementCopy)
	if err != nil {
		return nil, err
	}
	return &types.Empty{}, nil
}

func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*types.Empty, error) {
	err := d.checkMetadataEditRights(ctx, in)
	if err != nil {
		return nil, err
	}
	err = d.discovery.AddMetadata(in.ServiceName, in.ID, in.Metadata)
	if err != nil {
		return nil, err
	}
	return &types.Empty{}, nil
}

func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*types.Empty, error) {
	err := d.checkMetadataEditRights(ctx, in)
	if err != nil {
		return nil, err
	}
	err = d.discovery.DeleteMetadata(in.ServiceName, in.ID, in.Metadata)
	if err != nil {
		return nil, err
	}
	return &types.Empty{}, nil
}

func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetServiceRequest) (*pb.AnnouncementsResponse, error) {
	limit, offset, err := ttnctx.LimitAndOffsetFromIncomingContext(ctx)
	if err != nil {
		return nil, err
	}
	services, err := d.discovery.GetAll(req.ServiceName, limit, offset)
	if err != nil {
		return nil, err
	}
	return &pb.AnnouncementsResponse{
		Services: services,
	}, nil
}

func (d *discoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.Announcement, error) {
	service, err := d.discovery.Get(req.ServiceName, req.ID)
	if err != nil {
		return nil, err
	}
	return service, nil
}

func (d *discoveryServer) GetByAppID(ctx context.Context, req *pb.GetByAppIDRequest) (*pb.Announcement, error) {
	service, err := d.discovery.GetByAppID(req.AppID)
	if err != nil {
		return nil, err
	}
	return service, nil
}

func (d *discoveryServer) GetByGatewayID(ctx context.Context, req *pb.GetByGatewayIDRequest) (*pb.Announcement, error) {
	service, err := d.discovery.GetByGatewayID(req.GatewayID)
	if err != nil {
		return nil, err
	}
	return service, nil
}

func (d *discoveryServer) GetByAppEUI(ctx context.Context, req *pb.GetByAppEUIRequest) (*pb.Announcement, error) {
	if req.AppEUI == nil {
		return nil, errors.NewErrInvalidArgument("AppEUI", "empty")
	}
	service, err := d.discovery.GetByAppEUI(*req.AppEUI)
	if err != nil {
		return nil, err
	}
	return service, nil
}

// RegisterRPC registers the local discovery with a gRPC server
func (d *discovery) RegisterRPC(s *grpc.Server) {
	server := &discoveryServer{d}
	pb.RegisterDiscoveryServer(s, server)
}
<commit_msg>Fix rights checking for gateway-router mapping in Discovery<commit_after>// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package discovery

import (
	"fmt"

	pb "github.com/TheThingsNetwork/api/discovery"
	"github.com/TheThingsNetwork/go-account-lib/rights"
	"github.com/TheThingsNetwork/go-utils/grpc/ttnctx"
	"github.com/TheThingsNetwork/ttn/utils/errors"
	"github.com/gogo/protobuf/types"
	"golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711"
	"google.golang.org/grpc"
)

type discoveryServer struct {
	discovery *discovery
}

func errPermissionDeniedf(format string, args ...interface{}) error {
	return errors.NewErrPermissionDenied(fmt.Sprintf("Discovery:"+format, args...))
}

func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.MetadataRequest) error {
	claims, err := d.discovery.ValidateTTNAuthContext(ctx)
	if err != nil {
		return err
	}

	appEUI := in.Metadata.GetAppEUI()
	appID := in.Metadata.GetAppID()
	prefix := in.Metadata.GetDevAddrPrefix()
	gatewayID := in.Metadata.GetGatewayID()

	if appEUI == nil && appID == "" && prefix == nil && gatewayID == "" {
		return errPermissionDeniedf("Unknown Metadata type")
	}

	// AppEUI and AppID can only be added to Handlers
	if (appEUI != nil || appID != "") && in.ServiceName != "handler" {
		return errPermissionDeniedf("Announcement service type should be \"handler\"")
	}

	// DevAddrPrefix can only be added to Brokers
	if prefix != nil && in.ServiceName != "broker" {
		return errPermissionDeniedf("Announcement service type should be \"broker\"")
	}

	// GatewayID can only be added to Routers
	if gatewayID != "" && in.ServiceName != "router" {
		return errPermissionDeniedf("Announcement service type should be \"router\"")
	}

	// DevAddrPrefix and AppEUI are network level changes
	if prefix != nil || appEUI != nil {

		// If not in develop mode
		if d.discovery.Component.Identity.ID != "dev" {

			// We require a signature from a master auth server
			if !d.discovery.IsMasterAuthServer(claims.Issuer) {
				return errPermissionDeniedf("Token issuer \"%s\" is not allowed to make changes to the network settings", claims.Issuer)
			}

			// TODO: Check if claims allow DevAddrPrefix to be announced

			// AppEUI can not be announced yet
			if appEUI != nil {
				return errPermissionDeniedf("Can not announce AppEUIs at this time")
			}
		}

		// Can only be announced to "self"
		if claims.Type != in.ServiceName {
			return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName)
		}
		if claims.Subject != in.ID {
			return errPermissionDeniedf("Token subject %s does not correspond with announcement id %s", claims.Subject, in.ID)
		}
	}

	// Check claims for AppID
	if appID != "" {
		if !claims.AppRight(appID, rights.AppDelete) {
			return errPermissionDeniedf(`No "%s" rights to Application "%s"`, rights.AppDelete, appID)
		}
	}

	// Check claims for GatewayID
	if gatewayID != "" {
		if !claims.GatewayRight(gatewayID, rights.GatewayDelete) && !(claims.Type == "gateway" && claims.Subject == gatewayID) {
			return errPermissionDeniedf(`No "%s" rights to Gateway "%s"`, rights.GatewayDelete, gatewayID)
		}
	}
	return nil
}

func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*types.Empty, error) {
	claims, err := d.discovery.ValidateTTNAuthContext(ctx)
	if err != nil {
		return nil, err
	}

	// If not in development mode
	if d.discovery.Component.Identity.ID != "dev" {
		if !d.discovery.IsMasterAuthServer(claims.Issuer) {
			return nil, errPermissionDeniedf("Token issuer %s is not allowed to make changes to the network settings", claims.Issuer)
		}

		// Can't announce development components
		if claims.Subject == "dev" {
			return nil, errPermissionDeniedf("Can't announce development components to production networks")
		}
	}

	if claims.Subject != announcement.ID {
		return nil, errPermissionDeniedf("Token subject %s does not correspond with announcement ID %s", claims.Subject, announcement.ID)
	}
	if claims.Type != announcement.ServiceName {
		return nil, errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, announcement.ServiceName)
	}
	announcementCopy := *announcement
	announcement.Metadata = []*pb.Metadata{} // This will be taken from existing announcement
	err = d.discovery.Announce(&announcementCopy)
	if err != nil {
		return nil, err
	}
	return &types.Empty{}, nil
}

func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*types.Empty, error) {
	err := d.checkMetadataEditRights(ctx, in)
	if err != nil {
		return nil, err
	}
	err = d.discovery.AddMetadata(in.ServiceName, in.ID, in.Metadata)
	if err != nil {
		return nil, err
	}
	return &types.Empty{}, nil
}

func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*types.Empty, error) {
	err := d.checkMetadataEditRights(ctx, in)
	if err != nil {
		return nil, err
	}
	err = d.discovery.DeleteMetadata(in.ServiceName, in.ID, in.Metadata)
	if err != nil {
		return nil, err
	}
	return &types.Empty{}, nil
}

func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetServiceRequest) (*pb.AnnouncementsResponse, error) {
	limit, offset, err := ttnctx.LimitAndOffsetFromIncomingContext(ctx)
	if err != nil {
		return nil, err
	}
	services, err := d.discovery.GetAll(req.ServiceName, limit, offset)
	if err != nil {
		return nil, err
	}
	return &pb.AnnouncementsResponse{
		Services: services,
	}, nil
}

func (d *discoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.Announcement, error) {
	service, err := d.discovery.Get(req.ServiceName, req.ID)
	if err != nil {
		return nil, err
	}
	return service, nil
}

func (d *discoveryServer) GetByAppID(ctx context.Context, req *pb.GetByAppIDRequest) (*pb.Announcement, error) {
	service, err := d.discovery.GetByAppID(req.AppID)
	if err != nil {
		return nil, err
	}
	return service, nil
}

func (d *discoveryServer) GetByGatewayID(ctx context.Context, req *pb.GetByGatewayIDRequest) (*pb.Announcement, error) {
	service, err := d.discovery.GetByGatewayID(req.GatewayID)
	if err != nil {
		return nil, err
	}
	return service, nil
}

func (d *discoveryServer) GetByAppEUI(ctx context.Context, req *pb.GetByAppEUIRequest) (*pb.Announcement, error) {
	if req.AppEUI == nil {
		return nil, errors.NewErrInvalidArgument("AppEUI", "empty")
	}
	service, err := d.discovery.GetByAppEUI(*req.AppEUI)
	if err != nil {
		return nil, err
	}
	return service, nil
}

// RegisterRPC registers the local discovery with a gRPC server
func (d *discovery) RegisterRPC(s *grpc.Server) {
	server := &discoveryServer{d}
	pb.RegisterDiscoveryServer(s, server)
}
<|endoftext|>


<commit_before>package fs

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

	ds "github.com/jbenet/go-datastore"
	query "github.com/jbenet/go-datastore/query"
)

var ObjectKeySuffix = ".dsobject"

// Datastore uses a standard Go map for internal storage.
type Datastore struct {
	path string
}

// NewDatastore returns a new fs Datastore at given `path`
func NewDatastore(path string) (ds.Datastore, error) {
	if !isDir(path) {
		return nil, fmt.Errorf("Failed to find directory at: %v (file? perms?)", path)
	}

	return &Datastore{path: path}, nil
}

// KeyFilename returns the filename associated with `key`
func (d *Datastore) KeyFilename(key ds.Key) string {
	return filepath.Join(d.path, key.String(), ObjectKeySuffix)
}

// Put stores the given value.
func (d *Datastore) Put(key ds.Key, value interface{}) (err error) {

	// TODO: maybe use io.Readers/Writers?
	// r, err := dsio.CastAsReader(value)
	// if err != nil {
	// 	return err
	// }

	val, ok := value.([]byte)
	if !ok {
		return ds.ErrInvalidType
	}

	fn := d.KeyFilename(key)

	// mkdirall above.
	err = os.MkdirAll(filepath.Dir(fn), 0755)
	if err != nil {
		return err
	}

	return ioutil.WriteFile(fn, val, 0666)
}

// Get returns the value for given key
func (d *Datastore) Get(key ds.Key) (value interface{}, err error) {
	fn := d.KeyFilename(key)
	if !isFile(fn) {
		return nil, ds.ErrNotFound
	}

	return ioutil.ReadFile(fn)
}

// Has returns whether the datastore has a value for a given key
func (d *Datastore) Has(key ds.Key) (exists bool, err error) {
	return ds.GetBackedHas(d, key)
}

// Delete removes the value for given key
func (d *Datastore) Delete(key ds.Key) (err error) {
	fn := d.KeyFilename(key)
	if !isFile(fn) {
		return ds.ErrNotFound
	}

	return os.Remove(fn)
}

// Query implements Datastore.Query
func (d *Datastore) Query(q query.Query) (query.Results, error) {

	results := make(chan query.Result)

	walkFn := func(path string, info os.FileInfo, err error) error {
		// remove ds path prefix
		if strings.HasPrefix(path, d.path) {
			path = path[len(d.path):]
		}

		if !info.IsDir() {
			if strings.HasSuffix(path, ObjectKeySuffix) {
				path = path[:len(path)-len(ObjectKeySuffix)]
			}
			key := ds.NewKey(path)
			entry := query.Entry{Key: key.String(), Value: query.NotFetched}
			results <- query.Result{Entry: entry}
		}
		return nil
	}

	go func() {
		filepath.Walk(d.path, walkFn)
		close(results)
	}()
	r := query.ResultsWithChan(q, results)
	r = query.NaiveQueryApply(q, r)
	return r, nil
}

// isDir returns whether given path is a directory
func isDir(path string) bool {
	finfo, err := os.Stat(path)
	if err != nil {
		return false
	}

	return finfo.IsDir()
}

// isFile returns whether given path is a file
func isFile(path string) bool {
	finfo, err := os.Stat(path)
	if err != nil {
		return false
	}

	return !finfo.IsDir()
}
<commit_msg>Fix copy-paste in fs doc<commit_after>package fs

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

	ds "github.com/jbenet/go-datastore"
	query "github.com/jbenet/go-datastore/query"
)

var ObjectKeySuffix = ".dsobject"

// Datastore uses a uses a file per key to store values.
type Datastore struct {
	path string
}

// NewDatastore returns a new fs Datastore at given `path`
func NewDatastore(path string) (ds.Datastore, error) {
	if !isDir(path) {
		return nil, fmt.Errorf("Failed to find directory at: %v (file? perms?)", path)
	}

	return &Datastore{path: path}, nil
}

// KeyFilename returns the filename associated with `key`
func (d *Datastore) KeyFilename(key ds.Key) string {
	return filepath.Join(d.path, key.String(), ObjectKeySuffix)
}

// Put stores the given value.
func (d *Datastore) Put(key ds.Key, value interface{}) (err error) {

	// TODO: maybe use io.Readers/Writers?
	// r, err := dsio.CastAsReader(value)
	// if err != nil {
	// 	return err
	// }

	val, ok := value.([]byte)
	if !ok {
		return ds.ErrInvalidType
	}

	fn := d.KeyFilename(key)

	// mkdirall above.
	err = os.MkdirAll(filepath.Dir(fn), 0755)
	if err != nil {
		return err
	}

	return ioutil.WriteFile(fn, val, 0666)
}

// Get returns the value for given key
func (d *Datastore) Get(key ds.Key) (value interface{}, err error) {
	fn := d.KeyFilename(key)
	if !isFile(fn) {
		return nil, ds.ErrNotFound
	}

	return ioutil.ReadFile(fn)
}

// Has returns whether the datastore has a value for a given key
func (d *Datastore) Has(key ds.Key) (exists bool, err error) {
	return ds.GetBackedHas(d, key)
}

// Delete removes the value for given key
func (d *Datastore) Delete(key ds.Key) (err error) {
	fn := d.KeyFilename(key)
	if !isFile(fn) {
		return ds.ErrNotFound
	}

	return os.Remove(fn)
}

// Query implements Datastore.Query
func (d *Datastore) Query(q query.Query) (query.Results, error) {

	results := make(chan query.Result)

	walkFn := func(path string, info os.FileInfo, err error) error {
		// remove ds path prefix
		if strings.HasPrefix(path, d.path) {
			path = path[len(d.path):]
		}

		if !info.IsDir() {
			if strings.HasSuffix(path, ObjectKeySuffix) {
				path = path[:len(path)-len(ObjectKeySuffix)]
			}
			key := ds.NewKey(path)
			entry := query.Entry{Key: key.String(), Value: query.NotFetched}
			results <- query.Result{Entry: entry}
		}
		return nil
	}

	go func() {
		filepath.Walk(d.path, walkFn)
		close(results)
	}()
	r := query.ResultsWithChan(q, results)
	r = query.NaiveQueryApply(q, r)
	return r, nil
}

// isDir returns whether given path is a directory
func isDir(path string) bool {
	finfo, err := os.Stat(path)
	if err != nil {
		return false
	}

	return finfo.IsDir()
}

// isFile returns whether given path is a file
func isFile(path string) bool {
	finfo, err := os.Stat(path)
	if err != nil {
		return false
	}

	return !finfo.IsDir()
}
<|endoftext|>


<commit_before>package content

import "net/http"

// Item should only be embedded into content type structs.
type Item struct {
	ID        int    `json:"id"`
	Slug      string `json:"slug"`
	Timestamp int64  `json:"timestamp"`
	Updated   int64  `json:"updated"`
}

// Time partially implements the Sortable interface
func (i Item) Time() int64 {
	return i.Timestamp
}

// Touch partially implements the Sortable interface
func (i Item) Touch() int64 {
	return i.Updated
}

// ItemID partially implements the Sortable interface
func (i Item) ItemID() int {
	return i.ID
}

// SetSlug sets the item's slug for its URL
func (i *Item) SetSlug(slug string) {
	i.Slug = slug
}

// SetItemID sets the Item's ID field
func (i *Item) SetItemID(id int) {
	i.ID = id
}

// BeforeSave is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeSave(req *http.Request) error {
	return nil
}

// AfterSave is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterSave(req *http.Request) error {
	return nil
}

// BeforeDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeDelete(req *http.Request) error {
	return nil
}

// AfterDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterDelete(req *http.Request) error {
	return nil
}

// BeforeApprove is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeApprove(req *http.Request) error {
	return nil
}

// AfterApprove is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterApprove(req *http.Request) error {
	return nil
}

// BeforeReject is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeReject(req *http.Request) error {
	return nil
}

// AfterReject is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterReject(req *http.Request) error {
	return nil
}

// Sluggable makes a struct locatable by URL with it's own path
// As an Item implementing Sluggable, slugs may overlap. If this is an issue,
// make your content struct (or one which imbeds Item) implement Sluggable
// and it will override the slug created by Item's SetSlug with your struct's
type Sluggable interface {
	SetSlug(string)
}

// Identifiable enables a struct to have its ID set. Typically this is done
// to set an ID to -1 indicating it is new for DB inserts, since by default
// a newly initialized struct would have an ID of 0, the int zero-value, and
// BoltDB's starting key per bucket is 0, thus overwriting the first record.
type Identifiable interface {
	SetItemID(int)
}

// Hookable provides our user with an easy way to intercept or add functionality
// to the different lifecycles/events a struct may encounter. Item implements
// Hookable with no-ops so our user can override only whichever ones necessary.
type Hookable interface {
	BeforeSave(req *http.Request) error
	AfterSave(req *http.Request) error

	BeforeDelete(req *http.Request) error
	AfterDelete(req *http.Request) error

	BeforeApprove(req *http.Request) error
	AfterApprove(req *http.Request) error

	BeforeReject(req *http.Request) error
	AfterReject(req *http.Request) error
}
<commit_msg>rearranging code in item.go<commit_after>package content

import "net/http"

// Sluggable makes a struct locatable by URL with it's own path
// As an Item implementing Sluggable, slugs may overlap. If this is an issue,
// make your content struct (or one which imbeds Item) implement Sluggable
// and it will override the slug created by Item's SetSlug with your struct's
type Sluggable interface {
	SetSlug(string)
}

// Identifiable enables a struct to have its ID set. Typically this is done
// to set an ID to -1 indicating it is new for DB inserts, since by default
// a newly initialized struct would have an ID of 0, the int zero-value, and
// BoltDB's starting key per bucket is 0, thus overwriting the first record.
type Identifiable interface {
	SetItemID(int)
}

// Hookable provides our user with an easy way to intercept or add functionality
// to the different lifecycles/events a struct may encounter. Item implements
// Hookable with no-ops so our user can override only whichever ones necessary.
type Hookable interface {
	BeforeSave(req *http.Request) error
	AfterSave(req *http.Request) error

	BeforeDelete(req *http.Request) error
	AfterDelete(req *http.Request) error

	BeforeApprove(req *http.Request) error
	AfterApprove(req *http.Request) error

	BeforeReject(req *http.Request) error
	AfterReject(req *http.Request) error
}

// Item should only be embedded into content type structs.
type Item struct {
	ID        int    `json:"id"`
	Slug      string `json:"slug"`
	Timestamp int64  `json:"timestamp"`
	Updated   int64  `json:"updated"`
}

// Time partially implements the Sortable interface
func (i Item) Time() int64 {
	return i.Timestamp
}

// Touch partially implements the Sortable interface
func (i Item) Touch() int64 {
	return i.Updated
}

// ItemID partially implements the Sortable interface
func (i Item) ItemID() int {
	return i.ID
}

// SetSlug sets the item's slug for its URL
func (i *Item) SetSlug(slug string) {
	i.Slug = slug
}

// SetItemID sets the Item's ID field
func (i *Item) SetItemID(id int) {
	i.ID = id
}

// BeforeSave is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeSave(req *http.Request) error {
	return nil
}

// AfterSave is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterSave(req *http.Request) error {
	return nil
}

// BeforeDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeDelete(req *http.Request) error {
	return nil
}

// AfterDelete is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterDelete(req *http.Request) error {
	return nil
}

// BeforeApprove is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeApprove(req *http.Request) error {
	return nil
}

// AfterApprove is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterApprove(req *http.Request) error {
	return nil
}

// BeforeReject is a no-op to ensure structs which embed Item implement Hookable
func (i Item) BeforeReject(req *http.Request) error {
	return nil
}

// AfterReject is a no-op to ensure structs which embed Item implement Hookable
func (i Item) AfterReject(req *http.Request) error {
	return nil
}<|endoftext|>


<commit_before>package genericsite

import (
	"time"

	"github.com/drbawb/mustache"
	"github.com/hoisie/web"
	. "github.com/xyproto/onthefly"
	"github.com/xyproto/permissions"
	"github.com/xyproto/webhandle"
)

type ContentPage struct {
	GeneratedCSSurl          string
	ExtraCSSurls             []string
	JqueryJSurl              string
	Faviconurl               string
	BgImageURL               string
	StretchBackground        bool
	Title                    string
	Subtitle                 string
	ContentTitle             string
	ContentHTML              string
	HeaderJS                 string
	ContentJS                string
	SearchButtonText         string
	SearchURL                string
	FooterText               string
	BackgroundTextureURL     string
	DarkBackgroundTextureURL string
	FooterTextColor          string
	FooterColor              string
	UserState                *permissions.UserState
	RoundedLook              bool
	Url                      string
	ColorScheme              *ColorScheme
	SearchBox                bool
	GoogleFonts              []string
	CustomSansSerif          string
	CustomSerif              string
}

// Content page generator
type CPgen (func(userState *permissions.UserState) *ContentPage)

// A collection of ContentPages
type PageCollection []ContentPage

// Every input from the user must be intitially stored in a UserInput variable, not in a string!
// This is just to be aware of which data one should be careful with, and to keep it clean.
type UserInput string

type ColorScheme struct {
	Darkgray           string
	Nicecolor          string
	Menu_link          string
	Menu_hover         string
	Menu_active        string
	Default_background string
	TitleText          string
}

type BaseCP func(state *permissions.UserState) *ContentPage

type TemplateValueGeneratorFactory func(*permissions.UserState) webhandle.TemplateValueGenerator

// The default settings
// Do not publish this page directly, but use it as a basis for the other pages
func DefaultCP(userState *permissions.UserState) *ContentPage {
	var cp ContentPage
	cp.GeneratedCSSurl = "/css/style.css"
	cp.ExtraCSSurls = []string{"/css/menu.css"}
	// TODO: fallback to local jquery.min.js, google how
	cp.JqueryJSurl = "//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js" // "/js/jquery-2.0.0.js"
	cp.Faviconurl = "/img/favicon.ico"
	cp.ContentTitle = "NOP"
	cp.ContentHTML = "NOP NOP NOP"
	cp.ContentJS = ""
	cp.HeaderJS = ""
	cp.SearchButtonText = "Search"
	cp.SearchURL = "/search"
	cp.SearchBox = true

	// http://wptheming.wpengine.netdna-cdn.com/wp-content/uploads/2010/04/gray-texture.jpg
	// TODO: Draw these two backgroundimages with a canvas instead
	cp.BackgroundTextureURL = "" // "/img/gray.jpg"
	// http://turbo.designwoop.com/uploads/2012/03/16_free_subtle_textures_subtle_dark_vertical.jpg
	cp.DarkBackgroundTextureURL = "/img/darkgray.jpg"

	cp.FooterColor = "black"
	cp.FooterTextColor = "#303040"

	cp.FooterText = "NOP"

	cp.UserState = userState
	cp.RoundedLook = false

	cp.Url = "/" // To be filled in when published

	// The default color scheme
	var cs ColorScheme
	cs.Darkgray = "#202020"
	cs.Nicecolor = "#5080D0"   // nice blue
	cs.Menu_link = "#c0c0c0"   // light gray
	cs.Menu_hover = "#efefe0"  // light gray, somewhat yellow
	cs.Menu_active = "#ffffff" // white
	cs.Default_background = "#000030"
	cs.TitleText = "#303030"

	cp.ColorScheme = &cs

	cp.GoogleFonts = []string{"Armata", "IM Fell English SC"}
	cp.CustomSansSerif = "" // Use the default sans serif
	cp.CustomSerif = "IM Fell English SC"

	return &cp
}

func genericPageBuilder(cp *ContentPage) *Page {
	// TODO: Record the time from one step out, because content may be generated and inserted into this generated conten
	startTime := time.Now()

	page := NewHTML5Page(cp.Title + " " + cp.Subtitle)

	page.LinkToCSS(cp.GeneratedCSSurl)
	for _, cssurl := range cp.ExtraCSSurls {
		page.LinkToCSS(cssurl)
	}
	page.LinkToJS(cp.JqueryJSurl)
	page.LinkToFavicon(cp.Faviconurl)

	AddHeader(page, cp.HeaderJS)
	AddGoogleFonts(page, cp.GoogleFonts)
	AddBodyStyle(page, cp.BgImageURL, cp.StretchBackground)
	AddTopBox(page, cp.Title, cp.Subtitle, cp.SearchURL, cp.SearchButtonText, cp.BackgroundTextureURL, cp.RoundedLook, cp.ColorScheme, cp.SearchBox)

	// TODO: Move the menubox into the TopBox

	AddMenuBox(page, cp.DarkBackgroundTextureURL, cp.CustomSansSerif)

	AddContent(page, cp.ContentTitle, cp.ContentHTML+DocumentReadyJS(cp.ContentJS))

	elapsed := time.Since(startTime)
	AddFooter(page, cp.FooterText, cp.FooterTextColor, cp.FooterColor, elapsed)

	return page
}

// Publish a list of ContentPaages, a colorscheme and template content
func PublishCPs(userState *permissions.UserState, pc PageCollection, cs *ColorScheme, tvgf TemplateValueGeneratorFactory, cssurl string) {
	// For each content page in the page collection
	for _, cp := range pc {
		// TODO: different css urls for all of these?
		cp.Pub(userState, cp.Url, cssurl, cs, tvgf(userState))
	}
}

// Some Engines like Admin must be served separately
// jquerypath is ie "/js/jquery.2.0.0.js", will then serve the file at static/js/jquery.2.0.0.js
func ServeSite(basecp BaseCP, userState *permissions.UserState, cps PageCollection, tvgf TemplateValueGeneratorFactory, jquerypath string) {
	cs := basecp(userState).ColorScheme
	PublishCPs(userState, cps, cs, tvgf, "/css/menu.css")

	// TODO: Add fallback to this local version
	webhandle.Publish(jquerypath, "static"+jquerypath, true)

	// TODO: Generate these
	webhandle.Publish("/robots.txt", "static/various/robots.txt", false)
	webhandle.Publish("/sitemap_index.xml", "static/various/sitemap_index.xml", false)
}

// CSS for the menu, and a bit more
func GenerateMenuCSS(state *permissions.UserState, stretchBackground bool, cs *ColorScheme) webhandle.SimpleContextHandle {
	return func(ctx *web.Context) string {
		ctx.ContentType("css")

		// one of the extra css files that are loaded after the main style
		retval := mustache.RenderFile(menustyle_tmpl, cs)

		// The load order of background-color, background-size and background-image
		// is actually significant in some browsers! Do not reorder lightly.
		if stretchBackground {
			retval = "body {\nbackground-color: " + cs.Default_background + ";\nbackground-size: cover;\n}\n" + retval
		} else {
			retval = "body {\nbackground-color: " + cs.Default_background + ";\n}\n" + retval
		}
		retval += ".titletext { display: inline; }"
		return retval
	}
}

// Make an html and css page available
func (cp *ContentPage) Pub(userState *permissions.UserState, url, cssurl string, cs *ColorScheme, tvg webhandle.TemplateValueGenerator) {
	genericpage := genericPageBuilder(cp)
	web.Get(url, webhandle.GenerateHTMLwithTemplate(genericpage, tvg))
	web.Get(cp.GeneratedCSSurl, webhandle.GenerateCSS(genericpage))
	web.Get(cssurl, GenerateMenuCSS(userState, cp.StretchBackground, cs))
}

// TODO: Write a function for rendering a StandaloneTag inside a Page by the use of template {{{placeholders}}

// Render a page by inserting data at the {{{placeholders}}} for both html and css
func RenderPage(page *Page, templateContents map[string]string) (string, string) {
	// Note that the whitespace formatting of the generated html matter for the menu layout!
	return mustache.Render(page.String(), templateContents), mustache.Render(page.GetCSS(), templateContents)
}

// Wrap a lonely string in an entire webpage
func (cp *ContentPage) Surround(s string, templateContents map[string]string) (string, string) {
	cp.ContentHTML = s
	page := genericPageBuilder(cp)
	return RenderPage(page, templateContents)
}

// Uses a given WebHandle as the contents for the the ContentPage contents
func (cp *ContentPage) WrapWebHandle(wh webhandle.WebHandle, tvg webhandle.TemplateValueGenerator) webhandle.WebHandle {
	return func(ctx *web.Context, val string) string {
		html, css := cp.Surround(wh(ctx, val), tvg(ctx))
		web.Get(cp.GeneratedCSSurl, css)
		return html
	}
}

// Uses a given SimpleContextHandle as the contents for the the ContentPage contents
func (cp *ContentPage) WrapSimpleContextHandle(sch webhandle.SimpleContextHandle, tvg webhandle.TemplateValueGenerator) webhandle.SimpleContextHandle {
	return func(ctx *web.Context) string {
		html, css := cp.Surround(sch(ctx), tvg(ctx))
		web.Get(cp.GeneratedCSSurl, css)
		return html
	}
}

const menustyle_tmpl = `
a {
  text-decoration: none;
  color: #303030;
  font-weight: regular;
}

a:link {
  color: {{Menu_link}};
}

a:visited {
  color: {{Menu_link}};
}

a:hover {
  color: {{Menu_hover}};
}

a:active {
  color: {{Menu_active}};
}

.menuEntry {
  display: inline;
}

.menuList {
  list-style-type: none;
  float: left;
  margin: 0;
}

.separator {
  display: inline;
  color: #a0a0a0;
}
`
<commit_msg>Must render data, not file<commit_after>package genericsite

import (
	"time"

	"github.com/drbawb/mustache"
	"github.com/hoisie/web"
	. "github.com/xyproto/onthefly"
	"github.com/xyproto/permissions"
	"github.com/xyproto/webhandle"
)

type ContentPage struct {
	GeneratedCSSurl          string
	ExtraCSSurls             []string
	JqueryJSurl              string
	Faviconurl               string
	BgImageURL               string
	StretchBackground        bool
	Title                    string
	Subtitle                 string
	ContentTitle             string
	ContentHTML              string
	HeaderJS                 string
	ContentJS                string
	SearchButtonText         string
	SearchURL                string
	FooterText               string
	BackgroundTextureURL     string
	DarkBackgroundTextureURL string
	FooterTextColor          string
	FooterColor              string
	UserState                *permissions.UserState
	RoundedLook              bool
	Url                      string
	ColorScheme              *ColorScheme
	SearchBox                bool
	GoogleFonts              []string
	CustomSansSerif          string
	CustomSerif              string
}

// Content page generator
type CPgen (func(userState *permissions.UserState) *ContentPage)

// A collection of ContentPages
type PageCollection []ContentPage

// Every input from the user must be intitially stored in a UserInput variable, not in a string!
// This is just to be aware of which data one should be careful with, and to keep it clean.
type UserInput string

type ColorScheme struct {
	Darkgray           string
	Nicecolor          string
	Menu_link          string
	Menu_hover         string
	Menu_active        string
	Default_background string
	TitleText          string
}

type BaseCP func(state *permissions.UserState) *ContentPage

type TemplateValueGeneratorFactory func(*permissions.UserState) webhandle.TemplateValueGenerator

// The default settings
// Do not publish this page directly, but use it as a basis for the other pages
func DefaultCP(userState *permissions.UserState) *ContentPage {
	var cp ContentPage
	cp.GeneratedCSSurl = "/css/style.css"
	cp.ExtraCSSurls = []string{"/css/menu.css"}
	// TODO: fallback to local jquery.min.js, google how
	cp.JqueryJSurl = "//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js" // "/js/jquery-2.0.0.js"
	cp.Faviconurl = "/img/favicon.ico"
	cp.ContentTitle = "NOP"
	cp.ContentHTML = "NOP NOP NOP"
	cp.ContentJS = ""
	cp.HeaderJS = ""
	cp.SearchButtonText = "Search"
	cp.SearchURL = "/search"
	cp.SearchBox = true

	// http://wptheming.wpengine.netdna-cdn.com/wp-content/uploads/2010/04/gray-texture.jpg
	// TODO: Draw these two backgroundimages with a canvas instead
	cp.BackgroundTextureURL = "" // "/img/gray.jpg"
	// http://turbo.designwoop.com/uploads/2012/03/16_free_subtle_textures_subtle_dark_vertical.jpg
	cp.DarkBackgroundTextureURL = "/img/darkgray.jpg"

	cp.FooterColor = "black"
	cp.FooterTextColor = "#303040"

	cp.FooterText = "NOP"

	cp.UserState = userState
	cp.RoundedLook = false

	cp.Url = "/" // To be filled in when published

	// The default color scheme
	var cs ColorScheme
	cs.Darkgray = "#202020"
	cs.Nicecolor = "#5080D0"   // nice blue
	cs.Menu_link = "#c0c0c0"   // light gray
	cs.Menu_hover = "#efefe0"  // light gray, somewhat yellow
	cs.Menu_active = "#ffffff" // white
	cs.Default_background = "#000030"
	cs.TitleText = "#303030"

	cp.ColorScheme = &cs

	cp.GoogleFonts = []string{"Armata", "IM Fell English SC"}
	cp.CustomSansSerif = "" // Use the default sans serif
	cp.CustomSerif = "IM Fell English SC"

	return &cp
}

func genericPageBuilder(cp *ContentPage) *Page {
	// TODO: Record the time from one step out, because content may be generated and inserted into this generated conten
	startTime := time.Now()

	page := NewHTML5Page(cp.Title + " " + cp.Subtitle)

	page.LinkToCSS(cp.GeneratedCSSurl)
	for _, cssurl := range cp.ExtraCSSurls {
		page.LinkToCSS(cssurl)
	}
	page.LinkToJS(cp.JqueryJSurl)
	page.LinkToFavicon(cp.Faviconurl)

	AddHeader(page, cp.HeaderJS)
	AddGoogleFonts(page, cp.GoogleFonts)
	AddBodyStyle(page, cp.BgImageURL, cp.StretchBackground)
	AddTopBox(page, cp.Title, cp.Subtitle, cp.SearchURL, cp.SearchButtonText, cp.BackgroundTextureURL, cp.RoundedLook, cp.ColorScheme, cp.SearchBox)

	// TODO: Move the menubox into the TopBox

	AddMenuBox(page, cp.DarkBackgroundTextureURL, cp.CustomSansSerif)

	AddContent(page, cp.ContentTitle, cp.ContentHTML+DocumentReadyJS(cp.ContentJS))

	elapsed := time.Since(startTime)
	AddFooter(page, cp.FooterText, cp.FooterTextColor, cp.FooterColor, elapsed)

	return page
}

// Publish a list of ContentPaages, a colorscheme and template content
func PublishCPs(userState *permissions.UserState, pc PageCollection, cs *ColorScheme, tvgf TemplateValueGeneratorFactory, cssurl string) {
	// For each content page in the page collection
	for _, cp := range pc {
		// TODO: different css urls for all of these?
		cp.Pub(userState, cp.Url, cssurl, cs, tvgf(userState))
	}
}

// Some Engines like Admin must be served separately
// jquerypath is ie "/js/jquery.2.0.0.js", will then serve the file at static/js/jquery.2.0.0.js
func ServeSite(basecp BaseCP, userState *permissions.UserState, cps PageCollection, tvgf TemplateValueGeneratorFactory, jquerypath string) {
	cs := basecp(userState).ColorScheme
	PublishCPs(userState, cps, cs, tvgf, "/css/menu.css")

	// TODO: Add fallback to this local version
	webhandle.Publish(jquerypath, "static"+jquerypath, true)

	// TODO: Generate these
	webhandle.Publish("/robots.txt", "static/various/robots.txt", false)
	webhandle.Publish("/sitemap_index.xml", "static/various/sitemap_index.xml", false)
}

// CSS for the menu, and a bit more
func GenerateMenuCSS(state *permissions.UserState, stretchBackground bool, cs *ColorScheme) webhandle.SimpleContextHandle {
	return func(ctx *web.Context) string {
		ctx.ContentType("css")

		// one of the extra css files that are loaded after the main style
		retval := mustache.Render(menustyle_tmpl, cs)

		// The load order of background-color, background-size and background-image
		// is actually significant in some browsers! Do not reorder lightly.
		if stretchBackground {
			retval = "body {\nbackground-color: " + cs.Default_background + ";\nbackground-size: cover;\n}\n" + retval
		} else {
			retval = "body {\nbackground-color: " + cs.Default_background + ";\n}\n" + retval
		}
		retval += ".titletext { display: inline; }"
		return retval
	}
}

// Make an html and css page available
func (cp *ContentPage) Pub(userState *permissions.UserState, url, cssurl string, cs *ColorScheme, tvg webhandle.TemplateValueGenerator) {
	genericpage := genericPageBuilder(cp)
	web.Get(url, webhandle.GenerateHTMLwithTemplate(genericpage, tvg))
	web.Get(cp.GeneratedCSSurl, webhandle.GenerateCSS(genericpage))
	web.Get(cssurl, GenerateMenuCSS(userState, cp.StretchBackground, cs))
}

// TODO: Write a function for rendering a StandaloneTag inside a Page by the use of template {{{placeholders}}

// Render a page by inserting data at the {{{placeholders}}} for both html and css
func RenderPage(page *Page, templateContents map[string]string) (string, string) {
	// Note that the whitespace formatting of the generated html matter for the menu layout!
	return mustache.Render(page.String(), templateContents), mustache.Render(page.GetCSS(), templateContents)
}

// Wrap a lonely string in an entire webpage
func (cp *ContentPage) Surround(s string, templateContents map[string]string) (string, string) {
	cp.ContentHTML = s
	page := genericPageBuilder(cp)
	return RenderPage(page, templateContents)
}

// Uses a given WebHandle as the contents for the the ContentPage contents
func (cp *ContentPage) WrapWebHandle(wh webhandle.WebHandle, tvg webhandle.TemplateValueGenerator) webhandle.WebHandle {
	return func(ctx *web.Context, val string) string {
		html, css := cp.Surround(wh(ctx, val), tvg(ctx))
		web.Get(cp.GeneratedCSSurl, css)
		return html
	}
}

// Uses a given SimpleContextHandle as the contents for the the ContentPage contents
func (cp *ContentPage) WrapSimpleContextHandle(sch webhandle.SimpleContextHandle, tvg webhandle.TemplateValueGenerator) webhandle.SimpleContextHandle {
	return func(ctx *web.Context) string {
		html, css := cp.Surround(sch(ctx), tvg(ctx))
		web.Get(cp.GeneratedCSSurl, css)
		return html
	}
}

const menustyle_tmpl = `
a {
  text-decoration: none;
  color: #303030;
  font-weight: regular;
}

a:link {
  color: {{Menu_link}};
}

a:visited {
  color: {{Menu_link}};
}

a:hover {
  color: {{Menu_hover}};
}

a:active {
  color: {{Menu_active}};
}

.menuEntry {
  display: inline;
}

.menuList {
  list-style-type: none;
  float: left;
  margin: 0;
}

.separator {
  display: inline;
  color: #a0a0a0;
}
`
<|endoftext|>


<commit_before>// Copyright 2016 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package badges

import (
	"golang.org/x/net/context"

	grclient "github.com/keybase/client/go/gregor/client"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/client/go/protocol/keybase1"
)

type InboxVersionSource interface {
	GetInboxVersion(context.Context, gregor1.UID) (chat1.InboxVers, error)
}

type nullInboxVersionSource struct {
}

func (n nullInboxVersionSource) GetInboxVersion(ctx context.Context, uid gregor1.UID) (chat1.InboxVers, error) {
	return chat1.InboxVers(0), nil
}

// Badger keeps a BadgeState up to date and broadcasts it to electron.
// This is the client-specific glue.
// The state is kept up to date by subscribing to:
// - All gregor state updates
// - All chat.activity gregor OOBMs
// - Logout
type Badger struct {
	libkb.Contextified
	badgeState     *BadgeState
	iboxVersSource InboxVersionSource
}

func NewBadger(g *libkb.GlobalContext) *Badger {
	return &Badger{
		Contextified:   libkb.NewContextified(g),
		badgeState:     NewBadgeState(g.Log),
		iboxVersSource: nullInboxVersionSource{},
	}
}

func (b *Badger) SetInboxVersionSource(s InboxVersionSource) {
	b.iboxVersSource = s
}

func (b *Badger) PushState(ctx context.Context, state gregor1.State) {
	b.G().Log.CDebugf(ctx, "Badger update with gregor state")
	b.badgeState.UpdateWithGregor(ctx, state)
	err := b.Send(ctx)
	if err != nil {
		b.G().Log.Warning("Badger send (pushstate) failed: %v", err)
	}
}

func (b *Badger) PushChatUpdate(ctx context.Context, update chat1.UnreadUpdate, inboxVers chat1.InboxVers) {
	b.G().Log.CDebugf(ctx, "Badger update with chat update")
	b.badgeState.UpdateWithChat(ctx, update, inboxVers)
	err := b.Send(ctx)
	if err != nil {
		b.G().Log.CDebugf(ctx, "Badger send (pushchatupdate) failed: %v", err)
	}
}

func (b *Badger) inboxVersion(ctx context.Context) chat1.InboxVers {
	uid := b.G().Env.GetUID()
	vers, err := b.iboxVersSource.GetInboxVersion(ctx, uid.ToBytes())
	if err != nil {
		b.G().Log.CDebugf(ctx, "Badger: inboxVersion error: %s", err.Error())
		return chat1.InboxVers(0)
	}
	return vers
}

func (b *Badger) Resync(ctx context.Context, chatRemote func() chat1.RemoteInterface,
	gcli *grclient.Client, update *chat1.UnreadUpdateFull) (err error) {
	if update == nil {
		iboxVersion := b.inboxVersion(ctx)
		b.G().Log.Debug("Badger: Resync(): using inbox version: %v", iboxVersion)
		update = new(chat1.UnreadUpdateFull)
		*update, err = chatRemote().GetUnreadUpdateFull(ctx, iboxVersion)
		if err != nil {
			b.G().Log.Warning("Badger resync failed: %v", err)
			return err
		}
	} else {
		b.G().Log.CDebugf(ctx, "Badger: Resync(): skipping remote call, data previously obtained")
	}

	state, err := gcli.StateMachineState(ctx, nil, false)
	if err != nil {
		b.G().Log.CDebugf(ctx, "Badger: Resync(): unable to get state: %s", err.Error())
		state = gregor1.State{}
	}
	b.badgeState.UpdateWithChatFull(ctx, *update)
	b.badgeState.UpdateWithGregor(ctx, state)
	err = b.Send(ctx)
	if err != nil {
		b.G().Log.CDebugf(ctx, "Badger send (resync) failed: %v", err)
	} else {
		b.G().Log.CDebugf(ctx, "Badger resync complete")
	}
	return err
}

func (b *Badger) Clear(ctx context.Context) {
	b.badgeState.Clear()
	err := b.Send(ctx)
	if err != nil {
		b.G().Log.CDebugf(ctx, "Badger send (clear) failed: %v", err)
	}
}

// Send the badgestate to electron
func (b *Badger) Send(ctx context.Context) error {
	state, err := b.badgeState.Export()
	if err != nil {
		return err
	}
	b.log(ctx, state)
	b.G().NotifyRouter.HandleBadgeState(state)
	return nil
}

func (b *Badger) State() *BadgeState {
	return b.badgeState
}

// Log a copy of the badgestate with some zeros stripped off for brevity.
func (b *Badger) log(ctx context.Context, state1 keybase1.BadgeState) {
	var state2 keybase1.BadgeState
	state2 = state1
	state2.Conversations = nil
	for _, c1 := range state1.Conversations {
		if c1.UnreadMessages == 0 {
			continue
		}
		c2id := c1.ConvID
		if len(c1.ConvID) >= chat1.DbShortFormLen {
			// This is the db short form for logging brevity only.
			// Don't let this leave this method.
			c2id = chat1.ConversationID([]byte(c1.ConvID)).DbShortForm()
		}

		c2 := keybase1.BadgeConversationInfo{
			ConvID:         c2id,
			UnreadMessages: c1.UnreadMessages,
			BadgeCounts:    c1.BadgeCounts,
		}
		state2.Conversations = append(state2.Conversations, c2)
	}
	b.G().Log.CDebugf(ctx, "Badger send: %+v", state2)
}
<commit_msg>send badger updates from a single thread (#12714)<commit_after>// Copyright 2016 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package badges

import (
	"sync"

	"golang.org/x/net/context"

	grclient "github.com/keybase/client/go/gregor/client"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/client/go/protocol/keybase1"
)

type InboxVersionSource interface {
	GetInboxVersion(context.Context, gregor1.UID) (chat1.InboxVers, error)
}

type nullInboxVersionSource struct {
}

func (n nullInboxVersionSource) GetInboxVersion(ctx context.Context, uid gregor1.UID) (chat1.InboxVers, error) {
	return chat1.InboxVers(0), nil
}

// Badger keeps a BadgeState up to date and broadcasts it to electron.
// This is the client-specific glue.
// The state is kept up to date by subscribing to:
// - All gregor state updates
// - All chat.activity gregor OOBMs
// - Logout
type Badger struct {
	sync.Mutex
	libkb.Contextified
	badgeState     *BadgeState
	iboxVersSource InboxVersionSource
	notifyCh       chan keybase1.BadgeState
	shutdownCh     chan struct{}
	running        bool
}

func NewBadger(g *libkb.GlobalContext) *Badger {
	b := &Badger{
		Contextified:   libkb.NewContextified(g),
		badgeState:     NewBadgeState(g.Log),
		iboxVersSource: nullInboxVersionSource{},
		notifyCh:       make(chan keybase1.BadgeState, 1000),
		shutdownCh:     make(chan struct{}),
	}
	go b.notifyLoop()
	g.PushShutdownHook(func() error {
		close(b.shutdownCh)
		return nil
	})
	return b
}

func (b *Badger) notifyLoop() {
	for {
		select {
		case state := <-b.notifyCh:
			b.G().NotifyRouter.HandleBadgeState(state)
		case <-b.shutdownCh:
			return
		}
	}
}

func (b *Badger) SetInboxVersionSource(s InboxVersionSource) {
	b.iboxVersSource = s
}

func (b *Badger) PushState(ctx context.Context, state gregor1.State) {
	b.G().Log.CDebugf(ctx, "Badger update with gregor state")
	b.badgeState.UpdateWithGregor(ctx, state)
	err := b.Send(ctx)
	if err != nil {
		b.G().Log.Warning("Badger send (pushstate) failed: %v", err)
	}
}

func (b *Badger) PushChatUpdate(ctx context.Context, update chat1.UnreadUpdate, inboxVers chat1.InboxVers) {
	b.G().Log.CDebugf(ctx, "Badger update with chat update")
	b.badgeState.UpdateWithChat(ctx, update, inboxVers)
	err := b.Send(ctx)
	if err != nil {
		b.G().Log.CDebugf(ctx, "Badger send (pushchatupdate) failed: %v", err)
	}
}

func (b *Badger) inboxVersion(ctx context.Context) chat1.InboxVers {
	uid := b.G().Env.GetUID()
	vers, err := b.iboxVersSource.GetInboxVersion(ctx, uid.ToBytes())
	if err != nil {
		b.G().Log.CDebugf(ctx, "Badger: inboxVersion error: %s", err.Error())
		return chat1.InboxVers(0)
	}
	return vers
}

func (b *Badger) Resync(ctx context.Context, chatRemote func() chat1.RemoteInterface,
	gcli *grclient.Client, update *chat1.UnreadUpdateFull) (err error) {
	if update == nil {
		iboxVersion := b.inboxVersion(ctx)
		b.G().Log.Debug("Badger: Resync(): using inbox version: %v", iboxVersion)
		update = new(chat1.UnreadUpdateFull)
		*update, err = chatRemote().GetUnreadUpdateFull(ctx, iboxVersion)
		if err != nil {
			b.G().Log.Warning("Badger resync failed: %v", err)
			return err
		}
	} else {
		b.G().Log.CDebugf(ctx, "Badger: Resync(): skipping remote call, data previously obtained")
	}

	state, err := gcli.StateMachineState(ctx, nil, false)
	if err != nil {
		b.G().Log.CDebugf(ctx, "Badger: Resync(): unable to get state: %s", err.Error())
		state = gregor1.State{}
	}
	b.badgeState.UpdateWithChatFull(ctx, *update)
	b.badgeState.UpdateWithGregor(ctx, state)
	err = b.Send(ctx)
	if err != nil {
		b.G().Log.CDebugf(ctx, "Badger send (resync) failed: %v", err)
	} else {
		b.G().Log.CDebugf(ctx, "Badger resync complete")
	}
	return err
}

func (b *Badger) Clear(ctx context.Context) {
	b.badgeState.Clear()
	err := b.Send(ctx)
	if err != nil {
		b.G().Log.CDebugf(ctx, "Badger send (clear) failed: %v", err)
	}
}

// Send the badgestate to electron
func (b *Badger) Send(ctx context.Context) error {
	state, err := b.badgeState.Export()
	if err != nil {
		return err
	}
	b.log(ctx, state)
	b.notifyCh <- state
	return nil
}

func (b *Badger) State() *BadgeState {
	return b.badgeState
}

// Log a copy of the badgestate with some zeros stripped off for brevity.
func (b *Badger) log(ctx context.Context, state1 keybase1.BadgeState) {
	var state2 keybase1.BadgeState
	state2 = state1
	state2.Conversations = nil
	for _, c1 := range state1.Conversations {
		if c1.UnreadMessages == 0 {
			continue
		}
		c2id := c1.ConvID
		if len(c1.ConvID) >= chat1.DbShortFormLen {
			// This is the db short form for logging brevity only.
			// Don't let this leave this method.
			c2id = chat1.ConversationID([]byte(c1.ConvID)).DbShortForm()
		}

		c2 := keybase1.BadgeConversationInfo{
			ConvID:         c2id,
			UnreadMessages: c1.UnreadMessages,
			BadgeCounts:    c1.BadgeCounts,
		}
		state2.Conversations = append(state2.Conversations, c2)
	}
	b.G().Log.CDebugf(ctx, "Badger send: %+v", state2)
}
<|endoftext|>


<commit_before>package gps

import (
	"bytes"
	"fmt"
	"net/url"
	"path/filepath"

	"github.com/Masterminds/vcs"
)

type maybeSource interface {
	try(cachedir string, an ProjectAnalyzer) (source, string, error)
}

type maybeSources []maybeSource

func (mbs maybeSources) try(cachedir string, an ProjectAnalyzer) (source, string, error) {
	var e sourceFailures
	for _, mb := range mbs {
		src, ident, err := mb.try(cachedir, an)
		if err == nil {
			return src, ident, nil
		}
		e = append(e, sourceSetupFailure{
			ident: ident,
			err:   err,
		})
	}
	return nil, "", e
}

type sourceSetupFailure struct {
	ident string
	err   error
}

func (e sourceSetupFailure) Error() string {
	return fmt.Sprintf("failed to set up %q, error %s", e.ident, e.err.Error())
}

type sourceFailures []sourceSetupFailure

func (sf sourceFailures) Error() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "No valid source could be created:\n")
	for _, e := range sf {
		fmt.Fprintf(&buf, "\t%s", e.Error())
	}

	return buf.String()
}

type maybeGitSource struct {
	url *url.URL
}

func (m maybeGitSource) try(cachedir string, an ProjectAnalyzer) (source, string, error) {
	ustr := m.url.String()
	path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr))
	r, err := vcs.NewGitRepo(ustr, path)
	if err != nil {
		return nil, "", err
	}

	src := &gitSource{
		baseVCSSource: baseVCSSource{
			an: an,
			dc: newMetaCache(),
			crepo: &repo{
				r:     r,
				rpath: path,
			},
		},
	}

	_, err = src.listVersions()
	if err != nil {
		return nil, "", err
		//} else if pm.ex.f&existsUpstream == existsUpstream {
		//return pm, nil
	}

	return src, ustr, nil
}

type maybeBzrSource struct {
	url *url.URL
}

func (m maybeBzrSource) try(cachedir string, an ProjectAnalyzer) (source, string, error) {
	ustr := m.url.String()
	path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr))
	r, err := vcs.NewBzrRepo(ustr, path)
	if err != nil {
		return nil, "", err
	}
	if !r.Ping() {
		return nil, "", fmt.Errorf("Remote repository at %s does not exist, or is inaccessible", ustr)
	}

	return &bzrSource{
		baseVCSSource: baseVCSSource{
			an: an,
			dc: newMetaCache(),
			crepo: &repo{
				r:     r,
				rpath: path,
			},
		},
	}, ustr, nil
}

type maybeHgSource struct {
	url *url.URL
}

func (m maybeHgSource) try(cachedir string, an ProjectAnalyzer) (source, string, error) {
	ustr := m.url.String()
	path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr))
	r, err := vcs.NewHgRepo(ustr, path)
	if err != nil {
		return nil, "", err
	}
	if !r.Ping() {
		return nil, "", fmt.Errorf("Remote repository at %s does not exist, or is inaccessible", ustr)
	}

	return &hgSource{
		baseVCSSource: baseVCSSource{
			an: an,
			dc: newMetaCache(),
			crepo: &repo{
				r:     r,
				rpath: path,
			},
		},
	}, ustr, nil
}
<commit_msg>Populate baseVCSSource.lvfunc<commit_after>package gps

import (
	"bytes"
	"fmt"
	"net/url"
	"path/filepath"

	"github.com/Masterminds/vcs"
)

type maybeSource interface {
	try(cachedir string, an ProjectAnalyzer) (source, string, error)
}

type maybeSources []maybeSource

func (mbs maybeSources) try(cachedir string, an ProjectAnalyzer) (source, string, error) {
	var e sourceFailures
	for _, mb := range mbs {
		src, ident, err := mb.try(cachedir, an)
		if err == nil {
			return src, ident, nil
		}
		e = append(e, sourceSetupFailure{
			ident: ident,
			err:   err,
		})
	}
	return nil, "", e
}

type sourceSetupFailure struct {
	ident string
	err   error
}

func (e sourceSetupFailure) Error() string {
	return fmt.Sprintf("failed to set up %q, error %s", e.ident, e.err.Error())
}

type sourceFailures []sourceSetupFailure

func (sf sourceFailures) Error() string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "No valid source could be created:\n")
	for _, e := range sf {
		fmt.Fprintf(&buf, "\t%s", e.Error())
	}

	return buf.String()
}

type maybeGitSource struct {
	url *url.URL
}

func (m maybeGitSource) try(cachedir string, an ProjectAnalyzer) (source, string, error) {
	ustr := m.url.String()
	path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr))
	r, err := vcs.NewGitRepo(ustr, path)
	if err != nil {
		return nil, "", err
	}

	src := &gitSource{
		baseVCSSource: baseVCSSource{
			an: an,
			dc: newMetaCache(),
			crepo: &repo{
				r:     r,
				rpath: path,
			},
		},
	}

	src.baseVCSSource.lvfunc = src.listVersions

	_, err = src.listVersions()
	if err != nil {
		return nil, "", err
	}

	return src, ustr, nil
}

type maybeBzrSource struct {
	url *url.URL
}

func (m maybeBzrSource) try(cachedir string, an ProjectAnalyzer) (source, string, error) {
	ustr := m.url.String()
	path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr))
	r, err := vcs.NewBzrRepo(ustr, path)
	if err != nil {
		return nil, "", err
	}
	if !r.Ping() {
		return nil, "", fmt.Errorf("Remote repository at %s does not exist, or is inaccessible", ustr)
	}

	src := &bzrSource{
		baseVCSSource: baseVCSSource{
			an: an,
			dc: newMetaCache(),
			ex: existence{
				s: existsUpstream,
				f: existsUpstream,
			},
			crepo: &repo{
				r:     r,
				rpath: path,
			},
		},
	}
	src.baseVCSSource.lvfunc = src.listVersions

	return src, ustr, nil
}

type maybeHgSource struct {
	url *url.URL
}

func (m maybeHgSource) try(cachedir string, an ProjectAnalyzer) (source, string, error) {
	ustr := m.url.String()
	path := filepath.Join(cachedir, "sources", sanitizer.Replace(ustr))
	r, err := vcs.NewHgRepo(ustr, path)
	if err != nil {
		return nil, "", err
	}
	if !r.Ping() {
		return nil, "", fmt.Errorf("Remote repository at %s does not exist, or is inaccessible", ustr)
	}

	src := &hgSource{
		baseVCSSource: baseVCSSource{
			an: an,
			dc: newMetaCache(),
			ex: existence{
				s: existsUpstream,
				f: existsUpstream,
			},
			crepo: &repo{
				r:     r,
				rpath: path,
			},
		},
	}
	src.baseVCSSource.lvfunc = src.listVersions

	return src, ustr, nil
}
<|endoftext|>