<commit_before>package turbowookie

import (
  "encoding/json"
  "fmt"
  "github.com/gorilla/mux"
  "log"
  "net/http"
  "net/http/httputil"
  "net/url"
  "strconv"
  "time"
)

// TWHandler is our custom http.Handler used to actually do the HTTP stuff.
type TWHandler struct {
  // MpdClient is our MPD Client, used to tell MPD to do things. Important
  // things.
  MpdClient *TWMPDClient

  // ServerConfig is a map of configuration key/values found in
  // a config.yaml file.
  ServerConfig map[string]string

  // Router is a mux.Router, it's what really does all the HTTP stuff, we just
  // act as the interface. And the HandlerFuncs
  Router *mux.Router

  // updater is a channel used by our long poller/polar system. It contains
  // the message of what's been changed.
  updater chan string

  // pollerClients is the number of people currently connected to the long
  // poller.
  pollerClients int
}

// NewTWHandler creates a new TWHandler, using the passed in filename as a
// yaml file containing the server's configuation settings.
func NewTWHandler(filename string, serveDart, startMPD bool, portOverride int) (*TWHandler, error) {
  // make us a pointer to a handler.
  h := &TWHandler{}

  // attempt to read the passed in config file. See `yaml.go` for more info.
  config, err := ReadConfig(filename)
  if err != nil {
    return nil, err
  }

  if !(config["server_port"] != "9000" && portOverride == 9000) {
    config["server_port"] = strconv.Itoa(portOverride)
  }

  h.ServerConfig = config
  h.MpdClient = NewTWMPDClient(h.ServerConfig, startMPD) // see TWMPDClient.go

  // Make sure there's a server to connect to, and run some other startup
  // commands (like making sure there's music playing...).
  err = h.MpdClient.Startup()
  if err != nil {
    log.Fatal("Error running the TWMPDClient startup...\n", err)
  }

  // Actually make our HTTP Router
  h.Router = mux.NewRouter()

  // Let us play the MPD without having to deal with cross origin stuff.
  // Because cross origin is kinda a bitch.
  h.Router.HandleFunc("/stream", httputil.NewSingleHostReverseProxy(
    &url.URL{
      Scheme: "http",
      Host:   h.ServerConfig["mpd_domain"] + ":" + h.ServerConfig["mpd_http_port"],
      Path:   "/",
    }).ServeHTTP)

  h.Router.HandleFunc("/songs", h.listSongs)
  h.Router.HandleFunc("/artists", h.listArtists)
  h.Router.HandleFunc("/albums", h.listArtistAlbums)
  h.Router.HandleFunc("/current", h.getCurrentSong)
  h.Router.HandleFunc("/upcoming", h.getUpcomingSongs)
  h.Router.HandleFunc("/add", h.addSong)
  h.Router.HandleFunc("/polar", h.bear)

  // This needs to be last, otherwise it'll override all routes after it
  // because we're matching EVERYTHING.
  fileDir := h.ServerConfig["turbo_wookie_directory"] + "/frontend/turbo_wookie"
  if serveDart {
    fileDir += "/web"
  } else {
    fileDir += "/build/web"
  }
  h.Router.PathPrefix("/").Handler(http.FileServer(http.Dir(fileDir)))

  // setup our poller/polar stuff.
  h.updater = make(chan string)
  h.pollerClients = 0

  // nothing bad happened. Suprise!
  return h, nil
}

// Make TWHandler an HTTP.Handler. Hackily. Just pass up the underlying
// Router's function.
func (h *TWHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  h.Router.ServeHTTP(w, r)
}

// HandleFunc make TWHandler extendible...
// Same as ServeHTTP, just pass up the Router's function.
func (h *TWHandler) HandleFunc(path string, f func(w http.ResponseWriter, r *http.Request)) *mux.Route {
  return h.Router.HandleFunc(path, f)
}

// ListenAndServe serve up some TurboWookie. And setup an MPD Watcher to see
// when things happen to the stream. Because things sometimes happen to the
// stream.
func (h *TWHandler) ListenAndServe() error {
  // Setup a watcher.
  WatchMPD(h.ServerConfig["mpd_domain"]+":"+h.ServerConfig["mpd_control_port"], h)

  port := ":" + h.ServerConfig["server_port"]
  log.Println("Starting server on " + port)
  return http.ListenAndServe(port, h)
}

/************************
    HANDLER FUNCTIONS
************************/

// List all songs in the library, and information about those songs.
func (h *TWHandler) listSongs(w http.ResponseWriter, r *http.Request) {
  r.ParseForm()
  artist, ok1 := r.Form["artist"]
  album, ok2 := r.Form["album"]

  var songs []map[string]string
  var err error

  if ok1 && ok2 {
    songs, err = h.MpdClient.GetSongs(artist[0], album[0])
  } else if ok1 && !ok2 {
    songs, err = h.MpdClient.GetSongs(artist[0], "")
  } else {
    songs, err = h.MpdClient.GetFiles()
  }

  if err != nil {
    printError(w, "An error occured while processing your request", err)
    return
  }

  fmt.Fprintf(w, jsoniffy(songs))
}

func (h *TWHandler) listArtists(w http.ResponseWriter, r *http.Request) {
  artists, err := h.MpdClient.GetArtists()
  if err != nil {
    printError(w, "An error occured while processing your request", err)
    return
  }

  fmt.Fprintf(w, jsoniffy(artists))
}

func (h *TWHandler) listArtistAlbums(w http.ResponseWriter, r *http.Request) {
  r.ParseForm()
  artist, ok := r.Form["artist"]
  var albums map[string][]string
  var err error
  if !ok {
    albums, err = h.MpdClient.GetAlbums("")
  } else {
    albums, err = h.MpdClient.GetAlbums(artist[0])
  }
  if err != nil {
    printError(w, "An error occured while processing your request", err)
    return
  }

  fmt.Fprintf(w, jsoniffy(albums))
}

// Return information about the currently playing song.
func (h *TWHandler) getCurrentSong(w http.ResponseWriter, r *http.Request) {
  currentSong, err := h.MpdClient.CurrentSong()
  if err != nil {
    printError(w, "Couldn't get current song info", err)
    return
  }

  fmt.Fprintf(w, jsoniffy(currentSong))
}

// Return a list of all upcoming songs in the playlist.
// As in, return `playlist[current song + 1 :]`.
func (h *TWHandler) getUpcomingSongs(w http.ResponseWriter, r *http.Request) {
  upcoming, err := h.MpdClient.GetUpcoming()
  if err != nil {
    printError(w, "Couldn't get upcoming playlist", err)
    return
  }

  fmt.Fprintf(w, jsoniffy(upcoming))
}

// Add a song to the playlist. Using the format
//    `/add?song=[FilePath of song]`
func (h *TWHandler) addSong(w http.ResponseWriter, r *http.Request) {
  // for some reason Go doesn't do this automatically.
  r.ParseForm()

  // Get the song from the GET request variables,
  // and check that there's actually something called `song` in the request.
  rawSongs, ok := r.Form["song"]
  if !ok {
    log.Println(r.Form)
    printError(w, "No song specified", nil)
    return
  }

  song, err := url.QueryUnescape(rawSongs[0])
  if err != nil {
    log.Println("Error unescaping song...\n\tError:", err, "\n\tSong:", song)
  } else {
    song = rawSongs[0]
  }

  log.Println("Adding song", song, " : ", rawSongs[0])

  // Attempt to add the song to the playlist
  err = h.MpdClient.Add(song)
  if err != nil {
    printError(w, "Unknown song", err)
    return
  }

  // Return a simple note saying that we got the song
  m := make(map[string]string)
  m["note"] = "Added song: " + song
  fmt.Fprintf(w, jsoniffy(m))

  // tell long pollers that the playlist changed.
  h.PolarChanged("playlist")
}

// Our long poller. Accessed through `/polar`.
// Clients connect to this, and wait for either five minutes (after which
// they probably reconnect) or until the server tells them something has
// changed.
//
// This is done so that clients don't need to make periodic requests
// asking for updates.
func (h *TWHandler) bear(w http.ResponseWriter, r *http.Request) {
  // we got another live one.
  h.pollerClients += 1

  // Setup a timeout to make sure the client doesn't sit here forever.
  timeout := make(chan bool)
  defer func() { h.pollerClients -= 1 }()
  go func() {
    // if after five minutes nothing has changed, timeout and have the client
    // connect again.
    time.Sleep(5 * time.Minute)
    timeout <- true
  }()

  // Either the updater has news or the timeout expired.
  // Depending on which, tell the client something or nothing changed.
  select {
  case msg := <-h.updater:
    fmt.Fprintf(w, msg)
    if h.pollerClients > 1 {
      h.updater <- msg
    }
  case <-timeout:
    m := make(map[string]string)
    m["changed"] = "nothing"

    fmt.Fprintf(w, jsoniffy(m))
  }
}

/************************
    HELPER FUNCTIONS
************************/

// Print an error the the screen, and send a simple message to the client.
func printError(w http.ResponseWriter, msg string, err error) {
  log.Println("ERROR:", err)
  log.Println("Sending to client:", msg)
  fmt.Fprintf(w, msg+"\n")
}

// Turn things into JSON.
func jsoniffy(v interface{}) string {
  obj, err := json.MarshalIndent(v, "", "  ")
  if err != nil {
    log.Print("Couldn't turn something into JSON: ", v)
    log.Fatal(err)
  }

  return string(obj)
}

// PolarChanged tell clients connected to our long-poll system that something
// (element) has changed.
func (h *TWHandler) PolarChanged(element string) {
  if h.pollerClients < 1 {
    return
  }

  m2 := make(map[string]string)
  m2["changed"] = element
  h.updater <- jsoniffy(m2)
}
<commit_msg>Undid the new pathing for the unstable Dart stuff...<commit_after>package turbowookie

import (
  "encoding/json"
  "fmt"
  "github.com/gorilla/mux"
  "log"
  "net/http"
  "net/http/httputil"
  "net/url"
  "strconv"
  "time"
)

// TWHandler is our custom http.Handler used to actually do the HTTP stuff.
type TWHandler struct {
  // MpdClient is our MPD Client, used to tell MPD to do things. Important
  // things.
  MpdClient *TWMPDClient

  // ServerConfig is a map of configuration key/values found in
  // a config.yaml file.
  ServerConfig map[string]string

  // Router is a mux.Router, it's what really does all the HTTP stuff, we just
  // act as the interface. And the HandlerFuncs
  Router *mux.Router

  // updater is a channel used by our long poller/polar system. It contains
  // the message of what's been changed.
  updater chan string

  // pollerClients is the number of people currently connected to the long
  // poller.
  pollerClients int
}

// NewTWHandler creates a new TWHandler, using the passed in filename as a
// yaml file containing the server's configuation settings.
func NewTWHandler(filename string, serveDart, startMPD bool, portOverride int) (*TWHandler, error) {
  // make us a pointer to a handler.
  h := &TWHandler{}

  // attempt to read the passed in config file. See `yaml.go` for more info.
  config, err := ReadConfig(filename)
  if err != nil {
    return nil, err
  }

  if !(config["server_port"] != "9000" && portOverride == 9000) {
    config["server_port"] = strconv.Itoa(portOverride)
  }

  h.ServerConfig = config
  h.MpdClient = NewTWMPDClient(h.ServerConfig, startMPD) // see TWMPDClient.go

  // Make sure there's a server to connect to, and run some other startup
  // commands (like making sure there's music playing...).
  err = h.MpdClient.Startup()
  if err != nil {
    log.Fatal("Error running the TWMPDClient startup...\n", err)
  }

  // Actually make our HTTP Router
  h.Router = mux.NewRouter()

  // Let us play the MPD without having to deal with cross origin stuff.
  // Because cross origin is kinda a bitch.
  h.Router.HandleFunc("/stream", httputil.NewSingleHostReverseProxy(
    &url.URL{
      Scheme: "http",
      Host:   h.ServerConfig["mpd_domain"] + ":" + h.ServerConfig["mpd_http_port"],
      Path:   "/",
    }).ServeHTTP)

  h.Router.HandleFunc("/songs", h.listSongs)
  h.Router.HandleFunc("/artists", h.listArtists)
  h.Router.HandleFunc("/albums", h.listArtistAlbums)
  h.Router.HandleFunc("/current", h.getCurrentSong)
  h.Router.HandleFunc("/upcoming", h.getUpcomingSongs)
  h.Router.HandleFunc("/add", h.addSong)
  h.Router.HandleFunc("/polar", h.bear)

  // This needs to be last, otherwise it'll override all routes after it
  // because we're matching EVERYTHING.
  fileDir := h.ServerConfig["turbo_wookie_directory"] + "/frontend/turbo_wookie"
  if serveDart {
    fileDir += "/web"
  } else {
    fileDir += "/build"
  }
  h.Router.PathPrefix("/").Handler(http.FileServer(http.Dir(fileDir)))

  // setup our poller/polar stuff.
  h.updater = make(chan string)
  h.pollerClients = 0

  // nothing bad happened. Suprise!
  return h, nil
}

// Make TWHandler an HTTP.Handler. Hackily. Just pass up the underlying
// Router's function.
func (h *TWHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  h.Router.ServeHTTP(w, r)
}

// HandleFunc make TWHandler extendible...
// Same as ServeHTTP, just pass up the Router's function.
func (h *TWHandler) HandleFunc(path string, f func(w http.ResponseWriter, r *http.Request)) *mux.Route {
  return h.Router.HandleFunc(path, f)
}

// ListenAndServe serve up some TurboWookie. And setup an MPD Watcher to see
// when things happen to the stream. Because things sometimes happen to the
// stream.
func (h *TWHandler) ListenAndServe() error {
  // Setup a watcher.
  WatchMPD(h.ServerConfig["mpd_domain"]+":"+h.ServerConfig["mpd_control_port"], h)

  port := ":" + h.ServerConfig["server_port"]
  log.Println("Starting server on " + port)
  return http.ListenAndServe(port, h)
}

/************************
    HANDLER FUNCTIONS
************************/

// List all songs in the library, and information about those songs.
func (h *TWHandler) listSongs(w http.ResponseWriter, r *http.Request) {
  r.ParseForm()
  artist, ok1 := r.Form["artist"]
  album, ok2 := r.Form["album"]

  var songs []map[string]string
  var err error

  if ok1 && ok2 {
    songs, err = h.MpdClient.GetSongs(artist[0], album[0])
  } else if ok1 && !ok2 {
    songs, err = h.MpdClient.GetSongs(artist[0], "")
  } else {
    songs, err = h.MpdClient.GetFiles()
  }

  if err != nil {
    printError(w, "An error occured while processing your request", err)
    return
  }

  fmt.Fprintf(w, jsoniffy(songs))
}

func (h *TWHandler) listArtists(w http.ResponseWriter, r *http.Request) {
  artists, err := h.MpdClient.GetArtists()
  if err != nil {
    printError(w, "An error occured while processing your request", err)
    return
  }

  fmt.Fprintf(w, jsoniffy(artists))
}

func (h *TWHandler) listArtistAlbums(w http.ResponseWriter, r *http.Request) {
  r.ParseForm()
  artist, ok := r.Form["artist"]
  var albums map[string][]string
  var err error
  if !ok {
    albums, err = h.MpdClient.GetAlbums("")
  } else {
    albums, err = h.MpdClient.GetAlbums(artist[0])
  }
  if err != nil {
    printError(w, "An error occured while processing your request", err)
    return
  }

  fmt.Fprintf(w, jsoniffy(albums))
}

// Return information about the currently playing song.
func (h *TWHandler) getCurrentSong(w http.ResponseWriter, r *http.Request) {
  currentSong, err := h.MpdClient.CurrentSong()
  if err != nil {
    printError(w, "Couldn't get current song info", err)
    return
  }

  fmt.Fprintf(w, jsoniffy(currentSong))
}

// Return a list of all upcoming songs in the playlist.
// As in, return `playlist[current song + 1 :]`.
func (h *TWHandler) getUpcomingSongs(w http.ResponseWriter, r *http.Request) {
  upcoming, err := h.MpdClient.GetUpcoming()
  if err != nil {
    printError(w, "Couldn't get upcoming playlist", err)
    return
  }

  fmt.Fprintf(w, jsoniffy(upcoming))
}

// Add a song to the playlist. Using the format
//    `/add?song=[FilePath of song]`
func (h *TWHandler) addSong(w http.ResponseWriter, r *http.Request) {
  // for some reason Go doesn't do this automatically.
  r.ParseForm()

  // Get the song from the GET request variables,
  // and check that there's actually something called `song` in the request.
  rawSongs, ok := r.Form["song"]
  if !ok {
    log.Println(r.Form)
    printError(w, "No song specified", nil)
    return
  }

  song, err := url.QueryUnescape(rawSongs[0])
  if err != nil {
    log.Println("Error unescaping song...\n\tError:", err, "\n\tSong:", song)
  } else {
    song = rawSongs[0]
  }

  log.Println("Adding song", song, " : ", rawSongs[0])

  // Attempt to add the song to the playlist
  err = h.MpdClient.Add(song)
  if err != nil {
    printError(w, "Unknown song", err)
    return
  }

  // Return a simple note saying that we got the song
  m := make(map[string]string)
  m["note"] = "Added song: " + song
  fmt.Fprintf(w, jsoniffy(m))

  // tell long pollers that the playlist changed.
  h.PolarChanged("playlist")
}

// Our long poller. Accessed through `/polar`.
// Clients connect to this, and wait for either five minutes (after which
// they probably reconnect) or until the server tells them something has
// changed.
//
// This is done so that clients don't need to make periodic requests
// asking for updates.
func (h *TWHandler) bear(w http.ResponseWriter, r *http.Request) {
  // we got another live one.
  h.pollerClients += 1

  // Setup a timeout to make sure the client doesn't sit here forever.
  timeout := make(chan bool)
  defer func() { h.pollerClients -= 1 }()
  go func() {
    // if after five minutes nothing has changed, timeout and have the client
    // connect again.
    time.Sleep(5 * time.Minute)
    timeout <- true
  }()

  // Either the updater has news or the timeout expired.
  // Depending on which, tell the client something or nothing changed.
  select {
  case msg := <-h.updater:
    fmt.Fprintf(w, msg)
    if h.pollerClients > 1 {
      h.updater <- msg
    }
  case <-timeout:
    m := make(map[string]string)
    m["changed"] = "nothing"

    fmt.Fprintf(w, jsoniffy(m))
  }
}

/************************
    HELPER FUNCTIONS
************************/

// Print an error the the screen, and send a simple message to the client.
func printError(w http.ResponseWriter, msg string, err error) {
  log.Println("ERROR:", err)
  log.Println("Sending to client:", msg)
  fmt.Fprintf(w, msg+"\n")
}

// Turn things into JSON.
func jsoniffy(v interface{}) string {
  obj, err := json.MarshalIndent(v, "", "  ")
  if err != nil {
    log.Print("Couldn't turn something into JSON: ", v)
    log.Fatal(err)
  }

  return string(obj)
}

// PolarChanged tell clients connected to our long-poll system that something
// (element) has changed.
func (h *TWHandler) PolarChanged(element string) {
  if h.pollerClients < 1 {
    return
  }

  m2 := make(map[string]string)
  m2["changed"] = element
  h.updater <- jsoniffy(m2)
}
<|endoftext|>


<commit_before>/**
 * 数据存储定义
 * @author dzh
 * @date 2015-08-05
 */
package dict

import (
	"errors"
	"github.com/dzhcool/eye/utils"
	"sync"
	"time"
)

var (
	cache = make(map[string]*CacheTable)
	mutex sync.RWMutex
)
var ErrNil = errors.New("nil or type error")

type CacheTable struct {
	sync.RWMutex
	name    string
	items   map[interface{}]*CacheItem
	addTime time.Time
}

func Cache(table string) *CacheTable {
	mutex.RLock()
	t, ok := cache[table]
	mutex.RUnlock()

	if !ok {
		addtime := time.Now()
		t = &CacheTable{
			name:    table,
			items:   make(map[interface{}]*CacheItem),
			addTime: addtime,
		}
		mutex.Lock()
		cache[table] = t
		mutex.Unlock()
	}
	return t
}

func (p *CacheTable) Count(k interface{}, v interface{}) int {
	num := len(p.items)
	return num
}

func (p *CacheTable) Set(k interface{}, v interface{}, l ...int) *CacheItem {
	p.RLock()
	defer p.RUnlock()

	lifetime := 0 * time.Second
	if len(l) > 0 {
		lifetime = time.Duration(l[0]) * time.Second
	}
	item := createCacheItem(k, v, lifetime)
	p.items[k] = &item
	return &item
}

func (p *CacheTable) Add(k interface{}, v interface{}, l ...int) *CacheItem {
	return p.Set(k, v, l...)
}

func (p *CacheTable) Get(k interface{}) (interface{}, error) {
	r.Lock()
	r, ok := p.items[k]
	r.Unlock()
	if !ok {
		return nil, ErrNil
	}
	if r.Expired() {
		delete(p.items, k)
		return nil, ErrNil
	}

	return r.data, nil
}

func (p *CacheTable) Item(k interface{}) *CacheItem {
	r, ok := p.items[k]
	r.Lock()
	defer r.Unlock()

	if !ok {
		r = nil
	}
	return r
}

func (p *CacheTable) Items() map[interface{}]*CacheItem {
	p.Lock()
	defer p.Unlock()

	return p.items
}

func (p *CacheTable) Exists(k interface{}) bool {
	p.RLock()
	defer p.RUnlock()

	_, ok := p.items[k]
	return ok
}

func (p *CacheTable) Delete(k interface{}) (*CacheItem, error) {
	r, ok := p.items[k]
	p.RLock()
	defer p.RUnlock()

	if ok {
		delete(p.items, k)
		return r, nil
	}
	return nil, ErrNil
}

func (p *CacheTable) Int(reply interface{}, err error) (int, error) {
	return utils.Int(reply, err)
}

func (p *CacheTable) Int64(reply interface{}, err error) (int64, error) {
	return utils.Int64(reply, err)
}

func (p *CacheTable) Float64(reply interface{}, err error) (float64, error) {
	return utils.Float64(reply, err)
}

func (p *CacheTable) String(reply interface{}, err error) (string, error) {
	return utils.String(reply, err)
}
<commit_msg>fix dict<commit_after>/**
 * 数据存储定义
 * @author dzh
 * @date 2015-08-05
 */
package dict

import (
	"errors"
	"github.com/dzhcool/eye/utils"
	"sync"
	"time"
)

var (
	cache = make(map[string]*CacheTable)
	mutex sync.RWMutex
)
var ErrNil = errors.New("nil or type error")

type CacheTable struct {
	sync.RWMutex
	name    string
	items   map[interface{}]*CacheItem
	addTime time.Time
}

func Cache(table string) *CacheTable {
	mutex.RLock()
	t, ok := cache[table]
	mutex.RUnlock()

	if !ok {
		addtime := time.Now()
		t = &CacheTable{
			name:    table,
			items:   make(map[interface{}]*CacheItem),
			addTime: addtime,
		}
		mutex.Lock()
		cache[table] = t
		mutex.Unlock()
	}
	return t
}

func (p *CacheTable) Count(k interface{}, v interface{}) int {
	num := len(p.items)
	return num
}

func (p *CacheTable) Set(k interface{}, v interface{}, l ...int) *CacheItem {
	p.RLock()
	defer p.RUnlock()

	lifetime := 0 * time.Second
	if len(l) > 0 {
		lifetime = time.Duration(l[0]) * time.Second
	}
	item := createCacheItem(k, v, lifetime)
	p.items[k] = &item
	return &item
}

func (p *CacheTable) Add(k interface{}, v interface{}, l ...int) *CacheItem {
	return p.Set(k, v, l...)
}

func (p *CacheTable) Get(k interface{}) (interface{}, error) {
	p.RLock()
	r, ok := p.items[k]
	if !ok {
		p.RUnlock()
		return nil, ErrNil
	}
	p.RUnlock()

	if r.Expired() {
		delete(p.items, k)
		return nil, ErrNil
	}

	return r.data, nil
}

func (p *CacheTable) Item(k interface{}) *CacheItem {
	r, ok := p.items[k]
	r.Lock()
	defer r.Unlock()

	if !ok {
		r = nil
	}
	return r
}

func (p *CacheTable) Items() map[interface{}]*CacheItem {
	p.Lock()
	defer p.Unlock()

	return p.items
}

func (p *CacheTable) Exists(k interface{}) bool {
	p.RLock()
	defer p.RUnlock()

	_, ok := p.items[k]
	return ok
}

func (p *CacheTable) Delete(k interface{}) (*CacheItem, error) {
	r, ok := p.items[k]
	p.RLock()
	defer p.RUnlock()

	if ok {
		delete(p.items, k)
		return r, nil
	}
	return nil, ErrNil
}

func (p *CacheTable) Int(reply interface{}, err error) (int, error) {
	return utils.Int(reply, err)
}

func (p *CacheTable) Int64(reply interface{}, err error) (int64, error) {
	return utils.Int64(reply, err)
}

func (p *CacheTable) Float64(reply interface{}, err error) (float64, error) {
	return utils.Float64(reply, err)
}

func (p *CacheTable) String(reply interface{}, err error) (string, error) {
	return utils.String(reply, err)
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Che Wei, Lin
//
// 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 tinynet

import (
	"io"
	"io/ioutil"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"golang.org/x/net/context"
)

func initDocker(imageName string) (string, string) {
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		panic(err)
	}

	// docker pull busybox
	readCloser, err := cli.ImagePull(ctx, imageName, types.ImagePullOptions{})
	if err != nil {
		panic(err)
	} else {
		// because readCloser need to be handle so that image can be download.
		// we don't need output so send this to /dev/null
		io.Copy(ioutil.Discard, readCloser)
	}

	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image: imageName,
		Cmd:   []string{"sleep", "3600"},
	}, nil, nil, "")
	if err != nil {
		panic(err)
	}

	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		panic(err)
	}

	var cInfo types.ContainerJSON
	cInfo, err = cli.ContainerInspect(ctx, resp.ID)
	if err != nil {
		panic(err)
	}
	return resp.ID, cInfo.NetworkSettings.SandboxKey
}
<commit_msg>return container name, sandbox key<commit_after>// Copyright (c) 2017 Che Wei, Lin
//
// 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 tinynet

import (
	"io"
	"io/ioutil"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"golang.org/x/net/context"
)

func ensureDocker(imageRef string) (containerID string, sandboxKey string, err error) {
	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		return "", "", err
	}

	// docker pull busybox
	readCloser, err := cli.ImagePull(ctx, imageRef, types.ImagePullOptions{})
	if err != nil {
		return "", "", err
	} else {
		// because readCloser need to be handle so that image can be download.
		// we don't need output so send this to /dev/null
		io.Copy(ioutil.Discard, readCloser)
	}

	// docker run --net=none -d busybox sleep 3600
	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image:           imageRef,
		Cmd:             []string{"sleep", "3600"},
		NetworkDisabled: true,
	}, nil, nil, "")
	if err != nil {
		return "", "", err
	}

	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		return "", "", err
	}

	var cInfo types.ContainerJSON
	// docker inspect bb | grep -E 'SandboxKey|Id'
	cInfo, err = cli.ContainerInspect(ctx, resp.ID)
	if err != nil {
		return "", "", err
	}
	return resp.ID, cInfo.NetworkSettings.SandboxKey, err
}
<|endoftext|>


<commit_before>package adapter

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/gliderlabs/logspout/router"
)

const (
	logglyAddr          = "https://logs-01.loggly.com"
	logglyEventEndpoint = "/bulk"
	logglyTagsHeader    = "X-LOGGLY-TAG"
)

// Adapter satisfies the router.LogAdapter interface by providing Stream which
// passes all messages to loggly.
type Adapter struct {
	token      string
	client     *http.Client
	tags       string
	log        *log.Logger
	queue      chan logglyMessage
	m          sync.Mutex
	bufferSize int
}

// New returns an Adapter that receives messages from logspout. Additionally,
// it launches a goroutine to buffer and flush messages to loggly.
func New(logglyToken, tags string, bufferSize int) *Adapter {

	adapter := &Adapter{
		client:     http.DefaultClient,
		bufferSize: bufferSize,
		log:        log.New(os.Stdout, "logspout-loggly", log.LstdFlags),
		queue:      make(chan logglyMessage),
		token:      logglyToken,
		tags:       tags,
	}

	go adapter.readQueue()

	return adapter
}

// Stream satisfies the router.LogAdapter interface and passes all messages to
// Loggly
func (l *Adapter) Stream(logstream chan *router.Message) {
	for m := range logstream {
		l.queue <- logglyMessage{
			Message:           m.Data,
			ContainerName:     m.Container.Name,
			ContainerID:       m.Container.ID,
			ContainerImage:    m.Container.Config.Image,
			ContainerHostname: m.Container.Config.Hostname,
		}
	}
}

func (l *Adapter) readQueue() {
	buffer := l.newBuffer()

	periodicFlush := time.NewTicker(time.Second * 10)

	for {
		select {
		case msg := <-l.queue:
			if len(buffer) == cap(buffer) {
				l.flushBuffer(buffer)
				buffer = l.newBuffer()
			}

			buffer = append(buffer, msg)

		case <-periodicFlush.C:
			if len(buffer) > 0 {
				l.flushBuffer(buffer)
				buffer = l.newBuffer()
			}
		}
	}
}

func (l *Adapter) newBuffer() []logglyMessage {
	return make([]logglyMessage, 0, l.bufferSize)
}

func (l *Adapter) flushBuffer(buffer []logglyMessage) {
	var data bytes.Buffer

	for _, msg := range buffer {
		j, _ := json.Marshal(msg)
		data.Write(j)
		data.WriteString("\n")
	}

	req, _ := http.NewRequest(
		"POST",
		fmt.Sprintf("%s%s/%s", logglyAddr, logglyEventEndpoint, l.token),
		&data,
	)

	go l.sendRequestToLoggly(req)
}

func (l *Adapter) sendRequestToLoggly(req *http.Request) {
	l.m.Lock()
	defer l.m.Unlock()
	resp, err := l.client.Do(req)
	defer resp.Body.Close()

	if err != nil {
		l.log.Println(
			fmt.Errorf(
				"error from client: %s",
				err.Error(),
			),
		)
		return
	}

	if resp.StatusCode != http.StatusOK {
		l.log.Println(
			fmt.Errorf(
				"received a non 200 status code when sending message to loggly: %s",
				err.Error(),
			),
		)
	}
}
<commit_msg>use time.Timer instead of time.Ticker <commit_after>package adapter

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/gliderlabs/logspout/router"
)

const (
	logglyAddr          = "https://logs-01.loggly.com"
	logglyEventEndpoint = "/bulk"
	logglyTagsHeader    = "X-LOGGLY-TAG"
	flushTimeout        = 10 * time.Second
)

// Adapter satisfies the router.LogAdapter interface by providing Stream which
// passes all messages to loggly.
type Adapter struct {
	token      string
	client     *http.Client
	tags       string
	log        *log.Logger
	queue      chan logglyMessage
	m          sync.Mutex
	bufferSize int
}

// New returns an Adapter that receives messages from logspout. Additionally,
// it launches a goroutine to buffer and flush messages to loggly.
func New(logglyToken, tags string, bufferSize int) *Adapter {

	adapter := &Adapter{
		client:     http.DefaultClient,
		bufferSize: bufferSize,
		log:        log.New(os.Stdout, "logspout-loggly", log.LstdFlags),
		queue:      make(chan logglyMessage),
		token:      logglyToken,
		tags:       tags,
	}

	go adapter.readQueue()

	return adapter
}

// Stream satisfies the router.LogAdapter interface and passes all messages to
// Loggly
func (l *Adapter) Stream(logstream chan *router.Message) {
	for m := range logstream {
		l.queue <- logglyMessage{
			Message:           m.Data,
			ContainerName:     m.Container.Name,
			ContainerID:       m.Container.ID,
			ContainerImage:    m.Container.Config.Image,
			ContainerHostname: m.Container.Config.Hostname,
		}
	}
}

func (l *Adapter) readQueue() {
	buffer := l.newBuffer()

	timeout := time.NewTimer(flushTimeout)

	for {
		select {
		case msg := <-l.queue:
			if len(buffer) == cap(buffer) {
				timeout.Stop()
				l.flushBuffer(buffer)
				buffer = l.newBuffer()
				timeout.Reset(flushTimeout)
			}

			buffer = append(buffer, msg)

		case <-timeout.C:
			if len(buffer) > 0 {
				l.flushBuffer(buffer)
				buffer = l.newBuffer()
			}
		}
	}
}

func (l *Adapter) newBuffer() []logglyMessage {
	return make([]logglyMessage, 0, l.bufferSize)
}

func (l *Adapter) flushBuffer(buffer []logglyMessage) {
	var data bytes.Buffer

	for _, msg := range buffer {
		j, _ := json.Marshal(msg)
		data.Write(j)
		data.WriteString("\n")
	}

	req, _ := http.NewRequest(
		"POST",
		fmt.Sprintf("%s%s/%s", logglyAddr, logglyEventEndpoint, l.token),
		&data,
	)

	go l.sendRequestToLoggly(req)
}

func (l *Adapter) sendRequestToLoggly(req *http.Request) {
	l.m.Lock()
	defer l.m.Unlock()
	resp, err := l.client.Do(req)
	defer resp.Body.Close()

	if err != nil {
		l.log.Println(
			fmt.Errorf(
				"error from client: %s",
				err.Error(),
			),
		)
		return
	}

	if resp.StatusCode != http.StatusOK {
		l.log.Println(
			fmt.Errorf(
				"received a non 200 status code when sending message to loggly: %s",
				err.Error(),
			),
		)
	}
}
<|endoftext|>


<commit_before>package libvirt

/*
#cgo LDFLAGS: -lvirt -ldl
#include <libvirt/libvirt.h>
#include <libvirt/virterror.h>
#include <stdlib.h>
*/
import "C"

import (
	"errors"
	"unsafe"
)

type VirDomain struct {
	ptr C.virDomainPtr
}

type VirDomainInfo struct {
	ptr C.virDomainInfo
}

func (d *VirDomain) Create() error {
	result := C.virDomainCreate(d.ptr)
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) Destroy() error {
	result := C.virDomainDestroy(d.ptr)
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) Shutdown() error {
	result := C.virDomainShutdown(d.ptr)
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) Reboot(flags uint) error {
	result := C.virDomainReboot(d.ptr, C.uint(flags))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) IsActive() (bool, error) {
	result := C.virDomainIsActive(d.ptr)
	if result == -1 {
		return false, errors.New(GetLastError())
	}
	if result == 1 {
		return true, nil
	}
	return false, nil
}

func (d *VirDomain) SetAutostart(autostart bool) error {
	var cAutostart C.int
	switch autostart {
	case true:
		cAutostart = 1
	default:
		cAutostart = 0
	}
	result := C.virDomainSetAutostart(d.ptr, cAutostart)
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) GetAutostart() (bool, error) {
	var out C.int
	result := C.virDomainGetAutostart(d.ptr, (*C.int)(unsafe.Pointer(&out)))
	if result == -1 {
		return false, errors.New(GetLastError())
	}
	switch out {
	case 1:
		return true, nil
	default:
		return false, nil
	}
}

func (d *VirDomain) GetName() (string, error) {
	name := C.virDomainGetName(d.ptr)
	if name == nil {
		return "", errors.New(GetLastError())
	}
	return C.GoString(name), nil
}

func (d *VirDomain) GetState() ([]int, error) {
	var cState C.int
	var cReason C.int
	result := C.virDomainGetState(d.ptr,
		(*C.int)(unsafe.Pointer(&cState)),
		(*C.int)(unsafe.Pointer(&cReason)),
		0)
	if int(result) == -1 {
		return []int{}, errors.New(GetLastError())
	}
	return []int{int(cState), int(cReason)}, nil
}

func (d *VirDomain) GetUUID() ([]byte, error) {
	var cUuid [C.VIR_UUID_BUFLEN](byte)
	cuidPtr := unsafe.Pointer(&cUuid)
	result := C.virDomainGetUUID(d.ptr, (*C.uchar)(cuidPtr))
	if result != 0 {
		return []byte{}, errors.New(GetLastError())
	}
	return C.GoBytes(cuidPtr, C.VIR_UUID_BUFLEN), nil
}

func (d *VirDomain) GetUUIDString() (string, error) {
	var cUuid [C.VIR_UUID_STRING_BUFLEN](C.char)
	cuidPtr := unsafe.Pointer(&cUuid)
	result := C.virDomainGetUUIDString(d.ptr, (*C.char)(cuidPtr))
	if result != 0 {
		return "", errors.New(GetLastError())
	}
	return C.GoString((*C.char)(cuidPtr)), nil
}

func (d *VirDomain) GetInfo() (VirDomainInfo, error) {
	di := VirDomainInfo{}
	var ptr C.virDomainInfo
	result := C.virDomainGetInfo(d.ptr, (*C.virDomainInfo)(unsafe.Pointer(&ptr)))
	if result == -1 {
		return di, errors.New(GetLastError())
	}
	di.ptr = ptr
	return di, nil
}

func (d *VirDomain) GetXMLDesc(flags uint32) (string, error) {
	result := C.virDomainGetXMLDesc(d.ptr, C.uint(flags))
	if result == nil {
		return "", errors.New(GetLastError())
	}
	xml := C.GoString(result)
	C.free(unsafe.Pointer(result))
	return xml, nil
}

func (i *VirDomainInfo) GetState() uint8 {
	return uint8(i.ptr.state)
}

func (i *VirDomainInfo) GetMaxMem() uint64 {
	return uint64(i.ptr.maxMem)
}

func (i *VirDomainInfo) GetMemory() uint64 {
	return uint64(i.ptr.memory)
}

func (i *VirDomainInfo) GetNrVirtCpu() uint16 {
	return uint16(i.ptr.nrVirtCpu)
}

func (i *VirDomainInfo) GetCpuTime() uint64 {
	return uint64(i.ptr.cpuTime)
}

func (d *VirDomain) GetMetadata(tipus int, uri string, flags uint32) (string, error) {

	var cUri *C.char
	if uri != "" {
		cUri = C.CString(uri)
		defer C.free(unsafe.Pointer(cUri))
	}

	result := C.virDomainGetMetadata(d.ptr, C.int(tipus), cUri, C.uint(flags))
	if result == nil {
		return "", errors.New(GetLastError())

	}
	defer C.free(unsafe.Pointer(result))
	return C.GoString(result), nil
}

func (d *VirDomain) SetMetadata(metaDataType int, metaDataCont, uriKey, uri string, flags uint32) error {
	var cMetaDataCont *C.char
	var cUriKey *C.char
	var cUri *C.char

	cMetaDataCont = C.CString(metaDataCont)
	defer C.free(unsafe.Pointer(cMetaDataCont))

	if metaDataType == VIR_DOMAIN_METADATA_ELEMENT {
		cUriKey = C.CString(uriKey)
		defer C.free(unsafe.Pointer(cUriKey))
		cUri = C.CString(uri)
		defer C.free(unsafe.Pointer(cUri))
	}
	result := C.virDomainSetMetadata(d.ptr, C.int(metaDataType), cMetaDataCont, cUriKey, cUri, C.uint(flags))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) Undefine() error {
	result := C.virDomainUndefine(d.ptr)
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) SetMemory(memory uint64) error {
	result := C.virDomainSetMemory(d.ptr, C.ulong(memory))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) SetMemoryFlags(memory uint64, flags uint32) error {
	result := C.virDomainSetMemoryFlags(d.ptr, C.ulong(memory), C.uint(flags))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) SetVcpus(vcpu uint) error {
	result := C.virDomainSetVcpus(d.ptr, C.uint(vcpu))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}
<commit_msg>Add VirDomain.SetVcpusFlags<commit_after>package libvirt

/*
#cgo LDFLAGS: -lvirt -ldl
#include <libvirt/libvirt.h>
#include <libvirt/virterror.h>
#include <stdlib.h>
*/
import "C"

import (
	"errors"
	"unsafe"
)

type VirDomain struct {
	ptr C.virDomainPtr
}

type VirDomainInfo struct {
	ptr C.virDomainInfo
}

func (d *VirDomain) Create() error {
	result := C.virDomainCreate(d.ptr)
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) Destroy() error {
	result := C.virDomainDestroy(d.ptr)
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) Shutdown() error {
	result := C.virDomainShutdown(d.ptr)
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) Reboot(flags uint) error {
	result := C.virDomainReboot(d.ptr, C.uint(flags))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) IsActive() (bool, error) {
	result := C.virDomainIsActive(d.ptr)
	if result == -1 {
		return false, errors.New(GetLastError())
	}
	if result == 1 {
		return true, nil
	}
	return false, nil
}

func (d *VirDomain) SetAutostart(autostart bool) error {
	var cAutostart C.int
	switch autostart {
	case true:
		cAutostart = 1
	default:
		cAutostart = 0
	}
	result := C.virDomainSetAutostart(d.ptr, cAutostart)
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) GetAutostart() (bool, error) {
	var out C.int
	result := C.virDomainGetAutostart(d.ptr, (*C.int)(unsafe.Pointer(&out)))
	if result == -1 {
		return false, errors.New(GetLastError())
	}
	switch out {
	case 1:
		return true, nil
	default:
		return false, nil
	}
}

func (d *VirDomain) GetName() (string, error) {
	name := C.virDomainGetName(d.ptr)
	if name == nil {
		return "", errors.New(GetLastError())
	}
	return C.GoString(name), nil
}

func (d *VirDomain) GetState() ([]int, error) {
	var cState C.int
	var cReason C.int
	result := C.virDomainGetState(d.ptr,
		(*C.int)(unsafe.Pointer(&cState)),
		(*C.int)(unsafe.Pointer(&cReason)),
		0)
	if int(result) == -1 {
		return []int{}, errors.New(GetLastError())
	}
	return []int{int(cState), int(cReason)}, nil
}

func (d *VirDomain) GetUUID() ([]byte, error) {
	var cUuid [C.VIR_UUID_BUFLEN](byte)
	cuidPtr := unsafe.Pointer(&cUuid)
	result := C.virDomainGetUUID(d.ptr, (*C.uchar)(cuidPtr))
	if result != 0 {
		return []byte{}, errors.New(GetLastError())
	}
	return C.GoBytes(cuidPtr, C.VIR_UUID_BUFLEN), nil
}

func (d *VirDomain) GetUUIDString() (string, error) {
	var cUuid [C.VIR_UUID_STRING_BUFLEN](C.char)
	cuidPtr := unsafe.Pointer(&cUuid)
	result := C.virDomainGetUUIDString(d.ptr, (*C.char)(cuidPtr))
	if result != 0 {
		return "", errors.New(GetLastError())
	}
	return C.GoString((*C.char)(cuidPtr)), nil
}

func (d *VirDomain) GetInfo() (VirDomainInfo, error) {
	di := VirDomainInfo{}
	var ptr C.virDomainInfo
	result := C.virDomainGetInfo(d.ptr, (*C.virDomainInfo)(unsafe.Pointer(&ptr)))
	if result == -1 {
		return di, errors.New(GetLastError())
	}
	di.ptr = ptr
	return di, nil
}

func (d *VirDomain) GetXMLDesc(flags uint32) (string, error) {
	result := C.virDomainGetXMLDesc(d.ptr, C.uint(flags))
	if result == nil {
		return "", errors.New(GetLastError())
	}
	xml := C.GoString(result)
	C.free(unsafe.Pointer(result))
	return xml, nil
}

func (i *VirDomainInfo) GetState() uint8 {
	return uint8(i.ptr.state)
}

func (i *VirDomainInfo) GetMaxMem() uint64 {
	return uint64(i.ptr.maxMem)
}

func (i *VirDomainInfo) GetMemory() uint64 {
	return uint64(i.ptr.memory)
}

func (i *VirDomainInfo) GetNrVirtCpu() uint16 {
	return uint16(i.ptr.nrVirtCpu)
}

func (i *VirDomainInfo) GetCpuTime() uint64 {
	return uint64(i.ptr.cpuTime)
}

func (d *VirDomain) GetMetadata(tipus int, uri string, flags uint32) (string, error) {

	var cUri *C.char
	if uri != "" {
		cUri = C.CString(uri)
		defer C.free(unsafe.Pointer(cUri))
	}

	result := C.virDomainGetMetadata(d.ptr, C.int(tipus), cUri, C.uint(flags))
	if result == nil {
		return "", errors.New(GetLastError())

	}
	defer C.free(unsafe.Pointer(result))
	return C.GoString(result), nil
}

func (d *VirDomain) SetMetadata(metaDataType int, metaDataCont, uriKey, uri string, flags uint32) error {
	var cMetaDataCont *C.char
	var cUriKey *C.char
	var cUri *C.char

	cMetaDataCont = C.CString(metaDataCont)
	defer C.free(unsafe.Pointer(cMetaDataCont))

	if metaDataType == VIR_DOMAIN_METADATA_ELEMENT {
		cUriKey = C.CString(uriKey)
		defer C.free(unsafe.Pointer(cUriKey))
		cUri = C.CString(uri)
		defer C.free(unsafe.Pointer(cUri))
	}
	result := C.virDomainSetMetadata(d.ptr, C.int(metaDataType), cMetaDataCont, cUriKey, cUri, C.uint(flags))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) Undefine() error {
	result := C.virDomainUndefine(d.ptr)
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) SetMemory(memory uint64) error {
	result := C.virDomainSetMemory(d.ptr, C.ulong(memory))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) SetMemoryFlags(memory uint64, flags uint32) error {
	result := C.virDomainSetMemoryFlags(d.ptr, C.ulong(memory), C.uint(flags))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) SetVcpus(vcpu uint) error {
	result := C.virDomainSetVcpus(d.ptr, C.uint(vcpu))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}

func (d *VirDomain) SetVcpusFlags(vcpu uint, flags uint) error {
	result := C.virDomainSetVcpusFlags(d.ptr, C.uint(vcpu), C.uint(flags))
	if result == -1 {
		return errors.New(GetLastError())
	}
	return nil
}
<|endoftext|>


<commit_before>package db

import (
	"database/sql"
	"fmt"
	"sort"

	"github.com/lxc/lxd/lxd/db/query"
)

// StorageVolumeNodeAddresses returns the addresses of all nodes on which the
// volume with the given name if defined.
//
// The empty string is used in place of the address of the current node.
func (c *ClusterTx) StorageVolumeNodeAddresses(poolID int64, name string, typ int) ([]string, error) {
	nodes := []struct {
		id      int64
		address string
	}{}
	dest := func(i int) []interface{} {
		nodes = append(nodes, struct {
			id      int64
			address string
		}{})
		return []interface{}{&nodes[i].id, &nodes[i].address}

	}
	stmt := `
SELECT nodes.id, nodes.address
  FROM nodes JOIN storage_volumes ON storage_volumes.node_id=nodes.id
    WHERE storage_volumes.storage_pool_id=? AND storage_volumes.name=? AND storage_volumes.type=?
`
	err := query.SelectObjects(c.tx, dest, stmt, poolID, name, typ)
	if err != nil {
		return nil, err
	}

	addresses := []string{}
	for _, node := range nodes {
		address := node.address
		if node.id == c.nodeID {
			address = ""
		}
		addresses = append(addresses, address)
	}

	sort.Strings(addresses)

	if len(addresses) == 0 {
		return nil, ErrNoSuchObject
	}

	return addresses, nil
}

// StorageVolumeNodeGet returns the name of the node a storage volume is on.
func (c *Cluster) StorageVolumeNodeGet(volumeID int64) (string, error) {
	name := ""
	query := `
SELECT nodes.name FROM storage_volumes
  JOIN nodes ON nodes.id=storage_volumes.node_id
   WHERE storage_volumes.id=?
`
	inargs := []interface{}{volumeID}
	outargs := []interface{}{&name}

	err := dbQueryRowScan(c.db, query, inargs, outargs)
	if err != nil {
		if err == sql.ErrNoRows {
			return "", ErrNoSuchObject
		}
		return "", err
	}

	return name, nil
}

// Get config of a storage volume.
func (c *Cluster) StorageVolumeConfigGet(volumeID int64) (map[string]string, error) {
	var key, value string
	query := "SELECT key, value FROM storage_volumes_config WHERE storage_volume_id=?"
	inargs := []interface{}{volumeID}
	outargs := []interface{}{key, value}

	results, err := queryScan(c.db, query, inargs, outargs)
	if err != nil {
		return nil, err
	}

	config := map[string]string{}

	for _, r := range results {
		key = r[0].(string)
		value = r[1].(string)

		config[key] = value
	}

	return config, nil
}

// Get the description of a storage volume.
func (c *Cluster) StorageVolumeDescriptionGet(volumeID int64) (string, error) {
	description := sql.NullString{}
	query := "SELECT description FROM storage_volumes WHERE id=?"
	inargs := []interface{}{volumeID}
	outargs := []interface{}{&description}

	err := dbQueryRowScan(c.db, query, inargs, outargs)
	if err != nil {
		if err == sql.ErrNoRows {
			return "", ErrNoSuchObject
		}
		return "", err
	}

	return description.String, nil
}

// Update description of a storage volume.
func StorageVolumeDescriptionUpdate(tx *sql.Tx, volumeID int64, description string) error {
	_, err := tx.Exec("UPDATE storage_volumes SET description=? WHERE id=?", description, volumeID)
	return err
}

// Add new storage volume config into database.
func StorageVolumeConfigAdd(tx *sql.Tx, volumeID int64, volumeConfig map[string]string) error {
	str := "INSERT INTO storage_volumes_config (storage_volume_id, key, value) VALUES(?, ?, ?)"
	stmt, err := tx.Prepare(str)
	defer stmt.Close()
	if err != nil {
		return err
	}

	for k, v := range volumeConfig {
		if v == "" {
			continue
		}

		_, err = stmt.Exec(volumeID, k, v)
		if err != nil {
			return err
		}
	}

	return nil
}

// Delete storage volume config.
func StorageVolumeConfigClear(tx *sql.Tx, volumeID int64) error {
	_, err := tx.Exec("DELETE FROM storage_volumes_config WHERE storage_volume_id=?", volumeID)
	if err != nil {
		return err
	}

	return nil
}

// Get the IDs of all volumes with the given name and type associated with the
// given pool, regardless of their node_id column.
func storageVolumeIDsGet(tx *sql.Tx, volumeName string, volumeType int, poolID int64) ([]int64, error) {
	ids, err := query.SelectIntegers(tx, `
SELECT id FROM storage_volumes WHERE name=? AND type=? AND storage_pool_id=?
`, volumeName, volumeType, poolID)
	if err != nil {
		return nil, err
	}
	ids64 := make([]int64, len(ids))
	for i, id := range ids {
		ids64[i] = int64(id)
	}
	return ids64, nil
}

func (c *Cluster) StorageVolumeCleanupImages(fingerprints []string) error {
	stmt := fmt.Sprintf(
		"DELETE FROM storage_volumes WHERE type=? AND name NOT IN %s",
		query.Params(len(fingerprints)))
	args := []interface{}{StoragePoolVolumeTypeImage}
	for _, fingerprint := range fingerprints {
		args = append(args, fingerprint)
	}
	err := exec(c.db, stmt, args...)
	return err
}

func (c *Cluster) StorageVolumeMoveToLVMThinPoolNameKey() error {
	err := exec(c.db, "UPDATE storage_pools_config SET key='lvm.thinpool_name' WHERE key='volume.lvm.thinpool_name';")
	if err != nil {
		return err
	}

	err = exec(c.db, "DELETE FROM storage_volumes_config WHERE key='lvm.thinpool_name';")
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Fix lints in lxd/db/storage_volumes.go<commit_after>package db

import (
	"database/sql"
	"fmt"
	"sort"

	"github.com/lxc/lxd/lxd/db/query"
)

// StorageVolumeNodeAddresses returns the addresses of all nodes on which the
// volume with the given name if defined.
//
// The empty string is used in place of the address of the current node.
func (c *ClusterTx) StorageVolumeNodeAddresses(poolID int64, name string, typ int) ([]string, error) {
	nodes := []struct {
		id      int64
		address string
	}{}
	dest := func(i int) []interface{} {
		nodes = append(nodes, struct {
			id      int64
			address string
		}{})
		return []interface{}{&nodes[i].id, &nodes[i].address}

	}
	stmt := `
SELECT nodes.id, nodes.address
  FROM nodes JOIN storage_volumes ON storage_volumes.node_id=nodes.id
    WHERE storage_volumes.storage_pool_id=? AND storage_volumes.name=? AND storage_volumes.type=?
`
	err := query.SelectObjects(c.tx, dest, stmt, poolID, name, typ)
	if err != nil {
		return nil, err
	}

	addresses := []string{}
	for _, node := range nodes {
		address := node.address
		if node.id == c.nodeID {
			address = ""
		}
		addresses = append(addresses, address)
	}

	sort.Strings(addresses)

	if len(addresses) == 0 {
		return nil, ErrNoSuchObject
	}

	return addresses, nil
}

// StorageVolumeNodeGet returns the name of the node a storage volume is on.
func (c *Cluster) StorageVolumeNodeGet(volumeID int64) (string, error) {
	name := ""
	query := `
SELECT nodes.name FROM storage_volumes
  JOIN nodes ON nodes.id=storage_volumes.node_id
   WHERE storage_volumes.id=?
`
	inargs := []interface{}{volumeID}
	outargs := []interface{}{&name}

	err := dbQueryRowScan(c.db, query, inargs, outargs)
	if err != nil {
		if err == sql.ErrNoRows {
			return "", ErrNoSuchObject
		}
		return "", err
	}

	return name, nil
}

// StorageVolumeConfigGet gets the config of a storage volume.
func (c *Cluster) StorageVolumeConfigGet(volumeID int64) (map[string]string, error) {
	var key, value string
	query := "SELECT key, value FROM storage_volumes_config WHERE storage_volume_id=?"
	inargs := []interface{}{volumeID}
	outargs := []interface{}{key, value}

	results, err := queryScan(c.db, query, inargs, outargs)
	if err != nil {
		return nil, err
	}

	config := map[string]string{}

	for _, r := range results {
		key = r[0].(string)
		value = r[1].(string)

		config[key] = value
	}

	return config, nil
}

// StorageVolumeDescriptionGet gets the description of a storage volume.
func (c *Cluster) StorageVolumeDescriptionGet(volumeID int64) (string, error) {
	description := sql.NullString{}
	query := "SELECT description FROM storage_volumes WHERE id=?"
	inargs := []interface{}{volumeID}
	outargs := []interface{}{&description}

	err := dbQueryRowScan(c.db, query, inargs, outargs)
	if err != nil {
		if err == sql.ErrNoRows {
			return "", ErrNoSuchObject
		}
		return "", err
	}

	return description.String, nil
}

// StorageVolumeDescriptionUpdate updates the description of a storage volume.
func StorageVolumeDescriptionUpdate(tx *sql.Tx, volumeID int64, description string) error {
	_, err := tx.Exec("UPDATE storage_volumes SET description=? WHERE id=?", description, volumeID)
	return err
}

// StorageVolumeConfigAdd adds a new storage volume config into database.
func StorageVolumeConfigAdd(tx *sql.Tx, volumeID int64, volumeConfig map[string]string) error {
	str := "INSERT INTO storage_volumes_config (storage_volume_id, key, value) VALUES(?, ?, ?)"
	stmt, err := tx.Prepare(str)
	defer stmt.Close()
	if err != nil {
		return err
	}

	for k, v := range volumeConfig {
		if v == "" {
			continue
		}

		_, err = stmt.Exec(volumeID, k, v)
		if err != nil {
			return err
		}
	}

	return nil
}

// StorageVolumeConfigClear deletes storage volume config.
func StorageVolumeConfigClear(tx *sql.Tx, volumeID int64) error {
	_, err := tx.Exec("DELETE FROM storage_volumes_config WHERE storage_volume_id=?", volumeID)
	if err != nil {
		return err
	}

	return nil
}

// Get the IDs of all volumes with the given name and type associated with the
// given pool, regardless of their node_id column.
func storageVolumeIDsGet(tx *sql.Tx, volumeName string, volumeType int, poolID int64) ([]int64, error) {
	ids, err := query.SelectIntegers(tx, `
SELECT id FROM storage_volumes WHERE name=? AND type=? AND storage_pool_id=?
`, volumeName, volumeType, poolID)
	if err != nil {
		return nil, err
	}
	ids64 := make([]int64, len(ids))
	for i, id := range ids {
		ids64[i] = int64(id)
	}
	return ids64, nil
}

// StorageVolumeCleanupImages removes the volumes with the given fingerprints.
func (c *Cluster) StorageVolumeCleanupImages(fingerprints []string) error {
	stmt := fmt.Sprintf(
		"DELETE FROM storage_volumes WHERE type=? AND name NOT IN %s",
		query.Params(len(fingerprints)))
	args := []interface{}{StoragePoolVolumeTypeImage}
	for _, fingerprint := range fingerprints {
		args = append(args, fingerprint)
	}
	err := exec(c.db, stmt, args...)
	return err
}

// StorageVolumeMoveToLVMThinPoolNameKey upgrades the config keys of LVM
// volumes.
func (c *Cluster) StorageVolumeMoveToLVMThinPoolNameKey() error {
	err := exec(c.db, "UPDATE storage_pools_config SET key='lvm.thinpool_name' WHERE key='volume.lvm.thinpool_name';")
	if err != nil {
		return err
	}

	err = exec(c.db, "DELETE FROM storage_volumes_config WHERE key='lvm.thinpool_name';")
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>// Package htpasswd provides a simple authentication scheme that checks for the
// user credential hash in an htpasswd formatted file in a configuration-determined
// location.
//
// This authentication method MUST be used under TLS, as simple token-replay attack is possible.
package htpasswd

import (
	"errors"
	"fmt"
	"net/http"
	"os"

	"github.com/docker/distribution/context"
	"github.com/docker/distribution/registry/auth"
)

var (
	// ErrInvalidCredential is returned when the auth token does not authenticate correctly.
	ErrInvalidCredential = errors.New("invalid authorization credential")

	// ErrAuthenticationFailure returned when authentication failure to be presented to agent.
	ErrAuthenticationFailure = errors.New("authentication failured")
)

type accessController struct {
	realm    string
	htpasswd *htpasswd
}

var _ auth.AccessController = &accessController{}

func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
	realm, present := options["realm"]
	if _, ok := realm.(string); !present || !ok {
		return nil, fmt.Errorf(`"realm" must be set for htpasswd access controller`)
	}

	path, present := options["path"]
	if _, ok := path.(string); !present || !ok {
		return nil, fmt.Errorf(`"path" must be set for htpasswd access controller`)
	}

	f, err := os.Open(path.(string))
	if err != nil {
		return nil, err
	}
	defer f.Close()

	h, err := newHTPasswd(f)
	if err != nil {
		return nil, err
	}

	return &accessController{realm: realm.(string), htpasswd: h}, nil
}

func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
	req, err := context.GetRequest(ctx)
	if err != nil {
		return nil, err
	}

	username, password, ok := req.BasicAuth()
	if !ok {
		return nil, &challenge{
			realm: ac.realm,
			err:   ErrInvalidCredential,
		}
	}

	if err := ac.htpasswd.authenticateUser(username, password); err != nil {
		context.GetLogger(ctx).Errorf("error authenticating user %q: %v", username, err)
		return nil, &challenge{
			realm: ac.realm,
			err:   ErrAuthenticationFailure,
		}
	}

	return auth.WithUser(ctx, auth.UserInfo{Name: username}), nil
}

// challenge implements the auth.Challenge interface.
type challenge struct {
	realm string
	err   error
}

var _ auth.Challenge = challenge{}

// SetHeaders sets the basic challenge header on the response.
func (ch challenge) SetHeaders(w http.ResponseWriter) {
	w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", ch.realm))
}

func (ch challenge) Error() string {
	return fmt.Sprintf("basic authentication challenge for realm %q: %s", ch.realm, ch.err)
}

func init() {
	auth.Register("htpasswd", auth.InitFunc(newAccessController))
}
<commit_msg>Correct ErrAuthenticationFailure message<commit_after>// Package htpasswd provides a simple authentication scheme that checks for the
// user credential hash in an htpasswd formatted file in a configuration-determined
// location.
//
// This authentication method MUST be used under TLS, as simple token-replay attack is possible.
package htpasswd

import (
	"errors"
	"fmt"
	"net/http"
	"os"

	"github.com/docker/distribution/context"
	"github.com/docker/distribution/registry/auth"
)

var (
	// ErrInvalidCredential is returned when the auth token does not authenticate correctly.
	ErrInvalidCredential = errors.New("invalid authorization credential")

	// ErrAuthenticationFailure returned when authentication failure to be presented to agent.
	ErrAuthenticationFailure = errors.New("authentication failure")
)

type accessController struct {
	realm    string
	htpasswd *htpasswd
}

var _ auth.AccessController = &accessController{}

func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
	realm, present := options["realm"]
	if _, ok := realm.(string); !present || !ok {
		return nil, fmt.Errorf(`"realm" must be set for htpasswd access controller`)
	}

	path, present := options["path"]
	if _, ok := path.(string); !present || !ok {
		return nil, fmt.Errorf(`"path" must be set for htpasswd access controller`)
	}

	f, err := os.Open(path.(string))
	if err != nil {
		return nil, err
	}
	defer f.Close()

	h, err := newHTPasswd(f)
	if err != nil {
		return nil, err
	}

	return &accessController{realm: realm.(string), htpasswd: h}, nil
}

func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
	req, err := context.GetRequest(ctx)
	if err != nil {
		return nil, err
	}

	username, password, ok := req.BasicAuth()
	if !ok {
		return nil, &challenge{
			realm: ac.realm,
			err:   ErrInvalidCredential,
		}
	}

	if err := ac.htpasswd.authenticateUser(username, password); err != nil {
		context.GetLogger(ctx).Errorf("error authenticating user %q: %v", username, err)
		return nil, &challenge{
			realm: ac.realm,
			err:   ErrAuthenticationFailure,
		}
	}

	return auth.WithUser(ctx, auth.UserInfo{Name: username}), nil
}

// challenge implements the auth.Challenge interface.
type challenge struct {
	realm string
	err   error
}

var _ auth.Challenge = challenge{}

// SetHeaders sets the basic challenge header on the response.
func (ch challenge) SetHeaders(w http.ResponseWriter) {
	w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", ch.realm))
}

func (ch challenge) Error() string {
	return fmt.Sprintf("basic authentication challenge for realm %q: %s", ch.realm, ch.err)
}

func init() {
	auth.Register("htpasswd", auth.InitFunc(newAccessController))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"sync"

	"github.com/calavera/dkvolume"
	"github.com/calavera/docker-volume-glusterfs/rest"
)

type volume struct {
	name        string
	connections int
}

type glusterfsDriver struct {
	root       string
	restClient *rest.Client
	servers    []string
	volumes    map[string]*volume
	m          sync.Mutex
}

func newGlusterfsDriver(root, restAddress, gfsBase string, servers []string) glusterfsDriver {
	d := glusterfsDriver{
		root:    root,
		servers: servers,
		volumes: map[string]*volume{},
	}
	if len(restAddress) > 0 {
		d.restClient = rest.NewClient(restAddress, gfsBase)
	}
	return d
}

func (d glusterfsDriver) Create(r dkvolume.Request) dkvolume.Response {
	log.Printf("Creating volume %s\n", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	m := d.mountpoint(r.Name)

	if _, ok := d.volumes[m]; ok {
		return dkvolume.Response{}
	}

	if d.restClient != nil {
		exist, err := d.restClient.VolumeExist(r.Name)
		if err != nil {
			return dkvolume.Response{Err: err.Error()}
		}

		if !exist {
			if err := d.restClient.CreateVolume(r.Name, d.servers); err != nil {
				return dkvolume.Response{Err: err.Error()}
			}
		}
	}
	return dkvolume.Response{}
}

func (d glusterfsDriver) Remove(r dkvolume.Request) dkvolume.Response {
	log.Printf("Removing volume %s\n", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	m := d.mountpoint(r.Name)

	if s, ok := d.volumes[m]; ok {
		if s.connections <= 1 {
			if d.restClient != nil {
				if err := d.restClient.StopVolume(r.Name); err != nil {
					return dkvolume.Response{Err: err.Error()}
				}
			}
			delete(d.volumes, m)
		}
	}
	return dkvolume.Response{}
}

func (d glusterfsDriver) Path(r dkvolume.Request) dkvolume.Response {
	return dkvolume.Response{Mountpoint: d.mountpoint(r.Name)}
}

func (d glusterfsDriver) Mount(r dkvolume.Request) dkvolume.Response {
	d.m.Lock()
	defer d.m.Unlock()
	m := d.mountpoint(r.Name)
	log.Printf("Mounting volume %s on %s\n", r.Name, m)

	s, ok := d.volumes[m]
	if ok && s.connections > 0 {
		s.connections++
		return dkvolume.Response{Mountpoint: m}
	}

	fi, err := os.Lstat(m)

	if os.IsNotExist(err) {
		if err := os.MkdirAll(m, 0755); err != nil {
			return dkvolume.Response{Err: err.Error()}
		}
	} else if err != nil {
		return dkvolume.Response{Err: err.Error()}
	}

	if fi != nil && !fi.IsDir() {
		return dkvolume.Response{Err: fmt.Sprintf("%v already exist and it's not a directory", m)}
	}

	if err := d.mountVolume(r.Name, m); err != nil {
		return dkvolume.Response{Err: err.Error()}
	}

	d.volumes[m] = &volume{name: r.Name, connections: 1}

	return dkvolume.Response{Mountpoint: m}
}

func (d glusterfsDriver) Unmount(r dkvolume.Request) dkvolume.Response {
	d.m.Lock()
	defer d.m.Unlock()
	m := d.mountpoint(r.Name)
	log.Printf("Unmounting volume %s from %s\n", r.Name, m)

	if s, ok := d.volumes[m]; ok {
		if s.connections == 1 {
			if err := d.unmountVolume(m); err != nil {
				return dkvolume.Response{Err: err.Error()}
			}
		}
		s.connections--
	} else {
		return dkvolume.Response{Err: fmt.Sprintf("Unable to find volume mounted on %s", m)}
	}

	return dkvolume.Response{}
}

func (d *glusterfsDriver) mountpoint(name string) string {
	return filepath.Join(d.root, name)
}

func (d *glusterfsDriver) mountVolume(name, destination string) error {
	server := d.servers[rand.Intn(len(d.servers))]

	cmd := fmt.Sprintf("glusterfs --log-level=DEBUG --volfile-id=%s --volfile-server=%s %s", name, server, destination)
	if out, err := exec.Command("sh", "-c", cmd).CombinedOutput(); err != nil {
		log.Println(string(out))
		return err
	}
	return nil
}

func (d *glusterfsDriver) unmountVolume(target string) error {
	cmd := fmt.Sprintf("umount %s", target)
	if out, err := exec.Command("sh", "-c", cmd).CombinedOutput(); err != nil {
		log.Println(string(out))
		return err
	}
	return nil
}
<commit_msg>Use mutex reference.<commit_after>package main

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"sync"

	"github.com/calavera/dkvolume"
	"github.com/calavera/docker-volume-glusterfs/rest"
)

type volume struct {
	name        string
	connections int
}

type glusterfsDriver struct {
	root       string
	restClient *rest.Client
	servers    []string
	volumes    map[string]*volume
	m          *sync.Mutex
}

func newGlusterfsDriver(root, restAddress, gfsBase string, servers []string) glusterfsDriver {
	d := glusterfsDriver{
		root:    root,
		servers: servers,
		volumes: map[string]*volume{},
		m:       &sync.Mutex{},
	}
	if len(restAddress) > 0 {
		d.restClient = rest.NewClient(restAddress, gfsBase)
	}
	return d
}

func (d glusterfsDriver) Create(r dkvolume.Request) dkvolume.Response {
	log.Printf("Creating volume %s\n", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	m := d.mountpoint(r.Name)

	if _, ok := d.volumes[m]; ok {
		return dkvolume.Response{}
	}

	if d.restClient != nil {
		exist, err := d.restClient.VolumeExist(r.Name)
		if err != nil {
			return dkvolume.Response{Err: err.Error()}
		}

		if !exist {
			if err := d.restClient.CreateVolume(r.Name, d.servers); err != nil {
				return dkvolume.Response{Err: err.Error()}
			}
		}
	}
	return dkvolume.Response{}
}

func (d glusterfsDriver) Remove(r dkvolume.Request) dkvolume.Response {
	log.Printf("Removing volume %s\n", r.Name)
	d.m.Lock()
	defer d.m.Unlock()
	m := d.mountpoint(r.Name)

	if s, ok := d.volumes[m]; ok {
		if s.connections <= 1 {
			if d.restClient != nil {
				if err := d.restClient.StopVolume(r.Name); err != nil {
					return dkvolume.Response{Err: err.Error()}
				}
			}
			delete(d.volumes, m)
		}
	}
	return dkvolume.Response{}
}

func (d glusterfsDriver) Path(r dkvolume.Request) dkvolume.Response {
	return dkvolume.Response{Mountpoint: d.mountpoint(r.Name)}
}

func (d glusterfsDriver) Mount(r dkvolume.Request) dkvolume.Response {
	d.m.Lock()
	defer d.m.Unlock()
	m := d.mountpoint(r.Name)
	log.Printf("Mounting volume %s on %s\n", r.Name, m)

	s, ok := d.volumes[m]
	if ok && s.connections > 0 {
		s.connections++
		return dkvolume.Response{Mountpoint: m}
	}

	fi, err := os.Lstat(m)

	if os.IsNotExist(err) {
		if err := os.MkdirAll(m, 0755); err != nil {
			return dkvolume.Response{Err: err.Error()}
		}
	} else if err != nil {
		return dkvolume.Response{Err: err.Error()}
	}

	if fi != nil && !fi.IsDir() {
		return dkvolume.Response{Err: fmt.Sprintf("%v already exist and it's not a directory", m)}
	}

	if err := d.mountVolume(r.Name, m); err != nil {
		return dkvolume.Response{Err: err.Error()}
	}

	d.volumes[m] = &volume{name: r.Name, connections: 1}

	return dkvolume.Response{Mountpoint: m}
}

func (d glusterfsDriver) Unmount(r dkvolume.Request) dkvolume.Response {
	d.m.Lock()
	defer d.m.Unlock()
	m := d.mountpoint(r.Name)
	log.Printf("Unmounting volume %s from %s\n", r.Name, m)

	if s, ok := d.volumes[m]; ok {
		if s.connections == 1 {
			if err := d.unmountVolume(m); err != nil {
				return dkvolume.Response{Err: err.Error()}
			}
		}
		s.connections--
	} else {
		return dkvolume.Response{Err: fmt.Sprintf("Unable to find volume mounted on %s", m)}
	}

	return dkvolume.Response{}
}

func (d *glusterfsDriver) mountpoint(name string) string {
	return filepath.Join(d.root, name)
}

func (d *glusterfsDriver) mountVolume(name, destination string) error {
	server := d.servers[rand.Intn(len(d.servers))]

	cmd := fmt.Sprintf("glusterfs --log-level=DEBUG --volfile-id=%s --volfile-server=%s %s", name, server, destination)
	if out, err := exec.Command("sh", "-c", cmd).CombinedOutput(); err != nil {
		log.Println(string(out))
		return err
	}
	return nil
}

func (d *glusterfsDriver) unmountVolume(target string) error {
	cmd := fmt.Sprintf("umount %s", target)
	if out, err := exec.Command("sh", "-c", cmd).CombinedOutput(); err != nil {
		log.Println(string(out))
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	log "github.com/Sirupsen/logrus"
	"os"
	"path"
	"path/filepath"
	"sync"
	"syscall"

	"github.com/davecgh/go-spew/spew"
	"github.com/docker/go-plugins-helpers/volume"
)

// A single volume instance
type beegfsMount struct {
	name string
	path string
	root string
}

type beegfsDriver struct {
	mounts  map[string]*beegfsMount
	m       *sync.Mutex
}

func newBeeGFSDriver(root string) beegfsDriver {
	d := beegfsDriver{
		mounts:  make(map[string]*beegfsMount),
		m:       &sync.Mutex{},
	}

	return d
}

func (b beegfsDriver) Create(r volume.Request) volume.Response {
	var volumeRoot string

	log.Infof("Create: %s, %v", r.Name, r.Options)

	b.m.Lock()
	defer b.m.Unlock()

	// Handle options (unrecognized options are silently ignored):
	// root: directory to create new volumes (this should correspond with
	//       beegfs-mounts.conf).
	if optsRoot, ok := r.Options["root"]; ok {
		volumeRoot = optsRoot
	} else {
		// Assume the default root
		volumeRoot = *root
	}

	dest := filepath.Join(volumeRoot, r.Name)
	if !isbeegfs(dest) {
		emsg := fmt.Sprintf("Cannot create volume %s as it's not on a BeeGFS filesystem", dest)
		log.Error(emsg)
		return volume.Response{Err: emsg}
	}

	fmt.Printf("mounts: %d", len(b.mounts))
	if _, ok := b.mounts[r.Name]; ok {
		imsg := fmt.Sprintf("Cannot create volume %s, it already exists", dest)
		log.Info(imsg)
		return volume.Response{}
	}

	volumePath := filepath.Join(volumeRoot, r.Name)

	if err := createDest(dest); err != nil {
		return volume.Response{Err: err.Error()}
	}

	b.mounts[r.Name] = &beegfsMount {
		name: r.Name,
		path: volumePath,
		root: volumeRoot,
	}

	if *verbose {
		spew.Dump(b.mounts)
	}

	return volume.Response{}
}

func (b beegfsDriver) Remove(r volume.Request) volume.Response {
	log.Infof("Remove: %s", r.Name)

	b.m.Lock()
	defer b.m.Unlock()

	if _, ok := b.mounts[r.Name]; ok {
		delete(b.mounts, r.Name)
	}

	return volume.Response{}
}

func (b beegfsDriver) Path(r volume.Request) volume.Response {
	log.Debugf("Path: %s", r.Name)

	if _, ok := b.mounts[r.Name]; ok {
		return volume.Response{Mountpoint: b.mounts[r.Name].path}
	}

	return volume.Response{}
}

func (b beegfsDriver) Mount(r volume.MountRequest) volume.Response {
	log.Infof("Mount: %s", r.Name)
	dest := filepath.Join(b.mounts[r.Name].root, r.Name)

	if !isbeegfs(dest) {
		emsg := fmt.Sprintf("Cannot mount volume %s as it's not on a BeeGFS filesystem", dest)
		log.Error(emsg)
		return volume.Response{Err: emsg}
	}

	if _, ok := b.mounts[r.Name]; ok {
		return volume.Response{Mountpoint: b.mounts[r.Name].path}
	}

	return volume.Response{}
}

func (b beegfsDriver) Unmount(r volume.UnmountRequest) volume.Response {
	log.Infof("Unmount: %s", r.Name)
	return volume.Response{}
}

func (b beegfsDriver) Get(r volume.Request) volume.Response {
	log.Infof("Get: %s", r.Name)

	if v, ok := b.mounts[r.Name]; ok {
		return volume.Response{
			Volume: &volume.Volume{
				Name:       v.name,
				Mountpoint: v.path,
			},
		}
	}

	return volume.Response{Err: fmt.Sprintf("volume %s unknown", r.Name)}
}

func (b beegfsDriver) List(r volume.Request) volume.Response {
	log.Infof("List %v", r)

	volumes := []*volume.Volume{}

	for v := range b.mounts {
		volumes = append(volumes, &volume.Volume{Name: b.mounts[v].name, Mountpoint: b.mounts[v].path})
	}

	return volume.Response{Volumes: volumes}
}

func (d beegfsDriver) Capabilities(r volume.Request) volume.Response {
	var res volume.Response
	res.Capabilities = volume.Capability{Scope: "global"}
	return res
}

// Check if the parent directory (where the volume will be created)
// is of type 'beegfs' using the BEEGFS_MAGIC value.
func isbeegfs(volumepath string) bool {
	log.Debugf("isbeegfs() for %s", volumepath)
	stat := syscall.Statfs_t{}
	err := syscall.Statfs(path.Dir(volumepath), &stat)
	if err != nil {
		log.Errorf("Could not determine filesystem type for %s: %s", volumepath, err)
		return false
	}

	log.Debugf("Type for %s: %d", volumepath, stat.Type)

	// BEEGFS_MAGIC 0x19830326
	return stat.Type == 428016422
}

func createDest(dest string) error {
	fstat, err := os.Lstat(dest)

	if os.IsNotExist(err) {
		if err := os.MkdirAll(dest, 0755); err != nil {
			return err
		}
	} else if err != nil {
		return err
	}

	if fstat != nil && !fstat.IsDir() {
		return fmt.Errorf("%v already exist and it's not a directory", dest)
	}

	return nil
}
<commit_msg>driver is fixed according to the latest changes in volume library interface<commit_after>package main

import (
	"errors"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"os"
	"path"
	"path/filepath"
	"sync"
	"syscall"

	"github.com/davecgh/go-spew/spew"
	"github.com/docker/go-plugins-helpers/volume"
)

// A single volume instance
type beegfsMount struct {
	name string
	path string
	root string
}

type beegfsDriver struct {
	mounts  map[string]*beegfsMount
	m       *sync.Mutex
}

func newBeeGFSDriver(root string) beegfsDriver {
	d := beegfsDriver{
		mounts:  make(map[string]*beegfsMount),
		m:       &sync.Mutex{},
	}

	return d
}

func (b beegfsDriver) Create(r *volume.CreateRequest) error {
	var volumeRoot string

	log.Infof("Create: %s, %v", r.Name, r.Options)

	b.m.Lock()
	defer b.m.Unlock()

	// Handle options (unrecognized options are silently ignored):
	// root: directory to create new volumes (this should correspond with
	//       beegfs-mounts.conf).
	if optsRoot, ok := r.Options["root"]; ok {
		volumeRoot = optsRoot
	} else {
		// Assume the default root
		volumeRoot = *root
	}

	dest := filepath.Join(volumeRoot, r.Name)
	if !isbeegfs(dest) {
		emsg := fmt.Sprintf("Cannot create volume %s as it's not on a BeeGFS filesystem", dest)
		log.Error(emsg)
		return errors.New(emsg)
	}

	fmt.Printf("mounts: %d", len(b.mounts))
	if _, ok := b.mounts[r.Name]; ok {
		imsg := fmt.Sprintf("Cannot create volume %s, it already exists", dest)
		log.Info(imsg)
		return nil
	}

	volumePath := filepath.Join(volumeRoot, r.Name)

	if err := createDest(dest); err != nil {
		return err
	}

	b.mounts[r.Name] = &beegfsMount {
		name: r.Name,
		path: volumePath,
		root: volumeRoot,
	}

	if *verbose {
		spew.Dump(b.mounts)
	}

	return nil
}

func (b beegfsDriver) Remove(r *volume.RemoveRequest) error {
	log.Infof("Remove: %s", r.Name)

	b.m.Lock()
	defer b.m.Unlock()

	if _, ok := b.mounts[r.Name]; ok {
		delete(b.mounts, r.Name)
	}

	return nil
}

func (b beegfsDriver) Path(r *volume.PathRequest) (*volume.PathResponse, error) {
	log.Debugf("Path: %s", r.Name)

	if _, ok := b.mounts[r.Name]; ok {
		return &volume.PathResponse{Mountpoint: b.mounts[r.Name].path}, nil
	}

	return nil, nil
}

func (b beegfsDriver) Mount(r *volume.MountRequest) (*volume.MountResponse, error) {
	log.Infof("Mount: %s", r.Name)
	dest := filepath.Join(b.mounts[r.Name].root, r.Name)

	if !isbeegfs(dest) {
		emsg := fmt.Sprintf("Cannot mount volume %s as it's not on a BeeGFS filesystem", dest)
		log.Error(emsg)
        return nil, errors.New(emsg)
	}

	if _, ok := b.mounts[r.Name]; ok {
		return &volume.MountResponse{Mountpoint: b.mounts[r.Name].path}, nil
	}

	return nil, nil
}

func (b beegfsDriver) Unmount(r *volume.UnmountRequest) error {
	log.Infof("Unmount: %s", r.Name)
	return nil
}

func (b beegfsDriver) Get(r *volume.GetRequest) (*volume.GetResponse, error) {
	log.Infof("Get: %s", r.Name)

	if v, ok := b.mounts[r.Name]; ok {
		return &volume.GetResponse{
			Volume: &volume.Volume{
				Name:       v.name,
				Mountpoint: v.path,
			},
		}, nil
	}

	return nil, errors.New(fmt.Sprintf("volume %s unknown", r.Name))
}

func (b beegfsDriver) List() (*volume.ListResponse, error) {
	log.Infof("List")

	volumes := []*volume.Volume{}

	for v := range b.mounts {
		volumes = append(volumes, &volume.Volume{Name: b.mounts[v].name, Mountpoint: b.mounts[v].path})
	}

	return &volume.ListResponse{Volumes: volumes}, nil
}

func (d beegfsDriver) Capabilities() *volume.CapabilitiesResponse {
    return &volume.CapabilitiesResponse{
        Capabilities: volume.Capability{
            Scope: "global",
        },
    }
}

// Check if the parent directory (where the volume will be created)
// is of type 'beegfs' using the BEEGFS_MAGIC value.
func isbeegfs(volumepath string) bool {
	log.Debugf("isbeegfs() for %s", volumepath)
	stat := syscall.Statfs_t{}
	err := syscall.Statfs(path.Dir(volumepath), &stat)
	if err != nil {
		log.Errorf("Could not determine filesystem type for %s: %s", volumepath, err)
		return false
	}

	log.Debugf("Type for %s: %d", volumepath, stat.Type)

	// BEEGFS_MAGIC 0x19830326
	return stat.Type == 428016422
}

func createDest(dest string) error {
	fstat, err := os.Lstat(dest)

	if os.IsNotExist(err) {
		if err := os.MkdirAll(dest, 0755); err != nil {
			return err
		}
	} else if err != nil {
		return err
	}

	if fstat != nil && !fstat.IsDir() {
		return fmt.Errorf("%v already exist and it's not a directory", dest)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

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

	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/channels"
	"github.com/ninjasphere/go-ninja/devices"
	"github.com/ninjasphere/go-ninja/logger"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/wolfeidau/lifx"
)

var info = ninja.LoadModuleInfo("./package.json")

// Default values if we are only setting partial state.
// TODO: @wolfidau This should be coming from the bulb
var defaultTransition = 300
var defaultBrightness float64 = 1
var defaultSaturation float64 = 1
var defaultHue = 0

type LifxDriver struct {
	log       *logger.Logger
	config    *LifxDriverConfig
	conn      *ninja.Connection
	client    *lifx.Client
	sendEvent func(event string, payload interface{}) error

	devices map[string]*lifxDevice
}

// this struct holds channels which are specific to the lifx bulb
type lifxDevice struct {
	illuminance *channels.IlluminanceChannel
}

func NewLifxDriver() {
	d := &LifxDriver{
		log:     logger.GetLogger(info.Name),
		client:  lifx.NewClient(),
		devices: make(map[string]*lifxDevice),
	}

	conn, err := ninja.Connect(info.ID)
	if err != nil {
		d.log.Fatalf("Failed to connect to MQTT: %s", err)
	}

	err = conn.ExportDriver(d)

	if err != nil {
		d.log.Fatalf("Failed to export driver: %s", err)
	}

	go func() {

		sub := d.client.Subscribe()

		for {

			event := <-sub.Events

			switch evnt := event.(type) {
			case *lifx.Bulb:
				if isUnique(evnt) {
					d.log.Infof("creating new light")
					_, err := d.newLight(evnt)
					if err != nil {
						d.log.HandleErrorf(err, "Error creating light instance")
					}
					seenlights = append(seenlights, evnt) //TODO remove bulbs that haven't been seen in a while?
					err = d.client.GetBulbState(evnt)

					if err != nil {
						d.log.Warningf("unable to intiate bulb state request %s", err)
					}
				}
			case *lifx.LightSensorState:
				// handle these vents for each bulb
				if d.devices[evnt.GetLifxAddress()] != nil {
					d.devices[evnt.GetLifxAddress()].illuminance.SendState(float64(evnt.Lux))
				}

			default:
				d.log.Infof("Event %v", event)
			}

		}

	}()

	go d.publishSensorData()

	d.conn = conn
}

type LifxDriverConfig struct {
}

func (d *LifxDriver) Start(config *LifxDriverConfig) error {
	d.log.Infof("Starting with config %v", config)
	d.config = config

	err := d.client.StartDiscovery()
	if err != nil {
		err = fmt.Errorf("Failed to discover bulbs : %s", err)
	}
	return err
}

func (d *LifxDriver) Stop() error {
	return nil
}

func (d *LifxDriver) GetModuleInfo() *model.Module {
	return info
}

func (d *LifxDriver) SetEventHandler(sendEvent func(event string, payload interface{}) error) {
	d.sendEvent = sendEvent
}

//---------------------------------------------------------------[Bulb]----------------------------------------------------------------

func (d *LifxDriver) newLight(bulb *lifx.Bulb) (*devices.LightDevice, error) { //TODO cut this down!

	name := bulb.GetLabel()

	d.log.Infof("Making light with ID: %s Label: %s", bulb.GetLifxAddress(), name)

	light, err := devices.CreateLightDevice(d, &model.Device{
		NaturalID:     bulb.GetLifxAddress(),
		NaturalIDType: "lifx",
		Name:          &name,
		Signatures: &map[string]string{
			"ninja:manufacturer": "Lifx",
			"ninja:productName":  "Lifx Bulb",
			"ninja:productType":  "Light",
			"ninja:thingType":    "light",
		},
	}, d.conn)

	if err != nil {
		d.log.FatalError(err, "Could not create light device")
	}

	light.ApplyOnOff = func(state bool) error {
		var err error
		if state {
			err = d.client.LightOn(bulb)
		} else {
			err = d.client.LightOff(bulb)
		}
		if err != nil {
			return fmt.Errorf("Failed to set on-off state: %s", err)
		}
		return nil
	}

	light.ApplyLightState = func(state *devices.LightDeviceState) error {
		jsonState, _ := json.Marshal(state)
		d.log.Debugf("Sending light state to lifx bulb: %s", jsonState)

		if state.OnOff != nil {
			err := light.ApplyOnOff(*state.OnOff)
			if err != nil {
				return err
			}
		}

		if state.Color != nil || state.Brightness != nil || state.Transition != nil {

			if state.Brightness == nil {
				state.Brightness = &defaultBrightness
			}

			if state.Color == nil {
				state.Color = &channels.ColorState{
					Mode:       "hue",
					Saturation: &defaultSaturation,
				}
			}

			if state.Transition == nil {
				state.Transition = &defaultTransition
			}

			/*if state.Color == nil {
				return fmt.Errorf("Color value missing from batch set")
			}

			if state.Brightness == nil {
				return fmt.Errorf("Brightness value missing from batch set")
			}*/

			switch state.Color.Mode {
			case "hue":
				return d.client.LightColour(
					bulb,
					uint16(*state.Color.Hue*math.MaxUint16),
					uint16(*state.Color.Saturation*math.MaxUint16),
					uint16(*state.Brightness*math.MaxUint16),
					0,
					uint32(*state.Transition),
				)

			case "xy":
				//TODO: Lifx does not support XY color
				return fmt.Errorf("XY color mode is not yet supported in the Lifx driver")

			case "temperature":
				d.client.LightColour(
					bulb,
					0,
					0,
					uint16(*state.Brightness*math.MaxUint16),
					uint16(*state.Color.Temperature),
					uint32(*state.Transition),
				)

			default:
				return fmt.Errorf("Unknown color mode %s", state.Color.Mode)
			}

		}

		return nil
	}

	bulb.SetStateHandler(buildStateHandler(d, bulb, light))

	if err := light.EnableOnOffChannel(); err != nil {
		d.log.FatalError(err, "Could not enable lifx on-off channel")
	}
	if err := light.EnableBrightnessChannel(); err != nil {
		d.log.FatalError(err, "Could not enable lifx brightness channel")
	}
	if err := light.EnableColorChannel("temperature", "hue"); err != nil {
		d.log.FatalError(err, "Could not enable lifx color channel")
	}
	if err := light.EnableTransitionChannel(); err != nil {
		d.log.FatalError(err, "Could not enable lifx transition channel")
	}

	// extra channels for sensors
	illuminance := channels.NewIlluminanceChannel()

	if err := d.conn.ExportChannel(light, illuminance, "illuminance"); err != nil {
		d.log.FatalError(err, "Could not enable lifx illuminance channel")
	}

	d.log.Debugf("register illuminance channel for %s", bulb.GetLifxAddress())

	d.devices[bulb.GetLifxAddress()] = &lifxDevice{illuminance}

	return light, nil
}

func buildStateHandler(driver *LifxDriver, bulb *lifx.Bulb, light *devices.LightDevice) lifx.StateHandler {

	return func(bulbState *lifx.BulbState) {

		jsonState, _ := json.Marshal(bulbState)
		driver.log.Debugf("Incoming state: %s", jsonState)

		state := &devices.LightDeviceState{}

		onOff := int(bulbState.Power) > 0
		state.OnOff = &onOff

		brightness := float64(bulbState.Brightness) / math.MaxUint16
		state.Brightness = &brightness

		color := &channels.ColorState{}
		if bulbState.Saturation == 0 {
			color.Mode = "temperature"

			temperature := float64(bulbState.Kelvin)
			color.Temperature = &temperature

		} else {
			color.Mode = "hue"

			hue := float64(bulbState.Hue) / float64(math.MaxUint16)
			color.Hue = &hue

			saturation := float64(bulbState.Saturation) / float64(math.MaxUint16)
			color.Saturation = &saturation
		}

		state.Color = color

		light.SetLightState(state)

		// TODO use the state.Visible to indicate whether a globe is online/offline at the moment

	}
}

// this function publishes information from the sensors on the lifx bulb
// atm this is limited to illuminance
func (d *LifxDriver) publishSensorData() {
	c := time.Tick(30 * time.Second)
	for _ = range c {
		for _, bulb := range d.client.GetBulbs() {
			d.client.GetAmbientLight(bulb)
		}
	}
}

//---------------------------------------------------------------[Utils]----------------------------------------------------------------

var seenlights []*lifx.Bulb

func isUnique(newbulb *lifx.Bulb) bool {
	ret := true
	for _, bulb := range seenlights {
		if bulb.LifxAddress == newbulb.LifxAddress {
			ret = false
		}
	}
	return ret
}
<commit_msg>Fixed initial state issue.<commit_after>package main

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

	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/channels"
	"github.com/ninjasphere/go-ninja/devices"
	"github.com/ninjasphere/go-ninja/logger"
	"github.com/ninjasphere/go-ninja/model"
	"github.com/wolfeidau/lifx"
)

var info = ninja.LoadModuleInfo("./package.json")

// Default values if we are only setting partial state.
// TODO: @wolfidau This should be coming from the bulb
var defaultTransition = 300
var defaultBrightness float64 = 1

type LifxDriver struct {
	log       *logger.Logger
	config    *LifxDriverConfig
	conn      *ninja.Connection
	client    *lifx.Client
	sendEvent func(event string, payload interface{}) error

	devices map[string]*lifxDevice
}

// this struct holds channels which are specific to the lifx bulb
type lifxDevice struct {
	illuminance *channels.IlluminanceChannel
}

func NewLifxDriver() {
	d := &LifxDriver{
		log:     logger.GetLogger(info.Name),
		client:  lifx.NewClient(),
		devices: make(map[string]*lifxDevice),
	}

	conn, err := ninja.Connect(info.ID)
	if err != nil {
		d.log.Fatalf("Failed to connect to MQTT: %s", err)
	}

	err = conn.ExportDriver(d)

	if err != nil {
		d.log.Fatalf("Failed to export driver: %s", err)
	}

	go func() {

		sub := d.client.Subscribe()

		for {

			event := <-sub.Events

			switch evnt := event.(type) {
			case *lifx.Bulb:
				if isUnique(evnt) {
					d.log.Infof("creating new light")
					_, err := d.newLight(evnt)
					if err != nil {
						d.log.HandleErrorf(err, "Error creating light instance")
					}
					seenlights = append(seenlights, evnt) //TODO remove bulbs that haven't been seen in a while?
					err = d.client.GetBulbState(evnt)

					if err != nil {
						d.log.Warningf("unable to intiate bulb state request %s", err)
					}
				}
			case *lifx.LightSensorState:
				// handle these vents for each bulb
				if d.devices[evnt.GetLifxAddress()] != nil {
					d.devices[evnt.GetLifxAddress()].illuminance.SendState(float64(evnt.Lux))
				}

			default:
				d.log.Infof("Event %v", event)
			}

		}

	}()

	go d.publishSensorData()

	d.conn = conn
}

type LifxDriverConfig struct {
}

func (d *LifxDriver) Start(config *LifxDriverConfig) error {
	d.log.Infof("Starting with config %v", config)
	d.config = config

	err := d.client.StartDiscovery()
	if err != nil {
		err = fmt.Errorf("Failed to discover bulbs : %s", err)
	}
	return err
}

func (d *LifxDriver) Stop() error {
	return nil
}

func (d *LifxDriver) GetModuleInfo() *model.Module {
	return info
}

func (d *LifxDriver) SetEventHandler(sendEvent func(event string, payload interface{}) error) {
	d.sendEvent = sendEvent
}

//---------------------------------------------------------------[Bulb]----------------------------------------------------------------

func (d *LifxDriver) newLight(bulb *lifx.Bulb) (*devices.LightDevice, error) { //TODO cut this down!

	name := bulb.GetLabel()

	d.log.Infof("Making light with ID: %s Label: %s", bulb.GetLifxAddress(), name)

	light, err := devices.CreateLightDevice(d, &model.Device{
		NaturalID:     bulb.GetLifxAddress(),
		NaturalIDType: "lifx",
		Name:          &name,
		Signatures: &map[string]string{
			"ninja:manufacturer": "Lifx",
			"ninja:productName":  "Lifx Bulb",
			"ninja:productType":  "Light",
			"ninja:thingType":    "light",
		},
	}, d.conn)

	if err != nil {
		d.log.FatalError(err, "Could not create light device")
	}

	light.ApplyOnOff = func(state bool) error {
		var err error
		if state {
			err = d.client.LightOn(bulb)
		} else {
			err = d.client.LightOff(bulb)
		}
		if err != nil {
			return fmt.Errorf("Failed to set on-off state: %s", err)
		}
		return nil
	}

	light.ApplyLightState = func(state *devices.LightDeviceState) error {
		jsonState, _ := json.Marshal(state)
		d.log.Infof("Sending light state to lifx bulb: %s", jsonState)

		if state.OnOff != nil {
			err := light.ApplyOnOff(*state.OnOff)
			if err != nil {
				return err
			}
		}

		if state.Color != nil || state.Brightness != nil || state.Transition != nil {
			// pull out the origin state
			bulbState := bulb.GetState()

			if state.Brightness == nil {
				brightness := float64(bulbState.Brightness) / math.MaxUint16
				state.Brightness = &brightness
			}

			if state.Color == nil {
				// save the brightness
				brightness := state.Brightness
				state = convertState(&bulbState)
				state.Brightness = brightness
			}

			if state.Transition == nil {
				state.Transition = &defaultTransition
			}

			/*if state.Color == nil {
				return fmt.Errorf("Color value missing from batch set")
			}

			if state.Brightness == nil {
				return fmt.Errorf("Brightness value missing from batch set")
			}*/

			switch state.Color.Mode {
			case "hue":
				return d.client.LightColour(
					bulb,
					uint16(*state.Color.Hue*math.MaxUint16),
					uint16(*state.Color.Saturation*math.MaxUint16),
					uint16(*state.Brightness*math.MaxUint16),
					0,
					uint32(*state.Transition),
				)

			case "xy":
				//TODO: Lifx does not support XY color
				return fmt.Errorf("XY color mode is not yet supported in the Lifx driver")

			case "temperature":
				d.client.LightColour(
					bulb,
					0,
					0,
					uint16(*state.Brightness*math.MaxUint16),
					uint16(*state.Color.Temperature),
					uint32(*state.Transition),
				)

			default:
				return fmt.Errorf("Unknown color mode %s", state.Color.Mode)
			}

		}

		return nil
	}

	bulb.SetStateHandler(buildStateHandler(d, bulb, light))

	if err := light.EnableOnOffChannel(); err != nil {
		d.log.FatalError(err, "Could not enable lifx on-off channel")
	}
	if err := light.EnableBrightnessChannel(); err != nil {
		d.log.FatalError(err, "Could not enable lifx brightness channel")
	}
	if err := light.EnableColorChannel("temperature", "hue"); err != nil {
		d.log.FatalError(err, "Could not enable lifx color channel")
	}
	if err := light.EnableTransitionChannel(); err != nil {
		d.log.FatalError(err, "Could not enable lifx transition channel")
	}

	// extra channels for sensors
	illuminance := channels.NewIlluminanceChannel()

	if err := d.conn.ExportChannel(light, illuminance, "illuminance"); err != nil {
		d.log.FatalError(err, "Could not enable lifx illuminance channel")
	}

	d.log.Debugf("register illuminance channel for %s", bulb.GetLifxAddress())

	d.devices[bulb.GetLifxAddress()] = &lifxDevice{illuminance}

	return light, nil
}

func buildStateHandler(driver *LifxDriver, bulb *lifx.Bulb, light *devices.LightDevice) lifx.StateHandler {

	return func(bulbState *lifx.BulbState) {

		jsonState, _ := json.Marshal(bulbState)
		driver.log.Debugf("Incoming state: %s", jsonState)

		state := convertState(bulbState)
		light.SetLightState(state)
	}
}

func convertState(bulbState *lifx.BulbState) *devices.LightDeviceState {

	state := &devices.LightDeviceState{}

	onOff := int(bulbState.Power) > 0
	state.OnOff = &onOff

	brightness := float64(bulbState.Brightness) / math.MaxUint16
	state.Brightness = &brightness

	color := &channels.ColorState{}
	if bulbState.Saturation == 0 {
		color.Mode = "temperature"

		temperature := float64(bulbState.Kelvin)
		color.Temperature = &temperature

	} else {
		color.Mode = "hue"

		hue := float64(bulbState.Hue) / float64(math.MaxUint16)
		color.Hue = &hue

		saturation := float64(bulbState.Saturation) / float64(math.MaxUint16)
		color.Saturation = &saturation
	}

	state.Color = color

	// TODO use the state.Visible to indicate whether a globe is online/offline at the moment

	return state
}

// this function publishes information from the sensors on the lifx bulb
// atm this is limited to illuminance
func (d *LifxDriver) publishSensorData() {
	c := time.Tick(30 * time.Second)
	for _ = range c {
		for _, bulb := range d.client.GetBulbs() {
			d.client.GetAmbientLight(bulb)
		}
	}
}

//---------------------------------------------------------------[Utils]----------------------------------------------------------------

var seenlights []*lifx.Bulb

func isUnique(newbulb *lifx.Bulb) bool {
	ret := true
	for _, bulb := range seenlights {
		if bulb.LifxAddress == newbulb.LifxAddress {
			ret = false
		}
	}
	return ret
}
<|endoftext|>