<commit_before>package main

import (
	"context"
	"encoding/json"
	"log"
	"path/filepath"
	"regexp"

	"github.com/diginatu/nagome/nicolive"
)

var (
	broadIDRegex = regexp.MustCompile(`(lv|co)\d+`)
)

func processPluginMessage(cv *CommentViewer, m *Message) error {
	switch m.Domain {
	case DomainQuery:
		switch m.Command {
		case CommQueryBroadConnect:
			var err error
			var ct CtQueryBroadConnect
			if err = json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}

			broadMch := broadIDRegex.FindString(ct.BroadID)
			if broadMch == "" {
				cv.CreateEvNewDialog(CtUIDialogTypeWarn, "invalid BroadID", "no valid BroadID found in the ID text")
				return nicolive.MakeError(nicolive.ErrOther, "no valid BroadID found in the ID text")
			}

			lw := &nicolive.LiveWaku{Account: cv.Ac, BroadID: broadMch}
			if err = lw.FetchInformation(); err != nil {
				return err
			}

			cv.Disconnect()

			cv.Lw = lw
			cv.Cmm, err = nicolive.CommentConnect(context.TODO(), *cv.Lw, cv.prcdnle)
			if err != nil {
				return err
			}
			log.Println("connecting")

		case CommQueryBroadDisconnect:
			cv.Disconnect()

		case CommQueryBroadSendComment:
			if cv.Cmm == nil {
				return nicolive.MakeError(nicolive.ErrOther, "not connected to live")
			}
			var ct CtQueryBroadSendComment
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}
			cv.Cmm.SendComment(ct.Text, ct.Iyayo)

		case CommQueryAccountSet:
			var ct CtQueryAccountSet
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}
			if ct.Mail != "" {
				cv.Ac.Mail = ct.Mail
			}
			if ct.Pass != "" {
				cv.Ac.Pass = ct.Pass
			}
			if ct.Usersession != "" {
				cv.Ac.Usersession = ct.Usersession
			}

			cv.AntennaConnect()

		case CommQueryAccountLogin:
			err := cv.Ac.Login()
			if err != nil {
				if nerr, ok := err.(nicolive.Error); ok {
					cv.CreateEvNewDialog(CtUIDialogTypeWarn, "login error", nerr.Description())
				} else {
					cv.CreateEvNewDialog(CtUIDialogTypeWarn, "login error", err.Error())
				}
				return err
			}
			log.Println("logged in")
			cv.CreateEvNewDialog(CtUIDialogTypeInfo, "login succeeded", "login succeeded")

		case CommQueryAccountLoad:
			return cv.Ac.Load(filepath.Join(App.SavePath, accountFileName))

		case CommQueryAccountSave:
			return cv.Ac.Save(filepath.Join(App.SavePath, accountFileName))

		case CommQueryLogPrint:
			var ct CtQueryLogPrint
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}

			log.Printf("plug[%s] %s\n", cv.PluginName(m.prgno), ct.Text)

		case CommQuerySettingsSetCurrent:
			var ct CtQuerySettingsSetCurrent
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}

			cv.Settings = SettingsSlot(ct)

		case CommQuerySettingsSetAll:
			var ct CtQuerySettingsSetAll
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}

			App.SettingsSlots = SettingsSlots(ct)

		case CommQueryPlugEnable:
			var ct CtQueryPlugEnable
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}

			pl, err := cv.Plugin(ct.No)
			if err != nil {
				return err
			}
			pl.SetState(ct.Enable)
			if cv.Settings.PluginDisable == nil {
				cv.Settings.PluginDisable = make(map[string]bool)
			}
			cv.Settings.PluginDisable[pl.Name] = !ct.Enable

		default:
			return nicolive.MakeError(nicolive.ErrOther, "Message : invalid query command : "+m.Command)
		}

	case DomainAntenna:
		switch m.Command {
		case CommAntennaGot:
			if cv.Settings.AutoFollowNextWaku {
				var ct CtAntennaGot
				if err := json.Unmarshal(m.Content, &ct); err != nil {
					return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
				}
				if cv.Lw != nil && cv.Lw.Stream.CommunityID == ct.CommunityID {
					ct := CtQueryBroadConnect{ct.BroadID}
					log.Println("following to " + ct.BroadID)
					cv.Evch <- NewMessageMust(DomainQuery, CommQueryBroadConnect, ct)
				}
			}
		}
	}

	return nil
}

func processDirectMessage(cv *CommentViewer, m *Message) error {
	switch m.Command {
	case CommDirectPlugList:
		c := CtDirectngmPlugList{&cv.Pgns}
		t, err := NewMessage(DomainDirectngm, CommDirectngmPlugList, c)
		if err != nil {
			return nicolive.ErrFromStdErr(err)
		}
		cv.Pgns[m.prgno].WriteMess(t)
	case CommDirectSettingsCurrent:
		t, err := NewMessage(DomainDirectngm, CommDirectngmSettingsCurrent, CtDirectngmSettingsCurrent(cv.Settings))
		if err != nil {
			return nicolive.ErrFromStdErr(err)
		}
		cv.Pgns[m.prgno].WriteMess(t)
	case CommDirectSettingsAll:
		t, err := NewMessage(DomainDirectngm, CommDirectngmSettingsAll, CtDirectngmSettingsAll(App.SettingsSlots))
		if err != nil {
			return nicolive.ErrFromStdErr(err)
		}
		cv.Pgns[m.prgno].WriteMess(t)
	default:
		return nicolive.MakeError(nicolive.ErrOther, "Message : invalid query command : "+m.Command)
	}

	return nil
}
<commit_msg>applying current setting updates plugin status<commit_after>package main

import (
	"context"
	"encoding/json"
	"log"
	"path/filepath"
	"regexp"

	"github.com/diginatu/nagome/nicolive"
)

var (
	broadIDRegex = regexp.MustCompile(`(lv|co)\d+`)
)

func processPluginMessage(cv *CommentViewer, m *Message) error {
	switch m.Domain {
	case DomainQuery:
		switch m.Command {
		case CommQueryBroadConnect:
			var err error
			var ct CtQueryBroadConnect
			if err = json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}

			broadMch := broadIDRegex.FindString(ct.BroadID)
			if broadMch == "" {
				cv.CreateEvNewDialog(CtUIDialogTypeWarn, "invalid BroadID", "no valid BroadID found in the ID text")
				return nicolive.MakeError(nicolive.ErrOther, "no valid BroadID found in the ID text")
			}

			lw := &nicolive.LiveWaku{Account: cv.Ac, BroadID: broadMch}
			if err = lw.FetchInformation(); err != nil {
				return err
			}

			cv.Disconnect()

			cv.Lw = lw
			cv.Cmm, err = nicolive.CommentConnect(context.TODO(), *cv.Lw, cv.prcdnle)
			if err != nil {
				return err
			}
			log.Println("connecting")

		case CommQueryBroadDisconnect:
			cv.Disconnect()

		case CommQueryBroadSendComment:
			if cv.Cmm == nil {
				return nicolive.MakeError(nicolive.ErrOther, "not connected to live")
			}
			var ct CtQueryBroadSendComment
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}
			cv.Cmm.SendComment(ct.Text, ct.Iyayo)

		case CommQueryAccountSet:
			var ct CtQueryAccountSet
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}
			if ct.Mail != "" {
				cv.Ac.Mail = ct.Mail
			}
			if ct.Pass != "" {
				cv.Ac.Pass = ct.Pass
			}
			if ct.Usersession != "" {
				cv.Ac.Usersession = ct.Usersession
			}

			cv.AntennaConnect()

		case CommQueryAccountLogin:
			err := cv.Ac.Login()
			if err != nil {
				if nerr, ok := err.(nicolive.Error); ok {
					cv.CreateEvNewDialog(CtUIDialogTypeWarn, "login error", nerr.Description())
				} else {
					cv.CreateEvNewDialog(CtUIDialogTypeWarn, "login error", err.Error())
				}
				return err
			}
			log.Println("logged in")
			cv.CreateEvNewDialog(CtUIDialogTypeInfo, "login succeeded", "login succeeded")

		case CommQueryAccountLoad:
			return cv.Ac.Load(filepath.Join(App.SavePath, accountFileName))

		case CommQueryAccountSave:
			return cv.Ac.Save(filepath.Join(App.SavePath, accountFileName))

		case CommQueryLogPrint:
			var ct CtQueryLogPrint
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}

			log.Printf("plug[%s] %s\n", cv.PluginName(m.prgno), ct.Text)

		case CommQuerySettingsSetCurrent:
			var ct CtQuerySettingsSetCurrent
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}

			cv.Settings = SettingsSlot(ct)
			for _, p := range cv.Pgns {
				p.SetState(!cv.Settings.PluginDisable[p.Name])
			}

		case CommQuerySettingsSetAll:
			var ct CtQuerySettingsSetAll
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}

			App.SettingsSlots = SettingsSlots(ct)

		case CommQueryPlugEnable:
			var ct CtQueryPlugEnable
			if err := json.Unmarshal(m.Content, &ct); err != nil {
				return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
			}

			pl, err := cv.Plugin(ct.No)
			if err != nil {
				return err
			}
			pl.SetState(ct.Enable)
			if cv.Settings.PluginDisable == nil {
				cv.Settings.PluginDisable = make(map[string]bool)
			}
			cv.Settings.PluginDisable[pl.Name] = !ct.Enable

		default:
			return nicolive.MakeError(nicolive.ErrOther, "Message : invalid query command : "+m.Command)
		}

	case DomainAntenna:
		switch m.Command {
		case CommAntennaGot:
			if cv.Settings.AutoFollowNextWaku {
				var ct CtAntennaGot
				if err := json.Unmarshal(m.Content, &ct); err != nil {
					return nicolive.MakeError(nicolive.ErrOther, "JSON error in the content : "+err.Error())
				}
				if cv.Lw != nil && cv.Lw.Stream.CommunityID == ct.CommunityID {
					ct := CtQueryBroadConnect{ct.BroadID}
					log.Println("following to " + ct.BroadID)
					cv.Evch <- NewMessageMust(DomainQuery, CommQueryBroadConnect, ct)
				}
			}
		}
	}

	return nil
}

func processDirectMessage(cv *CommentViewer, m *Message) error {
	switch m.Command {
	case CommDirectPlugList:
		c := CtDirectngmPlugList{&cv.Pgns}
		t, err := NewMessage(DomainDirectngm, CommDirectngmPlugList, c)
		if err != nil {
			return nicolive.ErrFromStdErr(err)
		}
		cv.Pgns[m.prgno].WriteMess(t)
	case CommDirectSettingsCurrent:
		t, err := NewMessage(DomainDirectngm, CommDirectngmSettingsCurrent, CtDirectngmSettingsCurrent(cv.Settings))
		if err != nil {
			return nicolive.ErrFromStdErr(err)
		}
		cv.Pgns[m.prgno].WriteMess(t)
	case CommDirectSettingsAll:
		t, err := NewMessage(DomainDirectngm, CommDirectngmSettingsAll, CtDirectngmSettingsAll(App.SettingsSlots))
		if err != nil {
			return nicolive.ErrFromStdErr(err)
		}
		cv.Pgns[m.prgno].WriteMess(t)
	default:
		return nicolive.MakeError(nicolive.ErrOther, "Message : invalid query command : "+m.Command)
	}

	return nil
}
<|endoftext|>


<commit_before>// DO NOT USE THIS CODE.
package snakes

import (
	"math/big"
)

// Compute `a ** b % c`, just like `big.Int.Exp`, but do it in a way that is
// more constant time. This is useful for crypto stuff.
func MontgomeryLadderExp(a, b, c *big.Int) *big.Int {
	if a.Sign() == 0 && b.Sign() == 0 {
		return big.NewInt(1)
	}
	// For now we can't do the operations below in place, because we can't
	// mutate `a1`, since we don't "own" it. If we can make `a1` a copy of `a`
	// then we can do the below operations in place.
	a1 := a
	a2 := new(big.Int).Mul(a, a)
	for pos := b.BitLen() - 1; pos >= 0; pos-- {
		if b.Bit(pos) == 0 {
			a2 = new(big.Int).Mul(a2, a1)
			a1 = new(big.Int).Mul(a1, a1)
		} else {
			a1 = new(big.Int).Mul(a1, a2)
			a2 = new(big.Int).Mul(a2, a2)
		}

		a1.Mod(a1, c)
		a2.Mod(a2, c)
	}
	return a1
}
<commit_msg>Simplify<commit_after>// DO NOT USE THIS CODE.
package snakes

import (
	"math/big"
)

// Compute `a ** b % c`, just like `big.Int.Exp`, but do it in a way that is
// more constant time. This is useful for crypto stuff.
func MontgomeryLadderExp(a, b, c *big.Int) *big.Int {
	if a.Sign() == 0 && b.Sign() == 0 {
		return big.NewInt(1)
	}

	a1 := new(big.Int).Set(a)
	a2 := new(big.Int).Mul(a, a)
	for pos := b.BitLen() - 1; pos >= 0; pos-- {
		if b.Bit(pos) == 0 {
			a2.Mul(a2, a1)
			a1.Mul(a1, a1)
		} else {
			a1.Mul(a1, a2)
			a2.Mul(a2, a2)
		}

		a1.Mod(a1, c)
		a2.Mod(a2, c)
	}
	return a1
}
<|endoftext|>


<commit_before>// Copyright © 2017 The vt-go authors. All Rights Reserved.
// 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 vt

import (
	"bytes"
	"encoding/json"
	"fmt"
	"time"
)

// objectData is the structure that have the data returned by the API for an
// object.
type objectData struct {
	ID                string                   `json:"id,omitempty"`
	Type              string                   `json:"type,omitempty"`
	Attributes        map[string]interface{}   `json:"attributes,omitempty"`
	ContextAttributes map[string]interface{}   `json:"context_attributes,omitempty"`
	Relationships     map[string]*Relationship `json:"relationships,omitempty"`
	Links             *Links                   `json:"links,omitempty"`
}

// Object represents a VirusTotal API object.
type Object struct {
	// Contains the object's data as returned by the API.
	data objectData
	// Contains a list the attributes that have been modified via a call to
	// any of the SetXX methods.
	modifiedAttributes []string
}

// Links contains links related to an API object.
type Links struct {
	Self string `json:"self,omitempty"`
	Next string `json:"next,omitempty"`
}

// Relationship contains information about a related API object.
type Relationship struct {
	Data  json.RawMessage `json:"data,omitempty"`
	Links Links           `json:"links,omitempty"`

	// IsOneToOne is true if this is a one-to-one relationshio and False if
	// otherwise. If true RelatedObjects contains one object at most.
	IsOneToOne     bool
	RelatedObjects []Object
}

// NewObject creates a new object.
func NewObject(objType string) *Object {
	return &Object{data: objectData{
		Type:       objType,
		Attributes: make(map[string]interface{})}}
}

// ID returns the object's identifier.
func (obj *Object) ID() string {
	return obj.data.ID
}

// Type returns the object's type.
func (obj *Object) Type() string {
	return obj.data.Type
}

// MarshalJSON marshals a VirusTotal API object.
func (obj *Object) MarshalJSON() ([]byte, error) {
	return json.Marshal(obj.data)
}

// UnmarshalJSON unmarshals a VirusTotal API object from data.
func (obj *Object) UnmarshalJSON(data []byte) error {

	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.UseNumber()

	od := objectData{}
	if err := decoder.Decode(&od); err != nil {
		return err
	}

	obj.data = od

	for _, v := range obj.data.Relationships {
		var o Object
		// Try unmarshalling as an Object first, if it fails this is a
		// one-to-many relationship, so we try unmarshalling as an array.
		if err := json.Unmarshal(v.Data, &o); err == nil {
			v.IsOneToOne = true
			// If the value is null the ObjectDescriptor will have an empty
			// ID and Type.
			if o.data.ID == "" && o.data.Type == "" {
				v.RelatedObjects = nil
			} else {
				v.RelatedObjects = append(v.RelatedObjects, o)
			}
		} else {
			if err := json.Unmarshal(v.Data, &v.RelatedObjects); err != nil {
				return err
			}
		}
	}

	return nil
}

func (obj *Object) getAttributeNumber(name string) (n json.Number, err error) {
	if attrValue, attrExists := obj.data.Attributes[name]; attrExists {
		n, isNumber := attrValue.(json.Number)
		if !isNumber {
			err = fmt.Errorf("attribute \"%s\" is not a number", name)
		}
		return n, err
	}
	return n, fmt.Errorf("attribute \"%s\" does not exists", name)
}

func (obj *Object) getContextAttributeNumber(name string) (n json.Number, err error) {
	if attrValue, attrExists := obj.data.ContextAttributes[name]; attrExists {
		n, isNumber := attrValue.(json.Number)
		if !isNumber {
			err = fmt.Errorf("context attribute \"%s\" is not a number", name)
		}
		return n, err
	}
	return n, fmt.Errorf("context attribute \"%s\" does not exists", name)
}

// GetInt64 returns an attribute as an int64. It returns the attribute's
// value or an error if the attribute doesn't exist or is not a number.
func (obj *Object) GetInt64(name string) (int64, error) {
	n, err := obj.getAttributeNumber(name)
	if err == nil {
		return n.Int64()
	}
	return 0, err
}

// MustGetInt64 is like GetInt64, but it panic in case of error.
func (obj *Object) MustGetInt64(name string) int64 {
	result, err := obj.GetInt64(name)
	if err != nil {
		panic(err)
	}
	return result
}

// GetFloat64 returns an attribute as a float64. It returns the attribute's
// value or an error if the attribute doesn't exist or is not a number.
func (obj *Object) GetFloat64(name string) (float64, error) {
	n, err := obj.getAttributeNumber(name)
	if err == nil {
		return n.Float64()
	}
	return 0, err
}

// MustGetFloat64 is like GetFloat64, but it panic in case of error.
func (obj *Object) MustGetFloat64(name string) float64 {
	result, err := obj.GetFloat64(name)
	if err != nil {
		panic(err)
	}
	return result
}

// GetString returns an attribute as a string. It returns the attribute's
// valueor an error if the attribute doesn't exist or is not a string.
func (obj *Object) GetString(name string) (s string, err error) {
	if attrValue, attrExists := obj.data.Attributes[name]; attrExists {
		s, isString := attrValue.(string)
		if !isString {
			err = fmt.Errorf("attribute \"%s\" is not a string", name)
		}
		return s, err
	}
	return "", fmt.Errorf("attribute \"%s\" does not exists", name)
}

// MustGetString is like GetString, but it panic in case of error.
func (obj *Object) MustGetString(name string) string {
	result, err := obj.GetString(name)
	if err != nil {
		panic(err)
	}
	return result
}

// GetTime returns an attribute as a time. It returns the attribute's
// value or an error if the attribute doesn't exist or is not a time.
func (obj *Object) GetTime(name string) (t time.Time, err error) {
	n, err := obj.getAttributeNumber(name)
	if err == nil {
		i, err := n.Int64()
		return time.Unix(i, 0), err
	}
	return time.Unix(0, 0), err
}

// MustGetTime is like GetTime, but it panic in case of error.
func (obj *Object) MustGetTime(name string) time.Time {
	result, err := obj.GetTime(name)
	if err != nil {
		panic(err)
	}
	return result
}

// GetBool returns an attribute as a boolean. It returns the attribute's
// value or an error if the attribute doesn't exist or is not a boolean.
func (obj *Object) GetBool(name string) (b bool, err error) {
	if attrValue, attrExists := obj.data.Attributes[name]; attrExists {
		b, isBool := attrValue.(bool)
		if !isBool {
			err = fmt.Errorf("attribute \"%s\" is not a bool", name)
		}
		return b, err
	}
	return false, fmt.Errorf("attribute \"%s\" does not exists", name)
}

// MustGetBool is like GetTime, but it panic in case of error.
func (obj *Object) MustGetBool(name string) bool {
	result, err := obj.GetBool(name)
	if err != nil {
		panic(err)
	}
	return result
}

// GetContextInt64 returns a context attribute as an int64. It returns the
// attribute's value or an error if the attribute doesn't exist or is not a
// number.
func (obj *Object) GetContextInt64(name string) (int64, error) {
	n, err := obj.getContextAttributeNumber(name)
	if err == nil {
		return n.Int64()
	}
	return 0, err
}

// GetContextFloat64 returns a context attribute as an float64. It returns the
// attribute's value or an error if the attribute doesn't exist or is not a
// number.
func (obj *Object) GetContextFloat64(name string) (float64, error) {
	n, err := obj.getContextAttributeNumber(name)
	if err == nil {
		return n.Float64()
	}
	return 0, err
}

// GetContextString returns a context attribute as a string. It returns the
// attribute's value or an error if the attribute doesn't exist or is not a
// string.
func (obj *Object) GetContextString(name string) (s string, err error) {
	if attrValue, attrExists := obj.data.ContextAttributes[name]; attrExists {
		s, isString := attrValue.(string)
		if !isString {
			err = fmt.Errorf("context attribute \"%s\" is not a string", name)
		}
		return s, err
	}
	return "", fmt.Errorf("context attribute \"%s\" does not exists", name)
}

// GetContextBool returns a context attribute as a bool. It returns the
// attribute's value or an error if the attribute doesn't exist or is not a
// bool.
func (obj *Object) GetContextBool(name string) (b bool, err error) {
	if attrValue, attrExists := obj.data.ContextAttributes[name]; attrExists {
		b, isBool := attrValue.(bool)
		if !isBool {
			err = fmt.Errorf("context attribute \"%s\" is not a bool", name)
		}
		return b, err
	}
	return false, fmt.Errorf("context attribute \"%s\" does not exists", name)
}

// SetInt64 sets the value of an integer attribute.
func (obj *Object) SetInt64(name string, value int64) {
	currentValue, err := obj.GetInt64(name)
	if err != nil || currentValue != value {
		obj.modifiedAttributes = append(obj.modifiedAttributes, name)
		obj.data.Attributes[name] = value
	}
}

// SetFloat64 sets the value of an integer attribute.
func (obj *Object) SetFloat64(name string, value float64) {
	currentValue, err := obj.GetFloat64(name)
	if err != nil || currentValue != value {
		obj.modifiedAttributes = append(obj.modifiedAttributes, name)
		obj.data.Attributes[name] = value
	}
}

// SetString sets the value of a string attribute.
func (obj *Object) SetString(name, value string) {
	currentValue, err := obj.GetString(name)
	if err != nil || currentValue != value {
		obj.modifiedAttributes = append(obj.modifiedAttributes, name)
		obj.data.Attributes[name] = value
	}
}

// SetBool sets the value of a string attribute.
func (obj *Object) SetBool(name string, value bool) {
	currentValue, err := obj.GetBool(name)
	if err != nil || currentValue != value {
		obj.modifiedAttributes = append(obj.modifiedAttributes, name)
		obj.data.Attributes[name] = value
	}
}

// SetTime sets the value of a time attribute.
func (obj *Object) SetTime(name string, value time.Time) {
	currentValue, err := obj.GetTime(name)
	if err != nil || currentValue != value {
		obj.modifiedAttributes = append(obj.modifiedAttributes, name)
		obj.data.Attributes[name] = value
	}
}

// modifiedObject is a structure exactly like Object, but that implements the
// MarshalJSON interface differently. When a modifiedObject is marshalled as
// JSON only the attributes that has been modified are included. Context
// attributes, relationships and links are not included neither.
type modifiedObject Object

func (obj modifiedObject) MarshalJSON() ([]byte, error) {
	od := objectData{
		ID:         obj.data.ID,
		Type:       obj.data.Type,
		Attributes: make(map[string]interface{}),
	}
	for _, attr := range obj.modifiedAttributes {
		od.Attributes[attr] = obj.data.Attributes[attr]
	}
	return json.Marshal(&od)
}
<commit_msg>Implement constructor NewObjectWithID and refactor SetX methods.<commit_after>// Copyright © 2017 The vt-go authors. All Rights Reserved.
// 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 vt

import (
	"bytes"
	"encoding/json"
	"fmt"
	"time"
)

// objectData is the structure that have the data returned by the API for an
// object.
type objectData struct {
	ID                string                   `json:"id,omitempty"`
	Type              string                   `json:"type,omitempty"`
	Attributes        map[string]interface{}   `json:"attributes,omitempty"`
	ContextAttributes map[string]interface{}   `json:"context_attributes,omitempty"`
	Relationships     map[string]*Relationship `json:"relationships,omitempty"`
	Links             *Links                   `json:"links,omitempty"`
}

// Object represents a VirusTotal API object.
type Object struct {
	// Contains the object's data as returned by the API.
	data objectData
	// Contains a list the attributes that have been modified via a call to
	// any of the SetXX methods.
	modifiedAttributes []string
}

// Links contains links related to an API object.
type Links struct {
	Self string `json:"self,omitempty"`
	Next string `json:"next,omitempty"`
}

// Relationship contains information about a related API object.
type Relationship struct {
	Data  json.RawMessage `json:"data,omitempty"`
	Links Links           `json:"links,omitempty"`

	// IsOneToOne is true if this is a one-to-one relationshio and False if
	// otherwise. If true RelatedObjects contains one object at most.
	IsOneToOne     bool
	RelatedObjects []Object
}

// NewObject creates a new object.
func NewObject(objType string) *Object {
	return &Object{data: objectData{
		Type:       objType,
		Attributes: make(map[string]interface{})}}
}

// NewObjectWithID creates a new object with the specified ID.
func NewObjectWithID(objType, id string) *Object {
	return &Object{data: objectData{
		Type:       objType,
		ID:         id,
		Attributes: make(map[string]interface{})}}
}

// ID returns the object's identifier.
func (obj *Object) ID() string {
	return obj.data.ID
}

// Type returns the object's type.
func (obj *Object) Type() string {
	return obj.data.Type
}

// MarshalJSON marshals a VirusTotal API object.
func (obj *Object) MarshalJSON() ([]byte, error) {
	return json.Marshal(obj.data)
}

// UnmarshalJSON unmarshals a VirusTotal API object from data.
func (obj *Object) UnmarshalJSON(data []byte) error {

	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.UseNumber()

	od := objectData{}
	if err := decoder.Decode(&od); err != nil {
		return err
	}

	obj.data = od

	for _, v := range obj.data.Relationships {
		var o Object
		// Try unmarshalling as an Object first, if it fails this is a
		// one-to-many relationship, so we try unmarshalling as an array.
		if err := json.Unmarshal(v.Data, &o); err == nil {
			v.IsOneToOne = true
			// If the value is null the ObjectDescriptor will have an empty
			// ID and Type.
			if o.data.ID == "" && o.data.Type == "" {
				v.RelatedObjects = nil
			} else {
				v.RelatedObjects = append(v.RelatedObjects, o)
			}
		} else {
			if err := json.Unmarshal(v.Data, &v.RelatedObjects); err != nil {
				return err
			}
		}
	}

	return nil
}

func (obj *Object) getAttributeNumber(name string) (n json.Number, err error) {
	if attrValue, attrExists := obj.data.Attributes[name]; attrExists {
		n, isNumber := attrValue.(json.Number)
		if !isNumber {
			err = fmt.Errorf("attribute \"%s\" is not a number", name)
		}
		return n, err
	}
	return n, fmt.Errorf("attribute \"%s\" does not exists", name)
}

func (obj *Object) getContextAttributeNumber(name string) (n json.Number, err error) {
	if attrValue, attrExists := obj.data.ContextAttributes[name]; attrExists {
		n, isNumber := attrValue.(json.Number)
		if !isNumber {
			err = fmt.Errorf("context attribute \"%s\" is not a number", name)
		}
		return n, err
	}
	return n, fmt.Errorf("context attribute \"%s\" does not exists", name)
}

// GetInt64 returns an attribute as an int64. It returns the attribute's
// value or an error if the attribute doesn't exist or is not a number.
func (obj *Object) GetInt64(name string) (int64, error) {
	n, err := obj.getAttributeNumber(name)
	if err == nil {
		return n.Int64()
	}
	return 0, err
}

// MustGetInt64 is like GetInt64, but it panic in case of error.
func (obj *Object) MustGetInt64(name string) int64 {
	result, err := obj.GetInt64(name)
	if err != nil {
		panic(err)
	}
	return result
}

// GetFloat64 returns an attribute as a float64. It returns the attribute's
// value or an error if the attribute doesn't exist or is not a number.
func (obj *Object) GetFloat64(name string) (float64, error) {
	n, err := obj.getAttributeNumber(name)
	if err == nil {
		return n.Float64()
	}
	return 0, err
}

// MustGetFloat64 is like GetFloat64, but it panic in case of error.
func (obj *Object) MustGetFloat64(name string) float64 {
	result, err := obj.GetFloat64(name)
	if err != nil {
		panic(err)
	}
	return result
}

// GetString returns an attribute as a string. It returns the attribute's
// valueor an error if the attribute doesn't exist or is not a string.
func (obj *Object) GetString(name string) (s string, err error) {
	if attrValue, attrExists := obj.data.Attributes[name]; attrExists {
		s, isString := attrValue.(string)
		if !isString {
			err = fmt.Errorf("attribute \"%s\" is not a string", name)
		}
		return s, err
	}
	return "", fmt.Errorf("attribute \"%s\" does not exists", name)
}

// MustGetString is like GetString, but it panic in case of error.
func (obj *Object) MustGetString(name string) string {
	result, err := obj.GetString(name)
	if err != nil {
		panic(err)
	}
	return result
}

// GetTime returns an attribute as a time. It returns the attribute's
// value or an error if the attribute doesn't exist or is not a time.
func (obj *Object) GetTime(name string) (t time.Time, err error) {
	n, err := obj.getAttributeNumber(name)
	if err == nil {
		i, err := n.Int64()
		return time.Unix(i, 0), err
	}
	return time.Unix(0, 0), err
}

// MustGetTime is like GetTime, but it panic in case of error.
func (obj *Object) MustGetTime(name string) time.Time {
	result, err := obj.GetTime(name)
	if err != nil {
		panic(err)
	}
	return result
}

// GetBool returns an attribute as a boolean. It returns the attribute's
// value or an error if the attribute doesn't exist or is not a boolean.
func (obj *Object) GetBool(name string) (b bool, err error) {
	if attrValue, attrExists := obj.data.Attributes[name]; attrExists {
		b, isBool := attrValue.(bool)
		if !isBool {
			err = fmt.Errorf("attribute \"%s\" is not a bool", name)
		}
		return b, err
	}
	return false, fmt.Errorf("attribute \"%s\" does not exists", name)
}

// MustGetBool is like GetTime, but it panic in case of error.
func (obj *Object) MustGetBool(name string) bool {
	result, err := obj.GetBool(name)
	if err != nil {
		panic(err)
	}
	return result
}

// GetContextInt64 returns a context attribute as an int64. It returns the
// attribute's value or an error if the attribute doesn't exist or is not a
// number.
func (obj *Object) GetContextInt64(name string) (int64, error) {
	n, err := obj.getContextAttributeNumber(name)
	if err == nil {
		return n.Int64()
	}
	return 0, err
}

// GetContextFloat64 returns a context attribute as an float64. It returns the
// attribute's value or an error if the attribute doesn't exist or is not a
// number.
func (obj *Object) GetContextFloat64(name string) (float64, error) {
	n, err := obj.getContextAttributeNumber(name)
	if err == nil {
		return n.Float64()
	}
	return 0, err
}

// GetContextString returns a context attribute as a string. It returns the
// attribute's value or an error if the attribute doesn't exist or is not a
// string.
func (obj *Object) GetContextString(name string) (s string, err error) {
	if attrValue, attrExists := obj.data.ContextAttributes[name]; attrExists {
		s, isString := attrValue.(string)
		if !isString {
			err = fmt.Errorf("context attribute \"%s\" is not a string", name)
		}
		return s, err
	}
	return "", fmt.Errorf("context attribute \"%s\" does not exists", name)
}

// GetContextBool returns a context attribute as a bool. It returns the
// attribute's value or an error if the attribute doesn't exist or is not a
// bool.
func (obj *Object) GetContextBool(name string) (b bool, err error) {
	if attrValue, attrExists := obj.data.ContextAttributes[name]; attrExists {
		b, isBool := attrValue.(bool)
		if !isBool {
			err = fmt.Errorf("context attribute \"%s\" is not a bool", name)
		}
		return b, err
	}
	return false, fmt.Errorf("context attribute \"%s\" does not exists", name)
}

// Set the value for an attribute.
func (obj *Object) set(attr string, value interface{}) error {
	obj.modifiedAttributes = append(obj.modifiedAttributes, attr)
	obj.data.Attributes[attr] = value
	return nil
}

// SetInt64 sets the value of an integer attribute.
func (obj *Object) SetInt64(name string, value int64) error {
	return obj.set(name, value)
}

// SetFloat64 sets the value of an integer attribute.
func (obj *Object) SetFloat64(name string, value float64) error {
	return obj.set(name, value)
}

// SetString sets the value of a string attribute.
func (obj *Object) SetString(name, value string) error {
	return obj.set(name, value)
}

// SetBool sets the value of a string attribute.
func (obj *Object) SetBool(name string, value bool) error {
	return obj.set(name, value)
}

// SetTime sets the value of a time attribute.
func (obj *Object) SetTime(name string, value time.Time) error {
	return obj.set(name, value.Unix())
}

// modifiedObject is a structure exactly like Object, but that implements the
// MarshalJSON interface differently. When a modifiedObject is marshalled as
// JSON only the attributes that has been modified are included. Context
// attributes, relationships and links are not included neither.
type modifiedObject Object

func (obj modifiedObject) MarshalJSON() ([]byte, error) {
	od := objectData{
		ID:         obj.data.ID,
		Type:       obj.data.Type,
		Attributes: make(map[string]interface{}),
	}
	for _, attr := range obj.modifiedAttributes {
		od.Attributes[attr] = obj.data.Attributes[attr]
	}
	return json.Marshal(&od)
}
<|endoftext|>


<commit_before>package glplus

import (
	"math"

	"github.com/go-gl/mathgl/mgl32"
)

var (
	sVertShaderObj = `#version 330
  ATTRIBUTE vec3 position;
	ATTRIBUTE float uvs;
	ATTRIBUTE vec3 normal;
	VARYINGOUT float out_uvs;
	VARYINGOUT vec3 out_normal;
  uniform mat4 projection;
  uniform mat4 camera;
  uniform mat4 model;

  void main()
  {
      gl_Position = projection * camera * model * vec4(position, 1.0);
      out_normal = normalize(model * vec4(normal, 0.0)).xyz;
			out_uvs = uvs;
  }`

	sFragShaderObj = `#version 330
  uniform vec4 color1;
  uniform vec3 light;
	VARYINGIN float out_uvs;
	VARYINGIN vec3 out_normal;
  COLOROUT

  void main(void)
  {
		// "out_uvs / 1000" is there to trick the driver to use it
  	float cosTheta = clamp(dot(light, normalize(out_normal)), 0.3 + out_uvs / 1000.0, 1.0);
  	FRAGCOLOR = color1 * cosTheta;
  }`

	sVertShaderObjTex = `#version 330
	ATTRIBUTE vec3 position;
	ATTRIBUTE vec2 uvs;
	ATTRIBUTE vec3 normal;
	VARYINGOUT vec2 out_uvs;
	VARYINGOUT vec3 out_normal;
	uniform mat4 projection;
	uniform mat4 camera;
	uniform mat4 model;

	void main()
	{
			gl_Position = projection * camera * model * vec4(position, 1.0);
			out_uvs = uvs;
			out_normal = normalize(model * vec4(normal, 0.0)).xyz;
	}`

	sFragShaderObjTex = `#version 330
  uniform vec4 color1;
  uniform vec3 light;
	uniform sampler2D tex1;
	uniform mat3 matuv;
  VARYINGIN vec2 out_uvs;
  VARYINGIN vec3 out_normal;
  COLOROUT

  void main(void)
  {
		vec2 new_uvs = vec2(1.0-out_uvs.x, out_uvs.y);
		new_uvs = (matuv * vec3(new_uvs, 1)).xy;
		vec4 texcolor = TEXTURE2D(tex1, new_uvs);
  	float cosTheta = clamp(dot(light, normalize(out_normal)), 0.3, 1.0);
		FRAGCOLOR = mix(color1, texcolor, texcolor.w);
  	FRAGCOLOR = FRAGCOLOR * cosTheta;
  }`

	sVertShaderObjColorTable = `#version 330
	ATTRIBUTE vec3 position;
	ATTRIBUTE float uvs;
	ATTRIBUTE vec3 normal;
	VARYINGOUT float out_uvs;
	VARYINGOUT vec3 out_normal;
	uniform mat4 projection;
	uniform mat4 camera;
	uniform mat4 model;

	void main()
	{
			gl_Position = projection * camera * model * vec4(position, 1.0);
			out_uvs = uvs;
			out_normal = normalize(model * vec4(normal, 0.0)).xyz;
	}`

	sFragShaderObjColorTable = `#version 330
  uniform vec3 light;
	uniform sampler2D tex1;
  VARYINGIN float out_uvs;
  VARYINGIN vec3 out_normal;
  COLOROUT

  void main(void)
  {
		vec4 texcolor = TEXTURE2D(tex1, vec2(out_uvs, 0));
  	float cosTheta = clamp(dot(light, normalize(out_normal)), 0.3, 1.0);
  	FRAGCOLOR = texcolor * cosTheta;
  }`
)

// ObjRender ...
type ObjRender struct {
	Obj *Obj

	progCoord *GPProgram
	vbo       *VBO
	tex       *GPTexture
}

// ObjsRender ...
type ObjsRender struct {
	Objs []*ObjRender
}

// Bounds ...
func (m *ObjsRender) Bounds() (b Bounds) {
	var build BoundBuilder
	build.reset()

	for _, obj := range m.Objs {
		build.include64(obj.Obj.Bounds.X.Lo, obj.Obj.Bounds.Y.Lo, obj.Obj.Bounds.Z.Lo)
		build.include64(obj.Obj.Bounds.X.Hi, obj.Obj.Bounds.Y.Hi, obj.Obj.Bounds.Z.Hi)
	}
	return build.build()
}

// NormalizedMat ...
func (m *ObjsRender) NormalizedMat() (mres mgl32.Mat4) {
	center := m.Bounds().Center()
	length := m.Bounds().Length()
	scale := 1 / length

	mres = mgl32.HomogRotate3DX(math.Pi / 2)
	mres = mres.Mul4(mgl32.Scale3D(scale, scale, scale))
	mres = mres.Mul4(mgl32.Translate3D(-center[0], -center[1], -center[2]))
	return mres
}

// Delete ...
func (m *ObjsRender) Delete() {
	for _, obj := range m.Objs {
		obj.Delete()
	}
}

// Draw ...
func (m *ObjsRender) Draw(color1 [4]float32, camera, projection, model mgl32.Mat4, light mgl32.Vec3, uvAngle float64, tex *GPTexture) {
	for _, obj := range m.Objs {
		obj.Draw(color1, camera, projection, model, light, uvAngle, tex)
	}
}

// NewObjsVBO ...
func NewObjsVBO(objs []*Obj, hasColorTable bool) (m *ObjsRender) {
	m = &ObjsRender{}
	for _, obj := range objs {
		m.Objs = append(m.Objs, NewObjVBO(obj, hasColorTable))
	}
	return m
}

// NewObjVBO ...
func NewObjVBO(obj *Obj, hasColorTable bool) (m *ObjRender) {
	var err error

	m = &ObjRender{
		Obj: obj,
	}

	var attribs = []string{
		"position",
		"uvs",
		"normal",
	}
	if obj.TexImg != nil {
		if m.progCoord, err = LoadShaderProgram(sVertShaderObjTex, sFragShaderObjTex, attribs); err != nil {
			panic(err)
		}

		if m.tex, err = NewRGBATexture(obj.TexImg, true, false); err != nil {
			panic(err)
		}
	} else if hasColorTable {
		if m.progCoord, err = LoadShaderProgram(sVertShaderObjColorTable, sFragShaderObjColorTable, attribs); err != nil {
			panic(err)
		}
	} else {
		if m.progCoord, err = LoadShaderProgram(sVertShaderObj, sFragShaderObj, attribs); err != nil {
			panic(err)
		}
	}
	opt := DefaultVBOOptions()
	opt.Normals = 3
	if obj.TexImg == nil {
		opt.UV = 1
	}
	m.vbo = NewVBO(m.progCoord, opt, obj.ObjVertices, nil)

	return m
}

// Delete ...
func (m *ObjRender) Delete() {
	m.progCoord.DeleteProgram()
	m.vbo.DeleteVBO()
	if m.tex != nil {
		m.tex.DeleteTexture()
	}
}

// NormalizedMat ...
func (m *ObjRender) NormalizedMat() (mres mgl32.Mat4) {
	return m.Obj.NormalizedMat()
}

// Draw ...
func (m *ObjRender) Draw(color1 [4]float32, camera, projection, model mgl32.Mat4, light mgl32.Vec3, uvAngle float64, tex *GPTexture) {
	m.progCoord.UseProgram()

	matuv := mgl32.Translate2D(0.5, 0.5)
	matuv = matuv.Mul3(mgl32.Rotate3DZ(-float32(uvAngle)))
	matuv = matuv.Mul3(mgl32.Translate2D(-0.5, -0.5))

	m.progCoord.ProgramUniformMatrix4fv("projection", projection)
	m.progCoord.ProgramUniformMatrix4fv("camera", camera)
	m.progCoord.ProgramUniformMatrix4fv("model", model)
	m.progCoord.ProgramUniform3fv("light", light)
	m.progCoord.ProgramUniformMatrix3fv("matuv", matuv)

	if m.tex != nil {
		m.tex.BindTexture(0)
		m.progCoord.ProgramUniform1i("tex1", 0)
	} else if tex != nil {
		tex.BindTexture(0)
		m.progCoord.ProgramUniform1i("tex1", 0)
	}

	m.vbo.Bind(m.progCoord)
	m.progCoord.ProgramUniform4fv("color1", color1)

	var err error
	if err = m.progCoord.ValidateProgram(); err != nil {
		panic(err)
	}
	m.vbo.Draw()
	m.vbo.Unbind(m.progCoord)

	if m.tex != nil {
		m.tex.UnbindTexture(0)
	} else if tex != nil {
		m.tex.UnbindTexture(0)
	}

	m.progCoord.UnuseProgram()
}
<commit_msg>lighting<commit_after>package glplus

import (
	"math"

	"github.com/go-gl/mathgl/mgl32"
)

var (
	sVertShaderObj = `#version 330
  ATTRIBUTE vec3 position;
	ATTRIBUTE float uvs;
	ATTRIBUTE vec3 normal;
	VARYINGOUT float out_uvs;
	VARYINGOUT vec3 out_normal;
	uniform mat4 mProjViewModel;
	uniform mat4 mViewModel;
	uniform mat4 mView;

  void main()
  {
      out_normal = normalize(mViewModel * vec4(normal, 0.0)).xyz;
			out_uvs = uvs;
			gl_Position = mProjViewModel * vec4(position, 1.0);
  }`

	sFragShaderObj = `#version 330
  uniform vec4 color1;
  uniform vec3 light;
	uniform mat4 mView;
	VARYINGIN float out_uvs;
	VARYINGIN vec3 out_normal;
  COLOROUT

  void main(void)
  {
		// "out_uvs / 1000" is there to trick the driver to use it
  	float cosTheta = clamp(dot(normalize(mView * vec4(light, 0)).xyz, normalize(out_normal)), 0.3 + out_uvs / 1000.0, 1.0);
  	FRAGCOLOR = color1 * cosTheta;
  }`

	sVertShaderObjTex = `#version 330
	ATTRIBUTE vec3 position;
	ATTRIBUTE vec2 uvs;
	ATTRIBUTE vec3 normal;
	VARYINGOUT vec2 out_uvs;
	VARYINGOUT vec3 out_normal;
	uniform mat4 mProjViewModel;
	uniform mat4 mViewModel;
	uniform mat4 mView;

	void main()
	{
			out_uvs = uvs;
			out_normal = normalize(mViewModel * vec4(normal, 0.0)).xyz;
			gl_Position = mProjViewModel * vec4(position, 1.0);
	}`

	sFragShaderObjTex = `#version 330
  uniform vec4 color1;
  uniform vec3 light;
	uniform mat4 mView;
	uniform sampler2D tex1;
	uniform mat3 matuv;
  VARYINGIN vec2 out_uvs;
  VARYINGIN vec3 out_normal;
  COLOROUT

  void main(void)
  {
		vec2 new_uvs = vec2(1.0-out_uvs.x, out_uvs.y);
		new_uvs = (matuv * vec3(new_uvs, 1)).xy;
		vec4 texcolor = TEXTURE2D(tex1, new_uvs);
  	float cosTheta = clamp(dot(normalize(mView * vec4(light, 0)).xyz, normalize(out_normal)), 0.3, 1.0);
		FRAGCOLOR = mix(color1, texcolor, texcolor.w) * cosTheta;
  }`

	sVertShaderObjColorTable = `#version 330
	ATTRIBUTE vec3 position;
	ATTRIBUTE float uvs;
	ATTRIBUTE vec3 normal;
	VARYINGOUT float out_uvs;
	VARYINGOUT vec3 out_normal;
	uniform mat4 mProjViewModel;
	uniform mat4 mViewModel;
	uniform mat4 mView;

	void main()
	{
			out_uvs = uvs;
			out_normal = normalize(mViewModel * vec4(normal, 0.0)).xyz;
			gl_Position = mProjViewModel * vec4(position, 1.0);
	}`

	sFragShaderObjColorTable = `#version 330
  uniform vec3 light;
	uniform mat4 mView;
	uniform sampler2D tex1;
  VARYINGIN float out_uvs;
  VARYINGIN vec3 out_normal;
  COLOROUT

  void main(void)
  {
		vec4 texcolor = TEXTURE2D(tex1, vec2(out_uvs, 0));
  	float cosTheta = clamp(dot(normalize(mView * vec4(light, 0)).xyz, normalize(out_normal)), 0.3, 1.0);
  	FRAGCOLOR = texcolor * cosTheta;
  }`
)

// ObjRender ...
type ObjRender struct {
	Obj *Obj

	progCoord *GPProgram
	vbo       *VBO
	tex       *GPTexture
}

// ObjsRender ...
type ObjsRender struct {
	Objs []*ObjRender
}

// Bounds ...
func (m *ObjsRender) Bounds() (b Bounds) {
	var build BoundBuilder
	build.reset()

	for _, obj := range m.Objs {
		build.include64(obj.Obj.Bounds.X.Lo, obj.Obj.Bounds.Y.Lo, obj.Obj.Bounds.Z.Lo)
		build.include64(obj.Obj.Bounds.X.Hi, obj.Obj.Bounds.Y.Hi, obj.Obj.Bounds.Z.Hi)
	}
	return build.build()
}

// NormalizedMat ...
func (m *ObjsRender) NormalizedMat() (mres mgl32.Mat4) {
	center := m.Bounds().Center()
	length := m.Bounds().Length()
	scale := 1 / length

	mres = mgl32.HomogRotate3DX(math.Pi / 2)
	mres = mres.Mul4(mgl32.Scale3D(scale, scale, scale))
	mres = mres.Mul4(mgl32.Translate3D(-center[0], -center[1], -center[2]))
	return mres
}

// Delete ...
func (m *ObjsRender) Delete() {
	for _, obj := range m.Objs {
		obj.Delete()
	}
}

// Draw ...
func (m *ObjsRender) Draw(color1 [4]float32, camera, projection, model mgl32.Mat4, light mgl32.Vec3, uvAngle float64, tex *GPTexture) {
	for _, obj := range m.Objs {
		obj.Draw(color1, camera, projection, model, light, uvAngle, tex)
	}
}

// NewObjsVBO ...
func NewObjsVBO(objs []*Obj, hasColorTable bool) (m *ObjsRender) {
	m = &ObjsRender{}
	for _, obj := range objs {
		m.Objs = append(m.Objs, NewObjVBO(obj, hasColorTable))
	}
	return m
}

// NewObjVBO ...
func NewObjVBO(obj *Obj, hasColorTable bool) (m *ObjRender) {
	var err error

	m = &ObjRender{
		Obj: obj,
	}

	var attribs = []string{
		"position",
		"uvs",
		"normal",
	}
	if obj.TexImg != nil {
		if m.progCoord, err = LoadShaderProgram(sVertShaderObjTex, sFragShaderObjTex, attribs); err != nil {
			panic(err)
		}

		if m.tex, err = NewRGBATexture(obj.TexImg, true, false); err != nil {
			panic(err)
		}
	} else if hasColorTable {
		if m.progCoord, err = LoadShaderProgram(sVertShaderObjColorTable, sFragShaderObjColorTable, attribs); err != nil {
			panic(err)
		}
	} else {
		if m.progCoord, err = LoadShaderProgram(sVertShaderObj, sFragShaderObj, attribs); err != nil {
			panic(err)
		}
	}
	opt := DefaultVBOOptions()
	opt.Normals = 3
	if obj.TexImg == nil {
		opt.UV = 1
	}
	m.vbo = NewVBO(m.progCoord, opt, obj.ObjVertices, nil)

	return m
}

// Delete ...
func (m *ObjRender) Delete() {
	m.progCoord.DeleteProgram()
	m.vbo.DeleteVBO()
	if m.tex != nil {
		m.tex.DeleteTexture()
	}
}

// NormalizedMat ...
func (m *ObjRender) NormalizedMat() (mres mgl32.Mat4) {
	return m.Obj.NormalizedMat()
}

// Draw ...
func (m *ObjRender) Draw(color1 [4]float32, camera, projection, model mgl32.Mat4, light mgl32.Vec3, uvAngle float64, tex *GPTexture) {
	m.progCoord.UseProgram()

	matuv := mgl32.Translate2D(0.5, 0.5)
	matuv = matuv.Mul3(mgl32.Rotate3DZ(-float32(uvAngle)))
	matuv = matuv.Mul3(mgl32.Translate2D(-0.5, -0.5))

	mViewModel := camera.Mul4(model)
	mProjViewModel := projection.Mul4(mViewModel)
	m.progCoord.ProgramUniformMatrix4fv("mViewModel", mViewModel)
	m.progCoord.ProgramUniformMatrix4fv("mProjViewModel", mProjViewModel)
	m.progCoord.ProgramUniformMatrix4fv("mView", camera)
	m.progCoord.ProgramUniform3fv("light", light)
	m.progCoord.ProgramUniformMatrix3fv("matuv", matuv)

	if m.tex != nil {
		m.tex.BindTexture(0)
		m.progCoord.ProgramUniform1i("tex1", 0)
	} else if tex != nil {
		tex.BindTexture(0)
		m.progCoord.ProgramUniform1i("tex1", 0)
	}

	m.vbo.Bind(m.progCoord)
	m.progCoord.ProgramUniform4fv("color1", color1)

	var err error
	if err = m.progCoord.ValidateProgram(); err != nil {
		panic(err)
	}
	m.vbo.Draw()
	m.vbo.Unbind(m.progCoord)

	if m.tex != nil {
		m.tex.UnbindTexture(0)
	} else if tex != nil {
		m.tex.UnbindTexture(0)
	}

	m.progCoord.UnuseProgram()
}
<|endoftext|>


<commit_before>package ngnet

import (
    "bytes"
    "errors"
    "fmt"
    "github.com/google/gopacket"
    "github.com/google/gopacket/tcpassembly"
    "regexp"
    "strconv"
    "strings"
    "sync"
    "time"
)

type Direction byte

const (
    UpDirection   Direction = 1
    DownDirection Direction = 2
)

var (
    httpRequestFirtLine  *regexp.Regexp
    httpResponseFirtLine *regexp.Regexp
)

func init() {
    httpRequestFirtLine = regexp.MustCompile("([A-Z]+) (.+) (.+)")
    httpResponseFirtLine = regexp.MustCompile("(.+) (\\d{3}) (.+)")
}

type Packet struct {
    Meta string
    Data []byte
}

type PacketSource chan tcpassembly.Reassembly

type HttpStreamReader struct {
    src           PacketSource
    buffer        *bytes.Buffer
    seq           uint
    lastTimeStamp float64
}

func NewHttpStreamReader(s PacketSource, seq uint) *HttpStreamReader {
    f := new(HttpStreamReader)
    f.src = s
    f.buffer = bytes.NewBuffer([]byte(""))
    f.seq = seq
    return f
}

var (
    startTimestampSet bool
    startTimestamp    time.Time
)

func relativeTimestamp(t time.Time) float64 {
    if !startTimestampSet {
        startTimestamp = t
        startTimestampSet = true
        return 0.0
    } else {
        return t.Sub(startTimestamp).Seconds()
    }
}

func (s *HttpStreamReader) fillBuffer() error {
    if dataBlock, more := <-s.src; more {
        s.buffer.Write(dataBlock.Bytes)
        s.lastTimeStamp = relativeTimestamp(dataBlock.Seen)
        return nil
    } else {
        return errors.New("EOF")
    }
}

func (f *HttpStreamReader) ReadUntil(delim []byte) ([]byte, error) {
    var p int
    for {
        if p = bytes.Index(f.buffer.Bytes(), delim); p == -1 {
            if err := f.fillBuffer(); err != nil {
                return nil, err
            }
        } else {
            break
        }
    }
    return f.buffer.Next(p + len(delim)), nil
}

func (f HttpStreamReader) Next(n int) ([]byte, error) {
    for f.buffer.Len() < n {
        if err := f.fillBuffer(); err != nil {
            return nil, errors.New("EOF")
        }
    }
    return f.buffer.Next(n), nil
}

type HttpStream struct {
    reader     *HttpStreamReader
    streamSeq  uint
    requestSeq uint
    wg         *sync.WaitGroup
    eventChan  chan interface{}
    sem        chan byte
    direction  Direction
    bad        *bool
}

func (s HttpStream) getHeader() (m HttpMessage, more bool) {
    d, err := s.reader.ReadUntil([]byte("\r\n\r\n"))
    if err != nil {
        more = false
        return
    }
    data := string(d[:len(d)-4])
    more = true
    for i, line := range strings.Split(data, "\r\n") {
        if i == 0 {
            if s.direction == DownDirection {
                if line[:5] != "HTTP/" {
                    panic("Bad HTTP Response: \n" + line)
                }
                r := httpResponseFirtLine.FindStringSubmatch(line)
                if len(r) != 4 {
                    panic("Bad HTTP Response: \n" + line)
                }
                e := NewHttpResponseEvent()
                e.Type = "HttpResponse"
                e.Version = r[1]
                e.Code = r[2]
                e.Reason = r[3]
                m = e
            } else if s.direction == UpDirection {
                r := httpRequestFirtLine.FindStringSubmatch(line)
                if len(r) != 4 {
                    panic("Bad HTTP Request: \n" + line)
                }
                e := NewHttpRequestEvent()
                e.Type = "HttpRequest"
                e.Method = r[1]
                e.Uri = r[2]
                e.Version = r[3]
                m = e
            }
            m.SetStreamSeq(s.streamSeq)
            m.SetTimestamp(s.reader.lastTimeStamp)
            m.SetEndTimestamp(s.reader.lastTimeStamp)
            continue
        }
        p := strings.Index(line, ":")
        if p == -1 {
            panic(fmt.Sprintf("bad http header (line %d): %s", i, data))
        }
        var h HttpHeaderItem
        h.Name = line[:p]
        h.Value = strings.Trim(line[p+1:], " ")
        m.AddHeader(h)
    }
    return
}

func (s HttpStream) processChunked() ([]byte, bool) {
    var body []byte
    for {
        buf, err := s.reader.ReadUntil([]byte("\r\n"))
        if err != nil {
            return body, false
        }
        l := string(buf)
        l = strings.Trim(l[:len(l)-2], " ")
        blockLength, err := strconv.ParseInt(l, 16, 32)

        if err != nil {
            panic("bad chunked block length: " + l + "\n" + err.Error())
        }

        buf, err = s.reader.Next(int(blockLength))
        body = append(body, buf...)
        if err != nil {
            return body, false
        }
        buf, err = s.reader.Next(2)
        if err != nil {
            return body, false
        }
        CRLF := string(buf)
        if CRLF != "\r\n" {
            panic("bad chunked block data")
        }

        if blockLength == 0 {
            break
        }
    }
    return body, true
}

func (f HttpStream) processContentLength(contentLength int) ([]byte, bool) {
    body, err := f.reader.Next(contentLength)
    return body, err == nil
}

func (f HttpStream) processBody(contentLength int, chunked bool) (body []byte, more bool) {
    if chunked {
        body, more = f.processChunked()
    } else {
        body, more = f.processContentLength(contentLength)
    }
    return
}

func GetHttpSize(hs []HttpHeaderItem) (contentLength int, chunked bool) {
    for _, h := range hs {
        if h.Name == "Content-Length" {
            var err error
            contentLength, err = strconv.Atoi(h.Value)
            if err != nil {
                panic("Content-Length error: " + h.Value)
            }
        } else if h.Name == "Transfer-Encoding" && h.Value == "chunked" {
            chunked = true
        }
    }
    return
}

func syncStreamPir(s HttpStream) {
    if s.direction == DownDirection {
        <-s.sem
    } else if s.direction == UpDirection {
        s.sem <- 1
    }
}

func (s HttpStream) Process() {
    defer func() {
        if r := recover(); r != nil {
            *s.bad = true
            //close(s.reader.src)
            fmt.Println("HttpStream error: ", r)
        }
    }()
    defer s.wg.Done()
    for {
        m, more := s.getHeader()
        if !more {
            break
        }

        contentLength, chunked := GetHttpSize(m.Header())
        if contentLength == 0 && !chunked {
            syncStreamPir(s)
            s.eventChan <- m
            s.requestSeq++
            continue
        }

        body, more := s.processBody(contentLength, chunked)
        if !more {
            break
        }
        m.SetBody(string(body))
        m.SetEndTimestamp(s.reader.lastTimeStamp)
        syncStreamPir(s)
        s.eventChan <- m
        s.requestSeq++
    }
}

func NewHttpStream(src PacketSource, meta string, seq uint, wg *sync.WaitGroup, eventChan chan interface{}, sem chan byte, dir Direction) HttpStream {
    var s HttpStream
    s.reader = NewHttpStreamReader(src, seq)
    s.streamSeq = seq
    s.wg = wg
    s.eventChan = eventChan
    s.sem = sem
    s.direction = dir
    s.bad = new(bool)
    return s
}

func (s HttpStream) Reassembled(rs []tcpassembly.Reassembly) {
    for _, r := range rs {
        if *s.bad {
            break
        }
        s.reader.src <- r
    }
}

func (s HttpStream) ReassemblyComplete() {
    close(s.reader.src)
}

type HttpStreamPair struct {
    upStream   *HttpStream
    downStream *HttpStream
    sem        chan byte
    connSeq    uint
}

type HttpStreamFactory struct {
    wg         *sync.WaitGroup
    seq        *uint
    uniStreams *map[string]*HttpStreamPair
    eventChan  *chan interface{}
}

func NewHttpStreamFactory(out chan interface{}) HttpStreamFactory {
    var f HttpStreamFactory
    f.seq = new(uint)
    *f.seq = 0
    f.wg = new(sync.WaitGroup)
    f.uniStreams = new(map[string]*HttpStreamPair)
    *f.uniStreams = make(map[string]*HttpStreamPair)
    f.eventChan = new(chan interface{})
    *f.eventChan = out
    return f
}

func (f HttpStreamFactory) Wait() {
    f.wg.Wait()
}

func (f HttpStreamFactory) New(netFlow, tcpFlow gopacket.Flow) (ret tcpassembly.Stream) {
    revkey := fmt.Sprintf("%v:%v->%v:%v",
        netFlow.Dst(),
        tcpFlow.Dst(),
        netFlow.Src(),
        tcpFlow.Src())
    streamPair, ok := (*f.uniStreams)[revkey]
    src := make(PacketSource)
    if ok {
        if streamPair.upStream == nil {
            panic("fuck")
        }
        delete(*f.uniStreams, revkey)
        s := NewHttpStream(src, "", streamPair.connSeq, f.wg, *f.eventChan, streamPair.sem, DownDirection)
        streamPair.downStream = &s
        ret = s
        go s.Process()
    } else {
        streamPair = new(HttpStreamPair)
        streamPair.connSeq = *f.seq
        streamPair.sem = make(chan byte, 1)
        key := fmt.Sprintf("%v:%v->%v:%v",
            netFlow.Src(),
            tcpFlow.Src(),
            netFlow.Dst(),
            tcpFlow.Dst())
        s := NewHttpStream(src, "", streamPair.connSeq, f.wg, *f.eventChan, streamPair.sem, UpDirection)
        streamPair.upStream = &s
        (*f.uniStreams)[key] = streamPair
        *f.seq++
        //fmt.Printf("#%d Connect %s\n", streamPair.connSeq, key)
        go s.Process()
        ret = s
    }
    f.wg.Add(1)
    return
}
<commit_msg>support gzip encoding<commit_after>package ngnet

import (
    "bytes"
    "compress/gzip"
    "errors"
    "fmt"
    "github.com/google/gopacket"
    "github.com/google/gopacket/tcpassembly"
    "io/ioutil"
    "regexp"
    "strconv"
    "strings"
    "sync"
    "time"
)

type Direction byte

const (
    UpDirection   Direction = 1
    DownDirection Direction = 2
)

var (
    httpRequestFirtLine  *regexp.Regexp
    httpResponseFirtLine *regexp.Regexp
)

func init() {
    httpRequestFirtLine = regexp.MustCompile("([A-Z]+) (.+) (.+)")
    httpResponseFirtLine = regexp.MustCompile("(.+) (\\d{3}) (.+)")
}

type Packet struct {
    Meta string
    Data []byte
}

type PacketSource chan tcpassembly.Reassembly

type HttpStreamReader struct {
    src           PacketSource
    buffer        *bytes.Buffer
    seq           uint
    lastTimeStamp float64
}

func NewHttpStreamReader(s PacketSource, seq uint) *HttpStreamReader {
    f := new(HttpStreamReader)
    f.src = s
    f.buffer = bytes.NewBuffer([]byte(""))
    f.seq = seq
    return f
}

var (
    startTimestampSet bool
    startTimestamp    time.Time
)

func relativeTimestamp(t time.Time) float64 {
    if !startTimestampSet {
        startTimestamp = t
        startTimestampSet = true
        return 0.0
    } else {
        return t.Sub(startTimestamp).Seconds()
    }
}

func (s *HttpStreamReader) fillBuffer() error {
    if dataBlock, more := <-s.src; more {
        s.buffer.Write(dataBlock.Bytes)
        s.lastTimeStamp = relativeTimestamp(dataBlock.Seen)
        return nil
    } else {
        return errors.New("EOF")
    }
}

func (f *HttpStreamReader) ReadUntil(delim []byte) ([]byte, error) {
    var p int
    for {
        if p = bytes.Index(f.buffer.Bytes(), delim); p == -1 {
            if err := f.fillBuffer(); err != nil {
                return nil, err
            }
        } else {
            break
        }
    }
    return f.buffer.Next(p + len(delim)), nil
}

func (f HttpStreamReader) Next(n int) ([]byte, error) {
    for f.buffer.Len() < n {
        if err := f.fillBuffer(); err != nil {
            return nil, errors.New("EOF")
        }
    }
    return f.buffer.Next(n), nil
}

type HttpStream struct {
    reader     *HttpStreamReader
    streamSeq  uint
    requestSeq uint
    wg         *sync.WaitGroup
    eventChan  chan interface{}
    sem        chan byte
    direction  Direction
    bad        *bool
}

func (s HttpStream) getHeader() (m HttpMessage, more bool) {
    d, err := s.reader.ReadUntil([]byte("\r\n\r\n"))
    if err != nil {
        more = false
        return
    }
    data := string(d[:len(d)-4])
    more = true
    for i, line := range strings.Split(data, "\r\n") {
        if i == 0 {
            if s.direction == DownDirection {
                if line[:5] != "HTTP/" {
                    panic("Bad HTTP Response: \n" + line)
                }
                r := httpResponseFirtLine.FindStringSubmatch(line)
                if len(r) != 4 {
                    panic("Bad HTTP Response: \n" + line)
                }
                e := NewHttpResponseEvent()
                e.Type = "HttpResponse"
                e.Version = r[1]
                e.Code = r[2]
                e.Reason = r[3]
                m = e
            } else if s.direction == UpDirection {
                r := httpRequestFirtLine.FindStringSubmatch(line)
                if len(r) != 4 {
                    panic("Bad HTTP Request: \n" + line)
                }
                e := NewHttpRequestEvent()
                e.Type = "HttpRequest"
                e.Method = r[1]
                e.Uri = r[2]
                e.Version = r[3]
                m = e
            }
            m.SetStreamSeq(s.streamSeq)
            m.SetTimestamp(s.reader.lastTimeStamp)
            m.SetEndTimestamp(s.reader.lastTimeStamp)
            continue
        }
        p := strings.Index(line, ":")
        if p == -1 {
            panic(fmt.Sprintf("bad http header (line %d): %s", i, data))
        }
        var h HttpHeaderItem
        h.Name = line[:p]
        h.Value = strings.Trim(line[p+1:], " ")
        m.AddHeader(h)
    }
    return
}

func (s HttpStream) processChunked() ([]byte, bool) {
    var body []byte
    for {
        buf, err := s.reader.ReadUntil([]byte("\r\n"))
        if err != nil {
            return body, false
        }
        l := string(buf)
        l = strings.Trim(l[:len(l)-2], " ")
        blockLength, err := strconv.ParseInt(l, 16, 32)

        if err != nil {
            panic("bad chunked block length: " + l + "\n" + err.Error())
        }

        buf, err = s.reader.Next(int(blockLength))
        body = append(body, buf...)
        if err != nil {
            return body, false
        }
        buf, err = s.reader.Next(2)
        if err != nil {
            return body, false
        }
        CRLF := string(buf)
        if CRLF != "\r\n" {
            panic("bad chunked block data")
        }

        if blockLength == 0 {
            break
        }
    }
    return body, true
}

func (f HttpStream) processContentLength(contentLength int) ([]byte, bool) {
    body, err := f.reader.Next(contentLength)
    return body, err == nil
}

func (f HttpStream) processBody(contentLength int, chunked bool) (body []byte, more bool) {
    if chunked {
        body, more = f.processChunked()
    } else {
        body, more = f.processContentLength(contentLength)
    }
    return
}

func GetHttpSizeAndEncoding(hs []HttpHeaderItem) (contentLength int, chunked bool, contentEncoding string, contentType string) {
    for _, h := range hs {
        if h.Name == "Content-Length" {
            var err error
            contentLength, err = strconv.Atoi(h.Value)
            if err != nil {
                panic("Content-Length error: " + h.Value)
            }
        } else if h.Name == "Transfer-Encoding" && h.Value == "chunked" {
            chunked = true
        } else if h.Name == "Content-Encoding" {
            contentEncoding = h.Value
        } else if h.Name == "Content-Type" {
            contentType = h.Value
        }
    }
    return
}

func syncStreamPir(s HttpStream) {
    if s.direction == DownDirection {
        <-s.sem
    } else if s.direction == UpDirection {
        s.sem <- 1
    }
}

func (s HttpStream) Process() {
    defer func() {
        if r := recover(); r != nil {
            *s.bad = true
            //close(s.reader.src)
            fmt.Println("HttpStream error: ", r)
        }
    }()
    defer s.wg.Done()
    for {
        m, more := s.getHeader()
        if !more {
            break
        }

        contentLength, chunked, cEncoding, cType := GetHttpSizeAndEncoding(m.Header())
        if contentLength == 0 && !chunked {
            syncStreamPir(s)
            s.eventChan <- m
            s.requestSeq++
            continue
        }

        body, more := s.processBody(contentLength, chunked)
        if !more {
            break
        }
        if strings.HasPrefix(cType, "text/") {
            var uncompressedBody []byte
            var err error
            if cEncoding == "gzip" {
                buffer := bytes.NewBuffer(body)
                zipReader, _ := gzip.NewReader(buffer)
                uncompressedBody, err = ioutil.ReadAll(zipReader)
                defer zipReader.Close()
                if err != nil {
                    m.SetBody("gzip data uncompress error")
                } else {
                    m.SetBody(string(uncompressedBody))
                }
            }
        } else {
            m.SetBody("binary data...")
        }
        m.SetEndTimestamp(s.reader.lastTimeStamp)
        syncStreamPir(s)
        s.eventChan <- m
        s.requestSeq++
    }
}

func NewHttpStream(src PacketSource, meta string, seq uint, wg *sync.WaitGroup, eventChan chan interface{}, sem chan byte, dir Direction) HttpStream {
    var s HttpStream
    s.reader = NewHttpStreamReader(src, seq)
    s.streamSeq = seq
    s.wg = wg
    s.eventChan = eventChan
    s.sem = sem
    s.direction = dir
    s.bad = new(bool)
    return s
}

func (s HttpStream) Reassembled(rs []tcpassembly.Reassembly) {
    for _, r := range rs {
        if *s.bad {
            break
        }
        s.reader.src <- r
    }
}

func (s HttpStream) ReassemblyComplete() {
    close(s.reader.src)
}

type HttpStreamPair struct {
    upStream   *HttpStream
    downStream *HttpStream
    sem        chan byte
    connSeq    uint
}

type HttpStreamFactory struct {
    wg         *sync.WaitGroup
    seq        *uint
    uniStreams *map[string]*HttpStreamPair
    eventChan  *chan interface{}
}

func NewHttpStreamFactory(out chan interface{}) HttpStreamFactory {
    var f HttpStreamFactory
    f.seq = new(uint)
    *f.seq = 0
    f.wg = new(sync.WaitGroup)
    f.uniStreams = new(map[string]*HttpStreamPair)
    *f.uniStreams = make(map[string]*HttpStreamPair)
    f.eventChan = new(chan interface{})
    *f.eventChan = out
    return f
}

func (f HttpStreamFactory) Wait() {
    f.wg.Wait()
}

func (f HttpStreamFactory) New(netFlow, tcpFlow gopacket.Flow) (ret tcpassembly.Stream) {
    revkey := fmt.Sprintf("%v:%v->%v:%v",
        netFlow.Dst(),
        tcpFlow.Dst(),
        netFlow.Src(),
        tcpFlow.Src())
    streamPair, ok := (*f.uniStreams)[revkey]
    src := make(PacketSource)
    if ok {
        if streamPair.upStream == nil {
            panic("fuck")
        }
        delete(*f.uniStreams, revkey)
        s := NewHttpStream(src, "", streamPair.connSeq, f.wg, *f.eventChan, streamPair.sem, DownDirection)
        streamPair.downStream = &s
        ret = s
        go s.Process()
    } else {
        streamPair = new(HttpStreamPair)
        streamPair.connSeq = *f.seq
        streamPair.sem = make(chan byte, 1)
        key := fmt.Sprintf("%v:%v->%v:%v",
            netFlow.Src(),
            tcpFlow.Src(),
            netFlow.Dst(),
            tcpFlow.Dst())
        s := NewHttpStream(src, "", streamPair.connSeq, f.wg, *f.eventChan, streamPair.sem, UpDirection)
        streamPair.upStream = &s
        (*f.uniStreams)[key] = streamPair
        *f.seq++
        //fmt.Printf("#%d Connect %s\n", streamPair.connSeq, key)
        go s.Process()
        ret = s
    }
    f.wg.Add(1)
    return
}
<|endoftext|>


<commit_before>package router

import (
	"net/http"
	"regexp"
	"strings"

	"gopkg.in/karlseguin/params.v2"
	"gopkg.in/karlseguin/scratch.v1"
)

type Action struct {
	Name    string
	Handler Handler
}

var (
	AllMethods  = []string{"GET", "POST", "PUT", "DELETE", "PURGE", "PATCH", "OPTIONS", "HEAD"}
	EmptyParams = params.New(0)
)

type Handler func(out http.ResponseWriter, req *Request)

type Router struct {
	notFound  *Action
	routes    map[string]*RoutePart
	ParamPool *params.Pool
	valuePool *scratch.StringsPool
}

func New(config *Configuration) *Router {
	router := &Router{
		routes:   make(map[string]*RoutePart),
		notFound: &Action{"", notFoundHandler},
	}
	router.ParamPool = params.NewPool(config.paramPoolSize, config.paramPoolCount)
	router.valuePool = scratch.NewStrings(config.paramPoolSize, config.paramPoolCount)
	return router
}

func (r *Router) NotFound(handler Handler) {
	r.notFound = &Action{"", handler}
}

func (r *Router) Add(method, path string, handler Handler) {
	r.AddNamed(method+":"+path, method, path, handler)
}

func (r *Router) AddNamed(name, method, path string, handler Handler) {
	if method == "ALL" {
		for _, m := range AllMethods {
			r.AddNamed(name, m, path, handler)
		}
		return
	}
	rp, exists := r.routes[method]
	if exists == false {
		rp = newRoutePart()
		r.routes[method] = rp
	}
	r.add(rp, path, &Action{name, handler})
}

func (r *Router) All(path string, handler Handler) {
	for _, method := range AllMethods {
		r.Add(method, path, handler)
	}
}

func (r *Router) AllNamed(name, path string, handler Handler) {
	for _, method := range AllMethods {
		r.AddNamed(name, method, path, handler)
	}
}

func (r *Router) Get(path string, handler Handler) {
	r.Add("GET", path, handler)
}

func (r *Router) Post(path string, handler Handler) {
	r.Add("POST", path, handler)
}

func (r *Router) Put(path string, handler Handler) {
	r.Add("PUT", path, handler)
}

func (r *Router) Delete(path string, handler Handler) {
	r.Add("DELETE", path, handler)
}

func (r *Router) Purge(path string, handler Handler) {
	r.Add("PURGE", path, handler)
}

func (r *Router) Patch(path string, handler Handler) {
	r.Add("PATCH", path, handler)
}

func (r *Router) Options(path string, handler Handler) {
	r.Add("OPTIONS", path, handler)
}

func (r *Router) ServeHTTP(out http.ResponseWriter, hr *http.Request) {
	params, action := r.Lookup(hr)
	defer params.Release()
	req := NewRequest(hr, params)

	if action == nil || action.Handler == nil {
		r.notFound.Handler(out, req)
		return
	}
	action.Handler(out, req)
}

func (r *Router) Lookup(req *http.Request) (*params.Params, *Action) {
	rp, ok := r.routes[req.Method]
	params := EmptyParams
	if ok == false {
		return params, nil
	}
	path := req.URL.Path
	if path == "" || path == "/" {
		action := rp.action
		if action == nil {
			action = r.notFound
		}
		return params, action
	}

	if path[0] == '/' {
		path = path[1:]
	}
	if path[len(path)-1] == '/' {
		path = path[:(len(path) - 1)]
	}

	values := r.valuePool.Checkout()
	defer values.Release()
	var action *Action
	var glob *RoutePart
	for {
		original := rp
		index := strings.Index(path, "/")
		if index == -1 {
			index = len(path)
		}
		part := path[:index]
		if rp, ok = rp.parts[part]; ok == false {
			if original.prefixes != nil {
				lower := strings.ToLower(part)
				for _, prefix := range original.prefixes {
					if strings.HasPrefix(lower, prefix.value) {
						rp = original
						action = prefix.action
						break
					}
				}
				if action != nil {
					break
				}
			}
			l := len(part)
			for _, param := range original.params {
				p := part
				if lp := len(param.suffix); lp > 0 {
					if strings.HasSuffix(p, param.suffix) == false {
						continue
					}
					p = part[:l-lp]
				}
				if param.constraint == nil || param.constraint.MatchString(p) {
					rp = param.route
					part = p
					break
				}
			}
			if rp == nil {
				if original.glob {
					glob = original
				}
				break
			}
			values.Add(part)
		}

		if rp == nil || len(path) == index {
			break
		}
		if rp.glob {
			glob = rp
		}
		path = path[index+1:]
	}

	if rp == nil || rp.action == nil {
		if glob == nil {
			return params, nil
		}
		rp = glob
	}

	if rp.action == nil && action == nil {
		return params, nil
	}

	if l := values.Len(); l > 0 {
		params = r.ParamPool.Checkout()
		if lp := len(rp.variables); l > lp {
			l = lp
		}
		v := values.Values()
		for i := 0; i < l; i++ {
			params.Set(rp.variables[i], v[i])
		}
	}
	if action == nil {
		action = rp.action
	}
	return params, action
}

func (r *Router) add(rp *RoutePart, path string, action *Action) {
	if path == "" || path == "/" {
		rp.action = action
		return
	}

	if path[0] == '/' {
		path = path[1:]
	}
	if path[len(path)-1] == '/' {
		path = path[:(len(path) - 1)]
	}

	variables := make([]string, 0, 1)
	parts := strings.Split(path, "/")
	for _, part := range parts {
		if part[len(part)-1] == '*' {
			p := strings.ToLower(part[:len(part)-1])
			if len(p) == 0 {
				rp.glob = true
			} else {
				prefix := Prefix{value: p, action: action}
				rp.prefixes = append(rp.prefixes, prefix)
			}
			break
		}
		var sub *RoutePart
		if part[0] == ':' {
			var constraint *regexp.Regexp
			var suffix string
			variable := part[1:]
			if i := strings.IndexByte(variable, ':'); i != -1 {
				suffix = variable[i+1:]
				variable = variable[:i]
			}
			l := len(variable) - 1
			if variable[l] == ')' {
				if start := strings.IndexByte(variable, '('); start != -1 {
					constraint = regexp.MustCompile(variable[start+1 : l])
					variable = variable[:start]
				}
			}
			variables = append(variables, variable)
			for _, param := range rp.params {
				if param.constraint == nil && constraint == nil && len(param.suffix) == 0 && len(suffix) == 0 {
					sub = param.route
					break
				}
				if param.constraint == nil || constraint == nil || len(param.suffix) != 0 || len(suffix) != 0 {
					continue
				}
				if param.constraint.String() == constraint.String() && param.suffix == suffix {
					sub = param.route
					break
				}
			}

			if sub == nil {
				sub = newRoutePart()
				rp.params = append(rp.params, newParam(constraint, sub, suffix))
			}
		} else if sub = rp.parts[part]; sub == nil {
			sub = newRoutePart()
			rp.parts[part] = sub
		}
		rp = sub
	}
	if len(variables) > 0 {
		rp.variables = variables
	}
	if rp.action == nil {
		rp.action = action
	}
}

func (r Router) Routes() map[string]*RoutePart {
	return r.routes
}

func notFoundHandler(out http.ResponseWriter, req *Request) {
	out.WriteHeader(404)
}
<commit_msg>ability to lookup a route / action based on method + path instead in addition to a full *http.Request<commit_after>package router

import (
	"net/http"
	"regexp"
	"strings"

	"gopkg.in/karlseguin/params.v2"
	"gopkg.in/karlseguin/scratch.v1"
)

type Action struct {
	Name    string
	Handler Handler
}

var (
	AllMethods  = []string{"GET", "POST", "PUT", "DELETE", "PURGE", "PATCH", "OPTIONS", "HEAD"}
	EmptyParams = params.New(0)
)

type Handler func(out http.ResponseWriter, req *Request)

type Router struct {
	notFound  *Action
	routes    map[string]*RoutePart
	ParamPool *params.Pool
	valuePool *scratch.StringsPool
}

func New(config *Configuration) *Router {
	router := &Router{
		routes:   make(map[string]*RoutePart),
		notFound: &Action{"", notFoundHandler},
	}
	router.ParamPool = params.NewPool(config.paramPoolSize, config.paramPoolCount)
	router.valuePool = scratch.NewStrings(config.paramPoolSize, config.paramPoolCount)
	return router
}

func (r *Router) NotFound(handler Handler) {
	r.notFound = &Action{"", handler}
}

func (r *Router) Add(method, path string, handler Handler) {
	r.AddNamed(method+":"+path, method, path, handler)
}

func (r *Router) AddNamed(name, method, path string, handler Handler) {
	if method == "ALL" {
		for _, m := range AllMethods {
			r.AddNamed(name, m, path, handler)
		}
		return
	}
	rp, exists := r.routes[method]
	if exists == false {
		rp = newRoutePart()
		r.routes[method] = rp
	}
	r.add(rp, path, &Action{name, handler})
}

func (r *Router) All(path string, handler Handler) {
	for _, method := range AllMethods {
		r.Add(method, path, handler)
	}
}

func (r *Router) AllNamed(name, path string, handler Handler) {
	for _, method := range AllMethods {
		r.AddNamed(name, method, path, handler)
	}
}

func (r *Router) Get(path string, handler Handler) {
	r.Add("GET", path, handler)
}

func (r *Router) Post(path string, handler Handler) {
	r.Add("POST", path, handler)
}

func (r *Router) Put(path string, handler Handler) {
	r.Add("PUT", path, handler)
}

func (r *Router) Delete(path string, handler Handler) {
	r.Add("DELETE", path, handler)
}

func (r *Router) Purge(path string, handler Handler) {
	r.Add("PURGE", path, handler)
}

func (r *Router) Patch(path string, handler Handler) {
	r.Add("PATCH", path, handler)
}

func (r *Router) Options(path string, handler Handler) {
	r.Add("OPTIONS", path, handler)
}

func (r *Router) ServeHTTP(out http.ResponseWriter, hr *http.Request) {
	params, action := r.Lookup(hr)
	defer params.Release()
	req := NewRequest(hr, params)

	if action == nil || action.Handler == nil {
		r.notFound.Handler(out, req)
		return
	}
	action.Handler(out, req)
}

func (r *Router) Lookup(req *http.Request) (*params.Params, *Action) {
	return r.LookupByParts(req.Method, req.URL.Path)
}

func (r *Router) LookupByParts(method string, path string) (*params.Params, *Action) {
	rp, ok := r.routes[method]
	params := EmptyParams
	if ok == false {
		return params, nil
	}
	if path == "" || path == "/" {
		action := rp.action
		if action == nil {
			action = r.notFound
		}
		return params, action
	}

	if path[0] == '/' {
		path = path[1:]
	}
	if path[len(path)-1] == '/' {
		path = path[:(len(path) - 1)]
	}

	values := r.valuePool.Checkout()
	defer values.Release()
	var action *Action
	var glob *RoutePart
	for {
		original := rp
		index := strings.Index(path, "/")
		if index == -1 {
			index = len(path)
		}
		part := path[:index]
		if rp, ok = rp.parts[part]; ok == false {
			if original.prefixes != nil {
				lower := strings.ToLower(part)
				for _, prefix := range original.prefixes {
					if strings.HasPrefix(lower, prefix.value) {
						rp = original
						action = prefix.action
						break
					}
				}
				if action != nil {
					break
				}
			}
			l := len(part)
			for _, param := range original.params {
				p := part
				if lp := len(param.suffix); lp > 0 {
					if strings.HasSuffix(p, param.suffix) == false {
						continue
					}
					p = part[:l-lp]
				}
				if param.constraint == nil || param.constraint.MatchString(p) {
					rp = param.route
					part = p
					break
				}
			}
			if rp == nil {
				if original.glob {
					glob = original
				}
				break
			}
			values.Add(part)
		}

		if rp == nil || len(path) == index {
			break
		}
		if rp.glob {
			glob = rp
		}
		path = path[index+1:]
	}

	if rp == nil || rp.action == nil {
		if glob == nil {
			return params, nil
		}
		rp = glob
	}

	if rp.action == nil && action == nil {
		return params, nil
	}

	if l := values.Len(); l > 0 {
		params = r.ParamPool.Checkout()
		if lp := len(rp.variables); l > lp {
			l = lp
		}
		v := values.Values()
		for i := 0; i < l; i++ {
			params.Set(rp.variables[i], v[i])
		}
	}
	if action == nil {
		action = rp.action
	}
	return params, action
}

func (r *Router) add(rp *RoutePart, path string, action *Action) {
	if path == "" || path == "/" {
		rp.action = action
		return
	}

	if path[0] == '/' {
		path = path[1:]
	}
	if path[len(path)-1] == '/' {
		path = path[:(len(path) - 1)]
	}

	variables := make([]string, 0, 1)
	parts := strings.Split(path, "/")
	for _, part := range parts {
		if part[len(part)-1] == '*' {
			p := strings.ToLower(part[:len(part)-1])
			if len(p) == 0 {
				rp.glob = true
			} else {
				prefix := Prefix{value: p, action: action}
				rp.prefixes = append(rp.prefixes, prefix)
			}
			break
		}
		var sub *RoutePart
		if part[0] == ':' {
			var constraint *regexp.Regexp
			var suffix string
			variable := part[1:]
			if i := strings.IndexByte(variable, ':'); i != -1 {
				suffix = variable[i+1:]
				variable = variable[:i]
			}
			l := len(variable) - 1
			if variable[l] == ')' {
				if start := strings.IndexByte(variable, '('); start != -1 {
					constraint = regexp.MustCompile(variable[start+1 : l])
					variable = variable[:start]
				}
			}
			variables = append(variables, variable)
			for _, param := range rp.params {
				if param.constraint == nil && constraint == nil && len(param.suffix) == 0 && len(suffix) == 0 {
					sub = param.route
					break
				}
				if param.constraint == nil || constraint == nil || len(param.suffix) != 0 || len(suffix) != 0 {
					continue
				}
				if param.constraint.String() == constraint.String() && param.suffix == suffix {
					sub = param.route
					break
				}
			}

			if sub == nil {
				sub = newRoutePart()
				rp.params = append(rp.params, newParam(constraint, sub, suffix))
			}
		} else if sub = rp.parts[part]; sub == nil {
			sub = newRoutePart()
			rp.parts[part] = sub
		}
		rp = sub
	}
	if len(variables) > 0 {
		rp.variables = variables
	}
	if rp.action == nil {
		rp.action = action
	}
}

func (r Router) Routes() map[string]*RoutePart {
	return r.routes
}

func notFoundHandler(out http.ResponseWriter, req *Request) {
	out.WriteHeader(404)
}
<|endoftext|>


<commit_before>package storage

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"hash"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"path"

	"github.com/fd0/khepri/hashing"
)

const (
	dirMode    = 0700
	objectPath = "objects"
	refPath    = "refs"
	tempPath   = "tmp"
)

var (
	ErrIDDoesNotExist = errors.New("ID does not exist")
)

// Name stands for the alias given to an ID.
type Name string

func (n Name) Encode() string {
	return url.QueryEscape(string(n))
}

type DirRepository struct {
	path string
	hash func() hash.Hash
}

// NewDirRepository creates a new dir-baked repository at the given path.
func NewDirRepository(path string) (*DirRepository, error) {
	d := &DirRepository{
		path: path,
		hash: sha256.New,
	}

	err := d.create()

	if err != nil {
		return nil, err
	}

	return d, nil
}

func (r *DirRepository) create() error {
	dirs := []string{
		r.path,
		path.Join(r.path, objectPath),
		path.Join(r.path, refPath),
		path.Join(r.path, tempPath),
	}

	for _, dir := range dirs {
		err := os.MkdirAll(dir, dirMode)
		if err != nil {
			return err
		}
	}

	return nil
}

// SetHash changes the hash function used for deriving IDs. Default is SHA256.
func (r *DirRepository) SetHash(h func() hash.Hash) {
	r.hash = h
}

// Path returns the directory used for this repository.
func (r *DirRepository) Path() string {
	return r.path
}

// Put saves content and returns the ID.
func (r *DirRepository) Put(reader io.Reader) (ID, error) {
	// save contents to tempfile, hash while writing
	file, err := ioutil.TempFile(path.Join(r.path, tempPath), "temp-")
	if err != nil {
		return nil, err
	}

	rd := hashing.NewReader(reader, r.hash)
	_, err = io.Copy(file, rd)
	if err != nil {
		return nil, err
	}

	err = file.Close()
	if err != nil {
		return nil, err
	}

	// move file to final name using hash of contents
	id := ID(rd.Hash())
	filename := path.Join(r.path, objectPath, id.String())
	err = os.Rename(file.Name(), filename)
	if err != nil {
		return nil, err
	}

	return id, nil
}

// PutFile saves a file's content to the repository and returns the ID.
func (r *DirRepository) PutFile(path string) (ID, error) {
	f, err := os.Open(path)
	defer f.Close()
	if err != nil {
		return nil, err
	}

	return r.Put(f)
}

// PutRaw saves a []byte's content to the repository and returns the ID.
func (r *DirRepository) PutRaw(buf []byte) (ID, error) {
	// save contents to tempfile, hash while writing
	file, err := ioutil.TempFile(path.Join(r.path, tempPath), "temp-")
	if err != nil {
		return nil, err
	}

	wr := hashing.NewWriter(file, r.hash)
	n, err := wr.Write(buf)
	if err != nil {
		return nil, err
	}

	if n != len(buf) {
		return nil, errors.New("not all bytes written")
	}

	err = file.Close()
	if err != nil {
		return nil, err
	}

	// move file to final name using hash of contents
	id := ID(wr.Hash())
	filename := path.Join(r.path, objectPath, id.String())
	err = os.Rename(file.Name(), filename)
	if err != nil {
		return nil, err
	}

	return id, nil
}

// Test returns true if the given ID exists in the repository.
func (r *DirRepository) Test(id ID) (bool, error) {
	// try to open file
	file, err := os.Open(path.Join(r.path, objectPath, id.String()))
	defer func() {
		file.Close()
	}()

	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}

	return true, nil
}

// Get returns a reader for the content stored under the given ID.
func (r *DirRepository) Get(id ID) (io.Reader, error) {
	// try to open file
	file, err := os.Open(path.Join(r.path, objectPath, id.String()))
	if err != nil {
		return nil, err
	}

	return file, nil
}

// Remove removes the content stored at ID.
func (r *DirRepository) Remove(id ID) error {
	return os.Remove(path.Join(r.path, objectPath, id.String()))
}

// Unlink removes a named ID.
func (r *DirRepository) Unlink(name string) error {
	return os.Remove(path.Join(r.path, refPath, Name(name).Encode()))
}

// Link assigns a name to an ID. Name must be unique in this repository and ID must exist.
func (r *DirRepository) Link(name string, id ID) error {
	exist, err := r.Test(id)
	if err != nil {
		return err
	}

	if !exist {
		return ErrIDDoesNotExist
	}

	// create file, write id
	f, err := os.Create(path.Join(r.path, refPath, Name(name).Encode()))
	defer f.Close()

	if err != nil {
		return err
	}

	f.Write([]byte(hex.EncodeToString(id)))
	return nil
}

// Resolve returns the ID associated with the given name.
func (r *DirRepository) Resolve(name string) (ID, error) {
	f, err := os.Open(path.Join(r.path, refPath, Name(name).Encode()))
	defer f.Close()
	if err != nil {
		return nil, err
	}

	// read hex string
	l := r.hash().Size()
	buf := make([]byte, l*2)
	_, err = io.ReadFull(f, buf)

	if err != nil {
		return nil, err
	}

	id := make([]byte, l)
	_, err = hex.Decode(id, buf)
	if err != nil {
		return nil, err
	}

	return ID(id), nil
}
<commit_msg>Checking err is sufficient for io.Writer<commit_after>package storage

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"hash"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"path"

	"github.com/fd0/khepri/hashing"
)

const (
	dirMode    = 0700
	objectPath = "objects"
	refPath    = "refs"
	tempPath   = "tmp"
)

var (
	ErrIDDoesNotExist = errors.New("ID does not exist")
)

// Name stands for the alias given to an ID.
type Name string

func (n Name) Encode() string {
	return url.QueryEscape(string(n))
}

type DirRepository struct {
	path string
	hash func() hash.Hash
}

// NewDirRepository creates a new dir-baked repository at the given path.
func NewDirRepository(path string) (*DirRepository, error) {
	d := &DirRepository{
		path: path,
		hash: sha256.New,
	}

	err := d.create()

	if err != nil {
		return nil, err
	}

	return d, nil
}

func (r *DirRepository) create() error {
	dirs := []string{
		r.path,
		path.Join(r.path, objectPath),
		path.Join(r.path, refPath),
		path.Join(r.path, tempPath),
	}

	for _, dir := range dirs {
		err := os.MkdirAll(dir, dirMode)
		if err != nil {
			return err
		}
	}

	return nil
}

// SetHash changes the hash function used for deriving IDs. Default is SHA256.
func (r *DirRepository) SetHash(h func() hash.Hash) {
	r.hash = h
}

// Path returns the directory used for this repository.
func (r *DirRepository) Path() string {
	return r.path
}

// Put saves content and returns the ID.
func (r *DirRepository) Put(reader io.Reader) (ID, error) {
	// save contents to tempfile, hash while writing
	file, err := ioutil.TempFile(path.Join(r.path, tempPath), "temp-")
	if err != nil {
		return nil, err
	}

	rd := hashing.NewReader(reader, r.hash)
	_, err = io.Copy(file, rd)
	if err != nil {
		return nil, err
	}

	err = file.Close()
	if err != nil {
		return nil, err
	}

	// move file to final name using hash of contents
	id := ID(rd.Hash())
	filename := path.Join(r.path, objectPath, id.String())
	err = os.Rename(file.Name(), filename)
	if err != nil {
		return nil, err
	}

	return id, nil
}

// PutFile saves a file's content to the repository and returns the ID.
func (r *DirRepository) PutFile(path string) (ID, error) {
	f, err := os.Open(path)
	defer f.Close()
	if err != nil {
		return nil, err
	}

	return r.Put(f)
}

// PutRaw saves a []byte's content to the repository and returns the ID.
func (r *DirRepository) PutRaw(buf []byte) (ID, error) {
	// save contents to tempfile, hash while writing
	file, err := ioutil.TempFile(path.Join(r.path, tempPath), "temp-")
	if err != nil {
		return nil, err
	}

	wr := hashing.NewWriter(file, r.hash)
	_, err = wr.Write(buf)
	if err != nil {
		return nil, err
	}
	err = file.Close()
	if err != nil {
		return nil, err
	}

	// move file to final name using hash of contents
	id := ID(wr.Hash())
	filename := path.Join(r.path, objectPath, id.String())
	err = os.Rename(file.Name(), filename)
	if err != nil {
		return nil, err
	}

	return id, nil
}

// Test returns true if the given ID exists in the repository.
func (r *DirRepository) Test(id ID) (bool, error) {
	// try to open file
	file, err := os.Open(path.Join(r.path, objectPath, id.String()))
	defer func() {
		file.Close()
	}()

	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}

	return true, nil
}

// Get returns a reader for the content stored under the given ID.
func (r *DirRepository) Get(id ID) (io.Reader, error) {
	// try to open file
	file, err := os.Open(path.Join(r.path, objectPath, id.String()))
	if err != nil {
		return nil, err
	}

	return file, nil
}

// Remove removes the content stored at ID.
func (r *DirRepository) Remove(id ID) error {
	return os.Remove(path.Join(r.path, objectPath, id.String()))
}

// Unlink removes a named ID.
func (r *DirRepository) Unlink(name string) error {
	return os.Remove(path.Join(r.path, refPath, Name(name).Encode()))
}

// Link assigns a name to an ID. Name must be unique in this repository and ID must exist.
func (r *DirRepository) Link(name string, id ID) error {
	exist, err := r.Test(id)
	if err != nil {
		return err
	}

	if !exist {
		return ErrIDDoesNotExist
	}

	// create file, write id
	f, err := os.Create(path.Join(r.path, refPath, Name(name).Encode()))
	defer f.Close()

	if err != nil {
		return err
	}

	f.Write([]byte(hex.EncodeToString(id)))
	return nil
}

// Resolve returns the ID associated with the given name.
func (r *DirRepository) Resolve(name string) (ID, error) {
	f, err := os.Open(path.Join(r.path, refPath, Name(name).Encode()))
	defer f.Close()
	if err != nil {
		return nil, err
	}

	// read hex string
	l := r.hash().Size()
	buf := make([]byte, l*2)
	_, err = io.ReadFull(f, buf)

	if err != nil {
		return nil, err
	}

	id := make([]byte, l)
	_, err = hex.Decode(id, buf)
	if err != nil {
		return nil, err
	}

	return ID(id), nil
}
<|endoftext|>