<commit_before>package volume

import (
	"strings"

	snapv1 "github.com/kubernetes-incubator/external-storage/snapshot/pkg/apis/crd/v1"
	snapshotVolume "github.com/kubernetes-incubator/external-storage/snapshot/pkg/volume"
	stork_crd "github.com/libopenstorage/stork/pkg/apis/stork/v1alpha1"
	"github.com/libopenstorage/stork/pkg/errors"
	"github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
)

const (
	// Default snapshot type if drivers don't support different types or can't
	// find driver
	defaultSnapType = "Local"
)

// Driver defines an external volume driver interface.
// Any driver that wants to be used with stork needs to implement these
// interfaces.
type Driver interface {
	// Init initializes the volume driver.
	Init(interface{}) error

	// String returns the string name of this driver.
	String() string

	// InspectVolume returns information about a volume.
	InspectVolume(volumeID string) (*Info, error)

	// GetNodes Get the list of nodes where the driver is available
	GetNodes() ([]*NodeInfo, error)

	// GetPodVolumes Get all the volumes used by a pod backed by the driver
	GetPodVolumes(*v1.PodSpec, string) ([]*Info, error)

	// GetVolumeClaimTemplates Get all the volume templates from the list backed by
	// the driver
	GetVolumeClaimTemplates([]v1.PersistentVolumeClaim) ([]v1.PersistentVolumeClaim, error)

	// OwnsPVC returns true if the PVC is owned by the driver
	OwnsPVC(pvc *v1.PersistentVolumeClaim) bool

	// GetSnapshotPlugin Get the snapshot plugin to be used for the driver
	GetSnapshotPlugin() snapshotVolume.Plugin

	// GetSnapshotType Get the type of the snapshot. Return error is snapshot
	// doesn't belong to driver
	GetSnapshotType(snap *snapv1.VolumeSnapshot) (string, error)

	// Stop the driver
	Stop() error

	// ClusterPairPluginInterface Interface to pair clusters
	ClusterPairPluginInterface
	// MigratePluginInterface Interface to migrate data between clusters
	MigratePluginInterface
}

// ClusterPairPluginInterface Interface to pair clusters
type ClusterPairPluginInterface interface {
	// Create a pair with a remote cluster
	CreatePair(*stork_crd.ClusterPair) (string, error)
	// Deletes a paring with a remote cluster
	DeletePair(*stork_crd.ClusterPair) error
}

// MigratePluginInterface Interface to migrate data between clusters
type MigratePluginInterface interface {
	// Start migration of volumes specified by the spec. Should only migrate
	// volumes, not the specs associated with them
	StartMigration(*stork_crd.Migration) ([]*stork_crd.VolumeInfo, error)
	// Get the status of migration of the volumes specified in the status
	// for the migration spec
	GetMigrationStatus(*stork_crd.Migration) ([]*stork_crd.VolumeInfo, error)
	// Cancel the migration of volumes specified in the status
	CancelMigration(*stork_crd.Migration) error
	// Update the PVC spec to point to the migrated volume on the destination
	// cluster
	UpdateMigratedPersistentVolumeSpec(object runtime.Unstructured) (runtime.Unstructured, error)
}

// Info Information about a volume
type Info struct {
	// VolumeID is a unique identifier for the volume
	VolumeID string
	// VolumeName is the name for the volume
	VolumeName string
	// DataNodes is a list of nodes where the data for the volume resides
	DataNodes []string
	// Size is the size of the volume in GB
	Size uint64
	// ParentID points to the ID of the parent volume for snapshots
	ParentID string
	// Labels are user applied labels on the volume
	Labels map[string]string
	// VolumeSourceRef is a optional reference to the source of the volume
	VolumeSourceRef interface{}
}

// NodeStatus Status of driver on a node
type NodeStatus string

const (
	// NodeOnline Node is online
	NodeOnline NodeStatus = "Online"
	// NodeOffline Node is Offline
	NodeOffline NodeStatus = "Offline"
	// NodeDegraded Node is in degraded state
	NodeDegraded NodeStatus = "Degraded"
)

// NodeInfo Information about a node
type NodeInfo struct {
	// ID is a unique identifier for the node
	ID string
	// Hostname of the node. Should be in lower case because Kubernetes
	// converts it to lower case
	Hostname string
	// IPs List of IPs associated with the node
	IPs []string
	// Rack Specifies the rack within the datacenter where the node is located
	Rack string
	// Zone Specifies the zone where the rack is located
	Zone string
	// Region Specifies the region where the datacenter is located
	Region string
	// Status of the node
	Status NodeStatus
}

var (
	volDrivers = make(map[string]Driver)
)

// Register registers the given volume driver
func Register(name string, d Driver) error {
	logrus.Debugf("Registering volume driver: %v", name)
	volDrivers[name] = d
	return nil
}

// Get an external storage provider to be used with Stork
func Get(name string) (Driver, error) {
	d, ok := volDrivers[name]
	if ok {
		return d, nil
	}

	return nil, &errors.ErrNotFound{
		ID:   name,
		Type: "VolumeDriver",
	}
}

// ClusterPairNotSupported to be used by drivers that don't support pairing
type ClusterPairNotSupported struct{}

// CreatePair Returns ErrNotSupported
func (c *ClusterPairNotSupported) CreatePair(*stork_crd.ClusterPair) (string, error) {
	return "", &errors.ErrNotSupported{}
}

// DeletePair Returns ErrNotSupported
func (c *ClusterPairNotSupported) DeletePair(*stork_crd.ClusterPair) error {
	return &errors.ErrNotSupported{}
}

// MigrationNotSupported to be used by drivers that don't support migration
type MigrationNotSupported struct{}

// StartMigration returns ErrNotSupported
func (m *MigrationNotSupported) StartMigration(*stork_crd.Migration) ([]*stork_crd.VolumeInfo, error) {
	return nil, &errors.ErrNotSupported{}
}

// GetMigrationStatus returns ErrNotSupported
func (m *MigrationNotSupported) GetMigrationStatus(*stork_crd.Migration) ([]*stork_crd.VolumeInfo, error) {
	return nil, &errors.ErrNotSupported{}
}

// CancelMigration returns ErrNotSupported
func (m *MigrationNotSupported) CancelMigration(*stork_crd.Migration) error {
	return &errors.ErrNotSupported{}
}

// UpdateMigratedPersistentVolumeSpec returns ErrNotSupported
func (m *MigrationNotSupported) UpdateMigratedPersistentVolumeSpec(
	runtime.Unstructured,
) (runtime.Unstructured, error) {
	return nil, &errors.ErrNotSupported{}
}

// IsNodeMatch There are a couple of things that need to be checked to see if the driver
// node matched the k8s node since different k8s installs set the node name,
// hostname and IPs differently
func IsNodeMatch(k8sNode *v1.Node, driverNode *NodeInfo) bool {
	if driverNode == nil {
		return false
	}

	if isHostnameMatch(driverNode.ID, k8sNode.Name) {
		return true
	}
	for _, address := range k8sNode.Status.Addresses {
		switch address.Type {
		case v1.NodeHostName:
			if isHostnameMatch(driverNode.Hostname, address.Address) {
				return true
			}
		case v1.NodeInternalIP:
			for _, ip := range driverNode.IPs {
				if ip == address.Address {
					return true
				}
			}
		}
	}
	return false
}

// The driver might not return fully qualified hostnames, so check if the short
// hostname matches too
func isHostnameMatch(driverHostname string, k8sHostname string) bool {
	if driverHostname == k8sHostname {
		return true
	}
	if strings.HasPrefix(k8sHostname, driverHostname+".") {
		return true
	}
	return false
}

// GetSnapshotType gets the snapshot type
func GetSnapshotType(snap *snapv1.VolumeSnapshot) string {
	for _, driver := range volDrivers {
		snapType, err := driver.GetSnapshotType(snap)
		if err == nil {
			return snapType
		}
	}

	return defaultSnapType
}
<commit_msg>Add check to resolve hostname when matching nodes for k8s on DC/OS<commit_after>package volume

import (
	"net"
	"strings"

	snapv1 "github.com/kubernetes-incubator/external-storage/snapshot/pkg/apis/crd/v1"
	snapshotVolume "github.com/kubernetes-incubator/external-storage/snapshot/pkg/volume"
	stork_crd "github.com/libopenstorage/stork/pkg/apis/stork/v1alpha1"
	"github.com/libopenstorage/stork/pkg/errors"
	"github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
)

const (
	// Default snapshot type if drivers don't support different types or can't
	// find driver
	defaultSnapType = "Local"
)

// Driver defines an external volume driver interface.
// Any driver that wants to be used with stork needs to implement these
// interfaces.
type Driver interface {
	// Init initializes the volume driver.
	Init(interface{}) error

	// String returns the string name of this driver.
	String() string

	// InspectVolume returns information about a volume.
	InspectVolume(volumeID string) (*Info, error)

	// GetNodes Get the list of nodes where the driver is available
	GetNodes() ([]*NodeInfo, error)

	// GetPodVolumes Get all the volumes used by a pod backed by the driver
	GetPodVolumes(*v1.PodSpec, string) ([]*Info, error)

	// GetVolumeClaimTemplates Get all the volume templates from the list backed by
	// the driver
	GetVolumeClaimTemplates([]v1.PersistentVolumeClaim) ([]v1.PersistentVolumeClaim, error)

	// OwnsPVC returns true if the PVC is owned by the driver
	OwnsPVC(pvc *v1.PersistentVolumeClaim) bool

	// GetSnapshotPlugin Get the snapshot plugin to be used for the driver
	GetSnapshotPlugin() snapshotVolume.Plugin

	// GetSnapshotType Get the type of the snapshot. Return error is snapshot
	// doesn't belong to driver
	GetSnapshotType(snap *snapv1.VolumeSnapshot) (string, error)

	// Stop the driver
	Stop() error

	// ClusterPairPluginInterface Interface to pair clusters
	ClusterPairPluginInterface
	// MigratePluginInterface Interface to migrate data between clusters
	MigratePluginInterface
}

// ClusterPairPluginInterface Interface to pair clusters
type ClusterPairPluginInterface interface {
	// Create a pair with a remote cluster
	CreatePair(*stork_crd.ClusterPair) (string, error)
	// Deletes a paring with a remote cluster
	DeletePair(*stork_crd.ClusterPair) error
}

// MigratePluginInterface Interface to migrate data between clusters
type MigratePluginInterface interface {
	// Start migration of volumes specified by the spec. Should only migrate
	// volumes, not the specs associated with them
	StartMigration(*stork_crd.Migration) ([]*stork_crd.VolumeInfo, error)
	// Get the status of migration of the volumes specified in the status
	// for the migration spec
	GetMigrationStatus(*stork_crd.Migration) ([]*stork_crd.VolumeInfo, error)
	// Cancel the migration of volumes specified in the status
	CancelMigration(*stork_crd.Migration) error
	// Update the PVC spec to point to the migrated volume on the destination
	// cluster
	UpdateMigratedPersistentVolumeSpec(object runtime.Unstructured) (runtime.Unstructured, error)
}

// Info Information about a volume
type Info struct {
	// VolumeID is a unique identifier for the volume
	VolumeID string
	// VolumeName is the name for the volume
	VolumeName string
	// DataNodes is a list of nodes where the data for the volume resides
	DataNodes []string
	// Size is the size of the volume in GB
	Size uint64
	// ParentID points to the ID of the parent volume for snapshots
	ParentID string
	// Labels are user applied labels on the volume
	Labels map[string]string
	// VolumeSourceRef is a optional reference to the source of the volume
	VolumeSourceRef interface{}
}

// NodeStatus Status of driver on a node
type NodeStatus string

const (
	// NodeOnline Node is online
	NodeOnline NodeStatus = "Online"
	// NodeOffline Node is Offline
	NodeOffline NodeStatus = "Offline"
	// NodeDegraded Node is in degraded state
	NodeDegraded NodeStatus = "Degraded"
)

// NodeInfo Information about a node
type NodeInfo struct {
	// ID is a unique identifier for the node
	ID string
	// Hostname of the node. Should be in lower case because Kubernetes
	// converts it to lower case
	Hostname string
	// IPs List of IPs associated with the node
	IPs []string
	// Rack Specifies the rack within the datacenter where the node is located
	Rack string
	// Zone Specifies the zone where the rack is located
	Zone string
	// Region Specifies the region where the datacenter is located
	Region string
	// Status of the node
	Status NodeStatus
}

var (
	volDrivers = make(map[string]Driver)
)

// Register registers the given volume driver
func Register(name string, d Driver) error {
	logrus.Debugf("Registering volume driver: %v", name)
	volDrivers[name] = d
	return nil
}

// Get an external storage provider to be used with Stork
func Get(name string) (Driver, error) {
	d, ok := volDrivers[name]
	if ok {
		return d, nil
	}

	return nil, &errors.ErrNotFound{
		ID:   name,
		Type: "VolumeDriver",
	}
}

// ClusterPairNotSupported to be used by drivers that don't support pairing
type ClusterPairNotSupported struct{}

// CreatePair Returns ErrNotSupported
func (c *ClusterPairNotSupported) CreatePair(*stork_crd.ClusterPair) (string, error) {
	return "", &errors.ErrNotSupported{}
}

// DeletePair Returns ErrNotSupported
func (c *ClusterPairNotSupported) DeletePair(*stork_crd.ClusterPair) error {
	return &errors.ErrNotSupported{}
}

// MigrationNotSupported to be used by drivers that don't support migration
type MigrationNotSupported struct{}

// StartMigration returns ErrNotSupported
func (m *MigrationNotSupported) StartMigration(*stork_crd.Migration) ([]*stork_crd.VolumeInfo, error) {
	return nil, &errors.ErrNotSupported{}
}

// GetMigrationStatus returns ErrNotSupported
func (m *MigrationNotSupported) GetMigrationStatus(*stork_crd.Migration) ([]*stork_crd.VolumeInfo, error) {
	return nil, &errors.ErrNotSupported{}
}

// CancelMigration returns ErrNotSupported
func (m *MigrationNotSupported) CancelMigration(*stork_crd.Migration) error {
	return &errors.ErrNotSupported{}
}

// UpdateMigratedPersistentVolumeSpec returns ErrNotSupported
func (m *MigrationNotSupported) UpdateMigratedPersistentVolumeSpec(
	runtime.Unstructured,
) (runtime.Unstructured, error) {
	return nil, &errors.ErrNotSupported{}
}

// IsNodeMatch There are a couple of things that need to be checked to see if the driver
// node matched the k8s node since different k8s installs set the node name,
// hostname and IPs differently
func IsNodeMatch(k8sNode *v1.Node, driverNode *NodeInfo) bool {
	if driverNode == nil {
		return false
	}

	if isHostnameMatch(driverNode.ID, k8sNode.Name) {
		return true
	}
	for _, address := range k8sNode.Status.Addresses {
		switch address.Type {
		case v1.NodeHostName:
			if isHostnameMatch(driverNode.Hostname, address.Address) {
				return true
			}
			if isResolvedHostnameMatch(driverNode.IPs, address.Address) {
				return true
			}
		case v1.NodeInternalIP:
			for _, ip := range driverNode.IPs {
				if ip == address.Address {
					return true
				}
			}
		}
	}
	return false
}

// The driver might not return fully qualified hostnames, so check if the short
// hostname matches too
func isHostnameMatch(driverHostname string, k8sHostname string) bool {
	if driverHostname == k8sHostname {
		return true
	}
	if strings.HasPrefix(k8sHostname, driverHostname+".") {
		return true
	}
	return false
}

// For K8s on DC/OS lookup IPs of the hostname. Could block for invalid
// hostnames so don't want to do this for other environments currently.
func isResolvedHostnameMatch(driverIPs []string, k8sHostname string) bool {
	if strings.HasSuffix(k8sHostname, ".mesos") {
		k8sIPs, err := net.LookupHost(k8sHostname)
		if err != nil {
			return false
		}
		for _, dip := range driverIPs {
			for _, kip := range k8sIPs {
				if dip == kip {
					return true
				}
			}
		}
	}
	return false
}

// GetSnapshotType gets the snapshot type
func GetSnapshotType(snap *snapv1.VolumeSnapshot) string {
	for _, driver := range volDrivers {
		snapType, err := driver.GetSnapshotType(snap)
		if err == nil {
			return snapType
		}
	}

	return defaultSnapType
}
<|endoftext|>


<commit_before>// MIT License
//
// Copyright (c) 2019 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"time"

	log "github.com/Sirupsen/logrus"
	hue "github.com/stefanwichmann/go.hue"
)

// Light represents a light kelvin can automate in your system.
type Light struct {
	ID               int        `json:"id"`
	Name             string     `json:"name"`
	HueLight         HueLight   `json:"-"`
	TargetLightState LightState `json:"targetLightState,omitempty"`
	Scheduled        bool       `json:"scheduled"`
	Reachable        bool       `json:"reachable"`
	On               bool       `json:"on"`
	Tracking         bool       `json:"-"`
	Automatic        bool       `json:"automatic"`
	Schedule         Schedule   `json:"-"`
	Interval         Interval   `json:"interval"`
	Appearance       time.Time  `json:"-"`
}

func (light *Light) updateCurrentLightState(attr hue.LightAttributes) error {
	light.HueLight.updateCurrentLightState(attr)
	light.Reachable = light.HueLight.Reachable
	light.On = light.HueLight.On
	return nil
}

func (light *Light) update() (bool, error) {
	// Is the light associated to any schedule?
	if !light.Scheduled {
		return false, nil
	}

	// If the light is not reachable anymore clean up
	if !light.Reachable {
		if light.Tracking {
			log.Printf("💡 Light %s - Light is no longer reachable. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// If the light was turned off clean up
	if !light.On {
		if light.Tracking {
			log.Printf("💡 Light %s - Light was turned off. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// Did the light just appear?
	if !light.Tracking {
		log.Printf("💡 Light %s - Light just appeared.", light.Name)
		light.Tracking = true
		light.Appearance = time.Now()

		// Should we auto-enable Kelvin?
		if light.Schedule.enableWhenLightsAppear {
			log.Printf("💡 Light %s - Initializing state to %vK at %v%% brightness.", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				log.Debugf("💡 Light %s - Could not initialize light after %v", light.Name, time.Since(light.Appearance))
				return false, err
			}

			light.Automatic = true
			log.Debugf("💡 Light %s - Light was initialized to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}
	}

	// Ignore light if it was changed manually
	if !light.Automatic {
		// return if we should ignore color temperature and brightness
		if light.TargetLightState.ColorTemperature == -1 && light.TargetLightState.Brightness == -1 {
			return false, nil
		}

		// if status == scene state --> Activate Kelvin
		if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
			log.Printf("💡 Light %s - Detected matching target state. Activating Kelvin...", light.Name)
			light.Automatic = true

			// set correct target lightstate on HueLight
			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				return false, err
			}
			log.Debugf("💡 Light %s - Updated light state to %vK at %v%% brightness (Scene detection)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}

		// Light was changed manually and does not conform to scene detection
		return false, nil
	}

	// Did the user manually change the light state?
	if light.HueLight.hasChanged() {
		if log.GetLevel() == log.DebugLevel {
			log.Debugf("💡 Light %s - Light state has been changed manually after %v (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, time.Since(light.Appearance), light.HueLight.TargetColorTemperature, light.HueLight.CurrentColorTemperature, light.HueLight.TargetColor, light.HueLight.CurrentColor, light.HueLight.TargetBrightness, light.HueLight.CurrentBrightness)
		} else {
			log.Printf("💡 Light %s - Light state has been changed manually. Disabling Kelvin...", light.Name)
		}
		light.Automatic = false
		return false, nil
	}

	// Update of lightstate needed?
	if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
		return false, nil
	}

	// Light is turned on and in automatic state. Set target lightstate.
	err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	if err != nil {
		return false, err
	}

	log.Printf("💡 Light %s - Updated light state to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	return true, nil
}

func (light *Light) updateSchedule(schedule Schedule) {
	light.Schedule = schedule
	light.Scheduled = true
	log.Printf("💡 Light %s - Activating schedule for %v (Sunrise: %v, Sunset: %v)", light.Name, light.Schedule.endOfDay.Format("Jan 2 2006"), light.Schedule.sunrise.Time.Format("15:04"), light.Schedule.sunset.Time.Format("15:04"))
	light.updateInterval()
}

func (light *Light) updateInterval() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No interval to update...", light.Name)
		return
	}

	newInterval, err := light.Schedule.currentInterval(time.Now())
	if err != nil {
		log.Printf("💡 Light %s - Light has no active interval. Ignoring...", light.Name)
		light.Interval = newInterval // Assign empty interval
		return
	}
	if newInterval != light.Interval {
		light.Interval = newInterval
		log.Printf("💡 Light %s - Activating interval %v - %v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"))
	}
}

func (light *Light) updateTargetLightState() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No target light state to update...", light.Name)
		return
	}

	// Calculate the target lightstate from the interval
	newLightState := light.Interval.calculateLightStateInInterval(time.Now())

	// Did the target light state change?
	if newLightState.equals(light.TargetLightState) {
		return
	}

	// First initialization of the TargetLightState?
	if light.TargetLightState.ColorTemperature == 0 && light.TargetLightState.Brightness == 0 {
		log.Debugf("💡 Light %s - Initialized target light state for the interval %v - %v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), newLightState)
	} else {
		log.Debugf("💡 Light %s - Updated target light state for the interval %v - %v from %+v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), light.TargetLightState, newLightState)
	}

	light.TargetLightState = newLightState
}
<commit_msg>Signal to caller if we tried to update the light state<commit_after>// MIT License
//
// Copyright (c) 2019 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"time"

	log "github.com/Sirupsen/logrus"
	hue "github.com/stefanwichmann/go.hue"
)

// Light represents a light kelvin can automate in your system.
type Light struct {
	ID               int        `json:"id"`
	Name             string     `json:"name"`
	HueLight         HueLight   `json:"-"`
	TargetLightState LightState `json:"targetLightState,omitempty"`
	Scheduled        bool       `json:"scheduled"`
	Reachable        bool       `json:"reachable"`
	On               bool       `json:"on"`
	Tracking         bool       `json:"-"`
	Automatic        bool       `json:"automatic"`
	Schedule         Schedule   `json:"-"`
	Interval         Interval   `json:"interval"`
	Appearance       time.Time  `json:"-"`
}

func (light *Light) updateCurrentLightState(attr hue.LightAttributes) error {
	light.HueLight.updateCurrentLightState(attr)
	light.Reachable = light.HueLight.Reachable
	light.On = light.HueLight.On
	return nil
}

func (light *Light) update() (bool, error) {
	// Is the light associated to any schedule?
	if !light.Scheduled {
		return false, nil
	}

	// If the light is not reachable anymore clean up
	if !light.Reachable {
		if light.Tracking {
			log.Printf("💡 Light %s - Light is no longer reachable. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// If the light was turned off clean up
	if !light.On {
		if light.Tracking {
			log.Printf("💡 Light %s - Light was turned off. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// Did the light just appear?
	if !light.Tracking {
		log.Printf("💡 Light %s - Light just appeared.", light.Name)
		light.Tracking = true
		light.Appearance = time.Now()

		// Should we auto-enable Kelvin?
		if light.Schedule.enableWhenLightsAppear {
			log.Printf("💡 Light %s - Initializing state to %vK at %v%% brightness.", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				log.Debugf("💡 Light %s - Could not initialize light after %v", light.Name, time.Since(light.Appearance))
				return true, err
			}

			light.Automatic = true
			log.Debugf("💡 Light %s - Light was initialized to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}
	}

	// Ignore light if it was changed manually
	if !light.Automatic {
		// return if we should ignore color temperature and brightness
		if light.TargetLightState.ColorTemperature == -1 && light.TargetLightState.Brightness == -1 {
			return false, nil
		}

		// if status == scene state --> Activate Kelvin
		if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
			log.Printf("💡 Light %s - Detected matching target state. Activating Kelvin...", light.Name)
			light.Automatic = true

			// set correct target lightstate on HueLight
			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			if err != nil {
				return true, err
			}
			log.Debugf("💡 Light %s - Updated light state to %vK at %v%% brightness (Scene detection)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}

		// Light was changed manually and does not conform to scene detection
		return false, nil
	}

	// Did the user manually change the light state?
	if light.HueLight.hasChanged() {
		if log.GetLevel() == log.DebugLevel {
			log.Debugf("💡 Light %s - Light state has been changed manually after %v (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, time.Since(light.Appearance), light.HueLight.TargetColorTemperature, light.HueLight.CurrentColorTemperature, light.HueLight.TargetColor, light.HueLight.CurrentColor, light.HueLight.TargetBrightness, light.HueLight.CurrentBrightness)
		} else {
			log.Printf("💡 Light %s - Light state has been changed manually. Disabling Kelvin...", light.Name)
		}
		light.Automatic = false
		return false, nil
	}

	// Update of lightstate needed?
	if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
		return false, nil
	}

	// Light is turned on and in automatic state. Set target lightstate.
	err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	if err != nil {
		return true, err
	}

	log.Printf("💡 Light %s - Updated light state to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	return true, nil
}

func (light *Light) updateSchedule(schedule Schedule) {
	light.Schedule = schedule
	light.Scheduled = true
	log.Printf("💡 Light %s - Activating schedule for %v (Sunrise: %v, Sunset: %v)", light.Name, light.Schedule.endOfDay.Format("Jan 2 2006"), light.Schedule.sunrise.Time.Format("15:04"), light.Schedule.sunset.Time.Format("15:04"))
	light.updateInterval()
}

func (light *Light) updateInterval() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No interval to update...", light.Name)
		return
	}

	newInterval, err := light.Schedule.currentInterval(time.Now())
	if err != nil {
		log.Printf("💡 Light %s - Light has no active interval. Ignoring...", light.Name)
		light.Interval = newInterval // Assign empty interval
		return
	}
	if newInterval != light.Interval {
		light.Interval = newInterval
		log.Printf("💡 Light %s - Activating interval %v - %v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"))
	}
}

func (light *Light) updateTargetLightState() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No target light state to update...", light.Name)
		return
	}

	// Calculate the target lightstate from the interval
	newLightState := light.Interval.calculateLightStateInInterval(time.Now())

	// Did the target light state change?
	if newLightState.equals(light.TargetLightState) {
		return
	}

	// First initialization of the TargetLightState?
	if light.TargetLightState.ColorTemperature == 0 && light.TargetLightState.Brightness == 0 {
		log.Debugf("💡 Light %s - Initialized target light state for the interval %v - %v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), newLightState)
	} else {
		log.Debugf("💡 Light %s - Updated target light state for the interval %v - %v from %+v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), light.TargetLightState, newLightState)
	}

	light.TargetLightState = newLightState
}
<|endoftext|>


<commit_before>// MIT License
//
// Copyright (c) 2017 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"strconv"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/stefanwichmann/go.hue"
)

var lightsSupportingDimming = []string{"Dimmable Light", "Color Temperature Light", "Color Light", "Extended Color Light"}
var lightsSupportingColorTemperature = []string{"Color Temperature Light", "Extended Color Light"}
var lightsSupportingXYColor = []string{"Color Light", "Extended Color Light"}

// Light represents a single physical hue light in your system.
// It is used to read and write it's state.
type Light struct {
	ID                       int
	Name                     string
	HueLight                 hue.Light
	SavedLightState          LightState
	CurrentLightState        LightState
	TargetLightState         LightState
	Scheduled                bool
	On                       bool
	Reachable                bool
	Tracking                 bool
	Automatic                bool
	Dimmable                 bool
	SupportsColorTemperature bool
	SupportsXYColor          bool
	Configuration            *Configuration
	Schedule                 Schedule
	Interval                 Interval
}

const lightUpdateIntervalInSeconds = 1
const lightTransitionIntervalInSeconds = 1
const stateUpdateIntervalInSeconds = 60

func (light *Light) updateCyclic(configuration *Configuration) {
	light.Configuration = configuration

	// Filter devices we can't control
	if !light.Dimmable && !light.SupportsXYColor && !light.SupportsColorTemperature {
		log.Printf("💡 Device %v doesn't support any functionality Kelvin uses. Ignoring...", light.Name)
		return
	}

	light.updateSchedule()
	light.updateInterval()
	light.updateTargetLightState()

	// Start cyclic polling
	log.Debugf("💡 Light %s: Starting cyclic update...", light.Name)
	lightUpdateTick := time.Tick(lightUpdateIntervalInSeconds * time.Second)
	stateUpdateTick := time.Tick(stateUpdateIntervalInSeconds * time.Second)
	for {
		select {
		case <-time.After(time.Until(light.Schedule.endOfDay) + 1*time.Second):
			// Day has ended, calculate new schedule
			light.updateSchedule()
		case <-stateUpdateTick:
			// update interval and color every minute
			light.updateInterval()
			light.updateTargetLightState()
		case <-lightUpdateTick:
			light.update()
		}
	}
}

func (light *Light) initialize() error {
	attr, err := light.HueLight.GetLightAttributes()
	if err != nil {
		return err
	}

	// initialize non changing values
	light.Name = attr.Name
	light.Dimmable = containsString(lightsSupportingDimming, attr.Type)
	light.SupportsColorTemperature = containsString(lightsSupportingColorTemperature, attr.Type)
	light.SupportsXYColor = containsString(lightsSupportingXYColor, attr.Type)

	light.updateCurrentLightState()
	return nil
}

func (light *Light) updateCurrentLightState() error {
	attr, err := light.HueLight.GetLightAttributes()
	if err != nil {
		return err
	}
	light.Reachable = attr.State.Reachable
	if !light.Reachable {
		light.On = false
		light.CurrentLightState = LightState{0, []float32{0, 0}, 0}
	} else {
		light.On = attr.State.On
		light.CurrentLightState = lightStateFromHueValues(attr.State.Ct, attr.State.Xy, attr.State.Bri)
	}

	// validate lightstate after updating
	if !light.CurrentLightState.isValid() {
		log.Warningf("Validation failed in updateCurrentLightState for light %s", light.Name)
	}
	return nil
}

func (light *Light) update() error {
	// refresh current state
	light.updateCurrentLightState()

	// is the light associated to any schedule?
	if !light.Scheduled {
		return nil
	}

	// Light reachable or on?
	if !light.Reachable || !light.On {
		if light.Tracking {
			log.Printf("💡 Light %s is no longer reachable or turned on. Clearing state.", light.Name)
			light.Tracking = false
			light.Automatic = false
			return nil
		}

		// ignore light because we are not tracking it.
		return nil
	}

	// did the light just appear?
	if !light.Tracking {
		log.Printf("💡 Light %s just appeared. Initializing state to %vK at %v%% brightness.", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

		// For initialization we set the state again and again for 10 seconds
		// because during startup the zigbee communication might be unstable
		for index := 0; index < 9; index++ {
			light.setLightState(light.TargetLightState)
		}
		// safe the state of the last iteration
		setLightState, err := light.setLightState(light.TargetLightState)
		if err != nil {
			return err
		}
		if !setLightState.equals(light.TargetLightState) {
			log.Warningf("💡 Light %s - targetLightState and set state differ: %v, %v", light.Name, light.TargetLightState, setLightState)
		}
		light.SavedLightState = setLightState
		light.Tracking = true
		light.Automatic = true
		log.Debugf("💡 Light %s was updated to %vK at %v%% brightness", light.Name, light.SavedLightState.ColorTemperature, light.SavedLightState.Brightness)

		// Debug: Update current state to double check SetState
		if log.GetLevel() == log.DebugLevel {
			light.updateCurrentLightState()
			if !light.CurrentLightState.equals(light.SavedLightState) {
				log.Warningf("💡 Light %s failed to update it's state from %+v to %+v", light.Name, light.CurrentLightState, light.SavedLightState)
			} else {
				log.Debugf("💡 Light %s was successfully updated.", light.Name)
			}
		}
		return nil
	}

	// light in manual state
	if !light.Automatic {
		return nil
	}

	// did the user manually change the light state?
	if !light.SavedLightState.equals(light.CurrentLightState) {
		if log.GetLevel() == log.DebugLevel {
			log.Debugf("💡 Light %s was manually changed. Current state: %+v - Saved state: %+v", light.Name, light.CurrentLightState, light.SavedLightState)
		} else {
			log.Printf("💡 Light %s was changed manually. Disabling Kelvin.", light.Name)
		}
		light.Automatic = false
		return nil
	}

	// Update needed?
	if light.TargetLightState.equals(light.CurrentLightState) {
		return nil
	}
	log.Debugf("💡 Light %s - Target and Current state differ: %v, %v", light.Name, light.TargetLightState, light.CurrentLightState)

	// Light is reachable, turned on and in automatic state. Calculate next state and set it
	nextLightState := calculateNextLightstate(light.CurrentLightState, light.TargetLightState)
	setLightState, err := light.setLightState(nextLightState)
	if err != nil {
		return err
	}

	// Did the light accept the values?
	if !setLightState.equals(nextLightState) {
		log.Warningf("💡 Light %s - nextLightState and set state differ: %v, %v", light.Name, nextLightState, setLightState)
	}
	light.SavedLightState = setLightState
	log.Printf("💡 Light %s was updated to %vK at %v%% brightness", light.Name, light.SavedLightState.ColorTemperature, light.SavedLightState.Brightness)

	// Debug: Update current state to double check SetState
	if log.GetLevel() == log.DebugLevel {
		light.updateCurrentLightState()
		if !light.CurrentLightState.equals(light.SavedLightState) {
			log.Warningf("💡 Light %s failed to update it's state from %+v to %+v", light.Name, light.CurrentLightState, light.SavedLightState)
		} else {
			log.Debugf("💡 Light %s was successfully updated.", light.Name)
		}
	}

	return nil
}

func (light *Light) setLightState(state LightState) (LightState, error) {
	if !state.isValid() {
		log.Warningf("Validation failed in setLightState for light %s", light.Name)
	}

	var hueLightState hue.SetLightState
	colorTemperature, color, brightness := state.convertValuesToHue()

	// Should change colortemperature?
	if state.ColorTemperature != -1 {
		// Set supported colormodes. If both are, the brigde will choose xy
		if light.SupportsXYColor {
			hueLightState.Xy = []float32{color[0], color[1]}
		}
		if light.SupportsColorTemperature {
			hueLightState.Ct = strconv.Itoa(colorTemperature)
		}
	}

	// Should change brightness?
	if state.Brightness != -1 {
		if state.Brightness == 0 {
			// target brightness zero should turn the light off.
			hueLightState.On = "Off"
		} else if light.Dimmable {
			hueLightState.Bri = strconv.Itoa(brightness)
		}
	}
	hueLightState.TransitionTime = strconv.Itoa(lightTransitionIntervalInSeconds * 10) // conversion to 100ms-value

	// Send new state to the light
	hueResults, err := light.HueLight.SetState(hueLightState)
	if err != nil {
		return LightState{0, []float32{0, 0}, 0}, err
	}

	// iterate over result to acquire set values
	for _, result := range hueResults {
		for key, value := range result.Success {
			path := strings.Split(key, "/")

			switch path[len(path)-1] {
			case "ct":
				colorTemperature = int(value.(float64))
			case "xy":
				color = []float32{} // clear old color values
				for _, elem := range value.([]interface{}) {
					color = append(color, float32(elem.(float64)))
				}
			case "bri":
				brightness = int(value.(float64))
			case "on":
				brightness = 0
			}
		}
	}
	setLightState := lightStateFromHueValues(colorTemperature, color, brightness)
	//log.Debugf("Parsed SetLightState from %+v to %+v", hueResults, setLightState)
	if !setLightState.isValid() {
		log.Warningf("Validation failed in setLightState for light %s", light.Name)
	}

	// wait while the light is in transition before returning
	time.Sleep(lightTransitionIntervalInSeconds + 1*time.Second)
	return setLightState, nil
}

func calculateNextLightstate(currentLightState LightState, targetLightState LightState) LightState {
	nextLightState := LightState{-1, []float32{-1, -1}, -1}

	if targetLightState.ColorTemperature != -1 {
		if currentLightState.ColorTemperature < targetLightState.ColorTemperature {
			nextLightState.ColorTemperature = currentLightState.ColorTemperature + 5
		} else {
			nextLightState.ColorTemperature = currentLightState.ColorTemperature - 5
		}
		x, y := colorTemperatureToXYColor(nextLightState.ColorTemperature)
		nextLightState.Color = []float32{float32(x), float32(y)}
	}

	if targetLightState.Brightness != -1 {
		if currentLightState.Brightness < targetLightState.Brightness {
			nextLightState.Brightness = currentLightState.Brightness + 5
		} else {
			nextLightState.Brightness = currentLightState.Brightness - 5
		}
	}

	if !nextLightState.isValid() {
		log.Debugf("Validation failed in calculateNextLightstate")
	}
	return nextLightState
}

func (light *Light) updateConfiguration(configuration *Configuration) {
	light.Configuration = configuration
	light.updateSchedule()
	light.updateInterval()
	light.updateTargetLightState()
}

func (light *Light) updateSchedule() {
	newSchedule, err := light.Configuration.lightScheduleForDay(light.ID, time.Now())
	if err != nil {
		log.Printf("💡 Light %v is not associated to any schedule. Ignoring...", light.Name)
		light.Schedule = newSchedule // assign empty schedule
		light.Scheduled = false
		return
	}
	light.Schedule = newSchedule
	light.Scheduled = true
	log.Printf("💡 Light %s: Activating schedule for %v (sunrise: %v, sunset: %v)", light.Name, light.Schedule.endOfDay.Format("Jan 2 2006"), light.Schedule.sunrise.Time.Format("15:04"), light.Schedule.sunset.Time.Format("15:04"))
	light.updateInterval()
}

func (light *Light) updateInterval() {
	if !light.Scheduled {
		log.Debugf("💡 Light %v is not associated to any schedule. No interval to update...", light.Name)
		return
	}

	newInterval, err := light.Schedule.currentInterval(time.Now())
	if err != nil {
		log.Printf("💡 Light %v has no active interval. Ignoring...", light.Name)
		light.Interval = newInterval // assign empty interval
		return
	}
	if newInterval != light.Interval {
		light.Interval = newInterval
		log.Printf("💡 Light %s: Activating interval %v - %v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"))
	}
}

func (light *Light) updateTargetLightState() {
	if !light.Scheduled {
		log.Debugf("💡 Light %v is not associated to any schedule. No target light state to update...", light.Name)
		return
	}

	newLightState := light.Interval.getTargetLightState(time.Now())
	if !newLightState.isValid() {
		log.Warningf("Validation failed in updateTargetLightState for light %s", light.Name)
	}

	// First initialization of the TargetLightState
	if light.TargetLightState.ColorTemperature == 0 && len(light.TargetLightState.Color) == 0 && light.TargetLightState.Brightness == 0 {
		light.TargetLightState = newLightState
		log.Debugf("💡 Light %s: Initialized target state to %+v", light.Name, light.TargetLightState)
		return
	}

	if !light.TargetLightState.equals(newLightState) {
		light.TargetLightState = newLightState
		log.Debugf("💡 Light %s: Updated target state to %+v", light.Name, light.TargetLightState)
	}
}
<commit_msg>Gracefully shift to new color or brightness values if a light is on cleanup comments<commit_after>// MIT License
//
// Copyright (c) 2017 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"strconv"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/stefanwichmann/go.hue"
)

var lightsSupportingDimming = []string{"Dimmable Light", "Color Temperature Light", "Color Light", "Extended Color Light"}
var lightsSupportingColorTemperature = []string{"Color Temperature Light", "Extended Color Light"}
var lightsSupportingXYColor = []string{"Color Light", "Extended Color Light"}

// Light represents a single physical hue light in your system.
// It is used to read and write it's state.
type Light struct {
	ID                       int
	Name                     string
	HueLight                 hue.Light
	SavedLightState          LightState
	CurrentLightState        LightState
	TargetLightState         LightState
	Scheduled                bool
	On                       bool
	Reachable                bool
	Tracking                 bool
	Automatic                bool
	Dimmable                 bool
	SupportsColorTemperature bool
	SupportsXYColor          bool
	Configuration            *Configuration
	Schedule                 Schedule
	Interval                 Interval
}

const lightUpdateIntervalInSeconds = 1
const lightTransitionIntervalInSeconds = 1
const stateUpdateIntervalInSeconds = 60

func (light *Light) updateCyclic(configuration *Configuration) {
	light.Configuration = configuration

	// Filter devices we can't control
	if !light.Dimmable && !light.SupportsXYColor && !light.SupportsColorTemperature {
		log.Printf("💡 Device %v doesn't support any functionality Kelvin uses. Ignoring...", light.Name)
		return
	}

	light.updateSchedule()
	light.updateInterval()
	light.updateTargetLightState()

	// Start cyclic polling
	log.Debugf("💡 Light %s: Starting cyclic update...", light.Name)
	lightUpdateTick := time.Tick(lightUpdateIntervalInSeconds * time.Second)
	stateUpdateTick := time.Tick(stateUpdateIntervalInSeconds * time.Second)
	for {
		select {
		case <-time.After(time.Until(light.Schedule.endOfDay) + 1*time.Second):
			// Day has ended, calculate new schedule
			light.updateSchedule()
		case <-stateUpdateTick:
			// update interval and color every minute
			light.updateInterval()
			light.updateTargetLightState()
		case <-lightUpdateTick:
			light.update()
		}
	}
}

func (light *Light) initialize() error {
	attr, err := light.HueLight.GetLightAttributes()
	if err != nil {
		return err
	}

	// initialize non changing values
	light.Name = attr.Name
	light.Dimmable = containsString(lightsSupportingDimming, attr.Type)
	light.SupportsColorTemperature = containsString(lightsSupportingColorTemperature, attr.Type)
	light.SupportsXYColor = containsString(lightsSupportingXYColor, attr.Type)

	light.updateCurrentLightState()
	return nil
}

func (light *Light) updateCurrentLightState() error {
	attr, err := light.HueLight.GetLightAttributes()
	if err != nil {
		return err
	}
	light.Reachable = attr.State.Reachable
	if !light.Reachable {
		light.On = false
		light.CurrentLightState = LightState{0, []float32{0, 0}, 0}
	} else {
		light.On = attr.State.On
		light.CurrentLightState = lightStateFromHueValues(attr.State.Ct, attr.State.Xy, attr.State.Bri)
	}

	// validate lightstate after updating
	if !light.CurrentLightState.isValid() {
		log.Warningf("Validation failed in updateCurrentLightState for light %s", light.Name)
	}
	return nil
}

func (light *Light) update() error {
	// Refresh current light state from bridge
	light.updateCurrentLightState()

	// Is the light associated to any schedule?
	if !light.Scheduled {
		return nil
	}

	// If the light is turned off clean up and do nothing
	if !light.On {
		if light.Tracking {
			log.Printf("💡 Light %s is no longer reachable or turned on. Clearing state.", light.Name)
			light.Tracking = false
			light.Automatic = false
			return nil
		}

		// Ignore light because we are not tracking it.
		return nil
	}

	// Did the light just appear?
	if !light.Tracking {
		log.Printf("💡 Light %s just appeared. Initializing state to %vK at %v%% brightness.", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

		// For initialization we set the state again and again for 10 seconds
		// because during startup the zigbee communication might be unstable
		for index := 0; index < 9; index++ {
			light.setLightState(light.TargetLightState)
		}
		// Safe the state of the last iteration
		setLightState, err := light.setLightState(light.TargetLightState)
		if err != nil {
			return err
		}
		if !setLightState.equals(light.TargetLightState) {
			log.Warningf("💡 Light %s - targetLightState and set state differ: %v, %v", light.Name, light.TargetLightState, setLightState)
		}
		light.SavedLightState = setLightState
		light.Tracking = true
		light.Automatic = true
		log.Debugf("💡 Light %s was updated to %vK at %v%% brightness", light.Name, light.SavedLightState.ColorTemperature, light.SavedLightState.Brightness)

		// Debug: Update current state to double check
		if log.GetLevel() == log.DebugLevel {
			light.updateCurrentLightState()
			if !light.CurrentLightState.equals(light.SavedLightState) {
				log.Warningf("💡 Light %s failed to update it's state from %+v to %+v", light.Name, light.CurrentLightState, light.SavedLightState)
			} else {
				log.Debugf("💡 Light %s was successfully updated.", light.Name)
			}
		}
		return nil
	}

	// Ignore light if it was changed manually
	if !light.Automatic {
		return nil
	}

	// Did the user manually change the light state?
	if !light.SavedLightState.equals(light.CurrentLightState) {
		if log.GetLevel() == log.DebugLevel {
			log.Debugf("💡 Light %s was manually changed. Current state: %+v - Saved state: %+v", light.Name, light.CurrentLightState, light.SavedLightState)
		} else {
			log.Printf("💡 Light %s was changed manually. Disabling Kelvin.", light.Name)
		}
		light.Automatic = false
		return nil
	}

	// Update of lightstate needed?
	if light.TargetLightState.equals(light.CurrentLightState) {
		return nil
	}
	log.Debugf("💡 Light %s - Target and Current state differ: %v, %v", light.Name, light.TargetLightState, light.CurrentLightState)

	// Light is turned on and in automatic state. Set target lightstate.
	setLightState, err := light.setLightState(light.TargetLightState)
	if err != nil {
		return err
	}

	// Did the light accept the values?
	if !setLightState.equals(light.TargetLightState) {
		log.Warningf("💡 Light %s - TargetLightState and set state differ: %v, %v", light.Name, light.TargetLightState, setLightState)
	}
	light.SavedLightState = setLightState
	log.Printf("💡 Light %s was updated to %vK at %v%% brightness", light.Name, light.SavedLightState.ColorTemperature, light.SavedLightState.Brightness)

	// Debug: Update current state to double check
	if log.GetLevel() == log.DebugLevel {
		light.updateCurrentLightState()
		if !light.CurrentLightState.equals(light.SavedLightState) {
			log.Warningf("💡 Light %s failed to update it's state from %+v to %+v", light.Name, light.CurrentLightState, light.SavedLightState)
		} else {
			log.Debugf("💡 Light %s was successfully updated.", light.Name)
		}
	}

	return nil
}

func (light *Light) setLightState(state LightState) (LightState, error) {
	if !state.isValid() {
		log.Warningf("Validation failed in setLightState for light %s", light.Name)
	}

	var hueLightState hue.SetLightState
	colorTemperature, color, brightness := state.convertValuesToHue()

	// Should change color temperature?
	if state.ColorTemperature != -1 {
		// Set supported colormodes. If both are, the brigde will prefer xy colors
		if light.SupportsXYColor {
			hueLightState.Xy = []float32{color[0], color[1]}
		}
		if light.SupportsColorTemperature {
			hueLightState.Ct = strconv.Itoa(colorTemperature)
		}
	}

	// Should change brightness?
	if state.Brightness != -1 {
		if state.Brightness == 0 {
			// Target brightness zero should turn the light off.
			hueLightState.On = "Off"
		} else if light.Dimmable {
			hueLightState.Bri = strconv.Itoa(brightness)
		}
	}
	hueLightState.TransitionTime = strconv.Itoa(lightTransitionIntervalInSeconds * 10) // Conversion to 100ms-value

	// Send new state to the light
	hueResults, err := light.HueLight.SetState(hueLightState)
	if err != nil {
		return LightState{0, []float32{0, 0}, 0}, err
	}

	// Iterate over results to acquire set values
	for _, result := range hueResults {
		for key, value := range result.Success {
			path := strings.Split(key, "/")

			switch path[len(path)-1] {
			case "ct":
				colorTemperature = int(value.(float64))
			case "xy":
				color = []float32{} // Clear old color values
				for _, elem := range value.([]interface{}) {
					color = append(color, float32(elem.(float64)))
				}
			case "bri":
				brightness = int(value.(float64))
			case "on":
				brightness = 0
			}
		}
	}
	setLightState := lightStateFromHueValues(colorTemperature, color, brightness)

	// Wait while the light is in transition before returning
	time.Sleep(lightTransitionIntervalInSeconds + 1*time.Second)
	return setLightState, nil
}

func (light *Light) updateConfiguration(configuration *Configuration) {
	light.Configuration = configuration
	light.updateSchedule()
	light.updateInterval()
	light.updateTargetLightState()
}

func (light *Light) updateSchedule() {
	newSchedule, err := light.Configuration.lightScheduleForDay(light.ID, time.Now())
	if err != nil {
		log.Printf("💡 Light %v is not associated to any schedule. Ignoring...", light.Name)
		light.Schedule = newSchedule // Assign empty schedule
		light.Scheduled = false
		return
	}
	light.Schedule = newSchedule
	light.Scheduled = true
	log.Printf("💡 Light %s: Activating schedule for %v (sunrise: %v, sunset: %v)", light.Name, light.Schedule.endOfDay.Format("Jan 2 2006"), light.Schedule.sunrise.Time.Format("15:04"), light.Schedule.sunset.Time.Format("15:04"))
	light.updateInterval()
}

func (light *Light) updateInterval() {
	if !light.Scheduled {
		log.Debugf("💡 Light %v is not associated to any schedule. No interval to update...", light.Name)
		return
	}

	newInterval, err := light.Schedule.currentInterval(time.Now())
	if err != nil {
		log.Printf("💡 Light %v has no active interval. Ignoring...", light.Name)
		light.Interval = newInterval // Assign empty interval
		return
	}
	if newInterval != light.Interval {
		light.Interval = newInterval
		log.Printf("💡 Light %s: Activating interval %v - %v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"))
	}
}

func (light *Light) updateTargetLightState() {
	if !light.Scheduled {
		log.Debugf("💡 Light %v is not associated to any schedule. No target light state to update...", light.Name)
		return
	}

	// Calculate the target lightstate from the interval
	newLightState := light.Interval.calculateLightStateInInterval(time.Now())

	// First initialization of the TargetLightState?
	if light.TargetLightState.ColorTemperature == 0 && len(light.TargetLightState.Color) == 0 && light.TargetLightState.Brightness == 0 {
		light.TargetLightState = newLightState
		log.Debugf("💡 Light %s: Initialized target state to %+v", light.Name, light.TargetLightState)
		return
	}

	// If we currently control the light, shift gracefully to prevent sudden changes in color or brightness
	if light.Automatic {
		newLightState = calculateNextLightstate(light.CurrentLightState, newLightState)
	}

	light.TargetLightState = newLightState
	log.Debugf("💡 Light %s: Updated target state to %+v", light.Name, light.TargetLightState)
}

func calculateNextLightstate(currentLightState LightState, targetLightState LightState) LightState {
	nextLightState := LightState{-1, []float32{-1, -1}, -1}
	colorTemperatureStep := 5
	brightnessStep := 1

	if targetLightState.ColorTemperature != -1 {
		if abs(currentLightState.ColorTemperature-targetLightState.ColorTemperature) < colorTemperatureStep {
			nextLightState.ColorTemperature = targetLightState.ColorTemperature
		} else {
			if currentLightState.ColorTemperature < targetLightState.ColorTemperature {
				nextLightState.ColorTemperature = currentLightState.ColorTemperature + colorTemperatureStep
			} else {
				nextLightState.ColorTemperature = currentLightState.ColorTemperature - colorTemperatureStep
			}
		}
		x, y := colorTemperatureToXYColor(nextLightState.ColorTemperature)
		nextLightState.Color = []float32{float32(x), float32(y)}
	}

	if targetLightState.Brightness != -1 {
		if abs(currentLightState.Brightness-targetLightState.Brightness) < brightnessStep {
			nextLightState.Brightness = targetLightState.Brightness
		} else {
			if currentLightState.Brightness < targetLightState.Brightness {
				nextLightState.Brightness = currentLightState.Brightness + brightnessStep
			} else {
				nextLightState.Brightness = currentLightState.Brightness - brightnessStep
			}
		}
	}

	if !nextLightState.isValid() {
		log.Debugf("Validation failed in calculateNextLightstate")
	}
	return nextLightState
}
<|endoftext|>


<commit_before>package limit

import (
	"net/http"
	"time"
)

// Limiter is used to create limiting handlers. All handlers share the created limit
type Limiter struct {
	timeout time.Duration
	max     uint
	channel chan struct{}
}

// LimiterHandler is used to wrap an http.Handler with a request limit
type LimiterHandler struct {
	handler http.Handler
	limiter *Limiter
}

// New creates a new Limit Handler creator.  When max is reached, new requests may
// wait up until timeout. a 503 is returned if the timeout is reachd
func New(timeout time.Duration, max uint) *Limiter {
	l := &Limiter{
		timeout: timeout,
		max:     max,
		channel: make(chan struct{}, max),
	}

	for i := uint(0); i < max; i++ {
		l.channel <- struct{}{}
	}

	return l
}

// Handler wraps an http.Handler with a limiting handler
func (l *Limiter) Handler(h http.Handler) http.Handler {
	return &LimiterHandler{
		handler: h,
		limiter: l,
	}
}

// ServeHTTP will attempt to "reserve" a slot and wait up to timeout to do so.
// Will return a 503 error.
func (h *LimiterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	select {
	case <-h.limiter.channel:
		defer func(l *Limiter) {
			l.channel <- struct{}{}
		}(h.limiter)
		h.handler.ServeHTTP(w, r)
	case <-time.After(h.limiter.timeout):
		http.Error(w, "max concurreny", 503)
	}
}
<commit_msg>use provided vars rather than integer<commit_after>package limit

import (
	"net/http"
	"time"
)

// Limiter is used to create limiting handlers. All handlers share the created limit
type Limiter struct {
	timeout time.Duration
	max     uint
	channel chan struct{}
}

// LimiterHandler is used to wrap an http.Handler with a request limit
type LimiterHandler struct {
	handler http.Handler
	limiter *Limiter
}

// New creates a new Limit Handler creator.  When max is reached, new requests may
// wait up until timeout. a 503 is returned if the timeout is reachd
func New(timeout time.Duration, max uint) *Limiter {
	l := &Limiter{
		timeout: timeout,
		max:     max,
		channel: make(chan struct{}, max),
	}

	for i := uint(0); i < max; i++ {
		l.channel <- struct{}{}
	}

	return l
}

// Handler wraps an http.Handler with a limiting handler
func (l *Limiter) Handler(h http.Handler) http.Handler {
	return &LimiterHandler{
		handler: h,
		limiter: l,
	}
}

// ServeHTTP will attempt to "reserve" a slot and wait up to timeout to do so.
// Will return a 503 error.
func (h *LimiterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	select {
	case <-h.limiter.channel:
		defer func(l *Limiter) {
			l.channel <- struct{}{}
		}(h.limiter)
		h.handler.ServeHTTP(w, r)
	case <-time.After(h.limiter.timeout):
		http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
	}
}
<|endoftext|>


<commit_before>// Package login is a middleware for Martini that provides a simple way to track user sessions
// in on a website. This package is loosely based on Flask-Login.
//
//        package main
//
//        func NewUser() login.User {
//            /* Generate your *MyUserModel with safe values (zero value typically) */
//            return MyUserModel
//        }
//
//        store := sessions.NewCookieStore([]byte("secret123"))
//        m := martini.Classic()
//
//        m.Use(render.Renderer())
//        m.Use(sessions.Sessions("my_session", store))
//        m.Use(SessionUser(NewUser))
//
//        m.Get("/setauth", func(session sessions.Session, user User) string {
//                err := AuthenticateSession(session, user)
//                if err != nil {
//                        t.Error(err)
//                }
//                return "OK"
//        })
//
//        m.Get("/private", LoginRequired, func(session sessions.Session, user User) string {
//                return "OK"
//        })
//
//        m.Get("/logout", LoginRequired, func(session sessions.Session, user User) string {
//                Logout(session, user)
//                return "OK"
//        })
//
// Program Flow:
// Every request to Martini will generate a new User value using the function passed to SessionUser.
// This should default to a zero value user model, and must implement the login.User interface. If
// a user exists in the request session, this user will be injected into every request handler.
// Otherwise the zero value object will be injected.
//
// When a user visits any route with the LoginRequired handler, the login.User object will be
// examined with the IsAuthenticated() function. If the user is not authenticated, they will be
// redirected to a login page (/login).
//
// To log your users in, you should set a POST route, and verify the user/password that was sent
// form the client. Due to the vast possibilities of doing this, you must be responsible for
// validating a user. Once that user is validated, call login.AuthenticateSession() to mark the
// session as authenticated.
//
// Your user type should meet the login.User interface:
//
//    type User interface {
//        // Return whether this user is logged in or not
//        IsAuthenticated() bool
//
//        // Set any flags or extra data that should be available
//	  // for a logged in user
//        Login()
//
//        // Clear any sensitive data out of the user
//        Logout()
//    }
//
// The SessionUser() Martini middleware will inject the login.User interface
// into your route handlers. These interfaces must be converted to your
// appropriate type to function correcty.
//
//    func handler(user login.User, db *MyDB) {
//        u := user.(*UserModel)
//        db.Save(u)
//    }

package login

import (
	"encoding/json"
	"github.com/codegangsta/martini"
	"github.com/codegangsta/martini-contrib/render"
	"github.com/codegangsta/martini-contrib/sessions"
	"log"
	"net/http"
)

type User interface {
	// Return whether this user is logged in or not
	IsAuthenticated() bool

	// Set any flags or extra data that should be available
	Login()

	// Clear any sensitive data out of the user
	Logout()
}

// Try to read a valid user object out of the session. Inject that object, or
// the zero value user object (from newUser) into the context.
func SessionUser(newUser func() User) martini.Handler {
	return func(s sessions.Session, c martini.Context, l *log.Logger) {
		userJson := s.Get("AUTHUSER")
		user := newUser()

		if userJson != nil {
			err := json.Unmarshal(userJson.([]byte), user)
			if err != nil {
				l.Printf("Could not unmarshal user: %v", userJson)
			} else {
				user.Login()
			}
		}

		c.MapTo(user, (*User)(nil))
	}
}

// After you have validated a user, you should call this function with the current
// session and user object. This will mark this session as authenticated and call
// the Login() method of your user object.
func AuthenticateSession(s sessions.Session, user User) error {
	user.Login()
	userJson, err := json.Marshal(user)
	s.Set("AUTHUSER", userJson)
	return err
}

// To clear out the session, call this function. Your user object's Logout()
// function will be called.
func Logout(s sessions.Session, user User) {
	user.Logout()
	s.Delete("AUTHUSER")
}

// Any routes that require a login should have this handler placed in the flow.
func LoginRequired(r render.Render, user User, w http.ResponseWriter) {
	if user.IsAuthenticated() == false {
		w.Header().Set("Location", "/login")
		r.Error(302)
		// XXX implement ?next= pattern
	}
}
<commit_msg>Comments / next / UpdateUser<commit_after>// Package login is a middleware for Martini that provides a simple way to track user sessions
// in on a website. This package is loosely based on Flask-Login.
//
//        package main
//
//        func NewUser() login.User {
//            /* Generate your *MyUserModel with safe values (zero value typically) */
//            return MyUserModel
//        }
//
//        store := sessions.NewCookieStore([]byte("secret123"))
//        m := martini.Classic()
//
//        m.Use(render.Renderer())
//        m.Use(sessions.Sessions("my_session", store))
//        m.Use(SessionUser(NewUser))
//
//        m.Get("/setauth", func(session sessions.Session, user User) string {
//                err := AuthenticateSession(session, user)
//                if err != nil {
//                        t.Error(err)
//                }
//                return "OK"
//        })
//
//        m.Get("/private", LoginRequired, func(session sessions.Session, user User) string {
//                return "OK"
//        })
//
//        m.Get("/logout", LoginRequired, func(session sessions.Session, user User) string {
//                Logout(session, user)
//                return "OK"
//        })
//
// Program Flow:
// Every request to Martini will generate a new User value using the function passed to SessionUser.
// This should default to a zero value user model, and must implement the login.User interface. If
// a user exists in the request session, this user will be injected into every request handler.
// Otherwise the zero value object will be injected.
//
// When a user visits any route with the LoginRequired handler, the login.User object will be
// examined with the IsAuthenticated() function. If the user is not authenticated, they will be
// redirected to a login page (/login).
//
// To log your users in, you should set a POST route, and verify the user/password that was sent
// form the client. Due to the vast possibilities of doing this, you must be responsible for
// validating a user. Once that user is validated, call login.AuthenticateSession() to mark the
// session as authenticated.
//
// Your user type should meet the login.User interface:
//
//    type User interface {
//        // Return whether this user is logged in or not
//        IsAuthenticated() bool
//
//        // Set any flags or extra data that should be available
//	  // for a logged in user
//        Login()
//
//        // Clear any sensitive data out of the user
//        Logout()
//    }
//
// The SessionUser() Martini middleware will inject the login.User interface
// into your route handlers. These interfaces must be converted to your
// appropriate type to function correcty.
//
//    func handler(user login.User, db *MyDB) {
//        u := user.(*UserModel)
//        db.Save(u)
//    }
package login

import (
	"encoding/json"
	"fmt"
	"github.com/codegangsta/martini"
	"github.com/codegangsta/martini-contrib/render"
	"github.com/codegangsta/martini-contrib/sessions"
	"log"
	"net/http"
)

// User defines all the functions necessary to work with the user's authentication.
// The caller should implement these functions for whatever system of authentication
// they choose to use
type User interface {
	// Return whether this user is logged in or not
	IsAuthenticated() bool

	// Set any flags or extra data that should be available
	Login()

	// Clear any sensitive data out of the user
	Logout()
}

// SessionUser will try to read a valid user object out of the session. Then it will
// inject that object, or the zero value user object (from newUser) into the context.
// The newUser() function should provide a valid 0value structure for the caller's
// user type
func SessionUser(newUser func() User) martini.Handler {
	return func(s sessions.Session, c martini.Context, l *log.Logger) {
		userJson := s.Get("AUTHUSER")
		user := newUser()

		if userJson != nil {
			err := json.Unmarshal(userJson.([]byte), user)
			if err != nil {
				l.Printf("Could not unmarshal user: %v", userJson)
			} else {
				user.Login()
			}
		}

		c.MapTo(user, (*User)(nil))
	}
}

// AuthenticatSession will mark the session and user object as authenticated. Then
// the Login() user function will be called. This function should be called after
//you have validated a user.
func AuthenticateSession(s sessions.Session, user User) error {
	user.Login()
	return UpdateUser(s, user)
}

// Logout will clear out the session and call the Logout() user function.
func Logout(s sessions.Session, user User) {
	user.Logout()
	s.Delete("AUTHUSER")
}

// LoginRequired verifies that the current user is authenticated. Any routes that
// require a login should have this handler placed in the flow. If the user is not
// authenticated, they will be redirected to /login with the "next" get parameter
// set to the attempted URL.
func LoginRequired(r render.Render, user User, w http.ResponseWriter, req *http.Request) {
	if user.IsAuthenticated() == false {
		path := fmt.Sprintf("/login?next=%s", req.URL.Path)
		r.Redirect(path, 302)
	}
}

// UpdateUser updates the User object stored in the session. This is useful incase a change
// is made to the user model that needs to persist across requests.
func UpdateUser(s sessions.Session, user User) error {
	userJson, err := json.Marshal(user)
	if err != nil {
		return err
	}

	s.Set("AUTHUSER", userJson)
	return nil
}
<|endoftext|>


<commit_before>/*
 * magic.go
 *
 * Copyright 2013 Krzysztof Wilczynski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package magic

/*
#cgo LDFLAGS: -lmagic
#include "functions.h"
*/
import "C"

import (
	"fmt"
	"math"
	"os"
	"reflect"
	"runtime"
	"sort"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type magic struct {
	sync.Mutex
	flags  int
	path   []string
	cookie C.magic_t
}

func (m *magic) close() {
	if m != nil && m.cookie != nil {
		C.magic_close(m.cookie)
		m.path = []string{}
		m.cookie = nil
	}
	runtime.SetFinalizer(m, nil)
}

type Magic struct {
	*magic
}

func New(files ...string) (*Magic, error) {
	mgc, err := open()
	if err != nil {
		return nil, err
	}

	if _, err := mgc.Load(files...); err != nil {
		return nil, err
	}
	return mgc, nil
}

func (mgc *Magic) Close() {
	mgc.Lock()
	defer mgc.Unlock()
	mgc.magic.close()
}

func (mgc *Magic) String() string {
	return fmt.Sprintf("Magic{flags:%d path:%s cookie:%p}",
		mgc.flags, mgc.path, mgc.cookie)
}

func (mgc *Magic) Path() ([]string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return []string{}, mgc.error()
	}

	if len(mgc.path) > 0 && os.Getenv("MAGIC") == "" {
		return mgc.path, nil
	}
	rv := C.GoString(C.magic_getpath_wrapper())
	mgc.path = strings.Split(rv, ":")
	return mgc.path, nil
}

func (mgc *Magic) Flags() (int, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return -1, mgc.error()
	}
	return mgc.flags, nil
}

func (mgc *Magic) FlagsArray() ([]int, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return []int{}, mgc.error()
	}

	flags := make([]int, 0)

	n := 0
	for i := mgc.flags; i > 0; i = i - n {
		n = int(math.Log2(float64(i)))
		n = int(math.Pow(2, float64(n)))
		flags = append(flags, n)
	}
	sort.Ints(flags)
	return flags, nil
}

func (mgc *Magic) SetFlags(flags int) error {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return mgc.error()
	}

	rv, err := C.magic_setflags_wrapper(mgc.cookie, C.int(flags))
	if rv < 0 && err != nil {
		errno := err.(syscall.Errno)
		switch errno {
		case syscall.EINVAL:
			return &MagicError{int(errno), "unknown or invalid flag specified"}
		case syscall.ENOSYS:
			return &MagicError{int(errno), "flag is not implemented"}
		default:
			return mgc.error()
		}
	}

	mgc.flags = flags
	return nil
}

func (mgc *Magic) Load(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	defer C.free(unsafe.Pointer(cfiles))

	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
	} else {
		cfiles = C.magic_getpath_wrapper()
	}

	if rv := C.magic_load_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	mgc.path = strings.Split(C.GoString(cfiles), ":")
	return true, nil
}

func (mgc *Magic) Compile(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
		defer C.free(unsafe.Pointer(cfiles))
	}

	if rv := C.magic_compile_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	return true, nil
}

func (mgc *Magic) Check(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
		defer C.free(unsafe.Pointer(cfiles))
	}

	if rv := C.magic_check_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	return true, nil
}

func (mgc *Magic) File(filename string) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	cfilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cfilename))

	cstring := C.magic_file(mgc.cookie, cfilename)
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) Buffer(buffer []byte) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	p, length := unsafe.Pointer(&buffer[0]), C.size_t(len(buffer))

	cstring := C.magic_buffer(mgc.cookie, p, length)
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) Descriptor(fd uintptr) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	cstring := C.magic_descriptor(mgc.cookie, C.int(fd))
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) error() *MagicError {
	if mgc.cookie == nil {
		errno := syscall.EFAULT
		return &MagicError{int(errno), "Magic library is not open"}
	}

	cstring := C.magic_error(mgc.cookie)
	if cstring != nil {
		errno := int(C.magic_errno(mgc.cookie))
		return &MagicError{errno, C.GoString(cstring)}
	}
	return &MagicError{-1, "unknown error"}
}

func (mgc *Magic) destroy() {
	mgc.Close()
}

func open() (*Magic, error) {
	rv := C.magic_open(C.int(NONE))
	if rv == nil {
		errno := syscall.EPERM
		return nil, &MagicError{int(errno), "failed to initialize Magic library"}
	}

	mgc := &Magic{&magic{flags: NONE, cookie: rv}}
	runtime.SetFinalizer(mgc.magic, (*magic).close)
	return mgc, nil
}

func Open(f func(magic *Magic) error, files ...string) (err error) {
	var ok bool

	if f == nil || reflect.TypeOf(f).Kind() != reflect.Func {
		return &MagicError{-1, "not a function or nil pointer"}
	}

	mgc, err := New(files...)
	if err != nil {
		return err
	}
	defer mgc.Close()

	defer func() {
		if r := recover(); r != nil {
			err, ok = r.(error)
			if !ok {
				err = &MagicError{-1, fmt.Sprintf("%v", r)}
			}
		}
	}()

	return f(mgc)
}

func Compile(files ...string) (bool, error) {
	mgc, err := open()
	if err != nil {
		return false, err
	}
	defer mgc.Close()

	rv, err := mgc.Compile(files...)
	if err != nil {
		return rv, err
	}
	return rv, nil
}

func Check(files ...string) (bool, error) {
	mgc, err := open()
	if err != nil {
		return false, err
	}
	defer mgc.Close()

	rv, err := mgc.Check(files...)
	if err != nil {
		return rv, err
	}
	return rv, nil
}

func Version() (int, error) {
	rv, err := C.magic_version_wrapper()
	if rv < 0 && err != nil {
		errno := syscall.ENOSYS
		return -1, &MagicError{int(errno), "function is not implemented"}
	}
	return int(rv), nil
}

func VersionString() (string, error) {
	rv, err := Version()
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%d.%02d", rv/100, rv%100), nil
}

func FileMime(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME)
	return mgc.File(filename)
}

func FileEncoding(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_ENCODING)
	return mgc.File(filename)
}

func FileType(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_TYPE)
	return mgc.File(filename)
}

func BufferMime(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME)
	return mgc.Buffer(buffer)
}

func BufferEncoding(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_ENCODING)
	return mgc.Buffer(buffer)
}

func BufferType(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_TYPE)
	return mgc.Buffer(buffer)
}
<commit_msg>Make behaviour consistent among various versions of libmagic adhering to the POSIX standard.<commit_after>/*
 * magic.go
 *
 * Copyright 2013 Krzysztof Wilczynski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package magic

/*
#cgo LDFLAGS: -lmagic
#include "functions.h"
*/
import "C"

import (
	"fmt"
	"math"
	"os"
	"reflect"
	"runtime"
	"sort"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type magic struct {
	sync.Mutex
	flags  int
	path   []string
	cookie C.magic_t
}

func (m *magic) close() {
	if m != nil && m.cookie != nil {
		C.magic_close(m.cookie)
		m.path = []string{}
		m.cookie = nil
	}
	runtime.SetFinalizer(m, nil)
}

type Magic struct {
	*magic
}

func New(files ...string) (*Magic, error) {
	mgc, err := open()
	if err != nil {
		return nil, err
	}

	if _, err := mgc.Load(files...); err != nil {
		return nil, err
	}
	return mgc, nil
}

func (mgc *Magic) Close() {
	mgc.Lock()
	defer mgc.Unlock()
	mgc.magic.close()
}

func (mgc *Magic) String() string {
	return fmt.Sprintf("Magic{flags:%d path:%s cookie:%p}",
		mgc.flags, mgc.path, mgc.cookie)
}

func (mgc *Magic) Path() ([]string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return []string{}, mgc.error()
	}

	if len(mgc.path) > 0 && os.Getenv("MAGIC") == "" {
		return mgc.path, nil
	}
	rv := C.GoString(C.magic_getpath_wrapper())
	mgc.path = strings.Split(rv, ":")
	return mgc.path, nil
}

func (mgc *Magic) Flags() (int, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return -1, mgc.error()
	}
	return mgc.flags, nil
}

func (mgc *Magic) FlagsArray() ([]int, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return []int{}, mgc.error()
	}

	flags := make([]int, 0)

	n := 0
	for i := mgc.flags; i > 0; i = i - n {
		n = int(math.Log2(float64(i)))
		n = int(math.Pow(2, float64(n)))
		flags = append(flags, n)
	}
	sort.Ints(flags)
	return flags, nil
}

func (mgc *Magic) SetFlags(flags int) error {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return mgc.error()
	}

	rv, err := C.magic_setflags_wrapper(mgc.cookie, C.int(flags))
	if rv < 0 && err != nil {
		errno := err.(syscall.Errno)
		switch errno {
		case syscall.EINVAL:
			return &MagicError{int(errno), "unknown or invalid flag specified"}
		case syscall.ENOSYS:
			return &MagicError{int(errno), "flag is not implemented"}
		default:
			return mgc.error()
		}
	}

	mgc.flags = flags
	return nil
}

func (mgc *Magic) Load(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	defer C.free(unsafe.Pointer(cfiles))

	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
	} else {
		cfiles = C.magic_getpath_wrapper()
	}

	if rv := C.magic_load_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	mgc.path = strings.Split(C.GoString(cfiles), ":")
	return true, nil
}

func (mgc *Magic) Compile(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
		defer C.free(unsafe.Pointer(cfiles))
	}

	if rv := C.magic_compile_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	return true, nil
}

func (mgc *Magic) Check(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
		defer C.free(unsafe.Pointer(cfiles))
	}

	if rv := C.magic_check_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	return true, nil
}

func (mgc *Magic) File(filename string) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	cfilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cfilename))

	cstring := C.magic_file(mgc.cookie, cfilename)
	if cstring == nil {
		rv, _ := Version()

		if mgc.flags&ERROR != 0 {
			return "", mgc.error()
		} else if rv < 515 {
			C.magic_errno(mgc.cookie)
			cstring = C.magic_error(mgc.cookie)
		} else {
			panic("should be unreachable")
		}
	}

	if cstring == nil {
		return "", &MagicError{-1, "unknown or empty result"}
	}

	s := C.GoString(cstring)
	if s == "" || s == "(null)" {
		return "", &MagicError{-1, "unknown or invalid result"}
	}
	return s, nil
}

func (mgc *Magic) Buffer(buffer []byte) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	p, length := unsafe.Pointer(&buffer[0]), C.size_t(len(buffer))

	cstring := C.magic_buffer(mgc.cookie, p, length)
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) Descriptor(fd uintptr) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	cstring := C.magic_descriptor(mgc.cookie, C.int(fd))
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) error() *MagicError {
	if mgc.cookie == nil {
		errno := syscall.EFAULT
		return &MagicError{int(errno), "Magic library is not open"}
	}

	cstring := C.magic_error(mgc.cookie)
	if cstring != nil {
		errno := int(C.magic_errno(mgc.cookie))
		return &MagicError{errno, C.GoString(cstring)}
	}
	return &MagicError{-1, "unknown error"}
}

func (mgc *Magic) destroy() {
	mgc.Close()
}

func open() (*Magic, error) {
	rv := C.magic_open(C.int(NONE))
	if rv == nil {
		errno := syscall.EPERM
		return nil, &MagicError{int(errno), "failed to initialize Magic library"}
	}

	mgc := &Magic{&magic{flags: NONE, cookie: rv}}
	runtime.SetFinalizer(mgc.magic, (*magic).close)
	return mgc, nil
}

func Open(f func(magic *Magic) error, files ...string) (err error) {
	var ok bool

	if f == nil || reflect.TypeOf(f).Kind() != reflect.Func {
		return &MagicError{-1, "not a function or nil pointer"}
	}

	mgc, err := New(files...)
	if err != nil {
		return err
	}
	defer mgc.Close()

	defer func() {
		if r := recover(); r != nil {
			err, ok = r.(error)
			if !ok {
				err = &MagicError{-1, fmt.Sprintf("%v", r)}
			}
		}
	}()

	return f(mgc)
}

func Compile(files ...string) (bool, error) {
	mgc, err := open()
	if err != nil {
		return false, err
	}
	defer mgc.Close()

	rv, err := mgc.Compile(files...)
	if err != nil {
		return rv, err
	}
	return rv, nil
}

func Check(files ...string) (bool, error) {
	mgc, err := open()
	if err != nil {
		return false, err
	}
	defer mgc.Close()

	rv, err := mgc.Check(files...)
	if err != nil {
		return rv, err
	}
	return rv, nil
}

func Version() (int, error) {
	rv, err := C.magic_version_wrapper()
	if rv < 0 && err != nil {
		errno := syscall.ENOSYS
		return -1, &MagicError{int(errno), "function is not implemented"}
	}
	return int(rv), nil
}

func VersionString() (string, error) {
	rv, err := Version()
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%d.%02d", rv/100, rv%100), nil
}

func FileMime(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME)
	return mgc.File(filename)
}

func FileEncoding(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_ENCODING)
	return mgc.File(filename)
}

func FileType(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_TYPE)
	return mgc.File(filename)
}

func BufferMime(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME)
	return mgc.Buffer(buffer)
}

func BufferEncoding(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_ENCODING)
	return mgc.Buffer(buffer)
}

func BufferType(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_TYPE)
	return mgc.Buffer(buffer)
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"

	"github.com/containerum/chkit/pkg/cli/namespace"
	"github.com/containerum/chkit/pkg/cli/prerun"
	"github.com/containerum/chkit/pkg/cli/service"
	"github.com/containerum/chkit/pkg/configuration"
	"github.com/containerum/chkit/pkg/context"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

var Delete = &cobra.Command{
	Use: "delete",
	PersistentPreRun: func(command *cobra.Command, args []string) {
		prerun.PreRun()
	},
	Run: func(command *cobra.Command, args []string) {
		command.Help()
	},
	PersistentPostRun: func(command *cobra.Command, args []string) {
		if context.GlobalContext.Changed {
			if err := configuration.SaveConfig(); err != nil {
				logrus.WithError(err).Errorf("unable to save config")
				fmt.Printf("Unable to save config: %v\n", err)
				return
			}
		}
		if err := configuration.SaveTokens(context.GlobalContext.Client.Tokens); err != nil {
			logrus.WithError(err).Errorf("unable to save tokens")
			fmt.Printf("Unable to save tokens: %v\n", err)
			return
		}
	},
}

func init() {
	Delete.AddCommand(
		clinamespace.Delete,
		cliserv.Delete,
	)
}
<commit_msg>add delete deployment and delete pod commands<commit_after>package cli

import (
	"fmt"

	"github.com/containerum/chkit/pkg/cli/deployment"
	"github.com/containerum/chkit/pkg/cli/namespace"
	"github.com/containerum/chkit/pkg/cli/pod"
	"github.com/containerum/chkit/pkg/cli/prerun"
	"github.com/containerum/chkit/pkg/cli/service"
	"github.com/containerum/chkit/pkg/configuration"
	"github.com/containerum/chkit/pkg/context"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
)

var Delete = &cobra.Command{
	Use:   "delete",
	Short: "Delete resource",
	PersistentPreRun: func(command *cobra.Command, args []string) {
		prerun.PreRun()
	},
	Run: func(command *cobra.Command, args []string) {
		command.Help()
	},
	PersistentPostRun: func(command *cobra.Command, args []string) {
		if context.GlobalContext.Changed {
			if err := configuration.SaveConfig(); err != nil {
				logrus.WithError(err).Errorf("unable to save config")
				fmt.Printf("Unable to save config: %v\n", err)
				return
			}
		}
		if err := configuration.SaveTokens(context.GlobalContext.Client.Tokens); err != nil {
			logrus.WithError(err).Errorf("unable to save tokens")
			fmt.Printf("Unable to save tokens: %v\n", err)
			return
		}
	},
}

func init() {
	Delete.AddCommand(
		clinamespace.Delete,
		cliserv.Delete,
		clideployment.Delete(&context.GlobalContext),
		clipod.Delete(&context.GlobalContext),
	)
}
<|endoftext|>