<commit_before>package main

import (
	"fmt"
	"strings"

	"github.com/lxc/lxd/lxd/backup"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/version"
)

var supportedVolumeTypes = []int{db.StoragePoolVolumeTypeContainer, db.StoragePoolVolumeTypeVM, db.StoragePoolVolumeTypeCustom, db.StoragePoolVolumeTypeImage}
var supportedVolumeTypesInstances = []int{db.StoragePoolVolumeTypeContainer, db.StoragePoolVolumeTypeVM}

func storagePoolVolumeUpdateUsers(d *Daemon, projectName string, oldPoolName string, oldVol *api.StorageVolume, newPoolName string, newVol *api.StorageVolume) error {
	s := d.State()

	// Update all instances that are using the volume with a local (non-expanded) device.
	err := storagePools.VolumeUsedByInstanceDevices(s, oldPoolName, projectName, oldVol, false, func(dbInst db.Instance, project api.Project, profiles []api.Profile, usedByDevices []string) error {
		inst, err := instance.Load(s, db.InstanceToArgs(&dbInst), profiles)
		if err != nil {
			return err
		}

		localDevices := inst.LocalDevices()
		for _, devName := range usedByDevices {
			if _, exists := localDevices[devName]; exists {
				localDevices[devName]["pool"] = newPoolName
				localDevices[devName]["source"] = newVol.Name
			}
		}

		args := db.InstanceArgs{
			Architecture: inst.Architecture(),
			Description:  inst.Description(),
			Config:       inst.LocalConfig(),
			Devices:      localDevices,
			Ephemeral:    inst.IsEphemeral(),
			Profiles:     inst.Profiles(),
			Project:      inst.Project(),
			Type:         inst.Type(),
			Snapshot:     inst.IsSnapshot(),
		}

		err = inst.Update(args, false)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return err
	}

	// Update all profiles that are using the volume with a device.
	err = storagePools.VolumeUsedByProfileDevices(s, oldPoolName, projectName, oldVol, func(profile db.Profile, p api.Project, usedByDevices []string) error {
		for _, devName := range usedByDevices {
			if _, exists := profile.Devices[devName]; exists {
				profile.Devices[devName]["pool"] = newPoolName
				profile.Devices[devName]["source"] = newVol.Name
			}
		}

		pUpdate := api.ProfilePut{}
		pUpdate.Config = profile.Config
		pUpdate.Description = profile.Description
		pUpdate.Devices = profile.Devices
		apiProfile := db.ProfileToAPI(&profile)
		err = doProfileUpdate(d, profile.Project, profile.Name, int64(profile.ID), apiProfile, pUpdate)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return err
	}

	return nil
}

// volumeUsedBy = append(volumeUsedBy, fmt.Sprintf("/%s/containers/%s", version.APIVersion, ct))
func storagePoolVolumeUsedByGet(s *state.State, projectName string, poolName string, vol *api.StorageVolume) ([]string, error) {
	// Handle instance volumes.
	if vol.Type == db.StoragePoolVolumeTypeNameContainer || vol.Type == db.StoragePoolVolumeTypeNameVM {
		cName, sName, snap := shared.InstanceGetParentAndSnapshotName(vol.Name)
		if snap {
			if projectName == project.Default {
				return []string{fmt.Sprintf("/%s/instances/%s/snapshots/%s", version.APIVersion, cName, sName)}, nil
			} else {
				return []string{fmt.Sprintf("/%s/instances/%s/snapshots/%s?project=%s", version.APIVersion, cName, sName, projectName)}, nil
			}
		}

		if projectName == project.Default {
			return []string{fmt.Sprintf("/%s/instances/%s", version.APIVersion, cName)}, nil
		} else {
			return []string{fmt.Sprintf("/%s/instances/%s?project=%s", version.APIVersion, cName, projectName)}, nil
		}
	}

	// Handle image volumes.
	if vol.Type == db.StoragePoolVolumeTypeNameImage {
		if projectName == project.Default {
			return []string{fmt.Sprintf("/%s/images/%s", version.APIVersion, vol.Name)}, nil
		} else {
			return []string{fmt.Sprintf("/%s/images/%s?project=%s", version.APIVersion, vol.Name, projectName)}, nil
		}
	}

	// Check if the daemon itself is using it.
	used, err := storagePools.VolumeUsedByDaemon(s, poolName, vol.Name)
	if err != nil {
		return []string{}, err
	}

	if used {
		return []string{fmt.Sprintf("/%s", version.APIVersion)}, nil
	}

	// Look for instances using this volume.
	volumeUsedBy := []string{}

	// Pass false to expandDevices, as we only want to see instances directly using a volume, rather than their
	// profiles using a volume.
	err = storagePools.VolumeUsedByInstanceDevices(s, poolName, projectName, vol, false, func(inst db.Instance, p api.Project, profiles []api.Profile, usedByDevices []string) error {
		if inst.Project == project.Default {
			volumeUsedBy = append(volumeUsedBy, fmt.Sprintf("/%s/instances/%s", version.APIVersion, inst.Name))
		} else {
			volumeUsedBy = append(volumeUsedBy, fmt.Sprintf("/%s/instances/%s?project=%s", version.APIVersion, inst.Name, inst.Project))
		}

		return nil
	})
	if err != nil {
		return []string{}, err
	}

	// Look for profiles using this volume.
	profiles, err := profilesUsingPoolVolumeGetNames(s.Cluster, vol.Name, vol.Type)
	if err != nil {
		return []string{}, err
	}

	for _, pName := range profiles {
		if projectName == project.Default {
			volumeUsedBy = append(volumeUsedBy, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, pName))
		} else {
			volumeUsedBy = append(volumeUsedBy, fmt.Sprintf("/%s/profiles/%s?project=%s", version.APIVersion, pName, projectName))
		}
	}

	return volumeUsedBy, nil
}

func profilesUsingPoolVolumeGetNames(db *db.Cluster, volumeName string, volumeType string) ([]string, error) {
	usedBy := []string{}

	profiles, err := db.GetProfileNames(project.Default)
	if err != nil {
		return usedBy, err
	}

	for _, pName := range profiles {
		_, profile, err := db.GetProfile(project.Default, pName)
		if err != nil {
			return usedBy, err
		}

		volumeNameWithType := fmt.Sprintf("%s/%s", volumeType, volumeName)
		for _, v := range profile.Devices {
			if v["type"] != "disk" {
				continue
			}

			// Can't be a storage volume.
			if filepath.IsAbs(v["source"]) {
				continue
			}

			// Make sure that we don't compare against stuff
			// like "container////bla" but only against
			// "container/bla".
			cleanSource := filepath.Clean(v["source"])
			if cleanSource == volumeName || cleanSource == volumeNameWithType {
				usedBy = append(usedBy, pName)
			}
		}
	}

	return usedBy, nil
}

func storagePoolVolumeBackupLoadByName(s *state.State, projectName, poolName, backupName string) (*backup.VolumeBackup, error) {
	b, err := s.Cluster.GetStoragePoolVolumeBackup(projectName, poolName, backupName)
	if err != nil {
		return nil, err
	}

	volumeName := strings.Split(backupName, "/")[0]
	backup := backup.NewVolumeBackup(s, projectName, poolName, volumeName, b.ID, b.Name, b.CreationDate, b.ExpiryDate, b.VolumeOnly, b.OptimizedStorage)

	return backup, nil
}
<commit_msg>lxd/storage/volumes/utils: Updates storagePoolVolumeUsedByGet to use storagePools.VolumeUsedByProfileDevices<commit_after>package main

import (
	"fmt"
	"strings"

	"github.com/lxc/lxd/lxd/backup"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	"github.com/lxc/lxd/shared/version"
)

var supportedVolumeTypes = []int{db.StoragePoolVolumeTypeContainer, db.StoragePoolVolumeTypeVM, db.StoragePoolVolumeTypeCustom, db.StoragePoolVolumeTypeImage}
var supportedVolumeTypesInstances = []int{db.StoragePoolVolumeTypeContainer, db.StoragePoolVolumeTypeVM}

func storagePoolVolumeUpdateUsers(d *Daemon, projectName string, oldPoolName string, oldVol *api.StorageVolume, newPoolName string, newVol *api.StorageVolume) error {
	s := d.State()

	// Update all instances that are using the volume with a local (non-expanded) device.
	err := storagePools.VolumeUsedByInstanceDevices(s, oldPoolName, projectName, oldVol, false, func(dbInst db.Instance, project api.Project, profiles []api.Profile, usedByDevices []string) error {
		inst, err := instance.Load(s, db.InstanceToArgs(&dbInst), profiles)
		if err != nil {
			return err
		}

		localDevices := inst.LocalDevices()
		for _, devName := range usedByDevices {
			if _, exists := localDevices[devName]; exists {
				localDevices[devName]["pool"] = newPoolName
				localDevices[devName]["source"] = newVol.Name
			}
		}

		args := db.InstanceArgs{
			Architecture: inst.Architecture(),
			Description:  inst.Description(),
			Config:       inst.LocalConfig(),
			Devices:      localDevices,
			Ephemeral:    inst.IsEphemeral(),
			Profiles:     inst.Profiles(),
			Project:      inst.Project(),
			Type:         inst.Type(),
			Snapshot:     inst.IsSnapshot(),
		}

		err = inst.Update(args, false)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return err
	}

	// Update all profiles that are using the volume with a device.
	err = storagePools.VolumeUsedByProfileDevices(s, oldPoolName, projectName, oldVol, func(profile db.Profile, p api.Project, usedByDevices []string) error {
		for _, devName := range usedByDevices {
			if _, exists := profile.Devices[devName]; exists {
				profile.Devices[devName]["pool"] = newPoolName
				profile.Devices[devName]["source"] = newVol.Name
			}
		}

		pUpdate := api.ProfilePut{}
		pUpdate.Config = profile.Config
		pUpdate.Description = profile.Description
		pUpdate.Devices = profile.Devices
		apiProfile := db.ProfileToAPI(&profile)
		err = doProfileUpdate(d, profile.Project, profile.Name, int64(profile.ID), apiProfile, pUpdate)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		return err
	}

	return nil
}

// volumeUsedBy = append(volumeUsedBy, fmt.Sprintf("/%s/containers/%s", version.APIVersion, ct))
func storagePoolVolumeUsedByGet(s *state.State, projectName string, poolName string, vol *api.StorageVolume) ([]string, error) {
	// Handle instance volumes.
	if vol.Type == db.StoragePoolVolumeTypeNameContainer || vol.Type == db.StoragePoolVolumeTypeNameVM {
		cName, sName, snap := shared.InstanceGetParentAndSnapshotName(vol.Name)
		if snap {
			if projectName == project.Default {
				return []string{fmt.Sprintf("/%s/instances/%s/snapshots/%s", version.APIVersion, cName, sName)}, nil
			} else {
				return []string{fmt.Sprintf("/%s/instances/%s/snapshots/%s?project=%s", version.APIVersion, cName, sName, projectName)}, nil
			}
		}

		if projectName == project.Default {
			return []string{fmt.Sprintf("/%s/instances/%s", version.APIVersion, cName)}, nil
		} else {
			return []string{fmt.Sprintf("/%s/instances/%s?project=%s", version.APIVersion, cName, projectName)}, nil
		}
	}

	// Handle image volumes.
	if vol.Type == db.StoragePoolVolumeTypeNameImage {
		if projectName == project.Default {
			return []string{fmt.Sprintf("/%s/images/%s", version.APIVersion, vol.Name)}, nil
		} else {
			return []string{fmt.Sprintf("/%s/images/%s?project=%s", version.APIVersion, vol.Name, projectName)}, nil
		}
	}

	// Check if the daemon itself is using it.
	used, err := storagePools.VolumeUsedByDaemon(s, poolName, vol.Name)
	if err != nil {
		return []string{}, err
	}

	if used {
		return []string{fmt.Sprintf("/%s", version.APIVersion)}, nil
	}

	// Look for instances using this volume.
	volumeUsedBy := []string{}

	// Pass false to expandDevices, as we only want to see instances directly using a volume, rather than their
	// profiles using a volume.
	err = storagePools.VolumeUsedByInstanceDevices(s, poolName, projectName, vol, false, func(inst db.Instance, p api.Project, profiles []api.Profile, usedByDevices []string) error {
		if inst.Project == project.Default {
			volumeUsedBy = append(volumeUsedBy, fmt.Sprintf("/%s/instances/%s", version.APIVersion, inst.Name))
		} else {
			volumeUsedBy = append(volumeUsedBy, fmt.Sprintf("/%s/instances/%s?project=%s", version.APIVersion, inst.Name, inst.Project))
		}

		return nil
	})
	if err != nil {
		return []string{}, err
	}

	err = storagePools.VolumeUsedByProfileDevices(s, poolName, projectName, vol, func(profile db.Profile, p api.Project, usedByDevices []string) error {
		if profile.Project == project.Default {
			volumeUsedBy = append(volumeUsedBy, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, profile.Name))
		} else {
			volumeUsedBy = append(volumeUsedBy, fmt.Sprintf("/%s/profiles/%s?project=%s", version.APIVersion, profile.Name, profile.Project))
		}

		return nil
	})
	if err != nil {
		return []string{}, err
	}

	return volumeUsedBy, nil
}

func storagePoolVolumeBackupLoadByName(s *state.State, projectName, poolName, backupName string) (*backup.VolumeBackup, error) {
	b, err := s.Cluster.GetStoragePoolVolumeBackup(projectName, poolName, backupName)
	if err != nil {
		return nil, err
	}

	volumeName := strings.Split(backupName, "/")[0]
	backup := backup.NewVolumeBackup(s, projectName, poolName, volumeName, b.ID, b.Name, b.CreationDate, b.ExpiryDate, b.VolumeOnly, b.OptimizedStorage)

	return backup, nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"math"
	"net"
	"os"
	"strings"
	"time"

	"github.com/hirose31/ringbuffer"
	"github.com/mattn/go-isatty"
	termbox "github.com/nsf/termbox-go"
	"github.com/tatsushid/go-fastping"
)

var (
	writeFailedLog = isatty.IsTerminal(os.Stderr.Fd())
)

type response struct {
	addr *net.IPAddr
	rtt  time.Duration
}

func keyEventLoop(kch chan termbox.Key) {
	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			kch <- ev.Key
		case termbox.EventResize:
			layout.termW, layout.termH = termbox.Size()
			drawHeader()
		default:
		}
	}
}

func section(title string) string {
	line := "-[ " + title + " ]-"
	if w := layout.termW - len(line); w > 0 {
		line += strings.Repeat("-", w)
	}
	return line
}

func drawStr(x, y int, str string) {
	drawColorfulStr(x, y, str, termbox.ColorDefault, termbox.ColorDefault)
}

func drawColorfulStr(x, y int, str string, fg, bg termbox.Attribute) {
	runes := []rune(str)
	for i := 0; i < len(runes); i++ {
		termbox.SetCell(x+i, y, runes[i], fg, bg)
	}
}

var layout struct {
	resultL        int
	resultH        int
	hostnameL      int
	termW          int
	termH          int
	failedHistoryY int
}

func drawHeader() {
	header := fmt.Sprintf("pinger v%s", Version)
	drawStr(0, 0, header)
	header = "type ESC or C-c to exit"
	drawStr(layout.termW-len(header), 0, header)
	drawStr(0, 1, section("current rtt (avg) ms"))

	drawStr(0, layout.failedHistoryY, section("failed history"))
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage:\n  %s [options] hostname [hostname ...]\n\nOptions:\n", os.Args[0])
		flag.PrintDefaults()
	}
	flag.Parse()

	hostnames := flag.Args()
	if len(hostnames) == 0 {
		flag.Usage()
		os.Exit(1)
	}

	p := fastping.NewPinger()
	netProto := "ip4:icmp"
	p.MaxRTT = time.Second

	result := make(map[string]*response)
	rtt := make(map[string]*ringbuffer.RingBuffer)
	ipaddrOf := make(map[string]string)
	for _, hostname := range hostnames {
		ra, err := net.ResolveIPAddr(netProto, hostname)
		if err != nil {
			panic(err)
		}

		p.AddIPAddr(ra)

		ipaddr := ra.String()
		ipaddrOf[hostname] = ipaddr
		result[ipaddr] = nil
		rtt[ipaddr] = ringbuffer.NewRingBuffer(10)

		if hl := len(hostname); hl > layout.hostnameL {
			layout.hostnameL = hl
		}
	}
	layout.resultL = 2 + layout.hostnameL + 1 + 5 + 2 + 5 + 1
	layout.resultH = int(math.Ceil(float64(len(hostnames)) / 2.0))

	onRecv := make(chan *response)
	p.OnRecv = func(addr *net.IPAddr, t time.Duration) {
		onRecv <- &response{addr: addr, rtt: t}
	}

	onIdle := make(chan bool)
	p.OnIdle = func() {
		onIdle <- true
	}

	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()
	layout.termW, layout.termH = termbox.Size()

	layout.failedHistoryY = 1 + layout.resultH + 1 + 1

	histSize := layout.termH - layout.failedHistoryY - 2
	if histSize <= 0 {
		histSize = 10
	}
	failedHistory := ringbuffer.NewRingBuffer(histSize)

	drawHeader()
	termbox.Flush()

	keyCh := make(chan termbox.Key)
	go keyEventLoop(keyCh)

	p.RunLoop()
	defer p.Stop()

loop:
	for {
		select {
		case res := <-onRecv:
			result[res.addr.String()] = res
		case <-onIdle:
			y := 2

			for i, hostname := range hostnames {
				ipaddr := ipaddrOf[hostname]
				res := result[ipaddr]
				var msg string
				var fg termbox.Attribute
				var st string
				if res != nil {
					st = "o"
					fg = termbox.ColorGreen

					rtt[ipaddr].Push(res.rtt)
					rttHist, _ := rtt[ipaddr].Fetch()
					var rttSum float64
					for _, t := range rttHist {
						rttSum += t.(time.Duration).Seconds()
					}
					avg := rttSum / float64(len(rttHist))

					msg = fmt.Sprintf("%-[1]*s %5.2f (%5.2f)", layout.hostnameL, hostname, res.rtt.Seconds()*1000, avg*1000)
				} else {
					st = "x"
					fg = termbox.ColorYellow

					msg = fmt.Sprintf("%-[1]*s", layout.resultL, hostname)
					faillog := fmt.Sprintf("%s %-24s",
						time.Now().Format("2006-01-02 15:04:05.000"),
						hostname)
					failedHistory.Push(faillog)
					if writeFailedLog {
						fmt.Fprintln(os.Stderr, faillog)
					}
				}
				if i%2 == 0 {
					drawColorfulStr(0, y, st, fg, termbox.ColorDefault)
					drawStr(2, y, msg)
				} else {
					drawStr(layout.resultL, y, " | ")
					drawColorfulStr(layout.resultL+3, y, st, fg, termbox.ColorDefault)
					drawStr(layout.resultL+5, y, msg)
					y++
				}

				result[ipaddr] = nil
			}

			y = layout.failedHistoryY + 1

			history, err := failedHistory.Fetch()
			if err != nil {
				panic(err)
			}
			for i := len(history) - 1; i >= 0; i-- {
				drawStr(0, y, history[i].(string))
				y++
			}

			termbox.Flush()
		case key := <-keyCh:
			switch key {
			case termbox.KeyEsc, termbox.KeyCtrlC:
				break loop
			}
		case <-p.Done():
			if err = p.Err(); err != nil {
				panic(err)
			}
			break loop
		}
	}
}
<commit_msg>Correct writeFailedLog value<commit_after>package main

import (
	"flag"
	"fmt"
	"math"
	"net"
	"os"
	"strings"
	"time"

	"github.com/hirose31/ringbuffer"
	"github.com/mattn/go-isatty"
	termbox "github.com/nsf/termbox-go"
	"github.com/tatsushid/go-fastping"
)

var (
	writeFailedLog = !isatty.IsTerminal(os.Stderr.Fd())
)

type response struct {
	addr *net.IPAddr
	rtt  time.Duration
}

func keyEventLoop(kch chan termbox.Key) {
	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			kch <- ev.Key
		case termbox.EventResize:
			layout.termW, layout.termH = termbox.Size()
			drawHeader()
		default:
		}
	}
}

func section(title string) string {
	line := "-[ " + title + " ]-"
	if w := layout.termW - len(line); w > 0 {
		line += strings.Repeat("-", w)
	}
	return line
}

func drawStr(x, y int, str string) {
	drawColorfulStr(x, y, str, termbox.ColorDefault, termbox.ColorDefault)
}

func drawColorfulStr(x, y int, str string, fg, bg termbox.Attribute) {
	runes := []rune(str)
	for i := 0; i < len(runes); i++ {
		termbox.SetCell(x+i, y, runes[i], fg, bg)
	}
}

var layout struct {
	resultL        int
	resultH        int
	hostnameL      int
	termW          int
	termH          int
	failedHistoryY int
}

func drawHeader() {
	header := fmt.Sprintf("pinger v%s", Version)
	drawStr(0, 0, header)
	header = "type ESC or C-c to exit"
	drawStr(layout.termW-len(header), 0, header)
	drawStr(0, 1, section("current rtt (avg) ms"))

	drawStr(0, layout.failedHistoryY, section("failed history"))
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage:\n  %s [options] hostname [hostname ...]\n\nOptions:\n", os.Args[0])
		flag.PrintDefaults()
	}
	flag.Parse()

	hostnames := flag.Args()
	if len(hostnames) == 0 {
		flag.Usage()
		os.Exit(1)
	}

	p := fastping.NewPinger()
	netProto := "ip4:icmp"
	p.MaxRTT = time.Second

	result := make(map[string]*response)
	rtt := make(map[string]*ringbuffer.RingBuffer)
	ipaddrOf := make(map[string]string)
	for _, hostname := range hostnames {
		ra, err := net.ResolveIPAddr(netProto, hostname)
		if err != nil {
			panic(err)
		}

		p.AddIPAddr(ra)

		ipaddr := ra.String()
		ipaddrOf[hostname] = ipaddr
		result[ipaddr] = nil
		rtt[ipaddr] = ringbuffer.NewRingBuffer(10)

		if hl := len(hostname); hl > layout.hostnameL {
			layout.hostnameL = hl
		}
	}
	layout.resultL = 2 + layout.hostnameL + 1 + 5 + 2 + 5 + 1
	layout.resultH = int(math.Ceil(float64(len(hostnames)) / 2.0))

	onRecv := make(chan *response)
	p.OnRecv = func(addr *net.IPAddr, t time.Duration) {
		onRecv <- &response{addr: addr, rtt: t}
	}

	onIdle := make(chan bool)
	p.OnIdle = func() {
		onIdle <- true
	}

	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()
	layout.termW, layout.termH = termbox.Size()

	layout.failedHistoryY = 1 + layout.resultH + 1 + 1

	histSize := layout.termH - layout.failedHistoryY - 2
	if histSize <= 0 {
		histSize = 10
	}
	failedHistory := ringbuffer.NewRingBuffer(histSize)

	drawHeader()
	termbox.Flush()

	keyCh := make(chan termbox.Key)
	go keyEventLoop(keyCh)

	p.RunLoop()
	defer p.Stop()

loop:
	for {
		select {
		case res := <-onRecv:
			result[res.addr.String()] = res
		case <-onIdle:
			y := 2

			for i, hostname := range hostnames {
				ipaddr := ipaddrOf[hostname]
				res := result[ipaddr]
				var msg string
				var fg termbox.Attribute
				var st string
				if res != nil {
					st = "o"
					fg = termbox.ColorGreen

					rtt[ipaddr].Push(res.rtt)
					rttHist, _ := rtt[ipaddr].Fetch()
					var rttSum float64
					for _, t := range rttHist {
						rttSum += t.(time.Duration).Seconds()
					}
					avg := rttSum / float64(len(rttHist))

					msg = fmt.Sprintf("%-[1]*s %5.2f (%5.2f)", layout.hostnameL, hostname, res.rtt.Seconds()*1000, avg*1000)
				} else {
					st = "x"
					fg = termbox.ColorYellow

					msg = fmt.Sprintf("%-[1]*s", layout.resultL, hostname)
					faillog := fmt.Sprintf("%s %-24s",
						time.Now().Format("2006-01-02 15:04:05.000"),
						hostname)
					failedHistory.Push(faillog)
					if writeFailedLog {
						fmt.Fprintln(os.Stderr, faillog)
					}
				}
				if i%2 == 0 {
					drawColorfulStr(0, y, st, fg, termbox.ColorDefault)
					drawStr(2, y, msg)
				} else {
					drawStr(layout.resultL, y, " | ")
					drawColorfulStr(layout.resultL+3, y, st, fg, termbox.ColorDefault)
					drawStr(layout.resultL+5, y, msg)
					y++
				}

				result[ipaddr] = nil
			}

			y = layout.failedHistoryY + 1

			history, err := failedHistory.Fetch()
			if err != nil {
				panic(err)
			}
			for i := len(history) - 1; i >= 0; i-- {
				drawStr(0, y, history[i].(string))
				y++
			}

			termbox.Flush()
		case key := <-keyCh:
			switch key {
			case termbox.KeyEsc, termbox.KeyCtrlC:
				break loop
			}
		case <-p.Done():
			if err = p.Err(); err != nil {
				panic(err)
			}
			break loop
		}
	}
}
<|endoftext|>


<commit_before>package main

const MaxInt = int(^uint(0) >> 1)

var UpgradeVersions = map[string]map[int]func(int) int{
	"inventory": map[int]func(int) int{
		201605060001: upgrade_inventory_201605060001,
	},
	"auth": map[int]func(int) int{
		201605060001: upgrade_auth_201605060001,
	},
	"soma": map[int]func(int) int{
		201605060001: upgrade_soma_201605060001,
		201605060002: upgrade_soma_201605060002,
	},
}

func UpgradeSchema(target int) error {
	// no specific target specified => upgrade all the way
	if target == 0 {
		target = MaxInt
	}

loop:
	for schema, _ := range UpgradeVersions {
		// fetch current version from database
		version := getCurrentSchemaVersion(schema)

		if version >= target {
			// schema is already as updated as we need
			continue loop
		}

		for f, ok := UpgradeVersions[schema][version]; ok; f, ok = UpgradeVersions[schema][version] {
			version = f(version)
			if version == 0 {
				// something broke
				// TODO: set error
				break loop
			} else if version >= target {
				// job done, continue with next schema
				continue loop
			}
		}
	}
	return nil
}

func upgrade_inventory_201605060001(curr int) int {
	if curr != 201605060001 {
		return 0
	}
	return 201605060002
}

func upgrade_auth_201605060001(curr int) int {
	if curr != 201605060001 {
		return 0
	}
	return 201605060002
}

func upgrade_soma_201605060001(curr int) int {
	if curr != 201605060001 {
		return 0
	}
	return 201605060002
}

func upgrade_soma_201605060002(curr int) int {
	if curr != 201605060002 {
		return 0
	}
	return 201605060003
}

func getCurrentSchemaVersion(schema string) int {
	// TODO: needs hook to mock current version to report for no-execute
	//       case
	return 201605060001
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<commit_msg>Upgrade: add root/201605150001 + auth/201605150002<commit_after>package main

import "fmt"

const MaxInt = int(^uint(0) >> 1)

var UpgradeVersions = map[string]map[int]func(int, string) int{
	"inventory": map[int]func(int, string) int{
		201605060001: mock_upgrade_inventory_201605060001,
	},
	"auth": map[int]func(int, string) int{
		201605060001: upgrade_auth_to_201605150002,
	},
	"soma": map[int]func(int, string) int{
		201605060001: mock_upgrade_soma_201605060001,
		201605060002: mock_upgrade_soma_201605060002,
	},
	"root": map[int]func(int, string) int{
		000000000001: install_root_201605150001,
	},
}

func UpgradeSchema(target int, tool string) error {
	// no specific target specified => upgrade all the way
	if target == 0 {
		target = MaxInt
	}

loop:
	for schema, _ := range UpgradeVersions {
		// fetch current version from database
		version := getCurrentSchemaVersion(schema)

		if version >= target {
			// schema is already as updated as we need
			continue loop
		}

		for f, ok := UpgradeVersions[schema][version]; ok; f, ok = UpgradeVersions[schema][version] {
			version = f(version, tool)
			if version == 0 {
				// something broke
				// TODO: set error
				break loop
			} else if version >= target {
				// job done, continue with next schema
				continue loop
			}
		}
	}
	return nil
}

func mock_upgrade_inventory_201605060001(curr int, tool string) int {
	if curr != 201605060001 {
		return 0
	}
	return 201605060002
}

func upgrade_auth_to_201605150002(curr int, tool string) int {
	if curr != 201605060001 {
		return 0
	}
	dbOpen()

	db.Exec(`DELETE FROM auth.user_token_authentication;`)
	db.Exec(`ALTER TABLE auth.user_token_authentication ADD COLUMN salt varchar(256) NOT NULL;`)
	db.Exec(`ALTER TABLE auth.user_token_authentication RENAME TO tokens;`)
	db.Exec(`DROP TABLE auth.admin_token_authentication;`)
	db.Exec(`ALTER TABLE auth.tools ADD CHECK( left( tool_name, 5 ) = 'tool_' );`)
	db.Exec(`ALTER TABLE auth.user_authentication DROP COLUMN algorithm;`)
	db.Exec(`ALTER TABLE auth.user_authentication DROP COLUMN rounds;`)
	db.Exec(`ALTER TABLE auth.user_authentication DROP COLUMN salt;`)
	db.Exec(`ALTER TABLE auth.admin_authentication DROP COLUMN algorithm;`)
	db.Exec(`ALTER TABLE auth.admin_authentication DROP COLUMN rounds;`)
	db.Exec(`ALTER TABLE auth.admin_authentication DROP COLUMN salt;`)
	db.Exec(`ALTER TABLE auth.tool_authentication DROP COLUMN algorithm;`)
	db.Exec(`ALTER TABLE auth.tool_authentication DROP COLUMN rounds;`)
	db.Exec(`ALTER TABLE auth.tool_authentication DROP COLUMN salt;`)
	insert := fmt.Sprintf("INSERT INTO public.schema_versions (schema, version, description) VALUES ('auth', 201605150002, 'Upgrade - somadbctl %s');", tool)
	db.Exec(insert)
	return 201605150002
}

func install_root_201605150001(curr int, tool string) int {
	if curr != 000000000001 {
		return 0
	}
	dbOpen()

	db.Exec(`CREATE SCHEMA IF NOT EXISTS root;`)
	db.Exec(`GRANT USAGE ON SCHEMA root TO soma_svc;`)
	db.Exec(`CREATE TABLE IF NOT EXISTS root.token (token varchar(256) NOT NULL);`)
	db.Exec(`CREATE TABLE IF NOT EXISTS root.flags (flag varchar(256) NOT NULL, status boolean NOT NULL DEFAULT 'no');`)
	db.Exec(`GRANT SELECT ON ALL TABLES IN SCHEMA root TO soma_svc;`)
	db.Exec(`INSERT INTO root.flags (flag, status) VALUES ('restricted', false), ('disabled', false);`)
	insert := fmt.Sprintf("INSERT INTO public.schema_versions (schema, version, description) VALUES ('root', 201605150001, 'Upgrade create - somadbctl %s');", tool)
	db.Exec(insert)

	return 201605150001
}

func mock_upgrade_soma_201605060001(curr int, tool string) int {
	if curr != 201605060001 {
		return 0
	}
	return 201605060002
}

func mock_upgrade_soma_201605060002(curr int, tool string) int {
	if curr != 201605060002 {
		return 0
	}
	return 201605060003
}

func getCurrentSchemaVersion(schema string) int {
	// TODO: needs hook to mock current version to report for no-execute
	//       case
	return 201605060001
}

// vim: ts=4 sw=4 sts=4 noet fenc=utf-8 ffs=unix
<|endoftext|>


<commit_before>package resolver

import (
	"encoding/json"
	"fmt"
	"hash/fnv"
	"sort"
	"strings"

	"github.com/blang/semver"
	"k8s.io/apimachinery/pkg/runtime/schema"

	"github.com/operator-framework/operator-registry/pkg/api"
	"github.com/operator-framework/operator-registry/pkg/registry"
	opregistry "github.com/operator-framework/operator-registry/pkg/registry"

	"github.com/operator-framework/api/pkg/operators/v1alpha1"
)

type CatalogKey struct {
	Name      string
	Namespace string
}

func (k *CatalogKey) String() string {
	return fmt.Sprintf("%s/%s", k.Name, k.Namespace)
}

func (k *CatalogKey) IsEmpty() bool {
	return k.Name == "" && k.Namespace == ""
}

func (k *CatalogKey) IsEqual(compare CatalogKey) bool {
	return k.Name == compare.Name && k.Namespace == compare.Namespace
}

type APISet map[opregistry.APIKey]struct{}

func EmptyAPISet() APISet {
	return map[opregistry.APIKey]struct{}{}
}

func (s APISet) PopAPIKey() *opregistry.APIKey {
	for a := range s {
		api := &opregistry.APIKey{
			Group:   a.Group,
			Version: a.Version,
			Kind:    a.Kind,
			Plural:  a.Plural,
		}
		delete(s, a)
		return api
	}
	return nil
}

func GVKStringToProvidedAPISet(gvksStr string) APISet {
	set := make(APISet)
	// TODO: Should we make gvk strings lowercase to avoid issues with user set gvks?
	gvks := strings.Split(strings.Replace(gvksStr, " ", "", -1), ",")
	for _, gvkStr := range gvks {
		gvk, _ := schema.ParseKindArg(gvkStr)
		if gvk != nil {
			set[opregistry.APIKey{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind}] = struct{}{}
		}
	}

	return set
}

func APIKeyToGVKString(key opregistry.APIKey) string {
	// TODO: Add better validation of GVK
	return strings.Join([]string{key.Kind, key.Version, key.Group}, ".")
}

func APIKeyToGVKHash(key opregistry.APIKey) (string, error) {
	hash := fnv.New64a()
	if _, err := hash.Write([]byte(APIKeyToGVKString(key))); err != nil {
		return "", err
	}
	return fmt.Sprintf("%x", hash.Sum64()), nil
}

func (s APISet) String() string {
	gvkStrs := make([]string, len(s))
	i := 0
	for api := range s {
		// TODO: Only add valid GVK strings
		gvkStrs[i] = APIKeyToGVKString(api)
		i++
	}
	sort.Strings(gvkStrs)

	return strings.Join(gvkStrs, ",")
}

// TODO: Generalize set logic and make an abstraction for sets to implemement to feed into it.

// Union returns the union of the APISet and the given list of APISets
func (s APISet) Union(sets ...APISet) APISet {
	union := make(APISet)
	for api := range s {
		union[api] = struct{}{}
	}
	for _, set := range sets {
		for api := range set {
			union[api] = struct{}{}
		}
	}

	return union
}

// Intersection returns the intersection of the APISet and the given list of APISets
func (s APISet) Intersection(sets ...APISet) APISet {
	intersection := make(APISet)
	for _, set := range sets {
		for api := range set {
			if _, ok := s[api]; ok {
				intersection[api] = struct{}{}
			}
		}
	}

	return intersection
}

func (s APISet) Difference(set APISet) APISet {
	difference := make(APISet).Union(s)
	for api := range set {
		if _, ok := difference[api]; ok {
			delete(difference, api)
		}
	}

	return difference
}

// IsSubset returns true if the APISet is a subset of the given one
func (s APISet) IsSubset(set APISet) bool {
	for api := range s {
		if _, ok := set[api]; !ok {
			return false
		}
	}

	return true
}

// StripPlural returns the APISet with the Plural field of all APIKeys removed
func (s APISet) StripPlural() APISet {
	set := make(APISet)
	for api := range s {
		set[opregistry.APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind}] = struct{}{}
	}

	return set
}

type APIOwnerSet map[opregistry.APIKey]OperatorSurface

func EmptyAPIOwnerSet() APIOwnerSet {
	return map[opregistry.APIKey]OperatorSurface{}
}

type OperatorSet map[string]OperatorSurface

func EmptyOperatorSet() OperatorSet {
	return map[string]OperatorSurface{}
}

// Snapshot returns a new set, pointing to the same values
func (o OperatorSet) Snapshot() OperatorSet {
	out := make(map[string]OperatorSurface)
	for key, val := range o {
		out[key] = val
	}
	return out
}

type APIMultiOwnerSet map[opregistry.APIKey]OperatorSet

func EmptyAPIMultiOwnerSet() APIMultiOwnerSet {
	return map[opregistry.APIKey]OperatorSet{}
}

func (s APIMultiOwnerSet) PopAPIKey() *opregistry.APIKey {
	for a := range s {
		api := &opregistry.APIKey{
			Group:   a.Group,
			Version: a.Version,
			Kind:    a.Kind,
			Plural:  a.Plural,
		}
		delete(s, a)
		return api
	}
	return nil
}

func (s APIMultiOwnerSet) PopAPIRequirers() OperatorSet {
	requirers := EmptyOperatorSet()
	for a := range s {
		for key, op := range s[a] {
			requirers[key] = op
		}
		delete(s, a)
		return requirers
	}
	return nil
}

type OperatorSourceInfo struct {
	Package     string
	Channel     string
	StartingCSV string
	Catalog     CatalogKey
}

func (i *OperatorSourceInfo) String() string {
	return fmt.Sprintf("%s/%s in %s/%s", i.Package, i.Channel, i.Catalog.Name, i.Catalog.Namespace)
}

var ExistingOperator = OperatorSourceInfo{"", "", "", CatalogKey{"", ""}}

// OperatorSurface describes the API surfaces provided and required by an Operator.
type OperatorSurface interface {
	ProvidedAPIs() APISet
	RequiredAPIs() APISet
	Identifier() string
	Replaces() string
	Version() *semver.Version
	SourceInfo() *OperatorSourceInfo
	Bundle() *api.Bundle
	Inline() bool
	VersionDependencies() []VersionDependency
}

type Operator struct {
	name                string
	replaces            string
	providedAPIs        APISet
	requiredAPIs        APISet
	version             *semver.Version
	bundle              *api.Bundle
	sourceInfo          *OperatorSourceInfo
	versionDependencies []VersionDependency
}

type VersionDependency struct {
	Package string
	Version semver.Version
	// TODO: BundleImage string
	// TODO: VersionRange string
}

var _ OperatorSurface = &Operator{}

func NewOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey CatalogKey) (*Operator, error) {
	parsedVersion, err := semver.ParseTolerant(bundle.Version)
	version := &parsedVersion
	if err != nil {
		version = nil
	}
	provided := APISet{}
	for _, gvk := range bundle.ProvidedApis {
		provided[registry.APIKey{Plural: gvk.Plural, Group: gvk.Group, Kind: gvk.Kind, Version: gvk.Version}] = struct{}{}
	}
	required := APISet{}
	for _, gvk := range bundle.RequiredApis {
		required[registry.APIKey{Plural: gvk.Plural, Group: gvk.Group, Kind: gvk.Kind, Version: gvk.Version}] = struct{}{}
	}
	sourceInfo := &OperatorSourceInfo{
		Package:     bundle.PackageName,
		Channel:     bundle.ChannelName,
		StartingCSV: startingCSV,
		Catalog:     sourceKey,
	}

	// legacy support - if the grpc api doesn't contain the information we need, fallback to csv parsing
	if len(required) == 0 && len(provided) == 0 {
		// fallback to csv parsing
		if bundle.CsvJson == "" {
			if bundle.GetBundlePath() != "" {
				return nil, fmt.Errorf("couldn't parse bundle path, missing provided and required apis")
			}

			return nil, fmt.Errorf("couldn't parse bundle, missing provided and required apis")
		}

		csv := &v1alpha1.ClusterServiceVersion{}
		if err := json.Unmarshal([]byte(bundle.CsvJson), csv); err != nil {
			return nil, err
		}

		op, err := NewOperatorFromV1Alpha1CSV(csv)
		if err != nil {
			return nil, err
		}
		op.sourceInfo = sourceInfo
		op.bundle = bundle
		return op, nil
	}

	return &Operator{
		name:         bundle.CsvName,
		version:      version,
		providedAPIs: provided,
		requiredAPIs: required,
		bundle:       bundle,
		sourceInfo:   sourceInfo,
	}, nil
}

func NewOperatorFromV1Alpha1CSV(csv *v1alpha1.ClusterServiceVersion) (*Operator, error) {
	providedAPIs := EmptyAPISet()
	for _, crdDef := range csv.Spec.CustomResourceDefinitions.Owned {
		parts := strings.SplitN(crdDef.Name, ".", 2)
		if len(parts) < 2 {
			return nil, fmt.Errorf("error parsing crd name: %s", crdDef.Name)
		}
		providedAPIs[opregistry.APIKey{Plural: parts[0], Group: parts[1], Version: crdDef.Version, Kind: crdDef.Kind}] = struct{}{}
	}
	for _, api := range csv.Spec.APIServiceDefinitions.Owned {
		providedAPIs[opregistry.APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind, Plural: api.Name}] = struct{}{}
	}

	requiredAPIs := EmptyAPISet()
	for _, crdDef := range csv.Spec.CustomResourceDefinitions.Required {
		parts := strings.SplitN(crdDef.Name, ".", 2)
		if len(parts) < 2 {
			return nil, fmt.Errorf("error parsing crd name: %s", crdDef.Name)
		}
		requiredAPIs[opregistry.APIKey{Plural: parts[0], Group: parts[1], Version: crdDef.Version, Kind: crdDef.Kind}] = struct{}{}
	}
	for _, api := range csv.Spec.APIServiceDefinitions.Required {
		requiredAPIs[opregistry.APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind, Plural: api.Name}] = struct{}{}
	}

	return &Operator{
		name:         csv.GetName(),
		version:      &csv.Spec.Version.Version,
		providedAPIs: providedAPIs,
		requiredAPIs: requiredAPIs,
		sourceInfo:   &ExistingOperator,
	}, nil
}

func (o *Operator) ProvidedAPIs() APISet {
	return o.providedAPIs
}

func (o *Operator) RequiredAPIs() APISet {
	return o.requiredAPIs
}

func (o *Operator) Identifier() string {
	return o.name
}

func (o *Operator) Replaces() string {
	return o.replaces
}

func (o *Operator) SetReplaces(replacing string) {
	o.replaces = replacing
}

func (o *Operator) Package() string {
	return o.bundle.PackageName
}

func (o *Operator) SourceInfo() *OperatorSourceInfo {
	return o.sourceInfo
}

func (o *Operator) Bundle() *api.Bundle {
	return o.bundle
}

func (o *Operator) Version() *semver.Version {
	return o.version
}

func (o *Operator) Inline() bool {
	return o.bundle != nil && o.bundle.GetBundlePath() == ""
}

func (o *Operator) VersionDependencies() []VersionDependency {
	return o.versionDependencies
}
<commit_msg>Extract dependencies info from bundle and add to Operator object<commit_after>package resolver

import (
	"encoding/json"
	"fmt"
	"hash/fnv"
	"sort"
	"strings"

	"github.com/blang/semver"
	"k8s.io/apimachinery/pkg/runtime/schema"

	"github.com/operator-framework/operator-registry/pkg/api"
	"github.com/operator-framework/operator-registry/pkg/registry"
	opregistry "github.com/operator-framework/operator-registry/pkg/registry"

	"github.com/operator-framework/api/pkg/operators/v1alpha1"
)

type CatalogKey struct {
	Name      string
	Namespace string
}

func (k *CatalogKey) String() string {
	return fmt.Sprintf("%s/%s", k.Name, k.Namespace)
}

func (k *CatalogKey) IsEmpty() bool {
	return k.Name == "" && k.Namespace == ""
}

func (k *CatalogKey) IsEqual(compare CatalogKey) bool {
	return k.Name == compare.Name && k.Namespace == compare.Namespace
}

type APISet map[opregistry.APIKey]struct{}

func EmptyAPISet() APISet {
	return map[opregistry.APIKey]struct{}{}
}

func (s APISet) PopAPIKey() *opregistry.APIKey {
	for a := range s {
		api := &opregistry.APIKey{
			Group:   a.Group,
			Version: a.Version,
			Kind:    a.Kind,
			Plural:  a.Plural,
		}
		delete(s, a)
		return api
	}
	return nil
}

func GVKStringToProvidedAPISet(gvksStr string) APISet {
	set := make(APISet)
	// TODO: Should we make gvk strings lowercase to avoid issues with user set gvks?
	gvks := strings.Split(strings.Replace(gvksStr, " ", "", -1), ",")
	for _, gvkStr := range gvks {
		gvk, _ := schema.ParseKindArg(gvkStr)
		if gvk != nil {
			set[opregistry.APIKey{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind}] = struct{}{}
		}
	}

	return set
}

func APIKeyToGVKString(key opregistry.APIKey) string {
	// TODO: Add better validation of GVK
	return strings.Join([]string{key.Kind, key.Version, key.Group}, ".")
}

func APIKeyToGVKHash(key opregistry.APIKey) (string, error) {
	hash := fnv.New64a()
	if _, err := hash.Write([]byte(APIKeyToGVKString(key))); err != nil {
		return "", err
	}
	return fmt.Sprintf("%x", hash.Sum64()), nil
}

func (s APISet) String() string {
	gvkStrs := make([]string, len(s))
	i := 0
	for api := range s {
		// TODO: Only add valid GVK strings
		gvkStrs[i] = APIKeyToGVKString(api)
		i++
	}
	sort.Strings(gvkStrs)

	return strings.Join(gvkStrs, ",")
}

// TODO: Generalize set logic and make an abstraction for sets to implemement to feed into it.

// Union returns the union of the APISet and the given list of APISets
func (s APISet) Union(sets ...APISet) APISet {
	union := make(APISet)
	for api := range s {
		union[api] = struct{}{}
	}
	for _, set := range sets {
		for api := range set {
			union[api] = struct{}{}
		}
	}

	return union
}

// Intersection returns the intersection of the APISet and the given list of APISets
func (s APISet) Intersection(sets ...APISet) APISet {
	intersection := make(APISet)
	for _, set := range sets {
		for api := range set {
			if _, ok := s[api]; ok {
				intersection[api] = struct{}{}
			}
		}
	}

	return intersection
}

func (s APISet) Difference(set APISet) APISet {
	difference := make(APISet).Union(s)
	for api := range set {
		if _, ok := difference[api]; ok {
			delete(difference, api)
		}
	}

	return difference
}

// IsSubset returns true if the APISet is a subset of the given one
func (s APISet) IsSubset(set APISet) bool {
	for api := range s {
		if _, ok := set[api]; !ok {
			return false
		}
	}

	return true
}

// StripPlural returns the APISet with the Plural field of all APIKeys removed
func (s APISet) StripPlural() APISet {
	set := make(APISet)
	for api := range s {
		set[opregistry.APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind}] = struct{}{}
	}

	return set
}

type APIOwnerSet map[opregistry.APIKey]OperatorSurface

func EmptyAPIOwnerSet() APIOwnerSet {
	return map[opregistry.APIKey]OperatorSurface{}
}

type OperatorSet map[string]OperatorSurface

func EmptyOperatorSet() OperatorSet {
	return map[string]OperatorSurface{}
}

// Snapshot returns a new set, pointing to the same values
func (o OperatorSet) Snapshot() OperatorSet {
	out := make(map[string]OperatorSurface)
	for key, val := range o {
		out[key] = val
	}
	return out
}

type APIMultiOwnerSet map[opregistry.APIKey]OperatorSet

func EmptyAPIMultiOwnerSet() APIMultiOwnerSet {
	return map[opregistry.APIKey]OperatorSet{}
}

func (s APIMultiOwnerSet) PopAPIKey() *opregistry.APIKey {
	for a := range s {
		api := &opregistry.APIKey{
			Group:   a.Group,
			Version: a.Version,
			Kind:    a.Kind,
			Plural:  a.Plural,
		}
		delete(s, a)
		return api
	}
	return nil
}

func (s APIMultiOwnerSet) PopAPIRequirers() OperatorSet {
	requirers := EmptyOperatorSet()
	for a := range s {
		for key, op := range s[a] {
			requirers[key] = op
		}
		delete(s, a)
		return requirers
	}
	return nil
}

type OperatorSourceInfo struct {
	Package     string
	Channel     string
	StartingCSV string
	Catalog     CatalogKey
}

func (i *OperatorSourceInfo) String() string {
	return fmt.Sprintf("%s/%s in %s/%s", i.Package, i.Channel, i.Catalog.Name, i.Catalog.Namespace)
}

var ExistingOperator = OperatorSourceInfo{"", "", "", CatalogKey{"", ""}}

// OperatorSurface describes the API surfaces provided and required by an Operator.
type OperatorSurface interface {
	ProvidedAPIs() APISet
	RequiredAPIs() APISet
	Identifier() string
	Replaces() string
	Version() *semver.Version
	SourceInfo() *OperatorSourceInfo
	Bundle() *api.Bundle
	Inline() bool
	VersionDependencies() []VersionDependency
}

type Operator struct {
	name                string
	replaces            string
	providedAPIs        APISet
	requiredAPIs        APISet
	version             *semver.Version
	bundle              *api.Bundle
	sourceInfo          *OperatorSourceInfo
	versionDependencies []VersionDependency
}

type VersionDependency struct {
	Package string
	Version semver.Version
	// TODO: BundleImage string
	// TODO: VersionRange string
}

var _ OperatorSurface = &Operator{}

func NewOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey CatalogKey) (*Operator, error) {
	parsedVersion, err := semver.ParseTolerant(bundle.Version)
	version := &parsedVersion
	if err != nil {
		version = nil
	}
	provided := APISet{}
	for _, gvk := range bundle.ProvidedApis {
		provided[registry.APIKey{Plural: gvk.Plural, Group: gvk.Group, Kind: gvk.Kind, Version: gvk.Version}] = struct{}{}
	}
	required := APISet{}
	for _, gvk := range bundle.RequiredApis {
		required[registry.APIKey{Plural: gvk.Plural, Group: gvk.Group, Kind: gvk.Kind, Version: gvk.Version}] = struct{}{}
	}
	sourceInfo := &OperatorSourceInfo{
		Package:     bundle.PackageName,
		Channel:     bundle.ChannelName,
		StartingCSV: startingCSV,
		Catalog:     sourceKey,
	}

	// legacy support - if the grpc api doesn't contain the information we need, fallback to csv parsing
	if len(required) == 0 && len(provided) == 0 {
		// fallback to csv parsing
		if bundle.CsvJson == "" {
			if bundle.GetBundlePath() != "" {
				return nil, fmt.Errorf("couldn't parse bundle path, missing provided and required apis")
			}

			return nil, fmt.Errorf("couldn't parse bundle, missing provided and required apis")
		}

		csv := &v1alpha1.ClusterServiceVersion{}
		if err := json.Unmarshal([]byte(bundle.CsvJson), csv); err != nil {
			return nil, err
		}

		op, err := NewOperatorFromV1Alpha1CSV(csv)
		if err != nil {
			return nil, err
		}
		op.sourceInfo = sourceInfo
		op.bundle = bundle
		return op, nil
	}

	// Extract dependencies info
	depList := []VersionDependency{}
	for _, v := range bundle.GetDependencies() {
		if v.GetType() == registry.PackageType {
			pkgDep := registry.PackageDependency{}
			err := json.Unmarshal([]byte(v.GetValue()), &pkgDep)
			if err != nil {
				continue
			}
			// Ignore package dependency if it is not valid (missing PackageName or
			// invalid semver version).
			// TODO: Support semver range. This requires code change on `VersionDependency`
			// and probably the cache func as well.
			ver, err := semver.ParseTolerant(pkgDep.Version)
			if err != nil {
				continue
			}
			if pkgDep.PackageName == "" {
				continue
			}

			vd := VersionDependency{
				Package: pkgDep.PackageName,
				Version: ver,
			}
			depList = append(depList, vd)
		}
	}

	return &Operator{
		name:                bundle.CsvName,
		version:             version,
		providedAPIs:        provided,
		requiredAPIs:        required,
		bundle:              bundle,
		sourceInfo:          sourceInfo,
		versionDependencies: depList,
	}, nil
}

func NewOperatorFromV1Alpha1CSV(csv *v1alpha1.ClusterServiceVersion) (*Operator, error) {
	providedAPIs := EmptyAPISet()
	for _, crdDef := range csv.Spec.CustomResourceDefinitions.Owned {
		parts := strings.SplitN(crdDef.Name, ".", 2)
		if len(parts) < 2 {
			return nil, fmt.Errorf("error parsing crd name: %s", crdDef.Name)
		}
		providedAPIs[opregistry.APIKey{Plural: parts[0], Group: parts[1], Version: crdDef.Version, Kind: crdDef.Kind}] = struct{}{}
	}
	for _, api := range csv.Spec.APIServiceDefinitions.Owned {
		providedAPIs[opregistry.APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind, Plural: api.Name}] = struct{}{}
	}

	requiredAPIs := EmptyAPISet()
	for _, crdDef := range csv.Spec.CustomResourceDefinitions.Required {
		parts := strings.SplitN(crdDef.Name, ".", 2)
		if len(parts) < 2 {
			return nil, fmt.Errorf("error parsing crd name: %s", crdDef.Name)
		}
		requiredAPIs[opregistry.APIKey{Plural: parts[0], Group: parts[1], Version: crdDef.Version, Kind: crdDef.Kind}] = struct{}{}
	}
	for _, api := range csv.Spec.APIServiceDefinitions.Required {
		requiredAPIs[opregistry.APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind, Plural: api.Name}] = struct{}{}
	}

	return &Operator{
		name:         csv.GetName(),
		version:      &csv.Spec.Version.Version,
		providedAPIs: providedAPIs,
		requiredAPIs: requiredAPIs,
		sourceInfo:   &ExistingOperator,
	}, nil
}

func (o *Operator) ProvidedAPIs() APISet {
	return o.providedAPIs
}

func (o *Operator) RequiredAPIs() APISet {
	return o.requiredAPIs
}

func (o *Operator) Identifier() string {
	return o.name
}

func (o *Operator) Replaces() string {
	return o.replaces
}

func (o *Operator) SetReplaces(replacing string) {
	o.replaces = replacing
}

func (o *Operator) Package() string {
	return o.bundle.PackageName
}

func (o *Operator) SourceInfo() *OperatorSourceInfo {
	return o.sourceInfo
}

func (o *Operator) Bundle() *api.Bundle {
	return o.bundle
}

func (o *Operator) Version() *semver.Version {
	return o.version
}

func (o *Operator) Inline() bool {
	return o.bundle != nil && o.bundle.GetBundlePath() == ""
}

func (o *Operator) VersionDependencies() []VersionDependency {
	return o.versionDependencies
}
<|endoftext|>


<commit_before>//
//
// File generated from our OpenAPI spec
//
//

package stripe

import "encoding/json"

// BillingPortalSessionParams is the set of parameters that can be used when creating a billing portal session.
type BillingPortalSessionParams struct {
	Params        `form:"*"`
	Configuration *string `form:"configuration"`
	Customer      *string `form:"customer"`
	OnBehalfOf    *string `form:"on_behalf_of"`
	ReturnURL     *string `form:"return_url"`
}

// BillingPortalSession is the resource representing a billing portal session.
type BillingPortalSession struct {
	APIResource
	Configuration *BillingPortalConfiguration `json:"configuration"`
	Created       int64                       `json:"created"`
	Customer      string                      `json:"customer"`
	ID            string                      `json:"id"`
	Livemode      bool                        `json:"livemode"`
	Object        string                      `json:"object"`
	OnBehalfOf    string                      `json:"on_behalf_of"`
	ReturnURL     string                      `json:"return_url"`
	URL           string                      `json:"url"`
}

// UnmarshalJSON handles deserialization of a BillingPortalSession.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (b *BillingPortalSession) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		b.ID = id
		return nil
	}

	type billingPortalSession BillingPortalSession
	var v billingPortalSession
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	*b = BillingPortalSession(v)
	return nil
}
<commit_msg>API Updates (#1325)<commit_after>//
//
// File generated from our OpenAPI spec
//
//

package stripe

import "encoding/json"

// BillingPortalSessionParams is the set of parameters that can be used when creating a billing portal session.
type BillingPortalSessionParams struct {
	Params        `form:"*"`
	Configuration *string `form:"configuration"`
	Customer      *string `form:"customer"`
	Locale        *string `form:"locale"`
	OnBehalfOf    *string `form:"on_behalf_of"`
	ReturnURL     *string `form:"return_url"`
}

// BillingPortalSession is the resource representing a billing portal session.
type BillingPortalSession struct {
	APIResource
	Configuration *BillingPortalConfiguration `json:"configuration"`
	Created       int64                       `json:"created"`
	Customer      string                      `json:"customer"`
	ID            string                      `json:"id"`
	Livemode      bool                        `json:"livemode"`
	Locale        string                      `json:"locale"`
	Object        string                      `json:"object"`
	OnBehalfOf    string                      `json:"on_behalf_of"`
	ReturnURL     string                      `json:"return_url"`
	URL           string                      `json:"url"`
}

// UnmarshalJSON handles deserialization of a BillingPortalSession.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (b *BillingPortalSession) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		b.ID = id
		return nil
	}

	type billingPortalSession BillingPortalSession
	var v billingPortalSession
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	*b = BillingPortalSession(v)
	return nil
}
<|endoftext|>


<commit_before>package notification

import (
	"fmt"
	socialapimodels "socialapi/models"
	"socialapi/workers/notification/models"
	"time"

	"github.com/koding/logging"
	"github.com/koding/rabbitmq"
	"github.com/streadway/amqp"
)

const (
	NOTIFICATION_TYPE_SUBSCRIBE   = "subscribe"
	NOTIFICATION_TYPE_UNSUBSCRIBE = "unsubscribe"
)

type Controller struct {
	log             logging.Logger
	rmqConn         *amqp.Connection
	notifierRmqConn *amqp.Connection
}

func (n *Controller) DefaultErrHandler(delivery amqp.Delivery, err error) bool {
	n.log.Error("an error occurred: %s", err)
	delivery.Ack(false)

	return false
}

func New(rmq *rabbitmq.RabbitMQ, log logging.Logger) *Controller {
	return &Controller{
		log:     log,
		rmqConn: rmq.Conn(),
	}
}

// CreateReplyNotification notifies main thread owner.
func (n *Controller) CreateReplyNotification(mr *socialapimodels.MessageReply) error {
	// fetch replier
	reply := socialapimodels.NewChannelMessage()
	if err := reply.ById(mr.ReplyId); err != nil {
		return err
	}

	cm := socialapimodels.NewChannelMessage()
	// notify message owner
	if err := cm.ById(mr.MessageId); err != nil {
		return err
	}

	if cm.TypeConstant != socialapimodels.ChannelMessage_TYPE_POST {
		return nil
	}

	rn := models.NewReplyNotification()
	rn.TargetId = mr.MessageId
	rn.NotifierId = reply.AccountId
	rn.MessageId = reply.Id

	subscribedAt := time.Now()

	nc, err := models.CreateNotificationContent(rn)
	if err != nil {
		return err
	}

	groupChannel, err := reply.FetchParentChannel()
	if err != nil {
		return err
	}

	// if it is not notifier's own message then add replier to subscribers
	// for further reply notifications
	if cm.AccountId != rn.NotifierId {
		n.subscribe(nc.Id, cm.AccountId, subscribedAt, groupChannel)
	}

	notifiedUsers, err := rn.GetNotifiedUsers(nc.Id)
	if err != nil {
		return err
	}

	mentionedUsers, err := n.CreateMentionNotification(reply, cm.Id)
	if err != nil {
		return err
	}

	// if a user is already subscribed to a post, and also mentioned in a reply
	// just send mention notification -no need for reply notification.
	notifiedUsers = filterRepliers(notifiedUsers, mentionedUsers)

	notifierSubscribed := false
	for _, recipient := range notifiedUsers {
		if recipient == rn.NotifierId {
			notifierSubscribed = true
		}
		n.notify(nc.Id, recipient, groupChannel)
	}

	if !notifierSubscribed {
		n.subscribe(nc.Id, rn.NotifierId, subscribedAt, groupChannel)
	}

	return nil
}

// func (n *Controller) UnsubscribeMessage(data *socialapimodels.ChannelMessageList) error {
// 	return subscription(data, NOTIFICATION_TYPE_UNSUBSCRIBE)
// }

//func subscription(cml *socialapimodels.ChannelMessageList, typeConstant string) error {
//    c := socialapimodels.NewChannel()
//    if err := c.ById(cml.ChannelId); err != nil {
//        return err
//    }

//    if c.TypeConstant != socialapimodels.Channel_TYPE_PINNED_ACTIVITY {
//        return nil
//    }

//     user pinned (followed) a message
//    nc := models.NewNotificationContent()
//    nc.TargetId = cml.MessageId

//    n := models.NewNotification()
//    n.AccountId = c.CreatorId

//    switch typeConstant {
//    case NOTIFICATION_TYPE_SUBSCRIBE:
//        return n.Subscribe(nc)
//    case NOTIFICATION_TYPE_UNSUBSCRIBE:
//        return n.Unsubscribe(nc)
//    }

//    return nil
//}

func (n *Controller) HandleMessage(cm *socialapimodels.ChannelMessage) error {
	switch cm.TypeConstant {
	case socialapimodels.ChannelMessage_TYPE_POST:
		_, err := n.CreateMentionNotification(cm, cm.Id)
		return err
	default:
		return nil
	}
}

func (n *Controller) DeleteNotification(cm *socialapimodels.ChannelMessage) error {
	nc := models.NewNotificationContent()
	contentIds, err := nc.FetchIdsByTargetId(cm.Id)
	if err != nil {
		return err
	}

	if len(contentIds) == 0 {
		return nil
	}

	return models.NewNotification().HideByContentIds(contentIds)
}

// CreateMentionNotification creates mention notifications for the related channel messages
func (n *Controller) CreateMentionNotification(reply *socialapimodels.ChannelMessage, targetId int64) ([]int64, error) {
	mentionedUserIds := make([]int64, 0)
	usernames := reply.GetMentionedUsernames()

	// message does not contain any mentioned users
	if len(usernames) == 0 {
		return mentionedUserIds, nil
	}

	mentionedUsers, err := socialapimodels.FetchAccountsByNicks(usernames)
	if err != nil {
		return nil, err
	}

	groupChannel, err := reply.FetchParentChannel()
	if err != nil {
		return nil, err
	}

	for _, mentionedUser := range mentionedUsers {
		// if user mentions herself ignore it
		if mentionedUser.Id == reply.AccountId {
			continue
		}
		mn := models.NewMentionNotification()
		mn.TargetId = targetId
		mn.MessageId = reply.Id
		mn.NotifierId = reply.AccountId
		nc, err := models.CreateNotificationContent(mn)
		if err != nil {
			return nil, err
		}

		n.instantNotify(nc.Id, mentionedUser.Id, groupChannel)

		mentionedUserIds = append(mentionedUserIds, mentionedUser.Id)
	}

	return mentionedUserIds, nil
}

func (n *Controller) notify(contentId, notifierId int64, contextChannel *socialapimodels.Channel) {
	isParticipant, err := isParticipant(notifierId, contextChannel)
	if err != nil {
		n.log.Error("Could not check participation info for user %d: %s", notifierId, err)
		return
	}

	// this can be a message of an old participant, and we do not want to send
	// further notifications
	if !isParticipant {
		return
	}

	notification := buildNotification(contentId, notifierId, time.Now())
	notification.ContextChannelId = contextChannel.Id
	if err := notification.Upsert(); err != nil {
		n.log.Error("An error occurred while notifying user %d: %s", notification.AccountId, err.Error())
	}
}

func (n *Controller) instantNotify(contentId, notifierId int64, contextChannel *socialapimodels.Channel) {
	isParticipant, err := isParticipant(notifierId, contextChannel)
	if err != nil {
		n.log.Error("Could not check participation info for user %d: %s", notifierId, err)
		return
	}

	// when mentioned user does not exist within the group, do not send notification
	if !isParticipant {
		return
	}

	notification := prepareActiveNotification(contentId, notifierId)
	notification.ContextChannelId = contextChannel.Id
	if err := notification.Upsert(); err != nil {
		n.log.Error("An error occurred while notifying user %d: %s", notification.AccountId, err.Error())
	}
}

func isParticipant(accountId int64, c *socialapimodels.Channel) (bool, error) {
	cp := socialapimodels.NewChannelParticipant()
	cp.ChannelId = c.Id

	return cp.IsParticipant(accountId)
}

func (n *Controller) notifyOnce(contentId, notifierId int64) {
	notification := prepareActiveNotification(contentId, notifierId)
	if err := notification.Create(); err != nil {
		n.log.Error("An error occurred while notifying user %d: %s", notification.AccountId, err.Error())
	}
}

func prepareActiveNotification(contentId, notifierId int64) *models.Notification {
	notification := buildNotification(contentId, notifierId, time.Now())
	notification.ActivatedAt = time.Now()

	return notification
}

func (n *Controller) subscribe(contentId, notifierId int64, subscribedAt time.Time, c *socialapimodels.Channel) {
	notification := buildNotification(contentId, notifierId, subscribedAt)
	notification.SubscribeOnly = true
	notification.ContextChannelId = c.Id
	if err := notification.Create(); err != nil {
		n.log.Error("An error occurred while subscribing user %d: %s", notification.AccountId, err.Error())
	}
}

func buildNotification(contentId, notifierId int64, subscribedAt time.Time) *models.Notification {
	notification := models.NewNotification()
	notification.NotificationContentId = contentId
	notification.AccountId = notifierId
	notification.SubscribedAt = subscribedAt

	return notification
}

func (n *Controller) CreateInteractionNotification(i *socialapimodels.Interaction) error {
	cm := socialapimodels.NewChannelMessage()
	if err := cm.ById(i.MessageId); err != nil {
		return err
	}

	// user likes her own message, so we bypass notification
	if cm.AccountId == i.AccountId {
		return nil
	}

	groupChannel, err := cm.FetchParentChannel()
	if err != nil {
		return err
	}

	isParticipant, err := isParticipant(cm.AccountId, groupChannel)
	if err != nil {
		return err
	}

	if !isParticipant {
		return nil
	}

	in := models.NewInteractionNotification(i.TypeConstant)
	if cm.TypeConstant == socialapimodels.ChannelMessage_TYPE_POST {
		in.TargetId = i.MessageId
	} else {
		mr := socialapimodels.NewMessageReply()
		mr.ReplyId = i.MessageId

		cm, err := mr.FetchParent()
		if err != nil {
			return err
		}

		in.TargetId = cm.Id
	}

	in.MessageId = i.MessageId
	in.NotifierId = i.AccountId
	nc, err := models.CreateNotificationContent(in)
	if err != nil {
		return err
	}

	notification := models.NewNotification()
	notification.NotificationContentId = nc.Id
	notification.AccountId = cm.AccountId // notify message owner
	notification.ActivatedAt = time.Now() // enables notification immediately
	notification.ContextChannelId = groupChannel.Id
	if err = notification.Upsert(); err != nil {
		return fmt.Errorf("An error occurred while notifying user %d: %s", cm.AccountId, err.Error())
	}

	return nil
}

func filterRepliers(repliers, mentionedUsers []int64) []int64 {
	mentionMap := map[int64]struct{}{}
	flattened := make([]int64, 0)
	if len(mentionedUsers) == 0 {
		return repliers
	}

	for _, user := range mentionedUsers {
		mentionMap[user] = struct{}{}
	}

	for _, replier := range repliers {
		if _, ok := mentionMap[replier]; !ok {
			flattened = append(flattened, replier)
		}
	}

	return flattened
}
<commit_msg>notification: add one liner participant check<commit_after>package notification

import (
	"fmt"
	socialapimodels "socialapi/models"
	"socialapi/workers/notification/models"
	"time"

	"github.com/koding/logging"
	"github.com/koding/rabbitmq"
	"github.com/streadway/amqp"
)

const (
	NOTIFICATION_TYPE_SUBSCRIBE   = "subscribe"
	NOTIFICATION_TYPE_UNSUBSCRIBE = "unsubscribe"
)

type Controller struct {
	log             logging.Logger
	rmqConn         *amqp.Connection
	notifierRmqConn *amqp.Connection
}

func (n *Controller) DefaultErrHandler(delivery amqp.Delivery, err error) bool {
	n.log.Error("an error occurred: %s", err)
	delivery.Ack(false)

	return false
}

func New(rmq *rabbitmq.RabbitMQ, log logging.Logger) *Controller {
	return &Controller{
		log:     log,
		rmqConn: rmq.Conn(),
	}
}

// CreateReplyNotification notifies main thread owner.
func (n *Controller) CreateReplyNotification(mr *socialapimodels.MessageReply) error {
	// fetch replier
	reply := socialapimodels.NewChannelMessage()
	if err := reply.ById(mr.ReplyId); err != nil {
		return err
	}

	cm := socialapimodels.NewChannelMessage()
	// notify message owner
	if err := cm.ById(mr.MessageId); err != nil {
		return err
	}

	if cm.TypeConstant != socialapimodels.ChannelMessage_TYPE_POST {
		return nil
	}

	rn := models.NewReplyNotification()
	rn.TargetId = mr.MessageId
	rn.NotifierId = reply.AccountId
	rn.MessageId = reply.Id

	subscribedAt := time.Now()

	nc, err := models.CreateNotificationContent(rn)
	if err != nil {
		return err
	}

	groupChannel, err := reply.FetchParentChannel()
	if err != nil {
		return err
	}

	// if it is not notifier's own message then add replier to subscribers
	// for further reply notifications
	if cm.AccountId != rn.NotifierId {
		n.subscribe(nc.Id, cm.AccountId, subscribedAt, groupChannel)
	}

	notifiedUsers, err := rn.GetNotifiedUsers(nc.Id)
	if err != nil {
		return err
	}

	mentionedUsers, err := n.CreateMentionNotification(reply, cm.Id)
	if err != nil {
		return err
	}

	// if a user is already subscribed to a post, and also mentioned in a reply
	// just send mention notification -no need for reply notification.
	notifiedUsers = filterRepliers(notifiedUsers, mentionedUsers)

	notifierSubscribed := false
	for _, recipient := range notifiedUsers {
		if recipient == rn.NotifierId {
			notifierSubscribed = true
		}
		n.notify(nc.Id, recipient, groupChannel)
	}

	if !notifierSubscribed {
		n.subscribe(nc.Id, rn.NotifierId, subscribedAt, groupChannel)
	}

	return nil
}

// func (n *Controller) UnsubscribeMessage(data *socialapimodels.ChannelMessageList) error {
// 	return subscription(data, NOTIFICATION_TYPE_UNSUBSCRIBE)
// }

//func subscription(cml *socialapimodels.ChannelMessageList, typeConstant string) error {
//    c := socialapimodels.NewChannel()
//    if err := c.ById(cml.ChannelId); err != nil {
//        return err
//    }

//    if c.TypeConstant != socialapimodels.Channel_TYPE_PINNED_ACTIVITY {
//        return nil
//    }

//     user pinned (followed) a message
//    nc := models.NewNotificationContent()
//    nc.TargetId = cml.MessageId

//    n := models.NewNotification()
//    n.AccountId = c.CreatorId

//    switch typeConstant {
//    case NOTIFICATION_TYPE_SUBSCRIBE:
//        return n.Subscribe(nc)
//    case NOTIFICATION_TYPE_UNSUBSCRIBE:
//        return n.Unsubscribe(nc)
//    }

//    return nil
//}

func (n *Controller) HandleMessage(cm *socialapimodels.ChannelMessage) error {
	switch cm.TypeConstant {
	case socialapimodels.ChannelMessage_TYPE_POST:
		_, err := n.CreateMentionNotification(cm, cm.Id)
		return err
	default:
		return nil
	}
}

func (n *Controller) DeleteNotification(cm *socialapimodels.ChannelMessage) error {
	nc := models.NewNotificationContent()
	contentIds, err := nc.FetchIdsByTargetId(cm.Id)
	if err != nil {
		return err
	}

	if len(contentIds) == 0 {
		return nil
	}

	return models.NewNotification().HideByContentIds(contentIds)
}

// CreateMentionNotification creates mention notifications for the related channel messages
func (n *Controller) CreateMentionNotification(reply *socialapimodels.ChannelMessage, targetId int64) ([]int64, error) {
	mentionedUserIds := make([]int64, 0)
	usernames := reply.GetMentionedUsernames()

	// message does not contain any mentioned users
	if len(usernames) == 0 {
		return mentionedUserIds, nil
	}

	mentionedUsers, err := socialapimodels.FetchAccountsByNicks(usernames)
	if err != nil {
		return nil, err
	}

	groupChannel, err := reply.FetchParentChannel()
	if err != nil {
		return nil, err
	}

	for _, mentionedUser := range mentionedUsers {
		// if user mentions herself ignore it
		if mentionedUser.Id == reply.AccountId {
			continue
		}
		mn := models.NewMentionNotification()
		mn.TargetId = targetId
		mn.MessageId = reply.Id
		mn.NotifierId = reply.AccountId
		nc, err := models.CreateNotificationContent(mn)
		if err != nil {
			return nil, err
		}

		n.instantNotify(nc.Id, mentionedUser.Id, groupChannel)

		mentionedUserIds = append(mentionedUserIds, mentionedUser.Id)
	}

	return mentionedUserIds, nil
}

func (n *Controller) notify(contentId, notifierId int64, contextChannel *socialapimodels.Channel) {
	isParticipant, err := isParticipant(notifierId, contextChannel)
	if err != nil {
		n.log.Error("Could not check participation info for user %d: %s", notifierId, err)
		return
	}

	// this can be a message of an old participant, and we do not want to send
	// further notifications
	if !isParticipant {
		return
	}

	notification := buildNotification(contentId, notifierId, time.Now())
	notification.ContextChannelId = contextChannel.Id
	if err := notification.Upsert(); err != nil {
		n.log.Error("An error occurred while notifying user %d: %s", notification.AccountId, err.Error())
	}
}

func (n *Controller) instantNotify(contentId, notifierId int64, contextChannel *socialapimodels.Channel) {
	isParticipant, err := isParticipant(notifierId, contextChannel)
	if err != nil {
		n.log.Error("Could not check participation info for user %d: %s", notifierId, err)
		return
	}

	// when mentioned user does not exist within the group, do not send notification
	if !isParticipant {
		return
	}

	notification := prepareActiveNotification(contentId, notifierId)
	notification.ContextChannelId = contextChannel.Id
	if err := notification.Upsert(); err != nil {
		n.log.Error("An error occurred while notifying user %d: %s", notification.AccountId, err.Error())
	}
}

func isParticipant(accountId int64, c *socialapimodels.Channel) (bool, error) {
	cp := socialapimodels.NewChannelParticipant()
	cp.ChannelId = c.Id

	return cp.IsParticipant(accountId)
}

func (n *Controller) notifyOnce(contentId, notifierId int64) {
	notification := prepareActiveNotification(contentId, notifierId)
	if err := notification.Create(); err != nil {
		n.log.Error("An error occurred while notifying user %d: %s", notification.AccountId, err.Error())
	}
}

func prepareActiveNotification(contentId, notifierId int64) *models.Notification {
	notification := buildNotification(contentId, notifierId, time.Now())
	notification.ActivatedAt = time.Now()

	return notification
}

func (n *Controller) subscribe(contentId, notifierId int64, subscribedAt time.Time, c *socialapimodels.Channel) {
	notification := buildNotification(contentId, notifierId, subscribedAt)
	notification.SubscribeOnly = true
	notification.ContextChannelId = c.Id
	if err := notification.Create(); err != nil {
		n.log.Error("An error occurred while subscribing user %d: %s", notification.AccountId, err.Error())
	}
}

func buildNotification(contentId, notifierId int64, subscribedAt time.Time) *models.Notification {
	notification := models.NewNotification()
	notification.NotificationContentId = contentId
	notification.AccountId = notifierId
	notification.SubscribedAt = subscribedAt

	return notification
}

func (n *Controller) CreateInteractionNotification(i *socialapimodels.Interaction) error {
	cm := socialapimodels.NewChannelMessage()
	if err := cm.ById(i.MessageId); err != nil {
		return err
	}

	// user likes her own message, so we bypass notification
	if cm.AccountId == i.AccountId {
		return nil
	}

	groupChannel, err := cm.FetchParentChannel()
	if err != nil {
		return err
	}

	isParticipant, err := isParticipant(cm.AccountId, groupChannel)
	if err != nil || !isParticipant {
		return err
	}

	in := models.NewInteractionNotification(i.TypeConstant)
	if cm.TypeConstant == socialapimodels.ChannelMessage_TYPE_POST {
		in.TargetId = i.MessageId
	} else {
		mr := socialapimodels.NewMessageReply()
		mr.ReplyId = i.MessageId

		cm, err := mr.FetchParent()
		if err != nil {
			return err
		}

		in.TargetId = cm.Id
	}

	in.MessageId = i.MessageId
	in.NotifierId = i.AccountId
	nc, err := models.CreateNotificationContent(in)
	if err != nil {
		return err
	}

	notification := models.NewNotification()
	notification.NotificationContentId = nc.Id
	notification.AccountId = cm.AccountId // notify message owner
	notification.ActivatedAt = time.Now() // enables notification immediately
	notification.ContextChannelId = groupChannel.Id
	if err = notification.Upsert(); err != nil {
		return fmt.Errorf("An error occurred while notifying user %d: %s", cm.AccountId, err.Error())
	}

	return nil
}

func filterRepliers(repliers, mentionedUsers []int64) []int64 {
	mentionMap := map[int64]struct{}{}
	flattened := make([]int64, 0)
	if len(mentionedUsers) == 0 {
		return repliers
	}

	for _, user := range mentionedUsers {
		mentionMap[user] = struct{}{}
	}

	for _, replier := range repliers {
		if _, ok := mentionMap[replier]; !ok {
			flattened = append(flattened, replier)
		}
	}

	return flattened
}
<|endoftext|>


<commit_before>package metrics

import (
	"net/http"

	"github.com/argoproj/argo-cd/util/git"

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

type MetricsServer struct {
	handler           http.Handler
	gitRequestCounter *prometheus.CounterVec
	gitClientFactory  git.ClientFactory
}

type GitRequestType string

const (
	GitRequestTypeLsRemote = "ls-remote"
	GitRequestTypeFetch    = "fetch"
)

// NewMetricsServer returns a new prometheus server which collects application metrics
func NewMetricsServer(gitClientFactory git.ClientFactory) *MetricsServer {
	registry := prometheus.NewRegistry()
	registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
	registry.MustRegister(prometheus.NewGoCollector())

	gitRequestCounter := prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "argocd_git_ls_remote_total",
			Help: "Number of git requests performed by repo server",
		},
		[]string{"repo", "request_type"},
	)
	registry.MustRegister(gitRequestCounter)

	return &MetricsServer{
		gitClientFactory:  gitClientFactory,
		handler:           promhttp.HandlerFor(registry, promhttp.HandlerOpts{}),
		gitRequestCounter: gitRequestCounter,
	}
}

func (m *MetricsServer) GetHandler() http.Handler {
	return m.handler
}

// IncGitRequest increments the git requests counter
func (m *MetricsServer) IncGitRequest(repo string, requestType GitRequestType) {
	m.gitRequestCounter.WithLabelValues(repo, string(requestType)).Inc()
}

func (m *MetricsServer) NewClient(repoURL string, path string, creds git.Creds, insecureIgnoreHostKey bool) (git.Client, error) {
	client, err := m.gitClientFactory.NewClient(repoURL, path, creds, insecureIgnoreHostKey)
	if err != nil {
		return nil, err
	}
	return wrapGitClient(repoURL, m, client), nil
}
<commit_msg>Change git prometheus counter name (#1949)<commit_after>package metrics

import (
	"net/http"

	"github.com/argoproj/argo-cd/util/git"

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

type MetricsServer struct {
	handler           http.Handler
	gitRequestCounter *prometheus.CounterVec
	gitClientFactory  git.ClientFactory
}

type GitRequestType string

const (
	GitRequestTypeLsRemote = "ls-remote"
	GitRequestTypeFetch    = "fetch"
)

// NewMetricsServer returns a new prometheus server which collects application metrics
func NewMetricsServer(gitClientFactory git.ClientFactory) *MetricsServer {
	registry := prometheus.NewRegistry()
	registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
	registry.MustRegister(prometheus.NewGoCollector())

	gitRequestCounter := prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "argocd_git_request_total",
			Help: "Number of git requests performed by repo server",
		},
		[]string{"repo", "request_type"},
	)
	registry.MustRegister(gitRequestCounter)

	return &MetricsServer{
		gitClientFactory:  gitClientFactory,
		handler:           promhttp.HandlerFor(registry, promhttp.HandlerOpts{}),
		gitRequestCounter: gitRequestCounter,
	}
}

func (m *MetricsServer) GetHandler() http.Handler {
	return m.handler
}

// IncGitRequest increments the git requests counter
func (m *MetricsServer) IncGitRequest(repo string, requestType GitRequestType) {
	m.gitRequestCounter.WithLabelValues(repo, string(requestType)).Inc()
}

func (m *MetricsServer) NewClient(repoURL string, path string, creds git.Creds, insecureIgnoreHostKey bool) (git.Client, error) {
	client, err := m.gitClientFactory.NewClient(repoURL, path, creds, insecureIgnoreHostKey)
	if err != nil {
		return nil, err
	}
	return wrapGitClient(repoURL, m, client), nil
}
<|endoftext|>


<commit_before>package disgo

import (
	"bytes"
	"compress/zlib"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/slf4go/logger"
)

type shard struct {
	// Parent session
	session *Session

	// Websocket information used for identification and reconnection
	webSocket *websocket.Conn
	shard     int
	sessionID string
	sequence  int
	heartbeat int

	// Mutex locks, reconnect to make sure there is only 1 process reconnecting and concurrent read/write accesses on the socket
	readLock, writeLock sync.Mutex
	reconnectLock       sync.RWMutex
	isReconnecting      bool

	// Channels to pass around messages
	closeMessage chan int
	stopListen   chan bool
	stopRead     chan bool
}

func connectShard(session *Session, shardNum int) (*shard, error) {
	conn, _, err := websocket.DefaultDialer.Dial(session.wsUrl, http.Header{})
	if err != nil {
		return nil, err
	}

	s := &shard{
		session:      session,
		webSocket:    conn,
		shard:        shardNum,
		closeMessage: make(chan int, 1),
		stopListen:   make(chan bool),
		stopRead:     make(chan bool),
	}

	if err = s.handshake(); err != nil {
		return nil, err
	}

	return s, nil
}

func (s *shard) handshake() error {
	s.webSocket.SetCloseHandler(s.onClose)

	helloFrame, err := s.readFrame()
	if err != nil {
		return err
	} else if helloFrame.Op != opHello {
		return errors.New("First frame sent from the Discord Gateway is not hello")
	}

	hello := helloPayload{}
	if err = json.Unmarshal(helloFrame.Data, &hello); err != nil {
		return err
	}

	logger.Debugf("Connected to Discord servers: %s", strings.Join(hello.Servers, ", "))
	logger.Debugf("Setting up a heartbeat interval of %d ms", hello.HeartbeatInterval)
	s.heartbeat = hello.HeartbeatInterval

	if err = s.identify(); err != nil {
		return err
	}

	go s.mainLoop()
	return nil
}

func (s *shard) identify() error {
	if s.sessionID != "" {
		logger.Debugf("Resuming connection starting at sequence %d.", s.sequence)
		s.sendFrame(&gatewayFrame{opResume, resumePayload{
			Token:     s.session.token,
			SessionID: s.sessionID,
			Sequence:  s.sequence,
		}})
	} else {
		logger.Debugf("Identifying to websocket")
		s.sendFrame(&gatewayFrame{opIdentify, identifyPayload{
			Token:          s.session.token,
			Compress:       true,
			LargeThreshold: 250,
			Shard:          [2]int{s.shard, cap(s.session.shards)},
			Properties: propertiesPayload{
				OS:      runtime.GOOS,
				Browser: "DisGo",
				Device:  "DisGo",
			},
		}})
	}

	for {
		frame, err := s.readFrame()
		if err != nil {
			return err
		}

		if frame.Op == opDispatch {
			switch frame.EventName {
			case "READY":
				ready := ReadyEvent{}
				if err := json.Unmarshal(frame.Data, &ready); err != nil {
					return err
				}

				s.sessionID = ready.SessionID

				fallthrough
			case "RESUMED":
				s.session.dispatchEvent(frame)
				return nil // Break out of the loop, we have what we want
			default:
				s.session.dispatchEvent(frame) // Nope, resume action, let's wait for more frames
			}
		} else if frame.Op == opInvalidSession {
			s.sessionID = "" // Invalidate session and retry
			s.identify()
			return nil
		} else {
			return fmt.Errorf("Unexpected opCode received from Discord: %d", frame.Op)
		}
	}
}

func (s *shard) mainLoop() {
	logger.Debugf("Starting main loop for shard [%d/%d]", s.shard+1, cap(s.session.shards))
	defer logger.Debugf("Exiting main loop for shard [%d/%d]", s.shard+1, cap(s.session.shards))

	heartbeat := time.NewTicker(time.Duration(s.heartbeat) * time.Millisecond)
	defer heartbeat.Stop()
	sentHeartBeat := false

	reader := make(chan *receivedFrame, 1)
	go s.readWebSocket(reader)

	for {
		select {
		case <-heartbeat.C:
			if !sentHeartBeat {
				go s.sendFrame(&gatewayFrame{opHeartbeat, s.sequence})
				sentHeartBeat = true
			} else {
				go s.disconnect(websocket.CloseAbnormalClosure, "Did not respond to previous heartbeat")
			}
		case <-s.stopListen:
			return
		case frame := <-reader:
			switch frame.Op {
			case opHeartbeat:
				s.sendFrame(&gatewayFrame{Op: opHeartbeatAck})
			case opHeartbeatAck:
				sentHeartBeat = false
			case opReconnect:
				s.disconnect(websocket.CloseNormalClosure, "op Reconnect")
			case opDispatch:
				s.session.dispatchEvent(frame)
			default:
				logger.Errorf("Unexpected opCode received: %d", frame.Op)
			}
		}
	}
}

func (s *shard) sendFrame(frame *gatewayFrame) {
	// Resume will only be sent within the reconnect lock, we don't relock to prevent a deadlock
	if frame.Op != opResume {
		s.reconnectLock.Lock()
		defer s.reconnectLock.Unlock()
	}

	s.writeLock.Lock()
	defer s.writeLock.Unlock()

	logger.Debugf("Sending frame with opCode: %d", frame.Op)
	s.webSocket.WriteJSON(frame)
}

func (s *shard) readWebSocket(reader chan *receivedFrame) {
	logger.Debugf("Starting read loop for shard [%d/%d]", s.shard+1, cap(s.session.shards))
	defer logger.Debugf("Exiting read loop for shard [%d/%d]", s.shard+1, cap(s.session.shards))

	for {
		select {
		case <-s.stopRead:
			return
		default:
			frame, err := s.readFrame()
			if err != nil {
				if !s.session.isShuttingDown() {
					if closeErr, isClose := err.(*websocket.CloseError); isClose {
						s.closeMessage <- closeErr.Code
					}
					logger.ErrorE(err)
					go s.disconnect(websocket.CloseAbnormalClosure, err.Error())
				}
				<-s.stopRead // Wait for the connection to be closed
				return
			}

			reader <- frame
		}
	}

}

func (s *shard) readFrame() (*receivedFrame, error) {
	s.readLock.Lock()
	defer s.readLock.Unlock()

	logger.Tracef("Shard.readFrame() called")
	msgType, msg, err := s.webSocket.ReadMessage()
	if err != nil {
		return nil, err
	}

	var reader io.Reader
	reader = bytes.NewBuffer(msg)

	if msgType == websocket.BinaryMessage {
		zReader, err := zlib.NewReader(reader)
		if err != nil {
			return nil, err
		}

		defer zReader.Close()

		reader = zReader
	}

	frame := receivedFrame{Sequence: -1}
	json.NewDecoder(reader).Decode(&frame)

	logger.Debugf("Received frame with opCode: %d", frame.Op)

	if frame.Sequence != -1 {
		logger.Tracef("Last sequence received set to %d.", frame.Sequence)
		s.sequence = frame.Sequence
	}
	return &frame, nil
}

func (s *shard) startReconnect() {
	s.reconnectLock.RLock()
	if s.isReconnecting {
		return
	}
	s.reconnectLock.RUnlock()

	s.reconnectLock.Lock()
	s.isReconnecting = true
	go s.reconnect()
}

func (s *shard) reconnect() {
	if !s.session.isShuttingDown() {
		logger.Noticef("Reconnecting shard [%d/%d]", s.shard+1, cap(s.session.shards))
		conn, _, err := websocket.DefaultDialer.Dial(s.session.wsUrl, http.Header{})

		if err == nil {
			s.webSocket = conn
			err = s.handshake()

			if err != nil {
				conn.Close()
			}
		}

		if err != nil {
			logger.ErrorE(err)
			time.Sleep(1 * time.Second)
			go s.reconnect()
		} else {
			s.isReconnecting = false
			s.reconnectLock.Unlock()
		}
	}
}

func (s *shard) onClose(code int, text string) error {
	logger.Debugf("Received Close Frame from Discord. Code: %d. Text: %s", code, text)
	s.closeMessage <- code
	s.startReconnect()
	return nil
}

func (s *shard) disconnect(code int, text string) {
	logger.Tracef("Shard.disconnect() called")
	s.stopListen <- true

	s.writeLock.Lock()
	err := s.webSocket.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(code, text))
	s.writeLock.Unlock()
	if err != nil {
		logger.ErrorE(err)
	}

	select {
	case code := <-s.closeMessage:
		logger.Debugf("Discord connection closed with code %d", code)
	case <-time.After(1 * time.Second):
		logger.Warn("Discord did not reply to the close message, force-closing connection.")
	}

	s.webSocket.Close()
	s.stopRead <- true

	s.startReconnect()
}
<commit_msg>Removed RWMutex usage<commit_after>package disgo

import (
	"bytes"
	"compress/zlib"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/slf4go/logger"
)

type shard struct {
	// Parent session
	session *Session

	// Websocket information used for identification and reconnection
	webSocket *websocket.Conn
	shard     int
	sessionID string
	sequence  int
	heartbeat int

	// Mutex locks, reconnect to make sure there is only 1 process reconnecting and concurrent read/write accesses on the socket
	readLock, writeLock sync.Mutex
	reconnectLock       sync.Mutex
	isReconnecting      bool

	// Channels to pass around messages
	closeMessage chan int
	stopListen   chan bool
	stopRead     chan bool
}

func connectShard(session *Session, shardNum int) (*shard, error) {
	conn, _, err := websocket.DefaultDialer.Dial(session.wsUrl, http.Header{})
	if err != nil {
		return nil, err
	}

	s := &shard{
		session:      session,
		webSocket:    conn,
		shard:        shardNum,
		closeMessage: make(chan int, 1),
		stopListen:   make(chan bool),
		stopRead:     make(chan bool),
	}

	if err = s.handshake(); err != nil {
		return nil, err
	}

	return s, nil
}

func (s *shard) handshake() error {
	s.webSocket.SetCloseHandler(s.onClose)

	helloFrame, err := s.readFrame()
	if err != nil {
		return err
	} else if helloFrame.Op != opHello {
		return errors.New("First frame sent from the Discord Gateway is not hello")
	}

	hello := helloPayload{}
	if err = json.Unmarshal(helloFrame.Data, &hello); err != nil {
		return err
	}

	logger.Debugf("Connected to Discord servers: %s", strings.Join(hello.Servers, ", "))
	logger.Debugf("Setting up a heartbeat interval of %d ms", hello.HeartbeatInterval)
	s.heartbeat = hello.HeartbeatInterval

	if err = s.identify(); err != nil {
		return err
	}

	go s.mainLoop()
	return nil
}

func (s *shard) identify() error {
	if s.sessionID != "" {
		logger.Debugf("Resuming connection starting at sequence %d.", s.sequence)
		s.sendFrame(&gatewayFrame{opResume, resumePayload{
			Token:     s.session.token,
			SessionID: s.sessionID,
			Sequence:  s.sequence,
		}})
	} else {
		logger.Debugf("Identifying to websocket")
		s.sendFrame(&gatewayFrame{opIdentify, identifyPayload{
			Token:          s.session.token,
			Compress:       true,
			LargeThreshold: 250,
			Shard:          [2]int{s.shard, cap(s.session.shards)},
			Properties: propertiesPayload{
				OS:      runtime.GOOS,
				Browser: "DisGo",
				Device:  "DisGo",
			},
		}})
	}

	for {
		frame, err := s.readFrame()
		if err != nil {
			return err
		}

		if frame.Op == opDispatch {
			switch frame.EventName {
			case "READY":
				ready := ReadyEvent{}
				if err := json.Unmarshal(frame.Data, &ready); err != nil {
					return err
				}

				s.sessionID = ready.SessionID

				fallthrough
			case "RESUMED":
				s.session.dispatchEvent(frame)
				return nil // Break out of the loop, we have what we want
			default:
				s.session.dispatchEvent(frame) // Nope, resume action, let's wait for more frames
			}
		} else if frame.Op == opInvalidSession {
			s.sessionID = "" // Invalidate session and retry
			s.identify()
			return nil
		} else {
			return fmt.Errorf("Unexpected opCode received from Discord: %d", frame.Op)
		}
	}
}

func (s *shard) mainLoop() {
	logger.Debugf("Starting main loop for shard [%d/%d]", s.shard+1, cap(s.session.shards))
	defer logger.Debugf("Exiting main loop for shard [%d/%d]", s.shard+1, cap(s.session.shards))

	heartbeat := time.NewTicker(time.Duration(s.heartbeat) * time.Millisecond)
	defer heartbeat.Stop()
	sentHeartBeat := false

	reader := make(chan *receivedFrame, 1)
	go s.readWebSocket(reader)

	for {
		select {
		case <-heartbeat.C:
			if !sentHeartBeat {
				go s.sendFrame(&gatewayFrame{opHeartbeat, s.sequence})
				sentHeartBeat = true
			} else {
				go s.disconnect(websocket.CloseAbnormalClosure, "Did not respond to previous heartbeat")
			}
		case <-s.stopListen:
			return
		case frame := <-reader:
			switch frame.Op {
			case opHeartbeat:
				s.sendFrame(&gatewayFrame{Op: opHeartbeatAck})
			case opHeartbeatAck:
				sentHeartBeat = false
			case opReconnect:
				s.disconnect(websocket.CloseNormalClosure, "op Reconnect")
			case opDispatch:
				s.session.dispatchEvent(frame)
			default:
				logger.Errorf("Unexpected opCode received: %d", frame.Op)
			}
		}
	}
}

func (s *shard) sendFrame(frame *gatewayFrame) {
	// Resume will only be sent within the reconnect lock, we don't relock to prevent a deadlock
	if frame.Op != opResume {
		s.reconnectLock.Lock()
		defer s.reconnectLock.Unlock()
	}

	s.writeLock.Lock()
	defer s.writeLock.Unlock()

	logger.Debugf("Sending frame with opCode: %d", frame.Op)
	s.webSocket.WriteJSON(frame)
}

func (s *shard) readWebSocket(reader chan *receivedFrame) {
	logger.Debugf("Starting read loop for shard [%d/%d]", s.shard+1, cap(s.session.shards))
	defer logger.Debugf("Exiting read loop for shard [%d/%d]", s.shard+1, cap(s.session.shards))

	for {
		select {
		case <-s.stopRead:
			return
		default:
			frame, err := s.readFrame()
			if err != nil {
				if !s.session.isShuttingDown() {
					if closeErr, isClose := err.(*websocket.CloseError); isClose {
						s.closeMessage <- closeErr.Code
					}
					logger.ErrorE(err)
					go s.disconnect(websocket.CloseAbnormalClosure, err.Error())
				}
				<-s.stopRead // Wait for the connection to be closed
				return
			}

			reader <- frame
		}
	}

}

func (s *shard) readFrame() (*receivedFrame, error) {
	s.readLock.Lock()
	defer s.readLock.Unlock()

	logger.Tracef("Shard.readFrame() called")
	msgType, msg, err := s.webSocket.ReadMessage()
	if err != nil {
		return nil, err
	}

	var reader io.Reader
	reader = bytes.NewBuffer(msg)

	if msgType == websocket.BinaryMessage {
		zReader, err := zlib.NewReader(reader)
		if err != nil {
			return nil, err
		}

		defer zReader.Close()

		reader = zReader
	}

	frame := receivedFrame{Sequence: -1}
	json.NewDecoder(reader).Decode(&frame)

	logger.Debugf("Received frame with opCode: %d", frame.Op)

	if frame.Sequence != -1 {
		logger.Tracef("Last sequence received set to %d.", frame.Sequence)
		s.sequence = frame.Sequence
	}
	return &frame, nil
}

func (s *shard) startReconnect() {
	if s.isReconnecting {
		return
	}

	s.reconnectLock.Lock()
	s.isReconnecting = true
	go s.reconnect()
}

func (s *shard) reconnect() {
	if !s.session.isShuttingDown() {
		logger.Noticef("Reconnecting shard [%d/%d]", s.shard+1, cap(s.session.shards))
		conn, _, err := websocket.DefaultDialer.Dial(s.session.wsUrl, http.Header{})

		if err == nil {
			s.webSocket = conn
			err = s.handshake()

			if err != nil {
				conn.Close()
			}
		}

		if err != nil {
			logger.ErrorE(err)
			time.Sleep(1 * time.Second)
			go s.reconnect()
		} else {
			s.isReconnecting = false
			s.reconnectLock.Unlock()
		}
	}
}

func (s *shard) onClose(code int, text string) error {
	logger.Debugf("Received Close Frame from Discord. Code: %d. Text: %s", code, text)
	s.closeMessage <- code
	s.startReconnect()
	return nil
}

func (s *shard) disconnect(code int, text string) {
	logger.Tracef("Shard.disconnect() called")
	s.stopListen <- true

	s.writeLock.Lock()
	err := s.webSocket.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(code, text))
	s.writeLock.Unlock()
	if err != nil {
		logger.ErrorE(err)
	}

	select {
	case code := <-s.closeMessage:
		logger.Debugf("Discord connection closed with code %d", code)
	case <-time.After(1 * time.Second):
		logger.Warn("Discord did not reply to the close message, force-closing connection.")
	}

	s.webSocket.Close()
	s.stopRead <- true

	s.startReconnect()
}
<|endoftext|>


<commit_before>package lib

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/jh-bate/fantail-bot/Godeps/_workspace/src/github.com/tucnak/telebot"
)

const (
	chat_cmd, remind_cmd, reminders_cmd, notes_cmd = "/chat", "/remind", "/reminders", "/notes"

	free_form      = "JUST_SAYING"
	default_script = "default"
)

type QProcess struct {
	Details *Details
	lang    struct {
		questions `json:"QandA"`
	}
	next *Question
	done bool
}

func NewQProcess(d *Details) *QProcess {
	return &QProcess{Details: d}
}

func (this *QProcess) Run(input <-chan telebot.Message) {
	for msg := range input {
		this.Details.User = msg.Chat
		this.quickWin(msg).determineScript(msg).findNextQuestion(msg).andAsk()
	}
}

func (this *QProcess) quickWin(msg telebot.Message) *QProcess {

	/*if hasSubmisson(msg.Text, remind_cmd) {
		log.Println("making submisson ", msg.Text)
		this.Details.saveAsReminder(msg)
	} else */
	if isCmd(msg.Text, reminders_cmd) {
		log.Println("showing reminders ", msg.Text)

		r := this.Details.getReminders(fmt.Sprintf("%d", this.Details.User.ID))
		for i := range r {
			if r[i].RemindToday() {
				this.Details.send(r[i].Text)

			}
		}
	} else if isCmd(msg.Text, notes_cmd) {
		log.Println("showing notes ", msg.Text)

		r := this.Details.getNotes(fmt.Sprintf("%d", this.Details.User.ID))
		for i := range r {
			if r[i].IsCurrent() {
				this.Details.send(r[i].Text)
			}
		}
	}
	return this
}

func (this *QProcess) loadScript(scriptName string) {
	file, err := os.Open(fmt.Sprintf("./config/%s.json", scriptName))
	if err != nil {
		log.Panic("could not load QandA language file ", err.Error())
	}
	err = json.NewDecoder(file).Decode(&this.lang)
	if err != nil {
		log.Panic("could not decode QandA ", err.Error())
	}
}

func (this *QProcess) determineScript(msg telebot.Message) *QProcess {

	if isCmd(msg.Text, chat_cmd, notes_cmd) { //remind_cmd, chat_cmd, reminders_cmd, notes_cmd) {
		words := strings.Fields(msg.Text)
		scriptName := strings.SplitAfter(words[0], "/")[1]
		this.loadScript(scriptName)
	}
	return this
}

func (this *QProcess) findNextQuestion(msg telebot.Message) *QProcess {
	this.next = nil

	/*if this.done || this.lang.questions == nil {
		log.Println("unknown so will save as", free_form)
		this.Details.save(msg, free_form)
		//load default and start at the beginning
		this.loadScript(default_script)
		this.next = this.lang.questions[0]
		this.done = false
		return this
	} else */
	if isCmd(msg.Text, chat_cmd, notes_cmd) { //|| hasSubmisson(msg.Text, remind_cmd) {
		//start at the beginning
		this.next = this.lang.questions[0]
		//this.done = false
		return this
	} else {
		//find the next question
		searched := false
		for i := range this.lang.questions {
			searched = true
			log.Println("looking next q ...")
			for a := range this.lang.questions[i].RelatesTo.Answers {
				if this.lang.questions[i].RelatesTo.Answers[a] == msg.Text {
					//was the answer a remainder to save?
					if this.lang.questions[i].RelatesTo.Save {
						this.Details.save(msg, chat_cmd, this.lang.questions[i].RelatesTo.SaveTag)
					}
					this.next = this.lang.questions[i]
					//this.done = false
					return this
				}
			}
		}
		if !searched {
			log.Println("was just saying so we are just saving")
			this.Details.save(msg, free_form)
			//load default and start at the beginning
			this.loadScript(default_script)
			this.next = this.lang.questions[0]
			//this.done = false
			return this
		}
	}
	log.Println("looks like we are all done!")
	//this.done = true
	return this
}

func (this *QProcess) makeKeyboard() Keyboard {
	keyboard := Keyboard{}
	for i := range this.next.PossibleAnswers {
		keyboard = append(keyboard, []string{this.next.PossibleAnswers[i]})
	}
	return keyboard
}

func (this *QProcess) andAsk() {
	if this.next != nil {
		//context
		for i := range this.next.Context {
			this.Details.send(this.next.Context[i])
		}
		//the actual question
		this.Details.sendWithKeyboard(this.next.QuestionText, this.makeKeyboard())
	}

	return
}
<commit_msg>fix so I can make any comment<commit_after>package lib

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/jh-bate/fantail-bot/Godeps/_workspace/src/github.com/tucnak/telebot"
)

const (
	chat_cmd, remind_cmd, reminders_cmd, notes_cmd = "/chat", "/remind", "/reminders", "/notes"

	free_form      = "JUST_SAYING"
	default_script = "default"
)

type QProcess struct {
	Details *Details
	lang    struct {
		questions `json:"QandA"`
	}
	next *Question
	done bool
}

func NewQProcess(d *Details) *QProcess {
	return &QProcess{Details: d}
}

func (this *QProcess) Run(input <-chan telebot.Message) {
	for msg := range input {
		this.Details.User = msg.Chat
		this.quickWin(msg).determineScript(msg).findNextQuestion(msg).andAsk()
	}
}

func (this *QProcess) quickWin(msg telebot.Message) *QProcess {

	/*if hasSubmisson(msg.Text, remind_cmd) {
		log.Println("making submisson ", msg.Text)
		this.Details.saveAsReminder(msg)
	} else */
	if isCmd(msg.Text, reminders_cmd) {
		log.Println("showing reminders ", msg.Text)

		r := this.Details.getReminders(fmt.Sprintf("%d", this.Details.User.ID))
		for i := range r {
			if r[i].RemindToday() {
				this.Details.send(r[i].Text)

			}
		}
	} else if isCmd(msg.Text, notes_cmd) {
		log.Println("showing notes ", msg.Text)

		r := this.Details.getNotes(fmt.Sprintf("%d", this.Details.User.ID))
		for i := range r {
			if r[i].IsCurrent() {
				this.Details.send(r[i].Text)
			}
		}
	}
	return this
}

func (this *QProcess) loadScript(scriptName string) {
	file, err := os.Open(fmt.Sprintf("./config/%s.json", scriptName))
	if err != nil {
		log.Panic("could not load QandA language file ", err.Error())
	}
	err = json.NewDecoder(file).Decode(&this.lang)
	if err != nil {
		log.Panic("could not decode QandA ", err.Error())
	}
}

func (this *QProcess) determineScript(msg telebot.Message) *QProcess {

	if isCmd(msg.Text, chat_cmd, notes_cmd) { //remind_cmd, chat_cmd, reminders_cmd, notes_cmd) {
		words := strings.Fields(msg.Text)
		scriptName := strings.SplitAfter(words[0], "/")[1]
		this.loadScript(scriptName)
	}
	return this
}

func (this *QProcess) findNextQuestion(msg telebot.Message) *QProcess {
	this.next = nil

	/*if this.done || this.lang.questions == nil {
		log.Println("unknown so will save as", free_form)
		this.Details.save(msg, free_form)
		//load default and start at the beginning
		this.loadScript(default_script)
		this.next = this.lang.questions[0]
		this.done = false
		return this
	} else */
	if isCmd(msg.Text, chat_cmd, notes_cmd) { //|| hasSubmisson(msg.Text, remind_cmd) {
		//start at the beginning
		this.next = this.lang.questions[0]
		//this.done = false
		return this
	} else {
		//find the next question
		searched := false
		for i := range this.lang.questions {
			log.Println("looking next q ...")
			for a := range this.lang.questions[i].RelatesTo.Answers {
				searched = true
				if this.lang.questions[i].RelatesTo.Answers[a] == msg.Text {
					//was the answer a remainder to save?
					if this.lang.questions[i].RelatesTo.Save {
						this.Details.save(msg, chat_cmd, this.lang.questions[i].RelatesTo.SaveTag)
					}
					this.next = this.lang.questions[i]
					//this.done = false
					return this
				}
			}
		}
		if !searched {
			log.Println("was just saying so we are just saving")
			this.Details.save(msg, free_form)
			//load default and start at the beginning
			this.loadScript(default_script)
			this.next = this.lang.questions[0]
			//this.done = false
			return this
		}
	}
	log.Println("looks like we are all done!")
	//this.done = true
	return this
}

func (this *QProcess) makeKeyboard() Keyboard {
	keyboard := Keyboard{}
	for i := range this.next.PossibleAnswers {
		keyboard = append(keyboard, []string{this.next.PossibleAnswers[i]})
	}
	return keyboard
}

func (this *QProcess) andAsk() {
	if this.next != nil {
		//context
		for i := range this.next.Context {
			this.Details.send(this.next.Context[i])
		}
		//the actual question
		this.Details.sendWithKeyboard(this.next.QuestionText, this.makeKeyboard())
	}

	return
}
<|endoftext|>