<commit_before>package services_state

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

	"github.com/hashicorp/memberlist"
	"github.com/newrelic/bosun/service"
)

// services_state handles all of the eventual-consistency mechanisms for
// service discovery state. The ServicesState struct has a mapping of
// servers to Service lists and manages the lifecycle.

const (
	TOMBSTONE_LIFESPAN = 3 * time.Hour // How long we keep tombstones around
	TOMBSTONE_COUNT = 10               // Send tombstones at 1 per second 10 times
	ALIVE_LIFESPAN = 20 * time.Second  // Down if not heard from in 20 seconds
	SLEEP_INTERVAL = 2 * time.Second   // Sleep between local service checks
)

// Holds the state about one server in our cluster
type Server struct {
	Name string
	Services map[string]*service.Service
	LastUpdated time.Time
}

// Returns a pointer to a properly configured Server
func NewServer(name string) *Server {
	var server Server
	server.Name = name
	// Pre-create for 5 services per host
	server.Services = make(map[string]*service.Service, 5)
	server.LastUpdated = time.Unix(0, 0)

	return &server
}

// Holds the state about all the servers in the cluster
type ServicesState struct {
	Servers map[string]*Server
	HostnameFn func() (string, error)
	Broadcasts chan [][]byte
}

// Returns a pointer to a properly configured ServicesState
func NewServicesState() *ServicesState {
	var state ServicesState
	state.Servers    = make(map[string]*Server, 5)
	state.HostnameFn = os.Hostname
	state.Broadcasts = make(chan [][]byte)
	return &state
}

// Return a Marshaled/Encoded byte array that can be deocoded with
// services_state.Decode()
func (state *ServicesState) Encode() []byte {
	jsonData, err := json.Marshal(state.Servers)
	if err != nil {
		log.Println("ERROR: Failed to Marshal state")
		return []byte{}
	}

	return jsonData
}

// Shortcut for checking if the Servers map has an entry for this
// hostname.
func (state *ServicesState) HasServer(hostname string) bool {
	if state.Servers[hostname] != nil {
		return true
	}

	return false
}

// A server has left the cluster, so tombstone all of its records
func (state *ServicesState) ExpireServer(hostname string, quit chan bool) {
	if !state.HasServer(hostname) {
		log.Printf("No records to expire for %s\n", hostname)
		return
	}

	log.Printf("Expiring %s\n", hostname)

	tombstones := make([][]byte, 0, len(state.Servers[hostname].Services))

	for _, svc := range state.Servers[hostname].Services {
		svc.Tombstone()

		encoded, err := svc.Encode()
		if err != nil {
			log.Printf("ERROR encoding service: (%s)", err.Error())
			continue
		}

		tombstones = append(tombstones, encoded)
	}

	state.SendTombstones(tombstones, quit)
}

// Take a service and merge it into our state. Correctly handle
// timestamps so we only add things newer than what we already
// know about.
func (state *ServicesState) AddServiceEntry(entry service.Service) {

	if !state.HasServer(entry.Hostname) {
		state.Servers[entry.Hostname] = NewServer(entry.Hostname)
	}

	server := state.Servers[entry.Hostname]
	// Only apply changes that are newer
	if server.Services[entry.ID] == nil ||
			entry.Invalidates(server.Services[entry.ID]) {
		server.Services[entry.ID] = &entry
		server.LastUpdated = entry.Updated
	}
}

// Merge a complete state struct into this one. Usually used on
// node startup and during anti-entropy operations.
func (state *ServicesState) Merge(otherState *ServicesState) {
	for _, server := range otherState.Servers {
		for _, service := range server.Services {
			state.AddServiceEntry(*service)
		}
	}
}

// Pretty-print(ish) a services state struct so a human can read
// it on the terminal. Makes for awesome web apps.
func (state *ServicesState) Format(list *memberlist.Memberlist) string {
	var output string

	output += "Services ------------------------------\n"
	for hostname, server := range state.Servers {
		output += fmt.Sprintf("  %s: (%s)\n", hostname, server.LastUpdated.String())
		for _, service := range server.Services {
			output += service.Format()
		}
		output += "\n"
	}

	// Don't show member list
	if list == nil {
		return output
	}

	output += "\nCluster Hosts -------------------------\n"
	for _, host := range list.Members() {
		output += fmt.Sprintf("    %s\n", host.Name)
	}

	output += "---------------------------------------"

	return output
}

// Print the formatted struct
func (state *ServicesState) Print(list *memberlist.Memberlist) {
	log.Println(state.Format(list))
}

// Loops forever, keeping transmitting info about our containers
// on the broadcast channel. Intended to run as a background goroutine.
func (state *ServicesState) BroadcastServices(fn func() []service.Service, quit chan bool) {
	for ;; {
		containerList := fn()
		prepared := make([][]byte, 0, len(containerList))

		for _, container := range containerList {
			state.AddServiceEntry(container)
			encoded, err := container.Encode()
			if err != nil {
				log.Printf("ERROR encoding container: (%s)", err.Error())
				continue
			}

			prepared = append(prepared, encoded)
		}

		if len(prepared) > 0 {
			state.Broadcasts <- prepared // Put it on the wire
		} else {
			// We expect there to always be _something_ in the channel
			// once we've run.
			state.Broadcasts <- nil
		}

		// Now that we're finished, see if we're supposed to exit
		select {
			case <- quit:
				return
			default:
		}

		time.Sleep(SLEEP_INTERVAL)
	}
}

func (state *ServicesState) SendTombstones(tombstones [][]byte, quit chan bool) {
	state.Broadcasts <- tombstones // Put it on the wire

	// Announce these every second for awhile
	go func() {
		for i := 0; i < TOMBSTONE_COUNT; i++ {
			state.Broadcasts <- tombstones

			select {
			case <- quit:
				return
			default:
			}

			time.Sleep(1 * time.Second)
		}
	}()
}

func (state *ServicesState) BroadcastTombstones(fn func() []service.Service, quit chan bool) {
	hostname, _ := state.HostnameFn()
	propagateQuit := make(chan bool)

	for ;; {
		containerList := fn()
		// Tell people about our dead services
		otherTombstones := state.TombstoneOthersServices()
		tombstones      := state.TombstoneServices(hostname, containerList)

		tombstones = append(tombstones, otherTombstones...)

		if tombstones != nil && len(tombstones) > 0 {
			state.SendTombstones(tombstones, propagateQuit)
		} else {
			// We expect there to always be _something_ in the channel
			// once we've run.
			state.Broadcasts <- nil
		}

		// Now that we're finished, see if we're supposed to exit
		select {
			case <- quit:
				propagateQuit <- true
				return
			default:
		}

		time.Sleep(SLEEP_INTERVAL)
	}
}

func (state *ServicesState) TombstoneOthersServices() [][]byte {
	result := make([][]byte, 0, 1)

	// Manage tombstone life so we don't keep them forever. We have to do this
	// even for hosts that aren't running services now, because they might have
	// been. Make sure we don't keep alive services around for very much
	// time at all.
	state.EachService(func(hostname *string, id *string, svc *service.Service) {
		if svc.IsTombstone() &&
				svc.Updated.Before(time.Now().UTC().Add(0 - TOMBSTONE_LIFESPAN)) {
			delete(state.Servers[*hostname].Services, *id)
			// If this is the last service, remove the server
			if len(state.Servers[*hostname].Services) < 1 {
				delete(state.Servers, *hostname)
			}
		}

		if svc.IsAlive() &&
				svc.Updated.Before(time.Now().UTC().Add(0 - ALIVE_LIFESPAN)) {

			log.Printf("Found expired service %s from %s, tombstoning",
				svc.Name, svc.Hostname,
			)

			// Because we don't know that other hosts haven't gotten a newer
			// message that we missed, we'll tombstone them with the original
			// timestamp + 1 second. This way we don't invalidate newer records
			// we didn't see. This might happen when any node is removed from
			// cluster and re-joins, for example. So we can't use svc.Tombstone()
			// which updates the timestamp to Now().UTC()
			svc.Status = service.TOMBSTONE
			svc.Updated = svc.Updated.Add(time.Second)

			encoded, err := svc.Encode()

			if err != nil {
				log.Printf("ERROR encoding container: (%s)", err.Error())
			}

			result = append(result, encoded)
		}
	})

	return result
}

func (state *ServicesState) TombstoneServices(hostname string, containerList []service.Service) [][]byte {
	// Build a map from the list first
	mapping := makeServiceMapping(containerList)

	if !state.HasServer(hostname) {
		println("TombstoneServices(): New host or not running services, skipping.")
		return nil
	}

	result := make([][]byte, 0, len(containerList))

	// Copy this so we can change the real list in the loop
	services := state.Servers[hostname].Services

	// Tombstone our own services that went away
	for id, svc := range services {
		if mapping[id] == nil && svc.Status == service.ALIVE {
			log.Printf("Tombstoning %s\n", svc.ID)
			svc.Tombstone()
			// Tombstone each record twice to help with receipt
			// TODO do we need to do this now that we send them 10 times?
			encoded, err := svc.Encode()
			if err != nil {
				log.Printf("ERROR encoding container: (%s)", err.Error())
			}

			for i := 0; i < 2; i++ {
				result = append(result, encoded)
			}
		}
	}

	return result
}

func (state *ServicesState) EachService(fn func(hostname *string, serviceId *string, svc *service.Service)) {
	for hostname, _ := range state.Servers {
		for id, svc := range state.Servers[hostname].Services {
			fn(&hostname, &id, svc)
		}
	}
}
func (state *ServicesState) ByService() map[string]map[string]*service.Service {
	serviceMap := make(map[string]map[string]*service.Service)

	state.EachService(
		func(hostname *string, serviceId *string, svc *service.Service) {
			if _, ok := serviceMap[svc.Name]; !ok {
				serviceMap[svc.Name] = make(map[string]*service.Service)
			}
			serviceMap[svc.Name][*serviceId] = svc
		},
	)

	return serviceMap
}

func makeServiceMapping(containerList []service.Service) map[string]*service.Service {
	mapping := make(map[string]*service.Service, len(containerList))
	for _, container := range containerList {
		mapping[container.ID] = &container
	}

	return mapping
}

// Take a byte slice and return a properly reconstituted state struct
func Decode(data []byte) (*ServicesState, error) {
	newState := NewServicesState()
	err := json.Unmarshal(data, &newState.Servers)
	if err != nil {
		log.Printf("Error decoding state! (%s)", err.Error())
	}

	return newState, err
}
<commit_msg>More small refactors.<commit_after>package services_state

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

	"github.com/hashicorp/memberlist"
	"github.com/newrelic/bosun/service"
)

// services_state handles all of the eventual-consistency mechanisms for
// service discovery state. The ServicesState struct has a mapping of
// servers to Service lists and manages the lifecycle.

const (
	TOMBSTONE_LIFESPAN = 3 * time.Hour // How long we keep tombstones around
	TOMBSTONE_COUNT = 10               // Send tombstones at 1 per second 10 times
	ALIVE_LIFESPAN = 20 * time.Second  // Down if not heard from in 20 seconds
	SLEEP_INTERVAL = 2 * time.Second   // Sleep between local service checks
)

// Holds the state about one server in our cluster
type Server struct {
	Name string
	Services map[string]*service.Service
	LastUpdated time.Time
}

// Returns a pointer to a properly configured Server
func NewServer(name string) *Server {
	var server Server
	server.Name = name
	// Pre-create for 5 services per host
	server.Services = make(map[string]*service.Service, 5)
	server.LastUpdated = time.Unix(0, 0)

	return &server
}

// Holds the state about all the servers in the cluster
type ServicesState struct {
	Servers map[string]*Server
	HostnameFn func() (string, error)
	Broadcasts chan [][]byte
}

// Returns a pointer to a properly configured ServicesState
func NewServicesState() *ServicesState {
	var state ServicesState
	state.Servers    = make(map[string]*Server, 5)
	state.HostnameFn = os.Hostname
	state.Broadcasts = make(chan [][]byte)
	return &state
}

// Return a Marshaled/Encoded byte array that can be deocoded with
// services_state.Decode()
func (state *ServicesState) Encode() []byte {
	jsonData, err := json.Marshal(state.Servers)
	if err != nil {
		log.Println("ERROR: Failed to Marshal state")
		return []byte{}
	}

	return jsonData
}

// Shortcut for checking if the Servers map has an entry for this
// hostname.
func (state *ServicesState) HasServer(hostname string) bool {
	if state.Servers[hostname] != nil {
		return true
	}

	return false
}

// A server has left the cluster, so tombstone all of its records
func (state *ServicesState) ExpireServer(hostname string, quit chan bool) {
	if !state.HasServer(hostname) {
		log.Printf("No records to expire for %s\n", hostname)
		return
	}

	log.Printf("Expiring %s\n", hostname)

	tombstones := make([][]byte, 0, len(state.Servers[hostname].Services))

	for _, svc := range state.Servers[hostname].Services {
		svc.Tombstone()

		encoded, err := svc.Encode()
		if err != nil {
			log.Printf("ERROR encoding service: (%s)", err.Error())
			continue
		}

		tombstones = append(tombstones, encoded)
	}

	state.SendTombstones(tombstones, quit)
}

// Take a service and merge it into our state. Correctly handle
// timestamps so we only add things newer than what we already
// know about.
func (state *ServicesState) AddServiceEntry(entry service.Service) {

	if !state.HasServer(entry.Hostname) {
		state.Servers[entry.Hostname] = NewServer(entry.Hostname)
	}

	server := state.Servers[entry.Hostname]
	// Only apply changes that are newer
	if server.Services[entry.ID] == nil ||
			entry.Invalidates(server.Services[entry.ID]) {
		server.Services[entry.ID] = &entry
		server.LastUpdated = entry.Updated
	}
}

// Merge a complete state struct into this one. Usually used on
// node startup and during anti-entropy operations.
func (state *ServicesState) Merge(otherState *ServicesState) {
	for _, server := range otherState.Servers {
		for _, service := range server.Services {
			state.AddServiceEntry(*service)
		}
	}
}

// Pretty-print(ish) a services state struct so a human can read
// it on the terminal. Makes for awesome web apps.
func (state *ServicesState) Format(list *memberlist.Memberlist) string {
	var output string

	output += "Services ------------------------------\n"
	for hostname, server := range state.Servers {
		output += fmt.Sprintf("  %s: (%s)\n", hostname, server.LastUpdated.String())
		for _, service := range server.Services {
			output += service.Format()
		}
		output += "\n"
	}

	// Don't show member list
	if list == nil {
		return output
	}

	output += "\nCluster Hosts -------------------------\n"
	for _, host := range list.Members() {
		output += fmt.Sprintf("    %s\n", host.Name)
	}

	output += "---------------------------------------"

	return output
}

// Print the formatted struct
func (state *ServicesState) Print(list *memberlist.Memberlist) {
	log.Println(state.Format(list))
}

// Loops forever, keeping transmitting info about our containers
// on the broadcast channel. Intended to run as a background goroutine.
func (state *ServicesState) BroadcastServices(fn func() []service.Service, quit chan bool) {
	for ;; {
		containerList := fn()
		prepared := make([][]byte, 0, len(containerList))

		for _, container := range containerList {
			state.AddServiceEntry(container)
			encoded, err := container.Encode()
			if err != nil {
				log.Printf("ERROR encoding container: (%s)", err.Error())
				continue
			}

			prepared = append(prepared, encoded)
		}

		if len(prepared) > 0 {
			state.Broadcasts <- prepared // Put it on the wire
		} else {
			// We expect there to always be _something_ in the channel
			// once we've run.
			state.Broadcasts <- nil
		}

		// Now that we're finished, see if we're supposed to exit
		select {
			case <- quit:
				return
			default:
		}

		time.Sleep(SLEEP_INTERVAL)
	}
}

// Actually transmit an encoded tombstone record into the channel. Runs a
// background goroutine that continues the broadcast for 10 seconds so we
// have a pretty good idea that it was delivered.
func (state *ServicesState) SendTombstones(tombstones [][]byte, quit chan bool) {
	state.Broadcasts <- tombstones // Put it on the wire

	// Announce these every second for awhile
	go func() {
		for i := 0; i < TOMBSTONE_COUNT; i++ {
			state.Broadcasts <- tombstones

			select {
			case <- quit:
				return
			default:
			}

			time.Sleep(1 * time.Second)
		}
	}()
}

func (state *ServicesState) BroadcastTombstones(fn func() []service.Service, quit chan bool) {
	hostname, _ := state.HostnameFn()
	propagateQuit := make(chan bool)

	for ;; {
		containerList := fn()
		// Tell people about our dead services
		otherTombstones := state.TombstoneOthersServices()
		tombstones      := state.TombstoneServices(hostname, containerList)

		tombstones = append(tombstones, otherTombstones...)

		if tombstones != nil && len(tombstones) > 0 {
			state.SendTombstones(tombstones, propagateQuit)
		} else {
			// We expect there to always be _something_ in the channel
			// once we've run.
			state.Broadcasts <- nil
		}

		// Now that we're finished, see if we're supposed to exit
		select {
			case <- quit:
				propagateQuit <- true
				return
			default:
		}

		time.Sleep(SLEEP_INTERVAL)
	}
}

func (state *ServicesState) TombstoneOthersServices() [][]byte {
	result := make([][]byte, 0, 1)

	// Manage tombstone life so we don't keep them forever. We have to do this
	// even for hosts that aren't running services now, because they might have
	// been. Make sure we don't keep alive services around for very much
	// time at all.
	state.EachService(func(hostname *string, id *string, svc *service.Service) {
		if svc.IsTombstone() &&
				svc.Updated.Before(time.Now().UTC().Add(0 - TOMBSTONE_LIFESPAN)) {
			delete(state.Servers[*hostname].Services, *id)
			// If this is the last service, remove the server
			if len(state.Servers[*hostname].Services) < 1 {
				delete(state.Servers, *hostname)
			}
		}

		if svc.IsAlive() &&
				svc.Updated.Before(time.Now().UTC().Add(0 - ALIVE_LIFESPAN)) {

			log.Printf("Found expired service %s from %s, tombstoning",
				svc.Name, svc.Hostname,
			)

			// Because we don't know that other hosts haven't gotten a newer
			// message that we missed, we'll tombstone them with the original
			// timestamp + 1 second. This way we don't invalidate newer records
			// we didn't see. This might happen when any node is removed from
			// cluster and re-joins, for example. So we can't use svc.Tombstone()
			// which updates the timestamp to Now().UTC()
			svc.Status = service.TOMBSTONE
			svc.Updated = svc.Updated.Add(time.Second)

			encoded, err := svc.Encode()

			if err != nil {
				log.Printf("ERROR encoding container: (%s)", err.Error())
			}

			result = append(result, encoded)
		}
	})

	return result
}

func (state *ServicesState) TombstoneServices(hostname string, containerList []service.Service) [][]byte {

	if !state.HasServer(hostname) {
		println("TombstoneServices(): New host or not running services, skipping.")
		return nil
	}
	// Build a map from the list first
	mapping := makeServiceMapping(containerList)

	result := make([][]byte, 0, len(containerList))

	// Copy this so we can change the real list in the loop
	services := state.Servers[hostname].Services

	// Tombstone our own services that went away
	for id, svc := range services {
		if mapping[id] == nil && svc.Status == service.ALIVE {
			log.Printf("Tombstoning %s\n", svc.ID)
			svc.Tombstone()
			// Tombstone each record twice to help with receipt
			encoded, err := svc.Encode()
			if err != nil {
				log.Printf("ERROR encoding container: (%s)", err.Error())
			}

			for i := 0; i < 2; i++ {
				result = append(result, encoded)
			}
		}
	}

	return result
}

func (state *ServicesState) EachService(fn func(hostname *string, serviceId *string, svc *service.Service)) {
	for hostname, _ := range state.Servers {
		for id, svc := range state.Servers[hostname].Services {
			fn(&hostname, &id, svc)
		}
	}
}
func (state *ServicesState) ByService() map[string]map[string]*service.Service {
	serviceMap := make(map[string]map[string]*service.Service)

	state.EachService(
		func(hostname *string, serviceId *string, svc *service.Service) {
			if _, ok := serviceMap[svc.Name]; !ok {
				serviceMap[svc.Name] = make(map[string]*service.Service)
			}
			serviceMap[svc.Name][*serviceId] = svc
		},
	)

	return serviceMap
}

func makeServiceMapping(containerList []service.Service) map[string]*service.Service {
	mapping := make(map[string]*service.Service, len(containerList))
	for _, container := range containerList {
		mapping[container.ID] = &container
	}

	return mapping
}

// Take a byte slice and return a properly reconstituted state struct
func Decode(data []byte) (*ServicesState, error) {
	newState := NewServicesState()
	err := json.Unmarshal(data, &newState.Servers)
	if err != nil {
		log.Printf("Error decoding state! (%s)", err.Error())
	}

	return newState, err
}
<|endoftext|>


<commit_before>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package track

import (
	"html"
	"strings"

	"github.com/bogem/vnehm/util"
)

type Track struct {
	artist string
	title  string

	// Properties needed for JSON unmarshalling
	JArtist   string
	JDuration int64
	JID       int64
	JTitle    string
	JURL      string
}

func (t *Track) Artist() string {
	if t.artist == "" {
		t.artist, t.title = t.name()
	}
	return t.artist
}

func (t Track) Duration() string {
	return util.DurationString(util.ParseDuration(int(t.JDuration)))
}

func (t Track) Filename() string {
	// Replace all filesystem non-friendly runes with the underscore
	toReplace := "/\\"
	replaceRunes := func(r rune) rune {
		if strings.ContainsRune(toReplace, r) {
			return '_'
		}
		return r
	}

	return strings.Map(replaceRunes, t.Fullname()) + ".mp3"
}

func (t Track) Fullname() string {
	return t.Artist() + " – " + t.Title()
}

func (t Track) ID() int64 {
	return t.JID
}

// name splits track's title to artist and title if there is one of separators
// in there and unescape them.
// E.g. if track has title "Michael Jackson - Thriller" then this function will
// return as first string "Michael Jackson" and as second string "Thriller".
func (t Track) name() (string, string) {
	artist := t.JArtist
	title := t.JTitle
	separators := [...]string{" - ", " ~ ", " – "}
	for _, sep := range separators {
		if strings.Contains(t.JTitle, sep) {
			splitted := strings.SplitN(t.JTitle, sep, 2)
			artist = splitted[0]
			title = splitted[1]
		}
	}
	return html.UnescapeString(artist), html.UnescapeString(title)
}

func (t *Track) Title() string {
	if t.title == "" {
		t.artist, t.title = t.name()
	}
	return t.title
}

func (t Track) URL() string {
	return t.JURL
}
<commit_msg>Add deleting of leading and trailing whitespaces in track name<commit_after>// Copyright 2016 Albert Nigmatzianov. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package track

import (
	"html"
	"strings"

	"github.com/bogem/vnehm/util"
)

type Track struct {
	artist string
	title  string

	// Properties needed for JSON unmarshalling
	JArtist   string
	JDuration int64
	JID       int64
	JTitle    string
	JURL      string
}

func (t *Track) Artist() string {
	if t.artist == "" {
		t.artist, t.title = t.name()
	}
	return t.artist
}

func (t Track) Duration() string {
	return util.DurationString(util.ParseDuration(int(t.JDuration)))
}

func (t Track) Filename() string {
	// Replace all filesystem non-friendly runes with the underscore
	toReplace := "/\\"
	replaceRunes := func(r rune) rune {
		if strings.ContainsRune(toReplace, r) {
			return '_'
		}
		return r
	}

	return strings.Map(replaceRunes, t.Fullname()) + ".mp3"
}

func (t Track) Fullname() string {
	return t.Artist() + " – " + t.Title()
}

func (t Track) ID() int64 {
	return t.JID
}

// name splits track's title to artist and title if there is one of separators
// in there and unescape them.
// E.g. if track has title "Michael Jackson - Thriller" then this function will
// return as first string "Michael Jackson" and as second string "Thriller".
func (t Track) name() (string, string) {
	artist := html.UnescapeString(strings.TrimSpace(t.JArtist))
	title := html.UnescapeString(strings.TrimSpace(t.JTitle))
	separators := [...]string{" - ", " ~ ", " – "}
	for _, sep := range separators {
		if strings.Contains(t.JTitle, sep) {
			splitted := strings.SplitN(t.JTitle, sep, 2)
			artist = splitted[0]
			title = splitted[1]
		}
	}
	return artist, title
}

func (t *Track) Title() string {
	if t.title == "" {
		t.artist, t.title = t.name()
	}
	return t.title
}

func (t Track) URL() string {
	return t.JURL
}
<|endoftext|>


<commit_before>// Copyright 2016 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"os"
	"strconv"

	"github.com/FactomProject/factom"
)

// newtx creates a new transaction in the wallet.
var newtx = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli newtx TXNAME"
	cmd.description = "Create a new transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 1 {
			fmt.Println(cmd.helpMsg)
			return
		}
		if err := factom.NewTransaction(args[0]); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("newtx", cmd)
	return cmd
}()

// rmtx removes a transaction in the wallet.
var rmtx = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli rmtx TXNAME"
	cmd.description = "Remove a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 1 {
			fmt.Println(cmd.helpMsg)
			return
		}
		if err := factom.DeleteTransaction(args[0]); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("rmtx", cmd)
	return cmd
}()

// listtxs lists the working transactions in the wallet.
var listtxs = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli listtxs"
	cmd.description = "List current working transactions in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		txs, err := factom.ListTransactions()
		if err != nil {
			errorln(err)
			return
		}
		for _, tx := range txs {
			fmt.Println("{")
			fmt.Println("	Name:", tx.Name)
			fmt.Println("	TxID:", tx.TxID)
			fmt.Println("	TotalInputs:", float64(tx.TotalInputs)/100000000)
			fmt.Println("	TotalOutputs:", float64(tx.TotalOutputs)/100000000)
			fmt.Println("	TotalECOutputs:", float64(tx.TotalECOutputs)/100000000)
			feesPaid := tx.TotalInputs - (tx.TotalOutputs + tx.TotalECOutputs)
			fmt.Println("	FeesPaid:", float64(feesPaid)/100000000)
			fmt.Println("	RawTransaction:", tx.RawTransaction)
			fmt.Println("}")
		}
	}
	help.Add("listtxs", cmd)
	return cmd
}()

// addtxinput adds a factoid input to a transaction in the wallet.
var addtxinput = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli addtxinput TXNAME ADDRESS AMOUNT"
	cmd.description = "Add a Factoid input to a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 3 {
			fmt.Println(cmd.helpMsg)
			return
		}
		var amt uint64
		if i, err := strconv.ParseFloat(args[2], 64); err != nil {
			errorln(err)
		} else if i < 0 {
			errorln("AMMOUNT may not be less than 0")
		} else {
			amt = uint64(i * 1e8)
		}
		if err := factom.AddTransactionInput(args[0], args[1], amt); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("addtxinput", cmd)
	return cmd
}()

// addtxoutput adds a factoid output to a transaction in the wallet.
var addtxoutput = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli addtxoutput TXNAME ADDRESS AMOUNT"
	cmd.description = "Add a Factoid output to a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 3 {
			fmt.Println(cmd.helpMsg)
			return
		}
		var amt uint64
		if i, err := strconv.ParseFloat(args[2], 64); err != nil {
			errorln(err)
		} else if i < 0 {
			errorln("AMMOUNT may not be less than 0")
		} else {
			amt = uint64(i * 1e8)
		}
		if err := factom.AddTransactionOutput(args[0], args[1], amt); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("addtxoutput", cmd)
	return cmd
}()

// addtxecoutput adds an entry credit output to a transaction in the wallet.
var addtxecoutput = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli addtxecoutput TXNAME ADDRESS AMOUNT"
	cmd.description = "Add an Entry Credit output to a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 3 {
			fmt.Println(cmd.helpMsg)
			return
		}
		var amt uint64
		if i, err := strconv.ParseFloat(args[2], 64); err != nil {
			errorln(err)
		} else if i < 0 {
			errorln("AMMOUNT may not be less than 0")
		} else {
			amt = uint64(i * 1e8)
		}
		if err := factom.AddTransactionECOutput(args[0], args[1], amt); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("addtxecoutput", cmd)
	return cmd
}()

// addtxfee adds an entry credit output to a transaction in the wallet.
var addtxfee = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli addtxfee TXNAME ADDRESS"
	cmd.description = "Add the transaction fee to an input of a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 2 {
			fmt.Println(cmd.helpMsg)
			return
		}
		if err := factom.AddTransactionFee(args[0], args[1]); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("addtxfee", cmd)
	return cmd
}()

// subtxfee adds an entry credit output to a transaction in the wallet.
var subtxfee = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli subtxfee TXNAME ADDRESS"
	cmd.description = "Subtract the transaction fee from an output of a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 2 {
			fmt.Println(cmd.helpMsg)
			return
		}
		if err := factom.SubTransactionFee(args[0], args[1]); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("subtxfee", cmd)
	return cmd
}()

// signtx signs a transaction in the wallet.
var signtx = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli signtx TXNAME"
	cmd.description = "Sign a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 1 {
			fmt.Println(cmd.helpMsg)
			return
		}
		if err := factom.SignTransaction(args[0]); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("signtx", cmd)
	return cmd
}()

// composetx composes the signed json rpc object to make a transaction against factomd
var composetx = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli composetx TXNAME"
	cmd.description = "Compose a wallet transaction into a json rpc object"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 1 {
			fmt.Println(cmd.helpMsg)
			return
		}
		p, err := factom.ComposeTransaction(args[0])
		if err != nil {
			errorln(err)
			return
		}
		fmt.Println(string(p))
	}
	help.Add("composetx", cmd)
	return cmd
}()

// sendtx composes and sends the signed transaction to factomd
var sendtx = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli sendtx TXNAME"
	cmd.description = "Send a Transaction to Factom"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 1 {
			fmt.Println(cmd.helpMsg)
			return
		}
		t, err := factom.SendTransaction(args[0])
		if err != nil {
			errorln(err)
			return
		}
		fmt.Println("TxID:", t)
	}
	help.Add("sendtx", cmd)
	return cmd
}()

// sendfct sends factoids between 2 addresses
var sendfct = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli sendfct FROMADDRESS TOADDRESS AMMOUNT"
	cmd.description = "Send Factoids between 2 addresses"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 3 {
			fmt.Println(cmd.helpMsg)
			return
		}
		var amt uint64
		if i, err := strconv.ParseFloat(args[2], 64); err != nil {
			errorln(err)
		} else if i < 0 {
			errorln("AMMOUNT may not be less than 0")
		} else {
			amt = uint64(i * 1e8)
		}
		t, err := factom.SendFactoid(args[0], args[1], amt)
		if err != nil {
			errorln(err)
			return
		}
		fmt.Println("TxID:", t)
	}
	help.Add("sendfct", cmd)
	return cmd
}()

// buyec sends factoids between 2 addresses
var buyec = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli buyec FCTADDRESS ECADDRESS ECAMMOUNT"
	cmd.description = "Buy entry credits"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 3 {
			fmt.Println(cmd.helpMsg)
			return
		}
		var amt uint64
		if i, err := strconv.Atoi(args[2]); err != nil {
			errorln(err)
			return
		} else if i < 0 {
			errorln("AMMOUNT may not be less than 0")
			return
		} else {
			rate, err := factom.GetRate()
			if err != nil {
				errorln(err)
			}
			amt = uint64(i) * rate
		}
		t, err := factom.BuyEC(args[0], args[1], amt)
		if err != nil {
			errorln(err)
			return
		}
		fmt.Println("TxID:", t)
	}
	help.Add("buyec", cmd)
	return cmd
}()
<commit_msg>added dns address resolution to balance and addoutput commands<commit_after>// Copyright 2016 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"os"
	"strconv"

	"github.com/FactomProject/factom"
)

// newtx creates a new transaction in the wallet.
var newtx = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli newtx TXNAME"
	cmd.description = "Create a new transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 1 {
			fmt.Println(cmd.helpMsg)
			return
		}
		if err := factom.NewTransaction(args[0]); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("newtx", cmd)
	return cmd
}()

// rmtx removes a transaction in the wallet.
var rmtx = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli rmtx TXNAME"
	cmd.description = "Remove a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 1 {
			fmt.Println(cmd.helpMsg)
			return
		}
		if err := factom.DeleteTransaction(args[0]); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("rmtx", cmd)
	return cmd
}()

// listtxs lists the working transactions in the wallet.
var listtxs = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli listtxs"
	cmd.description = "List current working transactions in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		txs, err := factom.ListTransactions()
		if err != nil {
			errorln(err)
			return
		}
		for _, tx := range txs {
			fmt.Println("{")
			fmt.Println("	Name:", tx.Name)
			fmt.Println("	TxID:", tx.TxID)
			fmt.Println("	TotalInputs:", float64(tx.TotalInputs)/100000000)
			fmt.Println("	TotalOutputs:", float64(tx.TotalOutputs)/100000000)
			fmt.Println("	TotalECOutputs:", float64(tx.TotalECOutputs)/100000000)
			feesPaid := tx.TotalInputs - (tx.TotalOutputs + tx.TotalECOutputs)
			fmt.Println("	FeesPaid:", float64(feesPaid)/100000000)
			fmt.Println("	RawTransaction:", tx.RawTransaction)
			fmt.Println("}")
		}
	}
	help.Add("listtxs", cmd)
	return cmd
}()

// addtxinput adds a factoid input to a transaction in the wallet.
var addtxinput = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli addtxinput TXNAME ADDRESS AMOUNT"
	cmd.description = "Add a Factoid input to a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 3 {
			fmt.Println(cmd.helpMsg)
			return
		}
		var amt uint64
		if i, err := strconv.ParseFloat(args[2], 64); err != nil {
			errorln(err)
		} else if i < 0 {
			errorln("AMMOUNT may not be less than 0")
		} else {
			amt = uint64(i * 1e8)
		}
		if err := factom.AddTransactionInput(args[0], args[1], amt); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("addtxinput", cmd)
	return cmd
}()

// addtxoutput adds a factoid output to a transaction in the wallet.
var addtxoutput = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli addtxoutput TXNAME ADDRESS AMOUNT"
	cmd.description = "Add a Factoid output to a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		var res = flag.Bool("r", false, "resolve dns address")
		flag.Parse()
		args = flag.Args()

		if len(args) != 3 {
			fmt.Println(cmd.helpMsg)
			return
		}
		var amt uint64
		if i, err := strconv.ParseFloat(args[2], 64); err != nil {
			errorln(err)
		} else if i < 0 {
			errorln("AMMOUNT may not be less than 0")
		} else {
			amt = uint64(i * 1e8)
		}

		out := args[1]
		if *res {
			if f, _, err := factom.ResolveDnsName(args[1]); err != nil {
				errorln(err)
				return
			} else if f == "" {
				errorln("could not resolve factoid address")
			} else {
				out = f
			}
		}
		if err := factom.AddTransactionOutput(args[0], out, amt); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("addtxoutput", cmd)
	return cmd
}()

// addtxecoutput adds an entry credit output to a transaction in the wallet.
var addtxecoutput = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli addtxecoutput TXNAME ADDRESS AMOUNT"
	cmd.description = "Add an Entry Credit output to a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		var res = flag.Bool("r", false, "resolve dns address")
		flag.Parse()
		args = flag.Args()

		if len(args) != 3 {
			fmt.Println(cmd.helpMsg)
			return
		}
		var amt uint64
		if i, err := strconv.ParseFloat(args[2], 64); err != nil {
			errorln(err)
		} else if i < 0 {
			errorln("AMMOUNT may not be less than 0")
		} else {
			amt = uint64(i * 1e8)
		}

		out := args[1]
		if *res {
			if _, e, err := factom.ResolveDnsName(args[1]); err != nil {
				errorln(err)
				return
			} else if e == "" {
				errorln("could not resolve entry credit address")
			} else {
				out = e
			}
		}
		if err := factom.AddTransactionECOutput(args[0], out, amt); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("addtxecoutput", cmd)
	return cmd
}()

// addtxfee adds an entry credit output to a transaction in the wallet.
var addtxfee = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli addtxfee TXNAME ADDRESS"
	cmd.description = "Add the transaction fee to an input of a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 2 {
			fmt.Println(cmd.helpMsg)
			return
		}
		if err := factom.AddTransactionFee(args[0], args[1]); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("addtxfee", cmd)
	return cmd
}()

// subtxfee adds an entry credit output to a transaction in the wallet.
var subtxfee = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli subtxfee TXNAME ADDRESS"
	cmd.description = "Subtract the transaction fee from an output of a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 2 {
			fmt.Println(cmd.helpMsg)
			return
		}
		if err := factom.SubTransactionFee(args[0], args[1]); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("subtxfee", cmd)
	return cmd
}()

// signtx signs a transaction in the wallet.
var signtx = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli signtx TXNAME"
	cmd.description = "Sign a transaction in the wallet"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 1 {
			fmt.Println(cmd.helpMsg)
			return
		}
		if err := factom.SignTransaction(args[0]); err != nil {
			errorln(err)
			return
		}
	}
	help.Add("signtx", cmd)
	return cmd
}()

// composetx composes the signed json rpc object to make a transaction against factomd
var composetx = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli composetx TXNAME"
	cmd.description = "Compose a wallet transaction into a json rpc object"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 1 {
			fmt.Println(cmd.helpMsg)
			return
		}
		p, err := factom.ComposeTransaction(args[0])
		if err != nil {
			errorln(err)
			return
		}
		fmt.Println(string(p))
	}
	help.Add("composetx", cmd)
	return cmd
}()

// sendtx composes and sends the signed transaction to factomd
var sendtx = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli sendtx TXNAME"
	cmd.description = "Send a Transaction to Factom"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 1 {
			fmt.Println(cmd.helpMsg)
			return
		}
		t, err := factom.SendTransaction(args[0])
		if err != nil {
			errorln(err)
			return
		}
		fmt.Println("TxID:", t)
	}
	help.Add("sendtx", cmd)
	return cmd
}()

// sendfct sends factoids between 2 addresses
var sendfct = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli sendfct FROMADDRESS TOADDRESS AMMOUNT"
	cmd.description = "Send Factoids between 2 addresses"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 3 {
			fmt.Println(cmd.helpMsg)
			return
		}
		var amt uint64
		if i, err := strconv.ParseFloat(args[2], 64); err != nil {
			errorln(err)
		} else if i < 0 {
			errorln("AMMOUNT may not be less than 0")
		} else {
			amt = uint64(i * 1e8)
		}
		t, err := factom.SendFactoid(args[0], args[1], amt)
		if err != nil {
			errorln(err)
			return
		}
		fmt.Println("TxID:", t)
	}
	help.Add("sendfct", cmd)
	return cmd
}()

// buyec sends factoids between 2 addresses
var buyec = func() *fctCmd {
	cmd := new(fctCmd)
	cmd.helpMsg = "factom-cli buyec FCTADDRESS ECADDRESS ECAMMOUNT"
	cmd.description = "Buy entry credits"
	cmd.execFunc = func(args []string) {
		os.Args = args
		flag.Parse()
		args = flag.Args()

		if len(args) != 3 {
			fmt.Println(cmd.helpMsg)
			return
		}
		var amt uint64
		if i, err := strconv.Atoi(args[2]); err != nil {
			errorln(err)
			return
		} else if i < 0 {
			errorln("AMMOUNT may not be less than 0")
			return
		} else {
			rate, err := factom.GetRate()
			if err != nil {
				errorln(err)
			}
			amt = uint64(i) * rate
		}
		t, err := factom.BuyEC(args[0], args[1], amt)
		if err != nil {
			errorln(err)
			return
		}
		fmt.Println("TxID:", t)
	}
	help.Add("buyec", cmd)
	return cmd
}()
<|endoftext|>


<commit_before>package controller

import (
	. "eaciit/wfdemo-git/library/core"
	. "eaciit/wfdemo-git/library/models"
	"eaciit/wfdemo-git/web/helper"
	// "sort"
	// "strings"
	// "time"

	"github.com/eaciit/dbox"
	"github.com/eaciit/knot/knot.v1"
	tk "github.com/eaciit/toolkit"
)

type WindFarmAnalysisController struct {
	App
}

func CreateWindFarmAnalysisController() *WindFarmAnalysisController {
	c := new(WindFarmAnalysisController)
	return c
}

func (c *WindFarmAnalysisController) GetDataByProject(k *knot.WebContext) interface{} {
	k.Config.OutputType = knot.OutputJson

	p := struct {
		Project string
	}{}

	e := k.GetPayload(&p)
	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	project := p.Project

	csr, e := DB().Connection.NewQuery().From(new(GWFAnalysisByProject).TableName()).
		Where(dbox.And(dbox.Eq("projectname", project))).Order("OrderNo").Cursor(nil)

	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	datas := make([]GWFAnalysisByProject, 0)
	e = csr.Fetch(&datas, 0, false)
	csr.Close()

	title := make([]string, 4)
	if len(datas) > 0 {
		d := datas[0]
		title[0] = "Rolling 12 Days
" + d.Roll12Days.DateText + ""
		title[1] = "Rolling 12 Weeks
" + d.Roll12Weeks.DateText + ""
		title[2] = "Rolling 12 Months
" + d.Roll12Months.DateText + ""
		title[3] = "Rolling 12 Quarters
" + d.Roll12Quarters.DateText + ""
	}

	datareturn := tk.M{}.Set("data", datas).Set("header", title)

	return helper.CreateResult(true, datareturn, "success")
}

func (c *WindFarmAnalysisController) GetDataByTurbine1(k *knot.WebContext) interface{} {
	k.Config.OutputType = knot.OutputJson

	p := struct {
		Project  string
		Turbines []interface{}
	}{}

	e := k.GetPayload(&p)
	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	project := p.Project

	tQry := make([]*dbox.Filter, 0)
	tQry = append(tQry, dbox.Eq("projectname", project))
	if len(p.Turbines) > 0 {
		tQry = append(tQry, dbox.In("turbine", p.Turbines...))
	}

	csr, e := DB().Connection.NewQuery().From(new(GWFAnalysisByTurbine1).TableName()).
		Where(dbox.And(tQry...)).Order([]string{"turbine", "orderno"}...).Cursor(nil)

	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	datas := make([]GWFAnalysisByTurbine1, 0)
	e = csr.Fetch(&datas, 0, false)
	csr.Close()

	title := make([]string, 4)
	if len(datas) > 0 {
		d := datas[0]
		title[0] = "Rolling 12 Days
" + d.Roll12Days.DateText + ""
		title[1] = "Rolling 12 Weeks
" + d.Roll12Weeks.DateText + ""
		title[2] = "Rolling 12 Months
" + d.Roll12Months.DateText + ""
		title[3] = "Rolling 12 Quarters
" + d.Roll12Quarters.DateText + ""
	}

	datareturn := tk.M{}.Set("data", datas).Set("header", title)

	return helper.CreateResult(true, datareturn, "success")
}

func (c *WindFarmAnalysisController) GetDataByTurbine2(k *knot.WebContext) interface{} {
	k.Config.OutputType = knot.OutputJson

	p := struct {
		Project  string
		Turbines []string
	}{}

	colors := []string{
		"#ED1C24", "#A3238E", "#00A65D", "#F58220", "#0066B3", "#5C2D91", "#FFF200", "#579835", "#CF3834", "#00B274", "#74489D",
		"#C06616", "#5565AF", "#CCBE00", "#390A5D", "#006D6F", "#65C295", "#F04E4D", "#407927", "#00599D", "#A09600", "#0D1F63",
		"#C38312", "#003D73", "#454FA1", "#BC312E",
	}

	e := k.GetPayload(&p)
	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	project := p.Project

	tQry := make([]*dbox.Filter, 0)
	tQry = append(tQry, dbox.Eq("projectname", project))
	if len(p.Turbines) > 0 {
		tQry = append(tQry, dbox.In("turbine", p.Turbines))
	} else {
		turbines := make([]TurbineMaster, 0)
		csrt, et := DB().Connection.NewQuery().From(new(TurbineMaster).TableName()).
			Where(dbox.And(dbox.Eq("project", project))).Order("turbineid").Cursor(nil)

		if et != nil {
			tk.Println(et.Error())
		}
		et = csrt.Fetch(&turbines, 0, false)
		csrt.Close()

		for _, t := range turbines {
			p.Turbines = append(p.Turbines, t.TurbineId)
		}
	}

	csr, e := DB().Connection.NewQuery().From(new(GWFAnalysisByTurbine2).TableName()).
		Where(dbox.And(tQry...)).Order("orderno").Cursor(nil)

	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	chartSeries := []tk.M{}
	chartSeries = append(chartSeries, tk.M{
		"field": "Average",
		"name":  "Average",
		"color": colors[0],
	})
	for idx, t := range p.Turbines {
		chartSeries = append(chartSeries, tk.M{
			"field": t,
			"name":  t,
			"color": colors[(idx + 1)],
		})
	}

	datas := make([]GWFAnalysisByTurbine2, 0)
	e = csr.Fetch(&datas, 0, false)
	csr.Close()

	retVal := tk.M{}.Set("ChartSeries", chartSeries).Set("ChartData", datas)

	return helper.CreateResult(true, retVal, "success")
}
<commit_msg>updating turbine name on wind farm analysis page<commit_after>package controller

import (
	. "eaciit/wfdemo-git/library/core"
	. "eaciit/wfdemo-git/library/models"
	"eaciit/wfdemo-git/web/helper"
	// "sort"
	// "strings"
	// "time"

	"github.com/eaciit/dbox"
	"github.com/eaciit/knot/knot.v1"
	tk "github.com/eaciit/toolkit"
)

type WindFarmAnalysisController struct {
	App
}

func CreateWindFarmAnalysisController() *WindFarmAnalysisController {
	c := new(WindFarmAnalysisController)
	return c
}

func (c *WindFarmAnalysisController) GetDataByProject(k *knot.WebContext) interface{} {
	k.Config.OutputType = knot.OutputJson

	p := struct {
		Project string
	}{}

	e := k.GetPayload(&p)
	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	project := p.Project

	csr, e := DB().Connection.NewQuery().From(new(GWFAnalysisByProject).TableName()).
		Where(dbox.And(dbox.Eq("projectname", project))).Order("OrderNo").Cursor(nil)

	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	datas := make([]GWFAnalysisByProject, 0)
	e = csr.Fetch(&datas, 0, false)
	csr.Close()

	title := make([]string, 4)
	if len(datas) > 0 {
		d := datas[0]
		title[0] = "Rolling 12 Days
" + d.Roll12Days.DateText + ""
		title[1] = "Rolling 12 Weeks
" + d.Roll12Weeks.DateText + ""
		title[2] = "Rolling 12 Months
" + d.Roll12Months.DateText + ""
		title[3] = "Rolling 12 Quarters
" + d.Roll12Quarters.DateText + ""
	}

	datareturn := tk.M{}.Set("data", datas).Set("header", title)

	return helper.CreateResult(true, datareturn, "success")
}

func (c *WindFarmAnalysisController) GetDataByTurbine1(k *knot.WebContext) interface{} {
	k.Config.OutputType = knot.OutputJson

	p := struct {
		Project  string
		Turbines []interface{}
	}{}

	e := k.GetPayload(&p)
	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	project := p.Project

	tQry := make([]*dbox.Filter, 0)
	tQry = append(tQry, dbox.Eq("projectname", project))
	if len(p.Turbines) > 0 {
		tQry = append(tQry, dbox.In("turbine", p.Turbines...))
	}

	csr, e := DB().Connection.NewQuery().From(new(GWFAnalysisByTurbine1).TableName()).
		Where(dbox.And(tQry...)).Order([]string{"turbine", "orderno"}...).Cursor(nil)

	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	datas := make([]GWFAnalysisByTurbine1, 0)
	e = csr.Fetch(&datas, 0, false)
	csr.Close()

	title := make([]string, 4)
	turbineName, e := helper.GetTurbineNameList(project)
	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}
	if len(datas) > 0 {
		d := datas[0]
		title[0] = "Rolling 12 Days
" + d.Roll12Days.DateText + ""
		title[1] = "Rolling 12 Weeks
" + d.Roll12Weeks.DateText + ""
		title[2] = "Rolling 12 Months
" + d.Roll12Months.DateText + ""
		title[3] = "Rolling 12 Quarters
" + d.Roll12Quarters.DateText + ""
		for idx, val := range datas {
			datas[idx].Turbine = turbineName[val.Turbine]
		}
	}

	datareturn := tk.M{}.Set("data", datas).Set("header", title)

	return helper.CreateResult(true, datareturn, "success")
}

func (c *WindFarmAnalysisController) GetDataByTurbine2(k *knot.WebContext) interface{} {
	k.Config.OutputType = knot.OutputJson

	p := struct {
		Project  string
		Turbines []string
	}{}

	colors := []string{
		"#ED1C24", "#A3238E", "#00A65D", "#F58220", "#0066B3", "#5C2D91", "#FFF200", "#579835", "#CF3834", "#00B274", "#74489D",
		"#C06616", "#5565AF", "#CCBE00", "#390A5D", "#006D6F", "#65C295", "#F04E4D", "#407927", "#00599D", "#A09600", "#0D1F63",
		"#C38312", "#003D73", "#454FA1", "#BC312E",
	}

	e := k.GetPayload(&p)
	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	project := p.Project

	tQry := make([]*dbox.Filter, 0)
	tQry = append(tQry, dbox.Eq("projectname", project))
	if len(p.Turbines) > 0 {
		tQry = append(tQry, dbox.In("turbine", p.Turbines))
	} else {
		turbines := make([]TurbineMaster, 0)
		csrt, et := DB().Connection.NewQuery().From(new(TurbineMaster).TableName()).
			Where(dbox.And(dbox.Eq("project", project))).Order("turbineid").Cursor(nil)

		if et != nil {
			tk.Println(et.Error())
		}
		et = csrt.Fetch(&turbines, 0, false)
		csrt.Close()

		for _, t := range turbines {
			p.Turbines = append(p.Turbines, t.TurbineId)
		}
	}

	csr, e := DB().Connection.NewQuery().From(new(GWFAnalysisByTurbine2).TableName()).
		Where(dbox.And(tQry...)).Order("orderno").Cursor(nil)

	if e != nil {
		return helper.CreateResult(false, nil, e.Error())
	}

	chartSeries := []tk.M{}
	chartSeries = append(chartSeries, tk.M{
		"field": "Average",
		"name":  "Average",
		"color": colors[0],
	})
	for idx, t := range p.Turbines {
		chartSeries = append(chartSeries, tk.M{
			"field": t,
			"name":  t,
			"color": colors[(idx + 1)],
		})
	}

	datas := make([]GWFAnalysisByTurbine2, 0)
	e = csr.Fetch(&datas, 0, false)
	csr.Close()

	retVal := tk.M{}.Set("ChartSeries", chartSeries).Set("ChartData", datas)

	return helper.CreateResult(true, retVal, "success")
}
<|endoftext|>


<commit_before>package tunnel

import (
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/micro/go-micro/transport"
)

type link struct {
	transport.Socket

	sync.RWMutex

	// unique id of this link e.g uuid
	// which we define for ourselves
	id string
	// whether its a loopback connection
	// this flag is used by the transport listener
	// which accepts inbound quic connections
	loopback bool
	// whether its actually connected
	// dialled side sets it to connected
	// after sending the message. the
	// listener waits for the connect
	connected bool
	// the last time we received a keepalive
	// on this link from the remote side
	lastKeepAlive time.Time
	// channels keeps a mapping of channels and last seen
	channels map[string]time.Time
	// stop the link
	closed chan bool
}

func newLink(s transport.Socket) *link {
	l := &link{
		Socket:   s,
		id:       uuid.New().String(),
		channels: make(map[string]time.Time),
		closed:   make(chan bool),
	}
	go l.run()
	return l
}

func (l *link) run() {
	t := time.NewTicker(time.Minute)
	defer t.Stop()

	for {
		select {
		case <-l.closed:
			return
		case <-t.C:
			// drop any channel mappings older than 2 minutes
			var kill []string
			killTime := time.Minute * 2

			l.RLock()
			for ch, t := range l.channels {
				if d := time.Since(t); d > killTime {
					kill = append(kill, ch)
				}
			}
			l.RUnlock()

			// if nothing to kill don't both with a wasted lock
			if len(kill) == 0 {
				continue
			}

			// kill the channels!
			l.Lock()
			for _, ch := range kill {
				delete(l.channels, ch)
			}
			l.Unlock()
		}
	}
}

func (l *link) Close() {
	l.Lock()
	defer l.Unlock()

	select {
	case <-l.closed:
		return
	default:
		close(l.closed)
	}
}
<commit_msg>Fix typo<commit_after>package tunnel

import (
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/micro/go-micro/transport"
)

type link struct {
	transport.Socket

	sync.RWMutex

	// unique id of this link e.g uuid
	// which we define for ourselves
	id string
	// whether its a loopback connection
	// this flag is used by the transport listener
	// which accepts inbound quic connections
	loopback bool
	// whether its actually connected
	// dialled side sets it to connected
	// after sending the message. the
	// listener waits for the connect
	connected bool
	// the last time we received a keepalive
	// on this link from the remote side
	lastKeepAlive time.Time
	// channels keeps a mapping of channels and last seen
	channels map[string]time.Time
	// stop the link
	closed chan bool
}

func newLink(s transport.Socket) *link {
	l := &link{
		Socket:   s,
		id:       uuid.New().String(),
		channels: make(map[string]time.Time),
		closed:   make(chan bool),
	}
	go l.run()
	return l
}

func (l *link) run() {
	t := time.NewTicker(time.Minute)
	defer t.Stop()

	for {
		select {
		case <-l.closed:
			return
		case <-t.C:
			// drop any channel mappings older than 2 minutes
			var kill []string
			killTime := time.Minute * 2

			l.RLock()
			for ch, t := range l.channels {
				if d := time.Since(t); d > killTime {
					kill = append(kill, ch)
				}
			}
			l.RUnlock()

			// if nothing to kill don't bother with a wasted lock
			if len(kill) == 0 {
				continue
			}

			// kill the channels!
			l.Lock()
			for _, ch := range kill {
				delete(l.channels, ch)
			}
			l.Unlock()
		}
	}
}

func (l *link) Close() {
	l.Lock()
	defer l.Unlock()

	select {
	case <-l.closed:
		return
	default:
		close(l.closed)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"bufio"
	"github.com/apex/log"
	"github.com/pkg/errors"
	"github.com/pterodactyl/wings/api"
	"github.com/pterodactyl/wings/server/backup"
	"os"
	"path"
)

// Notifies the panel of a backup's state and returns an error if one is encountered
// while performing this action.
func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
	r := api.NewRequester()
	rerr, err := r.SendBackupStatus(uuid, ad.ToRequest(successful))
	if rerr != nil || err != nil {
		if err != nil {
			s.Log().WithFields(log.Fields{
				"backup": uuid,
				"error":  err,
			}).Error("failed to notify panel of backup status due to internal code error")

			return err
		}

		s.Log().WithField("backup", uuid).Warn(rerr.String())

		return errors.New(rerr.String())
	}

	return nil
}

// Get all of the ignored files for a server based on its .pteroignore file in the root.
func (s *Server) getServerwideIgnoredFiles() ([]string, error) {
	var ignored []string

	f, err := os.Open(path.Join(s.Filesystem.Path(), ".pteroignore"))
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}
	} else {
		scanner := bufio.NewScanner(f)
		for scanner.Scan() {
			// Only include non-empty lines, for the sake of clarity...
			if t := scanner.Text(); t != "" {
				ignored = append(ignored, t)
			}
		}

		if err := scanner.Err(); err != nil {
			return nil, err
		}
	}

	return ignored, nil
}

// Get the backup files to include when generating it.
func (s *Server) GetIncludedBackupFiles(ignored []string) (*backup.IncludedFiles, error) {
	// If no ignored files are present in the request, check for a .pteroignore file in the root
	// of the server files directory, and use that to generate the backup.
	if len(ignored) == 0 {
		if i, err := s.getServerwideIgnoredFiles(); err != nil {
			s.Log().WithField("error", err).Warn("failed to retrieve ignored files listing for server")
		} else {
			ignored = i
		}
	}

	// Get the included files based on the root path and the ignored files provided.
	return s.Filesystem.GetIncludedFiles(s.Filesystem.Path(), ignored)
}

// Performs a server backup and then emits the event over the server websocket. We
// let the actual backup system handle notifying the panel of the status, but that
// won't emit a websocket event.
func (s *Server) Backup(b backup.BackupInterface) error {
	// Get the included files based on the root path and the ignored files provided.
	inc, err := s.GetIncludedBackupFiles(b.Ignored())
	if err != nil {
		return errors.WithStack(err)
	}

	ad, err := b.Generate(inc, s.Filesystem.Path())
	if err != nil {
		if notifyError := s.notifyPanelOfBackup(b.Identifier(), &backup.ArchiveDetails{}, false); notifyError != nil {
			s.Log().WithFields(log.Fields{
				"backup": b.Identifier(),
				"error":  err,
			}).Warn("failed to notify panel of failed backup state")
		}

		return errors.WithStack(err)
	}

	// Try to notify the panel about the status of this backup. If for some reason this request
	// fails, delete the archive from the daemon and return that error up the chain to the caller.
	if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil {
		b.Remove()

		return notifyError
	}

	// Emit an event over the socket so we can update the backup in realtime on
	// the frontend for the server.
	s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
		"uuid":        b.Identifier(),
		"sha256_hash": ad.Checksum,
		"file_size":   ad.Size,
	})

	return nil
}
<commit_msg>return success state of the backup from wings<commit_after>package server

import (
	"bufio"
	"github.com/apex/log"
	"github.com/pkg/errors"
	"github.com/pterodactyl/wings/api"
	"github.com/pterodactyl/wings/server/backup"
	"os"
	"path"
)

// Notifies the panel of a backup's state and returns an error if one is encountered
// while performing this action.
func (s *Server) notifyPanelOfBackup(uuid string, ad *backup.ArchiveDetails, successful bool) error {
	r := api.NewRequester()
	rerr, err := r.SendBackupStatus(uuid, ad.ToRequest(successful))
	if rerr != nil || err != nil {
		if err != nil {
			s.Log().WithFields(log.Fields{
				"backup": uuid,
				"error":  err,
			}).Error("failed to notify panel of backup status due to internal code error")

			return err
		}

		s.Log().WithField("backup", uuid).Warn(rerr.String())

		return errors.New(rerr.String())
	}

	return nil
}

// Get all of the ignored files for a server based on its .pteroignore file in the root.
func (s *Server) getServerwideIgnoredFiles() ([]string, error) {
	var ignored []string

	f, err := os.Open(path.Join(s.Filesystem.Path(), ".pteroignore"))
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}
	} else {
		scanner := bufio.NewScanner(f)
		for scanner.Scan() {
			// Only include non-empty lines, for the sake of clarity...
			if t := scanner.Text(); t != "" {
				ignored = append(ignored, t)
			}
		}

		if err := scanner.Err(); err != nil {
			return nil, err
		}
	}

	return ignored, nil
}

// Get the backup files to include when generating it.
func (s *Server) GetIncludedBackupFiles(ignored []string) (*backup.IncludedFiles, error) {
	// If no ignored files are present in the request, check for a .pteroignore file in the root
	// of the server files directory, and use that to generate the backup.
	if len(ignored) == 0 {
		if i, err := s.getServerwideIgnoredFiles(); err != nil {
			s.Log().WithField("error", err).Warn("failed to retrieve ignored files listing for server")
		} else {
			ignored = i
		}
	}

	// Get the included files based on the root path and the ignored files provided.
	return s.Filesystem.GetIncludedFiles(s.Filesystem.Path(), ignored)
}

// Performs a server backup and then emits the event over the server websocket. We
// let the actual backup system handle notifying the panel of the status, but that
// won't emit a websocket event.
func (s *Server) Backup(b backup.BackupInterface) error {
	// Get the included files based on the root path and the ignored files provided.
	inc, err := s.GetIncludedBackupFiles(b.Ignored())
	if err != nil {
		return errors.WithStack(err)
	}

	ad, err := b.Generate(inc, s.Filesystem.Path())
	if err != nil {
		if notifyError := s.notifyPanelOfBackup(b.Identifier(), &backup.ArchiveDetails{}, false); notifyError != nil {
			s.Log().WithFields(log.Fields{
				"backup": b.Identifier(),
				"error":  err,
			}).Warn("failed to notify panel of failed backup state")
		}

		s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
			"uuid":          b.Identifier(),
			"is_successful": false,
			"sha256_hash":   "",
			"file_size":     0,
		})

		return errors.WithStack(err)
	}

	// Try to notify the panel about the status of this backup. If for some reason this request
	// fails, delete the archive from the daemon and return that error up the chain to the caller.
	if notifyError := s.notifyPanelOfBackup(b.Identifier(), ad, true); notifyError != nil {
		b.Remove()

		return notifyError
	}

	// Emit an event over the socket so we can update the backup in realtime on
	// the frontend for the server.
	s.Events().PublishJson(BackupCompletedEvent+":"+b.Identifier(), map[string]interface{}{
		"uuid":          b.Identifier(),
		"is_successful": true,
		"sha256_hash":   ad.Checksum,
		"file_size":     ad.Size,
	})

	return nil
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/auth"
	"github.com/smancke/guble/store"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	overloadedHandleChannelRatio = 0.9
	handleChannelCapacity        = 500
	subscribeChannelCapacity     = 10
	unsubscribeChannelCapacity   = 10
)

var logger = log.WithFields(log.Fields{
	"app":    "guble",
	"module": "router",
	"env":    "TBD"})

// Router interface provides mechanism for PubSub messaging
type Router interface {
	KVStore() (store.KVStore, error)
	AccessManager() (auth.AccessManager, error)
	MessageStore() (store.MessageStore, error)

	Subscribe(r *Route) (*Route, error)
	Unsubscribe(r *Route)
	HandleMessage(message *protocol.Message) error
}

// Helper struct to pass `Route` to subscription channel and provide a notification channel.
type subRequest struct {
	route      *Route
	doneNotify chan bool
}

type router struct {
	routes       map[protocol.Path][]*Route // mapping the path to the route slice
	handleC      chan *protocol.Message
	subscribeC   chan subRequest
	unsubscribeC chan subRequest
	stopC        chan bool      // Channel that signals stop of the router
	stopping     bool           // Flag: the router is in stopping process and no incoming messages are accepted
	wg           sync.WaitGroup // Add any operation that we need to wait upon here

	accessManager auth.AccessManager
	messageStore  store.MessageStore
	kvStore       store.KVStore
}

// NewRouter returns a pointer to Router
func NewRouter(accessManager auth.AccessManager, messageStore store.MessageStore, kvStore store.KVStore) Router {
	return &router{
		routes: make(map[protocol.Path][]*Route),

		handleC:      make(chan *protocol.Message, handleChannelCapacity),
		subscribeC:   make(chan subRequest, subscribeChannelCapacity),
		unsubscribeC: make(chan subRequest, unsubscribeChannelCapacity),
		stopC:        make(chan bool, 1),

		accessManager: accessManager,
		messageStore:  messageStore,
		kvStore:       kvStore,
	}
}

func (router *router) Start() error {
	router.panicIfInternalDependenciesAreNil()
	resetRouterMetrics()
	go func() {
		router.wg.Add(1)
		for {
			if router.stopping && router.channelsAreEmpty() {
				router.closeRoutes()
				router.wg.Done()
				return
			}

			func() {
				defer protocol.PanicLogger()

				select {
				case message := <-router.handleC:
					router.routeMessage(message)
					runtime.Gosched()
				case subscriber := <-router.subscribeC:
					router.subscribe(subscriber.route)
					subscriber.doneNotify <- true
				case unsubscriber := <-router.unsubscribeC:
					router.unsubscribe(unsubscriber.route)
					unsubscriber.doneNotify <- true
				case <-router.stopC:
					router.stopping = true
				}
			}()
		}
	}()

	return nil
}

// Stop stops the router by closing the stop channel, and waiting on the WaitGroup
func (router *router) Stop() error {
	logger.Debug("Stopping router")

	router.stopC <- true
	router.wg.Wait()
	return nil
}

func (router *router) Check() error {
	if router.accessManager == nil || router.messageStore == nil || router.kvStore == nil {
		logger.WithFields(log.Fields{
			"err": ErrServiceNotProvided,
		}).Error("Some services are not provided")
		return ErrServiceNotProvided
	}

	err := router.messageStore.Check()
	if err != nil {
		logger.WithFields(log.Fields{
			"err": err,
		}).Error("MessageStore check failed")
		return err
	}

	err = router.kvStore.Check()
	if err != nil {
		log.WithFields(log.Fields{
			"module": "router",
			"err":    err,
		}).Error("KvStore check failed")
		return err
	}

	return nil
}

func (router *router) HandleMessage(message *protocol.Message) error {
	logger.WithFields(log.Fields{
		"userID": message.UserID,
		"path":   message.Path,
	}).Debug("HandleMessage")

	mTotalMessagesIncoming.Add(1)
	if err := router.isStopping(); err != nil {
		logger.WithFields(log.Fields{
			"err": err,
		}).Error("Router is stopping")
		return err
	}

	if !router.accessManager.IsAllowed(auth.WRITE, message.UserID, message.Path) {
		return &PermissionDeniedError{message.UserID, auth.WRITE, message.Path}
	}

	return router.storeAndChannelMessage(message)
}

// Subscribe adds a route to the subscribers.
// If there is already a route with same Application Id and Path, it will be replaced.
func (router *router) Subscribe(r *Route) (*Route, error) {

	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"userID":        r.UserID,
		"path":          r.Path,
	}).Debug("Subscribe")

	if err := router.isStopping(); err != nil {
		return nil, err
	}

	accessAllowed := router.accessManager.IsAllowed(auth.READ, r.UserID, r.Path)
	if !accessAllowed {
		return r, &PermissionDeniedError{UserID: r.UserID, AccessType: auth.READ, Path: r.Path}
	}
	req := subRequest{
		route:      r,
		doneNotify: make(chan bool),
	}
	router.subscribeC <- req
	<-req.doneNotify
	return r, nil
}

func (router *router) Unsubscribe(r *Route) {
	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"userID":        r.UserID,
		"path":          r.Path,
	}).Debug("Unsubscribe")

	req := subRequest{
		route:      r,
		doneNotify: make(chan bool),
	}
	router.unsubscribeC <- req
	<-req.doneNotify
}

func (router *router) subscribe(r *Route) {
	logger.WithFields(log.Fields{
		"userID": r.UserID,
		"path":   r.Path,
	}).Debug("Intenal subscribe for")
	mTotalSubscriptionAttempts.Add(1)

	slice, present := router.routes[r.Path]
	var removed bool
	if present {
		// Try to remove, to avoid double subscriptions of the same app
		slice, removed = removeIfMatching(slice, r)
	} else {
		// Path not present yet. Initialize the slice
		slice = make([]*Route, 0, 1)
		router.routes[r.Path] = slice
		mCurrentRoutes.Add(1)
	}
	router.routes[r.Path] = append(slice, r)
	if removed {
		mTotalDuplicateSubscriptionsAttempts.Add(1)
	} else {
		mTotalSubscriptions.Add(1)
		mCurrentSubscriptions.Add(1)
	}
}

func (router *router) unsubscribe(r *Route) {

	logger.WithFields(log.Fields{
		"userID": r.UserID,
		"path":   r.Path,
	}).Debug("Intenal unsubscribe for :")
	mTotalUnsubscriptionAttempts.Add(1)

	slice, present := router.routes[r.Path]
	if !present {
		mTotalInvalidTopicOnUnsubscriptionAttempts.Add(1)
		return
	}
	var removed bool
	router.routes[r.Path], removed = removeIfMatching(slice, r)
	if removed {
		mTotalUnsubscriptions.Add(1)
		mCurrentSubscriptions.Add(-1)
	} else {
		mTotalInvalidUnsubscriptionAttempts.Add(1)
	}
	if len(router.routes[r.Path]) == 0 {
		delete(router.routes, r.Path)
		mCurrentRoutes.Add(-1)
	}
}

func (router *router) panicIfInternalDependenciesAreNil() {
	if router.accessManager == nil || router.kvStore == nil || router.messageStore == nil {
		panic(fmt.Sprintf("router: the internal dependencies marked with `true` are not set: AccessManager=%v, KVStore=%v, MessageStore=%v",
			router.accessManager == nil, router.kvStore == nil, router.messageStore == nil))
	}
}

func (router *router) channelsAreEmpty() bool {
	return len(router.handleC) == 0 && len(router.subscribeC) == 0 && len(router.unsubscribeC) == 0
}

func (router *router) isStopping() error {
	if router.stopping {
		return &ModuleStoppingError{"Router"}
	}
	return nil
}

// Assign the new message id, store message and handle it by passing the stored message
// to the messageIn channel
func (router *router) storeAndChannelMessage(msg *protocol.Message) error {
	txCallback := func(msgId uint64) []byte {
		msg.ID = msgId
		msg.Time = time.Now().Unix()
		return msg.Bytes()
	}
	lenMsg := int64(len(msg.Bytes()))
	mTotalMessagesIncomingBytes.Add(lenMsg)

	if err := router.messageStore.StoreTx(msg.Path.Partition(), txCallback); err != nil {
		logger.WithFields(log.Fields{
			"err":          err,
			"msgPartition": msg.Path.Partition(),
		}).Error("Error storing message in partition")
		mTotalMessageStoreErrors.Add(1)
		return err
	} else {
		mTotalMessagesStoredBytes.Add(lenMsg)
	}

	if float32(len(router.handleC))/float32(cap(router.handleC)) > overloadedHandleChannelRatio {
		logger.WithFields(log.Fields{
			"currentLength": len(router.handleC),
			"maxCapacity":   cap(router.handleC),
		}).Warn("Warning handleC channel almost full")
	mTotalOverloadedHandleChannel.Add(1)
	}

	router.handleC <- msg
	return nil
}

func (router *router) routeMessage(message *protocol.Message) {

	logger.WithFields(log.Fields{
		"msgMetadata": message.Metadata(),
	}).Debug("Called routeMessage for data")
	mTotalMessagesRouted.Add(1)

	for path, list := range router.routes {
		if matchesTopic(message.Path, path) {
			for _, route := range list {
				router.deliverMessage(route, message)
			}
		} else {
			mTotalMessagesNotMatchingTopic.Add(1)
		}
	}
}

func (router *router) deliverMessage(route *Route, message *protocol.Message) {
	defer protocol.PanicLogger()

	select {
	case route.MessagesChannel() <- &MessageForRoute{Message: message, Route: route}:
	// fine, we could send the message
	default:

		logger.WithFields(log.Fields{
			"route": route.String(),
		}).Warn(" deliverMessage: queue was full, unsubscribing and closing delivery channel for route")
		router.unsubscribe(route)
		route.Close()
		mTotalDeliverMessageErrors.Add(1)
	}
}

func (router *router) closeRoutes() {
	logger.Debug("Called closeRoutes")

	for _, currentRouteList := range router.routes {
		for _, route := range currentRouteList {
			router.unsubscribe(route)
			log.WithFields(log.Fields{
				"module": "router",
				"route":  route.String(),
			}).Debug("CLosing route for ")
			route.Close()
		}
	}
}

func copyOf(message []byte) []byte {
	messageCopy := make([]byte, len(message))
	copy(messageCopy, message)
	return messageCopy
}

// matchesTopic checks whether the supplied routePath matches the message topic
func matchesTopic(messagePath, routePath protocol.Path) bool {
	messagePathLen := len(string(messagePath))
	routePathLen := len(string(routePath))
	return strings.HasPrefix(string(messagePath), string(routePath)) &&
		(messagePathLen == routePathLen ||
			(messagePathLen > routePathLen && string(messagePath)[routePathLen] == '/'))
}

// remove removes a route from the supplied list, based on same ApplicationID id and same path (if existing)
// returns: the (possibly updated) slide, and a boolean value (true if route was removed, false otherwise)
func removeIfMatching(slice []*Route, route *Route) ([]*Route, bool) {
	position := -1
	for p, r := range slice {
		if r.ApplicationID == route.ApplicationID && r.Path == route.Path {
			position = p
		}
	}
	if position == -1 {
		return slice, false
	}
	return append(slice[:position], slice[position+1:]...), true
}

// AccessManager returns the `accessManager` provided for the router
func (router *router) AccessManager() (auth.AccessManager, error) {
	if router.accessManager == nil {
		return nil, ErrServiceNotProvided
	}
	return router.accessManager, nil
}

// MessageStore returns the `messageStore` provided for the router
func (router *router) MessageStore() (store.MessageStore, error) {
	if router.messageStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.messageStore, nil
}

// KVStore returns the `kvStore` provided for the router
func (router *router) KVStore() (store.KVStore, error) {
	if router.kvStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.kvStore, nil
}
<commit_msg>Fix spelling mistake<commit_after>package server

import (
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/auth"
	"github.com/smancke/guble/store"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	overloadedHandleChannelRatio = 0.9
	handleChannelCapacity        = 500
	subscribeChannelCapacity     = 10
	unsubscribeChannelCapacity   = 10
)

var logger = log.WithFields(log.Fields{
	"app":    "guble",
	"module": "router",
	"env":    "TBD"})

// Router interface provides mechanism for PubSub messaging
type Router interface {
	KVStore() (store.KVStore, error)
	AccessManager() (auth.AccessManager, error)
	MessageStore() (store.MessageStore, error)

	Subscribe(r *Route) (*Route, error)
	Unsubscribe(r *Route)
	HandleMessage(message *protocol.Message) error
}

// Helper struct to pass `Route` to subscription channel and provide a notification channel.
type subRequest struct {
	route      *Route
	doneNotify chan bool
}

type router struct {
	routes       map[protocol.Path][]*Route // mapping the path to the route slice
	handleC      chan *protocol.Message
	subscribeC   chan subRequest
	unsubscribeC chan subRequest
	stopC        chan bool      // Channel that signals stop of the router
	stopping     bool           // Flag: the router is in stopping process and no incoming messages are accepted
	wg           sync.WaitGroup // Add any operation that we need to wait upon here

	accessManager auth.AccessManager
	messageStore  store.MessageStore
	kvStore       store.KVStore
}

// NewRouter returns a pointer to Router
func NewRouter(accessManager auth.AccessManager, messageStore store.MessageStore, kvStore store.KVStore) Router {
	return &router{
		routes: make(map[protocol.Path][]*Route),

		handleC:      make(chan *protocol.Message, handleChannelCapacity),
		subscribeC:   make(chan subRequest, subscribeChannelCapacity),
		unsubscribeC: make(chan subRequest, unsubscribeChannelCapacity),
		stopC:        make(chan bool, 1),

		accessManager: accessManager,
		messageStore:  messageStore,
		kvStore:       kvStore,
	}
}

func (router *router) Start() error {
	router.panicIfInternalDependenciesAreNil()
	resetRouterMetrics()
	go func() {
		router.wg.Add(1)
		for {
			if router.stopping && router.channelsAreEmpty() {
				router.closeRoutes()
				router.wg.Done()
				return
			}

			func() {
				defer protocol.PanicLogger()

				select {
				case message := <-router.handleC:
					router.routeMessage(message)
					runtime.Gosched()
				case subscriber := <-router.subscribeC:
					router.subscribe(subscriber.route)
					subscriber.doneNotify <- true
				case unsubscriber := <-router.unsubscribeC:
					router.unsubscribe(unsubscriber.route)
					unsubscriber.doneNotify <- true
				case <-router.stopC:
					router.stopping = true
				}
			}()
		}
	}()

	return nil
}

// Stop stops the router by closing the stop channel, and waiting on the WaitGroup
func (router *router) Stop() error {
	logger.Debug("Stopping router")

	router.stopC <- true
	router.wg.Wait()
	return nil
}

func (router *router) Check() error {
	if router.accessManager == nil || router.messageStore == nil || router.kvStore == nil {
		logger.WithFields(log.Fields{
			"err": ErrServiceNotProvided,
		}).Error("Some services are not provided")
		return ErrServiceNotProvided
	}

	err := router.messageStore.Check()
	if err != nil {
		logger.WithFields(log.Fields{
			"err": err,
		}).Error("MessageStore check failed")
		return err
	}

	err = router.kvStore.Check()
	if err != nil {
		log.WithFields(log.Fields{
			"module": "router",
			"err":    err,
		}).Error("KvStore check failed")
		return err
	}

	return nil
}

func (router *router) HandleMessage(message *protocol.Message) error {
	logger.WithFields(log.Fields{
		"userID": message.UserID,
		"path":   message.Path,
	}).Debug("HandleMessage")

	mTotalMessagesIncoming.Add(1)
	if err := router.isStopping(); err != nil {
		logger.WithFields(log.Fields{
			"err": err,
		}).Error("Router is stopping")
		return err
	}

	if !router.accessManager.IsAllowed(auth.WRITE, message.UserID, message.Path) {
		return &PermissionDeniedError{message.UserID, auth.WRITE, message.Path}
	}

	return router.storeAndChannelMessage(message)
}

// Subscribe adds a route to the subscribers.
// If there is already a route with same Application Id and Path, it will be replaced.
func (router *router) Subscribe(r *Route) (*Route, error) {

	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"userID":        r.UserID,
		"path":          r.Path,
	}).Debug("Subscribe")

	if err := router.isStopping(); err != nil {
		return nil, err
	}

	accessAllowed := router.accessManager.IsAllowed(auth.READ, r.UserID, r.Path)
	if !accessAllowed {
		return r, &PermissionDeniedError{UserID: r.UserID, AccessType: auth.READ, Path: r.Path}
	}
	req := subRequest{
		route:      r,
		doneNotify: make(chan bool),
	}
	router.subscribeC <- req
	<-req.doneNotify
	return r, nil
}

func (router *router) Unsubscribe(r *Route) {
	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"userID":        r.UserID,
		"path":          r.Path,
	}).Debug("Unsubscribe")

	req := subRequest{
		route:      r,
		doneNotify: make(chan bool),
	}
	router.unsubscribeC <- req
	<-req.doneNotify
}

func (router *router) subscribe(r *Route) {
	logger.WithFields(log.Fields{
		"userID": r.UserID,
		"path":   r.Path,
	}).Debug("Intenal subscribe for")
	mTotalSubscriptionAttempts.Add(1)

	slice, present := router.routes[r.Path]
	var removed bool
	if present {
		// Try to remove, to avoid double subscriptions of the same app
		slice, removed = removeIfMatching(slice, r)
	} else {
		// Path not present yet. Initialize the slice
		slice = make([]*Route, 0, 1)
		router.routes[r.Path] = slice
		mCurrentRoutes.Add(1)
	}
	router.routes[r.Path] = append(slice, r)
	if removed {
		mTotalDuplicateSubscriptionsAttempts.Add(1)
	} else {
		mTotalSubscriptions.Add(1)
		mCurrentSubscriptions.Add(1)
	}
}

func (router *router) unsubscribe(r *Route) {

	logger.WithFields(log.Fields{
		"userID": r.UserID,
		"path":   r.Path,
	}).Debug("Intenal unsubscribe for :")
	mTotalUnsubscriptionAttempts.Add(1)

	slice, present := router.routes[r.Path]
	if !present {
		mTotalInvalidTopicOnUnsubscriptionAttempts.Add(1)
		return
	}
	var removed bool
	router.routes[r.Path], removed = removeIfMatching(slice, r)
	if removed {
		mTotalUnsubscriptions.Add(1)
		mCurrentSubscriptions.Add(-1)
	} else {
		mTotalInvalidUnsubscriptionAttempts.Add(1)
	}
	if len(router.routes[r.Path]) == 0 {
		delete(router.routes, r.Path)
		mCurrentRoutes.Add(-1)
	}
}

func (router *router) panicIfInternalDependenciesAreNil() {
	if router.accessManager == nil || router.kvStore == nil || router.messageStore == nil {
		panic(fmt.Sprintf("router: the internal dependencies marked with `true` are not set: AccessManager=%v, KVStore=%v, MessageStore=%v",
			router.accessManager == nil, router.kvStore == nil, router.messageStore == nil))
	}
}

func (router *router) channelsAreEmpty() bool {
	return len(router.handleC) == 0 && len(router.subscribeC) == 0 && len(router.unsubscribeC) == 0
}

func (router *router) isStopping() error {
	if router.stopping {
		return &ModuleStoppingError{"Router"}
	}
	return nil
}

// Assign the new message id, store message and handle it by passing the stored message
// to the messageIn channel
func (router *router) storeAndChannelMessage(msg *protocol.Message) error {
	txCallback := func(msgId uint64) []byte {
		msg.ID = msgId
		msg.Time = time.Now().Unix()
		return msg.Bytes()
	}
	lenMsg := int64(len(msg.Bytes()))
	mTotalMessagesIncomingBytes.Add(lenMsg)

	if err := router.messageStore.StoreTx(msg.Path.Partition(), txCallback); err != nil {
		logger.WithFields(log.Fields{
			"err":          err,
			"msgPartition": msg.Path.Partition(),
		}).Error("Error storing message in partition")
		mTotalMessageStoreErrors.Add(1)
		return err
	} else {
		mTotalMessagesStoredBytes.Add(lenMsg)
	}

	if float32(len(router.handleC))/float32(cap(router.handleC)) > overloadedHandleChannelRatio {
		logger.WithFields(log.Fields{
			"currentLength": len(router.handleC),
			"maxCapacity":   cap(router.handleC),
		}).Warn("Warning handleC channel almost full")
	mTotalOverloadedHandleChannel.Add(1)
	}

	router.handleC <- msg
	return nil
}

func (router *router) routeMessage(message *protocol.Message) {

	logger.WithFields(log.Fields{
		"msgMetadata": message.Metadata(),
	}).Debug("Called routeMessage for data")
	mTotalMessagesRouted.Add(1)

	for path, list := range router.routes {
		if matchesTopic(message.Path, path) {
			for _, route := range list {
				router.deliverMessage(route, message)
			}
		} else {
			mTotalMessagesNotMatchingTopic.Add(1)
		}
	}
}

func (router *router) deliverMessage(route *Route, message *protocol.Message) {
	defer protocol.PanicLogger()

	select {
	case route.MessagesChannel() <- &MessageForRoute{Message: message, Route: route}:
	// fine, we could send the message
	default:

		logger.WithFields(log.Fields{
			"route": route.String(),
		}).Warn(" deliverMessage: queue was full, unsubscribing and closing delivery channel for route")
		router.unsubscribe(route)
		route.Close()
		mTotalDeliverMessageErrors.Add(1)
	}
}

func (router *router) closeRoutes() {
	logger.Debug("Called closeRoutes")

	for _, currentRouteList := range router.routes {
		for _, route := range currentRouteList {
			router.unsubscribe(route)
			log.WithFields(log.Fields{
				"module": "router",
				"route":  route.String(),
			}).Debug("Closing route for ")
			route.Close()
		}
	}
}

func copyOf(message []byte) []byte {
	messageCopy := make([]byte, len(message))
	copy(messageCopy, message)
	return messageCopy
}

// matchesTopic checks whether the supplied routePath matches the message topic
func matchesTopic(messagePath, routePath protocol.Path) bool {
	messagePathLen := len(string(messagePath))
	routePathLen := len(string(routePath))
	return strings.HasPrefix(string(messagePath), string(routePath)) &&
		(messagePathLen == routePathLen ||
			(messagePathLen > routePathLen && string(messagePath)[routePathLen] == '/'))
}

// remove removes a route from the supplied list, based on same ApplicationID id and same path (if existing)
// returns: the (possibly updated) slide, and a boolean value (true if route was removed, false otherwise)
func removeIfMatching(slice []*Route, route *Route) ([]*Route, bool) {
	position := -1
	for p, r := range slice {
		if r.ApplicationID == route.ApplicationID && r.Path == route.Path {
			position = p
		}
	}
	if position == -1 {
		return slice, false
	}
	return append(slice[:position], slice[position+1:]...), true
}

// AccessManager returns the `accessManager` provided for the router
func (router *router) AccessManager() (auth.AccessManager, error) {
	if router.accessManager == nil {
		return nil, ErrServiceNotProvided
	}
	return router.accessManager, nil
}

// MessageStore returns the `messageStore` provided for the router
func (router *router) MessageStore() (store.MessageStore, error) {
	if router.messageStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.messageStore, nil
}

// KVStore returns the `kvStore` provided for the router
func (router *router) KVStore() (store.KVStore, error) {
	if router.kvStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.kvStore, nil
}
<|endoftext|>