<commit_before>// +build !gtk_3_6,!gtk_3_8,!gtk_3_10,!gtk_3_12,!gtk_3_14,!gtk_3_16,!gtk_3_18,!gtk_3_20
// Supports building with gtk 3.22+

// Copyright (c) 2013-2014 Conformal Systems <info@conformal.com>
//
// This file originated from: http://opensource.conformal.com/
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

package gdk

// #include <gdk/gdk.h>
// #include "gdk_since_3_22.go.h"
import "C"
import (
	"unsafe"

	"github.com/gotk3/gotk3/glib"
)

/*
 * Constants
 */

// TODO:
// GdkSeatCapabilities

/*
 * GdkDisplay
 */

// GetNMonitors is a wrapper around gdk_display_get_n_monitors().
func (v *Display) GetNMonitors() int {
	c := C.gdk_display_get_n_monitors(v.native())
	return int(c)
}

// GetPrimaryMonitor is a wrapper around gdk_display_get_primary_monitor().
func (v *Display) GetPrimaryMonitor() (*Monitor, error) {
	c := C.gdk_display_get_primary_monitor(v.native())
	if c == nil {
		return nil, nilPtrErr
	}

	return &Monitor{glib.Take(unsafe.Pointer(c))}, nil
}

// GetMonitor is a wrapper around gdk_display_get_monitor().
func (v *Display) GetMonitor(num int) (*Monitor, error) {
	c := C.gdk_display_get_monitor(v.native(), C.int(num))
	if c == nil {
		return nil, nilPtrErr
	}
	return &Monitor{glib.Take(unsafe.Pointer(c))}, nil
}

// GetMonitorAtWindow is a wrapper around gdk_display_get_monitor_at_window().
func (v *Display) GetMonitorAtWindow(w *Window) (*Monitor, error) {
	c := C.gdk_display_get_monitor_at_window(v.native(), w.native())
	if c == nil {
		return nil, nilPtrErr
	}
	return &Monitor{glib.Take(unsafe.Pointer(c))}, nil
}

// GetMonitorAtPoint is a wrapper around gdk_display_get_monitor_at_point().
func (v *Display) GetMonitorAtPoint(x int, y int) (*Monitor, error) {
	c := C.gdk_display_get_monitor_at_point(v.native(), C.int(x), C.int(y))
	if c == nil {
		return nil, nilPtrErr
	}
	return &Monitor{glib.Take(unsafe.Pointer(c))}, nil
}

/*
 * GdkSeat
 */

// TODO:
// GdkSeatGrabPrepareFunc
// gdk_seat_get_display().
// gdk_seat_grab().
// gdk_seat_ungrab().
// gdk_seat_get_capabilities().
// gdk_seat_get_pointer().
// gdk_seat_get_keyboard().
// gdk_seat_get_slaves().

/*
 * GdkMonitor
 */

// Monitor is a representation of GDK's GdkMonitor.
type Monitor struct {
	*glib.Object
}

// native returns a pointer to the underlying GdkMonitor.
func (v *Monitor) native() *C.GdkMonitor {
	if v == nil || v.GObject == nil {
		return nil
	}
	p := unsafe.Pointer(v.GObject)
	return C.toGdkMonitor(p)
}

// Native returns a pointer to the underlying GdkMonitor.
func (v *Monitor) Native() uintptr {
	return uintptr(unsafe.Pointer(v.native()))
}

func marshalMonitor(p uintptr) (interface{}, error) {
	c := C.g_value_get_object((*C.GValue)(unsafe.Pointer(p)))
	obj := &glib.Object{glib.ToGObject(unsafe.Pointer(c))}
	return &Monitor{obj}, nil
}

func toMonitor(s *C.GdkMonitor) (*Monitor, error) {
	if s == nil {
		return nil, nilPtrErr
	}
	obj := &glib.Object{glib.ToGObject(unsafe.Pointer(s))}
	return &Monitor{obj}, nil
}

// GetGeometry is a wrapper around gdk_monitor_get_geometry().
func (v *Monitor) GetGeometry() *Rectangle {
	var rect C.GdkRectangle

	C.gdk_monitor_get_geometry(v.native(), &rect)

	return WrapRectangle(uintptr(unsafe.Pointer(&rect)))
}

/*
 * GdkDevice
 */

// TODO:
// gdk_device_get_axes().
// gdk_device_tool_get_serial().
// gdk_device_tool_get_tool_type().

/*
 * GdkGLContext
 */

// GetUseES is a wrapper around gdk_gl_context_get_use_es().
func (v *GLContext) GetUseES() bool {
	return gobool(C.gdk_gl_context_get_use_es(v.native()))
}

// SetUseES is a wrapper around gdk_gl_context_set_use_es().
func (v *GLContext) SetUseES(es int) {
	C.gdk_gl_context_set_use_es(v.native(), (C.int)(es))
}
<commit_msg>adding bindings for GdkMonitor<commit_after>// +build !gtk_3_6,!gtk_3_8,!gtk_3_10,!gtk_3_12,!gtk_3_14,!gtk_3_16,!gtk_3_18,!gtk_3_20
// Supports building with gtk 3.22+

// Copyright (c) 2013-2014 Conformal Systems <info@conformal.com>
//
// This file originated from: http://opensource.conformal.com/
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

package gdk

// #include <gdk/gdk.h>
// #include "gdk_since_3_22.go.h"
import "C"
import (
	"unsafe"

	"github.com/gotk3/gotk3/glib"
)

func init() {

	tm := []glib.TypeMarshaler{
		{glib.Type(C.gdk_subpixel_layout_get_type()), marshalSubpixelLayout},
	}

	glib.RegisterGValueMarshalers(tm)
}

/*
 * Constants
 */

// TODO:
// GdkSeatCapabilities

// SubpixelLayout is a representation of GDK's GdkSubpixelLayout.
type SubpixelLayout int

const (
	SUBPIXEL_LAYOUT_UNKNOWN        SubpixelLayout = C.GDK_SUBPIXEL_LAYOUT_UNKNOWN
	SUBPIXEL_LAYOUT_NONE           SubpixelLayout = C.GDK_SUBPIXEL_LAYOUT_NONE
	SUBPIXEL_LAYOUT_HORIZONTAL_RGB SubpixelLayout = C.GDK_SUBPIXEL_LAYOUT_HORIZONTAL_RGB
	SUBPIXEL_LAYOUT_HORIZONTAL_BGR SubpixelLayout = C.GDK_SUBPIXEL_LAYOUT_HORIZONTAL_BGR
	SUBPIXEL_LAYOUT_VERTICAL_RGB   SubpixelLayout = C.GDK_SUBPIXEL_LAYOUT_VERTICAL_RGB
	SUBPIXEL_LAYOUT_VERTICAL_BGR   SubpixelLayout = C.GDK_SUBPIXEL_LAYOUT_VERTICAL_BGR
)

func marshalSubpixelLayout(p uintptr) (interface{}, error) {
	c := C.g_value_get_enum((*C.GValue)(unsafe.Pointer(p)))
	return SubpixelLayout(c), nil
}

/*
 * GdkDisplay
 */

// GetNMonitors is a wrapper around gdk_display_get_n_monitors().
func (v *Display) GetNMonitors() int {
	c := C.gdk_display_get_n_monitors(v.native())
	return int(c)
}

// GetPrimaryMonitor is a wrapper around gdk_display_get_primary_monitor().
func (v *Display) GetPrimaryMonitor() (*Monitor, error) {
	c := C.gdk_display_get_primary_monitor(v.native())
	if c == nil {
		return nil, nilPtrErr
	}

	return &Monitor{glib.Take(unsafe.Pointer(c))}, nil
}

// GetMonitor is a wrapper around gdk_display_get_monitor().
func (v *Display) GetMonitor(num int) (*Monitor, error) {
	c := C.gdk_display_get_monitor(v.native(), C.int(num))
	if c == nil {
		return nil, nilPtrErr
	}
	return &Monitor{glib.Take(unsafe.Pointer(c))}, nil
}

// GetMonitorAtWindow is a wrapper around gdk_display_get_monitor_at_window().
func (v *Display) GetMonitorAtWindow(w *Window) (*Monitor, error) {
	c := C.gdk_display_get_monitor_at_window(v.native(), w.native())
	if c == nil {
		return nil, nilPtrErr
	}
	return &Monitor{glib.Take(unsafe.Pointer(c))}, nil
}

// GetMonitorAtPoint is a wrapper around gdk_display_get_monitor_at_point().
func (v *Display) GetMonitorAtPoint(x int, y int) (*Monitor, error) {
	c := C.gdk_display_get_monitor_at_point(v.native(), C.int(x), C.int(y))
	if c == nil {
		return nil, nilPtrErr
	}
	return &Monitor{glib.Take(unsafe.Pointer(c))}, nil
}

/*
 * GdkSeat
 */

// TODO:
// GdkSeatGrabPrepareFunc
// gdk_seat_get_display().
// gdk_seat_grab().
// gdk_seat_ungrab().
// gdk_seat_get_capabilities().
// gdk_seat_get_pointer().
// gdk_seat_get_keyboard().
// gdk_seat_get_slaves().

/*
 * GdkMonitor
 */

// Monitor is a representation of GDK's GdkMonitor.
type Monitor struct {
	*glib.Object
}

// native returns a pointer to the underlying GdkMonitor.
func (v *Monitor) native() *C.GdkMonitor {
	if v == nil || v.GObject == nil {
		return nil
	}
	p := unsafe.Pointer(v.GObject)
	return C.toGdkMonitor(p)
}

// Native returns a pointer to the underlying GdkMonitor.
func (v *Monitor) Native() uintptr {
	return uintptr(unsafe.Pointer(v.native()))
}

func marshalMonitor(p uintptr) (interface{}, error) {
	c := C.g_value_get_object((*C.GValue)(unsafe.Pointer(p)))
	obj := &glib.Object{glib.ToGObject(unsafe.Pointer(c))}
	return &Monitor{obj}, nil
}

func toMonitor(s *C.GdkMonitor) (*Monitor, error) {
	if s == nil {
		return nil, nilPtrErr
	}
	obj := &glib.Object{glib.ToGObject(unsafe.Pointer(s))}
	return &Monitor{obj}, nil
}

// GetDisplay is a wrapper around gdk_monitor_get_display().
func (v *Monitor) GetDisplay() (*Display, error) {
	return toDisplay(C.gdk_monitor_get_display(v.native()))
}

// GetGeometry is a wrapper around gdk_monitor_get_geometry().
func (v *Monitor) GetGeometry() *Rectangle {
	var rect C.GdkRectangle

	C.gdk_monitor_get_geometry(v.native(), &rect)

	return wrapRectangle(&rect)
}

// GetWorkarea is a wrapper around gdk_monitor_get_workarea().
func (v *Monitor) GetWorkarea() *Rectangle {
	var rect C.GdkRectangle

	C.gdk_monitor_get_workarea(v.native(), &rect)

	return wrapRectangle(&rect)
}

// GetWidthMM is a wrapper around gdk_monitor_get_width_mm().
func (v *Monitor) GetWidthMM() int {
	return int(C.gdk_monitor_get_width_mm(v.native()))
}

// GetHeightMM is a wrapper around gdk_monitor_get_height_mm().
func (v *Monitor) GetHeightMM() int {
	return int(C.gdk_monitor_get_height_mm(v.native()))
}

// GetManufacturer is a wrapper around gdk_monitor_get_manufacturer().
func (v *Monitor) GetManufacturer() string {
	// transfer none: don't free data after the code is done.
	return  C.GoString(C.gdk_monitor_get_manufacturer(v.native()))
}

// GetModel is a wrapper around gdk_monitor_get_model().
func (v *Monitor) GetModel() string {
	// transfer none: don't free data after the code is done.
	return C.GoString(C.gdk_monitor_get_model(v.native()))
}

// GetScaleFactor is a wrapper around gdk_monitor_get_scale_factor().
func (v *Monitor) GetScaleFactor() int {
	return int(C.gdk_monitor_get_scale_factor(v.native()))
}

// GetRefreshRate is a wrapper around gdk_monitor_get_refresh_rate().
func (v *Monitor) GetRefreshRate() int {
	return int(C.gdk_monitor_get_refresh_rate(v.native()))
}

// GetSubpixelLayout is a wrapper around gdk_monitor_get_subpixel_layout().
func (v *Monitor) GetSubpixelLayout() SubpixelLayout {
	return SubpixelLayout(C.gdk_monitor_get_subpixel_layout(v.native()))
}

// IsPrimary is a wrapper around gdk_monitor_is_primary().
func (v *Monitor) IsPrimary() bool {
	return gobool(C.gdk_monitor_is_primary(v.native()))
}

/*
 * GdkDevice
 */

// TODO:
// gdk_device_get_axes().
// gdk_device_tool_get_serial().
// gdk_device_tool_get_tool_type().

/*
 * GdkGLContext
 */

// GetUseES is a wrapper around gdk_gl_context_get_use_es().
func (v *GLContext) GetUseES() bool {
	return gobool(C.gdk_gl_context_get_use_es(v.native()))
}

// SetUseES is a wrapper around gdk_gl_context_set_use_es().
func (v *GLContext) SetUseES(es int) {
	C.gdk_gl_context_set_use_es(v.native(), (C.int)(es))
}
<|endoftext|>


<commit_before>package upcloud

import (
	"fmt"
	"log"
	"strconv"
	"time"

	"github.com/UpCloudLtd/upcloud-go-api/upcloud"
	"github.com/UpCloudLtd/upcloud-go-api/upcloud/request"
	"github.com/UpCloudLtd/upcloud-go-api/upcloud/service"
	"github.com/hashicorp/terraform/helper/schema"
	uuid "github.com/satori/go.uuid"
)

func resourceUpCloudServer() *schema.Resource {
	return &schema.Resource{
		Create: resourceUpCloudServerCreate,
		Read:   resourceUpCloudServerRead,
		Update: resourceUpCloudServerUpdate,
		Delete: resourceUpCloudServerDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},
		Schema: map[string]*schema.Schema{
			"hostname": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"title": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"zone": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"cpu": {
				Type:     schema.TypeInt,
				Required: true,
			},
			"mem": {
				Type:     schema.TypeInt,
				Required: true,
			},
			"os_disk_size": {
				Type:     schema.TypeInt,
				Required: true,
			},
			"os_disk_uuid": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"os_disk_tier": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  "maxiops",
			},
			"template": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"private_networking": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"ipv4": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"ipv6": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"ipv4_address": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"ipv4_address_private": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"ipv6_address": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"user_data": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},
		},
	}
}

func resourceUpCloudServerCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	r, err := buildServerOpts(d, meta)
	if err != nil {
		return err
	}
	server, err := client.CreateServer(r)
	if err != nil {
		return err
	}
	d.SetId(server.UUID)
	log.Printf("[INFO] Server %s with UUID %s created", server.Title, server.UUID)

	server, err = client.WaitForServerState(&request.WaitForServerStateRequest{
		UUID:         server.UUID,
		DesiredState: upcloud.ServerStateStarted,
		Timeout:      time.Minute * 5,
	})
	if err != nil {
		return err
	}
	return resourceUpCloudServerRead(d, meta)
}

func resourceUpCloudServerRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	r := &request.GetServerDetailsRequest{
		UUID: d.Id(),
	}
	server, err := client.GetServerDetails(r)
	if err != nil {
		return err
	}
	d.Set("hostname", server.Hostname)
	d.Set("title", server.Title)
	d.Set("region", server.Zone)
	d.Set("cpu", server.CoreNumber)
	d.Set("mem", server.MemoryAmount)

	// TODO: Handle additional disks
	osDisk := server.StorageDevices[0]
	d.Set("os_disk_size", osDisk.Size)
	d.Set("os_disk_uuid", osDisk.UUID)

	for _, ip := range server.IPAddresses {
		if ip.Access == "private" && ip.Family == "IPv4" {
			d.Set("ipv4_address_private", ip.Address)
		}
		if ip.Access == "public" && ip.Family == "IPv4" {
			d.Set("ipv4_address", ip.Address)
		}
		if ip.Access == "public" && ip.Family == "IPv6" {
			d.Set("ipv6_address", ip.Address)
		}
	}
	return nil
}

func resourceUpCloudServerUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	if d.HasChange("mem") || d.HasChange("cpu") {
		_, newCPU := d.GetChange("cpu")
		_, newMem := d.GetChange("mem")
		if err := verifyServerStopped(d, meta); err != nil {
			return err
		}
		r := &request.ModifyServerRequest{
			UUID:         d.Id(),
			CoreNumber:   strconv.Itoa(newCPU.(int)),
			MemoryAmount: strconv.Itoa(newMem.(int)),
		}
		_, err := client.ModifyServer(r)
		if err != nil {
			return err
		}
		if err := verifyServerStarted(d, meta); err != nil {
			return err
		}

	}
	return resourceUpCloudServerRead(d, meta)
}

func resourceUpCloudServerDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	// Verify server is stopped before deletion
	if err := verifyServerStopped(d, meta); err != nil {
		return err
	}
	// Delete server
	deleteServerRequest := &request.DeleteServerRequest{
		UUID: d.Id(),
	}
	log.Printf("[INFO] Deleting server (server UUID: %s)", d.Id())
	err := client.DeleteServer(deleteServerRequest)
	if err != nil {
		return err
	}
	// Delete server root disk
	rootDiskUUID := d.Get("os_disk_uuid").(string)
	deleteStorageRequest := &request.DeleteStorageRequest{
		UUID: rootDiskUUID,
	}
	log.Printf("[INFO] Deleting server root disk (storage UUID: %s)", rootDiskUUID)
	err = client.DeleteStorage(deleteStorageRequest)
	if err != nil {
		return err
	}
	return nil
}

func buildServerOpts(d *schema.ResourceData, meta interface{}) (*request.CreateServerRequest, error) {
	r := &request.CreateServerRequest{
		Zone:     d.Get("zone").(string),
		Hostname: d.Get("hostname").(string),
		Title:    fmt.Sprintf("%s (managed by terraform)", d.Get("hostname").(string)),
	}

	if attr, ok := d.GetOk("cpu"); ok {
		r.CoreNumber = attr.(int)
	}
	if attr, ok := d.GetOk("mem"); ok {
		r.MemoryAmount = attr.(int)
	}
	if attr, ok := d.GetOk("userdata"); ok {
		r.UserData = attr.(string)
	}

	storageOpts, err := buildStorageOpts(d, meta)
	if err != nil {
		return nil, err
	}
	r.StorageDevices = storageOpts

	networkOpts, err := buildNetworkOpts(d, meta)
	if err != nil {
		return nil, err
	}
	r.IPAddresses = networkOpts

	return r, nil
}

func buildStorageOpts(d *schema.ResourceData, meta interface{}) ([]upcloud.CreateServerStorageDevice, error) {
	storageCfg := make([]upcloud.CreateServerStorageDevice, 0)
	source := d.Get("template").(string)
	_, err := uuid.FromString(source)
	// Assume template name is given and map name to UUID
	if err != nil {
		client := meta.(*service.Service)
		r := &request.GetStoragesRequest{
			Type: "template",
		}
		l, err := client.GetStorages(r)
		if err != nil {
			return nil, err
		}
		for _, s := range l.Storages {
			if s.Title == source {
				source = s.UUID
				break
			}
		}
	}
	osDisk := upcloud.CreateServerStorageDevice{
		Action:  upcloud.CreateServerStorageDeviceActionClone,
		Storage: source,
	}

	// Set size or use the one defined by target template
	if attr, ok := d.GetOk("os_disk_size"); ok {
		osDisk.Size = attr.(int)
	}

	// Autogenerate disk title
	osDisk.Title = fmt.Sprintf("terraform-os-disk")

	// Set disk tier or use the one defined by target template
	if attr, ok := d.GetOk("os_disk_tier"); ok {
		tier := attr.(string)
		switch tier {
		case "maxiops":
			osDisk.Tier = upcloud.StorageTierMaxIOPS
		case "hdd":
			osDisk.Tier = upcloud.StorageTierHDD
		default:
			return nil, fmt.Errorf("Invalid disk tier '%s'", tier)
		}
	}
	storageCfg = append(storageCfg, osDisk)

	// TODO: Handle additional disks
	return storageCfg, nil
}

func buildNetworkOpts(d *schema.ResourceData, meta interface{}) ([]request.CreateServerIPAddress, error) {
	ifaceCfg := make([]request.CreateServerIPAddress, 0)
	if attr, ok := d.GetOk("ipv4"); ok {
		publicIPv4 := attr.(bool)
		if publicIPv4 {
			publicIPv4 := request.CreateServerIPAddress{
				Access: upcloud.IPAddressAccessPublic,
				Family: upcloud.IPAddressFamilyIPv4,
			}
			ifaceCfg = append(ifaceCfg, publicIPv4)
		}
	}
	if attr, ok := d.GetOk("private_networking"); ok {
		setPrivateIP := attr.(bool)
		if setPrivateIP {
			privateIPv4 := request.CreateServerIPAddress{
				Access: upcloud.IPAddressAccessPrivate,
				Family: upcloud.IPAddressFamilyIPv4,
			}
			ifaceCfg = append(ifaceCfg, privateIPv4)
		}
	}
	if attr, ok := d.GetOk("ipv6"); ok {
		publicIPv6 := attr.(bool)
		if publicIPv6 {
			publicIPv6 := request.CreateServerIPAddress{
				Access: upcloud.IPAddressAccessPublic,
				Family: upcloud.IPAddressFamilyIPv6,
			}
			ifaceCfg = append(ifaceCfg, publicIPv6)
		}
	}
	return ifaceCfg, nil
}

func verifyServerStopped(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	// Get current server state
	r := &request.GetServerDetailsRequest{
		UUID: d.Id(),
	}
	server, err := client.GetServerDetails(r)
	if err != nil {
		return err
	}
	// If state is 'started' then the instance must be stopped
	// before trying to delete
	if server.State != "stopped" {
		// Soft stop with 2 minute timeout, after which
		// hard stop occurs
		stopRequest := &request.StopServerRequest{
			UUID:     d.Id(),
			StopType: "soft",
			Timeout:  time.Minute * 2,
		}
		log.Printf("[INFO] Stopping server (server UUID: %s)", d.Id())
		_, err := client.StopServer(stopRequest)
		if err != nil {
			return err
		}
		_, err = client.WaitForServerState(&request.WaitForServerStateRequest{
			UUID:         d.Id(),
			DesiredState: upcloud.ServerStateStopped,
			Timeout:      time.Minute * 5,
		})
		if err != nil {
			return err
		}
	}
	return nil
}

func verifyServerStarted(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	// Get current server state
	r := &request.GetServerDetailsRequest{
		UUID: d.Id(),
	}
	server, err := client.GetServerDetails(r)
	if err != nil {
		return err
	}
	// If state is 'started' then the instance must be stopped
	// before trying to delete
	if server.State != "started" {
		// Soft stop with 2 minute timeout, after which
		// hard stop occurs
		startRequest := &request.StartServerRequest{
			UUID:    d.Id(),
			Timeout: time.Minute * 2,
		}
		log.Printf("[INFO] Stopping server (server UUID: %s)", d.Id())
		_, err := client.StartServer(startRequest)
		if err != nil {
			return err
		}
		_, err = client.WaitForServerState(&request.WaitForServerStateRequest{
			UUID:         d.Id(),
			DesiredState: upcloud.ServerStateStarted,
			Timeout:      time.Minute * 5,
		})
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Use const strings from the SDK<commit_after>package upcloud

import (
	"fmt"
	"log"
	"strconv"
	"time"

	"github.com/UpCloudLtd/upcloud-go-api/upcloud"
	"github.com/UpCloudLtd/upcloud-go-api/upcloud/request"
	"github.com/UpCloudLtd/upcloud-go-api/upcloud/service"
	"github.com/hashicorp/terraform/helper/schema"
	uuid "github.com/satori/go.uuid"
)

func resourceUpCloudServer() *schema.Resource {
	return &schema.Resource{
		Create: resourceUpCloudServerCreate,
		Read:   resourceUpCloudServerRead,
		Update: resourceUpCloudServerUpdate,
		Delete: resourceUpCloudServerDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},
		Schema: map[string]*schema.Schema{
			"hostname": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"title": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"zone": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"cpu": {
				Type:     schema.TypeInt,
				Required: true,
			},
			"mem": {
				Type:     schema.TypeInt,
				Required: true,
			},
			"os_disk_size": {
				Type:     schema.TypeInt,
				Required: true,
			},
			"os_disk_uuid": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"os_disk_tier": {
				Type:     schema.TypeString,
				Optional: true,
				Default:  "maxiops",
			},
			"template": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"private_networking": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"ipv4": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"ipv6": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"ipv4_address": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"ipv4_address_private": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"ipv6_address": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"user_data": {
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},
		},
	}
}

func resourceUpCloudServerCreate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	r, err := buildServerOpts(d, meta)
	if err != nil {
		return err
	}
	server, err := client.CreateServer(r)
	if err != nil {
		return err
	}
	d.SetId(server.UUID)
	log.Printf("[INFO] Server %s with UUID %s created", server.Title, server.UUID)

	server, err = client.WaitForServerState(&request.WaitForServerStateRequest{
		UUID:         server.UUID,
		DesiredState: upcloud.ServerStateStarted,
		Timeout:      time.Minute * 5,
	})
	if err != nil {
		return err
	}
	return resourceUpCloudServerRead(d, meta)
}

func resourceUpCloudServerRead(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	r := &request.GetServerDetailsRequest{
		UUID: d.Id(),
	}
	server, err := client.GetServerDetails(r)
	if err != nil {
		return err
	}
	d.Set("hostname", server.Hostname)
	d.Set("title", server.Title)
	d.Set("region", server.Zone)
	d.Set("cpu", server.CoreNumber)
	d.Set("mem", server.MemoryAmount)

	// TODO: Handle additional disks
	osDisk := server.StorageDevices[0]
	d.Set("os_disk_size", osDisk.Size)
	d.Set("os_disk_uuid", osDisk.UUID)

	for _, ip := range server.IPAddresses {
		if ip.Access == upcloud.IPAddressAccessPrivate && ip.Family == upcloud.IPAddressFamilyIPv4 {
			d.Set("ipv4_address_private", ip.Address)
		}
		if ip.Access == upcloud.IPAddressAccessPublic && ip.Family == upcloud.IPAddressFamilyIPv4 {
			d.Set("ipv4_address", ip.Address)
		}
		if ip.Access == upcloud.IPAddressAccessPublic && ip.Family == upcloud.IPAddressFamilyIPv6 {
			d.Set("ipv6_address", ip.Address)
		}
	}
	return nil
}

func resourceUpCloudServerUpdate(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	if d.HasChange("mem") || d.HasChange("cpu") {
		_, newCPU := d.GetChange("cpu")
		_, newMem := d.GetChange("mem")
		if err := verifyServerStopped(d, meta); err != nil {
			return err
		}
		r := &request.ModifyServerRequest{
			UUID:         d.Id(),
			CoreNumber:   strconv.Itoa(newCPU.(int)),
			MemoryAmount: strconv.Itoa(newMem.(int)),
		}
		_, err := client.ModifyServer(r)
		if err != nil {
			return err
		}
		if err := verifyServerStarted(d, meta); err != nil {
			return err
		}

	}
	return resourceUpCloudServerRead(d, meta)
}

func resourceUpCloudServerDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	// Verify server is stopped before deletion
	if err := verifyServerStopped(d, meta); err != nil {
		return err
	}
	// Delete server
	deleteServerRequest := &request.DeleteServerRequest{
		UUID: d.Id(),
	}
	log.Printf("[INFO] Deleting server (server UUID: %s)", d.Id())
	err := client.DeleteServer(deleteServerRequest)
	if err != nil {
		return err
	}
	// Delete server root disk
	rootDiskUUID := d.Get("os_disk_uuid").(string)
	deleteStorageRequest := &request.DeleteStorageRequest{
		UUID: rootDiskUUID,
	}
	log.Printf("[INFO] Deleting server root disk (storage UUID: %s)", rootDiskUUID)
	err = client.DeleteStorage(deleteStorageRequest)
	if err != nil {
		return err
	}
	return nil
}

func buildServerOpts(d *schema.ResourceData, meta interface{}) (*request.CreateServerRequest, error) {
	r := &request.CreateServerRequest{
		Zone:     d.Get("zone").(string),
		Hostname: d.Get("hostname").(string),
		Title:    fmt.Sprintf("%s (managed by terraform)", d.Get("hostname").(string)),
	}

	if attr, ok := d.GetOk("cpu"); ok {
		r.CoreNumber = attr.(int)
	}
	if attr, ok := d.GetOk("mem"); ok {
		r.MemoryAmount = attr.(int)
	}
	if attr, ok := d.GetOk("userdata"); ok {
		r.UserData = attr.(string)
	}

	storageOpts, err := buildStorageOpts(d, meta)
	if err != nil {
		return nil, err
	}
	r.StorageDevices = storageOpts

	networkOpts, err := buildNetworkOpts(d, meta)
	if err != nil {
		return nil, err
	}
	r.IPAddresses = networkOpts

	return r, nil
}

func buildStorageOpts(d *schema.ResourceData, meta interface{}) ([]upcloud.CreateServerStorageDevice, error) {
	storageCfg := make([]upcloud.CreateServerStorageDevice, 0)
	source := d.Get("template").(string)
	_, err := uuid.FromString(source)
	// Assume template name is given and map name to UUID
	if err != nil {
		client := meta.(*service.Service)
		r := &request.GetStoragesRequest{
			Type: upcloud.StorageTypeTemplate,
		}
		l, err := client.GetStorages(r)
		if err != nil {
			return nil, err
		}
		for _, s := range l.Storages {
			if s.Title == source {
				source = s.UUID
				break
			}
		}
	}
	osDisk := upcloud.CreateServerStorageDevice{
		Action:  upcloud.CreateServerStorageDeviceActionClone,
		Storage: source,
	}

	// Set size or use the one defined by target template
	if attr, ok := d.GetOk("os_disk_size"); ok {
		osDisk.Size = attr.(int)
	}

	// Autogenerate disk title
	osDisk.Title = fmt.Sprintf("terraform-os-disk")

	// Set disk tier or use the one defined by target template
	if attr, ok := d.GetOk("os_disk_tier"); ok {
		tier := attr.(string)
		switch tier {
		case upcloud.StorageTierMaxIOPS:
			osDisk.Tier = upcloud.StorageTierMaxIOPS
		case upcloud.StorageTierHDD:
			osDisk.Tier = upcloud.StorageTierHDD
		default:
			return nil, fmt.Errorf("Invalid disk tier '%s'", tier)
		}
	}
	storageCfg = append(storageCfg, osDisk)

	// TODO: Handle additional disks
	return storageCfg, nil
}

func buildNetworkOpts(d *schema.ResourceData, meta interface{}) ([]request.CreateServerIPAddress, error) {
	ifaceCfg := make([]request.CreateServerIPAddress, 0)
	if attr, ok := d.GetOk("ipv4"); ok {
		publicIPv4 := attr.(bool)
		if publicIPv4 {
			publicIPv4 := request.CreateServerIPAddress{
				Access: upcloud.IPAddressAccessPublic,
				Family: upcloud.IPAddressFamilyIPv4,
			}
			ifaceCfg = append(ifaceCfg, publicIPv4)
		}
	}
	if attr, ok := d.GetOk("private_networking"); ok {
		setPrivateIP := attr.(bool)
		if setPrivateIP {
			privateIPv4 := request.CreateServerIPAddress{
				Access: upcloud.IPAddressAccessPrivate,
				Family: upcloud.IPAddressFamilyIPv4,
			}
			ifaceCfg = append(ifaceCfg, privateIPv4)
		}
	}
	if attr, ok := d.GetOk("ipv6"); ok {
		publicIPv6 := attr.(bool)
		if publicIPv6 {
			publicIPv6 := request.CreateServerIPAddress{
				Access: upcloud.IPAddressAccessPublic,
				Family: upcloud.IPAddressFamilyIPv6,
			}
			ifaceCfg = append(ifaceCfg, publicIPv6)
		}
	}
	return ifaceCfg, nil
}

func verifyServerStopped(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	// Get current server state
	r := &request.GetServerDetailsRequest{
		UUID: d.Id(),
	}
	server, err := client.GetServerDetails(r)
	if err != nil {
		return err
	}
	if server.State != upcloud.ServerStateStopped {
		// Soft stop with 2 minute timeout, after which hard stop occurs
		stopRequest := &request.StopServerRequest{
			UUID:     d.Id(),
			StopType: "soft",
			Timeout:  time.Minute * 2,
		}
		log.Printf("[INFO] Stopping server (server UUID: %s)", d.Id())
		_, err := client.StopServer(stopRequest)
		if err != nil {
			return err
		}
		_, err = client.WaitForServerState(&request.WaitForServerStateRequest{
			UUID:         d.Id(),
			DesiredState: upcloud.ServerStateStopped,
			Timeout:      time.Minute * 5,
		})
		if err != nil {
			return err
		}
	}
	return nil
}

func verifyServerStarted(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*service.Service)
	// Get current server state
	r := &request.GetServerDetailsRequest{
		UUID: d.Id(),
	}
	server, err := client.GetServerDetails(r)
	if err != nil {
		return err
	}
	if server.State != upcloud.ServerStateStarted {
		// Soft stop with 2 minute timeout, after which hard stop occurs
		startRequest := &request.StartServerRequest{
			UUID:    d.Id(),
			Timeout: time.Minute * 2,
		}
		log.Printf("[INFO] Stopping server (server UUID: %s)", d.Id())
		_, err := client.StartServer(startRequest)
		if err != nil {
			return err
		}
		_, err = client.WaitForServerState(&request.WaitForServerStateRequest{
			UUID:         d.Id(),
			DesiredState: upcloud.ServerStateStarted,
			Timeout:      time.Minute * 5,
		})
		if err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package aphgrpc

import (
	"fmt"
	"math"
	"net/http"
	"strings"

	"gopkg.in/mgutz/dat.v1/sqlx-runner"

	"github.com/dictyBase/apihelpers/aphgrpc"
	"github.com/dictyBase/go-genproto/dictybaseapis/api/jsonapi"
	"github.com/fatih/structs"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/ptypes"
	"github.com/golang/protobuf/ptypes/any"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	context "golang.org/x/net/context"
)

const (
	DefaultPagenum  = 1
	DefaultPagesize = 10
)

// JSONAPIParamsInfo interface should be implement by all grpc-gateway services
// that supports JSON API specifications.
type JSONAPIParamsInfo interface {
	// Relationships that could be included
	AllowedInclude() []string
	// Attribute fields that are allowed
	AllowedFields() []string
	// Filter fields that are allowed
	AllowedFilter() []string
	// FilterToColumns provides mapping between filter and storage columns
	FilterToColumns() map[string]string
	// RequiredAttrs are the mandatory attributes for creating a new resource
	RequiredAttrs() []string
}

// JSONAPIResource interface provides information about HTTP resource. All
// grpc-gateway services that supports JSONAPI should implement this interface.
type JSONAPIResource interface {
	//GetResourceName returns canonical resource name
	GetResourceName() string
	// GetBaseURL returns the base url with the scheme
	GetBaseURL() string
	// GetPrefix returns the path that could be appended to base url
	GetPathPrefix() string
}

// GetTotalPageNum calculate total no of pages from total no. records and page size
func GetTotalPageNum(record, pagesize int64) int64 {
	total := int64(math.Floor(float64(record) / float64(pagesize)))
	if math.Mod(float64(record), float64(pagesize)) > 0 {
		total += 1
	}
	return total
}

// GetPaginatedLinks gets paginated links and total page number for collection resources
func GetPaginatedLinks(rs JSONAPIResource, lastpage, pagenum, pagesize int64) map[string]string {
	var links map[string]string
	links["self"] = GenPaginatedResourceLink(rs, pagenum, pagesize)
	links["first"] = GenPaginatedResourceLink(rs, 1, pagesize)
	if pagenum != 1 {
		links["previous"] = GenPaginatedResourceLink(rs, pagenum-1, pagesize)
	}
	links["last"] = GenPaginatedResourceLink(rs, lastpage, pagesize)
	if pagenum != lastpage {
		links["next"] = GenPaginatedResourceLink(rs, pagenum+1, pagesize)
	}
	return links
}

func GenBaseLink(rs JSONAPIResource) string {
	return fmt.Sprintf(
		"%s/%s",
		strings.Trim(rs.GetBaseURL(), "/"),
		strings.Trim(rs.GetPathPrefix(), "/"),
	)
}

func GenSingleResourceLink(rs JSONAPIResource, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
	)
}

func GenMultiResourceLink(rs JSONAPIResource) string {
	return fmt.Sprintf(
		"%s/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
	)
}

func GenPaginatedResourceLink(rs JSONAPIResource, pagenum, pagesize int64) string {
	return fmt.Sprintf(
		"%s/%s?pagenum=%d&pagesize=%d",
		GenBaseLink(rs),
		rs.GetResourceName(),
		pagenum,
		pagesize,
	)
}

func GenSelfRelationshipLink(rs JSONAPIResource, rel string, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d/relationships/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
		rel,
	)
}

func GenRelatedRelationshipLink(rs JSONAPIResource, rel string, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
		rel,
	)
}

//GetDefinedTagsWithValue check for fields that are initialized and returns a map
//with the tag and their values
func GetDefinedTagsWithValue(i interface{}, key string) map[string]interface{} {
	m := make(map[string]interface{})
	s := structs.New(i)
	for _, f := range s.Fields() {
		if !f.IsZero() {
			m[f.Tag(key)] = f.Value()
		}
	}
	return m
}

//GetDefinedTags check for fields that are initialized and returns a slice of
//their matching tag values
func GetDefinedTags(i interface{}, tag string) []string {
	var v []string
	s := structs.New(i)
	for _, f := range s.Fields() {
		if !f.IsZero() {
			v = append(v, f.Tag(tag))
		}
	}
	return v
}

// HandleCreateResponse modifies the grpc gateway filter which adds the JSON API header and
// modifies the http status response for POST request
func HandleCreateResponse(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
	w.Header().Set("Content-Type", "application/vnd.api+json")
	md, ok := runtime.ServerMetadataFromContext(ctx)
	if ok {
		trMD := md.TrailerMD
		if _, ok := trMD["method"]; ok {
			if trMD["method"][0] == "POST" {
				w.WriteHeader(http.StatusCreated)
			}
		}
	}
	return nil
}

// ConvertAllToAny generates slice of arbitrary serialized protocol buffer
// message
func ConvertAllToAny(msg []proto.Message) ([]*any.Any, error) {
	as := make([]*any.Any, len(msg))
	for i, p := range msg {
		pkg, err := ptypes.MarshalAny(p)
		if err != nil {
			return as, err
		}
		as[i] = pkg
	}
	return as, nil
}

type Service struct {
	Dbh             *runner.DB
	pathPrefix      string
	include         []string
	includeStr      string
	fieldsToColumns map[string]string
	fieldsStr       string
	resource        string
	baseURL         string
	filterToColumns map[string]string
	filterStr       string
	params          *JSONAPIParams
	listMethod      bool
	requiredAttrs   []string
}

func (s *Service) RequiredAttrs() []string {
	return s.requiredAttrs
}

func (s *Service) IsListMethod() bool {
	return s.listMethod
}

func (s *Service) FilterToColumns() map[string]string {
	return s.filterToColumns
}

func (s *Service) AllowedFilter() []string {
	var f []string
	for k, _ := range s.filterToColumns {
		f = append(f, k)
	}
	return f
}

func (s *Service) AllowedInclude() []string {
	return s.include
}

func (s *Service) AllowedFields() []string {
	var f []string
	for k, _ := range s.fieldsToColumns {
		f = append(f, k)
	}
	return f
}

func (s *Service) GetResourceName() string {
	return s.resource
}

func (s *Service) GetBaseURL() string {
	return s.baseURL
}

func (s *Service) GetPathPrefix() string {
	return s.pathPrefix
}

func (s *Service) MapFieldsToColumns(fields []string) []string {
	var columns []string
	for _, v := range fields {
		columns = append(columns, s.fieldsToColumns[v])
	}
	return columns
}

func (s *Service) getCount(table string) (int64, error) {
	var count int64
	err := s.Dbh.Select("COUNT(*)").From(table).QueryScalar(&count)
	return count, err
}

func (s *Service) getAllFilteredCount(table string) (int64, error) {
	var count int64
	err := s.Dbh.Select("COUNT(*)").
		From(table).
		Scope(
			aphgrpc.FilterToWhereClause(s, s.params.Filter),
			aphgrpc.FilterToBindValue(s.params.Filter)...,
		).QueryScalar(&count)
	return count, err
}

func (s *Service) getPagination(record, pagenum, pagesize int64) (*jsonapi.PaginationLinks, int64) {
	pages := GetTotalPageNum(record, pagenum, pagesize)
	pageLinks := GetPaginatedLinks(s, pages, pagenum, pagesize)
	pageType := []string{"self", "last", "first", "previous", "next"}
	params := s.params
	switch {
	case params.HasFields && params.HasInclude && params.HasFilter:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&fields=%s&include=%s&filter=%s", s.fieldsStr, s.includeStr, s.filterStr)
			}
		}
	case params.HasFields && params.HasInclude:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&fields=%s&include=%s", s.fieldsStr, s.includeStr)
			}
		}
	case params.HasFields && params.HasFilter:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&fields=%s&filter=%s", s.fieldsStr, s.filterStr)
			}
		}
	case params.HasInclude && params.HasFilter:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&include=%s&filter=%s", s.includeStr, s.filterStr)
			}
		}
	case params.HasInclude:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&include=%s", s.includeStr)
			}
		}
	case params.HasFilter:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&filter=%s", s.filterStr)
			}
		}
	case params.HasFields:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&fields=%s", s.fieldsStr)
			}
		}
	}
	jsapiLinks := jsonapi.PaginationLinks{
		Self:  pageLinks["self"],
		Last:  pageLinks["last"],
		First: pageLinks["first"],
	}
	if _, ok := pageLinks["previous"]; ok {
		jsapiLinks.Previous = pageLinks["previous"]
	}
	if _, ok := pageLinks["next"]; ok {
		jsapiLinks.Next = pageLinks["next"]
	}
	return jsapiLinks, pages
}

func (s *Service) genCollResourceSelfLink() string {
	link := GenMultiResourceLink(s)
	params := s.params
	switch {
	case params.HasFields && params.HasFilter && params.HasInclude:
		link += fmt.Sprintf("?fields=%s&include=%s&filter=%s", s.fieldsStr, s.includeStr, s.filterStr)
	case params.HasFields && params.HasFilter:
		link += fmt.Sprintf("?fields=%s&filter=%s", s.fieldsStr, s.filterStr)
	case params.HasFields && params.HasInclude:
		link += fmt.Sprintf("?fields=%s&include=%s", s.fieldsStr, s.includeStr)
	case params.HasFilter && params.HasInclude:
		link += fmt.Sprintf("?filter=%s&include=%s", s.filterStr, s.includeStr)
	case params.HasInclude:
		link += fmt.Sprintf("?include=%s", s.includeStr)
	case params.HasFilter:
		link += fmt.Sprintf("?filter=%s", s.filterStr)
	case params.HasFields:
		link += fmt.Sprintf("?fields=%s", s.fieldsStr)
	}
	return link
}

func (s *Service) genResourceSelfLink(id int64) string {
	links := GenSingleResourceLink(s, id)
	if !s.IsListMethod() && s.params != nil {
		params := s.params
		switch {
		case params.HasFields && params.HasIncludes:
			links += fmt.Sprintf("?fields=%s&include=%s", s.fieldsStr, s.includeStr)
		case params.HasFields:
			links += fmt.Sprintf("?fields=%s", s.fieldsStr)
		case params.HasIncludes:
			links += fmt.Sprintf("?include=%s", s.includeStr)
		}
	}
	return links
}
<commit_msg>Added provision for sending 204 HTTP response<commit_after>package aphgrpc

import (
	"fmt"
	"math"
	"net/http"
	"strings"

	"gopkg.in/mgutz/dat.v1/sqlx-runner"

	"github.com/dictyBase/apihelpers/aphgrpc"
	"github.com/dictyBase/go-genproto/dictybaseapis/api/jsonapi"
	"github.com/fatih/structs"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/ptypes"
	"github.com/golang/protobuf/ptypes/any"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	context "golang.org/x/net/context"
)

const (
	DefaultPagenum  = 1
	DefaultPagesize = 10
)

// JSONAPIParamsInfo interface should be implement by all grpc-gateway services
// that supports JSON API specifications.
type JSONAPIParamsInfo interface {
	// Relationships that could be included
	AllowedInclude() []string
	// Attribute fields that are allowed
	AllowedFields() []string
	// Filter fields that are allowed
	AllowedFilter() []string
	// FilterToColumns provides mapping between filter and storage columns
	FilterToColumns() map[string]string
	// RequiredAttrs are the mandatory attributes for creating a new resource
	RequiredAttrs() []string
}

// JSONAPIResource interface provides information about HTTP resource. All
// grpc-gateway services that supports JSONAPI should implement this interface.
type JSONAPIResource interface {
	//GetResourceName returns canonical resource name
	GetResourceName() string
	// GetBaseURL returns the base url with the scheme
	GetBaseURL() string
	// GetPrefix returns the path that could be appended to base url
	GetPathPrefix() string
}

// GetTotalPageNum calculate total no of pages from total no. records and page size
func GetTotalPageNum(record, pagesize int64) int64 {
	total := int64(math.Floor(float64(record) / float64(pagesize)))
	if math.Mod(float64(record), float64(pagesize)) > 0 {
		total += 1
	}
	return total
}

// GetPaginatedLinks gets paginated links and total page number for collection resources
func GetPaginatedLinks(rs JSONAPIResource, lastpage, pagenum, pagesize int64) map[string]string {
	var links map[string]string
	links["self"] = GenPaginatedResourceLink(rs, pagenum, pagesize)
	links["first"] = GenPaginatedResourceLink(rs, 1, pagesize)
	if pagenum != 1 {
		links["previous"] = GenPaginatedResourceLink(rs, pagenum-1, pagesize)
	}
	links["last"] = GenPaginatedResourceLink(rs, lastpage, pagesize)
	if pagenum != lastpage {
		links["next"] = GenPaginatedResourceLink(rs, pagenum+1, pagesize)
	}
	return links
}

func GenBaseLink(rs JSONAPIResource) string {
	return fmt.Sprintf(
		"%s/%s",
		strings.Trim(rs.GetBaseURL(), "/"),
		strings.Trim(rs.GetPathPrefix(), "/"),
	)
}

func GenSingleResourceLink(rs JSONAPIResource, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
	)
}

func GenMultiResourceLink(rs JSONAPIResource) string {
	return fmt.Sprintf(
		"%s/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
	)
}

func GenPaginatedResourceLink(rs JSONAPIResource, pagenum, pagesize int64) string {
	return fmt.Sprintf(
		"%s/%s?pagenum=%d&pagesize=%d",
		GenBaseLink(rs),
		rs.GetResourceName(),
		pagenum,
		pagesize,
	)
}

func GenSelfRelationshipLink(rs JSONAPIResource, rel string, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d/relationships/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
		rel,
	)
}

func GenRelatedRelationshipLink(rs JSONAPIResource, rel string, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
		rel,
	)
}

//GetDefinedTagsWithValue check for fields that are initialized and returns a map
//with the tag and their values
func GetDefinedTagsWithValue(i interface{}, key string) map[string]interface{} {
	m := make(map[string]interface{})
	s := structs.New(i)
	for _, f := range s.Fields() {
		if !f.IsZero() {
			m[f.Tag(key)] = f.Value()
		}
	}
	return m
}

//GetDefinedTags check for fields that are initialized and returns a slice of
//their matching tag values
func GetDefinedTags(i interface{}, tag string) []string {
	var v []string
	s := structs.New(i)
	for _, f := range s.Fields() {
		if !f.IsZero() {
			v = append(v, f.Tag(tag))
		}
	}
	return v
}

// HandleCreateResponse modifies the grpc gateway filter which adds the JSON API header and
// modifies the http status response for POST request
func HandleCreateResponse(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
	w.Header().Set("Content-Type", "application/vnd.api+json")
	md, ok := runtime.ServerMetadataFromContext(ctx)
	if ok {
		trMD := md.TrailerMD
		if _, ok := trMD["method"]; ok {
			switch trMD["method"][0] {
			case "POST":
				w.WriteHeader(http.StatusCreated)
			case "POST_NO_CONTENT":
				w.WriteHeader(http.StatusNoContent)
			}
		}
	}
	return nil
}

// ConvertAllToAny generates slice of arbitrary serialized protocol buffer
// message
func ConvertAllToAny(msg []proto.Message) ([]*any.Any, error) {
	as := make([]*any.Any, len(msg))
	for i, p := range msg {
		pkg, err := ptypes.MarshalAny(p)
		if err != nil {
			return as, err
		}
		as[i] = pkg
	}
	return as, nil
}

type Service struct {
	Dbh             *runner.DB
	pathPrefix      string
	include         []string
	includeStr      string
	fieldsToColumns map[string]string
	fieldsStr       string
	resource        string
	baseURL         string
	filterToColumns map[string]string
	filterStr       string
	params          *JSONAPIParams
	listMethod      bool
	requiredAttrs   []string
}

func (s *Service) RequiredAttrs() []string {
	return s.requiredAttrs
}

func (s *Service) IsListMethod() bool {
	return s.listMethod
}

func (s *Service) FilterToColumns() map[string]string {
	return s.filterToColumns
}

func (s *Service) AllowedFilter() []string {
	var f []string
	for k, _ := range s.filterToColumns {
		f = append(f, k)
	}
	return f
}

func (s *Service) AllowedInclude() []string {
	return s.include
}

func (s *Service) AllowedFields() []string {
	var f []string
	for k, _ := range s.fieldsToColumns {
		f = append(f, k)
	}
	return f
}

func (s *Service) GetResourceName() string {
	return s.resource
}

func (s *Service) GetBaseURL() string {
	return s.baseURL
}

func (s *Service) GetPathPrefix() string {
	return s.pathPrefix
}

func (s *Service) MapFieldsToColumns(fields []string) []string {
	var columns []string
	for _, v := range fields {
		columns = append(columns, s.fieldsToColumns[v])
	}
	return columns
}

func (s *Service) getCount(table string) (int64, error) {
	var count int64
	err := s.Dbh.Select("COUNT(*)").From(table).QueryScalar(&count)
	return count, err
}

func (s *Service) getAllFilteredCount(table string) (int64, error) {
	var count int64
	err := s.Dbh.Select("COUNT(*)").
		From(table).
		Scope(
			aphgrpc.FilterToWhereClause(s, s.params.Filter),
			aphgrpc.FilterToBindValue(s.params.Filter)...,
		).QueryScalar(&count)
	return count, err
}

func (s *Service) getPagination(record, pagenum, pagesize int64) (*jsonapi.PaginationLinks, int64) {
	pages := GetTotalPageNum(record, pagenum, pagesize)
	pageLinks := GetPaginatedLinks(s, pages, pagenum, pagesize)
	pageType := []string{"self", "last", "first", "previous", "next"}
	params := s.params
	switch {
	case params.HasFields && params.HasInclude && params.HasFilter:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&fields=%s&include=%s&filter=%s", s.fieldsStr, s.includeStr, s.filterStr)
			}
		}
	case params.HasFields && params.HasInclude:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&fields=%s&include=%s", s.fieldsStr, s.includeStr)
			}
		}
	case params.HasFields && params.HasFilter:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&fields=%s&filter=%s", s.fieldsStr, s.filterStr)
			}
		}
	case params.HasInclude && params.HasFilter:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&include=%s&filter=%s", s.includeStr, s.filterStr)
			}
		}
	case params.HasInclude:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&include=%s", s.includeStr)
			}
		}
	case params.HasFilter:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&filter=%s", s.filterStr)
			}
		}
	case params.HasFields:
		for _, v := range pageType {
			if _, ok := pageLinks[v]; ok {
				pageLinks[v] += fmt.Sprintf("&fields=%s", s.fieldsStr)
			}
		}
	}
	jsapiLinks := jsonapi.PaginationLinks{
		Self:  pageLinks["self"],
		Last:  pageLinks["last"],
		First: pageLinks["first"],
	}
	if _, ok := pageLinks["previous"]; ok {
		jsapiLinks.Previous = pageLinks["previous"]
	}
	if _, ok := pageLinks["next"]; ok {
		jsapiLinks.Next = pageLinks["next"]
	}
	return jsapiLinks, pages
}

func (s *Service) genCollResourceSelfLink() string {
	link := GenMultiResourceLink(s)
	params := s.params
	switch {
	case params.HasFields && params.HasFilter && params.HasInclude:
		link += fmt.Sprintf("?fields=%s&include=%s&filter=%s", s.fieldsStr, s.includeStr, s.filterStr)
	case params.HasFields && params.HasFilter:
		link += fmt.Sprintf("?fields=%s&filter=%s", s.fieldsStr, s.filterStr)
	case params.HasFields && params.HasInclude:
		link += fmt.Sprintf("?fields=%s&include=%s", s.fieldsStr, s.includeStr)
	case params.HasFilter && params.HasInclude:
		link += fmt.Sprintf("?filter=%s&include=%s", s.filterStr, s.includeStr)
	case params.HasInclude:
		link += fmt.Sprintf("?include=%s", s.includeStr)
	case params.HasFilter:
		link += fmt.Sprintf("?filter=%s", s.filterStr)
	case params.HasFields:
		link += fmt.Sprintf("?fields=%s", s.fieldsStr)
	}
	return link
}

func (s *Service) genResourceSelfLink(id int64) string {
	links := GenSingleResourceLink(s, id)
	if !s.IsListMethod() && s.params != nil {
		params := s.params
		switch {
		case params.HasFields && params.HasIncludes:
			links += fmt.Sprintf("?fields=%s&include=%s", s.fieldsStr, s.includeStr)
		case params.HasFields:
			links += fmt.Sprintf("?fields=%s", s.fieldsStr)
		case params.HasIncludes:
			links += fmt.Sprintf("?include=%s", s.includeStr)
		}
	}
	return links
}
<|endoftext|>


<commit_before>package protocol

import (
	"sync"
	"time"

	"github.com/v2ray/v2ray-core/common"
	"github.com/v2ray/v2ray-core/common/signal"
)

const (
	updateIntervalSec = 10
	cacheDurationSec  = 120
)

type idEntry struct {
	id             *ID
	userIdx        int
	lastSec        Timestamp
	lastSecRemoval Timestamp
}

type UserValidator interface {
	common.Releasable

	Add(user *User) error
	Get(timeHash []byte) (*User, Timestamp, bool)
}

type TimedUserValidator struct {
	validUsers []*User
	userHash   map[[16]byte]*indexTimePair
	ids        []*idEntry
	access     sync.RWMutex
	hasher     IDHash
	cancel     *signal.CancelSignal
}

type indexTimePair struct {
	index   int
	timeSec Timestamp
}

func NewTimedUserValidator(hasher IDHash) UserValidator {
	tus := &TimedUserValidator{
		validUsers: make([]*User, 0, 16),
		userHash:   make(map[[16]byte]*indexTimePair, 512),
		access:     sync.RWMutex{},
		ids:        make([]*idEntry, 0, 512),
		hasher:     hasher,
		cancel:     signal.NewCloseSignal(),
	}
	go tus.updateUserHash(updateIntervalSec * time.Second)
	return tus
}

func (this *TimedUserValidator) Release() {
	this.cancel.Cancel()
	<-this.cancel.WaitForDone()

	this.validUsers = nil
	this.userHash = nil
	this.ids = nil
	this.hasher = nil
	this.cancel = nil
}

func (this *TimedUserValidator) generateNewHashes(nowSec Timestamp, idx int, entry *idEntry) {
	var hashValue [16]byte
	var hashValueRemoval [16]byte
	idHash := this.hasher(entry.id.Bytes())
	for entry.lastSec <= nowSec {
		idHash.Write(entry.lastSec.Bytes())
		idHash.Sum(hashValue[:0])
		idHash.Reset()

		idHash.Write(entry.lastSecRemoval.Bytes())
		idHash.Sum(hashValueRemoval[:0])
		idHash.Reset()

		this.access.Lock()
		this.userHash[hashValue] = &indexTimePair{idx, entry.lastSec}
		delete(this.userHash, hashValueRemoval)
		this.access.Unlock()

		entry.lastSec++
		entry.lastSecRemoval++
	}
}

func (this *TimedUserValidator) updateUserHash(interval time.Duration) {
L:
	for {
		select {
		case now := <-time.After(interval):
			nowSec := Timestamp(now.Unix() + cacheDurationSec)
			for _, entry := range this.ids {
				this.generateNewHashes(nowSec, entry.userIdx, entry)
			}
		case <-this.cancel.WaitForCancel():
			break L
		}
	}
	this.cancel.Done()
}

func (this *TimedUserValidator) Add(user *User) error {
	idx := len(this.validUsers)
	this.validUsers = append(this.validUsers, user)
	account := user.Account.(*VMessAccount)

	nowSec := time.Now().Unix()

	entry := &idEntry{
		id:             account.ID,
		userIdx:        idx,
		lastSec:        Timestamp(nowSec - cacheDurationSec),
		lastSecRemoval: Timestamp(nowSec - cacheDurationSec*3),
	}
	this.generateNewHashes(Timestamp(nowSec+cacheDurationSec), idx, entry)
	this.ids = append(this.ids, entry)
	for _, alterid := range account.AlterIDs {
		entry := &idEntry{
			id:             alterid,
			userIdx:        idx,
			lastSec:        Timestamp(nowSec - cacheDurationSec),
			lastSecRemoval: Timestamp(nowSec - cacheDurationSec*3),
		}
		this.generateNewHashes(Timestamp(nowSec+cacheDurationSec), idx, entry)
		this.ids = append(this.ids, entry)
	}

	return nil
}

func (this *TimedUserValidator) Get(userHash []byte) (*User, Timestamp, bool) {
	defer this.access.RUnlock()
	this.access.RLock()
	var fixedSizeHash [16]byte
	copy(fixedSizeHash[:], userHash)
	pair, found := this.userHash[fixedSizeHash]
	if found {
		return this.validUsers[pair.index], pair.timeSec, true
	}
	return nil, 0, false
}
<commit_msg>lock protected user validator<commit_after>package protocol

import (
	"sync"
	"time"

	"github.com/v2ray/v2ray-core/common"
	"github.com/v2ray/v2ray-core/common/signal"
)

const (
	updateIntervalSec = 10
	cacheDurationSec  = 120
)

type idEntry struct {
	id             *ID
	userIdx        int
	lastSec        Timestamp
	lastSecRemoval Timestamp
}

type UserValidator interface {
	common.Releasable

	Add(user *User) error
	Get(timeHash []byte) (*User, Timestamp, bool)
}

type TimedUserValidator struct {
	sync.RWMutex
	running    bool
	validUsers []*User
	userHash   map[[16]byte]*indexTimePair
	ids        []*idEntry
	hasher     IDHash
	cancel     *signal.CancelSignal
}

type indexTimePair struct {
	index   int
	timeSec Timestamp
}

func NewTimedUserValidator(hasher IDHash) UserValidator {
	tus := &TimedUserValidator{
		validUsers: make([]*User, 0, 16),
		userHash:   make(map[[16]byte]*indexTimePair, 512),
		ids:        make([]*idEntry, 0, 512),
		hasher:     hasher,
		running:    true,
		cancel:     signal.NewCloseSignal(),
	}
	go tus.updateUserHash(updateIntervalSec * time.Second)
	return tus
}

func (this *TimedUserValidator) Release() {
	if !this.running {
		return
	}

	this.cancel.Cancel()
	<-this.cancel.WaitForDone()

	this.Lock()
	defer this.Unlock()

	if !this.running {
		return
	}

	this.running = false
	this.validUsers = nil
	this.userHash = nil
	this.ids = nil
	this.hasher = nil
	this.cancel = nil
}

func (this *TimedUserValidator) generateNewHashes(nowSec Timestamp, idx int, entry *idEntry) {
	var hashValue [16]byte
	var hashValueRemoval [16]byte
	idHash := this.hasher(entry.id.Bytes())
	for entry.lastSec <= nowSec {
		idHash.Write(entry.lastSec.Bytes())
		idHash.Sum(hashValue[:0])
		idHash.Reset()

		idHash.Write(entry.lastSecRemoval.Bytes())
		idHash.Sum(hashValueRemoval[:0])
		idHash.Reset()

		this.Lock()
		this.userHash[hashValue] = &indexTimePair{idx, entry.lastSec}
		delete(this.userHash, hashValueRemoval)
		this.Unlock()

		entry.lastSec++
		entry.lastSecRemoval++
	}
}

func (this *TimedUserValidator) updateUserHash(interval time.Duration) {
L:
	for {
		select {
		case now := <-time.After(interval):
			nowSec := Timestamp(now.Unix() + cacheDurationSec)
			for _, entry := range this.ids {
				this.generateNewHashes(nowSec, entry.userIdx, entry)
			}
		case <-this.cancel.WaitForCancel():
			break L
		}
	}
	this.cancel.Done()
}

func (this *TimedUserValidator) Add(user *User) error {
	idx := len(this.validUsers)
	this.validUsers = append(this.validUsers, user)
	account := user.Account.(*VMessAccount)

	nowSec := time.Now().Unix()

	entry := &idEntry{
		id:             account.ID,
		userIdx:        idx,
		lastSec:        Timestamp(nowSec - cacheDurationSec),
		lastSecRemoval: Timestamp(nowSec - cacheDurationSec*3),
	}
	this.generateNewHashes(Timestamp(nowSec+cacheDurationSec), idx, entry)
	this.ids = append(this.ids, entry)
	for _, alterid := range account.AlterIDs {
		entry := &idEntry{
			id:             alterid,
			userIdx:        idx,
			lastSec:        Timestamp(nowSec - cacheDurationSec),
			lastSecRemoval: Timestamp(nowSec - cacheDurationSec*3),
		}
		this.generateNewHashes(Timestamp(nowSec+cacheDurationSec), idx, entry)
		this.ids = append(this.ids, entry)
	}

	return nil
}

func (this *TimedUserValidator) Get(userHash []byte) (*User, Timestamp, bool) {
	defer this.RUnlock()
	this.RLock()

	if !this.running {
		return nil, 0, false
	}
	var fixedSizeHash [16]byte
	copy(fixedSizeHash[:], userHash)
	pair, found := this.userHash[fixedSizeHash]
	if found {
		return this.validUsers[pair.index], pair.timeSec, true
	}
	return nil, 0, false
}
<|endoftext|>


<commit_before>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Git-codereview manages the code review process for Git changes using a Gerrit
server.

The git-codereview tool manages "change branches" in the local git repository.
Each such branch tracks a single commit, or "pending change",
that is reviewed using a Gerrit server; the Gerrit remote must be
named 'origin' in the local git repo.

Modifications to the pending change are applied by amending the commit.
This process implements the "single-commit feature branch" model.

Once installed as git-codereview, the tool's commands are available through git
either by running

	git codereview <command>

or, if aliases are installed, as

	git <command>

The review tool's command names do not conflict with any extant git commands.
This document uses the first form for clarity but most users install these
aliases in their .gitconfig file:

	[alias]
		change = codereview change
		gofmt = codereview gofmt
		mail = codereview mail
		pending = codereview pending
		rebase-work = codereview rebase-work
		submit = codereview submit
		sync = codereview sync

All commands accept these global flags:

The -v flag prints all commands that make changes.

The -n flag prints all commands that would be run, but does not run them.

Descriptions of each command follow.

Branchpoint

	git codereview branchpoint

The branchpoint command prints the commit hash of the most recent change
on the current branch that is shared with the Gerrit server. This is the point
where local work branched from the published tree. The command is intended
mainly for use in scripts. For example, "git diff $(git codereview branchpoint)"
or "git log $(git codereview branchpoint)..HEAD".

Change

The change command creates and moves between Git branches and maintains the
pending commits on work branches.

	git codereview change [-a] [-q] [branchname]

Given a branch name as an argument, the change command switches to the named
branch, creating it if necessary. If the branch is created and there are staged
changes, it will commit the changes to the branch, creating a new pending
change.

With no argument, the change command creates a new pending change from the
staged changes in the current branch or, if there is already a pending change,
amends that change.

The -q option skips the editing of an extant pending change's commit message.

The -a option automatically adds any unstaged changes in tracked files during
commit; it is equivalent to the 'git commit' -a option.

Gofmt

The gofmt command applies the gofmt program to all files modified in the
current work branch, both in the staging area (index) and the working tree
(local directory).

	git codereview gofmt [-l]

The -l option causes the command to list the files that need reformatting but
not reformat them. Otherwise, the gofmt command reformats modified files in
place. That is, files in the staging area are reformatted in the staging area,
and files in the working tree are reformatted in the working tree.

Help

The help command displays basic usage instructions.

	git codereview help

Hooks

The hooks command installs the Git hooks to enforce code review conventions.

	git codereview hooks

The pre-commit hook checks that all Go code is formatted with gofmt and that
the commit is not being made directly to the master branch.

The commit-msg hook adds the Gerrit "Change-Id" line to the commit message if
not present. It also checks that the message uses the convention established by
the Go project that the first line has the form, pkg/path: summary.

The hooks command will not overwrite an existing hook.
If it is not installing hooks, use 'git review hooks -v' for details.
This hook installation is also done at startup by all other git review
commands, except 'help'.

Hook-Invoke

The hook-invoke command is an internal command that invokes the named Git hook.

	git codereview hook-invoke <hook> [args]

It is run by the shell scripts installed by the "git review hooks" command.

Mail

The mail command starts the code review process for the pending change.

	git codereview mail [-f] [-r email] [-cc email]

It pushes the pending change commit in the current branch to the Gerrit code
review server and prints the URL for the change on the server.
If the change already exists on the server, the mail command updates that
change with a new changeset.

The -r and -cc flags identify the email addresses of people to do the code
review and to be CC'ed about the code review.
Multiple addresses are given as a comma-separated list.

An email address passed to -r or -cc can be shortened from name@domain to name.
The mail command resolves such shortenings by reading the list of past reviewers
from the git repository log to find email addresses of the form name@somedomain
and then, in case of ambiguity, using the reviewer who appears most often.

The mail command fails if there are staged changes that are not committed.
The -f flag overrides this behavior.

The mail command updates the tag <branchname>.mailed to refer to the
commit that was most recently mailed, so running 'git diff <branchname>.mailed'
shows diffs between what is on the Gerrit server and the current directory.

Pending

The pending command prints to standard output the status of all pending changes
and staged, unstaged, and untracked files in the local repository.

	git codereview pending [-l]

The -l flag causes the command to use only locally available information.
By default, it fetches recent commits and code review information from the
Gerrit server.

Rebase-work

The rebase-work command runs git rebase in interactive mode over pending changes.
It is shorthand for "git rebase -i $(git codereview branchpoint)".
It differs from plain "git rebase -i" in that the latter will try to incorporate
new commits from the origin branch during the rebase, and git rebase-work
does not.

Submit

The submit command pushes the pending change to the Gerrit server and tells
Gerrit to submit it to the master branch.

	git codereview submit

The command fails if there are modified files (staged or unstaged) that are not
part of the pending change.

After submitting the change, the change command tries to synchronize the
current branch to the submitted commit, if it can do so cleanly.
If not, it will prompt the user to run 'git review sync' manually.

After a successful sync, the branch can be used to prepare a new change.

Sync

The sync command updates the local repository.

	git codereview sync

It fetches changes from the remote repository and merges changes from the
upstream branch to the current branch, rebasing the pending change, if any,
onto those changes.

*/
package main
<commit_msg>git-codereview: fix references to 'git review' in doc.<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Git-codereview manages the code review process for Git changes using a Gerrit
server.

The git-codereview tool manages "change branches" in the local git repository.
Each such branch tracks a single commit, or "pending change",
that is reviewed using a Gerrit server; the Gerrit remote must be
named 'origin' in the local git repo.

Modifications to the pending change are applied by amending the commit.
This process implements the "single-commit feature branch" model.

Once installed as git-codereview, the tool's commands are available through git
either by running

	git codereview <command>

or, if aliases are installed, as

	git <command>

The review tool's command names do not conflict with any extant git commands.
This document uses the first form for clarity but most users install these
aliases in their .gitconfig file:

	[alias]
		change = codereview change
		gofmt = codereview gofmt
		mail = codereview mail
		pending = codereview pending
		rebase-work = codereview rebase-work
		submit = codereview submit
		sync = codereview sync

All commands accept these global flags:

The -v flag prints all commands that make changes.

The -n flag prints all commands that would be run, but does not run them.

Descriptions of each command follow.

Branchpoint

	git codereview branchpoint

The branchpoint command prints the commit hash of the most recent change
on the current branch that is shared with the Gerrit server. This is the point
where local work branched from the published tree. The command is intended
mainly for use in scripts. For example, "git diff $(git codereview branchpoint)"
or "git log $(git codereview branchpoint)..HEAD".

Change

The change command creates and moves between Git branches and maintains the
pending commits on work branches.

	git codereview change [-a] [-q] [branchname]

Given a branch name as an argument, the change command switches to the named
branch, creating it if necessary. If the branch is created and there are staged
changes, it will commit the changes to the branch, creating a new pending
change.

With no argument, the change command creates a new pending change from the
staged changes in the current branch or, if there is already a pending change,
amends that change.

The -q option skips the editing of an extant pending change's commit message.

The -a option automatically adds any unstaged changes in tracked files during
commit; it is equivalent to the 'git commit' -a option.

Gofmt

The gofmt command applies the gofmt program to all files modified in the
current work branch, both in the staging area (index) and the working tree
(local directory).

	git codereview gofmt [-l]

The -l option causes the command to list the files that need reformatting but
not reformat them. Otherwise, the gofmt command reformats modified files in
place. That is, files in the staging area are reformatted in the staging area,
and files in the working tree are reformatted in the working tree.

Help

The help command displays basic usage instructions.

	git codereview help

Hooks

The hooks command installs the Git hooks to enforce code review conventions.

	git codereview hooks

The pre-commit hook checks that all Go code is formatted with gofmt and that
the commit is not being made directly to the master branch.

The commit-msg hook adds the Gerrit "Change-Id" line to the commit message if
not present. It also checks that the message uses the convention established by
the Go project that the first line has the form, pkg/path: summary.

The hooks command will not overwrite an existing hook.
If it is not installing hooks, use 'git codereview hooks -v' for details.
This hook installation is also done at startup by all other git codereview
commands, except 'help'.

Hook-Invoke

The hook-invoke command is an internal command that invokes the named Git hook.

	git codereview hook-invoke <hook> [args]

It is run by the shell scripts installed by the "git codereview hooks" command.

Mail

The mail command starts the code review process for the pending change.

	git codereview mail [-f] [-r email] [-cc email]

It pushes the pending change commit in the current branch to the Gerrit code
review server and prints the URL for the change on the server.
If the change already exists on the server, the mail command updates that
change with a new changeset.

The -r and -cc flags identify the email addresses of people to do the code
review and to be CC'ed about the code review.
Multiple addresses are given as a comma-separated list.

An email address passed to -r or -cc can be shortened from name@domain to name.
The mail command resolves such shortenings by reading the list of past reviewers
from the git repository log to find email addresses of the form name@somedomain
and then, in case of ambiguity, using the reviewer who appears most often.

The mail command fails if there are staged changes that are not committed.
The -f flag overrides this behavior.

The mail command updates the tag <branchname>.mailed to refer to the
commit that was most recently mailed, so running 'git diff <branchname>.mailed'
shows diffs between what is on the Gerrit server and the current directory.

Pending

The pending command prints to standard output the status of all pending changes
and staged, unstaged, and untracked files in the local repository.

	git codereview pending [-l]

The -l flag causes the command to use only locally available information.
By default, it fetches recent commits and code review information from the
Gerrit server.

Rebase-work

The rebase-work command runs git rebase in interactive mode over pending changes.
It is shorthand for "git rebase -i $(git codereview branchpoint)".
It differs from plain "git rebase -i" in that the latter will try to incorporate
new commits from the origin branch during the rebase, and git rebase-work
does not.

Submit

The submit command pushes the pending change to the Gerrit server and tells
Gerrit to submit it to the master branch.

	git codereview submit

The command fails if there are modified files (staged or unstaged) that are not
part of the pending change.

After submitting the change, the change command tries to synchronize the
current branch to the submitted commit, if it can do so cleanly.
If not, it will prompt the user to run 'git codereview sync' manually.

After a successful sync, the branch can be used to prepare a new change.

Sync

The sync command updates the local repository.

	git codereview sync

It fetches changes from the remote repository and merges changes from the
upstream branch to the current branch, rebasing the pending change, if any,
onto those changes.

*/
package main
<|endoftext|>


<commit_before>package idols

import (
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/models"
	"github.com/bwmarrin/discordgo"
	"github.com/globalsign/mgo/bson"
)

const (
	GROUP_ALIAS_KEY = "groupAliases"
)

// maps real group name => aliases for group
var groupAliasesMap map[string][]string
var groupAliasMutex sync.RWMutex

// initAliases will load the aliases object from the database
func initAliases() {
	groupAliasesMap = make(map[string][]string)

	groupAliasMutex.Lock()
	getModuleCache(GROUP_ALIAS_KEY, &groupAliasesMap)
	groupAliasMutex.Unlock()
}

// getGroupAliases gets the current group aliases
func getGroupAliases() map[string][]string {
	groupAliasMutex.RLock()
	defer groupAliasMutex.RUnlock()
	return groupAliasesMap
}

// addGroupAlias will add an alias for a group or idol depending on the amount of arguments
func addAlias(msg *discordgo.Message, content string) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	// validate arguments
	commandArgs, err := helpers.ToArgv(content)
	if err != nil {
		helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
		return
	}

	// IDOL ALIAS
	if len(commandArgs) == 5 {
		addIdolAlias(msg, commandArgs[2], commandArgs[3], commandArgs[4])
		return
	}

	// GROUP ALIAS
	if len(commandArgs) == 4 {
		addGroupAlias(msg, commandArgs[2], commandArgs[3])
		return
	}

	helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
}

// addIdolAlias will add an alias for a idol
func addIdolAlias(msg *discordgo.Message, targetGroup string, targetName string, newAliasName string) {

	// check that the idol we're adding the alias too actually exists
	var targetIdol *Idol

	if _, _, targetIdol = GetMatchingIdolAndGroup(targetGroup, targetName, true); targetIdol == nil {
		helpers.SendMessage(msg.ChannelID, "Could not add alias for that idol because the idol could not be found.")
		return
	}

	// make map of group => []idol names and aliases
	groupIdolMap := make(map[string][]string)
	for _, idol := range GetAllIdols() {
		if idol.GroupName == targetIdol.GroupName {
			groupIdolMap[idol.GroupName] = append(groupIdolMap[idol.GroupName], idol.Name)
			groupIdolMap[idol.GroupName] = append(groupIdolMap[idol.GroupName], idol.NameAliases...)
		}
	}

	// confirm new alias doesn't match alias or name within a group
	for _, currentNamesOrAliases := range groupIdolMap {
		for _, currentName := range currentNamesOrAliases {
			if alphaNumericCompare(currentName, newAliasName) {
				helpers.SendMessage(msg.ChannelID, "That alias already exists for an idol in the group.")
				return
			}
		}
	}

	// add alias in memory
	allIdolsMutex.Lock()
	targetIdol.NameAliases = append(targetIdol.NameAliases, newAliasName)
	allIdolsMutex.Unlock()

	// update cache
	if len(GetAllIdols()) > 0 {
		setModuleCache(ALL_IDOLS_CACHE_KEY, GetAllIdols(), time.Hour*24*7)
	}

	// add alias in mongo
	var mongoIdol models.IdolEntry
	err := helpers.MdbOne(helpers.MdbCollection(models.IdolTable).Find(bson.M{"groupname": targetIdol.GroupName, "name": targetIdol.Name}), &mongoIdol)
	helpers.Relax(err)

	mongoIdol.NameAliases = append(mongoIdol.NameAliases, newAliasName)

	// save target idol with new images
	err = helpers.MDbUpsertID(models.IdolTable, mongoIdol.ID, mongoIdol)
	helpers.Relax(err)

	helpers.SendMessage(msg.ChannelID, fmt.Sprintf("The alias *%s* has been added for %s %s", newAliasName, targetIdol.GroupName, targetIdol.Name))
}

// addGroupAlias will add an alias for a group
func addGroupAlias(msg *discordgo.Message, targetGroup string, newAliasName string) {

	// check that the group we're adding the alias too actually exists
	if exists, realGroupName := GetMatchingGroup(targetGroup, true); exists == false {
		helpers.SendMessage(msg.ChannelID, "Could not add alias for that group because the group does not exist.")
		return
	} else {
		targetGroup = realGroupName
	}

	// make sure the alias doesn't match an existing group already
	if exists, matchinGroup := GetMatchingGroup(newAliasName, false); exists {
		helpers.SendMessage(msg.ChannelID, fmt.Sprintf("The alias you are trying to add already exists for the group **%s**", matchinGroup))
		return
	}

	// check if the alias already exists
	for curGroup, aliases := range getGroupAliases() {
		for _, alias := range aliases {
			if alphaNumericCompare(newAliasName, alias) {
				helpers.SendMessage(msg.ChannelID, fmt.Sprintf("This group alias already exists for the group **%s**", curGroup))
				return
			}
		}
	}

	// add the alias to the alias map
	groupAliasMutex.Lock()
	groupAliasesMap[targetGroup] = append(groupAliasesMap[targetGroup], newAliasName)
	groupAliasMutex.Unlock()

	// save to redis
	setModuleCache(GROUP_ALIAS_KEY, getGroupAliases(), 0)

	helpers.SendMessage(msg.ChannelID, fmt.Sprintf("The alias *%s* has been added for the group **%s**", newAliasName, targetGroup))
}

// deleteGroupAlias will delete the alias if it is found
func deleteIdolAlias(msg *discordgo.Message, commandArgs []string) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	targetGroup := commandArgs[2]
	targetName := commandArgs[3]
	aliasToDelete := commandArgs[4]

	var targetIdol *Idol
	if _, _, targetIdol = GetMatchingIdolAndGroup(targetGroup, targetName, false); targetIdol == nil {
		helpers.SendMessage(msg.ChannelID, helpers.GetText("plugins.biasgame.stats.no-matching-idol"))
		return
	}

	aliasFound := false
	for index, alias := range targetIdol.NameAliases {
		if alphaNumericCompare(alias, aliasToDelete) {
			aliasToDelete = alias
			aliasFound = true
			allIdolsMutex.Lock()
			targetIdol.NameAliases = append(targetIdol.NameAliases[:index], targetIdol.NameAliases[index+1:]...)
			allIdolsMutex.Unlock()
			break
		}
	}

	if aliasFound == false {
		helpers.SendMessage(msg.ChannelID, "That alias was not found for the given idol.")
		return
	}

	// update cache
	if len(GetAllIdols()) > 0 {
		setModuleCache(ALL_IDOLS_CACHE_KEY, GetAllIdols(), time.Hour*24*7)
	}

	var mongoIdol models.IdolEntry
	err := helpers.MdbOne(helpers.MdbCollection(models.IdolTable).Find(bson.M{"groupname": targetIdol.GroupName, "name": targetIdol.Name}), &mongoIdol)
	helpers.Relax(err)

	for index, alias := range mongoIdol.NameAliases {
		if alphaNumericCompare(alias, aliasToDelete) {
			mongoIdol.NameAliases = append(mongoIdol.NameAliases[:index], mongoIdol.NameAliases[index+1:]...)
			break
		}
	}
	err = helpers.MDbUpsertID(models.IdolTable, mongoIdol.ID, mongoIdol)
	helpers.Relax(err)

	helpers.SendMessage(msg.ChannelID, fmt.Sprintf("Deleted the alias *%s* from %s %s", aliasToDelete, targetIdol.GroupName, targetIdol.Name))
}

// deleteGroupAlias will delete the alias if it is found
func deleteGroupAlias(msg *discordgo.Message, commandArgs []string) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	aliasToDelete := commandArgs[2]

	// find and delete alias if one exists
	aliasDeleted := false
	regToDelete := strings.ToLower(alphaNumericRegex.ReplaceAllString(aliasToDelete, ""))
	groupAliasMutex.Lock()
GroupAliasLoop:
	for curGroup, aliases := range groupAliasesMap {
		for i, alias := range aliases {
			curAlias := strings.ToLower(alphaNumericRegex.ReplaceAllString(alias, ""))

			if curAlias == regToDelete {

				// if the alias is the last one for the group, remove the group from the alias map
				if len(aliases) == 1 {
					delete(groupAliasesMap, curGroup)
				} else {
					aliases = append(aliases[:i], aliases[i+1:]...)
					groupAliasesMap[curGroup] = aliases
				}

				aliasDeleted = true
				helpers.SendMessage(msg.ChannelID, fmt.Sprintf("Deleted the alias *%s* from the group **%s**", alias, curGroup))
				break GroupAliasLoop
			}
		}
	}
	groupAliasMutex.Unlock()

	// if no alias was deleted, send a message
	if aliasDeleted {
		// save to redis
		setModuleCache(GROUP_ALIAS_KEY, getGroupAliases(), 0)
	} else {
		helpers.SendMessage(msg.ChannelID, "Alias not found, no alias was deleted")
	}
}

// listAliases will list group aliases or idol name aliases for a group or idol
func listAliases(msg *discordgo.Message, content string) {
	contentArgs, err := helpers.ToArgv(content)
	helpers.Relax(err)

	// if enough args were passed, attempt to list aliases for idols
	switch len(contentArgs) {
	case 2:
		listGroupAliases(msg)
		break
	case 3:
		listNameAliasesByGroup(msg, contentArgs[2])
		break
	case 4:
		listNameAliases(msg, contentArgs[2], contentArgs[3])
		break
	default:
		helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))

	}
}

// listNameAliases lists aliases for a idol
func listNameAliases(msg *discordgo.Message, targetGroup string, targetName string) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	var targetIdol *Idol
	if _, _, targetIdol = GetMatchingIdolAndGroup(targetGroup, targetName, true); targetIdol == nil {
		helpers.SendMessage(msg.ChannelID, helpers.GetText("plugins.biasgame.stats.no-matching-idol"))
		return
	}

	// make sure there are aliases to display
	if len(targetIdol.NameAliases) == 0 {
		helpers.SendMessage(msg.ChannelID, "No aliases have been set for the given idol.")
		return
	}

	// set up base embed
	embed := &discordgo.MessageEmbed{
		Color: 0x0FADED,
		Author: &discordgo.MessageEmbedAuthor{
			Name:    fmt.Sprintf("Current aliases for %s %s", targetIdol.GroupName, targetIdol.Name),
			IconURL: msg.Author.AvatarURL("512"),
		},
	}

	embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
		Name:   "Name Aliases",
		Value:  strings.Join(targetIdol.NameAliases, ", "),
		Inline: true,
	})

	helpers.SendEmbed(msg.ChannelID, embed)
}

// listNameAliasesByGroup lists aliases for a group
func listNameAliasesByGroup(msg *discordgo.Message, targetGroup string) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	var realGroupName string
	if _, realGroupName = GetMatchingGroup(targetGroup, true); realGroupName == "" {
		helpers.SendMessage(msg.ChannelID, helpers.GetText("plugins.biasgame.stats.no-matching-group"))
		return
	}

	// set up base embed
	embed := &discordgo.MessageEmbed{
		Color: 0x0FADED,
		Author: &discordgo.MessageEmbedAuthor{
			Name:    fmt.Sprintf("Current aliases for %s", realGroupName),
			IconURL: msg.Author.AvatarURL("512"),
		},
	}

	// add field for group alias for the given group
	var aliasesForThisGroup []string
	for group, aliases := range getGroupAliases() {
		if alphaNumericCompare(realGroupName, group) {
			aliasesForThisGroup = aliases
		}
	}

	if len(aliasesForThisGroup) > 0 {

		embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
			Name:   "Group Aliases",
			Value:  strings.Join(aliasesForThisGroup, ", "),
			Inline: false,
		})
	}

	// add fields for name aliases for all idols
	for _, idol := range GetActiveIdols() {
		if realGroupName == idol.GroupName && len(idol.NameAliases) > 0 {

			embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
				Name:   idol.Name,
				Value:  strings.Join(idol.NameAliases, ", "),
				Inline: false,
			})
		}
	}

	// make sure there are aliases to display
	if len(embed.Fields) == 0 {
		helpers.SendMessage(msg.ChannelID, "No aliases have been set yet for the given group.")
		return
	}

	helpers.SendPagedMessage(msg, embed, 7)
}

// listGroupAliases will display the current group aliases in a embed message
func listGroupAliases(msg *discordgo.Message) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	// set up base embed
	embed := &discordgo.MessageEmbed{
		Color: 0x0FADED,
		Author: &discordgo.MessageEmbedAuthor{
			Name:    "Current group aliases",
			IconURL: msg.Author.AvatarURL("512"),
		},
	}

	groupAliases := getGroupAliases()

	// get group names into a slice so they can be sorted
	groups := make([]string, len(groupAliases))
	for group, _ := range groupAliases {
		groups = append(groups, group)
	}
	sort.Slice(groups, func(i, j int) bool {
		return groups[i] < groups[j]
	})

	// get aliases for each group and add them to the embed
	for _, groupName := range groups {
		aliases := groupAliases[groupName]

		// sort aliases
		sort.Slice(aliases, func(i, j int) bool {
			return aliases[i] < aliases[j]
		})

		// get the matching group, the aliases might have been saved before a small change was made to the real group name. i want to account for that
		if exists, realGroupName := GetMatchingGroup(groupName, true); exists {

			embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
				Name:   realGroupName,
				Value:  strings.Join(aliases, ", "),
				Inline: false,
			})
		}
	}

	// make sure there are aliases to display
	if len(embed.Fields) == 0 {
		helpers.SendMessage(msg.ChannelID, "No aliases have been set yet.")
		return
	}

	// send paged message with 7 fields per page
	helpers.SendPagedMessage(msg, embed, 7)
}

// getAlisesForGroup gets the aliases for a group if it exists.
//  first return will be false if the group was not found
func GetAlisesForGroup(targetGroup string) (bool, []string) {

	for aliasGroup, aliases := range getGroupAliases() {
		group := strings.ToLower(alphaNumericRegex.ReplaceAllString(aliasGroup, ""))

		if targetGroup == group {
			return true, aliases
		}
	}

	return false, nil
}
<commit_msg>[idol] fixes getting aliases for a group<commit_after>package idols

import (
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/models"
	"github.com/bwmarrin/discordgo"
	"github.com/globalsign/mgo/bson"
)

const (
	GROUP_ALIAS_KEY = "groupAliases"
)

// maps real group name => aliases for group
var groupAliasesMap map[string][]string
var groupAliasMutex sync.RWMutex

// initAliases will load the aliases object from the database
func initAliases() {
	groupAliasesMap = make(map[string][]string)

	groupAliasMutex.Lock()
	getModuleCache(GROUP_ALIAS_KEY, &groupAliasesMap)
	groupAliasMutex.Unlock()
}

// getGroupAliases gets the current group aliases
func getGroupAliases() map[string][]string {
	groupAliasMutex.RLock()
	defer groupAliasMutex.RUnlock()
	return groupAliasesMap
}

// addGroupAlias will add an alias for a group or idol depending on the amount of arguments
func addAlias(msg *discordgo.Message, content string) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	// validate arguments
	commandArgs, err := helpers.ToArgv(content)
	if err != nil {
		helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
		return
	}

	// IDOL ALIAS
	if len(commandArgs) == 5 {
		addIdolAlias(msg, commandArgs[2], commandArgs[3], commandArgs[4])
		return
	}

	// GROUP ALIAS
	if len(commandArgs) == 4 {
		addGroupAlias(msg, commandArgs[2], commandArgs[3])
		return
	}

	helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
}

// addIdolAlias will add an alias for a idol
func addIdolAlias(msg *discordgo.Message, targetGroup string, targetName string, newAliasName string) {

	// check that the idol we're adding the alias too actually exists
	var targetIdol *Idol

	if _, _, targetIdol = GetMatchingIdolAndGroup(targetGroup, targetName, true); targetIdol == nil {
		helpers.SendMessage(msg.ChannelID, "Could not add alias for that idol because the idol could not be found.")
		return
	}

	// make map of group => []idol names and aliases
	groupIdolMap := make(map[string][]string)
	for _, idol := range GetAllIdols() {
		if idol.GroupName == targetIdol.GroupName {
			groupIdolMap[idol.GroupName] = append(groupIdolMap[idol.GroupName], idol.Name)
			groupIdolMap[idol.GroupName] = append(groupIdolMap[idol.GroupName], idol.NameAliases...)
		}
	}

	// confirm new alias doesn't match alias or name within a group
	for _, currentNamesOrAliases := range groupIdolMap {
		for _, currentName := range currentNamesOrAliases {
			if alphaNumericCompare(currentName, newAliasName) {
				helpers.SendMessage(msg.ChannelID, "That alias already exists for an idol in the group.")
				return
			}
		}
	}

	// add alias in memory
	allIdolsMutex.Lock()
	targetIdol.NameAliases = append(targetIdol.NameAliases, newAliasName)
	allIdolsMutex.Unlock()

	// update cache
	if len(GetAllIdols()) > 0 {
		setModuleCache(ALL_IDOLS_CACHE_KEY, GetAllIdols(), time.Hour*24*7)
	}

	// add alias in mongo
	var mongoIdol models.IdolEntry
	err := helpers.MdbOne(helpers.MdbCollection(models.IdolTable).Find(bson.M{"groupname": targetIdol.GroupName, "name": targetIdol.Name}), &mongoIdol)
	helpers.Relax(err)

	mongoIdol.NameAliases = append(mongoIdol.NameAliases, newAliasName)

	// save target idol with new images
	err = helpers.MDbUpsertID(models.IdolTable, mongoIdol.ID, mongoIdol)
	helpers.Relax(err)

	helpers.SendMessage(msg.ChannelID, fmt.Sprintf("The alias *%s* has been added for %s %s", newAliasName, targetIdol.GroupName, targetIdol.Name))
}

// addGroupAlias will add an alias for a group
func addGroupAlias(msg *discordgo.Message, targetGroup string, newAliasName string) {

	// check that the group we're adding the alias too actually exists
	if exists, realGroupName := GetMatchingGroup(targetGroup, true); exists == false {
		helpers.SendMessage(msg.ChannelID, "Could not add alias for that group because the group does not exist.")
		return
	} else {
		targetGroup = realGroupName
	}

	// make sure the alias doesn't match an existing group already
	if exists, matchinGroup := GetMatchingGroup(newAliasName, false); exists {
		helpers.SendMessage(msg.ChannelID, fmt.Sprintf("The alias you are trying to add already exists for the group **%s**", matchinGroup))
		return
	}

	// check if the alias already exists
	for curGroup, aliases := range getGroupAliases() {
		for _, alias := range aliases {
			if alphaNumericCompare(newAliasName, alias) {
				helpers.SendMessage(msg.ChannelID, fmt.Sprintf("This group alias already exists for the group **%s**", curGroup))
				return
			}
		}
	}

	// add the alias to the alias map
	groupAliasMutex.Lock()
	groupAliasesMap[targetGroup] = append(groupAliasesMap[targetGroup], newAliasName)
	groupAliasMutex.Unlock()

	// save to redis
	setModuleCache(GROUP_ALIAS_KEY, getGroupAliases(), 0)

	helpers.SendMessage(msg.ChannelID, fmt.Sprintf("The alias *%s* has been added for the group **%s**", newAliasName, targetGroup))
}

// deleteGroupAlias will delete the alias if it is found
func deleteIdolAlias(msg *discordgo.Message, commandArgs []string) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	targetGroup := commandArgs[2]
	targetName := commandArgs[3]
	aliasToDelete := commandArgs[4]

	var targetIdol *Idol
	if _, _, targetIdol = GetMatchingIdolAndGroup(targetGroup, targetName, false); targetIdol == nil {
		helpers.SendMessage(msg.ChannelID, helpers.GetText("plugins.biasgame.stats.no-matching-idol"))
		return
	}

	aliasFound := false
	for index, alias := range targetIdol.NameAliases {
		if alphaNumericCompare(alias, aliasToDelete) {
			aliasToDelete = alias
			aliasFound = true
			allIdolsMutex.Lock()
			targetIdol.NameAliases = append(targetIdol.NameAliases[:index], targetIdol.NameAliases[index+1:]...)
			allIdolsMutex.Unlock()
			break
		}
	}

	if aliasFound == false {
		helpers.SendMessage(msg.ChannelID, "That alias was not found for the given idol.")
		return
	}

	// update cache
	if len(GetAllIdols()) > 0 {
		setModuleCache(ALL_IDOLS_CACHE_KEY, GetAllIdols(), time.Hour*24*7)
	}

	var mongoIdol models.IdolEntry
	err := helpers.MdbOne(helpers.MdbCollection(models.IdolTable).Find(bson.M{"groupname": targetIdol.GroupName, "name": targetIdol.Name}), &mongoIdol)
	helpers.Relax(err)

	for index, alias := range mongoIdol.NameAliases {
		if alphaNumericCompare(alias, aliasToDelete) {
			mongoIdol.NameAliases = append(mongoIdol.NameAliases[:index], mongoIdol.NameAliases[index+1:]...)
			break
		}
	}
	err = helpers.MDbUpsertID(models.IdolTable, mongoIdol.ID, mongoIdol)
	helpers.Relax(err)

	helpers.SendMessage(msg.ChannelID, fmt.Sprintf("Deleted the alias *%s* from %s %s", aliasToDelete, targetIdol.GroupName, targetIdol.Name))
}

// deleteGroupAlias will delete the alias if it is found
func deleteGroupAlias(msg *discordgo.Message, commandArgs []string) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	aliasToDelete := commandArgs[2]

	// find and delete alias if one exists
	aliasDeleted := false
	regToDelete := strings.ToLower(alphaNumericRegex.ReplaceAllString(aliasToDelete, ""))
	groupAliasMutex.Lock()
GroupAliasLoop:
	for curGroup, aliases := range groupAliasesMap {
		for i, alias := range aliases {
			curAlias := strings.ToLower(alphaNumericRegex.ReplaceAllString(alias, ""))

			if curAlias == regToDelete {

				// if the alias is the last one for the group, remove the group from the alias map
				if len(aliases) == 1 {
					delete(groupAliasesMap, curGroup)
				} else {
					aliases = append(aliases[:i], aliases[i+1:]...)
					groupAliasesMap[curGroup] = aliases
				}

				aliasDeleted = true
				helpers.SendMessage(msg.ChannelID, fmt.Sprintf("Deleted the alias *%s* from the group **%s**", alias, curGroup))
				break GroupAliasLoop
			}
		}
	}
	groupAliasMutex.Unlock()

	// if no alias was deleted, send a message
	if aliasDeleted {
		// save to redis
		setModuleCache(GROUP_ALIAS_KEY, getGroupAliases(), 0)
	} else {
		helpers.SendMessage(msg.ChannelID, "Alias not found, no alias was deleted")
	}
}

// listAliases will list group aliases or idol name aliases for a group or idol
func listAliases(msg *discordgo.Message, content string) {
	contentArgs, err := helpers.ToArgv(content)
	helpers.Relax(err)

	// if enough args were passed, attempt to list aliases for idols
	switch len(contentArgs) {
	case 2:
		listGroupAliases(msg)
		break
	case 3:
		listNameAliasesByGroup(msg, contentArgs[2])
		break
	case 4:
		listNameAliases(msg, contentArgs[2], contentArgs[3])
		break
	default:
		helpers.SendMessage(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))

	}
}

// listNameAliases lists aliases for a idol
func listNameAliases(msg *discordgo.Message, targetGroup string, targetName string) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	var targetIdol *Idol
	if _, _, targetIdol = GetMatchingIdolAndGroup(targetGroup, targetName, true); targetIdol == nil {
		helpers.SendMessage(msg.ChannelID, helpers.GetText("plugins.biasgame.stats.no-matching-idol"))
		return
	}

	// make sure there are aliases to display
	if len(targetIdol.NameAliases) == 0 {
		helpers.SendMessage(msg.ChannelID, "No aliases have been set for the given idol.")
		return
	}

	// set up base embed
	embed := &discordgo.MessageEmbed{
		Color: 0x0FADED,
		Author: &discordgo.MessageEmbedAuthor{
			Name:    fmt.Sprintf("Current aliases for %s %s", targetIdol.GroupName, targetIdol.Name),
			IconURL: msg.Author.AvatarURL("512"),
		},
	}

	embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
		Name:   "Name Aliases",
		Value:  strings.Join(targetIdol.NameAliases, ", "),
		Inline: true,
	})

	helpers.SendEmbed(msg.ChannelID, embed)
}

// listNameAliasesByGroup lists aliases for a group
func listNameAliasesByGroup(msg *discordgo.Message, targetGroup string) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	var realGroupName string
	if _, realGroupName = GetMatchingGroup(targetGroup, true); realGroupName == "" {
		helpers.SendMessage(msg.ChannelID, helpers.GetText("plugins.biasgame.stats.no-matching-group"))
		return
	}

	// set up base embed
	embed := &discordgo.MessageEmbed{
		Color: 0x0FADED,
		Author: &discordgo.MessageEmbedAuthor{
			Name:    fmt.Sprintf("Current aliases for %s", realGroupName),
			IconURL: msg.Author.AvatarURL("512"),
		},
	}

	// add field for group alias for the given group
	var aliasesForThisGroup []string
	for group, aliases := range getGroupAliases() {
		if alphaNumericCompare(realGroupName, group) {
			aliasesForThisGroup = aliases
		}
	}

	if len(aliasesForThisGroup) > 0 {

		embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
			Name:   "Group Aliases",
			Value:  strings.Join(aliasesForThisGroup, ", "),
			Inline: false,
		})
	}

	// add fields for name aliases for all idols
	for _, idol := range GetActiveIdols() {
		if realGroupName == idol.GroupName && len(idol.NameAliases) > 0 {

			embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
				Name:   idol.Name,
				Value:  strings.Join(idol.NameAliases, ", "),
				Inline: false,
			})
		}
	}

	// make sure there are aliases to display
	if len(embed.Fields) == 0 {
		helpers.SendMessage(msg.ChannelID, "No aliases have been set yet for the given group.")
		return
	}

	helpers.SendPagedMessage(msg, embed, 7)
}

// listGroupAliases will display the current group aliases in a embed message
func listGroupAliases(msg *discordgo.Message) {
	cache.GetSession().ChannelTyping(msg.ChannelID)

	// set up base embed
	embed := &discordgo.MessageEmbed{
		Color: 0x0FADED,
		Author: &discordgo.MessageEmbedAuthor{
			Name:    "Current group aliases",
			IconURL: msg.Author.AvatarURL("512"),
		},
	}

	groupAliases := getGroupAliases()

	// get group names into a slice so they can be sorted
	groups := make([]string, len(groupAliases))
	for group, _ := range groupAliases {
		groups = append(groups, group)
	}
	sort.Slice(groups, func(i, j int) bool {
		return groups[i] < groups[j]
	})

	// get aliases for each group and add them to the embed
	for _, groupName := range groups {
		aliases := groupAliases[groupName]

		// sort aliases
		sort.Slice(aliases, func(i, j int) bool {
			return aliases[i] < aliases[j]
		})

		// get the matching group, the aliases might have been saved before a small change was made to the real group name. i want to account for that
		if exists, realGroupName := GetMatchingGroup(groupName, true); exists {

			embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{
				Name:   realGroupName,
				Value:  strings.Join(aliases, ", "),
				Inline: false,
			})
		}
	}

	// make sure there are aliases to display
	if len(embed.Fields) == 0 {
		helpers.SendMessage(msg.ChannelID, "No aliases have been set yet.")
		return
	}

	// send paged message with 7 fields per page
	helpers.SendPagedMessage(msg, embed, 7)
}

// getAlisesForGroup gets the aliases for a group if it exists.
//  first return will be false if the group was not found
func GetAlisesForGroup(targetGroup string) (bool, []string) {

	for aliasGroup, aliases := range getGroupAliases() {

		if alphaNumericCompare(targetGroup, aliasGroup) {
			return true, aliases
		}
	}

	return false, nil
}
<|endoftext|>