<commit_before>package main

import (
  "os"
  "fmt"
  "flag"
  "github.com/gudtech/scamp-go/scamp"
  "sync"

  "github.com/gudtech/retailops-sdk/verify/verify_service"
  "github.com/gudtech/retailops-sdk/verify/sdk_actions"
)

var gtsoaConfigPath = flag.String("config", "/etc/GTSOA/soa.conf", "path to the GTSOA soa.conf")
var registrationTicket = flag.String("registration-ticket", "/backplane/etc/endorsements/gtuser-sdk_service.ticket", "path to gtuser sdk_service ticket")

func main() {
  var err error
  flag.Parse()

  fmt.Println("loading config from",*gtsoaConfigPath)

  err = scamp.Initialize(*gtsoaConfigPath)
  if err != nil {
    fmt.Println("failed to load scamp config:",err.Error())
    os.Exit(1)
  }

  // verifierSvc,err := scamp.NewService("main","0.0.0.0:0","sdk_service")
  verifierSvc,err := scamp.NewService("main","0.0.0.0:0","sdk_service")
  if err != nil {
    scamp.Error.Printf("could not create service: `%s`", err.Error())
    os.Exit(1)
  }
  verify_service.TicketPath = *registrationTicket
  verifierSvc.Register("Integration.Channel.certify", verify_service.VerifyAction)

  callbackSvc,err := scamp.NewService("channelmodule", "127.0.0.1:6000","sdk_service")
  if err != nil {
    scamp.Error.Printf("could not create serivce: `%s`", err.Error())
    os.Exit(1)
  }
  // callbackSvc.Register("SDK.items_returned",sdk_actions.OrderReturnedV1) // , SDK callback: order_returned_v1
  // callbackSvc.Register("SDK.shipment_submit",sdk_actions.OrderShipmentSubmitV1) // , SDK callback: order_shipment_submit_v1
  // callbackSvc.Register("SDK.order_cancel",sdk_actions.OrderCancelV1) // , SDK callback: order_cancel_v1
  // callbackSvc.Register("SDK.order_complete",sdk_actions.OrderCompleteV1) // , SDK callback: order_complete_v1
  // callbackSvc.Register("SDK.capture_channel_payments",sdk_actions.OrderSettlePaymentV1) // , SDK callback: order_settle_payment_v1
  // callbackSvc.Register("SDK.orderpull.order_ack",sdk_actions.OrderAcknowledgeV1) // , SDK callback: order_acknowledge_v1
  // callbackSvc.Register("SDK.orderpull.order_fetch",sdk_actions.OrderPullV1) // , SDK callback: order_pull_v1
  // callbackSvc.Register("SDK.catpush_config",sdk_actions.CatalogGetConfigV1) // , SDK callback: catalog_get_config_v1
  // callbackSvc.Register("SDK.catpush_transmit",sdk_actions.CatalogPushV1) // , SDK callback: catalog_push_v1
  callbackSvc.Register("SDK.inventory.invpush_transmit",sdk_actions.InventoryPushV1) // , SDK callback: inventory_push_v1
  // callbackSvc.Register("SDK.writeback",sdk_actions.OrderUpdateV1) // , SDK callback: order_update_v1

  announcer,err := scamp.NewDiscoveryAnnouncer()
  if err != nil {
    scamp.Error.Printf("failed to create announcer: `%s`", err)
    return
  }

  announcer.Track(verifierSvc)
  announcer.Track(callbackSvc)
  go announcer.AnnounceLoop()

  var wg sync.WaitGroup
  wg.Add(1)
  go func() {
    callbackSvc.Run()
    wg.Done()
  }()

  wg.Add(1)
  go func(){
    verifierSvc.Run()
    wg.Done()
  }()

  wg.Wait()
}
<commit_msg>removing test ip<commit_after>package main

import (
  "os"
  "fmt"
  "flag"
  "github.com/gudtech/scamp-go/scamp"
  "sync"

  "github.com/gudtech/retailops-sdk/verify/verify_service"
  "github.com/gudtech/retailops-sdk/verify/sdk_actions"
)

var gtsoaConfigPath = flag.String("config", "/etc/GTSOA/soa.conf", "path to the GTSOA soa.conf")
var registrationTicket = flag.String("registration-ticket", "/backplane/etc/endorsements/gtuser-sdk_service.ticket", "path to gtuser sdk_service ticket")

func main() {
  var err error
  flag.Parse()

  fmt.Println("loading config from",*gtsoaConfigPath)

  err = scamp.Initialize(*gtsoaConfigPath)
  if err != nil {
    fmt.Println("failed to load scamp config:",err.Error())
    os.Exit(1)
  }

  // verifierSvc,err := scamp.NewService("main","0.0.0.0:0","sdk_service")
  verifierSvc,err := scamp.NewService("main","0.0.0.0:0","sdk_service")
  if err != nil {
    scamp.Error.Printf("could not create service: `%s`", err.Error())
    os.Exit(1)
  }
  verify_service.TicketPath = *registrationTicket
  verifierSvc.Register("Integration.Channel.certify", verify_service.VerifyAction)

  callbackSvc,err := scamp.NewService("channelmodule", "0.0.0.0:0","sdk_service")
  if err != nil {
    scamp.Error.Printf("could not create serivce: `%s`", err.Error())
    os.Exit(1)
  }
  // callbackSvc.Register("SDK.items_returned",sdk_actions.OrderReturnedV1) // , SDK callback: order_returned_v1
  // callbackSvc.Register("SDK.shipment_submit",sdk_actions.OrderShipmentSubmitV1) // , SDK callback: order_shipment_submit_v1
  // callbackSvc.Register("SDK.order_cancel",sdk_actions.OrderCancelV1) // , SDK callback: order_cancel_v1
  // callbackSvc.Register("SDK.order_complete",sdk_actions.OrderCompleteV1) // , SDK callback: order_complete_v1
  // callbackSvc.Register("SDK.capture_channel_payments",sdk_actions.OrderSettlePaymentV1) // , SDK callback: order_settle_payment_v1
  // callbackSvc.Register("SDK.orderpull.order_ack",sdk_actions.OrderAcknowledgeV1) // , SDK callback: order_acknowledge_v1
  // callbackSvc.Register("SDK.orderpull.order_fetch",sdk_actions.OrderPullV1) // , SDK callback: order_pull_v1
  // callbackSvc.Register("SDK.catpush_config",sdk_actions.CatalogGetConfigV1) // , SDK callback: catalog_get_config_v1
  // callbackSvc.Register("SDK.catpush_transmit",sdk_actions.CatalogPushV1) // , SDK callback: catalog_push_v1
  callbackSvc.Register("SDK.inventory.invpush_transmit",sdk_actions.InventoryPushV1) // , SDK callback: inventory_push_v1
  // callbackSvc.Register("SDK.writeback",sdk_actions.OrderUpdateV1) // , SDK callback: order_update_v1

  announcer,err := scamp.NewDiscoveryAnnouncer()
  if err != nil {
    scamp.Error.Printf("failed to create announcer: `%s`", err)
    return
  }

  announcer.Track(verifierSvc)
  announcer.Track(callbackSvc)
  go announcer.AnnounceLoop()

  var wg sync.WaitGroup
  wg.Add(1)
  go func() {
    callbackSvc.Run()
    wg.Done()
  }()

  wg.Add(1)
  go func(){
    verifierSvc.Run()
    wg.Done()
  }()

  wg.Wait()
}
<|endoftext|>


<commit_before>// Copyright 2020 Istio 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 (
	"encoding/json"
	"fmt"
	"path"
	"regexp"
	"strings"
)

type Note struct {
	Kind          string         `json:"kind"`
	Area          string         `json:"area"`
	Docs          []string       `json:"docs,omitempty"`
	Issues        []string       `json:"issue,omitempty"`
	ReleaseNotes  []releaseNote  `json:"releaseNotes"`
	UpgradeNotes  []upgradeNote  `json:"upgradeNotes"`
	SecurityNotes []securityNote `json:"securityNotes"`
	File          string
}

func (note Note) getIssues() string {
	issueString := ""
	for _, issue := range note.Issues {
		if issueString != "" {
			issueString += ","
		}
		if strings.Contains(issue, "github.com") {
			issueNumber := path.Base(issue)
			issueString += fmt.Sprintf("([Issue #%s](%s))", issueNumber, issue)
		} else {
			issueString += fmt.Sprintf("([Issue #%s](https://github.com/istio/istio/issues/%s))", issue, issue)
		}
	}
	return issueString
}

func (note Note) getDocs() string {
	docsString := ""
	for _, docsEntry := range note.Docs {
		entryParts := strings.SplitN(docsEntry[1:], "]", 2)
		if len(entryParts) != 2 {
			continue
		}
		docsString += fmt.Sprintf("([%s](%s))", entryParts[0], entryParts[1])
	}
	return docsString
}

func (note Note) getReleaseNotes(area string, action string) []string {
	notes := make([]string, 0)
	for _, releaseNote := range note.ReleaseNotes {
		if (action == "" || releaseNote.Action == action) && (area == "" || note.Area == area) {
			noteEntry := fmt.Sprintf("%s %s %s\n", releaseNote, note.getDocs(), note.getIssues())
			if noteEntry != "" {
				notes = append(notes, noteEntry)
			}
		}
	}
	return notes
}

func (note Note) getUpgradeNotes() []string {
	notes := make([]string, 0)
	for _, upgradeNote := range note.UpgradeNotes {
		notes = append(notes, upgradeNote.String())
	}
	return notes
}

func (note Note) getSecurityNotes() []string {
	notes := make([]string, 0)
	for _, securityNote := range note.SecurityNotes {
		notes = append(notes, securityNote.String())
	}
	return notes
}

type upgradeNote struct {
	Title   string `json:"title"`
	Content string `json:"content"`
}

func (note *upgradeNote) UnmarshalJSON(data []byte) error {
	type noteIntType upgradeNote
	var noteInt noteIntType
	if err := json.Unmarshal(data, &noteInt); err != nil {
		return err
	}

	if noteInt.Title == "" {
		return fmt.Errorf("upgrade note title cannot be empty")
	}
	note.Title = noteInt.Title

	if noteInt.Content == "" {
		return fmt.Errorf("upgrade note body cannot be empty")
	}
	note.Content = noteInt.Content
	return nil

}

func (note upgradeNote) String() string {
	return fmt.Sprintf("## %s\n%s", note.Title, note.Content)
}

type releaseNote struct {
	Value  string
	Action string
	Issues []string
}

func (note *releaseNote) UnmarshalJSON(data []byte) error {
	if err := json.Unmarshal(data, &note.Value); err != nil {
		return err
	}
	if note.Value == "" {
		return fmt.Errorf("value missing for note: %s", note.Value)
	}

	note.Action = note.getAction(note.Value)
	if note.Action == "" {
		return fmt.Errorf("unable to determine action for note: %s", note.Value)
	}

	//TODO: Externalize this... we should validate this and action. However, they should not live in code.
	if note.Action != "Added" && note.Action != "Deprecated" && note.Action != "Enabled" &&
		note.Action != "Fixed" && note.Action != "Optimized" && note.Action != "Improved" &&
		note.Action != "Removed" && note.Action != "Upgraded" && note.Action != "Updated" && note.Action != "Promoted" {
		return fmt.Errorf("action %s is not allowed. Please reference the documentation for an allowed action", note.Action)
	}

	return nil
}

func (note releaseNote) getAction(line string) string {
	action := ""
	actionRegexp := regexp.MustCompile(`\*\*[A-Z][a-zA-Z]*\*\*`)
	if match := actionRegexp.FindString(line); match != "" {
		action = match[2 : len(match)-2]
	}
	return action
}

func (note releaseNote) String() string {
	return fmt.Sprintf("- %s", note.Value)
}

type securityNote string

func (note securityNote) String() string {
	return fmt.Sprintf("- %s", string(note))
}
<commit_msg>Clarify action requirement (#1198)<commit_after>// Copyright 2020 Istio 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 (
	"encoding/json"
	"fmt"
	"path"
	"regexp"
	"strings"
)

type Note struct {
	Kind          string         `json:"kind"`
	Area          string         `json:"area"`
	Docs          []string       `json:"docs,omitempty"`
	Issues        []string       `json:"issue,omitempty"`
	ReleaseNotes  []releaseNote  `json:"releaseNotes"`
	UpgradeNotes  []upgradeNote  `json:"upgradeNotes"`
	SecurityNotes []securityNote `json:"securityNotes"`
	File          string
}

func (note Note) getIssues() string {
	issueString := ""
	for _, issue := range note.Issues {
		if issueString != "" {
			issueString += ","
		}
		if strings.Contains(issue, "github.com") {
			issueNumber := path.Base(issue)
			issueString += fmt.Sprintf("([Issue #%s](%s))", issueNumber, issue)
		} else {
			issueString += fmt.Sprintf("([Issue #%s](https://github.com/istio/istio/issues/%s))", issue, issue)
		}
	}
	return issueString
}

func (note Note) getDocs() string {
	docsString := ""
	for _, docsEntry := range note.Docs {
		entryParts := strings.SplitN(docsEntry[1:], "]", 2)
		if len(entryParts) != 2 {
			continue
		}
		docsString += fmt.Sprintf("([%s](%s))", entryParts[0], entryParts[1])
	}
	return docsString
}

func (note Note) getReleaseNotes(area string, action string) []string {
	notes := make([]string, 0)
	for _, releaseNote := range note.ReleaseNotes {
		if (action == "" || releaseNote.Action == action) && (area == "" || note.Area == area) {
			noteEntry := fmt.Sprintf("%s %s %s\n", releaseNote, note.getDocs(), note.getIssues())
			if noteEntry != "" {
				notes = append(notes, noteEntry)
			}
		}
	}
	return notes
}

func (note Note) getUpgradeNotes() []string {
	notes := make([]string, 0)
	for _, upgradeNote := range note.UpgradeNotes {
		notes = append(notes, upgradeNote.String())
	}
	return notes
}

func (note Note) getSecurityNotes() []string {
	notes := make([]string, 0)
	for _, securityNote := range note.SecurityNotes {
		notes = append(notes, securityNote.String())
	}
	return notes
}

type upgradeNote struct {
	Title   string `json:"title"`
	Content string `json:"content"`
}

func (note *upgradeNote) UnmarshalJSON(data []byte) error {
	type noteIntType upgradeNote
	var noteInt noteIntType
	if err := json.Unmarshal(data, &noteInt); err != nil {
		return err
	}

	if noteInt.Title == "" {
		return fmt.Errorf("upgrade note title cannot be empty")
	}
	note.Title = noteInt.Title

	if noteInt.Content == "" {
		return fmt.Errorf("upgrade note body cannot be empty")
	}
	note.Content = noteInt.Content
	return nil

}

func (note upgradeNote) String() string {
	return fmt.Sprintf("## %s\n%s", note.Title, note.Content)
}

type releaseNote struct {
	Value  string
	Action string
	Issues []string
}

func (note *releaseNote) UnmarshalJSON(data []byte) error {
	if err := json.Unmarshal(data, &note.Value); err != nil {
		return err
	}
	if note.Value == "" {
		return fmt.Errorf("value missing for note: %s", note.Value)
	}

	note.Action = note.getAction(note.Value)
	if note.Action == "" {
		return fmt.Errorf("unable to determine action for note: %s; notes must start with an action and be of the form"+
			"**Action** {text} with an action listed here: https://github.com/istio/istio/tree/master/releasenotes#release-notes", note.Value)
	}

	//TODO: Externalize this... we should validate this and action. However, they should not live in code.
	if note.Action != "Added" && note.Action != "Deprecated" && note.Action != "Enabled" &&
		note.Action != "Fixed" && note.Action != "Optimized" && note.Action != "Improved" &&
		note.Action != "Removed" && note.Action != "Upgraded" && note.Action != "Updated" && note.Action != "Promoted" {
		return fmt.Errorf("action %s is not allowed;reference "+
			"https://github.com/istio/istio/tree/master/releasenotes#release-notes for a list of allowed actions", note.Action)
	}

	return nil
}

func (note releaseNote) getAction(line string) string {
	action := ""
	actionRegexp := regexp.MustCompile(`\*\*[A-Z][a-zA-Z]*\*\*`)
	if match := actionRegexp.FindString(line); match != "" {
		action = match[2 : len(match)-2]
	}
	return action
}

func (note releaseNote) String() string {
	return fmt.Sprintf("- %s", note.Value)
}

type securityNote string

func (note securityNote) String() string {
	return fmt.Sprintf("- %s", string(note))
}
<|endoftext|>


<commit_before>// Package union offers a simple http.FileSystem that can unify multiple filesystems at various mount points.
package union

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/shurcooL/go/vfs/httpfs/vfsutil"
)

// New creates an union filesystem with the provided mapping of mount points to filesystems.
//
// Each mount point must be of form "/mydir". It must start with a '/', and contain a single directory name.
func New(mapping map[string]http.FileSystem) http.FileSystem {
	u := &unionFS{
		ns: make(map[string]http.FileSystem),
		root: &dirInfo{
			name:    "/",
			modTime: time.Time{}, // An empty union filesystem has never been modified.
		},
	}
	for mountPoint, fs := range mapping {
		u.bind(mountPoint, fs)
	}
	return u
}

type unionFS struct {
	ns   map[string]http.FileSystem // Key is mount point, e.g., "/mydir".
	root *dirInfo
}

// bind mounts fs at mountPoint.
// mountPoint must be of form "/mydir". It must start with a '/', and contain a single directory name.
func (u *unionFS) bind(mountPoint string, fs http.FileSystem) {
	fi, err := vfsutil.Stat(fs, "/")
	if err != nil {
		log.Fatalln("can't stat root directory of provided filesystem:", err)
	}
	modTime := fi.ModTime()

	u.ns[mountPoint] = fs
	u.root.entries = append(u.root.entries, &dirInfo{
		name:    mountPoint[1:],
		modTime: modTime,
	})

	if modTime.After(u.root.modTime) {
		u.root.modTime = modTime
	}
}

// Open opens the named file.
func (u *unionFS) Open(path string) (http.File, error) {
	// TODO: Maybe clean path?
	if path == "/" {
		return &dir{
			dirInfo: u.root,
		}, nil
	}
	for prefix, fs := range u.ns {
		if path == prefix || strings.HasPrefix(path, prefix+"/") {
			innerPath := path[len(prefix):]
			if innerPath == "" {
				innerPath = "/"
			}
			return fs.Open(innerPath)
		}
	}
	return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
}

// dirInfo is a static definition of a directory.
type dirInfo struct {
	name    string
	entries []os.FileInfo
	modTime time.Time
}

func (d *dirInfo) Read([]byte) (int, error) {
	return 0, fmt.Errorf("cannot Read from directory %s", d.name)
}
func (d *dirInfo) Close() error               { return nil }
func (d *dirInfo) Stat() (os.FileInfo, error) { return d, nil }

func (d *dirInfo) Name() string       { return d.name }
func (d *dirInfo) Size() int64        { return 0 }
func (d *dirInfo) Mode() os.FileMode  { return 0755 | os.ModeDir }
func (d *dirInfo) ModTime() time.Time { return d.modTime }
func (d *dirInfo) IsDir() bool        { return true }
func (d *dirInfo) Sys() interface{}   { return nil }

// dir is an opened dir instance.
type dir struct {
	*dirInfo
	pos int // Position within entries for Seek and Readdir.
}

func (d *dir) Seek(offset int64, whence int) (int64, error) {
	if offset == 0 && whence == os.SEEK_SET {
		d.pos = 0
		return 0, nil
	}
	return 0, fmt.Errorf("unsupported Seek in directory %s", d.dirInfo.name)
}

func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
	if d.pos >= len(d.dirInfo.entries) && count > 0 {
		return nil, io.EOF
	}
	if count <= 0 || count > len(d.dirInfo.entries)-d.pos {
		count = len(d.dirInfo.entries) - d.pos
	}
	e := d.dirInfo.entries[d.pos : d.pos+count]
	d.pos += count
	return e, nil
}
<commit_msg>union: Rearrange dirInfo fields to have a more natural order.<commit_after>// Package union offers a simple http.FileSystem that can unify multiple filesystems at various mount points.
package union

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/shurcooL/go/vfs/httpfs/vfsutil"
)

// New creates an union filesystem with the provided mapping of mount points to filesystems.
//
// Each mount point must be of form "/mydir". It must start with a '/', and contain a single directory name.
func New(mapping map[string]http.FileSystem) http.FileSystem {
	u := &unionFS{
		ns: make(map[string]http.FileSystem),
		root: &dirInfo{
			name:    "/",
			modTime: time.Time{}, // An empty union filesystem has never been modified.
		},
	}
	for mountPoint, fs := range mapping {
		u.bind(mountPoint, fs)
	}
	return u
}

type unionFS struct {
	ns   map[string]http.FileSystem // Key is mount point, e.g., "/mydir".
	root *dirInfo
}

// bind mounts fs at mountPoint.
// mountPoint must be of form "/mydir". It must start with a '/', and contain a single directory name.
func (u *unionFS) bind(mountPoint string, fs http.FileSystem) {
	fi, err := vfsutil.Stat(fs, "/")
	if err != nil {
		log.Fatalln("can't stat root directory of provided filesystem:", err)
	}
	modTime := fi.ModTime()

	u.ns[mountPoint] = fs
	u.root.entries = append(u.root.entries, &dirInfo{
		name:    mountPoint[1:],
		modTime: modTime,
	})

	if modTime.After(u.root.modTime) {
		u.root.modTime = modTime
	}
}

// Open opens the named file.
func (u *unionFS) Open(path string) (http.File, error) {
	// TODO: Maybe clean path?
	if path == "/" {
		return &dir{
			dirInfo: u.root,
		}, nil
	}
	for prefix, fs := range u.ns {
		if path == prefix || strings.HasPrefix(path, prefix+"/") {
			innerPath := path[len(prefix):]
			if innerPath == "" {
				innerPath = "/"
			}
			return fs.Open(innerPath)
		}
	}
	return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
}

// dirInfo is a static definition of a directory.
type dirInfo struct {
	name    string
	modTime time.Time
	entries []os.FileInfo
}

func (d *dirInfo) Read([]byte) (int, error) {
	return 0, fmt.Errorf("cannot Read from directory %s", d.name)
}
func (d *dirInfo) Close() error               { return nil }
func (d *dirInfo) Stat() (os.FileInfo, error) { return d, nil }

func (d *dirInfo) Name() string       { return d.name }
func (d *dirInfo) Size() int64        { return 0 }
func (d *dirInfo) Mode() os.FileMode  { return 0755 | os.ModeDir }
func (d *dirInfo) ModTime() time.Time { return d.modTime }
func (d *dirInfo) IsDir() bool        { return true }
func (d *dirInfo) Sys() interface{}   { return nil }

// dir is an opened dir instance.
type dir struct {
	*dirInfo
	pos int // Position within entries for Seek and Readdir.
}

func (d *dir) Seek(offset int64, whence int) (int64, error) {
	if offset == 0 && whence == os.SEEK_SET {
		d.pos = 0
		return 0, nil
	}
	return 0, fmt.Errorf("unsupported Seek in directory %s", d.dirInfo.name)
}

func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
	if d.pos >= len(d.dirInfo.entries) && count > 0 {
		return nil, io.EOF
	}
	if count <= 0 || count > len(d.dirInfo.entries)-d.pos {
		count = len(d.dirInfo.entries) - d.pos
	}
	e := d.dirInfo.entries[d.pos : d.pos+count]
	d.pos += count
	return e, nil
}
<|endoftext|>


<commit_before>package converter

import (
	"reflect"
	"testing"
)

func TestKrSpaKa_Comma(t *testing.T) {
	expectedComma := ';'
	k := NewKrSpaKa()
	if k.Comma() != expectedComma {
		t.Errorf("Field seperator is %q instead of expected %q", k.Comma(), expectedComma)
	}
}

func TestKrSpaKa_IsTransaction(t *testing.T) {
	type testpair struct {
		expected bool
		record   []string
	}

	tests := []testpair{
		{false, []string{}},
		{false, []string{"1", "2", "3", "4", "5", "6", "7"}},
		{false, []string{"Auftragskonto", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17"}},
		{true, []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17"}},
	}

	k := NewKrSpaKa()
	for _, pair := range tests {
		actual := k.IsTransaction(pair.record)
		if actual != pair.expected {
			t.Errorf("Result is '%v', but it should be '%v' for %q", actual, pair.expected, pair.record)
		}
	}
}

func TestKrSpaKa_Convert(t *testing.T) {
	type testpair struct {
		record   []string
		expected []string
	}

	tests := []testpair{
		{
			[]string{"123456789", "14.10.16", "14.10.16", "FOLGELASTSCHRIFT", "1212-3434 acommpany ", "XC352647657", "sfasfae-325678fsdgr", "2323462477", "", "", "", "ACOMPANY", "DE1234567891234", "YYYYAAAAA", "-12,99", "EUR", "Umsatz gebucht"},
			[]string{"14/10/2016", "ACOMPANY", "", "[FOLGELASTSCHRIFT] 1212-3434 acommpany ", "12.99", ""},
		}, {
			[]string{"123456789", "15.10.16", "16.10.16", "FOLGELASTSCHRIFT", "1212-3434 bcommpany ", "XC352647657", "sfasfae-325678fsdgr", "2323462477", "", "", "", "BCOMPANY", "DE1234567891234", "YYYYAAAAA", "3092,44", "EUR", "Umsatz gebucht"},
			[]string{"15/10/2016", "BCOMPANY", "", "[FOLGELASTSCHRIFT] 1212-3434 bcommpany ", "", "3092.44"},
		},
	}

	k := NewKrSpaKa()
	for _, pair := range tests {
		actual := k.Convert(pair.record)
		if !reflect.DeepEqual(actual, pair.expected) {
			t.Errorf("Actual record (%q) does't match expected record (%q) with input (%q)", actual, pair.expected, pair.record)
		}
	}
}
<commit_msg>Add test to omit prescheduled transactions<commit_after>package converter

import (
	"reflect"
	"testing"
)

func TestKrSpaKa_Comma(t *testing.T) {
	expectedComma := ';'
	k := NewKrSpaKa()
	if k.Comma() != expectedComma {
		t.Errorf("Field seperator is %q instead of expected %q", k.Comma(), expectedComma)
	}
}

func TestKrSpaKa_IsTransaction(t *testing.T) {
	type testpair struct {
		expected bool
		record   []string
	}

	tests := []testpair{
		{false, []string{}},
		{false, []string{"1", "2", "3", "4", "5", "6", "7"}},
		{false, []string{"Auftragskonto", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", ""}},
		{false, []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "Umsatz vorgemerkt"}},
		{true, []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17"}},
	}

	k := NewKrSpaKa()
	for _, pair := range tests {
		actual := k.IsTransaction(pair.record)
		if actual != pair.expected {
			t.Errorf("Result is '%v', but it should be '%v' for %q", actual, pair.expected, pair.record)
		}
	}
}

func TestKrSpaKa_Convert(t *testing.T) {
	type testpair struct {
		record   []string
		expected []string
	}

	tests := []testpair{
		{
			[]string{"123456789", "14.10.16", "14.10.16", "FOLGELASTSCHRIFT", "1212-3434 acommpany ", "XC352647657", "sfasfae-325678fsdgr", "2323462477", "", "", "", "ACOMPANY", "DE1234567891234", "YYYYAAAAA", "-12,99", "EUR", "Umsatz gebucht"},
			[]string{"14/10/2016", "ACOMPANY", "", "[FOLGELASTSCHRIFT] 1212-3434 acommpany ", "12.99", ""},
		}, {
			[]string{"123456789", "15.10.16", "16.10.16", "FOLGELASTSCHRIFT", "1212-3434 bcommpany ", "XC352647657", "sfasfae-325678fsdgr", "2323462477", "", "", "", "BCOMPANY", "DE1234567891234", "YYYYAAAAA", "3092,44", "EUR", "Umsatz gebucht"},
			[]string{"15/10/2016", "BCOMPANY", "", "[FOLGELASTSCHRIFT] 1212-3434 bcommpany ", "", "3092.44"},
		},
	}

	k := NewKrSpaKa()
	for _, pair := range tests {
		actual := k.Convert(pair.record)
		if !reflect.DeepEqual(actual, pair.expected) {
			t.Errorf("Actual record (%q) does't match expected record (%q) with input (%q)", actual, pair.expected, pair.record)
		}
	}
}
<|endoftext|>


<commit_before>package sched

import (
	"bytes"
	"fmt"
	"html/template"
	"io"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/StackExchange/bosun/_third_party/github.com/StackExchange/scollector/metadata"
	"github.com/StackExchange/bosun/_third_party/github.com/StackExchange/scollector/opentsdb"
	"github.com/StackExchange/bosun/conf"
	"github.com/StackExchange/bosun/expr"
	"github.com/StackExchange/bosun/expr/parse"
)

type Context struct {
	*State
	Alert *conf.Alert

	schedule    *Schedule
	Attachments []*conf.Attachment
}

func (s *Schedule) Data(st *State, a *conf.Alert, isEmail bool) *Context {
	c := Context{
		State:    st,
		Alert:    a,
		schedule: s,
	}
	if isEmail {
		c.Attachments = make([]*conf.Attachment, 0)
	}
	return &c
}

type unknownContext struct {
	Time  time.Time
	Name  string
	Group expr.AlertKeys

	schedule *Schedule
}

func (s *Schedule) unknownData(t time.Time, name string, group expr.AlertKeys) *unknownContext {
	return &unknownContext{
		Time:     t,
		Group:    group,
		Name:     name,
		schedule: s,
	}
}

// URL returns a prepopulated URL for external access, with path and query empty.
func (s *Schedule) URL() *url.URL {
	u := url.URL{
		Scheme: "http",
		Host:   s.Conf.HttpListen,
	}
	if strings.HasPrefix(s.Conf.HttpListen, ":") {
		h, err := os.Hostname()
		if err != nil {
			u.Host = "localhost" + u.Host
		} else {
			u.Host = h + u.Host
		}
	}
	return &u
}

// Ack returns the URL to acknowledge an alert.
func (c *Context) Ack() string {
	u := c.schedule.URL()
	u.Path = "/action"
	u.RawQuery = url.Values{
		"type": []string{"ack"},
		"key":  []string{c.Alert.Name + c.State.Group.String()},
	}.Encode()
	return u.String()
}

// HostView returns the URL to the host view page.
func (c *Context) HostView(host string) string {
	u := c.schedule.URL()
	u.Path = "/host"
	u.RawQuery = fmt.Sprintf("time=1d-ago&host=%s", host)
	return u.String()
}

func (c *Context) Expr(v string) (string, error) {
	q := url.QueryEscape("q=" + opentsdb.ReplaceTags(v, c.Group))
	u := url.URL{
		Scheme:   "http",
		Host:     c.schedule.Conf.HttpListen,
		Path:     "/expr",
		RawQuery: q,
	}
	if strings.HasPrefix(c.schedule.Conf.HttpListen, ":") {
		h, err := os.Hostname()
		if err != nil {
			return "", err
		}
		u.Host = h + u.Host
	}
	return u.String(), nil
}

func (s *Schedule) ExecuteBody(w io.Writer, a *conf.Alert, st *State, isEmail bool) ([]*conf.Attachment, error) {
	t := a.Template
	if t == nil || t.Body == nil {
		return nil, nil
	}
	c := s.Data(st, a, isEmail)
	return c.Attachments, t.Body.Execute(w, c)
}

func (s *Schedule) ExecuteSubject(w io.Writer, a *conf.Alert, st *State) error {
	t := a.Template
	if t == nil || t.Subject == nil {
		return nil
	}
	return t.Subject.Execute(w, s.Data(st, a, false))
}

func (c *Context) eval(v interface{}, filter bool, series bool, autods int) ([]*expr.Result, error) {
	var e *expr.Expr
	var err error
	switch v := v.(type) {
	case string:
		e, err = expr.New(v)
	case *expr.Expr:
		e = v
	}
	if err != nil {
		return nil, fmt.Errorf("%v: %v", v, err)
	}
	var results []*expr.Result
	if series && e.Root.Return() != parse.TYPE_SERIES {
		return results, fmt.Errorf("egraph: requires an expression that returns a series")
	}
	res, _, err := e.Execute(c.schedule.cache, nil, c.schedule.CheckStart, autods, c.Alert.UnjoinedOK, c.schedule.Search, c.schedule.Lookups)
	if err != nil {
		return results, fmt.Errorf("%s: %v", v, err)
	}
	if !filter {
		return res.Results, nil
	}
	for _, r := range res.Results {
		if r.Group.Equal(c.State.Group) {
			results = append(results, r)
			return results, nil
		}
	}
	for _, r := range res.Results {
		if c.State.Group.Subset(r.Group) {
			results = append(results, r)
			return results, nil
		}
	}
	return nil, nil
}

// Lookup returns the value for a key in the lookup table for the context's tagset.
func (c *Context) Lookup(table, key string) (string, error) {
	l, ok := c.schedule.Lookups[table]
	if !ok {
		return "", fmt.Errorf("unknown lookup table %v", table)
	}
	if v, ok := l.Get(key, c.Group); ok {
		return v, nil
	} else {
		return "", fmt.Errorf("no entry for key %v in table %v for tagset %v", key, table, c.Group)
	}
}

// Eval executes the given expression and returns a value with corresponding
// tags to the context's tags. If no such result is found, the first result with
// nil tags is returned. If no such result is found, nil is returned.
func (c *Context) Eval(v interface{}) (interface{}, error) {
	res, err := c.eval(v, true, false, 0)
	if err != nil {
		return nil, err
	}
	if len(res) != 1 {
		return nil, fmt.Errorf("expected 1 result, got %v", len(res))
	}
	return res[0].Value, nil
}

// EvalAll returns the executed expression.
func (c *Context) EvalAll(v interface{}) (interface{}, error) {
	return c.eval(v, false, false, 0)
}

func (c *Context) IsEmail() bool {
	return c.Attachments != nil
}

func (c *Context) graph(v interface{}, filter bool) (interface{}, error) {
	res, err := c.eval(v, filter, true, 1000)
	if err != nil {
		return nil, err
	}
	var buf bytes.Buffer
	if err := c.schedule.ExprGraph(nil, &buf, res, fmt.Sprint(v), time.Now().UTC()); err != nil {
		return nil, err
	}
	if c.IsEmail() {
		name := fmt.Sprintf("%d.svg", len(c.Attachments)+1)
		c.Attachments = append(c.Attachments, &conf.Attachment{
			Data:        buf.Bytes(),
			Filename:    name,
			ContentType: "image/svg+xml",
		})
		return template.HTML(fmt.Sprintf(`<img alt="%s" src="cid:%s" />`,
			template.HTMLEscapeString(fmt.Sprint(v)),
			name,
		)), nil
	}
	return template.HTML(buf.String()), nil
}

func (c *Context) Graph(v interface{}) (interface{}, error) {
	return c.graph(v, true)
}

func (c *Context) GraphAll(v interface{}) (interface{}, error) {
	return c.graph(v, false)
}

func (c *Context) GetMeta(metric, name string, v interface{}) (interface{}, error) {
	var t opentsdb.TagSet
	switch v := v.(type) {
	case string:
		var err error
		t, err = opentsdb.ParseTags(v)
		if err != nil {
			return t, err
		}
	case opentsdb.TagSet:
		t = v
	}
	meta := c.schedule.GetMetadata(metric, t)
	if name == "" {
		return meta, nil
	}
	fm := make([]metadata.Metasend, 0)
	for _, m := range meta {
		if m.Name == name {
			fm = append(fm, m)
		}
	}
	return fm, nil
}

func (c *Context) Union(q ...interface{}) (interface{}, error) {
	if len(q) < 2 {
		return nil, fmt.Errorf("need at least expressions, got %v", len(q))
	}
	matrix := make([][]*expr.Result, 0)
	results := make([][]*expr.Result, len(q))
	for col, v := range q {
		res, err := c.eval(v, false, false, 0)
		if err != nil {
			return nil, err
		}
		results[col] = res
	}
	for row, first := range results[0] {
		matrix = append(matrix, make([]*expr.Result, len(q)))
		matrix[row][0] = first
		for col, res := range results[1:] {
			col++
			for _, r := range res {
				if first.Group.Subset(r.Group) {
					matrix[row][col] = r
				}
			}
		}
	}
	return matrix, nil
}
<commit_msg>Rename to LeftJoin (since it is a left outer join), fix error, style<commit_after>package sched

import (
	"bytes"
	"fmt"
	"html/template"
	"io"
	"net/url"
	"os"
	"strings"
	"time"

	"github.com/StackExchange/bosun/_third_party/github.com/StackExchange/scollector/metadata"
	"github.com/StackExchange/bosun/_third_party/github.com/StackExchange/scollector/opentsdb"
	"github.com/StackExchange/bosun/conf"
	"github.com/StackExchange/bosun/expr"
	"github.com/StackExchange/bosun/expr/parse"
)

type Context struct {
	*State
	Alert *conf.Alert

	schedule    *Schedule
	Attachments []*conf.Attachment
}

func (s *Schedule) Data(st *State, a *conf.Alert, isEmail bool) *Context {
	c := Context{
		State:    st,
		Alert:    a,
		schedule: s,
	}
	if isEmail {
		c.Attachments = make([]*conf.Attachment, 0)
	}
	return &c
}

type unknownContext struct {
	Time  time.Time
	Name  string
	Group expr.AlertKeys

	schedule *Schedule
}

func (s *Schedule) unknownData(t time.Time, name string, group expr.AlertKeys) *unknownContext {
	return &unknownContext{
		Time:     t,
		Group:    group,
		Name:     name,
		schedule: s,
	}
}

// URL returns a prepopulated URL for external access, with path and query empty.
func (s *Schedule) URL() *url.URL {
	u := url.URL{
		Scheme: "http",
		Host:   s.Conf.HttpListen,
	}
	if strings.HasPrefix(s.Conf.HttpListen, ":") {
		h, err := os.Hostname()
		if err != nil {
			u.Host = "localhost" + u.Host
		} else {
			u.Host = h + u.Host
		}
	}
	return &u
}

// Ack returns the URL to acknowledge an alert.
func (c *Context) Ack() string {
	u := c.schedule.URL()
	u.Path = "/action"
	u.RawQuery = url.Values{
		"type": []string{"ack"},
		"key":  []string{c.Alert.Name + c.State.Group.String()},
	}.Encode()
	return u.String()
}

// HostView returns the URL to the host view page.
func (c *Context) HostView(host string) string {
	u := c.schedule.URL()
	u.Path = "/host"
	u.RawQuery = fmt.Sprintf("time=1d-ago&host=%s", host)
	return u.String()
}

func (c *Context) Expr(v string) (string, error) {
	q := url.QueryEscape("q=" + opentsdb.ReplaceTags(v, c.Group))
	u := url.URL{
		Scheme:   "http",
		Host:     c.schedule.Conf.HttpListen,
		Path:     "/expr",
		RawQuery: q,
	}
	if strings.HasPrefix(c.schedule.Conf.HttpListen, ":") {
		h, err := os.Hostname()
		if err != nil {
			return "", err
		}
		u.Host = h + u.Host
	}
	return u.String(), nil
}

func (s *Schedule) ExecuteBody(w io.Writer, a *conf.Alert, st *State, isEmail bool) ([]*conf.Attachment, error) {
	t := a.Template
	if t == nil || t.Body == nil {
		return nil, nil
	}
	c := s.Data(st, a, isEmail)
	return c.Attachments, t.Body.Execute(w, c)
}

func (s *Schedule) ExecuteSubject(w io.Writer, a *conf.Alert, st *State) error {
	t := a.Template
	if t == nil || t.Subject == nil {
		return nil
	}
	return t.Subject.Execute(w, s.Data(st, a, false))
}

func (c *Context) eval(v interface{}, filter bool, series bool, autods int) ([]*expr.Result, error) {
	var e *expr.Expr
	var err error
	switch v := v.(type) {
	case string:
		e, err = expr.New(v)
	case *expr.Expr:
		e = v
	}
	if err != nil {
		return nil, fmt.Errorf("%v: %v", v, err)
	}
	var results []*expr.Result
	if series && e.Root.Return() != parse.TYPE_SERIES {
		return results, fmt.Errorf("egraph: requires an expression that returns a series")
	}
	res, _, err := e.Execute(c.schedule.cache, nil, c.schedule.CheckStart, autods, c.Alert.UnjoinedOK, c.schedule.Search, c.schedule.Lookups)
	if err != nil {
		return results, fmt.Errorf("%s: %v", v, err)
	}
	if !filter {
		return res.Results, nil
	}
	for _, r := range res.Results {
		if r.Group.Equal(c.State.Group) {
			results = append(results, r)
			return results, nil
		}
	}
	for _, r := range res.Results {
		if c.State.Group.Subset(r.Group) {
			results = append(results, r)
			return results, nil
		}
	}
	return nil, nil
}

// Lookup returns the value for a key in the lookup table for the context's tagset.
func (c *Context) Lookup(table, key string) (string, error) {
	l, ok := c.schedule.Lookups[table]
	if !ok {
		return "", fmt.Errorf("unknown lookup table %v", table)
	}
	if v, ok := l.Get(key, c.Group); ok {
		return v, nil
	} else {
		return "", fmt.Errorf("no entry for key %v in table %v for tagset %v", key, table, c.Group)
	}
}

// Eval executes the given expression and returns a value with corresponding
// tags to the context's tags. If no such result is found, the first result with
// nil tags is returned. If no such result is found, nil is returned.
func (c *Context) Eval(v interface{}) (interface{}, error) {
	res, err := c.eval(v, true, false, 0)
	if err != nil {
		return nil, err
	}
	if len(res) != 1 {
		return nil, fmt.Errorf("expected 1 result, got %v", len(res))
	}
	return res[0].Value, nil
}

// EvalAll returns the executed expression.
func (c *Context) EvalAll(v interface{}) (interface{}, error) {
	return c.eval(v, false, false, 0)
}

func (c *Context) IsEmail() bool {
	return c.Attachments != nil
}

func (c *Context) graph(v interface{}, filter bool) (interface{}, error) {
	res, err := c.eval(v, filter, true, 1000)
	if err != nil {
		return nil, err
	}
	var buf bytes.Buffer
	if err := c.schedule.ExprGraph(nil, &buf, res, fmt.Sprint(v), time.Now().UTC()); err != nil {
		return nil, err
	}
	if c.IsEmail() {
		name := fmt.Sprintf("%d.svg", len(c.Attachments)+1)
		c.Attachments = append(c.Attachments, &conf.Attachment{
			Data:        buf.Bytes(),
			Filename:    name,
			ContentType: "image/svg+xml",
		})
		return template.HTML(fmt.Sprintf(`<img alt="%s" src="cid:%s" />`,
			template.HTMLEscapeString(fmt.Sprint(v)),
			name,
		)), nil
	}
	return template.HTML(buf.String()), nil
}

func (c *Context) Graph(v interface{}) (interface{}, error) {
	return c.graph(v, true)
}

func (c *Context) GraphAll(v interface{}) (interface{}, error) {
	return c.graph(v, false)
}

func (c *Context) GetMeta(metric, name string, v interface{}) (interface{}, error) {
	var t opentsdb.TagSet
	switch v := v.(type) {
	case string:
		var err error
		t, err = opentsdb.ParseTags(v)
		if err != nil {
			return t, err
		}
	case opentsdb.TagSet:
		t = v
	}
	meta := c.schedule.GetMetadata(metric, t)
	if name == "" {
		return meta, nil
	}
	fm := make([]metadata.Metasend, 0)
	for _, m := range meta {
		if m.Name == name {
			fm = append(fm, m)
		}
	}
	return fm, nil
}

func (c *Context) LeftJoin(q ...interface{}) (interface{}, error) {
	if len(q) < 2 {
		return nil, fmt.Errorf("need at least two expressions, got %v", len(q))
	}
	matrix := make([][]*expr.Result, 0)
	results := make([][]*expr.Result, len(q))
	for col, v := range q {
		res, err := c.eval(v, false, false, 0)
		if err != nil {
			return nil, err
		}
		results[col] = res
	}
	for row, first := range results[0] {
		matrix = append(matrix, make([]*expr.Result, len(q)))
		matrix[row][0] = first
		for col, res := range results[1:] {
			for _, r := range res {
				if first.Group.Subset(r.Group) {
					matrix[row][col+1] = r
				}
			}
		}
	}
	return matrix, nil
}
<|endoftext|>


<commit_before>package chunk

import (
	"bytes"
	"compress/gzip"
	"context"
	"io"
	"path"
	"strings"
	"sync"

	"github.com/pachyderm/pachyderm/src/server/pkg/obj"
)

// Reader reads data from chunk storage.
type Reader struct {
	ctx      context.Context
	objC     obj.Client
	dataRefs []*DataRef
	peek     *DataReader
	prev     *DataReader
}

func newReader(ctx context.Context, objC obj.Client, dataRefs ...*DataRef) *Reader {
	return &Reader{
		ctx:      ctx,
		objC:     objC,
		dataRefs: dataRefs,
	}
}

// NextDataRefs sets the next data references for the reader.
func (r *Reader) NextDataRefs(dataRefs []*DataRef) {
	r.dataRefs = dataRefs
}

// Peek returns the next data reader without progressing the reader.
func (r *Reader) Peek() (*DataReader, error) {
	if r.peek == nil {
		var err error
		r.peek, err = r.Next()
		if err != nil {
			return nil, err
		}
	}
	return r.peek, nil
}

// Next progresses the reader to the next data reader.
func (r *Reader) Next() (*DataReader, error) {
	if r.peek != nil {
		dr := r.peek
		r.peek = nil
		return dr, nil
	}
	if len(r.dataRefs) == 0 {
		return nil, io.EOF
	}
	dr := newDataReader(r.ctx, r.objC, r.dataRefs[0], r.prev)
	r.dataRefs = r.dataRefs[1:]
	r.prev = dr
	return dr, nil
}

// Iterate iterates over the data readers for the current data references
// set in the reader.
func (r *Reader) Iterate(f func(*DataReader) error) error {
	for {
		dr, err := r.Peek()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
		if err := f(dr); err != nil {
			return err
		}
		if _, err := r.Next(); err != nil {
			return err
		}
	}
	return nil
}

// Get writes the concatenation of the data represented by the data references
// set in the reader.
// (bryce) probably should make a decision on whether this should be blocked for
// a reader that already has been partially iterated.
func (r *Reader) Get(w io.Writer) error {
	return r.Iterate(func(dr *DataReader) error {
		return dr.Get(w)
	})
}

// DataReader is an abstraction that lazily reads data referenced by a data reference.
// The seed is set to avoid re-downloading a chunk that is shared between this data reference
// and the prior in a chain of data references.
type DataReader struct {
	ctx        context.Context
	objC       obj.Client
	dataRef    *DataRef
	getChunkMu sync.Mutex
	chunk      []byte
	offset     int64
	tags       []*Tag
	seed       *DataReader
}

func newDataReader(ctx context.Context, objC obj.Client, dataRef *DataRef, seed *DataReader) *DataReader {
	return &DataReader{
		ctx:     ctx,
		objC:    objC,
		dataRef: dataRef,
		offset:  dataRef.OffsetBytes,
		tags:    dataRef.Tags,
		seed:    seed,
	}
}

// DataRef is the data reference associated with this data reader.
func (dr *DataReader) DataRef() *DataRef {
	return dr.dataRef
}

// Len is the length of the remaining data to be read.
func (dr *DataReader) Len() int64 {
	var size int64
	for _, tag := range dr.tags {
		size += tag.SizeBytes
	}
	return size
}

// Peek peeks ahead in the tags.
func (dr *DataReader) Peek() (*Tag, error) {
	if len(dr.tags) == 0 {
		return nil, io.EOF
	}
	return dr.tags[0], nil
}

// Iterate iterates over the tags in the data reference and passes the tag and a reader for getting
// the tagged content to the callback function.
func (dr *DataReader) Iterate(f func(*Tag, io.Reader) error, tagBound ...string) error {
	if err := dr.getChunk(); err != nil {
		return err
	}
	for {
		tag, err := dr.Peek()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
		if !BeforeBound(tag.Id, tagBound...) {
			return nil
		}
		if err := f(tag, bytes.NewReader(dr.chunk[dr.offset:dr.offset+tag.SizeBytes])); err != nil {
			return err
		}
		dr.tags = dr.tags[1:]
		dr.offset += tag.SizeBytes
	}
}

func (dr *DataReader) getChunk() error {
	dr.getChunkMu.Lock()
	defer dr.getChunkMu.Unlock()
	if dr.chunk != nil {
		return nil
	}
	// Use seed chunk if possible.
	if dr.seed != nil && dr.dataRef.ChunkInfo.Chunk.Hash == dr.seed.dataRef.ChunkInfo.Chunk.Hash {
		if err := dr.seed.getChunk(); err != nil {
			return err
		}
		dr.chunk = dr.seed.chunk
		return nil
	}
	// Get chunk from object storage.
	objR, err := dr.objC.Reader(dr.ctx, path.Join(prefix, dr.dataRef.ChunkInfo.Chunk.Hash), 0, 0)
	if err != nil {
		return err
	}
	defer objR.Close()
	gzipR, err := gzip.NewReader(objR)
	if err != nil {
		return err
	}
	defer gzipR.Close()
	buf := &bytes.Buffer{}
	if _, err := io.Copy(buf, gzipR); err != nil {
		return err
	}
	dr.chunk = buf.Bytes()
	return nil
}

// BeforeBound checks if the passed in string is before the string bound (exclusive).
// The string bound is optional, so if no string bound is passed then it returns true.
func BeforeBound(str string, strBound ...string) bool {
	return len(strBound) == 0 || strings.Compare(str, strBound[0]) < 0
}

// Get writes the data referenced by the data reference.
func (dr *DataReader) Get(w io.Writer) error {
	if err := dr.getChunk(); err != nil {
		return err
	}
	data := dr.chunk[dr.dataRef.OffsetBytes : dr.dataRef.OffsetBytes+dr.dataRef.SizeBytes]
	if _, err := w.Write(data); err != nil {
		return err
	}
	return nil
}

// LimitReader progresses the data reader to a tag bound and will return
// a new data reader that will read the tags up to the tag bound. This does
// not fetch any data.
func (dr *DataReader) LimitReader(tagBound ...string) *DataReader {
	offset := dr.offset
	var tags []*Tag
	for {
		if len(dr.tags) == 0 {
			break
		}
		tag := dr.tags[0]
		if !BeforeBound(tag.Id, tagBound...) {
			break
		}
		tags = append(tags, tag)
		dr.offset += tag.SizeBytes
		dr.tags = dr.tags[1:]
	}
	return &DataReader{
		ctx:     dr.ctx,
		objC:    dr.objC,
		dataRef: dr.dataRef,
		offset:  offset,
		tags:    tags,
		seed:    dr,
	}
}
<commit_msg>Address feedback regarding tag upper bounds<commit_after>package chunk

import (
	"bytes"
	"compress/gzip"
	"context"
	"io"
	"path"
	"sync"

	"github.com/pachyderm/pachyderm/src/server/pkg/obj"
)

// Reader reads data from chunk storage.
type Reader struct {
	ctx      context.Context
	objC     obj.Client
	dataRefs []*DataRef
	peek     *DataReader
	prev     *DataReader
}

func newReader(ctx context.Context, objC obj.Client, dataRefs ...*DataRef) *Reader {
	return &Reader{
		ctx:      ctx,
		objC:     objC,
		dataRefs: dataRefs,
	}
}

// NextDataRefs sets the next data references for the reader.
func (r *Reader) NextDataRefs(dataRefs []*DataRef) {
	r.dataRefs = dataRefs
}

// Peek returns the next data reader without progressing the reader.
func (r *Reader) Peek() (*DataReader, error) {
	if r.peek == nil {
		var err error
		r.peek, err = r.Next()
		if err != nil {
			return nil, err
		}
	}
	return r.peek, nil
}

// Next progresses the reader to the next data reader.
func (r *Reader) Next() (*DataReader, error) {
	if r.peek != nil {
		dr := r.peek
		r.peek = nil
		return dr, nil
	}
	if len(r.dataRefs) == 0 {
		return nil, io.EOF
	}
	dr := newDataReader(r.ctx, r.objC, r.dataRefs[0], r.prev)
	r.dataRefs = r.dataRefs[1:]
	r.prev = dr
	return dr, nil
}

// Iterate iterates over the data readers for the current data references
// set in the reader.
func (r *Reader) Iterate(f func(*DataReader) error) error {
	for {
		dr, err := r.Peek()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
		if err := f(dr); err != nil {
			return err
		}
		if _, err := r.Next(); err != nil {
			return err
		}
	}
	return nil
}

// Get writes the concatenation of the data represented by the data references
// set in the reader.
// (bryce) probably should make a decision on whether this should be blocked for
// a reader that already has been partially iterated.
func (r *Reader) Get(w io.Writer) error {
	return r.Iterate(func(dr *DataReader) error {
		return dr.Get(w)
	})
}

// DataReader is an abstraction that lazily reads data referenced by a data reference.
// The seed is set to avoid re-downloading a chunk that is shared between this data reference
// and the prior in a chain of data references.
type DataReader struct {
	ctx        context.Context
	objC       obj.Client
	dataRef    *DataRef
	getChunkMu sync.Mutex
	chunk      []byte
	offset     int64
	tags       []*Tag
	seed       *DataReader
}

func newDataReader(ctx context.Context, objC obj.Client, dataRef *DataRef, seed *DataReader) *DataReader {
	return &DataReader{
		ctx:     ctx,
		objC:    objC,
		dataRef: dataRef,
		offset:  dataRef.OffsetBytes,
		tags:    dataRef.Tags,
		seed:    seed,
	}
}

// DataRef is the data reference associated with this data reader.
func (dr *DataReader) DataRef() *DataRef {
	return dr.dataRef
}

// Len is the length of the remaining data to be read.
func (dr *DataReader) Len() int64 {
	var size int64
	for _, tag := range dr.tags {
		size += tag.SizeBytes
	}
	return size
}

// Peek peeks ahead in the tags.
func (dr *DataReader) Peek() (*Tag, error) {
	if len(dr.tags) == 0 {
		return nil, io.EOF
	}
	return dr.tags[0], nil
}

// Iterate iterates over the tags in the data reference and passes the tag and a reader for getting
// the tagged content to the callback function.
// tagUpperBound is an optional parameter for specifiying the upper bound (exclusive) of the iteration.
func (dr *DataReader) Iterate(f func(*Tag, io.Reader) error, tagUpperBound ...string) error {
	if err := dr.getChunk(); err != nil {
		return err
	}
	for {
		tag, err := dr.Peek()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return err
		}
		if !BeforeBound(tag.Id, tagUpperBound...) {
			return nil
		}
		if err := f(tag, bytes.NewReader(dr.chunk[dr.offset:dr.offset+tag.SizeBytes])); err != nil {
			return err
		}
		dr.tags = dr.tags[1:]
		dr.offset += tag.SizeBytes
	}
}

func (dr *DataReader) getChunk() error {
	dr.getChunkMu.Lock()
	defer dr.getChunkMu.Unlock()
	if dr.chunk != nil {
		return nil
	}
	// Use seed chunk if possible.
	if dr.seed != nil && dr.dataRef.ChunkInfo.Chunk.Hash == dr.seed.dataRef.ChunkInfo.Chunk.Hash {
		if err := dr.seed.getChunk(); err != nil {
			return err
		}
		dr.chunk = dr.seed.chunk
		return nil
	}
	// Get chunk from object storage.
	objR, err := dr.objC.Reader(dr.ctx, path.Join(prefix, dr.dataRef.ChunkInfo.Chunk.Hash), 0, 0)
	if err != nil {
		return err
	}
	defer objR.Close()
	gzipR, err := gzip.NewReader(objR)
	if err != nil {
		return err
	}
	defer gzipR.Close()
	buf := &bytes.Buffer{}
	if _, err := io.Copy(buf, gzipR); err != nil {
		return err
	}
	dr.chunk = buf.Bytes()
	return nil
}

// BeforeBound checks if the passed in string is before the string bound (exclusive).
// The string bound is optional, so if no string bound is passed then it returns true.
func BeforeBound(str string, strBound ...string) bool {
	return len(strBound) == 0 || str < strBound[0]
}

// Get writes the data referenced by the data reference.
func (dr *DataReader) Get(w io.Writer) error {
	if err := dr.getChunk(); err != nil {
		return err
	}
	data := dr.chunk[dr.dataRef.OffsetBytes : dr.dataRef.OffsetBytes+dr.dataRef.SizeBytes]
	if _, err := w.Write(data); err != nil {
		return err
	}
	return nil
}

// LimitReader creates a new data reader that reads a subset of the remaining tags in the current data reader.
// This tag subset is determined by the optional parameter tagUpperBound which specifies the upper bound (exclusive) of the new data reader.
// LimitReader will progress the current data reader past the tags in the tag subset.
// Data in the tag subset is fetched lazily by the new data reader.
func (dr *DataReader) LimitReader(tagUpperBound ...string) *DataReader {
	offset := dr.offset
	var tags []*Tag
	for {
		if len(dr.tags) == 0 {
			break
		}
		tag := dr.tags[0]
		if !BeforeBound(tag.Id, tagUpperBound...) {
			break
		}
		tags = append(tags, tag)
		dr.offset += tag.SizeBytes
		dr.tags = dr.tags[1:]
	}
	return &DataReader{
		ctx:     dr.ctx,
		objC:    dr.objC,
		dataRef: dr.dataRef,
		offset:  offset,
		tags:    tags,
		seed:    dr,
	}
}
<|endoftext|>


<commit_before>// Implementation of an R-Way Trie data structure.
//
// A Trie has a root Node which is the base of the tree.
// Each subsequent Node has a letter and children, which are
// nodes that have letter values associated with them.
package trie

import (
	"sort"
	"sync"
)

type Node struct {
	val      rune
	term     bool
	depth    int
	meta     interface{}
	mask     uint64
	parent   *Node
	children map[rune]*Node
}

type Trie struct {
	mu   sync.Mutex
	root *Node
	size int
}

type ByKeys []string

func (a ByKeys) Len() int           { return len(a) }
func (a ByKeys) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByKeys) Less(i, j int) bool { return len(a[i]) < len(a[j]) }

const nul = 0x0

// Creates a new Trie with an initialized root Node.
func New() *Trie {
	return &Trie{
		root: &Node{children: make(map[rune]*Node), depth: 0},
		size: 0,
	}
}

// Returns the root node for the Trie.
func (t *Trie) Root() *Node {
	return t.root
}

// Adds the key to the Trie, including meta data. Meta data
// is stored as `interface{}` and must be type cast by
// the caller.
func (t *Trie) Add(key string, meta interface{}) *Node {
	t.mu.Lock()
	defer t.mu.Unlock()

	t.size++
	runes := []rune(key)
	bitmask := maskruneslice(runes)
	node := t.root
	node.mask |= bitmask
	for i := range runes {
		r := runes[i]
		bitmask = maskruneslice(runes[i:])
		if n, ok := node.children[r]; ok {
			node = n
			node.mask |= bitmask
		} else {
			node = node.NewChild(r, bitmask, nil, false)
		}
	}
	node = node.NewChild(nul, 0, meta, true)
	return node
}

// Finds and returns meta data associated
// with `key`.
func (t *Trie) Find(key string) (*Node, bool) {
	node := findNode(t.Root(), []rune(key))
	if node == nil {
		return nil, false
	}

	node, ok := node.Children()[nul]
	if !ok || !node.term {
		return nil, false
	}

	return node, true
}

func (t *Trie) HasKeysWithPrefix(key string) bool {
	node := findNode(t.Root(), []rune(key))
	return node != nil
}

// Removes a key from the trie, ensuring that
// all bitmasks up to root are appropriately recalculated.
func (t *Trie) Remove(key string) {
	var (
		i    int
		rs   = []rune(key)
		node = findNode(t.Root(), []rune(key))
	)

	t.mu.Lock()
	defer t.mu.Unlock()

	t.size--
	for n := node.Parent(); n != nil; n = n.Parent() {
		i++
		if len(n.Children()) > 1 {
			r := rs[len(rs)-i]
			n.RemoveChild(r)
			break
		}
	}
}

// Returns all the keys currently stored in the trie.
func (t *Trie) Keys() []string {
	return t.PrefixSearch("")
}

// Performs a fuzzy search against the keys in the trie.
func (t Trie) FuzzySearch(pre string) []string {
	keys := fuzzycollect(t.Root(), []rune(pre))
	sort.Sort(ByKeys(keys))
	return keys
}

// Performs a prefix search against the keys in the trie.
func (t Trie) PrefixSearch(pre string) []string {
	node := findNode(t.Root(), []rune(pre))
	if node == nil {
		return nil
	}

	return collect(node)
}

// Creates and returns a pointer to a new child for the node.
func (n *Node) NewChild(val rune, bitmask uint64, meta interface{}, term bool) *Node {
	node := &Node{
		val:      val,
		mask:     bitmask,
		term:     term,
		meta:     meta,
		parent:   n,
		children: make(map[rune]*Node),
		depth:    n.depth + 1,
	}
	n.children[val] = node
	n.mask |= bitmask
	return node
}

func (n *Node) RemoveChild(r rune) {
	delete(n.children, r)
	for nd := n.parent; nd != nil; nd = nd.parent {
		nd.mask ^= nd.mask
		nd.mask |= uint64(1) << uint64(nd.val-'a')
		for _, c := range nd.children {
			nd.mask |= c.mask
		}
	}
}

// Returns the parent of this node.
func (n Node) Parent() *Node {
	return n.parent
}

// Returns the meta information of this node.
func (n Node) Meta() interface{} {
	return n.meta
}

// Returns the children of this node.
func (n Node) Children() map[rune]*Node {
	return n.children
}

func (n Node) Terminating() bool {
	return n.term
}

func (n Node) Val() rune {
	return n.val
}

func (n Node) Depth() int {
	return n.depth
}

// Returns a uint64 representing the current
// mask of this node.
func (n Node) Mask() uint64 {
	return n.mask
}

func findNode(node *Node, runes []rune) *Node {
	if node == nil {
		return nil
	}

	if len(runes) == 0 {
		return node
	}

	n, ok := node.Children()[runes[0]]
	if !ok {
		return nil
	}

	var nrunes []rune
	if len(runes) > 1 {
		nrunes = runes[1:]
	} else {
		nrunes = runes[0:0]
	}

	return findNode(n, nrunes)
}

func maskruneslice(rs []rune) uint64 {
	var m uint64
	for _, r := range rs {
		m |= uint64(1) << uint64(r-'a')
	}
	return m
}

func collect(node *Node) []string {
	var (
		keys []string
		n    *Node
		i    int
	)
	nodes := []*Node{node}
	for l := len(nodes); l != 0; l = len(nodes) {
		i = l - 1
		n = nodes[i]
		nodes = nodes[:i]
		for _, c := range n.children {
			nodes = append(nodes, c)
		}
		if n.term {
			word := ""
			for p := n.parent; p.depth != 0; p = p.parent {
				word = string(p.val) + word
			}
			keys = append(keys, word)
		}
	}
	return keys
}

type potentialSubtree struct {
	idx  int
	node *Node
}

func fuzzycollect(node *Node, partial []rune) []string {
	var (
		m    uint64
		i    int
		p    potentialSubtree
		keys []string
	)

	potential := []potentialSubtree{potentialSubtree{node: node, idx: 0}}
	for l := len(potential); l > 0; l = len(potential) {
		i = l - 1
		p = potential[i]
		potential = potential[:i]
		m = maskruneslice(partial[p.idx:])
		if (p.node.mask & m) != m {
			continue
		}

		if p.node.val == partial[p.idx] {
			p.idx++
			if p.idx == len(partial) {
				keys = append(keys, collect(p.node)...)
				continue
			}
		}

		for _, c := range p.node.children {
			potential = append(potential, potentialSubtree{node: c, idx: p.idx})
		}
	}
	return keys
}
<commit_msg>optimize collect() by introducing full path string in Node struct<commit_after>// Implementation of an R-Way Trie data structure.
//
// A Trie has a root Node which is the base of the tree.
// Each subsequent Node has a letter and children, which are
// nodes that have letter values associated with them.
package trie

import (
	"sort"
	"sync"
)

type Node struct {
	val      rune
	path     string
	term     bool
	depth    int
	meta     interface{}
	mask     uint64
	parent   *Node
	children map[rune]*Node
}

type Trie struct {
	mu   sync.Mutex
	root *Node
	size int
}

type ByKeys []string

func (a ByKeys) Len() int           { return len(a) }
func (a ByKeys) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByKeys) Less(i, j int) bool { return len(a[i]) < len(a[j]) }

const nul = 0x0

// Creates a new Trie with an initialized root Node.
func New() *Trie {
	return &Trie{
		root: &Node{children: make(map[rune]*Node), depth: 0},
		size: 0,
	}
}

// Returns the root node for the Trie.
func (t *Trie) Root() *Node {
	return t.root
}

// Adds the key to the Trie, including meta data. Meta data
// is stored as `interface{}` and must be type cast by
// the caller.
func (t *Trie) Add(key string, meta interface{}) *Node {
	t.mu.Lock()
	defer t.mu.Unlock()

	t.size++
	runes := []rune(key)
	bitmask := maskruneslice(runes)
	node := t.root
	node.mask |= bitmask
	cp := make([]rune, 0, len(runes))
	for i := range runes {
		r := runes[i]
		cp = append(cp, r)
		bitmask = maskruneslice(runes[i:])
		if n, ok := node.children[r]; ok {
			node = n
			node.mask |= bitmask
		} else {
			node = node.NewChild(r, string(cp), bitmask, nil, false)
		}
	}
	node = node.NewChild(nul, key, 0, meta, true)
	return node
}

// Finds and returns meta data associated
// with `key`.
func (t *Trie) Find(key string) (*Node, bool) {
	node := findNode(t.Root(), []rune(key))
	if node == nil {
		return nil, false
	}

	node, ok := node.Children()[nul]
	if !ok || !node.term {
		return nil, false
	}

	return node, true
}

func (t *Trie) HasKeysWithPrefix(key string) bool {
	node := findNode(t.Root(), []rune(key))
	return node != nil
}

// Removes a key from the trie, ensuring that
// all bitmasks up to root are appropriately recalculated.
func (t *Trie) Remove(key string) {
	var (
		i    int
		rs   = []rune(key)
		node = findNode(t.Root(), []rune(key))
	)

	t.mu.Lock()
	defer t.mu.Unlock()

	t.size--
	for n := node.Parent(); n != nil; n = n.Parent() {
		i++
		if len(n.Children()) > 1 {
			r := rs[len(rs)-i]
			n.RemoveChild(r)
			break
		}
	}
}

// Returns all the keys currently stored in the trie.
func (t *Trie) Keys() []string {
	return t.PrefixSearch("")
}

// Performs a fuzzy search against the keys in the trie.
func (t Trie) FuzzySearch(pre string) []string {
	keys := fuzzycollect(t.Root(), []rune(pre))
	sort.Sort(ByKeys(keys))
	return keys
}

// Performs a prefix search against the keys in the trie.
func (t Trie) PrefixSearch(pre string) []string {
	node := findNode(t.Root(), []rune(pre))
	if node == nil {
		return nil
	}

	return collect(node)
}

// Creates and returns a pointer to a new child for the node.
func (parent *Node) NewChild(val rune, path string, bitmask uint64, meta interface{}, term bool) *Node {
	node := &Node{
		val:      val,
		path:    path,
		mask:     bitmask,
		term:     term,
		meta:     meta,
		parent:   parent,
		children: make(map[rune]*Node),
		depth:    parent.depth + 1,
	}
	parent.children[node.val] = node
	parent.mask |= bitmask
	return node
}

func (n *Node) RemoveChild(r rune) {
	delete(n.children, r)
	for nd := n.parent; nd != nil; nd = nd.parent {
		nd.mask ^= nd.mask
		nd.mask |= uint64(1) << uint64(nd.val-'a')
		for _, c := range nd.children {
			nd.mask |= c.mask
		}
	}
}

// Returns the parent of this node.
func (n Node) Parent() *Node {
	return n.parent
}

// Returns the meta information of this node.
func (n Node) Meta() interface{} {
	return n.meta
}

// Returns the children of this node.
func (n Node) Children() map[rune]*Node {
	return n.children
}

func (n Node) Terminating() bool {
	return n.term
}

func (n Node) Val() rune {
	return n.val
}

func (n Node) Depth() int {
	return n.depth
}

// Returns a uint64 representing the current
// mask of this node.
func (n Node) Mask() uint64 {
	return n.mask
}

func findNode(node *Node, runes []rune) *Node {
	if node == nil {
		return nil
	}

	if len(runes) == 0 {
		return node
	}

	n, ok := node.Children()[runes[0]]
	if !ok {
		return nil
	}

	var nrunes []rune
	if len(runes) > 1 {
		nrunes = runes[1:]
	} else {
		nrunes = runes[0:0]
	}

	return findNode(n, nrunes)
}

func findPath(node *Node, runes []rune) []*Node {
	if node == nil {
		return []*Node{}
	}

	if len(runes) == 0 {
		return []*Node{node}
	}

	n, ok := node.Children()[runes[0]]
	if !ok {
		return nil
	}

	var nrunes []rune
	if len(runes) > 1 {
		nrunes = runes[1:]
	} else {
		nrunes = runes[0:0]
	}

	return append(findPath(n, nrunes), node)
}

func maskruneslice(rs []rune) uint64 {
	var m uint64
	for _, r := range rs {
		m |= uint64(1) << uint64(r-'a')
	}
	return m
}

func collect(node *Node) []string {
	var (
		keys []string
		n    *Node
		i    int
	)
	nodes := []*Node{node}
	for l := len(nodes); l != 0; l = len(nodes) {
		i = l - 1
		n = nodes[i]
		nodes = nodes[:i]
		for _, c := range n.children {
			nodes = append(nodes, c)
		}
		if n.term {
			word := n.path
			keys = append(keys, word)
		}
	}
	return keys
}

type potentialSubtree struct {
	idx  int
	node *Node
}

func fuzzycollect(node *Node, partial []rune) []string {
	var (
		m    uint64
		i    int
		p    potentialSubtree
		keys []string
	)

	potential := []potentialSubtree{potentialSubtree{node: node, idx: 0}}
	for l := len(potential); l > 0; l = len(potential) {
		i = l - 1
		p = potential[i]
		potential = potential[:i]
		m = maskruneslice(partial[p.idx:])
		if (p.node.mask & m) != m {
			continue
		}

		if p.node.val == partial[p.idx] {
			p.idx++
			if p.idx == len(partial) {
				keys = append(keys, collect(p.node)...)
				continue
			}
		}

		for _, c := range p.node.children {
			potential = append(potential, potentialSubtree{node: c, idx: p.idx})
		}
	}
	return keys
}
<|endoftext|>


<commit_before>package main

const (
	groupPlayer = iota
	groupEnemy
)

type unitID uint64

type unit struct {
	id         unitID
	playerName string
	unitName   string
	group      uint8
	seat       uint8
	hp         int32
	mp         int32
	us         *unitStatistics
	um         *unitModification
	operators  map[operator]bool
	dispatcher *eventDispatcher
	game       *game
}

// newUnit initializes a unit
func newUnit(g *game) *unit {
	return &unit{
		us:         &unitStatistics{},
		um:         &unitModification{},
		operators:  make(map[operator]bool),
		dispatcher: newEventDispatcher(),
		game:       g,
	}
}

func (u *unit) now() gameTime {
	return u.game.now()
}

func (u *unit) publish(m message) {
	u.game.publish(m)
}

func (u *unit) isAlive() bool {
	return u.hp > 0
}

func (u *unit) isDead() bool {
	return u.hp <= 0
}

func (u *unit) health() int32 {
	return u.us.health
}

func (u *unit) healthRegeneration() int32 {
	return u.us.healthRegeneration
}

func (u *unit) mana() int32 {
	return u.us.mana
}

func (u *unit) manaRegeneration() int32 {
	return u.us.manaRegeneration
}

func (u *unit) armor() int32 {
	return u.us.armor + u.um.armor
}

func (u *unit) magicResistance() int32 {
	return u.us.magicResistance + u.um.magicResistance
}

func (u *unit) criticalStrikeChance() int32 {
	return u.us.criticalStrikeChance + u.um.criticalStrikeChance
}

func (u *unit) criticalStrikeDamage() int32 {
	return u.us.criticalStrikeDamage + u.um.criticalStrikeDamage
}

func (u *unit) cooldownReduction() int32 {
	return u.us.cooldownReduction + u.um.cooldownReduction
}

func (u *unit) threatFactor() int32 {
	return u.us.threatFactor + u.um.threatFactor
}

// attachOperator adds the operator
func (u *unit) attachOperator(o operator) {
	u.operators[o] = true
	o.onAttach()
}

// detachOperator removes the operator
func (u *unit) detachOperator(o operator) {
	delete(u.operators, o)
	o.onDetach()
}

// addEventHandler registers the eventHandler
func (u *unit) addEventHandler(h eventHandler, e event) {
	u.dispatcher.addEventHandler(h, e)
}

// addEventHandler unregisters the eventHandler
func (u *unit) removeEventHandler(h eventHandler, e event) {
	u.dispatcher.removeEventHandler(h, e)
}

// triggerEvent triggers the event
func (u *unit) triggerEvent(e event) {
	u.dispatcher.triggerEvent(e)
}

// gameTick triggers onComplete iff the operator is completed
func (u *unit) gameTick() {
	if u.isDead() {
		return
	}
	u.triggerEvent(eventGameTick)
}

// statsTick performs regeneration and triggers statsTick
func (u *unit) statsTick() {
	if u.isDead() {
		return
	}
	u.performHealthRegeneration()
	u.performManaRegeneration()
	u.triggerEvent(eventStatsTick)
}

// performHealthRegeneration performs health regeneration
func (u *unit) performHealthRegeneration() {
	reg := u.healthRegeneration() + u.hp - u.health()
	if reg < 0 {
		return
	}
	u.hp += reg
	u.publish(message{
		// todo pack message
		t: outHealthReg,
	})
}

// performManaRegeneration performs mana regeneration
func (u *unit) performManaRegeneration() {
	reg := u.manaRegeneration() + u.mp - u.mana()
	if reg < 0 {
		return
	}
	u.mp += reg
	u.publish(message{
		// todo pack message
		t: outManaReg,
	})
}

// updateModification updates the unitModification
func (u *unit) updateModification() {
	u.um = &unitModification{}
	for o := range u.operators {
		if _, ok := o.(*modifier); ok {
			u.um.add(o.(*modifier).um)
		}
	}
	u.triggerEvent(eventStats)
}
<commit_msg>Add unit.maxHealth and unit.maxMana<commit_after>package main

const (
	groupPlayer = iota
	groupEnemy
)

type unitID uint64

type unit struct {
	id         unitID
	playerName string
	unitName   string
	group      uint8
	seat       uint8
	ur         *unitResource
	us         *unitStatistics
	um         *unitModification
	operators  map[operator]bool
	dispatcher *eventDispatcher
	game       *game
}

type unitResource struct {
	health int32
	mana   int32
}

// newUnit initializes a unit
func newUnit(g *game) *unit {
	return &unit{
		us:         &unitStatistics{},
		um:         &unitModification{},
		operators:  make(map[operator]bool),
		dispatcher: newEventDispatcher(),
		game:       g,
	}
}

func (u *unit) now() gameTime {
	return u.game.now()
}

func (u *unit) publish(m message) {
	u.game.publish(m)
}

func (u *unit) isAlive() bool {
	return u.ur.health > 0
}

func (u *unit) isDead() bool {
	return u.ur.health <= 0
}

func (u *unit) health() int32 {
	return u.ur.health
}

func (u *unit) maxHealth() int32 {
	return u.us.health
}

func (u *unit) healthRegeneration() int32 {
	return u.us.healthRegeneration
}

func (u *unit) mana() int32 {
	return u.ur.mana
}

func (u *unit) maxMana() int32 {
	return u.us.mana
}

func (u *unit) manaRegeneration() int32 {
	return u.us.manaRegeneration
}

func (u *unit) armor() int32 {
	return u.us.armor + u.um.armor
}

func (u *unit) magicResistance() int32 {
	return u.us.magicResistance + u.um.magicResistance
}

func (u *unit) criticalStrikeChance() int32 {
	return u.us.criticalStrikeChance + u.um.criticalStrikeChance
}

func (u *unit) criticalStrikeDamage() int32 {
	return u.us.criticalStrikeDamage + u.um.criticalStrikeDamage
}

func (u *unit) cooldownReduction() int32 {
	return u.us.cooldownReduction + u.um.cooldownReduction
}

func (u *unit) threatFactor() int32 {
	return u.us.threatFactor + u.um.threatFactor
}

// attachOperator adds the operator
func (u *unit) attachOperator(o operator) {
	u.operators[o] = true
	o.onAttach()
}

// detachOperator removes the operator
func (u *unit) detachOperator(o operator) {
	delete(u.operators, o)
	o.onDetach()
}

// addEventHandler registers the eventHandler
func (u *unit) addEventHandler(h eventHandler, e event) {
	u.dispatcher.addEventHandler(h, e)
}

// addEventHandler unregisters the eventHandler
func (u *unit) removeEventHandler(h eventHandler, e event) {
	u.dispatcher.removeEventHandler(h, e)
}

// triggerEvent triggers the event
func (u *unit) triggerEvent(e event) {
	u.dispatcher.triggerEvent(e)
}

// gameTick triggers onComplete iff the operator is completed
func (u *unit) gameTick() {
	if u.isDead() {
		return
	}
	u.triggerEvent(eventGameTick)
}

// statsTick performs regeneration and triggers statsTick
func (u *unit) statsTick() {
	if u.isDead() {
		return
	}
	u.performHealthRegeneration()
	u.performManaRegeneration()
	u.triggerEvent(eventStatsTick)
}

// performHealthRegeneration performs health regeneration
func (u *unit) performHealthRegeneration() {
	reg := u.healthRegeneration() + u.health() - u.maxHealth()
	if reg < 0 {
		return
	}
	u.ur.health += reg
	u.publish(message{
		// todo pack message
		t: outHealthReg,
	})
}

// performManaRegeneration performs mana regeneration
func (u *unit) performManaRegeneration() {
	reg := u.manaRegeneration() + u.mana() - u.maxMana()
	if reg < 0 {
		return
	}
	u.ur.mana += reg
	u.publish(message{
		// todo pack message
		t: outManaReg,
	})
}

// updateModification updates the unitModification
func (u *unit) updateModification() {
	u.um = &unitModification{}
	for o := range u.operators {
		if _, ok := o.(*modifier); ok {
			u.um.add(o.(*modifier).um)
		}
	}
	u.triggerEvent(eventStats)
}
<|endoftext|>


<commit_before>// Package upnp provides a simple and opinionated interface to UPnP-enabled
// routers, allowing users to forward ports and discover their external IP
// address. Specific quirks:
//
// - When attempting to discover UPnP-enabled routers on the network, only the
// first such router is returned. If you have multiple routers, this may cause
// some trouble. But why would you do that?
//
// - Forwarded ports are always symmetric, e.g. the router's port 9980 will be
// mapped to the client's port 9980. This will be unacceptable for some
// purposes, but too bad. Symmetric mappings are the desired behavior 99% of
// the time, and they save a function argument.
//
// - TCP and UDP protocols are forwarded together.
//
// - Ports are forwarded permanently. Some other implementations lease a port
// mapping for a set duration, and then renew it periodically. This is nice,
// because it means mappings won't stick around after they've served their
// purpose. Unfortunately, some routers only support permanent mappings, so this
// package has chosen to support the lowest common denominator. To un-forward a
// port, you must use the Clear function (or do it manually).
//
// Once you've discovered your router, you can retrieve its address by calling
// its Location method. This address can be supplied to Load to connect to the
// router directly, which is much faster than calling Discover.
package upnp

import (
	"context"
	"errors"
	"net"
	"net/url"
	"strings"
	"time"

	"gitlab.com/NebulousLabs/fastrand"
	"gitlab.com/NebulousLabs/go-upnp/goupnp"
	"gitlab.com/NebulousLabs/go-upnp/goupnp/dcps/internetgateway1"
)

// An IGD provides an interface to the most commonly used functions of an
// Internet Gateway Device: discovering the external IP, and forwarding ports.
type IGD struct {
	// This interface is satisfied by the internetgateway1.WANIPConnection1
	// and internetgateway1.WANPPPConnection1 types.
	client interface {
		GetExternalIPAddress() (string, error)
		AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
		GetSpecificPortMappingEntry(string, uint16, string) (uint16, string, bool, string, uint32, error)
		DeletePortMapping(string, uint16, string) error
		GetServiceClient() *goupnp.ServiceClient
	}
}

// ExternalIP returns the router's external IP.
func (d *IGD) ExternalIP() (string, error) {
	return d.client.GetExternalIPAddress()
}

// IsForwardedTCP checks whether a specific TCP port is forwarded to this host
func (d *IGD) IsForwardedTCP(port uint16) (bool, error) {
	return d.checkForward(port, "TCP")
}

// IsForwardedUDP checks whether a specific UDP port is forwarded to this host
func (d *IGD) IsForwardedUDP(port uint16) (bool, error) {
	return d.checkForward(port, "UDP")
}

// checkForward checks whether a specific TCP or UDP port is forwarded to this host
func (d *IGD) checkForward(port uint16, proto string) (bool, error) {
	time.Sleep(time.Millisecond)
	_, _, enabled, _, _, errClient := d.client.GetSpecificPortMappingEntry("", port, proto)

	//DEBUG
	//newPort, intClient, enabled, desc, duration, errClient := d.client.GetSpecificPortMappingEntry("", port, proto)
	//fmt.Println("-----")
	//fmt.Println("newPort: ", newPort)
	//fmt.Println("intClient: ", intClient)
	//fmt.Println("enabled: ", enabled)
	//fmt.Println("desc: ", desc)
	//fmt.Println("duration: ", duration)
	//fmt.Println("err: ", errClient)
	//fmt.Println("-----")

	if errClient != nil {
		// 714 "NoSuchEntryInArray" means that there is no such forwarding
		matched, _ := regexp.MatchString("<errorCode>714</errorCode>", errClient.Error())
		if matched == true {
			return false, nil
		}
		return false, errClient
	}

	if enabled == true {
		return true, nil
	}
	return false, nil
}

// Forward forwards the specified port, and adds its description to the
// router's port mapping table.
func (d *IGD) Forward(port uint16, desc string) error {
	ip, err := d.getInternalIP()
	if err != nil {
		return err
	}

	time.Sleep(time.Millisecond)
	err = d.client.AddPortMapping("", port, "TCP", port, ip, true, desc, 0)
	if err != nil {
		return err
	}

	time.Sleep(time.Millisecond)
	return d.client.AddPortMapping("", port, "UDP", port, ip, true, desc, 0)
}

// Clear un-forwards a port, removing it from the router's port mapping table.
func (d *IGD) Clear(port uint16) error {
	time.Sleep(time.Millisecond)
	tcpErr := d.client.DeletePortMapping("", port, "TCP")
	time.Sleep(time.Millisecond)
	udpErr := d.client.DeletePortMapping("", port, "UDP")

	// only return an error if both deletions failed
	if tcpErr != nil && udpErr != nil {
		return tcpErr
	}
	return nil
}

// Location returns the URL of the router, for future lookups (see Load).
func (d *IGD) Location() string {
	return d.client.GetServiceClient().Location.String()
}

// getInternalIP returns the user's local IP.
func (d *IGD) getInternalIP() (string, error) {
	host, _, _ := net.SplitHostPort(d.client.GetServiceClient().RootDevice.URLBase.Host)
	devIP := net.ParseIP(host)
	if devIP == nil {
		return "", errors.New("could not determine router's internal IP")
	}

	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}

	for _, iface := range ifaces {
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}

		for _, addr := range addrs {
			if x, ok := addr.(*net.IPNet); ok && x.Contains(devIP) {
				return x.IP.String(), nil
			}
		}
	}

	return "", errors.New("could not determine internal IP")
}

// Discover is deprecated; use DiscoverCtx instead.
func Discover() (*IGD, error) {
	return DiscoverCtx(context.Background())
}

// DiscoverCtx scans the local network for routers and returns the first
// UPnP-enabled router it encounters.  It will try up to 3 times to find a
// router, sleeping a random duration between each attempt.  This is to
// mitigate a race condition with many callers attempting to discover
// simultaneously.
func DiscoverCtx(ctx context.Context) (*IGD, error) {
	// TODO: if more than one client is found, only return those on the same
	// subnet as the user?
	maxTries := 3
	sleepTime := time.Millisecond * time.Duration(fastrand.Intn(5000))
	for try := 0; try < maxTries; try++ {
		pppclients, _, _ := internetgateway1.NewWANPPPConnection1Clients(ctx)
		if len(pppclients) > 0 {
			return &IGD{pppclients[0]}, nil
		}
		ipclients, _, _ := internetgateway1.NewWANIPConnection1Clients(ctx)
		if len(ipclients) > 0 {
			return &IGD{ipclients[0]}, nil
		}
		select {
		case <-ctx.Done():
			return nil, context.Canceled
		case <-time.After(sleepTime):
		}
		sleepTime *= 2
	}
	return nil, errors.New("no UPnP-enabled gateway found")
}

// Load connects to the router service specified by rawurl. This is much
// faster than Discover. Generally, Load should only be called with values
// returned by the IGD's Location method.
func Load(rawurl string) (*IGD, error) {
	loc, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}
	pppclients, _ := internetgateway1.NewWANPPPConnection1ClientsByURL(loc)
	if len(pppclients) > 0 {
		return &IGD{pppclients[0]}, nil
	}
	ipclients, _ := internetgateway1.NewWANIPConnection1ClientsByURL(loc)
	if len(ipclients) > 0 {
		return &IGD{ipclients[0]}, nil
	}
	return nil, errors.New("no UPnP-enabled gateway found at URL " + rawurl)
}
<commit_msg>clean up and use strings.Contains instead of RegEx<commit_after>// Package upnp provides a simple and opinionated interface to UPnP-enabled
// routers, allowing users to forward ports and discover their external IP
// address. Specific quirks:
//
// - When attempting to discover UPnP-enabled routers on the network, only the
// first such router is returned. If you have multiple routers, this may cause
// some trouble. But why would you do that?
//
// - Forwarded ports are always symmetric, e.g. the router's port 9980 will be
// mapped to the client's port 9980. This will be unacceptable for some
// purposes, but too bad. Symmetric mappings are the desired behavior 99% of
// the time, and they save a function argument.
//
// - TCP and UDP protocols are forwarded together.
//
// - Ports are forwarded permanently. Some other implementations lease a port
// mapping for a set duration, and then renew it periodically. This is nice,
// because it means mappings won't stick around after they've served their
// purpose. Unfortunately, some routers only support permanent mappings, so this
// package has chosen to support the lowest common denominator. To un-forward a
// port, you must use the Clear function (or do it manually).
//
// Once you've discovered your router, you can retrieve its address by calling
// its Location method. This address can be supplied to Load to connect to the
// router directly, which is much faster than calling Discover.
package upnp

import (
	"context"
	"errors"
	"net"
	"net/url"
	"strings"
	"time"

	"gitlab.com/NebulousLabs/fastrand"
	"gitlab.com/NebulousLabs/go-upnp/goupnp"
	"gitlab.com/NebulousLabs/go-upnp/goupnp/dcps/internetgateway1"
)

// An IGD provides an interface to the most commonly used functions of an
// Internet Gateway Device: discovering the external IP, and forwarding ports.
type IGD struct {
	// This interface is satisfied by the internetgateway1.WANIPConnection1
	// and internetgateway1.WANPPPConnection1 types.
	client interface {
		GetExternalIPAddress() (string, error)
		AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
		GetSpecificPortMappingEntry(string, uint16, string) (uint16, string, bool, string, uint32, error)
		DeletePortMapping(string, uint16, string) error
		GetServiceClient() *goupnp.ServiceClient
	}
}

// ExternalIP returns the router's external IP.
func (d *IGD) ExternalIP() (string, error) {
	return d.client.GetExternalIPAddress()
}

// IsForwardedTCP checks whether a specific TCP port is forwarded to this host
func (d *IGD) IsForwardedTCP(port uint16) (bool, error) {
	return d.checkForward(port, "TCP")
}

// IsForwardedUDP checks whether a specific UDP port is forwarded to this host
func (d *IGD) IsForwardedUDP(port uint16) (bool, error) {
	return d.checkForward(port, "UDP")
}

// checkForward checks whether a specific TCP or UDP port is forwarded to this host
func (d *IGD) checkForward(port uint16, proto string) (bool, error) {
	time.Sleep(time.Millisecond)
	_, _, enabled, _, _, err := d.client.GetSpecificPortMappingEntry("", port, proto)

	if err != nil {
		// 714 "NoSuchEntryInArray" means that there is no such forwarding
		if contained := strings.Contains(err.Error(), "<errorCode>714</errorCode>"); contained == true {
			return false, nil
		}
		return false, err
	}

	return enabled, nil
}

// Forward forwards the specified port, and adds its description to the
// router's port mapping table.
func (d *IGD) Forward(port uint16, desc string) error {
	ip, err := d.getInternalIP()
	if err != nil {
		return err
	}

	time.Sleep(time.Millisecond)
	err = d.client.AddPortMapping("", port, "TCP", port, ip, true, desc, 0)
	if err != nil {
		return err
	}

	time.Sleep(time.Millisecond)
	return d.client.AddPortMapping("", port, "UDP", port, ip, true, desc, 0)
}

// Clear un-forwards a port, removing it from the router's port mapping table.
func (d *IGD) Clear(port uint16) error {
	time.Sleep(time.Millisecond)
	tcpErr := d.client.DeletePortMapping("", port, "TCP")
	time.Sleep(time.Millisecond)
	udpErr := d.client.DeletePortMapping("", port, "UDP")

	// only return an error if both deletions failed
	if tcpErr != nil && udpErr != nil {
		return tcpErr
	}
	return nil
}

// Location returns the URL of the router, for future lookups (see Load).
func (d *IGD) Location() string {
	return d.client.GetServiceClient().Location.String()
}

// getInternalIP returns the user's local IP.
func (d *IGD) getInternalIP() (string, error) {
	host, _, _ := net.SplitHostPort(d.client.GetServiceClient().RootDevice.URLBase.Host)
	devIP := net.ParseIP(host)
	if devIP == nil {
		return "", errors.New("could not determine router's internal IP")
	}

	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}

	for _, iface := range ifaces {
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}

		for _, addr := range addrs {
			if x, ok := addr.(*net.IPNet); ok && x.Contains(devIP) {
				return x.IP.String(), nil
			}
		}
	}

	return "", errors.New("could not determine internal IP")
}

// Discover is deprecated; use DiscoverCtx instead.
func Discover() (*IGD, error) {
	return DiscoverCtx(context.Background())
}

// DiscoverCtx scans the local network for routers and returns the first
// UPnP-enabled router it encounters.  It will try up to 3 times to find a
// router, sleeping a random duration between each attempt.  This is to
// mitigate a race condition with many callers attempting to discover
// simultaneously.
func DiscoverCtx(ctx context.Context) (*IGD, error) {
	// TODO: if more than one client is found, only return those on the same
	// subnet as the user?
	maxTries := 3
	sleepTime := time.Millisecond * time.Duration(fastrand.Intn(5000))
	for try := 0; try < maxTries; try++ {
		pppclients, _, _ := internetgateway1.NewWANPPPConnection1Clients(ctx)
		if len(pppclients) > 0 {
			return &IGD{pppclients[0]}, nil
		}
		ipclients, _, _ := internetgateway1.NewWANIPConnection1Clients(ctx)
		if len(ipclients) > 0 {
			return &IGD{ipclients[0]}, nil
		}
		select {
		case <-ctx.Done():
			return nil, context.Canceled
		case <-time.After(sleepTime):
		}
		sleepTime *= 2
	}
	return nil, errors.New("no UPnP-enabled gateway found")
}

// Load connects to the router service specified by rawurl. This is much
// faster than Discover. Generally, Load should only be called with values
// returned by the IGD's Location method.
func Load(rawurl string) (*IGD, error) {
	loc, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}
	pppclients, _ := internetgateway1.NewWANPPPConnection1ClientsByURL(loc)
	if len(pppclients) > 0 {
		return &IGD{pppclients[0]}, nil
	}
	ipclients, _ := internetgateway1.NewWANIPConnection1ClientsByURL(loc)
	if len(ipclients) > 0 {
		return &IGD{ipclients[0]}, nil
	}
	return nil, errors.New("no UPnP-enabled gateway found at URL " + rawurl)
}
<|endoftext|>


<commit_before>// Package upnp provides a simple and opinionated interface to UPnP-enabled
// routers, allowing users to forward ports and discover their external IP
// address. Specific quirks:
//
// - When attempting to discover UPnP-enabled routers on the network, only the
// first such router is returned. If you have multiple routers, this may cause
// some trouble. But why would you do that?
//
// - Forwarded ports are always symmetric, e.g. the router's port 9980 will be
// mapped to the client's port 9980. This will be unacceptable for some
// purposes, but too bad. Symmetric mappings are the desired behavior 99% of
// the time, and they save a function argument.
//
// - TCP and UDP protocols are forwarded together.
//
// - Ports are forwarded permanently. Some other implementations lease a port
// mapping for a set duration, and then renew it periodically. This is nice,
// because it means mappings won't stick around after they've served their
// purpose. Unfortunately, some routers only support permanent mappings, so this
// package has chosen to support the lowest common denominator. To un-forward a
// port, you must use the Clear function (or do it manually).
//
// Once you've discovered your router, you can retrieve its address by calling
// its Location method. This address can be supplied to Load to connect to the
// router directly, which is much faster than calling Discover.
package upnp

import (
	"errors"
	"net"
	"net/url"
	"time"

	"github.com/NebulousLabs/Sia/crypto"
	"github.com/huin/goupnp"
	"github.com/huin/goupnp/dcps/internetgateway1"
)

// An IGD provides an interface to the most commonly used functions of an
// Internet Gateway Device: discovering the external IP, and forwarding ports.
type IGD struct {
	// This interface is satisfied by the internetgateway1.WANIPConnection1
	// and internetgateway1.WANPPPConnection1 types.
	client interface {
		GetExternalIPAddress() (string, error)
		AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
		DeletePortMapping(string, uint16, string) error
		GetServiceClient() *goupnp.ServiceClient
	}
}

// ExternalIP returns the router's external IP.
func (d *IGD) ExternalIP() (string, error) {
	return d.client.GetExternalIPAddress()
}

// Forward forwards the specified port, and adds its description to the
// router's port mapping table.
func (d *IGD) Forward(port uint16, desc string) error {
	ip, err := d.getInternalIP()
	if err != nil {
		return err
	}

	err = d.client.AddPortMapping("", port, "TCP", port, ip, true, desc, 0)
	if err != nil {
		return err
	}
	return d.client.AddPortMapping("", port, "UDP", port, ip, true, desc, 0)
}

// Clear un-forwards a port, removing it from the router's port mapping table.
func (d *IGD) Clear(port uint16) error {
	tcpErr := d.client.DeletePortMapping("", port, "TCP")
	udpErr := d.client.DeletePortMapping("", port, "UDP")
	// only return an error if both deletions failed
	if tcpErr != nil && udpErr != nil {
		return tcpErr
	}
	return nil
}

// Location returns the URL of the router, for future lookups (see Load).
func (d *IGD) Location() string {
	return d.client.GetServiceClient().Location.String()
}

// getInternalIP returns the user's local IP.
func (d *IGD) getInternalIP() (string, error) {
	host, _, _ := net.SplitHostPort(d.client.GetServiceClient().RootDevice.URLBase.Host)
	devIP := net.ParseIP(host)
	if devIP == nil {
		return "", errors.New("could not determine router's internal IP")
	}

	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}

	for _, iface := range ifaces {
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}

		for _, addr := range addrs {
			if x, ok := addr.(*net.IPNet); ok && x.Contains(devIP) {
				return x.IP.String(), nil
			}
		}
	}

	return "", errors.New("could not determine internal IP")
}

// Discover scans the local network for routers and returns the first
// UPnP-enabled router it encounters.  It will try up to 3 times to find a
// router, sleeping a random duration between each attempt.  This is to
// mitigate a race condition with many callers attempting to discover
// simultaniously.
//
// TODO: if more than one client is found, only return those on the same
// subnet as the user?
func Discover() (*IGD, error) {
	maxTries := 3
	sleepTime, _ := crypto.RandIntn(5)
	for try := 0; try < maxTries; try++ {
		time.Sleep(time.Second * time.Duration(sleepTime))
		pppclients, _, _ := internetgateway1.NewWANPPPConnection1Clients()
		if len(pppclients) > 0 {
			return &IGD{pppclients[0]}, nil
		}
		ipclients, _, _ := internetgateway1.NewWANIPConnection1Clients()
		if len(ipclients) > 0 {
			return &IGD{ipclients[0]}, nil
		}
		sleepTime *= 2
	}
	return nil, errors.New("no UPnP-enabled gateway found")
}

// Load connects to the router service specified by rawurl. This is much
// faster than Discover. Generally, Load should only be called with values
// returned by the IGD's Location method.
func Load(rawurl string) (*IGD, error) {
	loc, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}
	pppclients, _ := internetgateway1.NewWANPPPConnection1ClientsByURL(loc)
	if len(pppclients) > 0 {
		return &IGD{pppclients[0]}, nil
	}
	ipclients, _ := internetgateway1.NewWANIPConnection1ClientsByURL(loc)
	if len(ipclients) > 0 {
		return &IGD{ipclients[0]}, nil
	}
	return nil, errors.New("no UPnP-enabled gateway found at URL " + rawurl)
}
<commit_msg>use milliseconds for greater backoff distribution<commit_after>// Package upnp provides a simple and opinionated interface to UPnP-enabled
// routers, allowing users to forward ports and discover their external IP
// address. Specific quirks:
//
// - When attempting to discover UPnP-enabled routers on the network, only the
// first such router is returned. If you have multiple routers, this may cause
// some trouble. But why would you do that?
//
// - Forwarded ports are always symmetric, e.g. the router's port 9980 will be
// mapped to the client's port 9980. This will be unacceptable for some
// purposes, but too bad. Symmetric mappings are the desired behavior 99% of
// the time, and they save a function argument.
//
// - TCP and UDP protocols are forwarded together.
//
// - Ports are forwarded permanently. Some other implementations lease a port
// mapping for a set duration, and then renew it periodically. This is nice,
// because it means mappings won't stick around after they've served their
// purpose. Unfortunately, some routers only support permanent mappings, so this
// package has chosen to support the lowest common denominator. To un-forward a
// port, you must use the Clear function (or do it manually).
//
// Once you've discovered your router, you can retrieve its address by calling
// its Location method. This address can be supplied to Load to connect to the
// router directly, which is much faster than calling Discover.
package upnp

import (
	"errors"
	"net"
	"net/url"
	"time"

	"github.com/NebulousLabs/Sia/crypto"
	"github.com/huin/goupnp"
	"github.com/huin/goupnp/dcps/internetgateway1"
)

// An IGD provides an interface to the most commonly used functions of an
// Internet Gateway Device: discovering the external IP, and forwarding ports.
type IGD struct {
	// This interface is satisfied by the internetgateway1.WANIPConnection1
	// and internetgateway1.WANPPPConnection1 types.
	client interface {
		GetExternalIPAddress() (string, error)
		AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
		DeletePortMapping(string, uint16, string) error
		GetServiceClient() *goupnp.ServiceClient
	}
}

// ExternalIP returns the router's external IP.
func (d *IGD) ExternalIP() (string, error) {
	return d.client.GetExternalIPAddress()
}

// Forward forwards the specified port, and adds its description to the
// router's port mapping table.
func (d *IGD) Forward(port uint16, desc string) error {
	ip, err := d.getInternalIP()
	if err != nil {
		return err
	}

	err = d.client.AddPortMapping("", port, "TCP", port, ip, true, desc, 0)
	if err != nil {
		return err
	}
	return d.client.AddPortMapping("", port, "UDP", port, ip, true, desc, 0)
}

// Clear un-forwards a port, removing it from the router's port mapping table.
func (d *IGD) Clear(port uint16) error {
	tcpErr := d.client.DeletePortMapping("", port, "TCP")
	udpErr := d.client.DeletePortMapping("", port, "UDP")
	// only return an error if both deletions failed
	if tcpErr != nil && udpErr != nil {
		return tcpErr
	}
	return nil
}

// Location returns the URL of the router, for future lookups (see Load).
func (d *IGD) Location() string {
	return d.client.GetServiceClient().Location.String()
}

// getInternalIP returns the user's local IP.
func (d *IGD) getInternalIP() (string, error) {
	host, _, _ := net.SplitHostPort(d.client.GetServiceClient().RootDevice.URLBase.Host)
	devIP := net.ParseIP(host)
	if devIP == nil {
		return "", errors.New("could not determine router's internal IP")
	}

	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}

	for _, iface := range ifaces {
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}

		for _, addr := range addrs {
			if x, ok := addr.(*net.IPNet); ok && x.Contains(devIP) {
				return x.IP.String(), nil
			}
		}
	}

	return "", errors.New("could not determine internal IP")
}

// Discover scans the local network for routers and returns the first
// UPnP-enabled router it encounters.  It will try up to 3 times to find a
// router, sleeping a random duration between each attempt.  This is to
// mitigate a race condition with many callers attempting to discover
// simultaneously.
//
// TODO: if more than one client is found, only return those on the same
// subnet as the user?
func Discover() (*IGD, error) {
	maxTries := 3
	sleepMs, _ := crypto.RandIntn(5000)
	for try := 0; try < maxTries; try++ {
		time.Sleep(time.Millisecond * time.Duration(sleepMs))
		pppclients, _, _ := internetgateway1.NewWANPPPConnection1Clients()
		if len(pppclients) > 0 {
			return &IGD{pppclients[0]}, nil
		}
		ipclients, _, _ := internetgateway1.NewWANIPConnection1Clients()
		if len(ipclients) > 0 {
			return &IGD{ipclients[0]}, nil
		}
		sleepMs *= 2
	}
	return nil, errors.New("no UPnP-enabled gateway found")
}

// Load connects to the router service specified by rawurl. This is much
// faster than Discover. Generally, Load should only be called with values
// returned by the IGD's Location method.
func Load(rawurl string) (*IGD, error) {
	loc, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}
	pppclients, _ := internetgateway1.NewWANPPPConnection1ClientsByURL(loc)
	if len(pppclients) > 0 {
		return &IGD{pppclients[0]}, nil
	}
	ipclients, _ := internetgateway1.NewWANIPConnection1ClientsByURL(loc)
	if len(ipclients) > 0 {
		return &IGD{ipclients[0]}, nil
	}
	return nil, errors.New("no UPnP-enabled gateway found at URL " + rawurl)
}
<|endoftext|>


<commit_before>package wundergo

import "time"

// User contains information about a User.
type User struct {
	ID         uint      `json:"id"`
	Name       string    `json:"name"`
	Email      string    `json:"email"`
	CreatedAt  time.Time `json:"created_at"`
	UpdatedAt  time.Time `json:"updated_at"`
	Revision   uint      `json:"revision"`
	TypeString string    `json:"type"`
}
<commit_msg>Remove user.TypeString<commit_after>package wundergo

import "time"

// User contains information about a User.
type User struct {
	ID        uint      `json:"id"`
	Name      string    `json:"name"`
	Email     string    `json:"email"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	Revision  uint      `json:"revision"`
}
<|endoftext|>