<commit_before>package config

import (
	"database/sql"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"github.com/spf13/viper"
)

type DBPool struct {
	Falcon    *gorm.DB
	Graph     *gorm.DB
	Uic       *gorm.DB
	Dashboard *gorm.DB
	Alarm     *gorm.DB

	//fastweb only
	Boss *gorm.DB
}

var (
	dbp DBPool
)

func Con() DBPool {
	return dbp
}

func SetLogLevel(loggerlevel bool) {
	dbp.Uic.LogMode(loggerlevel)
	dbp.Graph.LogMode(loggerlevel)
	dbp.Falcon.LogMode(loggerlevel)
	dbp.Dashboard.LogMode(loggerlevel)
	dbp.Alarm.LogMode(loggerlevel)
	dbp.Boss.LogMode(loggerlevel)
}

func InitDB(loggerlevel bool) (err error) {
	var p *sql.DB
	portal, err := gorm.Open("mysql", viper.GetString("db.faclon_portal"))
	portal.Dialect().SetDB(p)
	portal.LogMode(loggerlevel)
	if err != nil {
		return
	}
	portal.SingularTable(true)
	dbp.Falcon = portal

	var g *sql.DB
	graphd, err := gorm.Open("mysql", viper.GetString("db.graph"))
	graphd.Dialect().SetDB(g)
	graphd.LogMode(loggerlevel)
	if err != nil {
		return
	}
	graphd.SingularTable(true)
	dbp.Graph = graphd

	var u *sql.DB
	uicd, err := gorm.Open("mysql", viper.GetString("db.uic"))
	uicd.Dialect().SetDB(u)
	uicd.LogMode(loggerlevel)
	if err != nil {
		return
	}
	uicd.SingularTable(true)
	dbp.Uic = uicd

	var d *sql.DB
	dashd, err := gorm.Open("mysql", viper.GetString("db.dashboard"))
	dashd.Dialect().SetDB(d)
	dashd.LogMode(loggerlevel)
	if err != nil {
		return
	}
	dashd.SingularTable(true)
	dbp.Dashboard = dashd

	var alm *sql.DB
	almd, err := gorm.Open("mysql", viper.GetString("db.alarms"))
	almd.Dialect().SetDB(alm)
	almd.LogMode(loggerlevel)
	if err != nil {
		return
	}
	almd.SingularTable(true)
	dbp.Alarm = almd

	//fastweb only
	var b *sql.DB
	bossd, err := gorm.Open("mysql", viper.GetString("db.boss"))
	bossd.Dialect().SetDB(b)
	dashd.LogMode(loggerlevel)
	if err != nil {
		return
	}
	bossd.SingularTable(true)
	dbp.Boss = bossd

	SetLogLevel(loggerlevel)
	return
}

func CloseDB() (err error) {
	err = dbp.Falcon.Close()
	if err != nil {
		return
	}
	err = dbp.Graph.Close()
	if err != nil {
		return
	}
	err = dbp.Uic.Close()
	if err != nil {
		return
	}

	err = dbp.Dashboard.Close()
	if err != nil {
		return
	}
	err = dbp.Alarm.Close()
	if err != nil {
		return
	}

	//fastweb only
	err = dbp.Boss.Close()
	if err != nil {
		return
	}
	return
}

func (db DBPool) HealthCheck() (errorBool int, errorTable []string) {
	errorTable = []string{}
	//0 means ok!, 1 means problem
	errorBool = 0
	if err := db.Boss.DB().Ping(); err != nil {
		errorTable = append(errorTable, "boss")
		errorBool = 1
	}
	if err := db.Dashboard.DB().Ping(); err != nil {
		errorTable = append(errorTable, "dashboard")
		errorBool = 1
	}
	if err := db.Falcon.DB().Ping(); err != nil {
		errorTable = append(errorTable, "falcon")
		errorBool = 1
	}
	if err := db.Uic.DB().Ping(); err != nil {
		errorTable = append(errorTable, "uic")
		errorBool = 1
	}
	if err := db.Alarm.DB().Ping(); err != nil {
		errorTable = append(errorTable, "alarm")
		errorBool = 1
	}
	return
}
<commit_msg>add more clearly debugging logs of database connection init<commit_after>package config

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"github.com/spf13/viper"
)

type DBPool struct {
	Falcon    *gorm.DB
	Graph     *gorm.DB
	Uic       *gorm.DB
	Dashboard *gorm.DB
	Alarm     *gorm.DB

	//fastweb only
	Boss *gorm.DB
}

var (
	dbp DBPool
)

func Con() DBPool {
	return dbp
}

func SetLogLevel(loggerlevel bool) {
	dbp.Uic.LogMode(loggerlevel)
	dbp.Graph.LogMode(loggerlevel)
	dbp.Falcon.LogMode(loggerlevel)
	dbp.Dashboard.LogMode(loggerlevel)
	dbp.Alarm.LogMode(loggerlevel)
	dbp.Boss.LogMode(loggerlevel)
}

func InitDB(loggerlevel bool) (err error) {
	var p *sql.DB
	portal, err := gorm.Open("mysql", viper.GetString("db.faclon_portal"))
	portal.Dialect().SetDB(p)
	portal.LogMode(loggerlevel)
	if err != nil {
		return fmt.Errorf("connect to falcon_portal: %s", err.Error())
	}
	portal.SingularTable(true)
	dbp.Falcon = portal

	var g *sql.DB
	graphd, err := gorm.Open("mysql", viper.GetString("db.graph"))
	graphd.Dialect().SetDB(g)
	graphd.LogMode(loggerlevel)
	if err != nil {
		return fmt.Errorf("connect to graph: %s", err.Error())
	}
	graphd.SingularTable(true)
	dbp.Graph = graphd

	var u *sql.DB
	uicd, err := gorm.Open("mysql", viper.GetString("db.uic"))
	uicd.Dialect().SetDB(u)
	uicd.LogMode(loggerlevel)
	if err != nil {
		return fmt.Errorf("connect to uic: %s", err.Error())
	}
	uicd.SingularTable(true)
	dbp.Uic = uicd

	var d *sql.DB
	dashd, err := gorm.Open("mysql", viper.GetString("db.dashboard"))
	dashd.Dialect().SetDB(d)
	dashd.LogMode(loggerlevel)
	if err != nil {
		return fmt.Errorf("connect to dashboard: %s", err.Error())
	}
	dashd.SingularTable(true)
	dbp.Dashboard = dashd

	var alm *sql.DB
	almd, err := gorm.Open("mysql", viper.GetString("db.alarms"))
	almd.Dialect().SetDB(alm)
	almd.LogMode(loggerlevel)
	if err != nil {
		return fmt.Errorf("connect to alarms: %s", err.Error())
	}
	almd.SingularTable(true)
	dbp.Alarm = almd

	//fastweb only
	var b *sql.DB
	bossd, err := gorm.Open("mysql", viper.GetString("db.boss"))
	bossd.Dialect().SetDB(b)
	dashd.LogMode(loggerlevel)
	if err != nil {
		return fmt.Errorf("connect to boss: %s", err.Error())
	}
	bossd.SingularTable(true)
	dbp.Boss = bossd

	SetLogLevel(loggerlevel)
	return
}

func CloseDB() (err error) {
	err = dbp.Falcon.Close()
	if err != nil {
		return
	}
	err = dbp.Graph.Close()
	if err != nil {
		return
	}
	err = dbp.Uic.Close()
	if err != nil {
		return
	}

	err = dbp.Dashboard.Close()
	if err != nil {
		return
	}
	err = dbp.Alarm.Close()
	if err != nil {
		return
	}

	//fastweb only
	err = dbp.Boss.Close()
	if err != nil {
		return
	}
	return
}

func (db DBPool) HealthCheck() (errorBool int, errorTable []string) {
	errorTable = []string{}
	//0 means ok!, 1 means problem
	errorBool = 0
	if err := db.Boss.DB().Ping(); err != nil {
		errorTable = append(errorTable, "boss")
		errorBool = 1
	}
	if err := db.Dashboard.DB().Ping(); err != nil {
		errorTable = append(errorTable, "dashboard")
		errorBool = 1
	}
	if err := db.Falcon.DB().Ping(); err != nil {
		errorTable = append(errorTable, "falcon")
		errorBool = 1
	}
	if err := db.Uic.DB().Ping(); err != nil {
		errorTable = append(errorTable, "uic")
		errorBool = 1
	}
	if err := db.Alarm.DB().Ping(); err != nil {
		errorTable = append(errorTable, "alarm")
		errorBool = 1
	}
	return
}
<|endoftext|>


<commit_before>package v1alpha1

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

const (
	// BackupLocationResourceName is name for "backuplocation" resource
	BackupLocationResourceName = "backuplocation"
	// BackupLocationResourcePlural is plural for "backuplocation" resource
	BackupLocationResourcePlural = "backuplocations"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// BackupLocation represents a backuplocation object
type BackupLocation struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Location          BackupLocationItem `json:"location"`
}

// BackupLocationItem is the spec used to store a backup location
// Only one of S3Config, AzureConfig or GoogleConfig should be specified and
// should match the Type field. Members of the config can be specified inline or
// through the SecretConfig
type BackupLocationItem struct {
	Type BackupLocationType `json:"type"`
	// Path is either the bucket or any other path for the backup location
	Path                 string        `json:"path"`
	EncryptionKey        string        `json:"encryptionKey"`
	S3Config             *S3Config     `json:"s3Config,omitempty"`
	AzureConfig          *AzureConfig  `json:"azureConfig,omitempty"`
	GoogleConfig         *GoogleConfig `json:"googleConfig,omitempty"`
	SecretConfig         string        `json:"secretConfig"`
	Sync                 bool          `json:"sync"`
	GenericBackupRepoKey string        `json:"genericBackupRepoKey"`
}

// BackupLocationType is the type of the backup location
type BackupLocationType string

const (
	// BackupLocationS3 stores the backup in an S3-compliant objectstore
	BackupLocationS3 BackupLocationType = "s3"
	// BackupLocationAzure stores the backup in Azure Blob Storage
	BackupLocationAzure BackupLocationType = "azure"
	// BackupLocationGoogle stores the backup in Google Cloud Storage
	BackupLocationGoogle BackupLocationType = "google"
)

// S3Config speficies the config required to connect to an S3-compliant
// objectstore
type S3Config struct {
	// Endpoint will be defaulted to s3.amazonaws.com by the controller if not provided
	Endpoint        string `json:"endpoint"`
	AccessKeyID     string `json:"accessKeyID"`
	SecretAccessKey string `json:"secretAccessKey"`
	// Region will be defaulted to us-east-1 by the controller if not provided
	Region string `json:"region"`
	// Disable SSL option if using with a non-AWS S3 objectstore which doesn't
	// have SSL enabled
	DisableSSL bool `json:"disableSSL"`
	// The S3 Storage Class to use when uploading objects. Glacier storage
	// classes are not supported
	StorageClass string `json:"storageClass"`
}

// AzureConfig specifies the config required to connect to Azure Blob Storage
type AzureConfig struct {
	StorageAccountName string `json:"storageAccountName"`
	StorageAccountKey  string `json:"storageAccountKey"`
}

// GoogleConfig specifies the config required to connect to Google Cloud Storage
type GoogleConfig struct {
	ProjectID  string `json:"projectID"`
	AccountKey string `json:"accountKey"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// BackupLocationList is a list of ApplicationBackups
type BackupLocationList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`

	Items []BackupLocation `json:"items"`
}

// UpdateFromSecret updated the config information from the secret if not provided inline
func (bl *BackupLocation) UpdateFromSecret(client kubernetes.Interface) error {
	if bl.Location.SecretConfig != "" {
		secretConfig, err := client.CoreV1().Secrets(bl.Namespace).Get(context.TODO(), bl.Location.SecretConfig, metav1.GetOptions{})
		if err != nil {
			return fmt.Errorf("error getting secretConfig for backupLocation: %v", err)
		}
		if val, ok := secretConfig.Data["encryptionKey"]; ok && val != nil {
			bl.Location.EncryptionKey = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["path"]; ok && val != nil {
			bl.Location.Path = strings.TrimSuffix(string(val), "\n")
		}
	}
	switch bl.Location.Type {
	case BackupLocationS3:
		return bl.getMergedS3Config(client)
	case BackupLocationAzure:
		return bl.getMergedAzureConfig(client)
	case BackupLocationGoogle:
		return bl.getMergedGoogleConfig(client)
	default:
		return fmt.Errorf("Invalid BackupLocation type %v", bl.Location.Type)
	}
}

func (bl *BackupLocation) getMergedS3Config(client kubernetes.Interface) error {
	if bl.Location.S3Config == nil {
		bl.Location.S3Config = &S3Config{}
		bl.Location.S3Config.Endpoint = "s3.amazonaws.com"
		bl.Location.S3Config.Region = "us-east-1"
		bl.Location.S3Config.DisableSSL = false
	}
	if bl.Location.SecretConfig != "" {
		secretConfig, err := client.CoreV1().Secrets(bl.Namespace).Get(context.TODO(), bl.Location.SecretConfig, metav1.GetOptions{})
		if err != nil {
			return fmt.Errorf("error getting secretConfig for backupLocation: %v", err)
		}
		if val, ok := secretConfig.Data["endpoint"]; ok && val != nil {
			bl.Location.S3Config.Endpoint = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["accessKeyID"]; ok && val != nil {
			bl.Location.S3Config.AccessKeyID = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["secretAccessKey"]; ok && val != nil {
			bl.Location.S3Config.SecretAccessKey = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["region"]; ok && val != nil {
			bl.Location.S3Config.Region = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["disableSSL"]; ok && val != nil {
			bl.Location.S3Config.DisableSSL, err = strconv.ParseBool(strings.TrimSuffix(string(val), "\n"))
			if err != nil {
				return fmt.Errorf("error parding disableSSL from Secret: %v", err)
			}
		}
		if val, ok := secretConfig.Data["storageClass"]; ok && val != nil {
			bl.Location.S3Config.StorageClass = strings.TrimSuffix(string(val), "\n")
		}
	}
	return nil
}

func (bl *BackupLocation) getMergedAzureConfig(client kubernetes.Interface) error {
	if bl.Location.AzureConfig == nil {
		bl.Location.AzureConfig = &AzureConfig{}
	}
	if bl.Location.SecretConfig != "" {
		secretConfig, err := client.CoreV1().Secrets(bl.Namespace).Get(context.TODO(), bl.Location.SecretConfig, metav1.GetOptions{})
		if err != nil {
			return fmt.Errorf("error getting secretConfig for backupLocation: %v", err)
		}
		if val, ok := secretConfig.Data["storageAccountName"]; ok && val != nil {
			bl.Location.AzureConfig.StorageAccountName = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["storageAccountKey"]; ok && val != nil {
			bl.Location.AzureConfig.StorageAccountKey = strings.TrimSuffix(string(val), "\n")
		}
	}
	return nil
}

func (bl *BackupLocation) getMergedGoogleConfig(client kubernetes.Interface) error {
	if bl.Location.GoogleConfig == nil {
		bl.Location.GoogleConfig = &GoogleConfig{}
	}
	if bl.Location.SecretConfig != "" {
		secretConfig, err := client.CoreV1().Secrets(bl.Namespace).Get(context.TODO(), bl.Location.SecretConfig, metav1.GetOptions{})
		if err != nil {
			return fmt.Errorf("error getting secretConfig for backupLocation: %v", err)
		}
		if val, ok := secretConfig.Data["projectID"]; ok && val != nil {
			bl.Location.GoogleConfig.ProjectID = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["accountKey"]; ok && val != nil {
			bl.Location.GoogleConfig.AccountKey = strings.TrimSuffix(string(val), "\n")
		}
	}
	return nil
}
<commit_msg>stor:468 Changed the field name GenericBackupRepoKey to RepositoryPassword in backuplocation CR<commit_after>package v1alpha1

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

const (
	// BackupLocationResourceName is name for "backuplocation" resource
	BackupLocationResourceName = "backuplocation"
	// BackupLocationResourcePlural is plural for "backuplocation" resource
	BackupLocationResourcePlural = "backuplocations"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// BackupLocation represents a backuplocation object
type BackupLocation struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	Location          BackupLocationItem `json:"location"`
}

// BackupLocationItem is the spec used to store a backup location
// Only one of S3Config, AzureConfig or GoogleConfig should be specified and
// should match the Type field. Members of the config can be specified inline or
// through the SecretConfig
type BackupLocationItem struct {
	Type BackupLocationType `json:"type"`
	// Path is either the bucket or any other path for the backup location
	Path               string        `json:"path"`
	EncryptionKey      string        `json:"encryptionKey"`
	S3Config           *S3Config     `json:"s3Config,omitempty"`
	AzureConfig        *AzureConfig  `json:"azureConfig,omitempty"`
	GoogleConfig       *GoogleConfig `json:"googleConfig,omitempty"`
	SecretConfig       string        `json:"secretConfig"`
	Sync               bool          `json:"sync"`
	RepositoryPassword string        `json:"repositoryPassword"`
}

// BackupLocationType is the type of the backup location
type BackupLocationType string

const (
	// BackupLocationS3 stores the backup in an S3-compliant objectstore
	BackupLocationS3 BackupLocationType = "s3"
	// BackupLocationAzure stores the backup in Azure Blob Storage
	BackupLocationAzure BackupLocationType = "azure"
	// BackupLocationGoogle stores the backup in Google Cloud Storage
	BackupLocationGoogle BackupLocationType = "google"
)

// S3Config speficies the config required to connect to an S3-compliant
// objectstore
type S3Config struct {
	// Endpoint will be defaulted to s3.amazonaws.com by the controller if not provided
	Endpoint        string `json:"endpoint"`
	AccessKeyID     string `json:"accessKeyID"`
	SecretAccessKey string `json:"secretAccessKey"`
	// Region will be defaulted to us-east-1 by the controller if not provided
	Region string `json:"region"`
	// Disable SSL option if using with a non-AWS S3 objectstore which doesn't
	// have SSL enabled
	DisableSSL bool `json:"disableSSL"`
	// The S3 Storage Class to use when uploading objects. Glacier storage
	// classes are not supported
	StorageClass string `json:"storageClass"`
}

// AzureConfig specifies the config required to connect to Azure Blob Storage
type AzureConfig struct {
	StorageAccountName string `json:"storageAccountName"`
	StorageAccountKey  string `json:"storageAccountKey"`
}

// GoogleConfig specifies the config required to connect to Google Cloud Storage
type GoogleConfig struct {
	ProjectID  string `json:"projectID"`
	AccountKey string `json:"accountKey"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// BackupLocationList is a list of ApplicationBackups
type BackupLocationList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata,omitempty"`

	Items []BackupLocation `json:"items"`
}

// UpdateFromSecret updated the config information from the secret if not provided inline
func (bl *BackupLocation) UpdateFromSecret(client kubernetes.Interface) error {
	if bl.Location.SecretConfig != "" {
		secretConfig, err := client.CoreV1().Secrets(bl.Namespace).Get(context.TODO(), bl.Location.SecretConfig, metav1.GetOptions{})
		if err != nil {
			return fmt.Errorf("error getting secretConfig for backupLocation: %v", err)
		}
		if val, ok := secretConfig.Data["encryptionKey"]; ok && val != nil {
			bl.Location.EncryptionKey = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["path"]; ok && val != nil {
			bl.Location.Path = strings.TrimSuffix(string(val), "\n")
		}
	}
	switch bl.Location.Type {
	case BackupLocationS3:
		return bl.getMergedS3Config(client)
	case BackupLocationAzure:
		return bl.getMergedAzureConfig(client)
	case BackupLocationGoogle:
		return bl.getMergedGoogleConfig(client)
	default:
		return fmt.Errorf("Invalid BackupLocation type %v", bl.Location.Type)
	}
}

func (bl *BackupLocation) getMergedS3Config(client kubernetes.Interface) error {
	if bl.Location.S3Config == nil {
		bl.Location.S3Config = &S3Config{}
		bl.Location.S3Config.Endpoint = "s3.amazonaws.com"
		bl.Location.S3Config.Region = "us-east-1"
		bl.Location.S3Config.DisableSSL = false
	}
	if bl.Location.SecretConfig != "" {
		secretConfig, err := client.CoreV1().Secrets(bl.Namespace).Get(context.TODO(), bl.Location.SecretConfig, metav1.GetOptions{})
		if err != nil {
			return fmt.Errorf("error getting secretConfig for backupLocation: %v", err)
		}
		if val, ok := secretConfig.Data["endpoint"]; ok && val != nil {
			bl.Location.S3Config.Endpoint = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["accessKeyID"]; ok && val != nil {
			bl.Location.S3Config.AccessKeyID = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["secretAccessKey"]; ok && val != nil {
			bl.Location.S3Config.SecretAccessKey = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["region"]; ok && val != nil {
			bl.Location.S3Config.Region = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["disableSSL"]; ok && val != nil {
			bl.Location.S3Config.DisableSSL, err = strconv.ParseBool(strings.TrimSuffix(string(val), "\n"))
			if err != nil {
				return fmt.Errorf("error parding disableSSL from Secret: %v", err)
			}
		}
		if val, ok := secretConfig.Data["storageClass"]; ok && val != nil {
			bl.Location.S3Config.StorageClass = strings.TrimSuffix(string(val), "\n")
		}
	}
	return nil
}

func (bl *BackupLocation) getMergedAzureConfig(client kubernetes.Interface) error {
	if bl.Location.AzureConfig == nil {
		bl.Location.AzureConfig = &AzureConfig{}
	}
	if bl.Location.SecretConfig != "" {
		secretConfig, err := client.CoreV1().Secrets(bl.Namespace).Get(context.TODO(), bl.Location.SecretConfig, metav1.GetOptions{})
		if err != nil {
			return fmt.Errorf("error getting secretConfig for backupLocation: %v", err)
		}
		if val, ok := secretConfig.Data["storageAccountName"]; ok && val != nil {
			bl.Location.AzureConfig.StorageAccountName = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["storageAccountKey"]; ok && val != nil {
			bl.Location.AzureConfig.StorageAccountKey = strings.TrimSuffix(string(val), "\n")
		}
	}
	return nil
}

func (bl *BackupLocation) getMergedGoogleConfig(client kubernetes.Interface) error {
	if bl.Location.GoogleConfig == nil {
		bl.Location.GoogleConfig = &GoogleConfig{}
	}
	if bl.Location.SecretConfig != "" {
		secretConfig, err := client.CoreV1().Secrets(bl.Namespace).Get(context.TODO(), bl.Location.SecretConfig, metav1.GetOptions{})
		if err != nil {
			return fmt.Errorf("error getting secretConfig for backupLocation: %v", err)
		}
		if val, ok := secretConfig.Data["projectID"]; ok && val != nil {
			bl.Location.GoogleConfig.ProjectID = strings.TrimSuffix(string(val), "\n")
		}
		if val, ok := secretConfig.Data["accountKey"]; ok && val != nil {
			bl.Location.GoogleConfig.AccountKey = strings.TrimSuffix(string(val), "\n")
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package hyperspace

import (
	"encoding/json"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"nimona.io/internal/context"
	"nimona.io/internal/store/graph"
	"nimona.io/pkg/crypto"
	"nimona.io/pkg/discovery"
	"nimona.io/pkg/middleware/handshake"
	"nimona.io/pkg/net"
	"nimona.io/pkg/object/exchange"
)

func TestDiscoverer_BootstrapLookup(t *testing.T) {
	_, k0, n0, x0, disc0, l0, ctx0 := newPeer(t, "peer0")
	_, k1, n1, x1, disc1, l1, ctx1 := newPeer(t, "peer1")

	d0, err := NewDiscoverer(ctx0, n0, x0, l0, []string{})
	assert.NoError(t, err)
	err = disc0.AddProvider(d0)
	assert.NoError(t, err)

	ba := l0.GetPeerInfo().Addresses

	d1, err := NewDiscoverer(ctx1, n1, x1, l1, ba)
	assert.NoError(t, err)
	err = disc1.AddProvider(d1)
	assert.NoError(t, err)

	ctxR1 := context.New(context.WithCorrelationID("req1"))
	peers, err := d1.FindByFingerprint(ctxR1, k0.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l0.GetPeerInfo().Addresses, peers[0].Addresses)

	ctxR2 := context.New(context.WithCorrelationID("req2"))
	peers, err = d0.FindByFingerprint(ctxR2, k1.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l1.GetPeerInfo().Addresses, peers[0].Addresses)
}

func TestDiscoverer_FindBothSides(t *testing.T) {
	_, k0, n0, x0, disc0, l0, ctx0 := newPeer(t, "peer0")
	_, k1, n1, x1, disc1, l1, ctx1 := newPeer(t, "peer1")
	_, k2, n2, x2, disc2, l2, ctx2 := newPeer(t, "peer2")

	fmt.Println("k0", k0.Fingerprint())
	fmt.Println("k1", k1.Fingerprint())
	fmt.Println("k2", k2.Fingerprint())

	d0, err := NewDiscoverer(ctx0, n0, x0, l0, []string{})
	assert.NoError(t, err)
	err = disc0.AddProvider(d0)
	assert.NoError(t, err)

	ba := l0.GetPeerInfo().Addresses

	d1, err := NewDiscoverer(ctx1, n1, x1, l1, ba)
	assert.NoError(t, err)
	err = disc1.AddProvider(d1)
	assert.NoError(t, err)

	d2, err := NewDiscoverer(ctx2, n2, x2, l2, ba)
	assert.NoError(t, err)
	err = disc2.AddProvider(d2)
	assert.NoError(t, err)

	ctxR1 := context.New(context.WithCorrelationID("req1"))
	peers, err := d1.FindByFingerprint(ctxR1, k2.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l2.GetPeerInfo().Addresses, peers[0].Addresses)

	ctxR2 := context.New(context.WithCorrelationID("req2"))
	peers, err = d2.FindByFingerprint(ctxR2, k1.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l1.GetPeerInfo().Addresses, peers[0].Addresses)
}

func TestDiscoverer_FindBothSides_SubKeys(t *testing.T) {
	_, k0, n0, x0, disc0, l0, ctx0 := newPeer(t, "peer0")
	ok1, k1, n1, x1, disc1, l1, ctx1 := newPeer(t, "peer1")
	ok2, k2, n2, x2, disc2, l2, ctx2 := newPeer(t, "peer2")

	fmt.Println("k0", k0.Fingerprint())
	fmt.Println("k1", k1.Fingerprint())
	fmt.Println("k2", k2.Fingerprint())

	d0, err := NewDiscoverer(ctx0, n0, x0, l0, []string{})
	assert.NoError(t, err)
	err = disc0.AddProvider(d0)
	assert.NoError(t, err)

	ba := l0.GetPeerInfo().Addresses

	d1, err := NewDiscoverer(ctx1, n1, x1, l1, ba)
	assert.NoError(t, err)
	err = disc1.AddProvider(d1)
	assert.NoError(t, err)

	d2, err := NewDiscoverer(ctx2, n2, x2, l2, ba)
	assert.NoError(t, err)
	err = disc2.AddProvider(d2)
	assert.NoError(t, err)

	ctxR1 := context.New(context.WithCorrelationID("req1"))
	peers, err := d1.FindByFingerprint(ctxR1, ok2.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l2.GetPeerInfo().Addresses, peers[0].Addresses)

	ctxR2 := context.New(context.WithCorrelationID("req2"))
	peers, err = d2.FindByFingerprint(ctxR2, ok1.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l1.GetPeerInfo().Addresses, peers[0].Addresses)
}

func newPeer(
	t *testing.T,
	name string,
) (
	*crypto.PrivateKey,
	*crypto.PrivateKey,
	net.Network,
	exchange.Exchange,
	discovery.Discoverer,
	*net.LocalInfo,
	context.Context,
) {
	ctx := context.New(context.WithCorrelationID(name))

	// owner key
	opk, err := crypto.GenerateKey()
	assert.NoError(t, err)

	// peer key
	pk, err := crypto.GenerateKey()
	assert.NoError(t, err)

	sig, err := crypto.NewSignature(
		opk,
		crypto.AlgorithmObjectHash,
		pk.ToObject(),
	)
	assert.NoError(t, err)

	pk.PublicKey.Signature = sig

	disc := discovery.NewDiscoverer()
	ds, err := graph.NewCayleyWithTempStore()
	local, err := net.NewLocalInfo("", pk)
	assert.NoError(t, err)

	n, err := net.New(disc, local)
	assert.NoError(t, err)

	tcp := net.NewTCPTransport(local, []string{})
	hsm := handshake.New(local, disc)
	n.AddMiddleware(hsm.Handle())
	n.AddTransport("tcps", tcp)

	x, err := exchange.New(
		ctx,
		pk,
		n,
		ds,
		disc,
		local,
		fmt.Sprintf("0.0.0.0:%d", 0),
	)
	assert.NoError(t, err)

	return opk, pk, n, x, disc, local, ctx
}

// jp is a lazy approach to comparing the mess that is unmarshaling json when
// dealing with numbers
func jp(v interface{}) string {
	b, _ := json.MarshalIndent(v, "", "  ") // nolint
	return string(b)
}
<commit_msg>chore(discovery): let the network settle after starting peers<commit_after>package hyperspace

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"nimona.io/internal/context"
	"nimona.io/internal/store/graph"
	"nimona.io/pkg/crypto"
	"nimona.io/pkg/discovery"
	"nimona.io/pkg/middleware/handshake"
	"nimona.io/pkg/net"
	"nimona.io/pkg/object/exchange"
)

func TestDiscoverer_BootstrapLookup(t *testing.T) {
	_, k0, n0, x0, disc0, l0, ctx0 := newPeer(t, "peer0")
	_, k1, n1, x1, disc1, l1, ctx1 := newPeer(t, "peer1")

	d0, err := NewDiscoverer(ctx0, n0, x0, l0, []string{})
	assert.NoError(t, err)
	err = disc0.AddProvider(d0)
	assert.NoError(t, err)

	ba := l0.GetPeerInfo().Addresses

	d1, err := NewDiscoverer(ctx1, n1, x1, l1, ba)
	assert.NoError(t, err)
	err = disc1.AddProvider(d1)
	assert.NoError(t, err)

	time.Sleep(time.Second)

	ctxR1 := context.New(context.WithCorrelationID("req1"))
	peers, err := d1.FindByFingerprint(ctxR1, k0.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l0.GetPeerInfo().Addresses, peers[0].Addresses)

	ctxR2 := context.New(context.WithCorrelationID("req2"))
	peers, err = d0.FindByFingerprint(ctxR2, k1.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l1.GetPeerInfo().Addresses, peers[0].Addresses)
}

func TestDiscoverer_FindBothSides(t *testing.T) {
	_, k0, n0, x0, disc0, l0, ctx0 := newPeer(t, "peer0")
	_, k1, n1, x1, disc1, l1, ctx1 := newPeer(t, "peer1")
	_, k2, n2, x2, disc2, l2, ctx2 := newPeer(t, "peer2")

	fmt.Println("k0", k0.Fingerprint())
	fmt.Println("k1", k1.Fingerprint())
	fmt.Println("k2", k2.Fingerprint())

	d0, err := NewDiscoverer(ctx0, n0, x0, l0, []string{})
	assert.NoError(t, err)
	err = disc0.AddProvider(d0)
	assert.NoError(t, err)

	ba := l0.GetPeerInfo().Addresses

	d1, err := NewDiscoverer(ctx1, n1, x1, l1, ba)
	assert.NoError(t, err)
	err = disc1.AddProvider(d1)
	assert.NoError(t, err)

	d2, err := NewDiscoverer(ctx2, n2, x2, l2, ba)
	assert.NoError(t, err)
	err = disc2.AddProvider(d2)
	assert.NoError(t, err)

	time.Sleep(time.Second)

	ctxR1 := context.New(context.WithCorrelationID("req1"))
	peers, err := d1.FindByFingerprint(ctxR1, k2.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l2.GetPeerInfo().Addresses, peers[0].Addresses)

	time.Sleep(time.Second)

	ctxR2 := context.New(context.WithCorrelationID("req2"))
	peers, err = d2.FindByFingerprint(ctxR2, k1.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l1.GetPeerInfo().Addresses, peers[0].Addresses)
}

func TestDiscoverer_FindBothSides_SubKeys(t *testing.T) {
	_, k0, n0, x0, disc0, l0, ctx0 := newPeer(t, "peer0")
	ok1, k1, n1, x1, disc1, l1, ctx1 := newPeer(t, "peer1")
	ok2, k2, n2, x2, disc2, l2, ctx2 := newPeer(t, "peer2")

	fmt.Println("k0", k0.Fingerprint())
	fmt.Println("k1", k1.Fingerprint())
	fmt.Println("k2", k2.Fingerprint())

	d0, err := NewDiscoverer(ctx0, n0, x0, l0, []string{})
	assert.NoError(t, err)
	err = disc0.AddProvider(d0)
	assert.NoError(t, err)

	ba := l0.GetPeerInfo().Addresses

	d1, err := NewDiscoverer(ctx1, n1, x1, l1, ba)
	assert.NoError(t, err)
	err = disc1.AddProvider(d1)
	assert.NoError(t, err)

	d2, err := NewDiscoverer(ctx2, n2, x2, l2, ba)
	assert.NoError(t, err)
	err = disc2.AddProvider(d2)
	assert.NoError(t, err)

	time.Sleep(time.Second)

	ctxR1 := context.New(context.WithCorrelationID("req1"))
	peers, err := d1.FindByFingerprint(ctxR1, ok2.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l2.GetPeerInfo().Addresses, peers[0].Addresses)

	time.Sleep(time.Second)

	ctxR2 := context.New(context.WithCorrelationID("req2"))
	peers, err = d2.FindByFingerprint(ctxR2, ok1.Fingerprint())
	require.NoError(t, err)
	require.Len(t, peers, 1)
	require.Equal(t, l1.GetPeerInfo().Addresses, peers[0].Addresses)
}

func newPeer(
	t *testing.T,
	name string,
) (
	*crypto.PrivateKey,
	*crypto.PrivateKey,
	net.Network,
	exchange.Exchange,
	discovery.Discoverer,
	*net.LocalInfo,
	context.Context,
) {
	ctx := context.New(context.WithCorrelationID(name))

	// owner key
	opk, err := crypto.GenerateKey()
	assert.NoError(t, err)

	// peer key
	pk, err := crypto.GenerateKey()
	assert.NoError(t, err)

	sig, err := crypto.NewSignature(
		opk,
		crypto.AlgorithmObjectHash,
		pk.ToObject(),
	)
	assert.NoError(t, err)

	pk.PublicKey.Signature = sig

	disc := discovery.NewDiscoverer()
	ds, err := graph.NewCayleyWithTempStore()
	local, err := net.NewLocalInfo("", pk)
	assert.NoError(t, err)

	n, err := net.New(disc, local)
	assert.NoError(t, err)

	tcp := net.NewTCPTransport(local, []string{})
	hsm := handshake.New(local, disc)
	n.AddMiddleware(hsm.Handle())
	n.AddTransport("tcps", tcp)

	x, err := exchange.New(
		ctx,
		pk,
		n,
		ds,
		disc,
		local,
		fmt.Sprintf("0.0.0.0:%d", 0),
	)
	assert.NoError(t, err)

	return opk, pk, n, x, disc, local, ctx
}

// jp is a lazy approach to comparing the mess that is unmarshaling json when
// dealing with numbers
func jp(v interface{}) string {
	b, _ := json.MarshalIndent(v, "", "  ") // nolint
	return string(b)
}
<|endoftext|>


<commit_before>// (C) 2017 Julian Andres Klode <jak@jak-linux.org>
// Licensed under the 2-Clause BSD license, see LICENSE for more information.

package permission

import (
	"testing"
)

type assignableToTestCase struct {
	from        string
	to          string
	assignable  bool
	refcopyable bool
	copyable    bool
}

var testcasesAssignableTo = []assignableToTestCase{
	// Basic types
	{"om", "om", true, false, true},
	{"ov", "ov", true, true, true},
	{"om", "ov", true, false, true},
	{"ov", "om", false, false, true},
	// pointers
	{"om *ov", "om *om", false, false, false},
	{"om *om", "om *ov", true, false, false},
	{"ov *ov", "ov *ov", true, true, true},
	{"om * om", "orR * om", true, false, false},
	{"om * om", "or * or", true, false, false},
	// channels
	{"om chan ov", "om chan om", false, false, false},
	{"om chan om", "om chan ov", true, false, false},
	{"ov chan ov", "ov chan ov", true, true, true}, // useless chan?
	// Array slice
	{"ov []ov", "ov []ov", true, true, true},
	{"ov [1]ov", "ov [1]ov", true, true, true},
	{"om []om", "om []ov", true, false, false},
	{"om [1]om", "om [1]ov", true, false, true},
	{"om []om", "om [1]ov", false, false, false},
	{"om [1]om", "om []ov", false, false, false},
	{"ov [1]ov", "ov []ov", false, true, false}, // can refcopy array to slice
	{"om []ov", "om []om", false, false, false},
	{"ov []ov", "om []ov", false, false, false},
	{"om []ov", "ov []ov", true, false, false},
	// channels
	{"ov map[ov] ov", "ov map[ov] ov", true, true, true},
	{"om map[ov] ov", "om map[om] ov", false, false, false},
	{"om map[om] ov", "om map[ov] ov", true, false, false},
	{"om map[ov] ov", "om map[ov] om", false, false, false},
	{"om map[ov] om", "om map[ov] ov", true, false, false},
	{"om map[ov] ov", "om map[ov] ov", true, false, false},
	// structs
	{"om struct {om}", "om struct {ov}", true, false, true},
	{"om struct {ov}", "om struct {om}", false, false, true},
	{"om struct {ov}", "ov struct {ov}", true, false, true},
	{"ov struct {ov}", "om struct {ov}", false, false, true},
	{"ov struct {ov}", "ov struct {ov}", true, true, true},
	// Incompatible types
	{"om", "om func ()", false, false, false},
	{"om func ()", "om", false, false, false},
	{"om interface {}", "om", false, false, false},
	{"om chan om", "om", false, false, false},
	{"om map[om] om", "om", false, false, false},
	{"om struct {om}", "om", false, false, false},
	{"om *om", "om", false, false, false},
	{"om []om", "om", false, false, false},
	{"om [1]om", "om", false, false, false},
	// Functions themselves are complicated: Here writeable actually means
	// that the function can return different values for the same arguments,
	// hence we can assign a constant function to a non-constant one.
	{"ov (ov) func ()", "ov (ov) func ()", true, true, true},
	{"om (ov) func ()", "om (ov) func ()", true, false, false},
	{"om (ov) func ()", "ov (ov) func ()", false, false, false},
	{"ov (ov) func ()", "om (ov) func ()", true, false, false},
	// Owned functions can be assigned to unowned however, and not vice versa
	{"om (ov) func ()", "m (ov) func ()", true, false, false},
	{"m (ov) func ()", "om (ov) func ()", false, false, false},
	// A function accepting mutable values cannot be used
	// as a function accepting values, but vice versa it works.
	{"om (om) func ()", "om (ov) func ()", false, false, false},
	{"om (ov) func ()", "om (om) func ()", true, false, false},
	{"om func (om)", "om func (ov)", false, false, false},
	{"om func (ov)", "om func (om)", true, false, false},
	// Function results: May be wider (excess rights stripped away)
	{"om func (om) ov", "om func (om) om", false, false, false},
	{"om func (om) om", "om func (om) ov", true, false, false},

	// Interfaces
	{"ov interface{}", "ov interface{}", true, true, true},
	{"om interface{}", "ov interface{}", true, false, false},
	{"ov interface{}", "om interface{}", false, false, false},
	{"om interface{}", "m interface{}", true, false, false},
	{"m interface{}", "om interface{}", false, false, false},
	// TODO: The receiver might actually be recursive, if no @cap declaration is
	// given, the receiver actually becomes identical (!) to the interface
	// permission itself. That's not possible to express in this syntax, and
	// usually not wanted anyway.
	{"om interface { ov (om) func()}", "om interface { om (om) func()}", true, false, false},
	{"om interface { om (om) func()}", "om interface { ov (om) func()}", false, false, false},
	{"ov interface { ov (ov) func()}", "ov interface { ov (ov) func()}", true, true, true},
	{"ov interface { om (om) func()}", "ov interface { om (om) func()}", true, true, true},
	{"ov interface { om (om) func()}", "ov interface { ov (om) func()}", false, false, false},

	// FIXME: Do we actually want to allow permissions like these?
	{"ov struct {ov}", "ov struct {om}", false, false, true},
	{"ov func (om)", "ov func (om)", true, true, true},
	{"ov func (om)", "ov func (ov)", false, false, false},
	{"ov func (om) (om)", "ov func (om) (ov)", true, true, true},
	{"ov func (om) (ov)", "ov func (om) (om)", false, false, false},
	{"ov (ov) func (om)", "ov (ov) func (om)", true, true, true},
	{"ov (ov) func (om)", "ov (om) func (om)", true, true, true},
	{"ov (om) func (om)", "ov (ov) func (om)", false, false, false},
	{"ov struct { ov []ov }", "ov struct {ov [] ov}", true, true, true},
}

func TestAssignableTo(t *testing.T) {

	for _, testCase := range testcasesAssignableTo {
		testCase := testCase
		t.Run(testCase.from+"=> "+testCase.to, func(t *testing.T) {
			p1, err := NewParser(testCase.from).Parse()
			if err != nil {
				t.Fatalf("Invalid from: %v", err)
			}
			p2, err := NewParser(testCase.to).Parse()
			if err != nil {
				t.Fatalf("Invalid to: %v", err)
			}
			assignable := MovableTo(p1, p2)
			if assignable != testCase.assignable {
				t.Errorf("Unexpected move result %v, expected %v", assignable, testCase.assignable)
			}
			refcopyable := RefcopyableTo(p1, p2)
			if refcopyable != testCase.refcopyable {
				t.Errorf("Unexpected refcopy result %v, expected %v", refcopyable, testCase.refcopyable)
			}
			copyable := CopyableTo(p1, p2)
			if copyable != testCase.copyable {
				t.Errorf("Unexpected copy result %v, expected %v", copyable, testCase.copyable)
			}
		})
	}
}

func TestAssignableTo_Recursive(t *testing.T) {
	var recursiveType = &InterfacePermission{BasePermission: Mutable}
	recursiveType.Methods = []Permission{
		&FuncPermission{
			BasePermission: Mutable,
			Receivers:      []Permission{recursiveType},
		},
	}
	var nonrecursiveType = &InterfacePermission{BasePermission: Mutable,
		Methods: []Permission{
			&FuncPermission{
				BasePermission: Mutable,
				Receivers:      []Permission{Mutable},
			},
		},
	}
	if !MovableTo(recursiveType, recursiveType) {
		t.Error("Cannot move recursive type to itself")
	}
	if MovableTo(recursiveType, Mutable) {
		t.Error("Could move recursive type to mutable value")
	}
	if MovableTo(recursiveType, nonrecursiveType) {
		t.Error("Could move recursive type to non-recursive type")
	}
	if MovableTo(nonrecursiveType, recursiveType) {
		t.Error("Could move non-recursive type to recursive type")
	}

	if RefcopyableTo(recursiveType, recursiveType) {
		t.Error("Could move recursive type to itself")
	}
	if RefcopyableTo(recursiveType, Mutable) {
		t.Error("Could move recursive type to mutable value")
	}
	if RefcopyableTo(recursiveType, nonrecursiveType) {
		t.Error("Could move recursive type to non-recursive type")
	}
	if RefcopyableTo(nonrecursiveType, recursiveType) {
		t.Error("Could move non-recursive type to recursive type")
	}

}
<commit_msg>permission: Add missing test for copy struct members<commit_after>// (C) 2017 Julian Andres Klode <jak@jak-linux.org>
// Licensed under the 2-Clause BSD license, see LICENSE for more information.

package permission

import (
	"testing"
)

type assignableToTestCase struct {
	from        string
	to          string
	assignable  bool
	refcopyable bool
	copyable    bool
}

var testcasesAssignableTo = []assignableToTestCase{
	// Basic types
	{"om", "om", true, false, true},
	{"ov", "ov", true, true, true},
	{"om", "ov", true, false, true},
	{"ov", "om", false, false, true},
	// pointers
	{"om *ov", "om *om", false, false, false},
	{"om *om", "om *ov", true, false, false},
	{"ov *ov", "ov *ov", true, true, true},
	{"om * om", "orR * om", true, false, false},
	{"om * om", "or * or", true, false, false},
	// channels
	{"om chan ov", "om chan om", false, false, false},
	{"om chan om", "om chan ov", true, false, false},
	{"ov chan ov", "ov chan ov", true, true, true}, // useless chan?
	// Array slice
	{"ov []ov", "ov []ov", true, true, true},
	{"ov [1]ov", "ov [1]ov", true, true, true},
	{"om []om", "om []ov", true, false, false},
	{"om [1]om", "om [1]ov", true, false, true},
	{"om []om", "om [1]ov", false, false, false},
	{"om [1]om", "om []ov", false, false, false},
	{"ov [1]ov", "ov []ov", false, true, false}, // can refcopy array to slice
	{"om []ov", "om []om", false, false, false},
	{"ov []ov", "om []ov", false, false, false},
	{"om []ov", "ov []ov", true, false, false},
	// channels
	{"ov map[ov] ov", "ov map[ov] ov", true, true, true},
	{"om map[ov] ov", "om map[om] ov", false, false, false},
	{"om map[om] ov", "om map[ov] ov", true, false, false},
	{"om map[ov] ov", "om map[ov] om", false, false, false},
	{"om map[ov] om", "om map[ov] ov", true, false, false},
	{"om map[ov] ov", "om map[ov] ov", true, false, false},
	// structs
	{"om struct {om}", "om struct {ov}", true, false, true},
	{"om struct {ov}", "om struct {om}", false, false, true},
	{"om struct {ov}", "ov struct {ov}", true, false, true},
	{"ov struct {ov}", "om struct {ov}", false, false, true},
	{"ov struct {ov}", "ov struct {ov}", true, true, true},
	{"ov struct {om * om}", "ov struct {om * om}", true, false, false},
	// Incompatible types
	{"om", "om func ()", false, false, false},
	{"om func ()", "om", false, false, false},
	{"om interface {}", "om", false, false, false},
	{"om chan om", "om", false, false, false},
	{"om map[om] om", "om", false, false, false},
	{"om struct {om}", "om", false, false, false},
	{"om *om", "om", false, false, false},
	{"om []om", "om", false, false, false},
	{"om [1]om", "om", false, false, false},
	// Functions themselves are complicated: Here writeable actually means
	// that the function can return different values for the same arguments,
	// hence we can assign a constant function to a non-constant one.
	{"ov (ov) func ()", "ov (ov) func ()", true, true, true},
	{"om (ov) func ()", "om (ov) func ()", true, false, false},
	{"om (ov) func ()", "ov (ov) func ()", false, false, false},
	{"ov (ov) func ()", "om (ov) func ()", true, false, false},
	// Owned functions can be assigned to unowned however, and not vice versa
	{"om (ov) func ()", "m (ov) func ()", true, false, false},
	{"m (ov) func ()", "om (ov) func ()", false, false, false},
	// A function accepting mutable values cannot be used
	// as a function accepting values, but vice versa it works.
	{"om (om) func ()", "om (ov) func ()", false, false, false},
	{"om (ov) func ()", "om (om) func ()", true, false, false},
	{"om func (om)", "om func (ov)", false, false, false},
	{"om func (ov)", "om func (om)", true, false, false},
	// Function results: May be wider (excess rights stripped away)
	{"om func (om) ov", "om func (om) om", false, false, false},
	{"om func (om) om", "om func (om) ov", true, false, false},

	// Interfaces
	{"ov interface{}", "ov interface{}", true, true, true},
	{"om interface{}", "ov interface{}", true, false, false},
	{"ov interface{}", "om interface{}", false, false, false},
	{"om interface{}", "m interface{}", true, false, false},
	{"m interface{}", "om interface{}", false, false, false},
	// TODO: The receiver might actually be recursive, if no @cap declaration is
	// given, the receiver actually becomes identical (!) to the interface
	// permission itself. That's not possible to express in this syntax, and
	// usually not wanted anyway.
	{"om interface { ov (om) func()}", "om interface { om (om) func()}", true, false, false},
	{"om interface { om (om) func()}", "om interface { ov (om) func()}", false, false, false},
	{"ov interface { ov (ov) func()}", "ov interface { ov (ov) func()}", true, true, true},
	{"ov interface { om (om) func()}", "ov interface { om (om) func()}", true, true, true},
	{"ov interface { om (om) func()}", "ov interface { ov (om) func()}", false, false, false},

	// FIXME: Do we actually want to allow permissions like these?
	{"ov struct {ov}", "ov struct {om}", false, false, true},
	{"ov func (om)", "ov func (om)", true, true, true},
	{"ov func (om)", "ov func (ov)", false, false, false},
	{"ov func (om) (om)", "ov func (om) (ov)", true, true, true},
	{"ov func (om) (ov)", "ov func (om) (om)", false, false, false},
	{"ov (ov) func (om)", "ov (ov) func (om)", true, true, true},
	{"ov (ov) func (om)", "ov (om) func (om)", true, true, true},
	{"ov (om) func (om)", "ov (ov) func (om)", false, false, false},
	{"ov struct { ov []ov }", "ov struct {ov [] ov}", true, true, true},
}

func TestAssignableTo(t *testing.T) {

	for _, testCase := range testcasesAssignableTo {
		testCase := testCase
		t.Run(testCase.from+"=> "+testCase.to, func(t *testing.T) {
			p1, err := NewParser(testCase.from).Parse()
			if err != nil {
				t.Fatalf("Invalid from: %v", err)
			}
			p2, err := NewParser(testCase.to).Parse()
			if err != nil {
				t.Fatalf("Invalid to: %v", err)
			}
			assignable := MovableTo(p1, p2)
			if assignable != testCase.assignable {
				t.Errorf("Unexpected move result %v, expected %v", assignable, testCase.assignable)
			}
			refcopyable := RefcopyableTo(p1, p2)
			if refcopyable != testCase.refcopyable {
				t.Errorf("Unexpected refcopy result %v, expected %v", refcopyable, testCase.refcopyable)
			}
			copyable := CopyableTo(p1, p2)
			if copyable != testCase.copyable {
				t.Errorf("Unexpected copy result %v, expected %v", copyable, testCase.copyable)
			}
		})
	}
}

func TestAssignableTo_Recursive(t *testing.T) {
	var recursiveType = &InterfacePermission{BasePermission: Mutable}
	recursiveType.Methods = []Permission{
		&FuncPermission{
			BasePermission: Mutable,
			Receivers:      []Permission{recursiveType},
		},
	}
	var nonrecursiveType = &InterfacePermission{BasePermission: Mutable,
		Methods: []Permission{
			&FuncPermission{
				BasePermission: Mutable,
				Receivers:      []Permission{Mutable},
			},
		},
	}
	if !MovableTo(recursiveType, recursiveType) {
		t.Error("Cannot move recursive type to itself")
	}
	if MovableTo(recursiveType, Mutable) {
		t.Error("Could move recursive type to mutable value")
	}
	if MovableTo(recursiveType, nonrecursiveType) {
		t.Error("Could move recursive type to non-recursive type")
	}
	if MovableTo(nonrecursiveType, recursiveType) {
		t.Error("Could move non-recursive type to recursive type")
	}

	if RefcopyableTo(recursiveType, recursiveType) {
		t.Error("Could move recursive type to itself")
	}
	if RefcopyableTo(recursiveType, Mutable) {
		t.Error("Could move recursive type to mutable value")
	}
	if RefcopyableTo(recursiveType, nonrecursiveType) {
		t.Error("Could move recursive type to non-recursive type")
	}
	if RefcopyableTo(nonrecursiveType, recursiveType) {
		t.Error("Could move non-recursive type to recursive type")
	}

}
<|endoftext|>


<commit_before>// The HTTP based parts of the config, Transport and Client

package fs

import (
	"crypto/tls"
	"net"
	"net/http"
	"net/http/httputil"
	"reflect"
	"sync"
	"time"
)

const (
	separatorReq  = ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
	separatorResp = "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
)

var (
	transport   http.RoundTripper
	noTransport sync.Once
)

// A net.Conn that sets a deadline for every Read or Write operation
type timeoutConn struct {
	net.Conn
	readTimer  *time.Timer
	writeTimer *time.Timer
	timeout    time.Duration
	_cancel    func()
	off        time.Time
}

// create a timeoutConn using the timeout
func newTimeoutConn(conn net.Conn, timeout time.Duration) *timeoutConn {
	return &timeoutConn{
		Conn:    conn,
		timeout: timeout,
	}
}

// Read bytes doing timeouts
func (c *timeoutConn) Read(b []byte) (n int, err error) {
	err = c.Conn.SetReadDeadline(time.Now().Add(c.timeout))
	if err != nil {
		return n, err
	}
	n, err = c.Conn.Read(b)
	cerr := c.Conn.SetReadDeadline(c.off)
	if cerr != nil {
		err = cerr
	}
	return n, err
}

// Write bytes doing timeouts
func (c *timeoutConn) Write(b []byte) (n int, err error) {
	err = c.Conn.SetWriteDeadline(time.Now().Add(c.timeout))
	if err != nil {
		return n, err
	}
	n, err = c.Conn.Write(b)
	cerr := c.Conn.SetWriteDeadline(c.off)
	if cerr != nil {
		err = cerr
	}
	return n, err
}

// setDefaults for a from b
//
// Copy the public members from b to a.  We can't just use a struct
// copy as Transport contains a private mutex.
func setDefaults(a, b interface{}) {
	pt := reflect.TypeOf(a)
	t := pt.Elem()
	va := reflect.ValueOf(a).Elem()
	vb := reflect.ValueOf(b).Elem()
	for i := 0; i < t.NumField(); i++ {
		aField := va.Field(i)
		// Set a from b if it is public
		if aField.CanSet() {
			bField := vb.Field(i)
			aField.Set(bField)
		}
	}
}

// Transport returns an http.RoundTripper with the correct timeouts
func (ci *ConfigInfo) Transport() http.RoundTripper {
	noTransport.Do(func() {
		// Start with a sensible set of defaults then override.
		// This also means we get new stuff when it gets added to go
		t := new(http.Transport)
		setDefaults(t, http.DefaultTransport.(*http.Transport))
		t.Proxy = http.ProxyFromEnvironment
		t.MaxIdleConnsPerHost = 4 * (ci.Checkers + ci.Transfers + 1)
		t.TLSHandshakeTimeout = ci.ConnectTimeout
		t.ResponseHeaderTimeout = ci.ConnectTimeout
		t.TLSClientConfig = &tls.Config{InsecureSkipVerify: ci.InsecureSkipVerify}
		t.DisableCompression = *noGzip
		// Set in http_old.go initTransport
		//   t.Dial
		// Set in http_new.go initTransport
		//   t.DialContext
		//   t.IdelConnTimeout
		//   t.ExpectContinueTimeout
		ci.initTransport(t)
		// Wrap that http.Transport in our own transport
		transport = NewTransport(t, ci.DumpHeaders, ci.DumpBodies)
	})
	return transport
}

// Client returns an http.Client with the correct timeouts
func (ci *ConfigInfo) Client() *http.Client {
	return &http.Client{
		Transport: ci.Transport(),
	}
}

// Transport is a our http Transport which wraps an http.Transport
// * Sets the User Agent
// * Does logging
type Transport struct {
	*http.Transport
	logHeader bool
	logBody   bool
}

// NewTransport wraps the http.Transport passed in and logs all
// roundtrips including the body if logBody is set.
func NewTransport(transport *http.Transport, logHeader, logBody bool) *Transport {
	return &Transport{
		Transport: transport,
		logHeader: logHeader,
		logBody:   logBody,
	}
}

// RoundTrip implements the RoundTripper interface.
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	// Force user agent
	req.Header.Set("User-Agent", UserAgent)
	// Log request
	if t.logHeader {
		buf, _ := httputil.DumpRequestOut(req, t.logBody)
		Debug(nil, "%s", separatorReq)
		Debug(nil, "%s", "HTTP REQUEST")
		Debug(nil, "%s", string(buf))
		Debug(nil, "%s", separatorReq)
	}
	// Do round trip
	resp, err = t.Transport.RoundTrip(req)
	// Log response
	if t.logHeader {
		Debug(nil, "%s", separatorResp)
		Debug(nil, "%s", "HTTP RESPONSE")
		if err != nil {
			Debug(nil, "Error: %v", err)
		} else {
			buf, _ := httputil.DumpResponse(resp, t.logBody)
			Debug(nil, "%s", string(buf))
		}
		Debug(nil, "%s", separatorResp)
	}
	return resp, err
}
<commit_msg>Make --dump-bodies imply --dump-headers<commit_after>// The HTTP based parts of the config, Transport and Client

package fs

import (
	"crypto/tls"
	"net"
	"net/http"
	"net/http/httputil"
	"reflect"
	"sync"
	"time"
)

const (
	separatorReq  = ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
	separatorResp = "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
)

var (
	transport   http.RoundTripper
	noTransport sync.Once
)

// A net.Conn that sets a deadline for every Read or Write operation
type timeoutConn struct {
	net.Conn
	readTimer  *time.Timer
	writeTimer *time.Timer
	timeout    time.Duration
	_cancel    func()
	off        time.Time
}

// create a timeoutConn using the timeout
func newTimeoutConn(conn net.Conn, timeout time.Duration) *timeoutConn {
	return &timeoutConn{
		Conn:    conn,
		timeout: timeout,
	}
}

// Read bytes doing timeouts
func (c *timeoutConn) Read(b []byte) (n int, err error) {
	err = c.Conn.SetReadDeadline(time.Now().Add(c.timeout))
	if err != nil {
		return n, err
	}
	n, err = c.Conn.Read(b)
	cerr := c.Conn.SetReadDeadline(c.off)
	if cerr != nil {
		err = cerr
	}
	return n, err
}

// Write bytes doing timeouts
func (c *timeoutConn) Write(b []byte) (n int, err error) {
	err = c.Conn.SetWriteDeadline(time.Now().Add(c.timeout))
	if err != nil {
		return n, err
	}
	n, err = c.Conn.Write(b)
	cerr := c.Conn.SetWriteDeadline(c.off)
	if cerr != nil {
		err = cerr
	}
	return n, err
}

// setDefaults for a from b
//
// Copy the public members from b to a.  We can't just use a struct
// copy as Transport contains a private mutex.
func setDefaults(a, b interface{}) {
	pt := reflect.TypeOf(a)
	t := pt.Elem()
	va := reflect.ValueOf(a).Elem()
	vb := reflect.ValueOf(b).Elem()
	for i := 0; i < t.NumField(); i++ {
		aField := va.Field(i)
		// Set a from b if it is public
		if aField.CanSet() {
			bField := vb.Field(i)
			aField.Set(bField)
		}
	}
}

// Transport returns an http.RoundTripper with the correct timeouts
func (ci *ConfigInfo) Transport() http.RoundTripper {
	noTransport.Do(func() {
		// Start with a sensible set of defaults then override.
		// This also means we get new stuff when it gets added to go
		t := new(http.Transport)
		setDefaults(t, http.DefaultTransport.(*http.Transport))
		t.Proxy = http.ProxyFromEnvironment
		t.MaxIdleConnsPerHost = 4 * (ci.Checkers + ci.Transfers + 1)
		t.TLSHandshakeTimeout = ci.ConnectTimeout
		t.ResponseHeaderTimeout = ci.ConnectTimeout
		t.TLSClientConfig = &tls.Config{InsecureSkipVerify: ci.InsecureSkipVerify}
		t.DisableCompression = *noGzip
		// Set in http_old.go initTransport
		//   t.Dial
		// Set in http_new.go initTransport
		//   t.DialContext
		//   t.IdelConnTimeout
		//   t.ExpectContinueTimeout
		ci.initTransport(t)
		// Wrap that http.Transport in our own transport
		transport = NewTransport(t, ci.DumpHeaders, ci.DumpBodies)
	})
	return transport
}

// Client returns an http.Client with the correct timeouts
func (ci *ConfigInfo) Client() *http.Client {
	return &http.Client{
		Transport: ci.Transport(),
	}
}

// Transport is a our http Transport which wraps an http.Transport
// * Sets the User Agent
// * Does logging
type Transport struct {
	*http.Transport
	logHeader bool
	logBody   bool
}

// NewTransport wraps the http.Transport passed in and logs all
// roundtrips including the body if logBody is set.
func NewTransport(transport *http.Transport, logHeader, logBody bool) *Transport {
	return &Transport{
		Transport: transport,
		logHeader: logHeader,
		logBody:   logBody,
	}
}

// RoundTrip implements the RoundTripper interface.
func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	// Force user agent
	req.Header.Set("User-Agent", UserAgent)
	// Log request
	if t.logHeader || t.logBody {
		buf, _ := httputil.DumpRequestOut(req, t.logBody)
		Debug(nil, "%s", separatorReq)
		Debug(nil, "%s", "HTTP REQUEST")
		Debug(nil, "%s", string(buf))
		Debug(nil, "%s", separatorReq)
	}
	// Do round trip
	resp, err = t.Transport.RoundTrip(req)
	// Log response
	if t.logHeader || t.logBody {
		Debug(nil, "%s", separatorResp)
		Debug(nil, "%s", "HTTP RESPONSE")
		if err != nil {
			Debug(nil, "Error: %v", err)
		} else {
			buf, _ := httputil.DumpResponse(resp, t.logBody)
			Debug(nil, "%s", string(buf))
		}
		Debug(nil, "%s", separatorResp)
	}
	return resp, err
}
<|endoftext|>


<commit_before>package fscache

import (
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
)

type Cache struct {
	mu    sync.Mutex
	dir   string
	files map[string]*cachedFile
}

// New creates a new Cache based on directory dir.
// Dir is created if it does not exist, and the files
// in it are loaded into the cache using their filename as their key.
func New(dir string) (*Cache, error) {
	err := os.MkdirAll(dir, 0666)
	if err != nil {
		return nil, err
	}
	c := &Cache{
		dir:   dir,
		files: make(map[string]*cachedFile),
	}
	return c, c.load()
}

func (c *Cache) load() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	files, err := ioutil.ReadDir(c.dir)
	if err != nil {
		return err
	}
	for _, f := range files {
		c.files[f.Name()] = oldFile(filepath.Join(c.dir, f.Name()))
	}
	return nil
}

// Get manages access to the streams in the cache.
// If the key does not exist, ok = false, r will be nil and you can start
// writing to the stream via w which must be closed once you finish streaming to it.
// If ok = true, then the stream has started. w will be nil, and r will
// allow you to read from the stream. Get is safe for concurrent calls, and
// multiple concurrent readers are allowed. The stream readers will only block when waiting
// for more data to be written to the stream, or the stream to be closed.
func (c *Cache) Get(key string) (r io.ReadCloser, w io.WriteCloser, ok bool, err error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	f, ok := c.files[key]
	if !ok {
		f, err = newFile(filepath.Join(c.dir, key))
		w = f
		c.files[key] = f
	} else {
		r, err = f.next()
	}

	return r, w, ok, err
}

// Clean will empty the cache and delete the cache folder.
// Clean is not safe to call while streams are being read/written.
func (c *Cache) Clean() error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.files = make(map[string]*cachedFile)
	return os.RemoveAll(c.dir)
}

type cachedFile struct {
	name string
	grp  sync.WaitGroup
	w    *os.File
	b    *broadcaster
}

func newFile(key string) (*cachedFile, error) {
	f, err := os.Create(key)
	return &cachedFile{
		name: key,
		w:    f,
		b:    newBroadcaster(),
	}, err
}

func oldFile(key string) *cachedFile {
	b := newBroadcaster()
	b.Close()
	return &cachedFile{
		name: key,
		b:    b,
	}
}

func (f *cachedFile) next() (r io.ReadCloser, err error) {
	r, err = os.Open(f.name)
	if err == nil {
		f.grp.Add(1)
	}
	return &cacheReader{
		grp: &f.grp,
		r:   r,
		b:   f.b,
	}, err
}

func (f *cachedFile) Write(p []byte) (int, error) {
	defer f.b.Broadcast()
	f.b.Lock()
	defer f.b.Unlock()
	return f.w.Write(p)
}

func (f *cachedFile) Close() error {
	defer f.b.Close()
	return f.w.Close()
}

type cacheReader struct {
	r   io.ReadCloser
	grp *sync.WaitGroup
	b   *broadcaster
}

func (r *cacheReader) Read(p []byte) (n int, err error) {
	r.b.RLock()
	defer r.b.RUnlock()

	for {

		n, err = r.r.Read(p)

		if r.b.IsOpen() { // file is still being written to

			if n != 0 && err == nil { // successful read
				return n, nil
			} else if err == io.EOF { // no data read, wait for some
				r.b.RUnlock()
				r.b.Wait()
				r.b.RLock()
			} else if err != nil { // non-nil, non-eof error
				return n, err
			}

		} else { // file is closed, just return
			return n, err
		}

	}

	return n, err
}

func (r *cacheReader) Close() error {
	defer r.grp.Done()
	return r.r.Close()
}
<commit_msg>updated godoc<commit_after>package fscache

import (
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
)

type Cache struct {
	mu    sync.Mutex
	dir   string
	files map[string]*cachedFile
}

// New creates a new Cache based on directory dir.
// Dir is created if it does not exist, and the files
// in it are loaded into the cache using their filename as their key.
func New(dir string) (*Cache, error) {
	err := os.MkdirAll(dir, 0666)
	if err != nil {
		return nil, err
	}
	c := &Cache{
		dir:   dir,
		files: make(map[string]*cachedFile),
	}
	return c, c.load()
}

func (c *Cache) load() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	files, err := ioutil.ReadDir(c.dir)
	if err != nil {
		return err
	}
	for _, f := range files {
		c.files[f.Name()] = oldFile(filepath.Join(c.dir, f.Name()))
	}
	return nil
}

// Get manages access to the streams in the cache.
// If the key does not exist, ok = false, r will be nil and you can start
// writing to the stream via w which must be closed once you finish streaming to it.
// If ok = true, then the stream has started. w will be nil, and r will
// allow you to read from the stream. Get is safe for concurrent calls, and
// multiple concurrent readers are allowed. The stream readers will only block when waiting
// for more data to be written to the stream, or the stream to be closed (signified by io.EOF).
func (c *Cache) Get(key string) (r io.ReadCloser, w io.WriteCloser, ok bool, err error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	f, ok := c.files[key]
	if !ok {
		f, err = newFile(filepath.Join(c.dir, key))
		w = f
		c.files[key] = f
	} else {
		r, err = f.next()
	}

	return r, w, ok, err
}

// Clean will empty the cache and delete the cache folder.
// Clean is not safe to call while streams are being read/written.
func (c *Cache) Clean() error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.files = make(map[string]*cachedFile)
	return os.RemoveAll(c.dir)
}

type cachedFile struct {
	name string
	grp  sync.WaitGroup
	w    *os.File
	b    *broadcaster
}

func newFile(key string) (*cachedFile, error) {
	f, err := os.Create(key)
	return &cachedFile{
		name: key,
		w:    f,
		b:    newBroadcaster(),
	}, err
}

func oldFile(key string) *cachedFile {
	b := newBroadcaster()
	b.Close()
	return &cachedFile{
		name: key,
		b:    b,
	}
}

func (f *cachedFile) next() (r io.ReadCloser, err error) {
	r, err = os.Open(f.name)
	if err == nil {
		f.grp.Add(1)
	}
	return &cacheReader{
		grp: &f.grp,
		r:   r,
		b:   f.b,
	}, err
}

func (f *cachedFile) Write(p []byte) (int, error) {
	defer f.b.Broadcast()
	f.b.Lock()
	defer f.b.Unlock()
	return f.w.Write(p)
}

func (f *cachedFile) Close() error {
	defer f.b.Close()
	return f.w.Close()
}

type cacheReader struct {
	r   io.ReadCloser
	grp *sync.WaitGroup
	b   *broadcaster
}

func (r *cacheReader) Read(p []byte) (n int, err error) {
	r.b.RLock()
	defer r.b.RUnlock()

	for {

		n, err = r.r.Read(p)

		if r.b.IsOpen() { // file is still being written to

			if n != 0 && err == nil { // successful read
				return n, nil
			} else if err == io.EOF { // no data read, wait for some
				r.b.RUnlock()
				r.b.Wait()
				r.b.RLock()
			} else if err != nil { // non-nil, non-eof error
				return n, err
			}

		} else { // file is closed, just return
			return n, err
		}

	}

	return n, err
}

func (r *cacheReader) Close() error {
	defer r.grp.Done()
	return r.r.Close()
}
<|endoftext|>


<commit_before>package discgo

import (
	"encoding/json"
	"time"

	"sync"

	"runtime"

	"compress/zlib"
	"errors"

	"strings"

	"github.com/gorilla/websocket"
	"github.com/nhooyr/log"
)

type Game struct {
	Name string  `json:"name"`
	Type *int    `json:"type"`
	URL  *string `json:"url"`
}

type endpointGateway struct {
	*endpoint
}

func (c *Client) gateway() endpointGateway {
	e2 := c.e.appendMajor("gateway")
	return endpointGateway{e2}
}

func (g endpointGateway) get() (url string, err error) {
	var urlStruct struct {
		URL string `json:"url"`
	}
	return urlStruct.URL, g.doMethod("GET", nil, &urlStruct)
}

type Conn struct {
	token      string
	userAgent  string
	gatewayURL string

	sessionID string

	closeChan          chan struct{}
	waitClosed         chan struct{}
	waitReadLoopClosed chan struct{}
	reconnectChan      chan struct{}

	wsConn *websocket.Conn

	mu                    sync.Mutex
	heartbeatAcknowledged bool
	sequenceNumber        int
}

func NewConn(apiClient *Client) (*Conn, error) {
	gatewayURL, err := apiClient.gateway().get()
	if err != nil {
		return nil, err
	}
	gatewayURL += "?v=" + apiVersion + "&encoding=json"
	return &Conn{
		token:              apiClient.Token,
		userAgent:          apiClient.UserAgent,
		gatewayURL:         gatewayURL,
		closeChan:          make(chan struct{}),
		waitClosed:         make(chan struct{}),
		waitReadLoopClosed: make(chan struct{}),
		reconnectChan:      make(chan struct{}),
	}, nil
}

const (
	dispatchOperation = iota
	heartbeatOperation
	identifyOperation
	statusUpdateOperation
	voiceStateUpdateOperation
	voiceServerPingOperation
	resumeOperation
	reconnectOperation
	requestGuildMembersOperation
	invalidSessionOperation
	helloOperation
	heartbeatACKOperation
)

func (c *Conn) close() error {
	// TODO I think this should be OK, but I'm not sure. Should there be a writerLoop routine to sync writes?
	closeMsg := websocket.FormatCloseMessage(websocket.CloseNoStatusReceived, "no heartbeat acknowledgment")
	err := c.wsConn.WriteMessage(websocket.CloseMessage, closeMsg)
	err2 := c.wsConn.Close()
	if err != nil {
		return err
	}
	return err2
}

func (c *Conn) Close() error {
	c.closeChan <- struct{}{}
	<-c.waitClosed
	return nil
}

type helloOPData struct {
	HeartbeatInterval int      `json:"heartbeat_interval"`
	Trace             []string `json:"_trace"`
}

func (c *Conn) Dial() (err error) {
	c.heartbeatAcknowledged = true

	// TODO Need to set read deadline for hello packet and I also need to set write deadlines.
	// TODO also max message
	c.wsConn, _, err = websocket.DefaultDialer.Dial(c.gatewayURL, nil)
	if err != nil {
		return err
	}

	if c.sessionID == "" {
		err = c.identify()
	} else {
		err = c.resume()
	}
	if err != nil {
		return err
	}

	go c.readLoop()

	return nil
}

type resumeOPData struct {
	Token     string `json:"token"`
	SessionID string `json:"session_id"`
	Seq       int    `json:"seq"`
}

func (c *Conn) resume() error {
	c.mu.Lock()
	p := &sendPayload{
		Operation: resumeOperation,
		Data: resumeOPData{
			Token:     c.token,
			SessionID: c.sessionID,
			Seq:       c.sequenceNumber,
		},
	}
	c.mu.Unlock()
	return c.wsConn.WriteJSON(p)
}

type readyEvent struct {
	V               int        `json:"v"`
	User            *User      `json:"user"`
	PrivateChannels []*Channel `json:"private_channels"`
	SessionID       string     `json:"session_id"`
	Trace           []string   `json:"_trace"`
}

type receivePayload struct {
	Operation      int             `json:"op"`
	Data           json.RawMessage `json:"d"`
	SequenceNumber int             `json:"s"`
	Type           string          `json:"t"`
}

func (c *Conn) readPayload() (*receivePayload, error) {
	var p receivePayload
	msgType, r, err := c.wsConn.NextReader()
	if err != nil {
		return nil, err
	}
	switch msgType {
	case websocket.BinaryMessage:
		r, err = zlib.NewReader(r)
		if err != nil {
			return nil, err
		}
		fallthrough
	case websocket.TextMessage:
		return &p, json.NewDecoder(r).Decode(&p)
	default:
		return nil, errors.New("unexpected websocket message type")
	}
	return &p, err
}

func (c *Conn) readLoop() {
	for {
		p, err := c.readPayload()
		if err != nil {
			errStr := err.Error()
			if strings.Contains(errStr, "use of closed network connection") {
				c.waitReadLoopClosed <- struct{}{}
			} else {
				log.Print(err)
				// It's possible we're being shutdown right now too.
				// Or maybe manager is already trying to reconnect.
				select {
				case c.reconnectChan <- struct{}{}:
				case c.waitReadLoopClosed <- struct{}{}:
				}
			}
			return
		}
		err = c.onPayload(p)
		if err != nil {
			log.Print(err)
			// It's possible we're being shutdown right now too.
			// Or maybe manager is already trying to reconnect.
			select {
			case c.reconnectChan <- struct{}{}:
			case c.waitReadLoopClosed <- struct{}{}:
			}
		}
	}
}

type sendPayload struct {
	Operation int         `json:"op"`
	Data      interface{} `json:"d,omitempty"`
	Sequence  int         `json:"s,omitempty"`
}

func (c *Conn) onPayload(p *receivePayload) error {
	switch p.Operation {
	case helloOperation:
		var hello helloOPData
		err := json.Unmarshal(p.Data, &hello)
		if err != nil {
			return err
		}
		go c.manager(hello.HeartbeatInterval)
	case heartbeatACKOperation:
		c.mu.Lock()
		c.heartbeatAcknowledged = true
		c.mu.Unlock()
	case invalidSessionOperation:
		// Wait out the possible rate limit.
		time.Sleep(time.Second * 5)
		err := c.identify()
		if err != nil {
			return err
		}
	case reconnectOperation:
		return errors.New("reconnect operation")
	case dispatchOperation:
		c.mu.Lock()
		c.sequenceNumber = p.SequenceNumber
		c.mu.Unlock()

		go c.onEvent(p)
	default:
		panic("discord gone crazy; unexpected operation type")
	}
	log.Print(p.Operation)
	if p.Type != "" {
		log.Print(p.Type)
	}
	log.Print(p.SequenceNumber)
	log.Printf("%s", p.Data)
	log.Print()
	return nil
}

func (c *Conn) onEvent(p *receivePayload) {
	switch p.Type {
	case "READY":
		var ready readyEvent
		err := json.Unmarshal(p.Data, &ready)
		if err != nil {
			log.Print(err)
		}
		c.sessionID = ready.SessionID
	}
	// TODO state tracking
}

func (c *Conn) manager(heartbeatInterval int) {
	ticker := time.NewTicker(time.Duration(heartbeatInterval) * time.Millisecond)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			err := c.heartbeat()
			if err != nil {
				log.Print(err)
				err := c.close()
				if err != nil {
					log.Print(err)
				}
				<-c.waitReadLoopClosed

				err = c.Dial()
				if err != nil {
					log.Print(err)
				}
				return
			}
		case <-c.reconnectChan:
			err := c.close()
			if err != nil {
				log.Print(err)
			}

			err = c.Dial()
			if err != nil {
				log.Print(err)
			}
			return
		case <-c.closeChan:
			err := c.close()
			if err != nil {
				log.Print(err)
			}
			<-c.waitReadLoopClosed
			c.waitClosed <- struct{}{}
			return
		}
	}
}

func (c *Conn) heartbeat() error {
	c.mu.Lock()
	if !c.heartbeatAcknowledged {
		c.mu.Unlock()
		return errors.New("heartbeat not acknowledged")
	}
	sequenceNumber := c.sequenceNumber
	c.heartbeatAcknowledged = false
	c.mu.Unlock()

	p := &sendPayload{Operation: heartbeatOperation, Data: sequenceNumber}
	return c.wsConn.WriteJSON(p)
}

type identifyOPData struct {
	Token          string             `json:"token"`
	Properties     identifyProperties `json:"properties"`
	Compress       bool               `json:"compress"`
	LargeThreshold int                `json:"large_threshold"`
}

type identifyProperties struct {
	OS              string `json:"$os,omitempty"`
	Browser         string `json:"$browser,omitempty"`
	Device          string `json:"$device,omitempty"`
	Referrer        string `json:"$referrer,omitempty"`
	ReferringDomain string `json:"$referring_domain,omitempty"`
}

func (c *Conn) identify() error {
	p := &sendPayload{
		Operation: identifyOperation,
		Data: identifyOPData{
			Token: c.token,
			Properties: identifyProperties{
				OS:      runtime.GOOS,
				Browser: c.userAgent,
			},
			// TODO COMPRESS!!!
			Compress:       true,
			LargeThreshold: 250,
		},
	}
	return c.wsConn.WriteJSON(p)
}
<commit_msg>done with the gateway for sure now. New PR for state tracking.<commit_after>package discgo

import (
	"encoding/json"
	"time"

	"sync"

	"runtime"

	"compress/zlib"
	"errors"

	"strings"

	"github.com/gorilla/websocket"
	"github.com/nhooyr/log"
)

type Game struct {
	Name string  `json:"name"`
	Type *int    `json:"type"`
	URL  *string `json:"url"`
}

type endpointGateway struct {
	*endpoint
}

func (c *Client) gateway() endpointGateway {
	e2 := c.e.appendMajor("gateway")
	return endpointGateway{e2}
}

func (g endpointGateway) get() (url string, err error) {
	var urlStruct struct {
		URL string `json:"url"`
	}
	return urlStruct.URL, g.doMethod("GET", nil, &urlStruct)
}

type Conn struct {
	token      string
	userAgent  string
	gatewayURL string

	sessionID string

	closeChan  chan struct{}
	closeOnce  sync.Once
	waitClosed chan struct{}

	waitReadLoopClosed chan struct{}
	reconnectChan      chan struct{}

	wsConn *websocket.Conn

	mu                    sync.Mutex
	heartbeatAcknowledged bool
	sequenceNumber        int
}

func NewConn(apiClient *Client) (*Conn, error) {
	gatewayURL, err := apiClient.gateway().get()
	if err != nil {
		return nil, err
	}
	gatewayURL += "?v=" + apiVersion + "&encoding=json"
	return &Conn{
		token:              apiClient.Token,
		userAgent:          apiClient.UserAgent,
		gatewayURL:         gatewayURL,
		closeChan:          make(chan struct{}),
		waitClosed:         make(chan struct{}),
		waitReadLoopClosed: make(chan struct{}),
		reconnectChan:      make(chan struct{}),
	}, nil
}

const (
	dispatchOperation            = iota
	heartbeatOperation
	identifyOperation
	statusUpdateOperation
	voiceStateUpdateOperation
	voiceServerPingOperation
	resumeOperation
	reconnectOperation
	requestGuildMembersOperation
	invalidSessionOperation
	helloOperation
	heartbeatACKOperation
)

func (c *Conn) close() error {
	// TODO I think this should be OK, but I'm not sure. Should there be a writerLoop routine to sync writes?
	closeMsg := websocket.FormatCloseMessage(websocket.CloseNoStatusReceived, "no heartbeat acknowledgment")
	err := c.wsConn.WriteMessage(websocket.CloseMessage, closeMsg)
	err2 := c.wsConn.Close()
	if err != nil {
		return err
	}
	return err2
}

func (c *Conn) Close() error {
	c.closeOnce.Do(func() {
		close(c.closeChan)
		<-c.waitClosed
	})
	return nil
}

type helloOPData struct {
	HeartbeatInterval int      `json:"heartbeat_interval"`
	Trace             []string `json:"_trace"`
}

func (c *Conn) Dial() (err error) {
	c.heartbeatAcknowledged = true

	// TODO Need to set read deadline for hello packet and I also need to set write deadlines.
	// TODO also max message
	c.wsConn, _, err = websocket.DefaultDialer.Dial(c.gatewayURL, nil)
	if err != nil {
		return err
	}

	if c.sessionID == "" {
		err = c.identify()
	} else {
		err = c.resume()
	}
	if err != nil {
		return err
	}

	go c.readLoop()

	return nil
}

type resumeOPData struct {
	Token     string `json:"token"`
	SessionID string `json:"session_id"`
	Seq       int    `json:"seq"`
}

func (c *Conn) resume() error {
	c.mu.Lock()
	p := &sendPayload{
		Operation: resumeOperation,
		Data: resumeOPData{
			Token:     c.token,
			SessionID: c.sessionID,
			Seq:       c.sequenceNumber,
		},
	}
	c.mu.Unlock()
	return c.wsConn.WriteJSON(p)
}

type readyEvent struct {
	V               int        `json:"v"`
	User            *User      `json:"user"`
	PrivateChannels []*Channel `json:"private_channels"`
	SessionID       string     `json:"session_id"`
	Trace           []string   `json:"_trace"`
}

type receivePayload struct {
	Operation      int             `json:"op"`
	Data           json.RawMessage `json:"d"`
	SequenceNumber int             `json:"s"`
	Type           string          `json:"t"`
}

func (c *Conn) readPayload() (*receivePayload, error) {
	var p receivePayload
	msgType, r, err := c.wsConn.NextReader()
	if err != nil {
		return nil, err
	}
	switch msgType {
	case websocket.BinaryMessage:
		r, err = zlib.NewReader(r)
		if err != nil {
			return nil, err
		}
		fallthrough
	case websocket.TextMessage:
		return &p, json.NewDecoder(r).Decode(&p)
	default:
		return nil, errors.New("unexpected websocket message type")
	}
	return &p, err
}

func (c *Conn) readLoop() {
	for {
		p, err := c.readPayload()
		if err != nil {
			errStr := err.Error()
			if strings.Contains(errStr, "use of closed network connection") {
				c.waitReadLoopClosed <- struct{}{}
			} else {
				log.Print(err)
				// It's possible we're being shutdown right now too.
				// Or maybe manager is already trying to reconnect.
				select {
				case c.reconnectChan <- struct{}{}:
				case c.waitReadLoopClosed <- struct{}{}:
				}
			}
			return
		}
		err = c.onPayload(p)
		if err != nil {
			log.Print(err)
			// It's possible we're being shutdown right now too.
			// Or maybe manager is already trying to reconnect.
			select {
			case c.reconnectChan <- struct{}{}:
			case c.waitReadLoopClosed <- struct{}{}:
			}
		}
	}
}

type sendPayload struct {
	Operation int         `json:"op"`
	Data      interface{} `json:"d,omitempty"`
	Sequence  int         `json:"s,omitempty"`
}

func (c *Conn) onPayload(p *receivePayload) error {
	switch p.Operation {
	case helloOperation:
		var hello helloOPData
		err := json.Unmarshal(p.Data, &hello)
		if err != nil {
			return err
		}
		go c.manager(hello.HeartbeatInterval)
	case heartbeatACKOperation:
		c.mu.Lock()
		c.heartbeatAcknowledged = true
		c.mu.Unlock()
	case invalidSessionOperation:
		// Wait out the possible rate limit.
		time.Sleep(time.Second * 5)
		err := c.identify()
		if err != nil {
			return err
		}
	case reconnectOperation:
		return errors.New("reconnect operation")
	case dispatchOperation:
		c.mu.Lock()
		c.sequenceNumber = p.SequenceNumber
		c.mu.Unlock()

		go c.onEvent(p)
	default:
		panic("discord gone crazy; unexpected operation type")
	}
	log.Print(p.Operation)
	if p.Type != "" {
		log.Print(p.Type)
	}
	log.Print(p.SequenceNumber)
	log.Printf("%s", p.Data)
	log.Print()
	return nil
}

func (c *Conn) onEvent(p *receivePayload) {
	switch p.Type {
	case "READY":
		var ready readyEvent
		err := json.Unmarshal(p.Data, &ready)
		if err != nil {
			log.Printf("closing connection due to invalid ready; %v", err)
			err := c.Close()
			if err != nil {
				log.Print(err)
			}
			return
		}
		c.sessionID = ready.SessionID
	}
}

func (c *Conn) manager(heartbeatInterval int) {
	ticker := time.NewTicker(time.Duration(heartbeatInterval) * time.Millisecond)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			err := c.heartbeat()
			if err != nil {
				log.Print(err)
				err := c.close()
				if err != nil {
					log.Print(err)
				}
				<-c.waitReadLoopClosed

				err = c.Dial()
				if err != nil {
					log.Print(err)
				}
				return
			}
		case <-c.reconnectChan:
			err := c.close()
			if err != nil {
				log.Print(err)
			}

			err = c.Dial()
			if err != nil {
				log.Print(err)
			}
			return
		case <-c.closeChan:
			err := c.close()
			if err != nil {
				log.Print(err)
			}
			<-c.waitReadLoopClosed
			c.waitClosed <- struct{}{}
			return
		}
	}
}

func (c *Conn) heartbeat() error {
	c.mu.Lock()
	if !c.heartbeatAcknowledged {
		c.mu.Unlock()
		return errors.New("heartbeat not acknowledged")
	}
	sequenceNumber := c.sequenceNumber
	c.heartbeatAcknowledged = false
	c.mu.Unlock()

	p := &sendPayload{Operation: heartbeatOperation, Data: sequenceNumber}
	return c.wsConn.WriteJSON(p)
}

type identifyOPData struct {
	Token          string             `json:"token"`
	Properties     identifyProperties `json:"properties"`
	Compress       bool               `json:"compress"`
	LargeThreshold int                `json:"large_threshold"`
}

type identifyProperties struct {
	OS              string `json:"$os,omitempty"`
	Browser         string `json:"$browser,omitempty"`
	Device          string `json:"$device,omitempty"`
	Referrer        string `json:"$referrer,omitempty"`
	ReferringDomain string `json:"$referring_domain,omitempty"`
}

func (c *Conn) identify() error {
	p := &sendPayload{
		Operation: identifyOperation,
		Data: identifyOPData{
			Token: c.token,
			Properties: identifyProperties{
				OS:      runtime.GOOS,
				Browser: c.userAgent,
			},
			// TODO COMPRESS!!!
			Compress:       true,
			LargeThreshold: 250,
		},
	}
	return c.wsConn.WriteJSON(p)
}
<|endoftext|>


<commit_before>// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.

package spacestatus

import (
	"encoding/json"
	"errors"
	"github.com/nmeum/marvin/irc"
	"github.com/nmeum/marvin/modules"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

// Supported SpaceAPI version
const apiVersion = "0.13"

type spaceapi struct {
	api   string `json:"api"`
	space string `json:"space"`
	state struct {
		open bool `json:"open"`
	} `json:"state"`
}

type Module struct {
	api      *spaceapi
	URL      string `json:"url"`
	Notify   bool   `json:"notify"`
	Interval string `json:"interval"`
}

func Init(moduleSet *modules.ModuleSet) {
	moduleSet.Register(new(Module))
}

func (m *Module) Name() string {
	return "spacestatus"
}

func (m *Module) Help() string {
	return "USAGE: !spacestatus"
}

func (m *Module) Defaults() {
	m.Notify = true
	m.Interval = "0h15m"
}

func (m *Module) Load(client *irc.Client) error {
	if len(m.URL) <= 0 {
		return nil
	}

	duration, err := time.ParseDuration(m.Interval)
	if err != nil {
		return err
	}

	if err := m.updateHandler(client); err != nil {
		return err
	}

	if m.api.api != apiVersion {
		return errors.New("Unsupported SpaceAPI version")
	}

	go func(c *irc.Client) {
		for {
			time.Sleep(duration)
			m.updateHandler(c)
		}
	}(client)

	client.CmdHook("privmsg", m.statusCmd)
	return nil
}

func (m *Module) updateHandler(client *irc.Client) error {
	var oldState bool
	if m.api == nil {
		oldState = false
	} else {
		oldState = m.api.state.open
	}

	firstPoll := m.api == nil
	if err := m.pollStatus(); err != nil {
		return err
	}

	newState := m.api.state.open
	if newState != oldState && m.Notify && !firstPoll {
		m.notify(client, newState)
	}

	return nil
}

func (m *Module) pollStatus() error {
	resp, err := http.Get(m.URL)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if err := json.Unmarshal(data, &m.api); err != nil {
		return err
	}

	return nil
}

func (m *Module) statusCmd(client *irc.Client, msg irc.Message) error {
	if msg.Data != "!spacestatus" {
		return nil
	}

	if m.api == nil {
		return client.Write("NOTICE %s: Status currently unknown.",
			msg.Receiver)
	}

	var state string
	if m.api.state.open {
		state = "open"
	} else {
		state = "closed"
	}

	return client.Write("NOTICE %s: %s is currently %s",
		msg.Receiver, m.api.space, state)
}

func (m *Module) notify(client *irc.Client, open bool) {
	name := m.api.space
	for _, ch := range client.Channels {
		var oldState, newState string
		if open {
			oldState = "closed"
			newState = "open"
		} else {
			oldState = "open"
			newState = "closed"
		}

		client.Write("NOTICE %s: %s -- %s changed state from %s to %s",
			ch, strings.ToUpper(m.Name()), name, oldState, newState)
	}
}
<commit_msg>spacestatus: more tiny fixes<commit_after>// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with this program. If not, see <http://www.gnu.org/licenses/>.

package spacestatus

import (
	"encoding/json"
	"errors"
	"github.com/nmeum/marvin/irc"
	"github.com/nmeum/marvin/modules"
	"io/ioutil"
	"net/http"
	"time"
)

// Supported SpaceAPI version
const apiVersion = "0.13"

type spaceapi struct {
	api   string `json:"api"`
	space string `json:"space"`
	state struct {
		open bool `json:"open"`
	} `json:"state"`
}

type Module struct {
	api      *spaceapi
	URL      string `json:"url"`
	Notify   bool   `json:"notify"`
	Interval string `json:"interval"`
}

func Init(moduleSet *modules.ModuleSet) {
	moduleSet.Register(new(Module))
}

func (m *Module) Name() string {
	return "spacestatus"
}

func (m *Module) Help() string {
	return "USAGE: !spacestatus"
}

func (m *Module) Defaults() {
	m.Notify = true
	m.Interval = "0h15m"
}

func (m *Module) Load(client *irc.Client) error {
	if len(m.URL) <= 0 {
		return nil
	}

	duration, err := time.ParseDuration(m.Interval)
	if err != nil {
		return err
	}

	if err := m.updateHandler(client); err != nil {
		return err
	}

	if m.api.api != apiVersion {
		return errors.New("unsupported spaceapi version")
	}

	go func(c *irc.Client) {
		for {
			time.Sleep(duration)
			m.updateHandler(c)
		}
	}(client)

	client.CmdHook("privmsg", m.statusCmd)
	return nil
}

func (m *Module) updateHandler(client *irc.Client) error {
	var oldState bool
	if m.api == nil {
		oldState = false
	} else {
		oldState = m.api.state.open
	}

	firstPoll := m.api == nil
	if err := m.pollStatus(); err != nil {
		return err
	}

	newState := m.api.state.open
	if newState != oldState && m.Notify && !firstPoll {
		m.notify(client, newState)
	}

	return nil
}

func (m *Module) pollStatus() error {
	resp, err := http.Get(m.URL)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if err := json.Unmarshal(data, &m.api); err != nil {
		return err
	}

	return nil
}

func (m *Module) statusCmd(client *irc.Client, msg irc.Message) error {
	if msg.Data != "!spacestatus" {
		return nil
	}

	if m.api == nil {
		return client.Write("NOTICE %s: Status currently unknown.",
			msg.Receiver)
	}

	var state string
	if m.api.state.open {
		state = "open"
	} else {
		state = "closed"
	}

	return client.Write("NOTICE %s: %s is currently %s",
		msg.Receiver, m.api.space, state)
}

func (m *Module) notify(client *irc.Client, open bool) {
	var oldState, newState string
	if open {
		oldState = "closed"
		newState = "open"
	} else {
		oldState = "open"
		newState = "closed"
	}

	for _, ch := range client.Channels {
		client.Write("NOTICE %s: %s changed door status from %s to %s",
			ch, m.api.space, oldState, newState)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"math/rand"
	"net/mail"
	"strconv"
	"time"

	"log"

	"github.com/extemporalgenome/slug"
	"github.com/joshsoftware/curem/config"
	"labix.org/v2/mgo/bson"
)

// contact type holds the fields related to a particular contact.
// omitempty tag will make sure the database doesn't contain content like:
//
//   {
//    _id: someId
//    company: ABC
//    Person: Xyz
//    Phone:
//    Skype:
//    Country:
//   }
// Instead, it will store the above data as:
//
//   {
//    _id: someId
//    company: ABC
//    Person: Xyz
//   }
type contact struct {
	Id        bson.ObjectId `bson:"_id"                  json:"id"`
	Company   string        `bson:"company,omitempty"    json:"company,omitempty"`
	Person    string        `bson:"person,omitempty"     json:"person,omitempty"`
	Slug      string        `bson:"slug,omitempty"       json:"slug,omitempty"`
	Email     string        `bson:"email,omitempty"      json:"email,omitempty"`
	Phone     string        `bson:"phone,omitempty"      json:"phone,omitempty"`
	SkypeId   string        `bson:"skypeid,omitempty"    json:"skypeid,omitempty"`
	Country   string        `bson:"country,omitempty"    json:"country,omitempty"`
	CreatedAt time.Time     `bson:"createdAt,omitempty"  json:"createdAt,omitempty"`
	UpdatedAt time.Time     `bson:"updatedAt,omitempty"  json:"updatedAt,omitempty"`
}

// incomingContact type is used for handling PATCH requests.
// To understand why we are using pointer types for fields,
// refer to http://blog.golang.org/json-and-go .
// Using pointer types, we can differentiate intentional nil value fields
// and empty fields.
// This type is used *only* for decoding json obtained from a PATCH request.
type incomingContact struct {
	Id      *bson.ObjectId `json:"id"`
	Company *string        `json:"company,omitempty"`
	Person  *string        `json:"person,omitempty"`
	Slug    *string        `json:"slug,omitempty"`
	Email   *string        `json:"email,omitempty"`
	Phone   *string        `json:"phone,omitempty"`
	SkypeId *string        `json:"skypeid,omitempty"`
	Country *string        `json:"country,omitempty"`
}

// copyIncomingFields copies the fields from an incomingContact into a
// contact object.
// Using multiple if statements provides more granularity, while allowing
// update of only some specific fields.
// TODO(Hari): Use reflect package instead of multiple if statements
func (c *contact) copyIncomingFields(i *incomingContact) error {
	if i.Id != nil {
		if *i.Id != c.Id {
			return errors.New("Id doesn't match")
		}
	}
	if i.Slug != nil {
		if *i.Slug != c.Slug {
			return errors.New("Slug can't be updated")
		}
	}
	if i.Company != nil {
		c.Company = *i.Company
	}
	if i.Person != nil {
		c.Person = *i.Person
	}
	if i.Slug != nil {
		c.Slug = *i.Slug
	}
	if i.Email != nil {
		c.Email = *i.Email
	}
	if i.Phone != nil {
		c.Phone = *i.Phone
	}
	if i.SkypeId != nil {
		c.SkypeId = *i.SkypeId
	}
	if i.Country != nil {
		c.Country = *i.Country
	}
	return nil
}

func validateContact(c *contact) error {
	if c.Person == "" {
		err := errors.New("person can't be empty")
		return err
	}
	if c.Email == "" {
		err := errors.New("email can't be empty")
		return err
	}
	if _, err := mail.ParseAddress(c.Email); err != nil {
		return err
	}

	return nil
}

// NewContact takes the fields of a contact, initializes a struct of contact type and returns
// the pointer to that struct.
func NewContact(company, person, email, phone, skypeid, country string) (*contact, error) {
	doc := contact{
		Id:      bson.NewObjectId(),
		Company: company,
		Person:  person,
		Email:   email,
		Phone:   phone,
		SkypeId: skypeid,
		Country: country,
	}
	if err := validateContact(&doc); err != nil {
		return &contact{}, err
	}
	slugify(&doc)

	doc.CreatedAt = doc.Id.Time()
	doc.UpdatedAt = doc.CreatedAt

	err := config.ContactsCollection.Insert(doc)
	if err != nil {
		return &contact{}, err
	}
	return &doc, nil
}

// GetContactById takes the contact Id as an argument and returns a pointer to the contact object.
func GetContactById(i bson.ObjectId) (*contact, error) {
	var c contact
	err := config.ContactsCollection.FindId(i).One(&c)
	if err != nil {
		return &contact{}, err
	}
	return &c, nil
}

// GetContactBySlug takes the contact slug as an argument and returns a pointer to the contact object.
func GetContactBySlug(slug string) (*contact, error) {
	var c []contact
	err := config.ContactsCollection.Find(bson.M{"slug": slug}).All(&c)
	if err != nil {
		return &contact{}, err
	}

	if len(c) == 0 {
		return &contact{}, errors.New("no contact")
	}

	if len(c) > 1 {
		return &contact{}, errors.New("more than 1 contact found")
	}
	return &c[0], nil
}

// GetAllContacts fetches all the contacts from the database.
func GetAllContacts() ([]contact, error) {
	var c []contact
	err := config.ContactsCollection.Find(nil).All(&c)
	if err != nil {
		return []contact{}, err
	}
	return c, nil
}

// Update updates the contact in the database.
// First, fetch a contact from the database and change the necessary fields.
// Then call the Update method on that contact object.
func (c *contact) Update() error {
	if err := validateContact(c); err != nil {
		return err
	}
	c.UpdatedAt = bson.Now()
	err := config.ContactsCollection.UpdateId(c.Id, c)
	return err
}

// Delete deletes the contact from the database.
func (c *contact) Delete() error {
	return config.ContactsCollection.RemoveId(c.Id)
}

func slugify(c *contact) {
	base := slug.SlugAscii(c.Person)
	temp := base
	rand.Seed(time.Now().UnixNano()) // takes the current time in nanoseconds as the seed
	i := rand.Intn(10000)
	for {
		if slugExists(temp) {
			temp = base + "-" + strconv.Itoa(i)
			i = rand.Intn(10000)
		} else {
			c.Slug = temp
			return
		}

	}
}

func slugExists(slug string) bool {
	var c []contact
	err := config.ContactsCollection.Find(bson.M{"slug": slug}).All(&c)
	if err != nil {
		log.Fatalf("%s", err)
	}
	if len(c) == 0 {
		return false
	}
	return true
}
<commit_msg>Update doc and change names of slug functions to be specific to contacts.<commit_after>package main

import (
	"errors"
	"math/rand"
	"net/mail"
	"strconv"
	"time"

	"log"

	"github.com/extemporalgenome/slug"
	"github.com/joshsoftware/curem/config"
	"labix.org/v2/mgo/bson"
)

// contact type holds the fields related to a particular contact.
// omitempty tag will make sure the database doesn't contain content like:
//
//   {
//    _id: someId
//    company: ABC
//    Person: Xyz
//    Phone:
//    Skype:
//    Country:
//   }
// Instead, it will store the above data as:
//
//   {
//    _id: someId
//    company: ABC
//    Person: Xyz
//   }
type contact struct {
	Id        bson.ObjectId `bson:"_id"                  json:"id"`
	Company   string        `bson:"company,omitempty"    json:"company,omitempty"`
	Person    string        `bson:"person,omitempty"     json:"person,omitempty"`
	Slug      string        `bson:"slug,omitempty"       json:"slug,omitempty"`
	Email     string        `bson:"email,omitempty"      json:"email,omitempty"`
	Phone     string        `bson:"phone,omitempty"      json:"phone,omitempty"`
	SkypeId   string        `bson:"skypeid,omitempty"    json:"skypeid,omitempty"`
	Country   string        `bson:"country,omitempty"    json:"country,omitempty"`
	CreatedAt time.Time     `bson:"createdAt,omitempty"  json:"createdAt,omitempty"`
	UpdatedAt time.Time     `bson:"updatedAt,omitempty"  json:"updatedAt,omitempty"`
}

// incomingContact type is used for handling PATCH requests.
// To understand why we are using pointer types for fields,
// refer to http://blog.golang.org/json-and-go .
// Using pointer types, we can differentiate intentional nil value fields
// and empty fields.
// This type is used *only* for decoding json obtained from a PATCH request.
type incomingContact struct {
	Id      *bson.ObjectId `json:"id"`
	Company *string        `json:"company,omitempty"`
	Person  *string        `json:"person,omitempty"`
	Slug    *string        `json:"slug,omitempty"`
	Email   *string        `json:"email,omitempty"`
	Phone   *string        `json:"phone,omitempty"`
	SkypeId *string        `json:"skypeid,omitempty"`
	Country *string        `json:"country,omitempty"`
}

// copyIncomingFields copies the fields from an incomingContact into a
// contact object.
// Using multiple if statements provides more granularity, while allowing
// update of only some specific fields.
// TODO(Hari): Use reflect package instead of multiple if statements
func (c *contact) copyIncomingFields(i *incomingContact) error {
	if i.Id != nil {
		if *i.Id != c.Id {
			return errors.New("Id doesn't match")
		}
	}
	if i.Slug != nil {
		if *i.Slug != c.Slug {
			return errors.New("Slug can't be updated")
		}
	}
	if i.Company != nil {
		c.Company = *i.Company
	}
	if i.Person != nil {
		c.Person = *i.Person
	}
	if i.Slug != nil {
		c.Slug = *i.Slug
	}
	if i.Email != nil {
		c.Email = *i.Email
	}
	if i.Phone != nil {
		c.Phone = *i.Phone
	}
	if i.SkypeId != nil {
		c.SkypeId = *i.SkypeId
	}
	if i.Country != nil {
		c.Country = *i.Country
	}
	return nil
}

func validateContact(c *contact) error {
	if c.Person == "" {
		err := errors.New("person can't be empty")
		return err
	}
	if c.Email == "" {
		err := errors.New("email can't be empty")
		return err
	}
	if _, err := mail.ParseAddress(c.Email); err != nil {
		return err
	}

	return nil
}

// NewContact takes the fields of a contact, initializes a struct of contact type and returns
// the pointer to that struct.
func NewContact(company, person, email, phone, skypeid, country string) (*contact, error) {
	doc := contact{
		Id:      bson.NewObjectId(),
		Company: company,
		Person:  person,
		Email:   email,
		Phone:   phone,
		SkypeId: skypeid,
		Country: country,
	}
	if err := validateContact(&doc); err != nil {
		return &contact{}, err
	}
	slugifyContact(&doc)

	doc.CreatedAt = doc.Id.Time()
	doc.UpdatedAt = doc.CreatedAt

	err := config.ContactsCollection.Insert(doc)
	if err != nil {
		return &contact{}, err
	}
	return &doc, nil
}

// GetContactById takes the contact Id as an argument and returns a pointer to the contact object.
func GetContactById(i bson.ObjectId) (*contact, error) {
	var c contact
	err := config.ContactsCollection.FindId(i).One(&c)
	if err != nil {
		return &contact{}, err
	}
	return &c, nil
}

// GetContactBySlug takes the contact slug as an argument and returns a pointer to the contact object.
func GetContactBySlug(slug string) (*contact, error) {
	var c []contact
	err := config.ContactsCollection.Find(bson.M{"slug": slug}).All(&c)
	if err != nil {
		return &contact{}, err
	}

	if len(c) == 0 {
		return &contact{}, errors.New("no contact")
	}

	if len(c) > 1 {
		return &contact{}, errors.New("more than 1 contact found")
	}
	return &c[0], nil
}

// GetAllContacts fetches all the contacts from the database.
func GetAllContacts() ([]contact, error) {
	var c []contact
	err := config.ContactsCollection.Find(nil).All(&c)
	if err != nil {
		return []contact{}, err
	}
	return c, nil
}

// Update updates the contact in the database.
// First, fetch a contact from the database and change the necessary fields.
// Then call the Update method on that contact object.
func (c *contact) Update() error {
	if err := validateContact(c); err != nil {
		return err
	}
	c.UpdatedAt = bson.Now()
	err := config.ContactsCollection.UpdateId(c.Id, c)
	return err
}

// Delete deletes the contact from the database.
func (c *contact) Delete() error {
	return config.ContactsCollection.RemoveId(c.Id)
}

// slugifyContact takes a pointer to a contact as the argument and
// creates a slug and populates the slug field of the contact.
func slugifyContact(c *contact) {
	base := slug.SlugAscii(c.Person)
	temp := base
	rand.Seed(time.Now().UnixNano()) // takes the current time in nanoseconds as the seed
	i := rand.Intn(10000)
	for {
		if contactSlugExists(temp) {
			temp = base + "-" + strconv.Itoa(i)
			i = rand.Intn(10000)
		} else {
			c.Slug = temp
			return
		}

	}
}

// contactSlugExists returns true if a slug for a contact already exists in the database
// else returns false.
func contactSlugExists(slug string) bool {
	var c []contact
	err := config.ContactsCollection.Find(bson.M{"slug": slug}).All(&c)
	if err != nil {
		log.Println(err)
		return true // returns true just to be safe.
	}
	if len(c) == 0 {
		return false
	}
	return true
}
<|endoftext|>