<commit_before>package state

import (
	"fmt"
	"github.com/fluffle/golog/logging"
	"reflect"
	"strconv"
)

// A struct representing an IRC channel
type Channel struct {
	Name, Topic string
	Modes       *ChanMode
	lookup      map[string]*Nick
	nicks       map[*Nick]*ChanPrivs
	l           logging.Logger
}

// A struct representing the modes of an IRC Channel
// (the ones we care about, at least).
type ChanMode struct {
	// MODE +p, +s, +t, +n, +m
	Private, Secret, ProtectedTopic, NoExternalMsg, Moderated bool

	// MODE +i, +O, +z
	InviteOnly, OperOnly, SSLOnly bool

	// MODE +k
	Key string

	// MODE +l
	Limit int
}

// A struct representing the modes a Nick can have on a Channel
type ChanPrivs struct {
	// MODE +q, +a, +o, +h, +v
	Owner, Admin, Op, HalfOp, Voice bool
}

// Map ChanMode fields to IRC mode characters
var StringToChanMode = map[string]string{}
var ChanModeToString = map[string]string{
	"Private":        "p",
	"Secret":         "s",
	"ProtectedTopic": "t",
	"NoExternalMsg":  "n",
	"Moderated":      "m",
	"InviteOnly":     "i",
	"OperOnly":       "O",
	"SSLOnly":        "z",
	"Key":            "k",
	"Limit":          "l",
}

// Map *irc.ChanPrivs fields to IRC mode characters
var StringToChanPriv = map[string]string{}
var ChanPrivToString = map[string]string{
	"Owner":  "q",
	"Admin":  "a",
	"Op":     "o",
	"HalfOp": "h",
	"Voice":  "v",
}

// Map *irc.ChanPrivs fields to the symbols used to represent these modes
// in NAMES and WHOIS responses
var ModeCharToChanPriv = map[byte]string{}
var ChanPrivToModeChar = map[string]byte{
	"Owner":  '~',
	"Admin":  '&',
	"Op":     '@',
	"HalfOp": '%',
	"Voice":  '+',
}

// Init function to fill in reverse mappings for *toString constants.
func init() {
	for k, v := range ChanModeToString {
		StringToChanMode[v] = k
	}
	for k, v := range ChanPrivToString {
		StringToChanPriv[v] = k
	}
	for k, v := range ChanPrivToModeChar {
		ModeCharToChanPriv[v] = k
	}
}

/******************************************************************************\
 * Channel methods for state management
\******************************************************************************/

func NewChannel(name string, l logging.Logger) *Channel {
	return &Channel{
		Name:   name,
		Modes:  new(ChanMode),
		nicks:  make(map[*Nick]*ChanPrivs),
		lookup: make(map[string]*Nick),
		l:      l,
	}
}

// Returns true if the Nick is associated with the Channel
func (ch *Channel) IsOn(nk *Nick) (*ChanPrivs, bool) {
	cp, ok := ch.nicks[nk]
	return cp, ok
}

func (ch *Channel) IsOnStr(n string) (*Nick, bool) {
	nk, ok := ch.lookup[n]
	return nk, ok
}

// Associates a Nick with a Channel
func (ch *Channel) addNick(nk *Nick, cp *ChanPrivs) {
	if _, ok := ch.nicks[nk]; !ok {
		ch.nicks[nk] = cp
		ch.lookup[nk.Nick] = nk
	} else {
		ch.l.Warn("Channel.addNick(): %s already on %s.", nk.Nick, ch.Name)
	}
}

// Disassociates a Nick from a Channel.
func (ch *Channel) delNick(nk *Nick) {
	if _, ok := ch.nicks[nk]; ok {
		delete(ch.nicks, nk)
		delete(ch.lookup, nk.Nick)
	} else {
		ch.l.Warn("Channel.delNick(): %s not on %s.", nk.Nick, ch.Name)
	}
}

// Parses mode strings for a channel.
func (ch *Channel) ParseModes(modes string, modeargs ...string) {
	var modeop bool // true => add mode, false => remove mode
	var modestr string
	for i := 0; i < len(modes); i++ {
		switch m := modes[i]; m {
		case '+':
			modeop = true
			modestr = string(m)
		case '-':
			modeop = false
			modestr = string(m)
		case 'i':
			ch.Modes.InviteOnly = modeop
		case 'm':
			ch.Modes.Moderated = modeop
		case 'n':
			ch.Modes.NoExternalMsg = modeop
		case 'p':
			ch.Modes.Private = modeop
		case 's':
			ch.Modes.Secret = modeop
		case 't':
			ch.Modes.ProtectedTopic = modeop
		case 'z':
			ch.Modes.SSLOnly = modeop
		case 'O':
			ch.Modes.OperOnly = modeop
		case 'k':
			if modeop && len(modeargs) != 0 {
				ch.Modes.Key, modeargs = modeargs[0], modeargs[1:]
			} else if !modeop {
				ch.Modes.Key = ""
			} else {
				ch.l.Warn("Channel.ParseModes(): not enough arguments to "+
					"process MODE %s %s%c", ch.Name, modestr, m)
			}
		case 'l':
			if modeop && len(modeargs) != 0 {
				ch.Modes.Limit, _ = strconv.Atoi(modeargs[0])
				modeargs = modeargs[1:]
			} else if !modeop {
				ch.Modes.Limit = 0
			} else {
				ch.l.Warn("Channel.ParseModes(): not enough arguments to "+
					"process MODE %s %s%c", ch.Name, modestr, m)
			}
		case 'q', 'a', 'o', 'h', 'v':
			if len(modeargs) != 0 {
				if nk, ok := ch.lookup[modeargs[0]]; ok {
					cp := ch.nicks[nk]
					switch m {
					case 'q':
						cp.Owner = modeop
					case 'a':
						cp.Admin = modeop
					case 'o':
						cp.Op = modeop
					case 'h':
						cp.HalfOp = modeop
					case 'v':
						cp.Voice = modeop
					}
					modeargs = modeargs[1:]
				} else {
					ch.l.Warn("Channel.ParseModes(): untracked nick %s "+
						"received MODE on channel %s", modeargs[0], ch.Name)
				}
			} else {
				ch.l.Warn("Channel.ParseModes(): not enough arguments to "+
					"process MODE %s %s%c", ch.Name, modestr, m)
			}
		default:
			ch.l.Info("Channel.ParseModes(): unknown mode char %c", m)
		}
	}
}

// Returns a string representing the channel. Looks like:
//	Channel: <channel name> e.g. #moo
//	Topic: <channel topic> e.g. Discussing the merits of cows!
//	Mode: <channel modes> e.g. +nsti
//	Nicks:
//		<nick>: <privs> e.g. CowMaster: +o
//		...
func (ch *Channel) String() string {
	str := "Channel: " + ch.Name + "\n\t"
	str += "Topic: " + ch.Topic + "\n\t"
	str += "Modes: " + ch.Modes.String() + "\n\t"
	str += "Nicks: \n"
	for nk, cp := range ch.nicks {
		str += "\t\t" + nk.Nick + ": " + cp.String() + "\n"
	}
	return str
}

// Returns a string representing the channel modes. Looks like:
//	+npk key
func (cm *ChanMode) String() string {
	str := "+"
	a := make([]string, 0)
	v := reflect.Indirect(reflect.ValueOf(cm))
	t := v.Type()
	for i := 0; i < v.NumField(); i++ {
		switch f := v.Field(i); f.Kind() {
		case reflect.Bool:
			if f.Bool() {
				str += ChanModeToString[t.Field(i).Name]
			}
		case reflect.String:
			if f.String() != "" {
				str += ChanModeToString[t.Field(i).Name]
				a = append(a, f.String())
			}
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			if f.Int() != 0 {
				str += ChanModeToString[t.Field(i).Name]
				a = append(a, fmt.Sprintf("%d", f.Int()))
			}
		}
	}
	for _, s := range a {
		if s != "" {
			str += " " + s
		}
	}
	if str == "+" {
		str = "No modes set"
	}
	return str
}

// Returns a string representing the channel privileges. Looks like:
//	+o
func (cp *ChanPrivs) String() string {
	str := "+"
	v := reflect.Indirect(reflect.ValueOf(cp))
	t := v.Type()
	for i := 0; i < v.NumField(); i++ {
		switch f := v.Field(i); f.Kind() {
		// only bools here at the mo too!
		case reflect.Bool:
			if f.Bool() {
				str += ChanPrivToString[t.Field(i).Name]
			}
		}
	}
	if str == "+" {
		str = "No modes set"
	}
	return str
}
<commit_msg>state: channel: add Nicks() and NicksStr() accessors for nicks on channel<commit_after>package state

import (
	"fmt"
	"github.com/fluffle/golog/logging"
	"reflect"
	"strconv"
)

// A struct representing an IRC channel
type Channel struct {
	Name, Topic string
	Modes       *ChanMode
	lookup      map[string]*Nick
	nicks       map[*Nick]*ChanPrivs
	l           logging.Logger
}

// A struct representing the modes of an IRC Channel
// (the ones we care about, at least).
type ChanMode struct {
	// MODE +p, +s, +t, +n, +m
	Private, Secret, ProtectedTopic, NoExternalMsg, Moderated bool

	// MODE +i, +O, +z
	InviteOnly, OperOnly, SSLOnly bool

	// MODE +k
	Key string

	// MODE +l
	Limit int
}

// A struct representing the modes a Nick can have on a Channel
type ChanPrivs struct {
	// MODE +q, +a, +o, +h, +v
	Owner, Admin, Op, HalfOp, Voice bool
}

// Map ChanMode fields to IRC mode characters
var StringToChanMode = map[string]string{}
var ChanModeToString = map[string]string{
	"Private":        "p",
	"Secret":         "s",
	"ProtectedTopic": "t",
	"NoExternalMsg":  "n",
	"Moderated":      "m",
	"InviteOnly":     "i",
	"OperOnly":       "O",
	"SSLOnly":        "z",
	"Key":            "k",
	"Limit":          "l",
}

// Map *irc.ChanPrivs fields to IRC mode characters
var StringToChanPriv = map[string]string{}
var ChanPrivToString = map[string]string{
	"Owner":  "q",
	"Admin":  "a",
	"Op":     "o",
	"HalfOp": "h",
	"Voice":  "v",
}

// Map *irc.ChanPrivs fields to the symbols used to represent these modes
// in NAMES and WHOIS responses
var ModeCharToChanPriv = map[byte]string{}
var ChanPrivToModeChar = map[string]byte{
	"Owner":  '~',
	"Admin":  '&',
	"Op":     '@',
	"HalfOp": '%',
	"Voice":  '+',
}

// Init function to fill in reverse mappings for *toString constants.
func init() {
	for k, v := range ChanModeToString {
		StringToChanMode[v] = k
	}
	for k, v := range ChanPrivToString {
		StringToChanPriv[v] = k
	}
	for k, v := range ChanPrivToModeChar {
		ModeCharToChanPriv[v] = k
	}
}

/******************************************************************************\
 * Channel methods for state management
\******************************************************************************/

func NewChannel(name string, l logging.Logger) *Channel {
	return &Channel{
		Name:   name,
		Modes:  new(ChanMode),
		nicks:  make(map[*Nick]*ChanPrivs),
		lookup: make(map[string]*Nick),
		l:      l,
	}
}

// Returns true if the Nick is associated with the Channel
func (ch *Channel) IsOn(nk *Nick) (*ChanPrivs, bool) {
	cp, ok := ch.nicks[nk]
	return cp, ok
}

func (ch *Channel) IsOnStr(n string) (*Nick, bool) {
	nk, ok := ch.lookup[n]
	return nk, ok
}

// Associates a Nick with a Channel
func (ch *Channel) addNick(nk *Nick, cp *ChanPrivs) {
	if _, ok := ch.nicks[nk]; !ok {
		ch.nicks[nk] = cp
		ch.lookup[nk.Nick] = nk
	} else {
		ch.l.Warn("Channel.addNick(): %s already on %s.", nk.Nick, ch.Name)
	}
}

// Disassociates a Nick from a Channel.
func (ch *Channel) delNick(nk *Nick) {
	if _, ok := ch.nicks[nk]; ok {
		delete(ch.nicks, nk)
		delete(ch.lookup, nk.Nick)
	} else {
		ch.l.Warn("Channel.delNick(): %s not on %s.", nk.Nick, ch.Name)
	}
}

// Parses mode strings for a channel.
func (ch *Channel) ParseModes(modes string, modeargs ...string) {
	var modeop bool // true => add mode, false => remove mode
	var modestr string
	for i := 0; i < len(modes); i++ {
		switch m := modes[i]; m {
		case '+':
			modeop = true
			modestr = string(m)
		case '-':
			modeop = false
			modestr = string(m)
		case 'i':
			ch.Modes.InviteOnly = modeop
		case 'm':
			ch.Modes.Moderated = modeop
		case 'n':
			ch.Modes.NoExternalMsg = modeop
		case 'p':
			ch.Modes.Private = modeop
		case 's':
			ch.Modes.Secret = modeop
		case 't':
			ch.Modes.ProtectedTopic = modeop
		case 'z':
			ch.Modes.SSLOnly = modeop
		case 'O':
			ch.Modes.OperOnly = modeop
		case 'k':
			if modeop && len(modeargs) != 0 {
				ch.Modes.Key, modeargs = modeargs[0], modeargs[1:]
			} else if !modeop {
				ch.Modes.Key = ""
			} else {
				ch.l.Warn("Channel.ParseModes(): not enough arguments to "+
					"process MODE %s %s%c", ch.Name, modestr, m)
			}
		case 'l':
			if modeop && len(modeargs) != 0 {
				ch.Modes.Limit, _ = strconv.Atoi(modeargs[0])
				modeargs = modeargs[1:]
			} else if !modeop {
				ch.Modes.Limit = 0
			} else {
				ch.l.Warn("Channel.ParseModes(): not enough arguments to "+
					"process MODE %s %s%c", ch.Name, modestr, m)
			}
		case 'q', 'a', 'o', 'h', 'v':
			if len(modeargs) != 0 {
				if nk, ok := ch.lookup[modeargs[0]]; ok {
					cp := ch.nicks[nk]
					switch m {
					case 'q':
						cp.Owner = modeop
					case 'a':
						cp.Admin = modeop
					case 'o':
						cp.Op = modeop
					case 'h':
						cp.HalfOp = modeop
					case 'v':
						cp.Voice = modeop
					}
					modeargs = modeargs[1:]
				} else {
					ch.l.Warn("Channel.ParseModes(): untracked nick %s "+
						"received MODE on channel %s", modeargs[0], ch.Name)
				}
			} else {
				ch.l.Warn("Channel.ParseModes(): not enough arguments to "+
					"process MODE %s %s%c", ch.Name, modestr, m)
			}
		default:
			ch.l.Info("Channel.ParseModes(): unknown mode char %c", m)
		}
	}
}

// Nicks returns a list of *Nick that are on the channel.
func (ch *Channel) Nicks() []*Nick {
	nicks := make([]*Nick, 0, len(ch.lookup))
	for _, nick := range ch.lookup {
		nicks = append(nicks, nick)
	}
	return nicks
}

// NicksStr returns a list of nick strings that are on the channel.
func (ch *Channel) NicksStr() []string {
	nicks := make([]string, 0, len(ch.lookup))
	for _, nick := range ch.lookup {
		nicks = append(nicks, nick.Nick)
	}
	return nicks
}

// Returns a string representing the channel. Looks like:
//	Channel: <channel name> e.g. #moo
//	Topic: <channel topic> e.g. Discussing the merits of cows!
//	Mode: <channel modes> e.g. +nsti
//	Nicks:
//		<nick>: <privs> e.g. CowMaster: +o
//		...
func (ch *Channel) String() string {
	str := "Channel: " + ch.Name + "\n\t"
	str += "Topic: " + ch.Topic + "\n\t"
	str += "Modes: " + ch.Modes.String() + "\n\t"
	str += "Nicks: \n"
	for nk, cp := range ch.nicks {
		str += "\t\t" + nk.Nick + ": " + cp.String() + "\n"
	}
	return str
}

// Returns a string representing the channel modes. Looks like:
//	+npk key
func (cm *ChanMode) String() string {
	str := "+"
	a := make([]string, 0)
	v := reflect.Indirect(reflect.ValueOf(cm))
	t := v.Type()
	for i := 0; i < v.NumField(); i++ {
		switch f := v.Field(i); f.Kind() {
		case reflect.Bool:
			if f.Bool() {
				str += ChanModeToString[t.Field(i).Name]
			}
		case reflect.String:
			if f.String() != "" {
				str += ChanModeToString[t.Field(i).Name]
				a = append(a, f.String())
			}
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			if f.Int() != 0 {
				str += ChanModeToString[t.Field(i).Name]
				a = append(a, fmt.Sprintf("%d", f.Int()))
			}
		}
	}
	for _, s := range a {
		if s != "" {
			str += " " + s
		}
	}
	if str == "+" {
		str = "No modes set"
	}
	return str
}

// Returns a string representing the channel privileges. Looks like:
//	+o
func (cp *ChanPrivs) String() string {
	str := "+"
	v := reflect.Indirect(reflect.ValueOf(cp))
	t := v.Type()
	for i := 0; i < v.NumField(); i++ {
		switch f := v.Field(i); f.Kind() {
		// only bools here at the mo too!
		case reflect.Bool:
			if f.Bool() {
				str += ChanPrivToString[t.Field(i).Name]
			}
		}
	}
	if str == "+" {
		str = "No modes set"
	}
	return str
}
<|endoftext|>


<commit_before>package models

import (
	"socialapi/request"
	"strings"
	"time"

	"github.com/koding/bongo"
)

// ChannelLink holds the link between two channels
type ChannelLink struct {
	// Id holds the unique id of the link between channels
	Id int64 `json:"id,string"`

	// RootId is the id of the root channel
	RootId int64 `json:"rootId,string"       sql:"NOT NULL"`

	// LeafId is the id of the leaf channel
	LeafId int64 `json:"leafId,string"       sql:"NOT NULL"`

	// CreatedAt holds the creation time of the channel_link
	CreatedAt time.Time `json:"createdAt"    sql:"NOT NULL"`

	// IsFinished holds the linking process
	// we link leaf channel to the root channel
	// and marks channel_link as true
	// if isFinished is false, it means
	// linking the channels process is not done yet.
	IsFinished bool `json:"isFinished"		  sql:"NOT NULL"`

	// options for operations

	// DeleteMessages remove the messages of a channel
	DeleteMessages bool `json:"deleteMessages,omitempty" sql:"-"`
}

// List gets the all leaves of a given channel
func (c *ChannelLink) List(q *request.Query) ([]Channel, error) {
	if c.RootId == 0 {
		return nil, ErrRootIsNotSet
	}

	var leafIds []int64

	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
		},
		Pluck:      "leaf_id",
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
	}

	err := c.Some(&leafIds, bq)
	if err != nil {
		return nil, err
	}

	return NewChannel().FetchByIds(leafIds)
}

// FetchRoot fetches the root of a channel if linked
func (c *ChannelLink) FetchRoot() (*Channel, error) {
	if c.LeafId == 0 {
		return nil, ErrLeafIsNotSet
	}

	var rootIds []int64

	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"leaf_id": c.LeafId,
		},
		Pluck: "root_id",
	}

	if err := c.Some(&rootIds, bq); err != nil {
		return nil, err
	}

	if len(rootIds) == 0 {
		return nil, bongo.RecordNotFound
	}

	channel := NewChannel()
	if err := channel.ById(rootIds[0]); err != nil {
		return nil, err
	}

	return channel, nil
}

// Create creates a link between two channels
func (c *ChannelLink) Create() error {
	return c.create()
}

// Delete removes the link between two channels, most probably it wont touch to
// the messages
func (c *ChannelLink) Delete() error {
	if err := c.validate(); err != nil {
		return err
	}

	// first update the leaf node with it's previous channel type constant
	leaf := NewChannel()
	if err := leaf.ById(c.LeafId); err != nil {
		if err == bongo.RecordNotFound {
			return ErrChannelNotFound
		}

		return err
	}

	leaf.TypeConstant = strings.TrimPrefix(
		string(leaf.TypeConstant),
		ChannelLinkedPrefix,
	)

	if err := leaf.Update(); err != nil {
		return err
	}

	toBeDeletedCL := NewChannelLink()
	// then delete the link between two channels
	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
			"leaf_id": c.LeafId,
		},
	}

	if err := toBeDeletedCL.One(bq); err != nil {
		return err
	}

	return bongo.B.Delete(toBeDeletedCL)
}

// Blacklist deletes the messages and links the leaf channel to the root while
// removing the participants
func (c *ChannelLink) Blacklist() error {
	c.DeleteMessages = true
	return c.Create()
}

func (c *ChannelLink) create() error {
	if err := c.validate(); err != nil {
		return err
	}

	// it controls the channel linking process, if not finished
	// then dont try to link another channel and return error
	isInProgress, err := c.IsInProgress(c.RootId)
	if err != nil {
		return err
	}
	if isInProgress {
		return ErrLinkingProcessNotDone
	}

	// then delete the link between two channels
	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
			"leaf_id": c.LeafId,
		},
	}

	// if there is no error, it means we already have it
	if err := c.One(bq); err == nil {
		c.AfterCreate() // just mimic as if created
		return nil
	}

	// first update the leaf
	leaf := NewChannel()
	if err := leaf.ById(c.LeafId); err != nil {
		if err == bongo.RecordNotFound {
			return ErrChannelNotFound
		}

		return err
	}

	// mark channel as linked
	leaf.TypeConstant = ChannelLinkedPrefix + leaf.TypeConstant

	if err := leaf.Update(); err != nil {
		return err
	}

	return bongo.B.Create(c)
}

// IsInProgress checks the db if linking process is completely done or not
// if true -> linking process is not done
// if false -> linking process is completely done
// We will searh root channel in db as leaf channel,
// Aim of this search, protect linking conflict of root-leaf channels
func (c *ChannelLink) IsInProgress(rootID int64) (bool, error) {
	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"leaf_id":     rootID,
			"is_finished": false,
		},
	}

	// if there is no error, it means we already have it
	err := c.One(bq)

	if err != nil && err != bongo.RecordNotFound {
		return false, err
	}

	if err == nil {
		return true, nil
	}

	return false, nil
}

func (c *ChannelLink) validate() error {
	if c.LeafId == 0 {
		return ErrLeafIsNotSet
	}

	if c.RootId == 0 {
		return ErrRootIsNotSet
	}

	return nil
}
<commit_msg>social: One is used in NewChannelLink<commit_after>package models

import (
	"socialapi/request"
	"strings"
	"time"

	"github.com/koding/bongo"
)

// ChannelLink holds the link between two channels
type ChannelLink struct {
	// Id holds the unique id of the link between channels
	Id int64 `json:"id,string"`

	// RootId is the id of the root channel
	RootId int64 `json:"rootId,string"       sql:"NOT NULL"`

	// LeafId is the id of the leaf channel
	LeafId int64 `json:"leafId,string"       sql:"NOT NULL"`

	// CreatedAt holds the creation time of the channel_link
	CreatedAt time.Time `json:"createdAt"    sql:"NOT NULL"`

	// IsFinished holds the linking process
	// we link leaf channel to the root channel
	// and marks channel_link as true
	// if isFinished is false, it means
	// linking the channels process is not done yet.
	IsFinished bool `json:"isFinished"		  sql:"NOT NULL"`

	// options for operations

	// DeleteMessages remove the messages of a channel
	DeleteMessages bool `json:"deleteMessages,omitempty" sql:"-"`
}

// List gets the all leaves of a given channel
func (c *ChannelLink) List(q *request.Query) ([]Channel, error) {
	if c.RootId == 0 {
		return nil, ErrRootIsNotSet
	}

	var leafIds []int64

	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
		},
		Pluck:      "leaf_id",
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
	}

	err := c.Some(&leafIds, bq)
	if err != nil {
		return nil, err
	}

	return NewChannel().FetchByIds(leafIds)
}

// FetchRoot fetches the root of a channel if linked
func (c *ChannelLink) FetchRoot() (*Channel, error) {
	if c.LeafId == 0 {
		return nil, ErrLeafIsNotSet
	}

	var rootIds []int64

	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"leaf_id": c.LeafId,
		},
		Pluck: "root_id",
	}

	if err := c.Some(&rootIds, bq); err != nil {
		return nil, err
	}

	if len(rootIds) == 0 {
		return nil, bongo.RecordNotFound
	}

	channel := NewChannel()
	if err := channel.ById(rootIds[0]); err != nil {
		return nil, err
	}

	return channel, nil
}

// Create creates a link between two channels
func (c *ChannelLink) Create() error {
	return c.create()
}

// Delete removes the link between two channels, most probably it wont touch to
// the messages
func (c *ChannelLink) Delete() error {
	if err := c.validate(); err != nil {
		return err
	}

	// first update the leaf node with it's previous channel type constant
	leaf := NewChannel()
	if err := leaf.ById(c.LeafId); err != nil {
		if err == bongo.RecordNotFound {
			return ErrChannelNotFound
		}

		return err
	}

	leaf.TypeConstant = strings.TrimPrefix(
		string(leaf.TypeConstant),
		ChannelLinkedPrefix,
	)

	if err := leaf.Update(); err != nil {
		return err
	}

	toBeDeletedCL := NewChannelLink()
	// then delete the link between two channels
	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
			"leaf_id": c.LeafId,
		},
	}

	if err := toBeDeletedCL.One(bq); err != nil {
		return err
	}

	return bongo.B.Delete(toBeDeletedCL)
}

// Blacklist deletes the messages and links the leaf channel to the root while
// removing the participants
func (c *ChannelLink) Blacklist() error {
	c.DeleteMessages = true
	return c.Create()
}

func (c *ChannelLink) create() error {
	if err := c.validate(); err != nil {
		return err
	}

	// it controls the channel linking process, if not finished
	// then dont try to link another channel and return error
	isInProgress, err := c.IsInProgress(c.RootId)
	if err != nil {
		return err
	}
	if isInProgress {
		return ErrLinkingProcessNotDone
	}

	// then delete the link between two channels
	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
			"leaf_id": c.LeafId,
		},
	}

	// if there is no error, it means we already have it
	if err := c.One(bq); err == nil {
		c.AfterCreate() // just mimic as if created
		return nil
	}

	// first update the leaf
	leaf := NewChannel()
	if err := leaf.ById(c.LeafId); err != nil {
		if err == bongo.RecordNotFound {
			return ErrChannelNotFound
		}

		return err
	}

	// mark channel as linked
	leaf.TypeConstant = ChannelLinkedPrefix + leaf.TypeConstant

	if err := leaf.Update(); err != nil {
		return err
	}

	return bongo.B.Create(c)
}

// IsInProgress checks the db if linking process is completely done or not
// if true -> linking process is not done
// if false -> linking process is completely done
// We will searh root channel in db as leaf channel,
// Aim of this search, protect linking conflict of root-leaf channels
func (c *ChannelLink) IsInProgress(rootID int64) (bool, error) {
	newCh := NewChannelLink()
	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"leaf_id":     rootID,
			"is_finished": false,
		},
	}

	// if there is no error, it means we already have it
	err := newCh.One(bq)

	if err != nil && err != bongo.RecordNotFound {
		return false, err
	}

	if err == nil {
		return true, nil
	}

	return false, nil
}

func (c *ChannelLink) validate() error {
	if c.LeafId == 0 {
		return ErrLeafIsNotSet
	}

	if c.RootId == 0 {
		return ErrRootIsNotSet
	}

	return nil
}
<|endoftext|>


<commit_before>package models

import (
	"socialapi/request"
	"strings"
	"time"

	"github.com/koding/bongo"
)

// ChannelLink holds the link between two channels
type ChannelLink struct {
	// Id holds the unique id of the link between channels
	Id int64 `json:"id,string"`

	// RootId is the id of the root channel
	RootId int64 `json:"rootId,string"       sql:"NOT NULL"`

	// LeafId is the id of the leaf channel
	LeafId int64 `json:"leafId,string"       sql:"NOT NULL"`

	// CreatedAt holds the creation time of the channel_link
	CreatedAt time.Time `json:"createdAt"    sql:"NOT NULL"`

	// options for operations

	// DeleteMessages remove the messages of a channel
	DeleteMessages bool `json:"deleteMessages,omitempty" sql:"-"`
}

// List gets the all leaves of a given channel
func (c *ChannelLink) List(q *request.Query) ([]Channel, error) {
	if c.RootId == 0 {
		return nil, ErrRootIsNotSet
	}

	var leafIds []int64

	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
		},
		Pluck:      "leaf_id",
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
	}

	err := c.Some(&leafIds, bq)
	if err != nil {
		return nil, err
	}

	return NewChannel().FetchByIds(leafIds)
}

// FetchRoot fetches the root of a channel if linked
func (c *ChannelLink) FetchRoot() (*Channel, error) {
	if c.LeafId == 0 {
		return nil, ErrLeafIsNotSet
	}

	var rootIds []int64

	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"leaf_id": c.LeafId,
		},
		Pluck: "root_id",
	}

	if err := c.Some(&rootIds, bq); err != nil {
		return nil, err
	}

	if len(rootIds) == 0 {
		return nil, bongo.RecordNotFound
	}

	channel := NewChannel()
	if err := channel.ById(rootIds[0]); err != nil {
		return nil, err
	}

	return channel, nil
}

// Create creates a link between two channels
func (c *ChannelLink) Create() error {
	return c.create()
}

// Delete removes the link between two channels, most probably it wont touch to
// the messages
func (c *ChannelLink) Delete() error {
	if err := c.validate(); err != nil {
		return err
	}

	// first update the leaf node with it's previous channel type constant
	leaf := NewChannel()
	if err := leaf.ById(c.LeafId); err != nil {
		if err == bongo.RecordNotFound {
			return ErrChannelNotFound
		}

		return err
	}

	leaf.TypeConstant = strings.TrimPrefix(
		string(leaf.TypeConstant),
		ChannelLinkedPrefix,
	)

	if err := leaf.Update(); err != nil {
		return err
	}

	toBeDeletedCL := NewChannelLink()
	// then delete the link between two channels
	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
			"leaf_id": c.LeafId,
		},
	}

	if err := toBeDeletedCL.One(bq); err != nil {
		return err
	}

	return bongo.B.Delete(toBeDeletedCL)
}

// Blacklist deletes the messages and links the leaf channel to the root while
// removing the participants
func (c *ChannelLink) Blacklist() error {
	c.DeleteMessages = true
	return c.Create()
}

func (c *ChannelLink) create() error {
	if err := c.validate(); err != nil {
		return err
	}

	// then delete the link between two channels
	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
			"leaf_id": c.LeafId,
		},
	}

	// if there is no error, it means we already have it
	if err := c.One(bq); err == nil {
		c.AfterCreate() // just mimic as if created
		return nil
	}

	// first update the leaf
	leaf := NewChannel()
	if err := leaf.ById(c.LeafId); err != nil {
		if err == bongo.RecordNotFound {
			return ErrChannelNotFound
		}

		return err
	}

	// mark channel as linked
	leaf.TypeConstant = ChannelLinkedPrefix + leaf.TypeConstant

	if err := leaf.Update(); err != nil {
		return err
	}

	return bongo.B.Create(c)
}

func (c *ChannelLink) validate() error {
	if c.LeafId == 0 {
		return ErrLeafIsNotSet
	}

	if c.RootId == 0 {
		return ErrRootIsNotSet
	}

	return nil
}
<commit_msg>social/moderation: deletedAt is added into channelLink struct<commit_after>package models

import (
	"socialapi/request"
	"strings"
	"time"

	"github.com/koding/bongo"
)

// ChannelLink holds the link between two channels
type ChannelLink struct {
	// Id holds the unique id of the link between channels
	Id int64 `json:"id,string"`

	// RootId is the id of the root channel
	RootId int64 `json:"rootId,string"       sql:"NOT NULL"`

	// LeafId is the id of the leaf channel
	LeafId int64 `json:"leafId,string"       sql:"NOT NULL"`

	// CreatedAt holds the creation time of the channel_link
	CreatedAt time.Time `json:"createdAt"    sql:"NOT NULL"`

	// Deletion date of the leaf channel
	// In theory, we don't delete leaf channel
	// we link leaf channel to the root channel
	// and marks leaf channel as deleted
	DeletedAt time.Time `json:"deletedAt"`

	// options for operations

	// DeleteMessages remove the messages of a channel
	DeleteMessages bool `json:"deleteMessages,omitempty" sql:"-"`
}

// List gets the all leaves of a given channel
func (c *ChannelLink) List(q *request.Query) ([]Channel, error) {
	if c.RootId == 0 {
		return nil, ErrRootIsNotSet
	}

	var leafIds []int64

	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
		},
		Pluck:      "leaf_id",
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
	}

	err := c.Some(&leafIds, bq)
	if err != nil {
		return nil, err
	}

	return NewChannel().FetchByIds(leafIds)
}

// FetchRoot fetches the root of a channel if linked
func (c *ChannelLink) FetchRoot() (*Channel, error) {
	if c.LeafId == 0 {
		return nil, ErrLeafIsNotSet
	}

	var rootIds []int64

	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"leaf_id": c.LeafId,
		},
		Pluck: "root_id",
	}

	if err := c.Some(&rootIds, bq); err != nil {
		return nil, err
	}

	if len(rootIds) == 0 {
		return nil, bongo.RecordNotFound
	}

	channel := NewChannel()
	if err := channel.ById(rootIds[0]); err != nil {
		return nil, err
	}

	return channel, nil
}

// Create creates a link between two channels
func (c *ChannelLink) Create() error {
	return c.create()
}

// Delete removes the link between two channels, most probably it wont touch to
// the messages
func (c *ChannelLink) Delete() error {
	if err := c.validate(); err != nil {
		return err
	}

	// first update the leaf node with it's previous channel type constant
	leaf := NewChannel()
	if err := leaf.ById(c.LeafId); err != nil {
		if err == bongo.RecordNotFound {
			return ErrChannelNotFound
		}

		return err
	}

	leaf.TypeConstant = strings.TrimPrefix(
		string(leaf.TypeConstant),
		ChannelLinkedPrefix,
	)

	if err := leaf.Update(); err != nil {
		return err
	}

	toBeDeletedCL := NewChannelLink()
	// then delete the link between two channels
	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
			"leaf_id": c.LeafId,
		},
	}

	if err := toBeDeletedCL.One(bq); err != nil {
		return err
	}

	return bongo.B.Delete(toBeDeletedCL)
}

// Blacklist deletes the messages and links the leaf channel to the root while
// removing the participants
func (c *ChannelLink) Blacklist() error {
	c.DeleteMessages = true
	return c.Create()
}

func (c *ChannelLink) create() error {
	if err := c.validate(); err != nil {
		return err
	}

	// then delete the link between two channels
	bq := &bongo.Query{
		Selector: map[string]interface{}{
			"root_id": c.RootId,
			"leaf_id": c.LeafId,
		},
	}

	// if there is no error, it means we already have it
	if err := c.One(bq); err == nil {
		c.AfterCreate() // just mimic as if created
		return nil
	}

	// first update the leaf
	leaf := NewChannel()
	if err := leaf.ById(c.LeafId); err != nil {
		if err == bongo.RecordNotFound {
			return ErrChannelNotFound
		}

		return err
	}

	// mark channel as linked
	leaf.TypeConstant = ChannelLinkedPrefix + leaf.TypeConstant

	if err := leaf.Update(); err != nil {
		return err
	}

	return bongo.B.Create(c)
}

func (c *ChannelLink) validate() error {
	if c.LeafId == 0 {
		return ErrLeafIsNotSet
	}

	if c.RootId == 0 {
		return ErrRootIsNotSet
	}

	return nil
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 graph

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

	"github.com/skydive-project/skydive/config"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/topology/graph/gremlin"
	"github.com/skydive-project/skydive/topology/graph/orientdb"
)

type OrientDBBackend struct {
	client *orientdb.Client
}

func graphElementToOrientDBSetString(e graphElement) (s string) {
	properties := []string{
		fmt.Sprintf("ID = \"%s\"", string(e.ID)),
		fmt.Sprintf("Host = \"%s\"", e.host),
	}
	s = strings.Join(properties, ", ")
	if m := metadataToOrientDBSetString(e.metadata); m != "" {
		s += ", " + m
	}
	return
}

func metadataToOrientDBSetString(m Metadata) string {
	properties := []string{}
	for k, v := range m {
		encoder := gremlin.GremlinPropertiesEncoder{}
		encoder.Encode(v)
		properties = append(properties, fmt.Sprintf("%s = %s", k, encoder.String()))
	}
	return strings.Join(properties, ", ")
}

func orientDBDocumentToGraphElement(doc orientdb.Document) graphElement {
	element := graphElement{
		metadata: make(Metadata),
	}
	element.ID = Identifier(doc["ID"].(string))
	delete(doc, "ID")
	if host, ok := doc["Host"]; ok {
		element.host = host.(string)
		delete(doc, "Host")
	}
	for field, value := range doc {
		if !strings.HasPrefix(field, "@") && !strings.HasPrefix(field, "_") {
			field = strings.Replace(field, "/", ".", -1)
			if n, ok := value.(json.Number); ok {
				var err error
				if value, err = n.Int64(); err == nil {
					value = int(value.(int64))
				} else {
					value, _ = n.Float64()
				}
			}
			element.metadata[field] = value
		}
	}
	return element
}

func graphElementToOrientDBDocument(e graphElement) orientdb.Document {
	doc := make(orientdb.Document)
	doc["@class"] = "Node"
	doc["ID"] = e.ID
	doc["Host"] = e.host
	for k, v := range e.metadata {
		k = strings.Replace(k, ".", "/", -1)
		doc[k] = v
	}
	return doc
}

func orientDBDocumentToNode(doc orientdb.Document) *Node {
	delete(doc, "in_Link")
	delete(doc, "out_Link")
	return &Node{
		graphElement: orientDBDocumentToGraphElement(doc),
	}
}

func orientDBDocumentToEdge(doc orientdb.Document) *Edge {
	e := &Edge{}

	if parent, ok := doc["parent"]; ok {
		e.parent = Identifier(parent.(string))
		delete(doc, "parent")
	}

	if child, ok := doc["child"]; ok {
		e.child = Identifier(child.(string))
		delete(doc, "child")
	}

	delete(doc, "in")
	delete(doc, "out")

	e.graphElement = orientDBDocumentToGraphElement(doc)

	return e
}

func (o *OrientDBBackend) AddNode(n *Node) bool {
	doc := graphElementToOrientDBDocument(n.graphElement)
	doc["@class"] = "Node"
	doc["CreatedAt"] = time.Now().String()
	_, err := o.client.CreateDocument(doc)
	if err != nil {
		logging.GetLogger().Errorf("Error while adding node %s: %s", n.ID, err.Error())
		return false
	}
	return true
}

func (o *OrientDBBackend) DelNode(n *Node) bool {
	query := fmt.Sprintf("UPDATE Node SET DeletedAt = '%s' WHERE DeletedAt IS NULL AND ID = '%s'", time.Now(), n.ID)
	docs, err := o.client.Sql(query)
	if err != nil || (err == nil && len(docs) != 1) {
		logging.GetLogger().Errorf("Error while deleting node %s: %s (sql: %s)", n.ID, err.Error(), query)
		return false
	}
	value, ok := docs[0]["value"]
	if !ok {
		return false
	}
	i, err := value.(json.Number).Int64()
	return err == nil && i == 1
}

func (o *OrientDBBackend) GetNode(i Identifier, t *time.Time) *Node {
	query := fmt.Sprintf("SELECT FROM Node WHERE %s AND ID = '%s'", o.getTimeClause(t), i)
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving node %s: %s", i, err.Error())
		return nil
	}
	if len(docs) != 0 {
		return orientDBDocumentToNode(docs[0])
	}
	return nil
}

func (o *OrientDBBackend) GetNodeEdges(n *Node, t *time.Time) (edges []*Edge) {
	query := fmt.Sprintf("SELECT FROM Link WHERE %s AND (parent = '%s' OR child = '%s')", o.getTimeClause(t), n.ID, n.ID)
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving edges for node %s: %s", n.ID, err.Error())
		return nil
	}

	for _, doc := range docs {
		edges = append(edges, orientDBDocumentToEdge(doc))
	}
	return
}

func (o *OrientDBBackend) AddEdge(e *Edge) bool {
	query := fmt.Sprintf("CREATE EDGE Link FROM (SELECT FROM Node WHERE DeletedAt IS NULL AND ID = '%s') TO (SELECT FROM Node WHERE DeletedAt IS NULL AND ID = '%s') SET %s, parent = '%s', child = '%s', CreatedAt = '%s'", e.parent, e.child, graphElementToOrientDBSetString(e.graphElement), e.parent, e.child, time.Now().String())
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while adding edge %s: %s (sql: %s)", e.ID, err.Error(), query)
		return false
	}
	return len(docs) == 1
}

func (o *OrientDBBackend) DelEdge(e *Edge) bool {
	query := fmt.Sprintf("UPDATE Link SET DeletedAt = '%s' WHERE DeletedAt IS NULL AND ID = '%s'", time.Now(), e.ID)
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while deleting edge %s: %s", e.ID, err.Error())
		return false
	}
	value, ok := docs[0]["value"]
	if !ok {
		return false
	}
	i, err := value.(json.Number).Int64()
	return err == nil && i == 1
}

func (o *OrientDBBackend) GetEdge(i Identifier, t *time.Time) *Edge {
	query := fmt.Sprintf("SELECT FROM Link WHERE %s AND ID = '%s'", o.getTimeClause(t), i)
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving edge %s: %s", i, err.Error())
		return nil
	}
	if len(docs) != 0 {
		return orientDBDocumentToEdge(docs[0])
	}
	return nil
}

func (o *OrientDBBackend) GetEdgeNodes(e *Edge, t *time.Time) (n1 *Node, n2 *Node) {
	query := fmt.Sprintf("SELECT FROM Node WHERE %s AND ID in [\"%s\", \"%s\"]", o.getTimeClause(t), e.parent, e.child)
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving nodes for edge %s: %s", e.ID, err.Error())
		return nil, nil
	}

	var id1 Identifier
	if len(docs) > 0 {
		n1 = orientDBDocumentToNode(docs[0])
		id1 = Identifier(n1.ID)
	}

	if len(docs) > 1 {
		n2 = orientDBDocumentToNode(docs[1])
	}

	if id1 == e.parent {
		return n1, n2
	} else {
		return n2, n1
	}
}

func (o *OrientDBBackend) updateGraphElement(i interface{}) bool {
	success := true

	switch i.(type) {
	case *Node:
		node := i.(*Node)
		now := time.Now()
		edges := o.GetNodeEdges(node, &now)
		o.DelNode(node)
		o.AddNode(node)
		for _, e := range edges {
			parent, child := o.GetEdgeNodes(e, &now)
			if parent == nil || child == nil {
				continue
			}

			if success = o.DelEdge(e); !success {
				break
			}

			if success = o.AddEdge(e); !success {
				break
			}
		}

	case *Edge:
		edge := i.(*Edge)
		if success = o.DelEdge(edge); !success {
			break
		}

		success = o.AddEdge(edge)
	}

	return success
}

func (o *OrientDBBackend) AddMetadata(i interface{}, k string, v interface{}) bool {
	success := o.updateGraphElement(i)
	if !success {
		logging.GetLogger().Errorf("Error while adding metadata")
	}
	return success
}

func (o *OrientDBBackend) SetMetadata(i interface{}, m Metadata) bool {
	success := o.updateGraphElement(i)
	if !success {
		logging.GetLogger().Errorf("Error while setting metadata")
	}
	return success
}

func (*OrientDBBackend) getTimeClause(t *time.Time) string {
	var t2 time.Time
	if t == nil {
		t2 = time.Now()
	} else {
		t2 = *t
	}
	s := t2.String()
	return fmt.Sprintf("CreatedAt <= '%s' AND (DeletedAt > '%s' OR DeletedAt is NULL)", s, s)
}

func (o *OrientDBBackend) GetNodes(t *time.Time) (nodes []*Node) {
	query := fmt.Sprintf("SELECT FROM Node WHERE %s ", o.getTimeClause(t))
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving nodes: %s", err.Error(), docs)
		return
	}

	for _, doc := range docs {
		nodes = append(nodes, orientDBDocumentToNode(doc))
	}

	return
}

func (o *OrientDBBackend) GetEdges(t *time.Time) (edges []*Edge) {
	query := fmt.Sprintf("SELECT FROM Link WHERE %s", o.getTimeClause(t))
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving edges: %s", err.Error(), docs)
		return
	}

	for _, doc := range docs {
		edges = append(edges, orientDBDocumentToEdge(doc))
	}

	return
}

func NewOrientDBBackend(addr string, database string, username string, password string) (*OrientDBBackend, error) {
	client, err := orientdb.NewClient(addr, database, username, password)
	if err != nil {
		return nil, err
	}

	if _, err := client.GetDocumentClass("Node"); err != nil {
		class := orientdb.ClassDefinition{
			Name:       "Node",
			SuperClass: "V",
			Properties: []orientdb.Property{
				{Name: "ID", Type: "STRING", Mandatory: true, NotNull: true},
				{Name: "Host", Type: "STRING", Mandatory: true, NotNull: true},
				{Name: "CreatedAt", Type: "DATETIME", Mandatory: true, NotNull: true, ReadOnly: true},
				{Name: "DeletedAt", Type: "DATETIME", NotNull: true},
			},
			Indexes: []orientdb.Index{
				{Name: "Node.TimeSpan", Fields: []string{"CreatedAt", "DeletedAt"}, Type: "NOTUNIQUE"},
			},
		}
		if err := client.CreateDocumentClass(class); err != nil {
			return nil, fmt.Errorf("Failed to register class Node: %s", err.Error())
		}
	}

	if _, err := client.GetDocumentClass("Link"); err != nil {
		class := orientdb.ClassDefinition{
			Name:       "Link",
			SuperClass: "E",
			Properties: []orientdb.Property{
				{Name: "ID", Type: "STRING", Mandatory: true, NotNull: true},
				{Name: "Host", Type: "STRING", Mandatory: true, NotNull: true},
				{Name: "CreatedAt", Type: "DATETIME", Mandatory: true, NotNull: true, ReadOnly: true},
				{Name: "DeletedAt", Type: "DATETIME", NotNull: true},
				{Name: "parent", Type: "STRING", Mandatory: true, NotNull: true},
				{Name: "child", Type: "STRING", Mandatory: true, NotNull: true},
			},
			Indexes: []orientdb.Index{
				{Name: "Link.TimeSpan", Fields: []string{"CreatedAt", "DeletedAt"}, Type: "NOTUNIQUE"},
			},
		}
		if err := client.CreateDocumentClass(class); err != nil {
			return nil, fmt.Errorf("Failed to register class Link: %s", err.Error())
		}
	}

	return &OrientDBBackend{
		client: client,
	}, nil
}

func NewOrientDBBackendFromConfig() (*OrientDBBackend, error) {
	addr := config.GetConfig().GetString("storage.orientdb.addr")
	database := config.GetConfig().GetString("storage.orientdb.database")
	username := config.GetConfig().GetString("storage.orientdb.username")
	password := config.GetConfig().GetString("storage.orientdb.password")
	return NewOrientDBBackend(addr, database, username, password)
}
<commit_msg>Do not replace slashes by dots in OrientDB metadata<commit_after>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 graph

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

	"github.com/skydive-project/skydive/config"
	"github.com/skydive-project/skydive/logging"
	"github.com/skydive-project/skydive/topology/graph/gremlin"
	"github.com/skydive-project/skydive/topology/graph/orientdb"
)

type OrientDBBackend struct {
	client *orientdb.Client
}

func graphElementToOrientDBSetString(e graphElement) (s string) {
	properties := []string{
		fmt.Sprintf("ID = \"%s\"", string(e.ID)),
		fmt.Sprintf("Host = \"%s\"", e.host),
	}
	s = strings.Join(properties, ", ")
	if m := metadataToOrientDBSetString(e.metadata); m != "" {
		s += ", " + m
	}
	return
}

func metadataToOrientDBSetString(m Metadata) string {
	properties := []string{}
	for k, v := range m {
		encoder := gremlin.GremlinPropertiesEncoder{}
		encoder.Encode(v)
		properties = append(properties, fmt.Sprintf("%s = %s", k, encoder.String()))
	}
	return strings.Join(properties, ", ")
}

func orientDBDocumentToGraphElement(doc orientdb.Document) graphElement {
	element := graphElement{
		metadata: make(Metadata),
	}
	element.ID = Identifier(doc["ID"].(string))
	delete(doc, "ID")
	if host, ok := doc["Host"]; ok {
		element.host = host.(string)
		delete(doc, "Host")
	}
	for field, value := range doc {
		if !strings.HasPrefix(field, "@") && !strings.HasPrefix(field, "_") {
			if n, ok := value.(json.Number); ok {
				var err error
				if value, err = n.Int64(); err == nil {
					value = int(value.(int64))
				} else {
					value, _ = n.Float64()
				}
			}
			element.metadata[field] = value
		}
	}
	return element
}

func graphElementToOrientDBDocument(e graphElement) orientdb.Document {
	doc := make(orientdb.Document)
	doc["@class"] = "Node"
	doc["ID"] = e.ID
	doc["Host"] = e.host
	for k, v := range e.metadata {
		doc[k] = v
	}
	return doc
}

func orientDBDocumentToNode(doc orientdb.Document) *Node {
	delete(doc, "in_Link")
	delete(doc, "out_Link")
	return &Node{
		graphElement: orientDBDocumentToGraphElement(doc),
	}
}

func orientDBDocumentToEdge(doc orientdb.Document) *Edge {
	e := &Edge{}

	if parent, ok := doc["parent"]; ok {
		e.parent = Identifier(parent.(string))
		delete(doc, "parent")
	}

	if child, ok := doc["child"]; ok {
		e.child = Identifier(child.(string))
		delete(doc, "child")
	}

	delete(doc, "in")
	delete(doc, "out")

	e.graphElement = orientDBDocumentToGraphElement(doc)

	return e
}

func (o *OrientDBBackend) AddNode(n *Node) bool {
	doc := graphElementToOrientDBDocument(n.graphElement)
	doc["@class"] = "Node"
	doc["CreatedAt"] = time.Now().String()
	_, err := o.client.CreateDocument(doc)
	if err != nil {
		logging.GetLogger().Errorf("Error while adding node %s: %s", n.ID, err.Error())
		return false
	}
	return true
}

func (o *OrientDBBackend) DelNode(n *Node) bool {
	query := fmt.Sprintf("UPDATE Node SET DeletedAt = '%s' WHERE DeletedAt IS NULL AND ID = '%s'", time.Now(), n.ID)
	docs, err := o.client.Sql(query)
	if err != nil || (err == nil && len(docs) != 1) {
		logging.GetLogger().Errorf("Error while deleting node %s: %s (sql: %s)", n.ID, err.Error(), query)
		return false
	}
	value, ok := docs[0]["value"]
	if !ok {
		return false
	}
	i, err := value.(json.Number).Int64()
	return err == nil && i == 1
}

func (o *OrientDBBackend) GetNode(i Identifier, t *time.Time) *Node {
	query := fmt.Sprintf("SELECT FROM Node WHERE %s AND ID = '%s'", o.getTimeClause(t), i)
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving node %s: %s", i, err.Error())
		return nil
	}
	if len(docs) != 0 {
		return orientDBDocumentToNode(docs[0])
	}
	return nil
}

func (o *OrientDBBackend) GetNodeEdges(n *Node, t *time.Time) (edges []*Edge) {
	query := fmt.Sprintf("SELECT FROM Link WHERE %s AND (parent = '%s' OR child = '%s')", o.getTimeClause(t), n.ID, n.ID)
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving edges for node %s: %s", n.ID, err.Error())
		return nil
	}

	for _, doc := range docs {
		edges = append(edges, orientDBDocumentToEdge(doc))
	}
	return
}

func (o *OrientDBBackend) AddEdge(e *Edge) bool {
	query := fmt.Sprintf("CREATE EDGE Link FROM (SELECT FROM Node WHERE DeletedAt IS NULL AND ID = '%s') TO (SELECT FROM Node WHERE DeletedAt IS NULL AND ID = '%s') SET %s, parent = '%s', child = '%s', CreatedAt = '%s'", e.parent, e.child, graphElementToOrientDBSetString(e.graphElement), e.parent, e.child, time.Now().String())
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while adding edge %s: %s (sql: %s)", e.ID, err.Error(), query)
		return false
	}
	return len(docs) == 1
}

func (o *OrientDBBackend) DelEdge(e *Edge) bool {
	query := fmt.Sprintf("UPDATE Link SET DeletedAt = '%s' WHERE DeletedAt IS NULL AND ID = '%s'", time.Now(), e.ID)
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while deleting edge %s: %s", e.ID, err.Error())
		return false
	}
	value, ok := docs[0]["value"]
	if !ok {
		return false
	}
	i, err := value.(json.Number).Int64()
	return err == nil && i == 1
}

func (o *OrientDBBackend) GetEdge(i Identifier, t *time.Time) *Edge {
	query := fmt.Sprintf("SELECT FROM Link WHERE %s AND ID = '%s'", o.getTimeClause(t), i)
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving edge %s: %s", i, err.Error())
		return nil
	}
	if len(docs) != 0 {
		return orientDBDocumentToEdge(docs[0])
	}
	return nil
}

func (o *OrientDBBackend) GetEdgeNodes(e *Edge, t *time.Time) (n1 *Node, n2 *Node) {
	query := fmt.Sprintf("SELECT FROM Node WHERE %s AND ID in [\"%s\", \"%s\"]", o.getTimeClause(t), e.parent, e.child)
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving nodes for edge %s: %s", e.ID, err.Error())
		return nil, nil
	}

	var id1 Identifier
	if len(docs) > 0 {
		n1 = orientDBDocumentToNode(docs[0])
		id1 = Identifier(n1.ID)
	}

	if len(docs) > 1 {
		n2 = orientDBDocumentToNode(docs[1])
	}

	if id1 == e.parent {
		return n1, n2
	} else {
		return n2, n1
	}
}

func (o *OrientDBBackend) updateGraphElement(i interface{}) bool {
	success := true

	switch i.(type) {
	case *Node:
		node := i.(*Node)
		now := time.Now()
		edges := o.GetNodeEdges(node, &now)
		o.DelNode(node)
		o.AddNode(node)
		for _, e := range edges {
			parent, child := o.GetEdgeNodes(e, &now)
			if parent == nil || child == nil {
				continue
			}

			if success = o.DelEdge(e); !success {
				break
			}

			if success = o.AddEdge(e); !success {
				break
			}
		}

	case *Edge:
		edge := i.(*Edge)
		if success = o.DelEdge(edge); !success {
			break
		}

		success = o.AddEdge(edge)
	}

	return success
}

func (o *OrientDBBackend) AddMetadata(i interface{}, k string, v interface{}) bool {
	success := o.updateGraphElement(i)
	if !success {
		logging.GetLogger().Errorf("Error while adding metadata")
	}
	return success
}

func (o *OrientDBBackend) SetMetadata(i interface{}, m Metadata) bool {
	success := o.updateGraphElement(i)
	if !success {
		logging.GetLogger().Errorf("Error while setting metadata")
	}
	return success
}

func (*OrientDBBackend) getTimeClause(t *time.Time) string {
	var t2 time.Time
	if t == nil {
		t2 = time.Now()
	} else {
		t2 = *t
	}
	s := t2.String()
	return fmt.Sprintf("CreatedAt <= '%s' AND (DeletedAt > '%s' OR DeletedAt is NULL)", s, s)
}

func (o *OrientDBBackend) GetNodes(t *time.Time) (nodes []*Node) {
	query := fmt.Sprintf("SELECT FROM Node WHERE %s ", o.getTimeClause(t))
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving nodes: %s", err.Error(), docs)
		return
	}

	for _, doc := range docs {
		nodes = append(nodes, orientDBDocumentToNode(doc))
	}

	return
}

func (o *OrientDBBackend) GetEdges(t *time.Time) (edges []*Edge) {
	query := fmt.Sprintf("SELECT FROM Link WHERE %s", o.getTimeClause(t))
	docs, err := o.client.Sql(query)
	if err != nil {
		logging.GetLogger().Errorf("Error while retrieving edges: %s", err.Error(), docs)
		return
	}

	for _, doc := range docs {
		edges = append(edges, orientDBDocumentToEdge(doc))
	}

	return
}

func NewOrientDBBackend(addr string, database string, username string, password string) (*OrientDBBackend, error) {
	client, err := orientdb.NewClient(addr, database, username, password)
	if err != nil {
		return nil, err
	}

	if _, err := client.GetDocumentClass("Node"); err != nil {
		class := orientdb.ClassDefinition{
			Name:       "Node",
			SuperClass: "V",
			Properties: []orientdb.Property{
				{Name: "ID", Type: "STRING", Mandatory: true, NotNull: true},
				{Name: "Host", Type: "STRING", Mandatory: true, NotNull: true},
				{Name: "CreatedAt", Type: "DATETIME", Mandatory: true, NotNull: true, ReadOnly: true},
				{Name: "DeletedAt", Type: "DATETIME", NotNull: true},
			},
			Indexes: []orientdb.Index{
				{Name: "Node.TimeSpan", Fields: []string{"CreatedAt", "DeletedAt"}, Type: "NOTUNIQUE"},
			},
		}
		if err := client.CreateDocumentClass(class); err != nil {
			return nil, fmt.Errorf("Failed to register class Node: %s", err.Error())
		}
	}

	if _, err := client.GetDocumentClass("Link"); err != nil {
		class := orientdb.ClassDefinition{
			Name:       "Link",
			SuperClass: "E",
			Properties: []orientdb.Property{
				{Name: "ID", Type: "STRING", Mandatory: true, NotNull: true},
				{Name: "Host", Type: "STRING", Mandatory: true, NotNull: true},
				{Name: "CreatedAt", Type: "DATETIME", Mandatory: true, NotNull: true, ReadOnly: true},
				{Name: "DeletedAt", Type: "DATETIME", NotNull: true},
				{Name: "parent", Type: "STRING", Mandatory: true, NotNull: true},
				{Name: "child", Type: "STRING", Mandatory: true, NotNull: true},
			},
			Indexes: []orientdb.Index{
				{Name: "Link.TimeSpan", Fields: []string{"CreatedAt", "DeletedAt"}, Type: "NOTUNIQUE"},
			},
		}
		if err := client.CreateDocumentClass(class); err != nil {
			return nil, fmt.Errorf("Failed to register class Link: %s", err.Error())
		}
	}

	return &OrientDBBackend{
		client: client,
	}, nil
}

func NewOrientDBBackendFromConfig() (*OrientDBBackend, error) {
	addr := config.GetConfig().GetString("storage.orientdb.addr")
	database := config.GetConfig().GetString("storage.orientdb.database")
	username := config.GetConfig().GetString("storage.orientdb.username")
	password := config.GetConfig().GetString("storage.orientdb.password")
	return NewOrientDBBackend(addr, database, username, password)
}
<|endoftext|>


<commit_before>package schemas

// IdentifySchema takes the topic name as a parameter
// and determines the struct to return as a schema.
func IdentifySchema(topicName string) interface{} {
	switch topicName {
	case "tx-closed", "tx-closed-failed":
		return &TxClosed{}
	case "document-generation-started":
		return &DocumentGenerationStarted{}
	case "document-generation-completed":
		return &DocumentGenerationCompleted{}
	case "email-send":
		return &EmailSend{}
	case "render-submitted-data-document":
		return &RenderSubmittedDataDocument{}
	case "filing-received":
		return &FilingReceived{}
	case "filing-processed":
		return &FilingProcessed{}
	case "resource_changed":
		return &ResourceChanged{}
	}
	return nil
}
<commit_msg>rename resource_changed topic to resource-changed<commit_after>package schemas

// IdentifySchema takes the topic name as a parameter
// and determines the struct to return as a schema.
func IdentifySchema(topicName string) interface{} {
	switch topicName {
	case "tx-closed", "tx-closed-failed":
		return &TxClosed{}
	case "document-generation-started":
		return &DocumentGenerationStarted{}
	case "document-generation-completed":
		return &DocumentGenerationCompleted{}
	case "email-send":
		return &EmailSend{}
	case "render-submitted-data-document":
		return &RenderSubmittedDataDocument{}
	case "filing-received":
		return &FilingReceived{}
	case "filing-processed":
		return &FilingProcessed{}
	case "resource-changed":
		return &ResourceChanged{}
	}
	return nil
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/aws/aws-sdk-go/service/rds"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsDbSubnetGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsDbSubnetGroupCreate,
		Read:   resourceAwsDbSubnetGroupRead,
		Update: resourceAwsDbSubnetGroupUpdate,
		Delete: resourceAwsDbSubnetGroupDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				ForceNew: true,
				Required: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if !regexp.MustCompile(`^[ .0-9A-Za-z-_]+$`).MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"only alphanumeric characters, hyphens, underscores, periods, and spaces allowed in %q", k))
					}
					if len(value) > 255 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 255 characters", k))
					}
					if regexp.MustCompile(`(?i)^default$`).MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"%q is not allowed as %q", "Default", k))
					}
					return
				},
			},

			"description": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"subnet_ids": &schema.Schema{
				Type:     schema.TypeSet,
				Required: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set:      schema.HashString,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceAwsDbSubnetGroupCreate(d *schema.ResourceData, meta interface{}) error {
	rdsconn := meta.(*AWSClient).rdsconn
	tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))

	subnetIdsSet := d.Get("subnet_ids").(*schema.Set)
	subnetIds := make([]*string, subnetIdsSet.Len())
	for i, subnetId := range subnetIdsSet.List() {
		subnetIds[i] = aws.String(subnetId.(string))
	}

	createOpts := rds.CreateDBSubnetGroupInput{
		DBSubnetGroupName:        aws.String(d.Get("name").(string)),
		DBSubnetGroupDescription: aws.String(d.Get("description").(string)),
		SubnetIds:                subnetIds,
		Tags:                     tags,
	}

	log.Printf("[DEBUG] Create DB Subnet Group: %#v", createOpts)
	_, err := rdsconn.CreateDBSubnetGroup(&createOpts)
	if err != nil {
		return fmt.Errorf("Error creating DB Subnet Group: %s", err)
	}

	d.SetId(*createOpts.DBSubnetGroupName)
	log.Printf("[INFO] DB Subnet Group ID: %s", d.Id())
	return resourceAwsDbSubnetGroupRead(d, meta)
}

func resourceAwsDbSubnetGroupRead(d *schema.ResourceData, meta interface{}) error {
	rdsconn := meta.(*AWSClient).rdsconn

	describeOpts := rds.DescribeDBSubnetGroupsInput{
		DBSubnetGroupName: aws.String(d.Id()),
	}

	describeResp, err := rdsconn.DescribeDBSubnetGroups(&describeOpts)
	if err != nil {
		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "DBSubnetGroupNotFoundFault" {
			// Update state to indicate the db subnet no longer exists.
			d.SetId("")
			return nil
		}
		return err
	}

	if len(describeResp.DBSubnetGroups) == 0 {
		return fmt.Errorf("Unable to find DB Subnet Group: %#v", describeResp.DBSubnetGroups)
	}

	var subnetGroup *rds.DBSubnetGroup
	for _, s := range describeResp.DBSubnetGroups {
		// AWS is down casing the name provided, so we compare lower case versions
		// of the names. We lower case both our name and their name in the check,
		// incase they change that someday.
		if strings.ToLower(d.Id()) == strings.ToLower(*s.DBSubnetGroupName) {
			subnetGroup = describeResp.DBSubnetGroups[0]
		}
	}

	if subnetGroup.DBSubnetGroupName == nil {
		return fmt.Errorf("Unable to find DB Subnet Group: %#v", describeResp.DBSubnetGroups)
	}

	d.Set("name", d.Id())
	d.Set("description", *subnetGroup.DBSubnetGroupDescription)

	subnets := make([]string, 0, len(subnetGroup.Subnets))
	for _, s := range subnetGroup.Subnets {
		subnets = append(subnets, *s.SubnetIdentifier)
	}
	d.Set("subnet_ids", subnets)

	// list tags for resource
	// set tags
	conn := meta.(*AWSClient).rdsconn
	arn, err := buildRDSsubgrpARN(d, meta)
	if err != nil {
		log.Printf("[DEBUG] Error building ARN for DB Subnet Group, not setting Tags for group %s", *subnetGroup.DBSubnetGroupName)
	} else {
		resp, err := conn.ListTagsForResource(&rds.ListTagsForResourceInput{
			ResourceName: aws.String(arn),
		})

		if err != nil {
			log.Printf("[DEBUG] Error retreiving tags for ARN: %s", arn)
		}

		var dt []*rds.Tag
		if len(resp.TagList) > 0 {
			dt = resp.TagList
		}
		d.Set("tags", tagsToMapRDS(dt))
	}

	return nil
}

func resourceAwsDbSubnetGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).rdsconn
	if d.HasChange("subnet_ids") {
		_, n := d.GetChange("subnet_ids")
		if n == nil {
			n = new(schema.Set)
		}
		ns := n.(*schema.Set)

		var sIds []*string
		for _, s := range ns.List() {
			sIds = append(sIds, aws.String(s.(string)))
		}

		_, err := conn.ModifyDBSubnetGroup(&rds.ModifyDBSubnetGroupInput{
			DBSubnetGroupName: aws.String(d.Id()),
			SubnetIds:         sIds,
		})

		if err != nil {
			return err
		}
	}

	if arn, err := buildRDSsubgrpARN(d, meta); err == nil {
		if err := setTagsRDS(conn, d, arn); err != nil {
			return err
		} else {
			d.SetPartial("tags")
		}
	}

	return resourceAwsDbSubnetGroupRead(d, meta)
}

func resourceAwsDbSubnetGroupDelete(d *schema.ResourceData, meta interface{}) error {
	stateConf := &resource.StateChangeConf{
		Pending:    []string{"pending"},
		Target:     "destroyed",
		Refresh:    resourceAwsDbSubnetGroupDeleteRefreshFunc(d, meta),
		Timeout:    3 * time.Minute,
		MinTimeout: 1 * time.Second,
	}
	_, err := stateConf.WaitForState()
	return err
}

func resourceAwsDbSubnetGroupDeleteRefreshFunc(
	d *schema.ResourceData,
	meta interface{}) resource.StateRefreshFunc {
	rdsconn := meta.(*AWSClient).rdsconn

	return func() (interface{}, string, error) {

		deleteOpts := rds.DeleteDBSubnetGroupInput{
			DBSubnetGroupName: aws.String(d.Id()),
		}

		if _, err := rdsconn.DeleteDBSubnetGroup(&deleteOpts); err != nil {
			rdserr, ok := err.(awserr.Error)
			if !ok {
				return d, "error", err
			}

			if rdserr.Code() != "DBSubnetGroupNotFoundFault" {
				return d, "error", err
			}
		}

		return d, "destroyed", nil
	}
}

func buildRDSsubgrpARN(d *schema.ResourceData, meta interface{}) (string, error) {
	iamconn := meta.(*AWSClient).iamconn
	region := meta.(*AWSClient).region
	// An zero value GetUserInput{} defers to the currently logged in user
	resp, err := iamconn.GetUser(&iam.GetUserInput{})
	if err != nil {
		return "", err
	}
	userARN := *resp.User.Arn
	accountID := strings.Split(userARN, ":")[4]
	arn := fmt.Sprintf("arn:aws:rds:%s:%s:subgrp:%s", region, accountID, d.Id())
	return arn, nil
}
<commit_msg>Adding the ARN as an output of the DB Subnet Group<commit_after>package aws

import (
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/aws/aws-sdk-go/service/rds"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsDbSubnetGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsDbSubnetGroupCreate,
		Read:   resourceAwsDbSubnetGroupRead,
		Update: resourceAwsDbSubnetGroupUpdate,
		Delete: resourceAwsDbSubnetGroupDelete,

		Schema: map[string]*schema.Schema{
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},

			"name": &schema.Schema{
				Type:     schema.TypeString,
				ForceNew: true,
				Required: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if !regexp.MustCompile(`^[ .0-9A-Za-z-_]+$`).MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"only alphanumeric characters, hyphens, underscores, periods, and spaces allowed in %q", k))
					}
					if len(value) > 255 {
						errors = append(errors, fmt.Errorf(
							"%q cannot be longer than 255 characters", k))
					}
					if regexp.MustCompile(`(?i)^default$`).MatchString(value) {
						errors = append(errors, fmt.Errorf(
							"%q is not allowed as %q", "Default", k))
					}
					return
				},
			},

			"description": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"subnet_ids": &schema.Schema{
				Type:     schema.TypeSet,
				Required: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set:      schema.HashString,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceAwsDbSubnetGroupCreate(d *schema.ResourceData, meta interface{}) error {
	rdsconn := meta.(*AWSClient).rdsconn
	tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))

	subnetIdsSet := d.Get("subnet_ids").(*schema.Set)
	subnetIds := make([]*string, subnetIdsSet.Len())
	for i, subnetId := range subnetIdsSet.List() {
		subnetIds[i] = aws.String(subnetId.(string))
	}

	createOpts := rds.CreateDBSubnetGroupInput{
		DBSubnetGroupName:        aws.String(d.Get("name").(string)),
		DBSubnetGroupDescription: aws.String(d.Get("description").(string)),
		SubnetIds:                subnetIds,
		Tags:                     tags,
	}

	log.Printf("[DEBUG] Create DB Subnet Group: %#v", createOpts)
	_, err := rdsconn.CreateDBSubnetGroup(&createOpts)
	if err != nil {
		return fmt.Errorf("Error creating DB Subnet Group: %s", err)
	}

	d.SetId(*createOpts.DBSubnetGroupName)
	log.Printf("[INFO] DB Subnet Group ID: %s", d.Id())
	return resourceAwsDbSubnetGroupRead(d, meta)
}

func resourceAwsDbSubnetGroupRead(d *schema.ResourceData, meta interface{}) error {
	rdsconn := meta.(*AWSClient).rdsconn

	describeOpts := rds.DescribeDBSubnetGroupsInput{
		DBSubnetGroupName: aws.String(d.Id()),
	}

	describeResp, err := rdsconn.DescribeDBSubnetGroups(&describeOpts)
	if err != nil {
		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "DBSubnetGroupNotFoundFault" {
			// Update state to indicate the db subnet no longer exists.
			d.SetId("")
			return nil
		}
		return err
	}

	if len(describeResp.DBSubnetGroups) == 0 {
		return fmt.Errorf("Unable to find DB Subnet Group: %#v", describeResp.DBSubnetGroups)
	}

	var subnetGroup *rds.DBSubnetGroup
	for _, s := range describeResp.DBSubnetGroups {
		// AWS is down casing the name provided, so we compare lower case versions
		// of the names. We lower case both our name and their name in the check,
		// incase they change that someday.
		if strings.ToLower(d.Id()) == strings.ToLower(*s.DBSubnetGroupName) {
			subnetGroup = describeResp.DBSubnetGroups[0]
		}
	}

	if subnetGroup.DBSubnetGroupName == nil {
		return fmt.Errorf("Unable to find DB Subnet Group: %#v", describeResp.DBSubnetGroups)
	}

	d.Set("name", d.Id())
	d.Set("description", *subnetGroup.DBSubnetGroupDescription)

	subnets := make([]string, 0, len(subnetGroup.Subnets))
	for _, s := range subnetGroup.Subnets {
		subnets = append(subnets, *s.SubnetIdentifier)
	}
	d.Set("subnet_ids", subnets)

	// list tags for resource
	// set tags
	conn := meta.(*AWSClient).rdsconn
	arn, err := buildRDSsubgrpARN(d, meta)
	if err != nil {
		log.Printf("[DEBUG] Error building ARN for DB Subnet Group, not setting Tags for group %s", *subnetGroup.DBSubnetGroupName)
	} else {
		d.Set("arn", arn)
		resp, err := conn.ListTagsForResource(&rds.ListTagsForResourceInput{
			ResourceName: aws.String(arn),
		})

		if err != nil {
			log.Printf("[DEBUG] Error retreiving tags for ARN: %s", arn)
		}

		var dt []*rds.Tag
		if len(resp.TagList) > 0 {
			dt = resp.TagList
		}
		d.Set("tags", tagsToMapRDS(dt))
	}

	return nil
}

func resourceAwsDbSubnetGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).rdsconn
	if d.HasChange("subnet_ids") {
		_, n := d.GetChange("subnet_ids")
		if n == nil {
			n = new(schema.Set)
		}
		ns := n.(*schema.Set)

		var sIds []*string
		for _, s := range ns.List() {
			sIds = append(sIds, aws.String(s.(string)))
		}

		_, err := conn.ModifyDBSubnetGroup(&rds.ModifyDBSubnetGroupInput{
			DBSubnetGroupName: aws.String(d.Id()),
			SubnetIds:         sIds,
		})

		if err != nil {
			return err
		}
	}

	if arn, err := buildRDSsubgrpARN(d, meta); err == nil {
		if err := setTagsRDS(conn, d, arn); err != nil {
			return err
		} else {
			d.SetPartial("tags")
		}
	}

	return resourceAwsDbSubnetGroupRead(d, meta)
}

func resourceAwsDbSubnetGroupDelete(d *schema.ResourceData, meta interface{}) error {
	stateConf := &resource.StateChangeConf{
		Pending:    []string{"pending"},
		Target:     "destroyed",
		Refresh:    resourceAwsDbSubnetGroupDeleteRefreshFunc(d, meta),
		Timeout:    3 * time.Minute,
		MinTimeout: 1 * time.Second,
	}
	_, err := stateConf.WaitForState()
	return err
}

func resourceAwsDbSubnetGroupDeleteRefreshFunc(
	d *schema.ResourceData,
	meta interface{}) resource.StateRefreshFunc {
	rdsconn := meta.(*AWSClient).rdsconn

	return func() (interface{}, string, error) {

		deleteOpts := rds.DeleteDBSubnetGroupInput{
			DBSubnetGroupName: aws.String(d.Id()),
		}

		if _, err := rdsconn.DeleteDBSubnetGroup(&deleteOpts); err != nil {
			rdserr, ok := err.(awserr.Error)
			if !ok {
				return d, "error", err
			}

			if rdserr.Code() != "DBSubnetGroupNotFoundFault" {
				return d, "error", err
			}
		}

		return d, "destroyed", nil
	}
}

func buildRDSsubgrpARN(d *schema.ResourceData, meta interface{}) (string, error) {
	iamconn := meta.(*AWSClient).iamconn
	region := meta.(*AWSClient).region
	// An zero value GetUserInput{} defers to the currently logged in user
	resp, err := iamconn.GetUser(&iam.GetUserInput{})
	if err != nil {
		return "", err
	}
	userARN := *resp.User.Arn
	accountID := strings.Split(userARN, ":")[4]
	arn := fmt.Sprintf("arn:aws:rds:%s:%s:subgrp:%s", region, accountID, d.Id())
	return arn, nil
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"log"
	"strings"
)

func writeGNUPlotCATCoSchedFile(pairs [][2]string, filename []string) {
	if len(pairs) == 0 {
		return
	}
	if len(pairs) != len(filename) {
		log.Fatalln("Not all apps have CAT data? Not supported atm.")
	}

	log.Println("Creating plot file for co-scheduling CAT runs.")

	var ret string
	ret += "set terminal pdf\n"
	ret += "set output 'co-sched-cat.pdf'\n"

	ret += "set yrange [0:*]\n"
	ret += "set key right bottom\n"
	ret += "unset x2tics\n"
	ret += "unset y2tics\n"
	ret += "set border 3\n"

	ret += "set xlabel 'L3 Cache (MB)'\n"
	ret += "set ylabel 'Runtime (s)'\n"

	ret += "shadecolor = '#80E0A080'\n"

	for i, pair := range pairs {
		ret += "plot '" + filename[i]
		ret += "' using 1:($2+$3):($2-$3) with filledcurve fc rgb shadecolor title 'Std. dev. (" + prettyAppCmd(pair[0]) + ")', "
		ret += "'' using 1:2 smooth mcspline lw 2 title 'Mean runtime (" + prettyAppCmd(pair[0]) + ")', "
		ret += "'' using 1:($4+$5):($4-$5) with filledcurve fc rgb shadecolor title 'Std. dev. (" + prettyAppCmd(pair[1]) + ")', "
		ret += "'' using 1:4 smooth mcspline lw 2 title 'Mean runtime (" + prettyAppCmd(pair[1]) + ")'\n"
	}

	err := ioutil.WriteFile("co-sched-cat.plot", []byte(ret), 0644)
	if err != nil {
		log.Fatalf("Error while write file co-sched-cat.plot: %v", err)
	}
}

func writeGNUPlotCATIndvFile(apps []string, filename []string) {
	if len(apps) == 0 {
		return
	}
	if len(apps) != len(filename) {
		log.Fatalln("Not all apps have CAT data? Not supported atm.")
	}

	log.Println("Creating plot file for individual CAT runs.")

	var ret string
	ret += "set terminal pdf\n"
	ret += "set output 'indv-cat.pdf'\n"

	ret += "set yrange [0:*]\n"
	ret += "set key right bottom\n"
	ret += "unset x2tics\n"
	ret += "unset y2tics\n"
	ret += "set border 3\n"

	ret += "set xlabel 'L3 Cache (MB)'\n"
	ret += "set ylabel 'Runtime (s)'\n"

	ret += "shadecolor = '#80E0A080'\n"

	for i, app := range apps {
		ret += "plot '" + filename[i] + "' using 1:($2+$3):($2-$3) with filledcurve fc rgb shadecolor title 'Std. dev.', '' using 1:2 smooth mcspline lw 2 title 'Mean runtime (" + prettyAppCmd(app) + ")'\n"
	}

	err := ioutil.WriteFile("indv-cat.plot", []byte(ret), 0644)
	if err != nil {
		log.Fatalf("Error while write file indv-cat.plot: %v", err)
	}
}

func prettyAppCmd(app string) string {
	slash := strings.LastIndex(app, "/")
	space := strings.Index(app[slash+1:], " ")
	if space == -1 {
		space = len(app)
	}

	ret := app[slash+1 : space]
	ret = strings.Replace(ret, "_", "\\_", -1)

	return ret
}
<commit_msg>Fixed dat file naming.<commit_after>package main

import (
	"io/ioutil"
	"log"
	"strings"
)

func writeGNUPlotCATCoSchedFile(pairs [][2]string, filename []string) {
	if len(pairs) == 0 {
		return
	}
	if len(pairs) != len(filename) {
		log.Fatalln("Not all apps have CAT data? Not supported atm.")
	}

	log.Println("Creating plot file for co-scheduling CAT runs.")

	var ret string
	ret += "set terminal pdf\n"
	ret += "set output 'co-sched-cat.pdf'\n"

	ret += "set yrange [0:*]\n"
	ret += "set key right bottom\n"
	ret += "unset x2tics\n"
	ret += "unset y2tics\n"
	ret += "set border 3\n"

	ret += "set xlabel 'L3 Cache (MB)'\n"
	ret += "set ylabel 'Runtime (s)'\n"

	ret += "shadecolor = '#80E0A080'\n"

	for i, pair := range pairs {
		ret += "plot '" + filename[i]
		ret += "' using 1:($2+$3):($2-$3) with filledcurve fc rgb shadecolor title 'Std. dev. (" + gnuplotEscape(prettyAppCmd(pair[0])) + ")', "
		ret += "'' using 1:2 smooth mcspline lw 2 title 'Mean runtime (" + gnuplotEscape(prettyAppCmd(pair[0])) + ")', "
		ret += "'' using 1:($4+$5):($4-$5) with filledcurve fc rgb shadecolor title 'Std. dev. (" + gnuplotEscape(prettyAppCmd(pair[1])) + ")', "
		ret += "'' using 1:4 smooth mcspline lw 2 title 'Mean runtime (" + gnuplotEscape(prettyAppCmd(pair[1])) + ")'\n"
	}

	err := ioutil.WriteFile("co-sched-cat.plot", []byte(ret), 0644)
	if err != nil {
		log.Fatalf("Error while write file co-sched-cat.plot: %v", err)
	}
}

func writeGNUPlotCATIndvFile(apps []string, filename []string) {
	if len(apps) == 0 {
		return
	}
	if len(apps) != len(filename) {
		log.Fatalln("Not all apps have CAT data? Not supported atm.")
	}

	log.Println("Creating plot file for individual CAT runs.")

	var ret string
	ret += "set terminal pdf\n"
	ret += "set output 'indv-cat.pdf'\n"

	ret += "set yrange [0:*]\n"
	ret += "set key right bottom\n"
	ret += "unset x2tics\n"
	ret += "unset y2tics\n"
	ret += "set border 3\n"

	ret += "set xlabel 'L3 Cache (MB)'\n"
	ret += "set ylabel 'Runtime (s)'\n"

	ret += "shadecolor = '#80E0A080'\n"

	for i, app := range apps {
		ret += "plot '" + filename[i] + "' using 1:($2+$3):($2-$3) with filledcurve fc rgb shadecolor title 'Std. dev.', '' using 1:2 smooth mcspline lw 2 title 'Mean runtime (" + gnuplotEscape(prettyAppCmd(app)) + ")'\n"
	}

	err := ioutil.WriteFile("indv-cat.plot", []byte(ret), 0644)
	if err != nil {
		log.Fatalf("Error while write file indv-cat.plot: %v", err)
	}
}

// only supports some characters
func gnuplotEscape(s string) string {
	ret := strings.Replace(s, "_", "\\_", -1)

	return ret
}

func prettyAppCmd(app string) string {
	slash := strings.LastIndex(app, "/")
	space := strings.Index(app[slash+1:], " ")
	if space == -1 {
		space = len(app)
	}

	return app[slash+1 : space]
}
<|endoftext|>


<commit_before>// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

package popcount_test

import (
	"testing"

	"gopl/ch02/p03_popcount"
)

// -- Alternative implementations --

func BitCount(x uint64) int {
	// Hacker's Delight, Figure 5-2.
	x = x - ((x >> 1) & 0x5555555555555555)
	x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333)
	x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f
	x = x + (x >> 8)
	x = x + (x >> 16)
	x = x + (x >> 32)
	return int(x & 0x7f)
}

func PopCountByClearing(x uint64) int {
	n := 0
	for x != 0 {
		x = x & (x - 1) // clear rightmost non-zero bit
		n++
	}
	return n
}

func PopCountByShifting(x uint64) int {
	n := 0
	for i := uint(0); i < 64; i++ {
		if x&(1<<i) != 0 {
			n++
		}
	}
	return n
}

// -- Benchmarks --

func BenchmarkPopCount(b *testing.B) {
	for i := 0; i < b.N; i++ {
		popcount.PopCount(0x1234567890ABCDEF)
	}
}

func BenchmarkBitCount(b *testing.B) {
	for i := 0; i < b.N; i++ {
		BitCount(0x1234567890ABCDEF)
	}
}

func BenchmarkPopCountByClearing(b *testing.B) {
	for i := 0; i < b.N; i++ {
		PopCountByClearing(0x1234567890ABCDEF)
	}
}

func BenchmarkPopCountByShifting(b *testing.B) {
	for i := 0; i < b.N; i++ {
		PopCountByShifting(0x1234567890ABCDEF)
	}
}

// 2.67GHz Xeon
// $ go test -cpu=4 -bench=. gopl.io/ch2/popcount
// BenchmarkPopCount-4                  200000000         6.30 ns/op
// BenchmarkBitCount-4                  300000000         4.15 ns/op
// BenchmarkPopCountByClearing-4        30000000         45.2 ns/op
// BenchmarkPopCountByShifting-4        10000000        153 ns/op
//
// 2.5GHz Intel Core i5
// $ go test -cpu=4 -bench=. gopl.io/ch2/popcount
// testing: warning: no tests to run
// BenchmarkPopCount-4                  200000000         7.52 ns/op
// BenchmarkBitCount-4                  500000000         3.36 ns/op
// BenchmarkPopCountByClearing-4        50000000         34.3 ns/op
// BenchmarkPopCountByShifting-4        20000000        108 ns/op
<commit_msg>Modified popcount_test.go.<commit_after>// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/

package popcount_test

import (
	"testing"

	"gopl/ch02/p03_popcount"
)

// -- Alternative implementations --

func BitCount(x uint64) int {
	// Hacker's Delight, Figure 5-2.
	x = x - ((x >> 1) & 0x5555555555555555)
	x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333)
	x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0f
	x = x + (x >> 8)
	x = x + (x >> 16)
	x = x + (x >> 32)
	return int(x & 0x7f)
}

func PopCountByClearing(x uint64) int {
	n := 0
	for x != 0 {
		x = x & (x - 1) // clear rightmost non-zero bit
		n++
	}
	return n
}

func PopCountByShifting(x uint64) int {
	n := 0
	for i := uint(0); i < 64; i++ {
		if x&(1<<i) != 0 {
			n++
		}
	}
	return n
}

// -- Benchmarks --

func BenchmarkPopCount(b *testing.B) {
	for i := 0; i < b.N; i++ {
		popcount.PopCount(0x1234567890ABCDEF)
	}
}

func BenchmarkBitCount(b *testing.B) {
	for i := 0; i < b.N; i++ {
		BitCount(0x1234567890ABCDEF)
	}
}

func BenchmarkPopCountByClearing(b *testing.B) {
	for i := 0; i < b.N; i++ {
		PopCountByClearing(0x1234567890ABCDEF)
	}
}

func BenchmarkPopCountByShifting(b *testing.B) {
	for i := 0; i < b.N; i++ {
		PopCountByShifting(0x1234567890ABCDEF)
	}
}

// 2.67GHz Xeon
// $ go test -cpu=4 -bench=. gopl.io/ch2/popcount
// BenchmarkPopCount-4                  200000000         6.30 ns/op
// BenchmarkBitCount-4                  300000000         4.15 ns/op
// BenchmarkPopCountByClearing-4        30000000         45.2 ns/op
// BenchmarkPopCountByShifting-4        10000000        153 ns/op
//
// 2.5GHz Intel Core i5
// $ go test -cpu=4 -bench=. gopl.io/ch2/popcount
// testing: warning: no tests to run
// BenchmarkPopCount-4                  200000000         7.52 ns/op
// BenchmarkBitCount-4                  500000000         3.36 ns/op
// BenchmarkPopCountByClearing-4        50000000         34.3 ns/op
// BenchmarkPopCountByShifting-4        20000000        108 ns/op

// 1.4GHz Interl Core i5
// $ go version
// go version go1.7.3 darwin/amd64
// $ go test -cpu=4 -bench=.
// BenchmarkPopCount-4                  2000000000         0.45 ns/op
// BenchmarkBitCount-4                  2000000000         0.45 ns/op
// BenchmarkPopCountByClearing-4        30000000        41.8 ns/op
// BenchmarkPopCountByShifting-4        20000000       116 ns/op
<|endoftext|>


<commit_before>// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// +build windows

package protocol

// Windows uses backslashes as file separator and disallows a bunch of
// characters in the filename

import (
	"path/filepath"
	"strings"
)

var disallowedCharacters = string([]rune{
	'<', '>', ':', '"', '|', '?', '*',
	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
	11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
	21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
	31,
})

type nativeModel struct {
	next Model
}

func (m nativeModel) Index(nodeID NodeID, repo string, files []FileInfo) {
	for i, f := range files {
		if strings.ContainsAny(f.Name, disallowedCharacters) {
			files[i].Flags |= FlagInvalid
			l.Warnf("File name %q contains invalid characters; marked as invalid.", f.Name)
		}
		files[i].Name = filepath.FromSlash(f.Name)
	}
	m.next.Index(nodeID, repo, files)
}

func (m nativeModel) IndexUpdate(nodeID NodeID, repo string, files []FileInfo) {
	for i, f := range files {
		if strings.ContainsAny(f.Name, disallowedCharacters) {
			files[i].Flags |= FlagInvalid
			l.Warnf("File name %q contains invalid characters; marked as invalid.", f.Name)
		}
		files[i].Name = filepath.FromSlash(files[i].Name)
	}
	m.next.IndexUpdate(nodeID, repo, files)
}

func (m nativeModel) Request(nodeID NodeID, repo string, name string, offset int64, size int) ([]byte, error) {
	name = filepath.FromSlash(name)
	return m.next.Request(nodeID, repo, name, offset, size)
}

func (m nativeModel) ClusterConfig(nodeID NodeID, config ClusterConfigMessage) {
	m.next.ClusterConfig(nodeID, config)
}

func (m nativeModel) Close(nodeID NodeID, err error) {
	m.next.Close(nodeID, err)
}
<commit_msg>Windows nodes should ignore deleted impossible files<commit_after>// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// +build windows

package protocol

// Windows uses backslashes as file separator and disallows a bunch of
// characters in the filename

import (
	"path/filepath"
	"strings"
)

var disallowedCharacters = string([]rune{
	'<', '>', ':', '"', '|', '?', '*',
	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
	11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
	21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
	31,
})

type nativeModel struct {
	next Model
}

func (m nativeModel) Index(nodeID NodeID, repo string, files []FileInfo) {
	for i, f := range files {
		if strings.ContainsAny(f.Name, disallowedCharacters) {
			if f.IsDeleted() {
				// Don't complain if the file is marked as deleted, since it
				// can't possibly exist here anyway.
				continue
			}
			files[i].Flags |= FlagInvalid
			l.Warnf("File name %q contains invalid characters; marked as invalid.", f.Name)
		}
		files[i].Name = filepath.FromSlash(f.Name)
	}
	m.next.Index(nodeID, repo, files)
}

func (m nativeModel) IndexUpdate(nodeID NodeID, repo string, files []FileInfo) {
	for i, f := range files {
		if strings.ContainsAny(f.Name, disallowedCharacters) {
			if f.IsDeleted() {
				// Don't complain if the file is marked as deleted, since it
				// can't possibly exist here anyway.
				continue
			}
			files[i].Flags |= FlagInvalid
			l.Warnf("File name %q contains invalid characters; marked as invalid.", f.Name)
		}
		files[i].Name = filepath.FromSlash(files[i].Name)
	}
	m.next.IndexUpdate(nodeID, repo, files)
}

func (m nativeModel) Request(nodeID NodeID, repo string, name string, offset int64, size int) ([]byte, error) {
	name = filepath.FromSlash(name)
	return m.next.Request(nodeID, repo, name, offset, size)
}

func (m nativeModel) ClusterConfig(nodeID NodeID, config ClusterConfigMessage) {
	m.next.ClusterConfig(nodeID, config)
}

func (m nativeModel) Close(nodeID NodeID, err error) {
	m.next.Close(nodeID, err)
}
<|endoftext|>


<commit_before>package meddler

import (
	"fmt"
	"io"
	"net/http"
	"path/filepath"
	"strings"
)

type DownloadItem struct {
	URI       string `form:"uri" binding:"required"`
	PublicKey string `form:"pubkey" binding:"-"`
	Signature string `form:"signature" binding:"-"`
}

type UriInsert struct {
	UriList []string
	Source  string
}

func headerShallowCopy(from, to http.Header) {
	for k, v := range from {
		to.Set(k, strings.Join(v, ","))
	}
}

func HeadGet(di DownloadItem, res http.ResponseWriter, req *http.Request) error {
	uri := di.URI
	headResponse, err := http.Head(uri)

	if err != nil {
		return err
	}

	dlHeader := headResponse.Header
	headerShallowCopy(dlHeader, res.Header())

	return nil
}

func Download(di DownloadItem, res http.ResponseWriter, req *http.Request) {
	uri := di.URI

	downloadResult, err := http.Get(uri)

	if err != nil {
		fmt.Fprintf(res, "%v", err)
		return
	}

	if downloadResult == nil || downloadResult.Body == nil {
		fmt.Fprintf(res, "could not get %q", uri)
		return
	}

	body := downloadResult.Body
	dlHeader := downloadResult.Header

	if downloadResult.Close {
		defer body.Close()
	}

	headerShallowCopy(dlHeader, res.Header())

	basename := filepath.Base(uri)
	extraHeaders := map[string][]string{
		"Content-Disposition": []string{
			fmt.Sprintf("attachment;filename=%q", basename),
		},
	}

	headerShallowCopy(extraHeaders, res.Header())

	res.WriteHeader(200)
	io.Copy(res, body)
}
<commit_msg>allowing payload unravelling + support<commit_after>package meddler

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"
)

type DownloadItem struct {
	URI       string `form:"uri" binding:"required"`
	PublicKey string `form:"pubkey" binding:"-"`
	Signature string `form:"signature" binding:"-"`
}

type UriInsert struct {
	UriList []string
	Source  string
}

type Payload struct {
	URI         string `form:"uri" binding:"required"`
	PublicKey   string `form:"pubkey" binding:"-"`
	Signature   string `form:"signature" binding:"-"`
	Payload     string `form:"payload" binding:"-"`
	RequestTime int64  `form:"requesttime" binding:"required"`
}

func (pl *Payload) RawTextForSigning() string {
	return fmt.Sprintf("%q%q%q%q", pl.URI, pl.RequestTime, pl.Payload, pl.PublicKey)
}

func (pl *Payload) ToUrlValues(extras ...map[string]interface{}) url.Values {
	uv := url.Values{}
	uv.Set("requesttime", fmt.Sprintf("%v", pl.RequestTime))
	uv.Set("payload", pl.Payload)
	uv.Set("pubkey", pl.PublicKey)
	uv.Set("signature", pl.Signature)
	uv.Set("uri", pl.URI)

	return uv
}

func headerShallowCopy(from, to http.Header) {
	for k, v := range from {
		to.Set(k, strings.Join(v, ","))
	}
}

func HeadGet(di DownloadItem, res http.ResponseWriter, req *http.Request) error {
	uri := di.URI
	headResponse, err := http.Head(uri)

	if err != nil {
		return err
	}

	dlHeader := headResponse.Header
	headerShallowCopy(dlHeader, res.Header())

	return nil
}

func Download(di DownloadItem, res http.ResponseWriter, req *http.Request) {
	uri := di.URI

	downloadResult, err := http.Get(uri)

	if err != nil {
		fmt.Fprintf(res, "%v", err)
		return
	}

	if downloadResult == nil || downloadResult.Body == nil {
		fmt.Fprintf(res, "could not get %q", uri)
		return
	}

	body := downloadResult.Body
	dlHeader := downloadResult.Header

	if downloadResult.Close {
		defer body.Close()
	}

	headerShallowCopy(dlHeader, res.Header())

	basename := filepath.Base(uri)
	extraHeaders := map[string][]string{
		"Content-Disposition": []string{
			fmt.Sprintf("attachment;filename=%q", basename),
		},
	}

	headerShallowCopy(extraHeaders, res.Header())

	res.WriteHeader(200)
	io.Copy(res, body)
}
<|endoftext|>