<commit_before>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"errors"
	"fmt"
	"github.com/conformal/btcwire"
	"github.com/conformal/go-flags"
	"net"
	"os"
	"path/filepath"
	"time"
)

const (
	defaultConfigFilename = "btcd.conf"
	defaultLogLevel       = "info"
	defaultBtcnet         = btcwire.MainNet
	defaultMaxPeers       = 8
	defaultBanDuration    = time.Hour * 24
	defaultVerifyEnabled  = false
)

var (
	defaultConfigFile = filepath.Join(btcdHomeDir(), defaultConfigFilename)
	defaultDbDir      = filepath.Join(btcdHomeDir(), "db")
)

// config defines the configuration options for btcd.
//
// See loadConfig for details on the configuration load process.
type config struct {
	DebugLevel     string        `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
	AddPeers       []string      `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
	ConnectPeers   []string      `long:"connect" description:"Connect only to the specified peers at startup"`
	SeedPeer       string        `short:"s" long:"seedpeer" description:"Retrieve peer addresses from this peer and then disconnect"`
	Port           string        `short:"p" long:"port" description:"Listen for connections on this port (default: 8333, testnet: 18333)"`
	MaxPeers       int           `long:"maxpeers" description:"Max number of inbound and outbound peers"`
	BanDuration    time.Duration `long:"banduration" description:"How long to ban misbehaving peers.  Valid time units are {s, m, h}.  Minimum 1 second"`
	VerifyDisabled bool          `long:"noverify" description:"Disable block/transaction verification -- WARNING: This option can be dangerous and is for development use only"`
	ConfigFile     string        `short:"C" long:"configfile" description:"Path to configuration file"`
	DbDir          string        `short:"b" long:"dbdir" description:"Directory to store database"`
	RpcUser        string        `short:"u" long:"rpcuser" description:"Username for rpc connections"`
	RpcPass        string        `short:"P" long:"rpcpass" description:"Password for rpc connections"`
	RpcPort        string        `short:"r" long:"rpcport" description:"Listen for json/rpc messages on this port"`
	DisableRpc     bool          `long:"norpc" description:"Disable built-in RPC server -- NOTE: The RPC server is disabled by default if no rpcuser/rpcpass is specified"`
	DisableDNSSeed bool          `long:"nodnsseed" description:"Disable DNS seeding for peers"`
	TestNet3       bool          `long:"testnet" description:"Use the test network"`
	RegressionTest bool          `long:"regtest" description:"Use the regression test network"`
}

// btcdHomeDir returns an OS appropriate home directory for btcd.
func btcdHomeDir() string {
	// Search for Windows APPDATA first.  This won't exist on POSIX OSes.
	appData := os.Getenv("APPDATA")
	if appData != "" {
		return filepath.Join(appData, "btcd")
	}

	// Fall back to standard HOME directory that works for most POSIX OSes.
	home := os.Getenv("HOME")
	if home != "" {
		return filepath.Join(home, ".btcd")
	}

	// In the worst case, use the current directory.
	return "."
}

// validLogLevel returns whether or not logLevel is a valid debug log level.
func validLogLevel(logLevel string) bool {
	switch logLevel {
	case "trace":
		fallthrough
	case "debug":
		fallthrough
	case "info":
		fallthrough
	case "warn":
		fallthrough
	case "error":
		fallthrough
	case "critical":
		return true
	}
	return false
}

// normalizePeerAddress returns addr with the default peer port appended if
// there is not already a port specified.
func normalizePeerAddress(addr string) string {
	_, _, err := net.SplitHostPort(addr)
	if err != nil {
		return net.JoinHostPort(addr, activeNetParams.peerPort)
	}
	return addr
}

// removeDuplicateAddresses returns a new slice with all duplicate entries in
// addrs removed.
func removeDuplicateAddresses(addrs []string) []string {
	result := make([]string, 0)
	seen := map[string]bool{}
	for _, val := range addrs {
		if _, ok := seen[val]; !ok {
			result = append(result, val)
			seen[val] = true
		}
	}
	return result
}

func normalizeAndRemoveDuplicateAddresses(addrs []string) []string {
	for i, addr := range addrs {
		addrs[i] = normalizePeerAddress(addr)
	}
	addrs = removeDuplicateAddresses(addrs)

	return addrs
}

// updateConfigWithActiveParams update the passed config with parameters
// from the active net params if the relevant options in the passed config
// object are the default so options specified by the user on the command line
// are not overridden.
func updateConfigWithActiveParams(cfg *config) {
	if cfg.Port == netParams(defaultBtcnet).listenPort {
		cfg.Port = activeNetParams.listenPort
	}

	if cfg.RpcPort == netParams(defaultBtcnet).rpcPort {
		cfg.RpcPort = activeNetParams.rpcPort
	}
}

// filesExists reports whether the named file or directory exists.
func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// loadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
// 	1) Start with a default config with sane settings
// 	2) Pre-parse the command line to check for an alternative config file
// 	3) Load configuration file overwriting defaults with any specified options
// 	4) Parse CLI options and overwrite/add any specified options
//
// The above results in btcd functioning properly without any config settings
// while still allowing the user to override settings with config files and
// command line options.  Command line options always take precedence.
func loadConfig() (*config, []string, error) {
	// Default config.
	cfg := config{
		DebugLevel:  defaultLogLevel,
		Port:        netParams(defaultBtcnet).listenPort,
		RpcPort:     netParams(defaultBtcnet).rpcPort,
		MaxPeers:    defaultMaxPeers,
		BanDuration: defaultBanDuration,
		ConfigFile:  defaultConfigFile,
		DbDir:       defaultDbDir,
	}

	// A config file in the current directory takes precedence.
	if fileExists(defaultConfigFilename) {
		cfg.ConfigFile = defaultConfigFilename
	}

	// Pre-parse the command line options to see if an alternative config
	// file was specified.
	preCfg := cfg
	preParser := flags.NewParser(&preCfg, flags.Default)
	_, err := preParser.Parse()
	if err != nil {
		if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
			preParser.WriteHelp(os.Stderr)
		}
		return nil, nil, err
	}

	// Load additional config from file.
	parser := flags.NewParser(&cfg, flags.Default)
	err = parser.ParseIniFile(preCfg.ConfigFile)
	if err != nil {
		if _, ok := err.(*os.PathError); !ok {
			fmt.Fprintln(os.Stderr, err)
			parser.WriteHelp(os.Stderr)
			return nil, nil, err
		}
		log.Warnf("%v", err)
	}

	// Parse command line options again to ensure they take precedence.
	remainingArgs, err := parser.Parse()
	if err != nil {
		if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
			parser.WriteHelp(os.Stderr)
		}
		return nil, nil, err
	}

	// The two test networks can't be selected simultaneously.
	if cfg.TestNet3 && cfg.RegressionTest {
		str := "%s: The testnet and regtest params can't be used " +
			"together -- choose one of the two"
		err := errors.New(fmt.Sprintf(str, "loadConfig"))
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Choose the active network params based on the testnet and regression
	// test net flags.
	if cfg.TestNet3 {
		activeNetParams = netParams(btcwire.TestNet3)
	} else if cfg.RegressionTest {
		activeNetParams = netParams(btcwire.TestNet)
	}
	updateConfigWithActiveParams(&cfg)

	// Validate debug log level.
	if !validLogLevel(cfg.DebugLevel) {
		str := "%s: The specified debug level is invalid -- parsed [%v]"
		err := errors.New(fmt.Sprintf(str, "loadConfig", cfg.DebugLevel))
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Don't allow ban durations that are too short.
	if cfg.BanDuration < time.Duration(time.Second) {
		str := "%s: The banduration option may not be less than 1s -- parsed [%v]"
		err := errors.New(fmt.Sprintf(str, "loadConfig", cfg.BanDuration))
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --addPeer and --connect do not mix.
	if len(cfg.AddPeers) > 0 && len(cfg.ConnectPeers) > 0 {
		str := "%s: the --addpeer and --connect options can not be " +
			"mixed"
		err := errors.New(fmt.Sprintf(str, "loadConfig"))
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Connect means no seeding or listening.
	if len(cfg.ConnectPeers) > 0 {
		cfg.DisableDNSSeed = true
		// XXX turn off server listening.
	}

	// The RPC server is disabled if no username or password is provided.
	if cfg.RpcUser == "" || cfg.RpcPass == "" {
		cfg.DisableRpc = true
	}

	// Add default port to all added peer addresses if needed and remove
	// duplicate addresses.
	cfg.AddPeers = normalizeAndRemoveDuplicateAddresses(cfg.AddPeers)
	cfg.ConnectPeers =
		normalizeAndRemoveDuplicateAddresses(cfg.ConnectPeers)

	return &cfg, remainingArgs, nil
}
<commit_msg>Reorder some of the config options.<commit_after>// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"errors"
	"fmt"
	"github.com/conformal/btcwire"
	"github.com/conformal/go-flags"
	"net"
	"os"
	"path/filepath"
	"time"
)

const (
	defaultConfigFilename = "btcd.conf"
	defaultLogLevel       = "info"
	defaultBtcnet         = btcwire.MainNet
	defaultMaxPeers       = 8
	defaultBanDuration    = time.Hour * 24
	defaultVerifyEnabled  = false
)

var (
	defaultConfigFile = filepath.Join(btcdHomeDir(), defaultConfigFilename)
	defaultDbDir      = filepath.Join(btcdHomeDir(), "db")
)

// config defines the configuration options for btcd.
//
// See loadConfig for details on the configuration load process.
type config struct {
	ConfigFile     string        `short:"C" long:"configfile" description:"Path to configuration file"`
	DbDir          string        `short:"b" long:"dbdir" description:"Directory to store database"`
	AddPeers       []string      `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
	ConnectPeers   []string      `long:"connect" description:"Connect only to the specified peers at startup"`
	SeedPeer       string        `short:"s" long:"seedpeer" description:"Retrieve peer addresses from this peer and then disconnect"`
	Port           string        `short:"p" long:"port" description:"Listen for connections on this port (default: 8333, testnet: 18333)"`
	MaxPeers       int           `long:"maxpeers" description:"Max number of inbound and outbound peers"`
	BanDuration    time.Duration `long:"banduration" description:"How long to ban misbehaving peers.  Valid time units are {s, m, h}.  Minimum 1 second"`
	VerifyDisabled bool          `long:"noverify" description:"Disable block/transaction verification -- WARNING: This option can be dangerous and is for development use only"`
	RpcUser        string        `short:"u" long:"rpcuser" description:"Username for rpc connections"`
	RpcPass        string        `short:"P" long:"rpcpass" description:"Password for rpc connections"`
	RpcPort        string        `short:"r" long:"rpcport" description:"Listen for json/rpc messages on this port"`
	DisableRpc     bool          `long:"norpc" description:"Disable built-in RPC server -- NOTE: The RPC server is disabled by default if no rpcuser/rpcpass is specified"`
	DisableDNSSeed bool          `long:"nodnsseed" description:"Disable DNS seeding for peers"`
	TestNet3       bool          `long:"testnet" description:"Use the test network"`
	RegressionTest bool          `long:"regtest" description:"Use the regression test network"`
	DebugLevel     string        `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
}

// btcdHomeDir returns an OS appropriate home directory for btcd.
func btcdHomeDir() string {
	// Search for Windows APPDATA first.  This won't exist on POSIX OSes.
	appData := os.Getenv("APPDATA")
	if appData != "" {
		return filepath.Join(appData, "btcd")
	}

	// Fall back to standard HOME directory that works for most POSIX OSes.
	home := os.Getenv("HOME")
	if home != "" {
		return filepath.Join(home, ".btcd")
	}

	// In the worst case, use the current directory.
	return "."
}

// validLogLevel returns whether or not logLevel is a valid debug log level.
func validLogLevel(logLevel string) bool {
	switch logLevel {
	case "trace":
		fallthrough
	case "debug":
		fallthrough
	case "info":
		fallthrough
	case "warn":
		fallthrough
	case "error":
		fallthrough
	case "critical":
		return true
	}
	return false
}

// normalizePeerAddress returns addr with the default peer port appended if
// there is not already a port specified.
func normalizePeerAddress(addr string) string {
	_, _, err := net.SplitHostPort(addr)
	if err != nil {
		return net.JoinHostPort(addr, activeNetParams.peerPort)
	}
	return addr
}

// removeDuplicateAddresses returns a new slice with all duplicate entries in
// addrs removed.
func removeDuplicateAddresses(addrs []string) []string {
	result := make([]string, 0)
	seen := map[string]bool{}
	for _, val := range addrs {
		if _, ok := seen[val]; !ok {
			result = append(result, val)
			seen[val] = true
		}
	}
	return result
}

func normalizeAndRemoveDuplicateAddresses(addrs []string) []string {
	for i, addr := range addrs {
		addrs[i] = normalizePeerAddress(addr)
	}
	addrs = removeDuplicateAddresses(addrs)

	return addrs
}

// updateConfigWithActiveParams update the passed config with parameters
// from the active net params if the relevant options in the passed config
// object are the default so options specified by the user on the command line
// are not overridden.
func updateConfigWithActiveParams(cfg *config) {
	if cfg.Port == netParams(defaultBtcnet).listenPort {
		cfg.Port = activeNetParams.listenPort
	}

	if cfg.RpcPort == netParams(defaultBtcnet).rpcPort {
		cfg.RpcPort = activeNetParams.rpcPort
	}
}

// filesExists reports whether the named file or directory exists.
func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// loadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
// 	1) Start with a default config with sane settings
// 	2) Pre-parse the command line to check for an alternative config file
// 	3) Load configuration file overwriting defaults with any specified options
// 	4) Parse CLI options and overwrite/add any specified options
//
// The above results in btcd functioning properly without any config settings
// while still allowing the user to override settings with config files and
// command line options.  Command line options always take precedence.
func loadConfig() (*config, []string, error) {
	// Default config.
	cfg := config{
		DebugLevel:  defaultLogLevel,
		Port:        netParams(defaultBtcnet).listenPort,
		RpcPort:     netParams(defaultBtcnet).rpcPort,
		MaxPeers:    defaultMaxPeers,
		BanDuration: defaultBanDuration,
		ConfigFile:  defaultConfigFile,
		DbDir:       defaultDbDir,
	}

	// A config file in the current directory takes precedence.
	if fileExists(defaultConfigFilename) {
		cfg.ConfigFile = defaultConfigFilename
	}

	// Pre-parse the command line options to see if an alternative config
	// file was specified.
	preCfg := cfg
	preParser := flags.NewParser(&preCfg, flags.Default)
	_, err := preParser.Parse()
	if err != nil {
		if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
			preParser.WriteHelp(os.Stderr)
		}
		return nil, nil, err
	}

	// Load additional config from file.
	parser := flags.NewParser(&cfg, flags.Default)
	err = parser.ParseIniFile(preCfg.ConfigFile)
	if err != nil {
		if _, ok := err.(*os.PathError); !ok {
			fmt.Fprintln(os.Stderr, err)
			parser.WriteHelp(os.Stderr)
			return nil, nil, err
		}
		log.Warnf("%v", err)
	}

	// Parse command line options again to ensure they take precedence.
	remainingArgs, err := parser.Parse()
	if err != nil {
		if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
			parser.WriteHelp(os.Stderr)
		}
		return nil, nil, err
	}

	// The two test networks can't be selected simultaneously.
	if cfg.TestNet3 && cfg.RegressionTest {
		str := "%s: The testnet and regtest params can't be used " +
			"together -- choose one of the two"
		err := errors.New(fmt.Sprintf(str, "loadConfig"))
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Choose the active network params based on the testnet and regression
	// test net flags.
	if cfg.TestNet3 {
		activeNetParams = netParams(btcwire.TestNet3)
	} else if cfg.RegressionTest {
		activeNetParams = netParams(btcwire.TestNet)
	}
	updateConfigWithActiveParams(&cfg)

	// Validate debug log level.
	if !validLogLevel(cfg.DebugLevel) {
		str := "%s: The specified debug level is invalid -- parsed [%v]"
		err := errors.New(fmt.Sprintf(str, "loadConfig", cfg.DebugLevel))
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Don't allow ban durations that are too short.
	if cfg.BanDuration < time.Duration(time.Second) {
		str := "%s: The banduration option may not be less than 1s -- parsed [%v]"
		err := errors.New(fmt.Sprintf(str, "loadConfig", cfg.BanDuration))
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// --addPeer and --connect do not mix.
	if len(cfg.AddPeers) > 0 && len(cfg.ConnectPeers) > 0 {
		str := "%s: the --addpeer and --connect options can not be " +
			"mixed"
		err := errors.New(fmt.Sprintf(str, "loadConfig"))
		fmt.Fprintln(os.Stderr, err)
		parser.WriteHelp(os.Stderr)
		return nil, nil, err
	}

	// Connect means no seeding or listening.
	if len(cfg.ConnectPeers) > 0 {
		cfg.DisableDNSSeed = true
		// XXX turn off server listening.
	}

	// The RPC server is disabled if no username or password is provided.
	if cfg.RpcUser == "" || cfg.RpcPass == "" {
		cfg.DisableRpc = true
	}

	// Add default port to all added peer addresses if needed and remove
	// duplicate addresses.
	cfg.AddPeers = normalizeAndRemoveDuplicateAddresses(cfg.AddPeers)
	cfg.ConnectPeers =
		normalizeAndRemoveDuplicateAddresses(cfg.ConnectPeers)

	return &cfg, remainingArgs, nil
}
<|endoftext|>


<commit_before>/**
 * (C) Copyright 2013, Deft Labs
 *
 * 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 dlshared

import (
	"os"
	"github.com/daviddengcn/go-ljson-conf"
)

type Configuration struct {

	Version string
	PidFile string

	Pid int

	data *ljconf.Conf

	Hostname string
	FileName string
}

func (self *Configuration) String(key string, def string) string {
	return self.data.String(key, def)
}

func (self *Configuration) Int(key string, def int) int {
	return self.data.Int(key, def)
}

func (self *Configuration) Bool(key string, def bool) bool {
	return self.data.Bool(key, def)
}

func (self *Configuration) Float(key string, def float64) float64 {
	return self.data.Float(key, def)
}

func (self *Configuration) StrList(key string, def [] string) []string {
	return self.data.StringList(key, def)
}

func (self *Configuration) IntList(key string, def []int) []int {
	return self.data.IntList(key, def)
}

func NewConfiguration(fileName string) (*Configuration, error) {

	conf := &Configuration{ FileName : fileName }

	var err error
	if conf.data, err = ljconf.Load(fileName); err != nil {
		return nil, err
	}

	conf.PidFile = conf.data.String("pidFile", "")

	if len(conf.PidFile) == 0 {
		return nil, NewStackError("Configuration file error - pidFile not set")
	}

	conf.Version = conf.data.String("version", "@VERSION@")

	conf.Pid = os.Getpid()

	conf.Hostname, err = os.Hostname()
	if err != nil {
		return nil, err
	}

	if len(conf.Version) == 0 || conf.Version == "@VERSION@" {
		return nil, NewStackError("Configuration file error - version not set")
	}

	return conf, nil
}

<commit_msg>added a required property - environment.<commit_after>/**
 * (C) Copyright 2013, Deft Labs
 *
 * 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 dlshared

import (
	"os"
	"github.com/daviddengcn/go-ljson-conf"
)

type Configuration struct {

	Version string
	PidFile string

	Pid int

	data *ljconf.Conf

	Environment string

	Hostname string
	FileName string
}

func (self *Configuration) String(key string, def string) string {
	return self.data.String(key, def)
}

func (self *Configuration) Int(key string, def int) int {
	return self.data.Int(key, def)
}

func (self *Configuration) Bool(key string, def bool) bool {
	return self.data.Bool(key, def)
}

func (self *Configuration) Float(key string, def float64) float64 {
	return self.data.Float(key, def)
}

func (self *Configuration) StrList(key string, def [] string) []string {
	return self.data.StringList(key, def)
}

func (self *Configuration) IntList(key string, def []int) []int {
	return self.data.IntList(key, def)
}

func NewConfiguration(fileName string) (*Configuration, error) {

	conf := &Configuration{ FileName : fileName }

	var err error
	if conf.data, err = ljconf.Load(fileName); err != nil {
		return nil, err
	}

	conf.PidFile = conf.data.String("pidFile", "")

	if len(conf.PidFile) == 0 {
		return nil, NewStackError("Configuration file error - pidFile not set")
	}

	conf.Environment = conf.data.String("environment", "")

	conf.Version = conf.data.String("version", "")

	conf.Pid = os.Getpid()

	conf.Hostname, err = os.Hostname()
	if err != nil {
		return nil, err
	}

	if len(conf.Version) == 0 {
		return nil, NewStackError("Configuration file error - version not set")
	}

	if len(conf.Environment) == 0 {
		return nil, NewStackError("Configuration file error - environment not set")
	}

	return conf, nil
}

<|endoftext|>


<commit_before>package windows

import (
  "github.com/mattn/go-gtk/gtk"
  "ghighlighter/models"
)

type GhMainWindow struct {
  GtkWindow *gtk.GtkWindow
}

func (w *GhMainWindow) build() {
  w.GtkWindow.Connect("destroy", gtk.MainQuit)

  w.GtkWindow.SetTitle("ghighlights")
  w.GtkWindow.Connect("destroy", gtk.MainQuit)

  mainVBox := gtk.VBox(false, 1)

  scrolledTextViewWindow := gtk.ScrolledWindow(nil, nil)
  scrolledTextViewWindow.SetPolicy(gtk.GTK_POLICY_AUTOMATIC, gtk.GTK_POLICY_AUTOMATIC)

  textView := gtk.TextView()
  textView.SetSizeRequest(600, 100)
  scrolledTextViewWindow.Add(textView)

  scrolledTextViewWindow.SetSizeRequest(600, 100)
  mainVBox.Add(scrolledTextViewWindow)

  readingHBox := gtk.HBox(false, 1)

  readingsComboBox := gtk.ComboBoxText()
  readings := models.Readings()
  for _, reading := range readings.Items {
    readingsComboBox.AppendText(reading.Title)
  }
  readingsComboBox.SetActive(0)
  readingHBox.Add(readingsComboBox)

  mainVBox.Add(readingHBox)

  addHighlightButton := gtk.ButtonWithLabel("Add highlight")
  highlights := models.Highlights()
  addHighlightButton.Clicked(func() {
    readingId := readings.FindByTitle(readingsComboBox.GetActiveText()).ReadmillId

    buffer := textView.GetBuffer()
    var startIter, endIter gtk.GtkTextIter
    buffer.GetStartIter(&startIter)
    buffer.GetEndIter(&endIter)
    content := buffer.GetText(&startIter, &endIter, true)

    highlight := models.GhHighlight{content, readingId, 0}
    highlights.Add(highlight)
  })
  mainVBox.Add(addHighlightButton)

  w.GtkWindow.Add(mainVBox)
  return
}

func MainWindow() *GhMainWindow {
  mainWindow := &GhMainWindow{gtk.Window(gtk.GTK_WINDOW_TOPLEVEL)}
  mainWindow.build()
  return mainWindow
}

<commit_msg>Add menu bar to main window<commit_after>package windows

import (
  "fmt"
  "github.com/mattn/go-gtk/gtk"
  "ghighlighter/models"
)

type GhMainWindow struct {
  GtkWindow *gtk.GtkWindow
}

func (w *GhMainWindow) build() {
  w.GtkWindow.Connect("destroy", gtk.MainQuit)

  w.GtkWindow.SetTitle("ghighlights")
  w.GtkWindow.Connect("destroy", gtk.MainQuit)

  mainVBox := gtk.VBox(false, 1)

  menubar := w.buildMenuBar()
  mainVBox.PackStart(menubar, false, false, 0)

  scrolledTextViewWindow := gtk.ScrolledWindow(nil, nil)
  scrolledTextViewWindow.SetPolicy(gtk.GTK_POLICY_AUTOMATIC, gtk.GTK_POLICY_AUTOMATIC)

  textView := gtk.TextView()
  textView.SetSizeRequest(600, 100)
  scrolledTextViewWindow.Add(textView)

  scrolledTextViewWindow.SetSizeRequest(600, 100)
  mainVBox.Add(scrolledTextViewWindow)

  readingHBox := gtk.HBox(false, 1)

  readingsComboBox := gtk.ComboBoxText()
  readings := models.Readings()
  for _, reading := range readings.Items {
    readingsComboBox.AppendText(reading.Title)
  }
  readingsComboBox.SetActive(0)
  readingHBox.Add(readingsComboBox)

  mainVBox.Add(readingHBox)

  addHighlightButton := gtk.ButtonWithLabel("Add highlight")
  highlights := models.Highlights()
  addHighlightButton.Clicked(func() {
    readingId := readings.FindByTitle(readingsComboBox.GetActiveText()).ReadmillId

    buffer := textView.GetBuffer()
    var startIter, endIter gtk.GtkTextIter
    buffer.GetStartIter(&startIter)
    buffer.GetEndIter(&endIter)
    content := buffer.GetText(&startIter, &endIter, true)

    highlight := models.GhHighlight{content, readingId, 0}
    highlights.Add(highlight)
  })
  mainVBox.Add(addHighlightButton)

  w.GtkWindow.Add(mainVBox)
  return
}

func (w *GhMainWindow) buildMenuBar() *gtk.GtkMenuBar {
  menubar := gtk.MenuBar()

  fileMenuItem := gtk.MenuItemWithMnemonic("_File")
  menubar.Append(fileMenuItem)

  fileMenu := gtk.Menu()
  fileMenuItem.SetSubmenu(fileMenu)

  quitMenuItem := gtk.MenuItemWithMnemonic("_Quit")
  quitMenuItem.Connect("activate", func() {
    gtk.MainQuit()
  })
  fileMenu.Append(quitMenuItem)

  viewMenuItem := gtk.MenuItemWithMnemonic("_View")
  menubar.Append(viewMenuItem)

  viewMenu := gtk.Menu()
  viewMenuItem.SetSubmenu(viewMenu)

  queuedHighlightsMenuItem := gtk.MenuItemWithMnemonic("Queued _Highlights")
  queuedHighlightsMenuItem.Connect("activate", func() {
    fmt.Println("Open queued highlights window")
  })
  viewMenu.Append(queuedHighlightsMenuItem)

  helpMenuItem := gtk.MenuItemWithMnemonic("_Help")
  menubar.Append(helpMenuItem)

  helpMenu := gtk.Menu()
  helpMenuItem.SetSubmenu(helpMenu)

  aboutMenuItem := gtk.MenuItemWithMnemonic("About")
  aboutMenuItem.Connect("activate", func() {
    fmt.Println("Show about dialog")
  })
  helpMenu.Append(aboutMenuItem)

  return menubar
}

func MainWindow() *GhMainWindow {
  mainWindow := &GhMainWindow{gtk.Window(gtk.GTK_WINDOW_TOPLEVEL)}
  mainWindow.build()
  return mainWindow
}

<|endoftext|>


<commit_before>// Copyright 2019 The Cloud Robotics 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 (
	"fmt"
	"log"
	"sync"
	"time"

	pb "github.com/googlecloudrobotics/core/src/proto/http-relay"

	"github.com/prometheus/client_golang/prometheus"
)

var (
	brokerRequests = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "broker_requests",
			Help: "Number of requests to the broker",
		},
		[]string{"method"},
	)
	brokerResponses = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "broker_responses",
			Help: "Number of responses from the broker",
		},
		[]string{"method", "result"},
	)
	brokerResponseDurations = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "broker_responses_durations",
			Help: "Time from request to final response in ms",
		},
		[]string{"method"},
	)
)

func init() {
	prometheus.MustRegister(brokerRequests)
	prometheus.MustRegister(brokerResponses)
	prometheus.MustRegister(brokerResponseDurations)
}

type pendingResponse struct {
	requestStream  chan []byte
	responseStream chan *pb.HttpResponse
	lastActivity   time.Time
	// For diagnostics only.
	startTime time.Time
	server    string
}

// broker implements a thread-safe map for the request and response queues.
// Requests (req) are mapped by server-name. There is only channel per relay-
// client (identified by the server query parameter)
// Responses (resp) are mapped by stream id (randomly generated hex string).
// There can be multiple concurrent transfers per relay-client, each identified
// by a unique id query parameter.
type broker struct {
	m    sync.Mutex
	req  map[string]chan *pb.HttpRequest
	resp map[string]*pendingResponse
}

func newBroker() *broker {
	var r broker
	r.req = make(map[string]chan *pb.HttpRequest)
	r.resp = make(map[string]*pendingResponse)
	return &r
}

// RelayRequest matches a pending relay client's request to the encapsulated
// request and returns a channel for the results.
func (r *broker) RelayRequest(server string, request *pb.HttpRequest) (<-chan *pb.HttpResponse, error) {
	id := *request.Id

	r.m.Lock()
	if r.req[server] == nil {
		// This happens when the relay-client connects for the first time.
		r.req[server] = make(chan *pb.HttpRequest)
	}
	if r.resp[id] != nil {
		return nil, fmt.Errorf("Multiple clients trying to handle request ID %s on server %s", id, server)
	}
	ts := time.Now()
	r.resp[id] = &pendingResponse{
		requestStream:  make(chan []byte),
		responseStream: make(chan *pb.HttpResponse),
		lastActivity:   ts,
		startTime:      ts,
		server:         server,
	}
	reqChan := r.req[server]
	respChan := r.resp[id].responseStream
	r.m.Unlock()

	log.Printf("Enqueuing request %s for %s", id, server)
	brokerRequests.WithLabelValues("client").Inc()
	select {
	case reqChan <- request:
		return respChan, nil
	case <-time.After(10 * time.Second):
		return nil, fmt.Errorf("Timeout waiting for relay client to accept request for %s", server)
	}
}

// GetRequest obtains a client's request for the server identifier. It blocks
// until a client makes a request.
func (r *broker) GetRequest(server string) (*pb.HttpRequest, error) {
	r.m.Lock()
	if r.req[server] == nil {
		// This happens when the relay-server started and a client connects before
		// the relay-client connected.
		r.req[server] = make(chan *pb.HttpRequest)
	}
	reqChan := r.req[server]
	r.m.Unlock()

	brokerRequests.WithLabelValues("server_request").Inc()
	select {
	case req := <-reqChan:
		brokerResponses.WithLabelValues("server_request", "ok").Inc()
		return req, nil
	case <-time.After(time.Second * 30):
		brokerResponses.WithLabelValues("server_request", "timeout").Inc()
		return nil, fmt.Errorf("No request received within timeout")
	}
}

// GetRequestStream gets data from the stream that follows a client's HTTP
// request. For example, when using `kubectl exec` this passes stdin data from
// the broker to the relay client.
// If no ongoing request matches the given ID, this returns ok=false.
func (r *broker) GetRequestStream(id string) ([]byte, bool) {
	r.m.Lock()
	pr := r.resp[id]
	r.m.Unlock()
	if pr == nil {
		return nil, false
	}

	select {
	case req := <-pr.requestStream:
		return req, true
	case <-time.After(time.Second * 30):
		return []byte{}, true
	}
}

// PutsRequestStream adds data from the stream that follows a client's HTTP
// request. For example, when using `kubectl exec` this passes stdin data from
// kubectl to the broker.
// If no ongoing request matches the given ID, this returns ok=false.
func (r *broker) PutRequestStream(id string, data []byte) bool {
	r.m.Lock()
	pr := r.resp[id]
	r.m.Unlock()
	if pr == nil {
		return false
	}

	pr.requestStream <- data
	return true
}

// SendResponse delivers the HttpResponse to the client handler that created the
// request. It fails if and only if the request ID is not recognized.
func (r *broker) SendResponse(resp *pb.HttpResponse) error {
	id := *resp.Id
	r.m.Lock()
	pr := r.resp[id]
	if pr == nil {
		r.m.Unlock()
		brokerResponses.WithLabelValues("server_response", "invalid").Inc()
		return fmt.Errorf("Duplicate or invalid request ID %s", id)
	}
	if resp.GetEof() {
		delete(r.resp, id)
	} else {
		pr.lastActivity = time.Now()
	}
	duration := time.Since(pr.startTime).Seconds()
	pr.responseStream <- resp
	r.m.Unlock()
	brokerRequests.WithLabelValues("server_response").Inc()
	brokerResponseDurations.WithLabelValues("server_response").Observe(duration)
	log.Printf("Delivered response %s for server %s to client, elapsed %.3fs", id, pr.server, duration)
	if resp.GetEof() {
		close(pr.responseStream)
	}
	brokerResponses.WithLabelValues("server_response", "ok").Inc()
	return nil
}

func (r *broker) ReapInactiveRequests(threshold time.Time) {
	r.m.Lock()
	for key, value := range r.resp {
		if value.lastActivity.Before(threshold) {
			log.Printf("Timeout on inactive request %s", key)
			defer close(value.requestStream)
			defer close(value.responseStream)
			// Amazingly, this is safe in Go: https://stackoverflow.com/questions/23229975/is-it-safe-to-remove-selected-keys-from-map-within-a-range-loop
			delete(r.resp, key)
		}
	}
	r.m.Unlock()
}
<commit_msg>Use a more actionable error for timeouts<commit_after>// Copyright 2019 The Cloud Robotics 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 (
	"fmt"
	"log"
	"sync"
	"time"

	pb "github.com/googlecloudrobotics/core/src/proto/http-relay"

	"github.com/prometheus/client_golang/prometheus"
)

var (
	brokerRequests = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "broker_requests",
			Help: "Number of requests to the broker",
		},
		[]string{"method"},
	)
	brokerResponses = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "broker_responses",
			Help: "Number of responses from the broker",
		},
		[]string{"method", "result"},
	)
	brokerResponseDurations = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Name: "broker_responses_durations",
			Help: "Time from request to final response in ms",
		},
		[]string{"method"},
	)
)

func init() {
	prometheus.MustRegister(brokerRequests)
	prometheus.MustRegister(brokerResponses)
	prometheus.MustRegister(brokerResponseDurations)
}

type pendingResponse struct {
	requestStream  chan []byte
	responseStream chan *pb.HttpResponse
	lastActivity   time.Time
	// For diagnostics only.
	startTime time.Time
	server    string
}

// broker implements a thread-safe map for the request and response queues.
// Requests (req) are mapped by server-name. There is only channel per relay-
// client (identified by the server query parameter)
// Responses (resp) are mapped by stream id (randomly generated hex string).
// There can be multiple concurrent transfers per relay-client, each identified
// by a unique id query parameter.
type broker struct {
	m    sync.Mutex
	req  map[string]chan *pb.HttpRequest
	resp map[string]*pendingResponse
}

func newBroker() *broker {
	var r broker
	r.req = make(map[string]chan *pb.HttpRequest)
	r.resp = make(map[string]*pendingResponse)
	return &r
}

// RelayRequest matches a pending relay client's request to the encapsulated
// request and returns a channel for the results.
func (r *broker) RelayRequest(server string, request *pb.HttpRequest) (<-chan *pb.HttpResponse, error) {
	id := *request.Id

	r.m.Lock()
	if r.req[server] == nil {
		// This happens when the relay-client connects for the first time.
		r.req[server] = make(chan *pb.HttpRequest)
	}
	if r.resp[id] != nil {
		return nil, fmt.Errorf("Multiple clients trying to handle request ID %s on server %s", id, server)
	}
	ts := time.Now()
	r.resp[id] = &pendingResponse{
		requestStream:  make(chan []byte),
		responseStream: make(chan *pb.HttpResponse),
		lastActivity:   ts,
		startTime:      ts,
		server:         server,
	}
	reqChan := r.req[server]
	respChan := r.resp[id].responseStream
	r.m.Unlock()

	log.Printf("Enqueuing request %s for %s", id, server)
	brokerRequests.WithLabelValues("client").Inc()
	select {
	case reqChan <- request:
		return respChan, nil
	case <-time.After(10 * time.Second):
		return nil, fmt.Errorf("%q doesn't appear to be running the relay client. Check that it's turned on, set up, and connected to the internet. (timeout waiting for relay client to accept request)", server)
	}
}

// GetRequest obtains a client's request for the server identifier. It blocks
// until a client makes a request.
func (r *broker) GetRequest(server string) (*pb.HttpRequest, error) {
	r.m.Lock()
	if r.req[server] == nil {
		// This happens when the relay-server started and a client connects before
		// the relay-client connected.
		r.req[server] = make(chan *pb.HttpRequest)
	}
	reqChan := r.req[server]
	r.m.Unlock()

	brokerRequests.WithLabelValues("server_request").Inc()
	select {
	case req := <-reqChan:
		brokerResponses.WithLabelValues("server_request", "ok").Inc()
		return req, nil
	case <-time.After(time.Second * 30):
		brokerResponses.WithLabelValues("server_request", "timeout").Inc()
		return nil, fmt.Errorf("No request received within timeout")
	}
}

// GetRequestStream gets data from the stream that follows a client's HTTP
// request. For example, when using `kubectl exec` this passes stdin data from
// the broker to the relay client.
// If no ongoing request matches the given ID, this returns ok=false.
func (r *broker) GetRequestStream(id string) ([]byte, bool) {
	r.m.Lock()
	pr := r.resp[id]
	r.m.Unlock()
	if pr == nil {
		return nil, false
	}

	select {
	case req := <-pr.requestStream:
		return req, true
	case <-time.After(time.Second * 30):
		return []byte{}, true
	}
}

// PutsRequestStream adds data from the stream that follows a client's HTTP
// request. For example, when using `kubectl exec` this passes stdin data from
// kubectl to the broker.
// If no ongoing request matches the given ID, this returns ok=false.
func (r *broker) PutRequestStream(id string, data []byte) bool {
	r.m.Lock()
	pr := r.resp[id]
	r.m.Unlock()
	if pr == nil {
		return false
	}

	pr.requestStream <- data
	return true
}

// SendResponse delivers the HttpResponse to the client handler that created the
// request. It fails if and only if the request ID is not recognized.
func (r *broker) SendResponse(resp *pb.HttpResponse) error {
	id := *resp.Id
	r.m.Lock()
	pr := r.resp[id]
	if pr == nil {
		r.m.Unlock()
		brokerResponses.WithLabelValues("server_response", "invalid").Inc()
		return fmt.Errorf("Duplicate or invalid request ID %s", id)
	}
	if resp.GetEof() {
		delete(r.resp, id)
	} else {
		pr.lastActivity = time.Now()
	}
	duration := time.Since(pr.startTime).Seconds()
	pr.responseStream <- resp
	r.m.Unlock()
	brokerRequests.WithLabelValues("server_response").Inc()
	brokerResponseDurations.WithLabelValues("server_response").Observe(duration)
	log.Printf("Delivered response %s for server %s to client, elapsed %.3fs", id, pr.server, duration)
	if resp.GetEof() {
		close(pr.responseStream)
	}
	brokerResponses.WithLabelValues("server_response", "ok").Inc()
	return nil
}

func (r *broker) ReapInactiveRequests(threshold time.Time) {
	r.m.Lock()
	for key, value := range r.resp {
		if value.lastActivity.Before(threshold) {
			log.Printf("Timeout on inactive request %s", key)
			defer close(value.requestStream)
			defer close(value.responseStream)
			// Amazingly, this is safe in Go: https://stackoverflow.com/questions/23229975/is-it-safe-to-remove-selected-keys-from-map-within-a-range-loop
			delete(r.resp, key)
		}
	}
	r.m.Unlock()
}
<|endoftext|>


<commit_before>package wingedGrid

import (
    "errors"
    //"fmt"
    "math"
)
// This file contains functions for testing the expected data structure of
// any wingedGrid

// Returns whether the edge order from the vertex index matches that of the edges
// NOT YET IMPLEMENTED
func VertEdgesMatchEdgeOrder(theGrid WingedGrid, vertIndex int32) (bool, error) {

    return false, nil
}
// Returns whether the edge has its vertices ordered correctly,
// such that FirstVertexA it the first vertex encounter when clockwise traversing
// FaceA
func EdgeVertsInCorrectOrientation(theGrid WingedGrid, edgeIndex int32) (bool, error){
    var theEdge WingedEdge = theGrid.Edges[edgeIndex]
    var nextEdge WingedEdge = theGrid.Edges[theEdge.NextA]
    // Second clockwise vertex on theEdge should be the same as the first for
    // nextEdgeA for faceA, only need to test for one pair.
    // Check with second vertex for next edge incase the next edge is the wrong one
    if theEdge.FirstVertexB == nextEdge.FirstVertexA ||
       theEdge.FirstVertexB == nextEdge.FirstVertexB {
        return true, nil
    }
    return false, errors.New("This Edge has wrong order")
}
// Returns whether the edge order from the face index matches that of the edges
// taken from edge.next
func FaceEdgesMatchesOrderFromEdge(theGrid WingedGrid, faceIndex int32) (bool, error) {
    var theFace WingedFace = theGrid.Faces[faceIndex]
    if len(theFace.Edges) <= 2 {
        return false, errors.New("Face has too few edges.")
    }
    var currentEdge WingedEdge
    currentEdge = theGrid.Edges[theFace.Edges[len(theFace.Edges) - 1]]
    for i := 0; i < len(theFace.Edges); i++ {
        next, err := currentEdge.NextEdgeForFace(faceIndex)
        if err != nil {
            return false, err
        }
        if next == theFace.Edges[i] {
            currentEdge = theGrid.Edges[next]
        } else {
            return false, nil
        }
    }
    return true, nil
}
// Returns whether a the vertices of a face are within square tolerance distance
// from the plane of the first three vertices
// NOT FULLY IMPLEMENTED
func FaceVerticesPlanar(theGrid WingedGrid, faceIndex int32, tolerance float64) (bool) {
    var theFace WingedFace
    theFace = theGrid.Faces[faceIndex]
    // always true for three or fewer points
    if len(theFace.Edges) <= 3 {
        return true
    }

    return false
}
// Returns whether a face has an orientation in the same direction as the
// position of its center, within the square tolerance.
// This test is only applicable to WingedGrids that are expected to aproximate a
// sphere.
func FaceOrientation(theGrid WingedGrid, faceIndex int32, tolerance float64) (bool, error) {
    var err error
    var theFace WingedFace = theGrid.Faces[faceIndex]
    // find center of face
    var vectorP, vectorQ, faceCenter [3]float64
    var count float64
    for _, edgeIndex := range theFace.Edges {
        var vertex WingedVertex
        var vertexIndex int32
        vertexIndex, err = theGrid.Edges[edgeIndex].FirstVertexForFace(faceIndex)
        if err != nil {
            return false, nil
        }
        vertex = theGrid.Vertices[vertexIndex]
        faceCenter[0] = faceCenter[0] + vertex.Coords[0]
        faceCenter[1] = faceCenter[1] + vertex.Coords[1]
        faceCenter[2] = faceCenter[2] + vertex.Coords[2]
        count = count + 1
    }
    faceCenter[0] = faceCenter[0] / count
    faceCenter[1] = faceCenter[1] / count
    faceCenter[1] = faceCenter[1] / count

    // simply use the first two edges, vectors away from their shared vertex
    // from these we get the face normal vector
    var edgeP, edgeQ WingedEdge
    edgeP = theGrid.Edges[theFace.Edges[0]]
    edgeQ = theGrid.Edges[theFace.Edges[1]]

    // get and check we find valid vertices
    var startVertexIndex, endVertexIndex int32
    endVertexIndex, err = edgeP.FirstVertexForFace(faceIndex)
    if err != nil {
        return false, err
    }
    startVertexIndex, err = edgeP.SecondVertexForFace(faceIndex)
    if err != nil {
        return false, err
    }
    // get vector along edge P
    vectorP[0] = theGrid.Vertices[endVertexIndex].Coords[0] -
                    theGrid.Vertices[startVertexIndex].Coords[0]
    vectorP[1] = theGrid.Vertices[endVertexIndex].Coords[1] -
                    theGrid.Vertices[startVertexIndex].Coords[1]
    vectorP[2] = theGrid.Vertices[endVertexIndex].Coords[2] -
                    theGrid.Vertices[startVertexIndex].Coords[2]

    // get and check we find valid vertices
    endVertexIndex, err = edgeQ.SecondVertexForFace(faceIndex)
    if err != nil {
        return false, err
    }
    startVertexIndex, err = edgeQ.FirstVertexForFace(faceIndex)
    if err != nil {
        return false, err
    }
    // get vector along edge Q
    vectorQ[0] = theGrid.Vertices[endVertexIndex].Coords[0] -
                    theGrid.Vertices[startVertexIndex].Coords[0]
    vectorQ[1] = theGrid.Vertices[endVertexIndex].Coords[1] -
                    theGrid.Vertices[startVertexIndex].Coords[1]
    vectorQ[2] = theGrid.Vertices[endVertexIndex].Coords[2] -
                    theGrid.Vertices[startVertexIndex].Coords[2]

    // replace with cross product at some point, copying here from original
    // icosahedron test where I simply subtracted normalized components
    var scaleFactor float64
    // normalize the center to unit vector
    scaleFactor = 1/math.Sqrt(faceCenter[0]*faceCenter[0] +
                              faceCenter[1]*faceCenter[1] +
                              faceCenter[2]*faceCenter[2])
    faceCenter[0] = faceCenter[0] * scaleFactor
    faceCenter[1] = faceCenter[1] * scaleFactor
    faceCenter[2] = faceCenter[2] * scaleFactor

    // find normal to face, cross product!
    var faceNormal [3]float64
    faceNormal[0] = vectorP[1]*vectorQ[2] - vectorP[2]*vectorQ[1]
    faceNormal[1] = -(vectorP[0]*vectorQ[2] - vectorP[2]*vectorQ[0])
    faceNormal[2] = vectorP[0]*vectorQ[1] - vectorP[1]*vectorQ[0]
    // normalize it!
    scaleFactor = 1/math.Sqrt(faceNormal[0]*faceNormal[0] +
                              faceNormal[1]*faceNormal[1] +
                              faceNormal[2]*faceNormal[2])
    faceNormal[0] = faceNormal[0] * scaleFactor
    faceNormal[1] = faceNormal[1] * scaleFactor
    faceNormal[2] = faceNormal[2] * scaleFactor

    // they should be parallel (not antiparrallel!)
    //  ie, components should subract to zero, since unit vectors
    if !((faceNormal[0] - faceCenter[0])*(faceNormal[0] - faceCenter[0])>tolerance ||
         (faceNormal[1] - faceCenter[1])*(faceNormal[1] - faceCenter[1])>tolerance ||
         (faceNormal[2] - faceCenter[2])*(faceNormal[2] - faceCenter[2])>tolerance   ) {

        return false, nil
    }
    return true, nil
}<commit_msg>fix test logic so it actually does something<commit_after>package wingedGrid

import (
    "errors"
    //"fmt"
    "math"
    "log"
)
// This file contains functions for testing the expected data structure of
// any wingedGrid

// Returns whether the edge order from the vertex index matches that of the edges
// NOT YET IMPLEMENTED
func VertEdgesMatchEdgeOrder(theGrid WingedGrid, vertIndex int32) (bool, error) {

    return false, nil
}
// Returns whether the edge has its vertices ordered correctly,
// such that FirstVertexA it the first vertex encounter when clockwise traversing
// FaceA
func EdgeVertsInCorrectOrientation(theGrid WingedGrid, edgeIndex int32) (bool, error){
    var theEdge WingedEdge = theGrid.Edges[edgeIndex]
    var nextEdge WingedEdge = theGrid.Edges[theEdge.NextA]
    // Second clockwise vertex on theEdge should be the same as the first for
    // nextEdgeA for faceA, only need to test for one pair.
    // Check with second vertex for next edge incase the next edge is the wrong one
    if theEdge.FirstVertexB == nextEdge.FirstVertexA ||
       theEdge.FirstVertexB == nextEdge.FirstVertexB {
        return true, nil
    }
    return false, errors.New("This Edge has wrong order")
}
// Returns whether the edge order from the face index matches that of the edges
// taken from edge.next
func FaceEdgesMatchesOrderFromEdge(theGrid WingedGrid, faceIndex int32) (bool, error) {
    var theFace WingedFace = theGrid.Faces[faceIndex]
    if len(theFace.Edges) <= 2 {
        return false, errors.New("Face has too few edges.")
    }
    var currentEdge WingedEdge
    currentEdge = theGrid.Edges[theFace.Edges[len(theFace.Edges) - 1]]
    for i := 0; i < len(theFace.Edges); i++ {
        next, err := currentEdge.NextEdgeForFace(faceIndex)
        if err != nil {
            return false, err
        }
        if next == theFace.Edges[i] {
            currentEdge = theGrid.Edges[next]
        } else {
            return false, nil
        }
    }
    return true, nil
}
// Returns whether a the vertices of a face are within square tolerance distance
// from the plane of the first three vertices
// NOT FULLY IMPLEMENTED
func FaceVerticesPlanar(theGrid WingedGrid, faceIndex int32, tolerance float64) (bool) {
    var theFace WingedFace
    theFace = theGrid.Faces[faceIndex]
    // always true for three or fewer points
    if len(theFace.Edges) <= 3 {
        return true
    }

    return false
}
// Returns whether a face has an orientation in the same direction as the
// position of its center, within the square tolerance.
// This test is only applicable to WingedGrids that are expected to aproximate a
// sphere.
func FaceOrientation(theGrid WingedGrid, faceIndex int32, tolerance float64) (bool, error) {
    var err error
    var theFace WingedFace = theGrid.Faces[faceIndex]
    // find center of face
    var vectorP, vectorQ, faceCenter [3]float64
    var count float64
    for _, edgeIndex := range theFace.Edges {
        var vertex WingedVertex
        var vertexIndex int32
        vertexIndex, err = theGrid.Edges[edgeIndex].FirstVertexForFace(faceIndex)
        if err != nil {
            return false, nil
        }
        vertex = theGrid.Vertices[vertexIndex]
        faceCenter[0] = faceCenter[0] + vertex.Coords[0]
        faceCenter[1] = faceCenter[1] + vertex.Coords[1]
        faceCenter[2] = faceCenter[2] + vertex.Coords[2]
        count = count + 1
    }
    faceCenter[0] = faceCenter[0] / count
    faceCenter[1] = faceCenter[1] / count
    faceCenter[2] = faceCenter[2] / count

    // simply use the first two edges, vectors away from their shared vertex
    // from these we get the face normal vector
    var edgeP, edgeQ WingedEdge
    edgeP = theGrid.Edges[theFace.Edges[0]]
    edgeQ = theGrid.Edges[theFace.Edges[1]]

    // get and check we find valid vertices
    var startVertexIndex, endVertexIndex int32
    endVertexIndex, err = edgeP.FirstVertexForFace(faceIndex)
    if err != nil {
        return false, err
    }
    startVertexIndex, err = edgeP.SecondVertexForFace(faceIndex)
    if err != nil {
        return false, err
    }
    // get vector along edge P
    vectorP[0] = theGrid.Vertices[endVertexIndex].Coords[0] -
                    theGrid.Vertices[startVertexIndex].Coords[0]
    vectorP[1] = theGrid.Vertices[endVertexIndex].Coords[1] -
                    theGrid.Vertices[startVertexIndex].Coords[1]
    vectorP[2] = theGrid.Vertices[endVertexIndex].Coords[2] -
                    theGrid.Vertices[startVertexIndex].Coords[2]

    // get and check we find valid vertices
    endVertexIndex, err = edgeQ.SecondVertexForFace(faceIndex)
    if err != nil {
        return false, err
    }
    startVertexIndex, err = edgeQ.FirstVertexForFace(faceIndex)
    if err != nil {
        return false, err
    }
    // get vector along edge Q
    vectorQ[0] = theGrid.Vertices[endVertexIndex].Coords[0] -
                    theGrid.Vertices[startVertexIndex].Coords[0]
    vectorQ[1] = theGrid.Vertices[endVertexIndex].Coords[1] -
                    theGrid.Vertices[startVertexIndex].Coords[1]
    vectorQ[2] = theGrid.Vertices[endVertexIndex].Coords[2] -
                    theGrid.Vertices[startVertexIndex].Coords[2]

    // replace with cross product at some point, copying here from original
    // icosahedron test where I simply subtracted normalized components
    var scaleFactor float64
    // normalize the center to unit vector
    scaleFactor = 1/math.Sqrt(faceCenter[0]*faceCenter[0] +
                              faceCenter[1]*faceCenter[1] +
                              faceCenter[2]*faceCenter[2])
    faceCenter[0] = faceCenter[0] * scaleFactor
    faceCenter[1] = faceCenter[1] * scaleFactor
    faceCenter[2] = faceCenter[2] * scaleFactor

    // find normal to face, cross product!
    var faceNormal [3]float64
    faceNormal[0] = vectorP[1]*vectorQ[2] - vectorP[2]*vectorQ[1]
    faceNormal[1] = -(vectorP[0]*vectorQ[2] - vectorP[2]*vectorQ[0])
    faceNormal[2] = vectorP[0]*vectorQ[1] - vectorP[1]*vectorQ[0]
    // normalize it!
    scaleFactor = 1/math.Sqrt(faceNormal[0]*faceNormal[0] +
                              faceNormal[1]*faceNormal[1] +
                              faceNormal[2]*faceNormal[2])
    faceNormal[0] = faceNormal[0] * scaleFactor
    faceNormal[1] = faceNormal[1] * scaleFactor
    faceNormal[2] = faceNormal[2] * scaleFactor
    log.Printf("Center: %v Normal: %v",faceCenter,faceNormal)
    // they should be parallel (not antiparrallel!)
    //  ie, components should subract to zero, since unit vectors
    if ( (faceNormal[0] - faceCenter[0])*(faceNormal[0] - faceCenter[0])>tolerance ||
         (faceNormal[1] - faceCenter[1])*(faceNormal[1] - faceCenter[1])>tolerance ||
         (faceNormal[2] - faceCenter[2])*(faceNormal[2] - faceCenter[2])>tolerance   ) {

        return false, nil
    }
    return true, nil
}<|endoftext|>


<commit_before>package mocks

import (
	"fmt"
	"reflect"

	"github.com/konjoot/reeky/errors"
)

type ResourceMock struct {
	F        interface{}
	V        interface{}
	Invalid  bool
	NotFound bool

	created  bool
	findedBy string
}

func (r *ResourceMock) Created() bool {
	return r.created
}

func (r *ResourceMock) BindedWith(f interface{}) (binded bool) {
	var (
		name string
		val  interface{}
	)

	if _, ok := f.(map[string]string); !ok {
		return
	}

	rForm := reflect.ValueOf(r.F).Elem()

	for name, val = range f.(map[string]string) {
		if field := rForm.FieldByName(name); field.IsValid() && field.Interface() == val {
			continue
		}
		return
	}

	return true
}

func (r *ResourceMock) String() string {
	return fmt.Sprintf("ResourceMock{Invalid: %t, created: %t, findedBy: %#v, Form: %#v, View: %#v}", r.Invalid, r.created, r.findedBy, r.F, r.V)
}

func (r *ResourceMock) Url() string {
	return "some/url"
}

func (r *ResourceMock) Form() interface{} {
	return r.F
}

func (r *ResourceMock) Save() (e error) {
	if r.Invalid {
		e = errors.NewConflictError()
		return
	}

	r.created = true

	return
}

func (r *ResourceMock) FindedBy() string {
	return r.findedBy
}

func (r *ResourceMock) Finded() bool {
	return r.findedBy != ""
}
<commit_msg>now ResourceMock implements Finder and Viewer interfaces<commit_after>package mocks

import (
	"fmt"
	"reflect"

	"github.com/konjoot/reeky/errors"

	i "github.com/konjoot/reeky/interfaces"
)

type ResourceMock struct {
	F        interface{}
	V        interface{}
	Invalid  bool
	NotFound bool

	created  bool
	findedBy string
}

func (r *ResourceMock) Created() bool {
	return r.created
}

func (r *ResourceMock) BindedWith(f interface{}) (binded bool) {
	var (
		name string
		val  interface{}
	)

	if _, ok := f.(map[string]string); !ok {
		return
	}

	rForm := reflect.ValueOf(r.F).Elem()

	for name, val = range f.(map[string]string) {
		if field := rForm.FieldByName(name); field.IsValid() && field.Interface() == val {
			continue
		}
		return
	}

	return true
}

func (r *ResourceMock) String() string {
	return fmt.Sprintf("ResourceMock{Invalid: %t, created: %t, findedBy: %#v, Form: %#v, View: %#v}", r.Invalid, r.created, r.findedBy, r.F, r.V)
}

func (r *ResourceMock) Url() string {
	return "some/url"
}

func (r *ResourceMock) Form() interface{} {
	return r.F
}

func (r *ResourceMock) Save() (e error) {
	if r.Invalid {
		e = errors.NewConflictError()
		return
	}

	r.created = true

	return
}

func (r *ResourceMock) FindedBy() string {
	return r.findedBy
}

func (r *ResourceMock) Finded() bool {
	return r.findedBy != ""
}

func (r *ResourceMock) Find(id string) (i.Viewer, error) {
	r.findedBy = id
	return r, nil
}

func (r *ResourceMock) View() interface{} {
	return r.V
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"github.com/intervention-engine/fhir/models"
	"gopkg.in/mgo.v2/bson"
)

func ObservationIndexHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	var result []models.Observation
	c := Database.C("observations")
	iter := c.Find(nil).Limit(100).Iter()
	err := iter.All(&result)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	var observationEntryList []models.ObservationBundleEntry
	for _, observation := range result {
		var entry models.ObservationBundleEntry
		entry.Title = "Observation " + observation.Id
		entry.Id = observation.Id
		entry.Content = observation
		observationEntryList = append(observationEntryList, entry)
	}

	var bundle models.ObservationBundle
	bundle.Type = "Bundle"
	bundle.Title = "Observation Index"
	bundle.Id = bson.NewObjectId().Hex()
	bundle.Updated = time.Now()
	bundle.TotalResults = len(result)
	bundle.Entry = observationEntryList

	log.Println("Setting observation search context")
	context.Set(r, "Observation", result)
	context.Set(r, "Resource", "Observation")
	context.Set(r, "Action", "search")

	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	json.NewEncoder(rw).Encode(bundle)
}

func LoadObservation(r *http.Request) (*models.Observation, error) {
	var id bson.ObjectId

	idString := mux.Vars(r)["id"]
	if bson.IsObjectIdHex(idString) {
		id = bson.ObjectIdHex(idString)
	} else {
		return nil, errors.New("Invalid id")
	}

	c := Database.C("observations")
	result := models.Observation{}
	err := c.Find(bson.M{"_id": id.Hex()}).One(&result)
	if err != nil {
		return nil, err
	}

	log.Println("Setting observation read context")
	context.Set(r, "Observation", result)
	context.Set(r, "Resource", "Observation")
	return &result, nil
}

func ObservationShowHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	context.Set(r, "Action", "read")
	_, err := LoadObservation(r)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}
	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	json.NewEncoder(rw).Encode(context.Get(r, "Observation"))
}

func ObservationCreateHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	decoder := json.NewDecoder(r.Body)
	observation := &models.Observation{}
	err := decoder.Decode(observation)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	c := Database.C("observations")
	i := bson.NewObjectId()
	observation.Id = i.Hex()
	err = c.Insert(observation)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	log.Println("Setting observation create context")
	context.Set(r, "Observation", observation)
	context.Set(r, "Resource", "Observation")
	context.Set(r, "Action", "create")

	host, err := os.Hostname()
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	rw.Header().Add("Location", "http://"+host+":3001/Observation/"+i.Hex())
}

func ObservationUpdateHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

	var id bson.ObjectId

	idString := mux.Vars(r)["id"]
	if bson.IsObjectIdHex(idString) {
		id = bson.ObjectIdHex(idString)
	} else {
		http.Error(rw, "Invalid id", http.StatusBadRequest)
	}

	decoder := json.NewDecoder(r.Body)
	observation := &models.Observation{}
	err := decoder.Decode(observation)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	c := Database.C("observations")
	observation.Id = id.Hex()
	err = c.Update(bson.M{"_id": id.Hex()}, observation)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	log.Println("Setting observation update context")
	context.Set(r, "Observation", observation)
	context.Set(r, "Resource", "Observation")
	context.Set(r, "Action", "update")
}

func ObservationDeleteHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	var id bson.ObjectId

	idString := mux.Vars(r)["id"]
	if bson.IsObjectIdHex(idString) {
		id = bson.ObjectIdHex(idString)
	} else {
		http.Error(rw, "Invalid id", http.StatusBadRequest)
	}

	c := Database.C("observations")

	err := c.Remove(bson.M{"_id": id.Hex()})
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	log.Println("Setting observation delete context")
	context.Set(r, "Observation", id.Hex())
	context.Set(r, "Resource", "Observation")
	context.Set(r, "Action", "delete")
}
<commit_msg>initial implementation of search for Observations<commit_after>package server

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"os"
	"time"
	"strings"

	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"github.com/intervention-engine/fhir/models"
	"gopkg.in/mgo.v2/bson"
)

func ObservationIndexHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	var result []models.Observation
	c := Database.C("observations")

	r.ParseForm()
	if (len(r.Form) == 0) {
		iter := c.Find(nil).Limit(100).Iter()
		err := iter.All(&result)
		if err != nil {
			http.Error(rw, err.Error(), http.StatusInternalServerError)
		}
	} else {
		for key, value := range r.Form {
			splitKey := strings.Split(key, ":")
			if (len(splitKey) > 1) && (splitKey[0] == "subject") {
				subjectType := splitKey[1]
				//TODO:figure out what hostname to use here depending on whether reference is internal or external
				referenceString := "http://localhost:3001/"+subjectType+"/"+value[0]
				err := c.Find(bson.M{"subject.reference": referenceString}).All(&result)
				if err != nil {
					http.Error(rw, err.Error(), http.StatusInternalServerError)
				}
			}
		}
	}

	var observationEntryList []models.ObservationBundleEntry
	for _, observation := range result {
		var entry models.ObservationBundleEntry
		entry.Title = "Observation " + observation.Id
		entry.Id = observation.Id
		entry.Content = observation
		observationEntryList = append(observationEntryList, entry)
	}

	var bundle models.ObservationBundle
	bundle.Type = "Bundle"
	bundle.Title = "Observation Index"
	bundle.Id = bson.NewObjectId().Hex()
	bundle.Updated = time.Now()
	bundle.TotalResults = len(result)
	bundle.Entry = observationEntryList

	log.Println("Setting observation search context")
	context.Set(r, "Observation", result)
	context.Set(r, "Resource", "Observation")
	context.Set(r, "Action", "search")

	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	json.NewEncoder(rw).Encode(bundle)
}

func LoadObservation(r *http.Request) (*models.Observation, error) {
	var id bson.ObjectId

	idString := mux.Vars(r)["id"]
	if bson.IsObjectIdHex(idString) {
		id = bson.ObjectIdHex(idString)
	} else {
		return nil, errors.New("Invalid id")
	}

	c := Database.C("observations")
	result := models.Observation{}
	err := c.Find(bson.M{"_id": id.Hex()}).One(&result)
	if err != nil {
		return nil, err
	}

	log.Println("Setting observation read context")
	context.Set(r, "Observation", result)
	context.Set(r, "Resource", "Observation")
	return &result, nil
}

func ObservationShowHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	context.Set(r, "Action", "read")
	_, err := LoadObservation(r)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}
	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	json.NewEncoder(rw).Encode(context.Get(r, "Observation"))
}

func ObservationCreateHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	decoder := json.NewDecoder(r.Body)
	observation := &models.Observation{}
	err := decoder.Decode(observation)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	c := Database.C("observations")
	i := bson.NewObjectId()
	observation.Id = i.Hex()
	err = c.Insert(observation)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	log.Println("Setting observation create context")
	context.Set(r, "Observation", observation)
	context.Set(r, "Resource", "Observation")
	context.Set(r, "Action", "create")

	host, err := os.Hostname()
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	rw.Header().Add("Location", "http://"+host+":3001/Observation/"+i.Hex())
}

func ObservationUpdateHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

	var id bson.ObjectId

	idString := mux.Vars(r)["id"]
	if bson.IsObjectIdHex(idString) {
		id = bson.ObjectIdHex(idString)
	} else {
		http.Error(rw, "Invalid id", http.StatusBadRequest)
	}

	decoder := json.NewDecoder(r.Body)
	observation := &models.Observation{}
	err := decoder.Decode(observation)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	c := Database.C("observations")
	observation.Id = id.Hex()
	err = c.Update(bson.M{"_id": id.Hex()}, observation)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
	}

	log.Println("Setting observation update context")
	context.Set(r, "Observation", observation)
	context.Set(r, "Resource", "Observation")
	context.Set(r, "Action", "update")
}

func ObservationDeleteHandler(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	var id bson.ObjectId

	idString := mux.Vars(r)["id"]
	if bson.IsObjectIdHex(idString) {
		id = bson.ObjectIdHex(idString)
	} else {
		http.Error(rw, "Invalid id", http.StatusBadRequest)
	}

	c := Database.C("observations")

	err := c.Remove(bson.M{"_id": id.Hex()})
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	log.Println("Setting observation delete context")
	context.Set(r, "Observation", id.Hex())
	context.Set(r, "Resource", "Observation")
	context.Set(r, "Action", "delete")
}
<|endoftext|>


<commit_before>package consul

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

	"github.com/42wim/registrator-work/bridge"
	consulapi "github.com/hashicorp/consul/api"
)

const DefaultInterval = "10s"

func init() {
	bridge.Register(new(Factory), "consul")
}

func (r *ConsulAdapter) interpolateService(script string, service *bridge.Service) string {
	withIp := strings.Replace(script, "$SERVICE_IP", service.Origin.HostIP, -1)
	withPort := strings.Replace(withIp, "$SERVICE_PORT", service.Origin.HostPort, -1)
	return withPort
}

type Factory struct{}

func (f *Factory) New(uri *url.URL) bridge.RegistryAdapter {
	config := consulapi.DefaultConfig()
	if uri.Host != "" {
		config.Address = uri.Host
	}
	client, err := consulapi.NewClient(config)
	if err != nil {
		log.Fatal("consul: ", uri.Scheme)
	}
	return &ConsulAdapter{client: client}
}

type ConsulAdapter struct {
	client *consulapi.Client
}

// Ping will try to connect to consul by attempting to retrieve the current leader.
func (r *ConsulAdapter) Ping() error {
	status := r.client.Status()
	leader, err := status.Leader()
	if err != nil {
		return err
	}
	log.Println("consul: current leader ", leader)

	return nil
}

func (r *ConsulAdapter) Register(service *bridge.Service) error {
	registration := new(consulapi.AgentServiceRegistration)
	registration.ID = service.ID
	registration.Name = service.Name
	registration.Port = service.Port
	registration.Tags = service.Tags
	registration.Address = service.IP
	registration.Check = r.buildCheck(service)
	return r.client.Agent().ServiceRegister(registration)
}

func (r *ConsulAdapter) buildCheck(service *bridge.Service) *consulapi.AgentServiceCheck {
	check := new(consulapi.AgentServiceCheck)
	if path := service.Attrs["check_http"]; path != "" {
		if net.ParseIP(service.IP).To4() == nil && service.IP[0] != '[' { // verify if its an ipv6 address
			service.IP = fmt.Sprintf("[%s]", service.IP)
		}
		check.HTTP = fmt.Sprintf("http://%s:%d%s", service.IP, service.Port, path)
		if timeout := service.Attrs["check_timeout"]; timeout != "" {
			check.Timeout = timeout
		}
	} else if cmd := service.Attrs["check_cmd"]; cmd != "" {
		check.Script = fmt.Sprintf("check-cmd %s %s %s", service.Origin.ContainerID[:12], service.Origin.ExposedPort, cmd)
	} else if script := service.Attrs["check_script"]; script != "" {
		check.Script = r.interpolateService(script, service)
	} else if ttl := service.Attrs["check_ttl"]; ttl != "" {
		check.TTL = ttl
	} else {
		return nil
	}
	if check.Script != "" || check.HTTP != "" {
		if interval := service.Attrs["check_interval"]; interval != "" {
			check.Interval = interval
		} else {
			check.Interval = DefaultInterval
		}
	}
	return check
}

func (r *ConsulAdapter) Deregister(service *bridge.Service) error {
	return r.client.Agent().ServiceDeregister(service.ID)
}

func (r *ConsulAdapter) Refresh(service *bridge.Service) error {
	return nil
}

func (r *ConsulAdapter) Services() ([]*bridge.Service, error) {
	services, err := r.client.Agent().Services()
	if err != nil {
		return []*bridge.Service{}, err
	}
	out := make([]*bridge.Service, len(services))
	i := 0
	for _, v := range services {
		s := &bridge.Service{
			ID:   v.ID,
			Name: v.Service,
			Port: v.Port,
			Tags: v.Tags,
			IP:   v.Address,
		}
		out[i] = s
		i++
	}
	return out, nil
}
<commit_msg>Fix service.IP overwrite with http_check<commit_after>package consul

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

	"github.com/42wim/registrator-work/bridge"
	consulapi "github.com/hashicorp/consul/api"
)

const DefaultInterval = "10s"

func init() {
	bridge.Register(new(Factory), "consul")
}

func (r *ConsulAdapter) interpolateService(script string, service *bridge.Service) string {
	withIp := strings.Replace(script, "$SERVICE_IP", service.Origin.HostIP, -1)
	withPort := strings.Replace(withIp, "$SERVICE_PORT", service.Origin.HostPort, -1)
	return withPort
}

type Factory struct{}

func (f *Factory) New(uri *url.URL) bridge.RegistryAdapter {
	config := consulapi.DefaultConfig()
	if uri.Host != "" {
		config.Address = uri.Host
	}
	client, err := consulapi.NewClient(config)
	if err != nil {
		log.Fatal("consul: ", uri.Scheme)
	}
	return &ConsulAdapter{client: client}
}

type ConsulAdapter struct {
	client *consulapi.Client
}

// Ping will try to connect to consul by attempting to retrieve the current leader.
func (r *ConsulAdapter) Ping() error {
	status := r.client.Status()
	leader, err := status.Leader()
	if err != nil {
		return err
	}
	log.Println("consul: current leader ", leader)

	return nil
}

func (r *ConsulAdapter) Register(service *bridge.Service) error {
	registration := new(consulapi.AgentServiceRegistration)
	registration.ID = service.ID
	registration.Name = service.Name
	registration.Port = service.Port
	registration.Tags = service.Tags
	registration.Address = service.IP
	registration.Check = r.buildCheck(service)
	return r.client.Agent().ServiceRegister(registration)
}

func (r *ConsulAdapter) buildCheck(service *bridge.Service) *consulapi.AgentServiceCheck {
	check := new(consulapi.AgentServiceCheck)
	if path := service.Attrs["check_http"]; path != "" {
		if net.ParseIP(service.IP).To4() == nil { // verify if its an ipv6 address
			check.HTTP = fmt.Sprintf("http://%s:%d%s", fmt.Sprintf("[%s]", service.IP), service.Port, path)
		}
		if timeout := service.Attrs["check_timeout"]; timeout != "" {
			check.Timeout = timeout
		}
	} else if cmd := service.Attrs["check_cmd"]; cmd != "" {
		check.Script = fmt.Sprintf("check-cmd %s %s %s", service.Origin.ContainerID[:12], service.Origin.ExposedPort, cmd)
	} else if script := service.Attrs["check_script"]; script != "" {
		check.Script = r.interpolateService(script, service)
	} else if ttl := service.Attrs["check_ttl"]; ttl != "" {
		check.TTL = ttl
	} else {
		return nil
	}
	if check.Script != "" || check.HTTP != "" {
		if interval := service.Attrs["check_interval"]; interval != "" {
			check.Interval = interval
		} else {
			check.Interval = DefaultInterval
		}
	}
	return check
}

func (r *ConsulAdapter) Deregister(service *bridge.Service) error {
	return r.client.Agent().ServiceDeregister(service.ID)
}

func (r *ConsulAdapter) Refresh(service *bridge.Service) error {
	return nil
}

func (r *ConsulAdapter) Services() ([]*bridge.Service, error) {
	services, err := r.client.Agent().Services()
	if err != nil {
		return []*bridge.Service{}, err
	}
	out := make([]*bridge.Service, len(services))
	i := 0
	for _, v := range services {
		s := &bridge.Service{
			ID:   v.ID,
			Name: v.Service,
			Port: v.Port,
			Tags: v.Tags,
			IP:   v.Address,
		}
		out[i] = s
		i++
	}
	return out, nil
}
<|endoftext|>


<commit_before>package node

import (
	"regexp"
	"time"

	"github.com/MG-RAST/Shock/shock-server/cache"
	"github.com/MG-RAST/Shock/shock-server/conf"
	"github.com/MG-RAST/Shock/shock-server/logger"
	"github.com/MG-RAST/Shock/shock-server/node/locker"
	"gopkg.in/mgo.v2/bson"
)

var (
	Ttl         *NodeReaper
	ExpireRegex = regexp.MustCompile(`^(\d+)(M|H|D)$`)
)

func InitReaper() {
	Ttl = NewNodeReaper()
}

type NodeReaper struct{}

func NewNodeReaper() *NodeReaper {
	return &NodeReaper{}
}

func (nr *NodeReaper) Handle() {
	waitDuration := time.Duration(conf.EXPIRE_WAIT) * time.Minute
MainLoop:
	for {

		// sleep
		time.Sleep(waitDuration)
		// query to get expired nodes
		nodes := Nodes{}
		query := nr.getQuery()
		nodes.GetAll(query)

	NodeLoop:
		// loop thru all nodes
		for _, n := range nodes {
			logger.Infof("Deleting expired node: %s", n.Id)
			// delete expired nodes
			if err := n.Delete(); err != nil {
				err_msg := "err:@node_delete: " + err.Error()
				logger.Error(err_msg)
			}
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************

			// write info for node migration
			logger.Debug(1, "writing info for node migration: %s", n.Id)
			// CODE MISSING HERE FOR WRITING FILES WITH INFO FOR MIGRATION
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//	for this node check all locations to check if node Data files can be erased
			for _, loc := range n.Locations {
				var counter int = 0 // we need at least N locations before we can erase data files on local disk

				// delete only if other locations exist
				locObj, ok := conf.LocationsMap[loc.ID]

				if !ok {
					logger.Errorf("(Reaper-->FileReaper) location %s is not defined in this server instance \n ", loc)
					continue
				}
				//fmt.Printf("(Reaper-->FileReaper) locObj.Persistent =  %b  \n ", locObj.Persistent)
				if locObj.Persistent == true {
					logger.Debug(2, "(Reaper-->FileReaper) has remote Location (%s) removing from Data: %s", loc.ID, n.Id)
					counter++ // increment counter
				}
				if counter >= conf.MIN_REPLICA_COUNT {
					err := n.DeleteFiles() // delete all data files for node in PATH_DATA NOTE: this is different from PATH_CACHE
					if err != nil {
						logger.Errorf("(Reaper-->FileReaper) files for node %s could not be deleted (Err: %s) ", n.Id, err.Error())
						continue
					}
					continue NodeLoop
					// the innermost loop
				}
				///

			}
			// garbage collection: remove old nodes from Lockers, value is hours old
			locker.NodeLockMgr.RemoveOld(1)
			locker.FileLockMgr.RemoveOld(6)
			locker.IndexLockMgr.RemoveOld(6)

			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************

			// we do not start deletings files if we are not in cache mode
			// we might want to change this, if we are in shock-migrate or we do not have a cache_path we skip this
			if conf.PATH_CACHE == "" {
				continue MainLoop
			}
		CacheMapLoop:
			// start a FILE REAPER that loops thru CacheMap[*]
			for ID := range cache.CacheMap {

				logger.Debug(3, "(Reaper-->FileReaper) checking %s in cache\n", ID)

				now := time.Now()
				lru := cache.CacheMap[ID].Access
				diff := now.Sub(lru)

				// we use a very simple scheme for caching initially (file not used for 1 day)
				if diff.Hours() < float64(conf.CACHE_TTL) {
					logger.Debug(3, "Reaper-->FileReaper) not deleting %s from cache it was last accessed %s hours ago\n", ID, diff.Hours())
					continue CacheMapLoop
				}

				n, err := Load(ID)
				if err != nil {
					logger.Debug(1, "(Reaper-->FileReaper) Cannot access CacheMapItem[%s] (%s)", ID, err.Error())
					continue CacheMapLoop
				}
				cache.Remove(ID)

				logger.Errorf("(Reaper-->FileReaper) cannot delete %s from cache [This should not happen!!]", ID)
			}
		}
	}
	return
}

func (nr *NodeReaper) getQuery() (query bson.M) {
	hasExpire := bson.M{"expiration": bson.M{"$exists": true}}   // has the field
	toExpire := bson.M{"expiration": bson.M{"$ne": time.Time{}}} // value has been set, not default
	isExpired := bson.M{"expiration": bson.M{"$lt": time.Now()}} // value is too old
	query = bson.M{"$and": []bson.M{hasExpire, toExpire, isExpired}}
	return
}
<commit_msg>restructured reaper<commit_after>package node

import (
	"regexp"
	"time"

	"github.com/MG-RAST/Shock/shock-server/cache"
	"github.com/MG-RAST/Shock/shock-server/conf"
	"github.com/MG-RAST/Shock/shock-server/logger"
	"github.com/MG-RAST/Shock/shock-server/node/locker"
	"gopkg.in/mgo.v2/bson"
)

var (
	Ttl         *NodeReaper
	ExpireRegex = regexp.MustCompile(`^(\d+)(M|H|D)$`)
)

func InitReaper() {
	Ttl = NewNodeReaper()
}

type NodeReaper struct{}

func NewNodeReaper() *NodeReaper {
	return &NodeReaper{}
}

func (nr *NodeReaper) Handle() {
	waitDuration := time.Duration(conf.EXPIRE_WAIT) * time.Minute
MainLoop:
	for {

		// sleep
		time.Sleep(waitDuration)
		// query to get expired nodes
		nodes := Nodes{}
		query := nr.getQuery()
		nodes.GetAll(query)

	NodeLoop:
		// loop thru all nodes
		for _, n := range nodes {
			logger.Infof("Deleting expired node: %s", n.Id)
			// delete expired nodes
			if err := n.Delete(); err != nil {
				err_msg := "err:@node_delete: " + err.Error()
				logger.Error(err_msg)
			}
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************

			// write info for node migration
			logger.Debug(3, "writing info for node migration: %s", n.Id)
			// CODE MISSING HERE FOR WRITING FILES WITH INFO FOR MIGRATION

			// check if we want to migrate nodes
			if conf.NODE_MIGRATION != true {
				logger.Debug(3, "not starting node migration")
				continue MainLoop
			}

			// CODE for data migration here...

			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//	for this node check all locations to check if node Data files can be erased

			// check global flag to see if we want to remove local data
			if conf.NODE_DATA_REMOVAL == true {
				for _, loc := range n.Locations {
					var counter int = 0 // we need at least N locations before we can erase data files on local disk
					// delete only if other locations exist
					locObj, ok := conf.LocationsMap[loc.ID]
					if !ok {
						logger.Errorf("(Reaper-->FileReaper) location %s is not defined in this server instance \n ", loc)
						continue
					}
					//fmt.Printf("(Reaper-->FileReaper) locObj.Persistent =  %b  \n ", locObj.Persistent)
					if locObj.Persistent == true {
						logger.Debug(2, "(Reaper-->FileReaper) has remote Location (%s) removing from Data: %s", loc.ID, n.Id)
						counter++ // increment counter
					}
					if counter >= conf.MIN_REPLICA_COUNT {
						err := n.DeleteFiles() // delete all data files for node in PATH_DATA NOTE: this is different from PATH_CACHE
						if err != nil {
							logger.Errorf("(Reaper-->FileReaper) files for node %s could not be deleted (Err: %s) ", n.Id, err.Error())
							continue
						}
						continue NodeLoop
						// the innermost loop
					}
					///
				}
			}
			// garbage collection: remove old nodes from Lockers, value is hours old
			locker.NodeLockMgr.RemoveOld(1)
			locker.FileLockMgr.RemoveOld(6)
			locker.IndexLockMgr.RemoveOld(6)

			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************
			//  ************************ ************************ ************************ ************************ ************************ ************************ ************************ ************************

			// we do not start deletings files if we are not in cache mode
			// we might want to change this, if we are in shock-migrate or we do not have a cache_path we skip this
			if conf.PATH_CACHE == "" {
				continue MainLoop
			}
		CacheMapLoop:
			// start a FILE REAPER that loops thru CacheMap[*]
			for ID := range cache.CacheMap {

				logger.Debug(3, "(Reaper-->FileReaper) checking %s in cache\n", ID)

				now := time.Now()
				lru := cache.CacheMap[ID].Access
				diff := now.Sub(lru)

				// we use a very simple scheme for caching initially (file not used for 1 day)
				if diff.Hours() < float64(conf.CACHE_TTL) {
					logger.Debug(3, "Reaper-->FileReaper) not deleting %s from cache it was last accessed %s hours ago\n", ID, diff.Hours())
					continue CacheMapLoop
				}

				n, err := Load(ID)
				if err != nil {
					logger.Debug(1, "(Reaper-->FileReaper) Cannot access CacheMapItem[%s] (%s)", ID, err.Error())
					continue CacheMapLoop
				}
				cache.Remove(ID)

				logger.Errorf("(Reaper-->FileReaper) cannot delete %s from cache [This should not happen!!]", ID)
			}
		}
	}
	return
}

func (nr *NodeReaper) getQuery() (query bson.M) {
	hasExpire := bson.M{"expiration": bson.M{"$exists": true}}   // has the field
	toExpire := bson.M{"expiration": bson.M{"$ne": time.Time{}}} // value has been set, not default
	isExpired := bson.M{"expiration": bson.M{"$lt": time.Now()}} // value is too old
	query = bson.M{"$and": []bson.M{hasExpire, toExpire, isExpired}}
	return
}
<|endoftext|>


<commit_before>package classpath

import (
	. "jvmgo/testing"
	"strings"
	"testing"
)

func TestParseClassPath_Empty(t *testing.T) {
	cp := ParseClassPath("").ccpe
	AssertEquals(0, len(cp.entries))
}

func TestParseClassPath_OneDir(t *testing.T) {
	dirs := []string{".", "abc", "a/b/c"}
	for _, dir := range dirs {
		cp := ParseClassPath(dir).ccpe
		AssertEquals(1, len(cp.entries))
	}
}

func TestParseClassPath_List(t *testing.T) {
	pathList := []string{".", "rt.jar"}
	pathStr := strings.Join(pathList, pathListSeparator)
	cp := ParseClassPath(pathStr).ccpe
	AssertEquals(2, len(cp.entries))
}
<commit_msg>fix tests<commit_after>package classpath

import (
	. "jvmgo/testing"
	"strings"
	"testing"
)

func TestParseClassPath_Empty(t *testing.T) {
	cp := ParseClassPath("").compoundEntry
	AssertEquals(0, len(cp.entries))
}

func TestParseClassPath_OneDir(t *testing.T) {
	dirs := []string{".", "abc", "a/b/c"}
	for _, dir := range dirs {
		cp := ParseClassPath(dir).compoundEntry
		AssertEquals(1, len(cp.entries))
	}
}

func TestParseClassPath_List(t *testing.T) {
	pathList := []string{".", "rt.jar"}
	pathStr := strings.Join(pathList, pathListSeparator)
	cp := ParseClassPath(pathStr).compoundEntry
	AssertEquals(2, len(cp.entries))
}
<|endoftext|>


<commit_before>package timeShift

import (
	"context"
	"fmt"

	"github.com/lomik/zapwriter"
	"github.com/spf13/viper"
	"go.uber.org/zap"

	"github.com/go-graphite/carbonapi/expr/helper"
	"github.com/go-graphite/carbonapi/expr/interfaces"
	"github.com/go-graphite/carbonapi/expr/types"
	"github.com/go-graphite/carbonapi/pkg/parser"
)

type timeShift struct {
	interfaces.FunctionBase

	config timeShiftConfig
}

func GetOrder() interfaces.Order {
	return interfaces.Any
}

type timeShiftConfig struct {
	ResetEndDefaultValue *bool
}

func New(configFile string) []interfaces.FunctionMetadata {
	logger := zapwriter.Logger("functionInit").With(zap.String("function", "timeShift"))
	res := make([]interfaces.FunctionMetadata, 0)
	f := &timeShift{}
	functions := []string{"timeShift"}
	for _, n := range functions {
		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
	}

	cfg := timeShiftConfig{}
	v := viper.New()
	v.SetConfigFile(configFile)
	err := v.ReadInConfig()
	if err != nil {
		logger.Info("failed to read config file, using default",
			zap.Error(err),
		)
	} else {
		err = v.Unmarshal(&cfg)
		if err != nil {
			logger.Fatal("failed to parse config",
				zap.Error(err),
			)
			return nil
		}

		f.config = cfg
	}

	if cfg.ResetEndDefaultValue == nil {
		// TODO(civil): Change default value in 0.15
		v := false
		f.config.ResetEndDefaultValue = &v
		logger.Warn("timeShift function in graphite-web have a default value for resetEnd set to true." +
			"carbonapi currently forces this to be false. This behavior will change in next major release (0.15)" +
			"to be compatible with graphite-web. Please change your dashboards to explicitly pass resetEnd parameter" +
			"or create a config file for this function that sets it to false." +
			"Please see https://github.com/go-graphite/carbonapi/blob/main/doc/configuration.md#example-for-timeshift")
	}

	return res
}

func firstTimeRangeMap(values map[parser.MetricRequest][]*types.MetricData) (int64, int64) {
	if len(values) == 0 {
		return 0, 0
	}
	for _, v := range values {
		if len(v) == 0 {
			return 0, 0
		}
		return v[0].StartTime, v[0].StopTime
	}
	return 0, 0 // make compiler happy, unreacheble
}

// timeShift(seriesList, timeShift, resetEnd=True)
func (f *timeShift) Do(ctx context.Context, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
	// FIXME(civil): support alignDst

	offs, err := e.GetIntervalArg(1, -1)
	if err != nil {
		return nil, err
	}

	resetEnd, err := e.GetBoolArgDefault(2, *f.config.ResetEndDefaultValue)
	if err != nil {
		return nil, err
	}

	arg, err := helper.GetSeriesArg(e.Args()[0], from+int64(offs), until+int64(offs), values)
	if err != nil {
		return nil, err
	}
	results := make([]*types.MetricData, 0, len(arg))

	for _, a := range arg {
		r := *a
		r.Name = fmt.Sprintf("timeShift(%s,'%d',%v)", a.Name, offs, resetEnd)
		r.StartTime = a.StartTime - int64(offs)
		r.StopTime = a.StopTime - int64(offs)
		if resetEnd && r.StopTime > until {
			r.StopTime = until
		}
		length := int((r.StopTime - r.StartTime) / r.StepTime)
		if length < 0 {
			continue
		}
		r.Values = r.Values[:length]
		results = append(results, &r)
	}

	return results, nil
}

// Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
func (f *timeShift) Description() map[string]types.FunctionDescription {
	return map[string]types.FunctionDescription{
		"timeShift": {
			Description: "Takes one metric or a wildcard seriesList, followed by a quoted string with the\nlength of time (See ``from / until`` in the render\\_api_ for examples of time formats).\n\nDraws the selected metrics shifted in time. If no sign is given, a minus sign ( - ) is\nimplied which will shift the metric back in time. If a plus sign ( + ) is given, the\nmetric will be shifted forward in time.\n\nWill reset the end date range automatically to the end of the base stat unless\nresetEnd is False. Example case is when you timeshift to last week and have the graph\ndate range set to include a time in the future, will limit this timeshift to pretend\nending at the current time. If resetEnd is False, will instead draw full range including\nfuture time.\n\nBecause time is shifted by a fixed number of seconds, comparing a time period with DST to\na time period without DST, and vice-versa, will result in an apparent misalignment. For\nexample, 8am might be overlaid with 7am. To compensate for this, use the alignDST option.\n\nUseful for comparing a metric against itself at a past periods or correcting data\nstored at an offset.\n\nExample:\n\n.. code-block:: none\n\n  &target=timeShift(Sales.widgets.largeBlue,\"7d\")\n  &target=timeShift(Sales.widgets.largeBlue,\"-7d\")\n  &target=timeShift(Sales.widgets.largeBlue,\"+1h\")",
			Function:    "timeShift(seriesList, timeShift, resetEnd=True, alignDST=False)",
			Group:       "Transform",
			Module:      "graphite.render.functions",
			Name:        "timeShift",
			Params: []types.FunctionParam{
				{
					Name:     "seriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "timeShift",
					Required: true,
					Suggestions: types.NewSuggestions(
						"1h",
						"6h",
						"12h",
						"1d",
						"2d",
						"7d",
						"14d",
						"30d",
					),
					Type: types.Interval,
				},
				{
					Default: types.NewSuggestion(*f.config.ResetEndDefaultValue),
					Name:    "resetEnd",
					Type:    types.Boolean,
				},
				/*
					{
						Default: types.NewSuggestion(false),
						Name:    "alignDst",
						Type:    types.Boolean,
					},
				*/
			},
		},
	}
}
<commit_msg>timeShift: cleanup<commit_after>package timeShift

import (
	"context"
	"fmt"

	"github.com/lomik/zapwriter"
	"github.com/spf13/viper"
	"go.uber.org/zap"

	"github.com/go-graphite/carbonapi/expr/helper"
	"github.com/go-graphite/carbonapi/expr/interfaces"
	"github.com/go-graphite/carbonapi/expr/types"
	"github.com/go-graphite/carbonapi/pkg/parser"
)

type timeShift struct {
	interfaces.FunctionBase

	config timeShiftConfig
}

func GetOrder() interfaces.Order {
	return interfaces.Any
}

type timeShiftConfig struct {
	ResetEndDefaultValue *bool
}

func New(configFile string) []interfaces.FunctionMetadata {
	logger := zapwriter.Logger("functionInit").With(zap.String("function", "timeShift"))
	res := make([]interfaces.FunctionMetadata, 0)
	f := &timeShift{}
	functions := []string{"timeShift"}
	for _, n := range functions {
		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
	}

	cfg := timeShiftConfig{}
	v := viper.New()
	v.SetConfigFile(configFile)
	err := v.ReadInConfig()
	if err != nil {
		logger.Info("failed to read config file, using default",
			zap.Error(err),
		)
	} else {
		err = v.Unmarshal(&cfg)
		if err != nil {
			logger.Fatal("failed to parse config",
				zap.Error(err),
			)
			return nil
		}

		f.config = cfg
	}

	if cfg.ResetEndDefaultValue == nil {
		// TODO(civil): Change default value in 0.15
		v := false
		f.config.ResetEndDefaultValue = &v
		logger.Warn("timeShift function in graphite-web have a default value for resetEnd set to true." +
			"carbonapi currently forces this to be false. This behavior will change in next major release (0.15)" +
			"to be compatible with graphite-web. Please change your dashboards to explicitly pass resetEnd parameter" +
			"or create a config file for this function that sets it to false." +
			"Please see https://github.com/go-graphite/carbonapi/blob/main/doc/configuration.md#example-for-timeshift")
	}

	return res
}

// timeShift(seriesList, timeShift, resetEnd=True)
func (f *timeShift) Do(ctx context.Context, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
	// FIXME(civil): support alignDst

	offs, err := e.GetIntervalArg(1, -1)
	if err != nil {
		return nil, err
	}

	resetEnd, err := e.GetBoolArgDefault(2, *f.config.ResetEndDefaultValue)
	if err != nil {
		return nil, err
	}

	arg, err := helper.GetSeriesArg(e.Args()[0], from+int64(offs), until+int64(offs), values)
	if err != nil {
		return nil, err
	}
	results := make([]*types.MetricData, 0, len(arg))

	for _, a := range arg {
		r := *a
		r.Name = fmt.Sprintf("timeShift(%s,'%d',%v)", a.Name, offs, resetEnd)
		r.StartTime = a.StartTime - int64(offs)
		r.StopTime = a.StopTime - int64(offs)
		if resetEnd && r.StopTime > until {
			r.StopTime = until
		}
		length := int((r.StopTime - r.StartTime) / r.StepTime)
		if length < 0 {
			continue
		}
		r.Values = r.Values[:length]
		results = append(results, &r)
	}

	return results, nil
}

// Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
func (f *timeShift) Description() map[string]types.FunctionDescription {
	return map[string]types.FunctionDescription{
		"timeShift": {
			Description: "Takes one metric or a wildcard seriesList, followed by a quoted string with the\nlength of time (See ``from / until`` in the render\\_api_ for examples of time formats).\n\nDraws the selected metrics shifted in time. If no sign is given, a minus sign ( - ) is\nimplied which will shift the metric back in time. If a plus sign ( + ) is given, the\nmetric will be shifted forward in time.\n\nWill reset the end date range automatically to the end of the base stat unless\nresetEnd is False. Example case is when you timeshift to last week and have the graph\ndate range set to include a time in the future, will limit this timeshift to pretend\nending at the current time. If resetEnd is False, will instead draw full range including\nfuture time.\n\nBecause time is shifted by a fixed number of seconds, comparing a time period with DST to\na time period without DST, and vice-versa, will result in an apparent misalignment. For\nexample, 8am might be overlaid with 7am. To compensate for this, use the alignDST option.\n\nUseful for comparing a metric against itself at a past periods or correcting data\nstored at an offset.\n\nExample:\n\n.. code-block:: none\n\n  &target=timeShift(Sales.widgets.largeBlue,\"7d\")\n  &target=timeShift(Sales.widgets.largeBlue,\"-7d\")\n  &target=timeShift(Sales.widgets.largeBlue,\"+1h\")",
			Function:    "timeShift(seriesList, timeShift, resetEnd=True, alignDST=False)",
			Group:       "Transform",
			Module:      "graphite.render.functions",
			Name:        "timeShift",
			Params: []types.FunctionParam{
				{
					Name:     "seriesList",
					Required: true,
					Type:     types.SeriesList,
				},
				{
					Name:     "timeShift",
					Required: true,
					Suggestions: types.NewSuggestions(
						"1h",
						"6h",
						"12h",
						"1d",
						"2d",
						"7d",
						"14d",
						"30d",
					),
					Type: types.Interval,
				},
				{
					Default: types.NewSuggestion(*f.config.ResetEndDefaultValue),
					Name:    "resetEnd",
					Type:    types.Boolean,
				},
				/*
					{
						Default: types.NewSuggestion(false),
						Name:    "alignDst",
						Type:    types.Boolean,
					},
				*/
			},
		},
	}
}
<|endoftext|>