<commit_before>package client

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"github.com/fluffle/goirc/logging"
	"github.com/fluffle/goirc/state"
	"io"
	"net"
	"strings"
	"sync"
	"time"
)

// An IRC connection is represented by this struct.
type Conn struct {
	// For preventing races on (dis)connect.
	mu sync.RWMutex

	// Contains parameters that people can tweak to change client behaviour.
	cfg *Config

	// Handlers
	intHandlers *hSet
	fgHandlers  *hSet
	bgHandlers  *hSet

	// State tracker for nicks and channels
	st         state.Tracker
	stRemovers []Remover

	// I/O stuff to server
	dialer    *net.Dialer
	sock      net.Conn
	io        *bufio.ReadWriter
	in        chan *Line
	out       chan string
	connected bool

	// Control channel and WaitGroup for goroutines
	die chan struct{}
	wg  sync.WaitGroup

	// Internal counters for flood protection
	badness  time.Duration
	lastsent time.Time
}

// Misc knobs to tweak client behaviour go in here
type Config struct {
	// Set this to provide the Nick, Ident and Name for the client to use.
	Me *state.Nick

	// Hostname to connect to and optional connect password.
	Server, Pass string

	// Are we connecting via SSL? Do we care about certificate validity?
	SSL       bool
	SSLConfig *tls.Config

	// Local address to connect to the server.
	LocalAddr string

	// Replaceable function to customise the 433 handler's new nick
	NewNick func(string) string

	// Client->server ping frequency, in seconds. Defaults to 3m.
	PingFreq time.Duration

	// Set this to true to disable flood protection and false to re-enable
	Flood bool

	// Sent as the reply to a CTCP VERSION message
	Version string

	// Sent as the QUIT message.
	QuitMessage string

	// Configurable panic recovery for all handlers.
	Recover func(*Conn, *Line)

	// Split PRIVMSGs, NOTICEs and CTCPs longer than
	// SplitLen characters over multiple lines.
	SplitLen int

	// Timeout, The amount of time in seconds until a timeout is triggered.
	Timeout time.Duration
}

func NewConfig(nick string, args ...string) *Config {
	cfg := &Config{
		Me:       &state.Nick{Nick: nick},
		PingFreq: 3 * time.Minute,
		NewNick:  func(s string) string { return s + "_" },
		Recover:  (*Conn).LogPanic, // in dispatch.go
		SplitLen: 450,
		Timeout:  60 * time.Second,
	}
	cfg.Me.Ident = "goirc"
	if len(args) > 0 && args[0] != "" {
		cfg.Me.Ident = args[0]
	}
	cfg.Me.Name = "Powered by GoIRC"
	if len(args) > 1 && args[1] != "" {
		cfg.Me.Name = args[1]
	}
	cfg.Version = "Powered by GoIRC"
	cfg.QuitMessage = "GoBye!"
	return cfg
}

// Creates a new IRC connection object, but doesn't connect to anything so
// that you can add event handlers to it. See AddHandler() for details
func SimpleClient(nick string, args ...string) *Conn {
	conn := Client(NewConfig(nick, args...))
	return conn
}

func Client(cfg *Config) *Conn {
	if cfg == nil {
		cfg = NewConfig("__idiot__")
	}
	if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" {
		cfg.Me = &state.Nick{Nick: "__idiot__"}
		cfg.Me.Ident = "goirc"
		cfg.Me.Name = "Powered by GoIRC"
	}

	dialer := new(net.Dialer)
	dialer.Timeout = cfg.Timeout
	if cfg.LocalAddr != "" {
		if !hasPort(cfg.LocalAddr) {
			cfg.LocalAddr += ":0"
		}

		local, err := net.ResolveTCPAddr("tcp", cfg.LocalAddr)
		if err == nil {
			dialer.LocalAddr = local
		} else {
			logging.Error("irc.Client(): Cannot resolve local address %s: %s", cfg.LocalAddr, err)
		}
	}

	conn := &Conn{
		cfg:         cfg,
		dialer:      dialer,
		in:          make(chan *Line, 32),
		out:         make(chan string, 32),
		intHandlers: handlerSet(),
		fgHandlers:  handlerSet(),
		bgHandlers:  handlerSet(),
		stRemovers:  make([]Remover, 0, len(stHandlers)),
		lastsent:    time.Now(),
	}
	conn.addIntHandlers()
	conn.initialise()
	return conn
}

func (conn *Conn) Connected() bool {
	conn.mu.RLock()
	defer conn.mu.RUnlock()
	return conn.connected
}

func (conn *Conn) Config() *Config {
	return conn.cfg
}

func (conn *Conn) Me() *state.Nick {
	conn.mu.RLock()
	defer conn.mu.RUnlock()
	if conn.st != nil {
		conn.cfg.Me = conn.st.Me()
	}
	return conn.cfg.Me
}

func (conn *Conn) StateTracker() state.Tracker {
	return conn.st
}

func (conn *Conn) EnableStateTracking() {
	conn.mu.Lock()
	defer conn.mu.Unlock()
	if conn.st == nil {
		n := conn.cfg.Me
		conn.st = state.NewTracker(n.Nick)
		conn.st.NickInfo(n.Nick, n.Ident, n.Host, n.Name)
		conn.cfg.Me = conn.st.Me()
		conn.addSTHandlers()
	}
}

func (conn *Conn) DisableStateTracking() {
	conn.mu.Lock()
	defer conn.mu.Unlock()
	if conn.st != nil {
		conn.cfg.Me = conn.st.Me()
		conn.delSTHandlers()
		conn.st.Wipe()
		conn.st = nil
	}
}

// Per-connection state initialisation.
func (conn *Conn) initialise() {
	conn.io = nil
	conn.sock = nil
	conn.die = make(chan struct{})
	if conn.st != nil {
		conn.st.Wipe()
	}
}

// Connect the IRC connection object to "host[:port]" which should be either
// a hostname or an IP address, with an optional port. To enable explicit SSL
// on the connection to the IRC server, set Conn.SSL to true before calling
// Connect(). The port will default to 6697 if ssl is enabled, and 6667
// otherwise. You can also provide an optional connect password.
func (conn *Conn) ConnectTo(host string, pass ...string) error {
	conn.cfg.Server = host
	if len(pass) > 0 {
		conn.cfg.Pass = pass[0]
	}
	return conn.Connect()
}

func (conn *Conn) Connect() error {
	conn.mu.Lock()
	defer conn.mu.Unlock()

	if conn.cfg.Server == "" {
		return fmt.Errorf("irc.Connect(): cfg.Server must be non-empty")
	}
	if conn.connected {
		return fmt.Errorf("irc.Connect(): Cannot connect to %s, already connected.", conn.cfg.Server)
	}
	if conn.cfg.SSL {
		if !hasPort(conn.cfg.Server) {
			conn.cfg.Server = net.JoinHostPort(conn.cfg.Server, "6697")
		}
		logging.Info("irc.Connect(): Connecting to %s with SSL.", conn.cfg.Server)
		if s, err := tls.DialWithDialer(conn.dialer, "tcp", conn.cfg.Server, conn.cfg.SSLConfig); err == nil {
			conn.sock = s
		} else {
			return err
		}
	} else {
		if !hasPort(conn.cfg.Server) {
			conn.cfg.Server = net.JoinHostPort(conn.cfg.Server, "6667")
		}
		logging.Info("irc.Connect(): Connecting to %s without SSL.", conn.cfg.Server)
		if s, err := conn.dialer.Dial("tcp", conn.cfg.Server); err == nil {
			conn.sock = s
		} else {
			return err
		}
	}
	conn.connected = true
	conn.postConnect(true)
	conn.dispatch(&Line{Cmd: REGISTER, Time: time.Now()})
	return nil
}

// Post-connection setup (for ease of testing)
func (conn *Conn) postConnect(start bool) {
	conn.io = bufio.NewReadWriter(
		bufio.NewReader(conn.sock),
		bufio.NewWriter(conn.sock))
	if start {
		conn.wg.Add(3)
		go conn.send()
		go conn.recv()
		go conn.runLoop()
		if conn.cfg.PingFreq > 0 {
			conn.wg.Add(1)
			go conn.ping()
		}
	}
}

// copied from http.client for great justice
func hasPort(s string) bool {
	return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
}

// goroutine to pass data from output channel to write()
func (conn *Conn) send() {
	defer conn.wg.Done()
	for {
		select {
		case line := <-conn.out:
			conn.write(line)
		case <-conn.die:
			// control channel closed, bail out
			return
		}
	}
}

// receive one \r\n terminated line from peer, parse and dispatch it
func (conn *Conn) recv() {
	for {
		s, err := conn.io.ReadString('\n')
		if err != nil {
			if err != io.EOF {
				logging.Error("irc.recv(): %s", err.Error())
			}
			// We can't defer this, because shutdown() waits for it.
			conn.wg.Done()
			conn.shutdown()
			return
		}
		s = strings.Trim(s, "\r\n")
		logging.Debug("<- %s", s)

		if line := ParseLine(s); line != nil {
			line.Time = time.Now()
			conn.in <- line
		} else {
			logging.Warn("irc.recv(): problems parsing line:\n  %s", s)
		}
	}
}

// Repeatedly pings the server every PingFreq seconds (no matter what)
func (conn *Conn) ping() {
	defer conn.wg.Done()
	tick := time.NewTicker(conn.cfg.PingFreq)
	for {
		select {
		case <-tick.C:
			conn.Ping(fmt.Sprintf("%d", time.Now().UnixNano()))
		case <-conn.die:
			// control channel closed, bail out
			tick.Stop()
			return
		}
	}
}

// goroutine to dispatch events for lines received on input channel
func (conn *Conn) runLoop() {
	defer conn.wg.Done()
	for {
		select {
		case line := <-conn.in:
			conn.dispatch(line)
		case <-conn.die:
			// control channel closed, bail out
			return
		}
	}
}

// Write a \r\n terminated line of output to the connected server,
// using Hybrid's algorithm to rate limit if conn.cfg.Flood is false.
func (conn *Conn) write(line string) {
	if !conn.cfg.Flood {
		if t := conn.rateLimit(len(line)); t != 0 {
			// sleep for the current line's time value before sending it
			logging.Info("irc.rateLimit(): Flood! Sleeping for %.2f secs.",
				t.Seconds())
			<-time.After(t)
		}
	}

	if _, err := conn.io.WriteString(line + "\r\n"); err != nil {
		logging.Error("irc.send(): %s", err.Error())
		conn.shutdown()
		return
	}
	if err := conn.io.Flush(); err != nil {
		logging.Error("irc.send(): %s", err.Error())
		conn.shutdown()
		return
	}
	logging.Debug("-> %s", line)
}

// Implement Hybrid's flood control algorithm to rate-limit outgoing lines.
func (conn *Conn) rateLimit(chars int) time.Duration {
	// Hybrid's algorithm allows for 2 seconds per line and an additional
	// 1/120 of a second per character on that line.
	linetime := 2*time.Second + time.Duration(chars)*time.Second/120
	elapsed := time.Now().Sub(conn.lastsent)
	if conn.badness += linetime - elapsed; conn.badness < 0 {
		// negative badness times are badness...
		conn.badness = 0
	}
	conn.lastsent = time.Now()
	// If we've sent more than 10 second's worth of lines according to the
	// calculation above, then we're at risk of "Excess Flood".
	if conn.badness > 10*time.Second {
		return linetime
	}
	return 0
}

func (conn *Conn) shutdown() {
	// Guard against double-call of shutdown() if we get an error in send()
	// as calling sock.Close() will cause recv() to receive EOF in readstring()
	conn.mu.Lock()
	defer conn.mu.Unlock()
	if !conn.connected {
		return
	}
	logging.Info("irc.shutdown(): Disconnected from server.")
	conn.connected = false
	conn.sock.Close()
	close(conn.die)
	conn.wg.Wait()
	// Dispatch after closing connection but before reinit
	// so event handlers can still access state information.
	conn.dispatch(&Line{Cmd: DISCONNECTED, Time: time.Now()})
	// reinit datastructures ready for next connection
	conn.initialise()
}

// Dumps a load of information about the current state of the connection to a
// string for debugging state tracking and other such things.
func (conn *Conn) String() string {
	str := "GoIRC Connection\n"
	str += "----------------\n\n"
	if conn.Connected() {
		str += "Connected to " + conn.cfg.Server + "\n\n"
	} else {
		str += "Not currently connected!\n\n"
	}
	str += conn.Me().String() + "\n"
	if conn.st != nil {
		str += conn.st.String() + "\n"
	}
	return str
}
<commit_msg>Alternative deadlock fix.<commit_after>package client

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"github.com/fluffle/goirc/logging"
	"github.com/fluffle/goirc/state"
	"io"
	"net"
	"strings"
	"sync"
	"time"
)

// An IRC connection is represented by this struct.
type Conn struct {
	// For preventing races on (dis)connect.
	mu sync.RWMutex

	// Contains parameters that people can tweak to change client behaviour.
	cfg *Config

	// Handlers
	intHandlers *hSet
	fgHandlers  *hSet
	bgHandlers  *hSet

	// State tracker for nicks and channels
	st         state.Tracker
	stRemovers []Remover

	// I/O stuff to server
	dialer    *net.Dialer
	sock      net.Conn
	io        *bufio.ReadWriter
	in        chan *Line
	out       chan string
	connected bool

	// Control channel and WaitGroup for goroutines
	die chan struct{}
	wg  sync.WaitGroup

	// Internal counters for flood protection
	badness  time.Duration
	lastsent time.Time
}

// Misc knobs to tweak client behaviour go in here
type Config struct {
	// Set this to provide the Nick, Ident and Name for the client to use.
	Me *state.Nick

	// Hostname to connect to and optional connect password.
	Server, Pass string

	// Are we connecting via SSL? Do we care about certificate validity?
	SSL       bool
	SSLConfig *tls.Config

	// Local address to connect to the server.
	LocalAddr string

	// Replaceable function to customise the 433 handler's new nick
	NewNick func(string) string

	// Client->server ping frequency, in seconds. Defaults to 3m.
	PingFreq time.Duration

	// Set this to true to disable flood protection and false to re-enable
	Flood bool

	// Sent as the reply to a CTCP VERSION message
	Version string

	// Sent as the QUIT message.
	QuitMessage string

	// Configurable panic recovery for all handlers.
	Recover func(*Conn, *Line)

	// Split PRIVMSGs, NOTICEs and CTCPs longer than
	// SplitLen characters over multiple lines.
	SplitLen int

	// Timeout, The amount of time in seconds until a timeout is triggered.
	Timeout time.Duration
}

func NewConfig(nick string, args ...string) *Config {
	cfg := &Config{
		Me:       &state.Nick{Nick: nick},
		PingFreq: 3 * time.Minute,
		NewNick:  func(s string) string { return s + "_" },
		Recover:  (*Conn).LogPanic, // in dispatch.go
		SplitLen: 450,
		Timeout:  60 * time.Second,
	}
	cfg.Me.Ident = "goirc"
	if len(args) > 0 && args[0] != "" {
		cfg.Me.Ident = args[0]
	}
	cfg.Me.Name = "Powered by GoIRC"
	if len(args) > 1 && args[1] != "" {
		cfg.Me.Name = args[1]
	}
	cfg.Version = "Powered by GoIRC"
	cfg.QuitMessage = "GoBye!"
	return cfg
}

// Creates a new IRC connection object, but doesn't connect to anything so
// that you can add event handlers to it. See AddHandler() for details
func SimpleClient(nick string, args ...string) *Conn {
	conn := Client(NewConfig(nick, args...))
	return conn
}

func Client(cfg *Config) *Conn {
	if cfg == nil {
		cfg = NewConfig("__idiot__")
	}
	if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" {
		cfg.Me = &state.Nick{Nick: "__idiot__"}
		cfg.Me.Ident = "goirc"
		cfg.Me.Name = "Powered by GoIRC"
	}

	dialer := new(net.Dialer)
	dialer.Timeout = cfg.Timeout
	if cfg.LocalAddr != "" {
		if !hasPort(cfg.LocalAddr) {
			cfg.LocalAddr += ":0"
		}

		local, err := net.ResolveTCPAddr("tcp", cfg.LocalAddr)
		if err == nil {
			dialer.LocalAddr = local
		} else {
			logging.Error("irc.Client(): Cannot resolve local address %s: %s", cfg.LocalAddr, err)
		}
	}

	conn := &Conn{
		cfg:         cfg,
		dialer:      dialer,
		in:          make(chan *Line, 32),
		out:         make(chan string, 32),
		intHandlers: handlerSet(),
		fgHandlers:  handlerSet(),
		bgHandlers:  handlerSet(),
		stRemovers:  make([]Remover, 0, len(stHandlers)),
		lastsent:    time.Now(),
	}
	conn.addIntHandlers()
	conn.initialise()
	return conn
}

func (conn *Conn) Connected() bool {
	conn.mu.RLock()
	defer conn.mu.RUnlock()
	return conn.connected
}

func (conn *Conn) Config() *Config {
	return conn.cfg
}

func (conn *Conn) Me() *state.Nick {
	conn.mu.RLock()
	defer conn.mu.RUnlock()
	if conn.st != nil {
		conn.cfg.Me = conn.st.Me()
	}
	return conn.cfg.Me
}

func (conn *Conn) StateTracker() state.Tracker {
	return conn.st
}

func (conn *Conn) EnableStateTracking() {
	conn.mu.Lock()
	defer conn.mu.Unlock()
	if conn.st == nil {
		n := conn.cfg.Me
		conn.st = state.NewTracker(n.Nick)
		conn.st.NickInfo(n.Nick, n.Ident, n.Host, n.Name)
		conn.cfg.Me = conn.st.Me()
		conn.addSTHandlers()
	}
}

func (conn *Conn) DisableStateTracking() {
	conn.mu.Lock()
	defer conn.mu.Unlock()
	if conn.st != nil {
		conn.cfg.Me = conn.st.Me()
		conn.delSTHandlers()
		conn.st.Wipe()
		conn.st = nil
	}
}

// Per-connection state initialisation.
func (conn *Conn) initialise() {
	conn.io = nil
	conn.sock = nil
	conn.die = make(chan struct{})
	if conn.st != nil {
		conn.st.Wipe()
	}
}

// Connect the IRC connection object to "host[:port]" which should be either
// a hostname or an IP address, with an optional port. To enable explicit SSL
// on the connection to the IRC server, set Conn.SSL to true before calling
// Connect(). The port will default to 6697 if ssl is enabled, and 6667
// otherwise. You can also provide an optional connect password.
func (conn *Conn) ConnectTo(host string, pass ...string) error {
	conn.cfg.Server = host
	if len(pass) > 0 {
		conn.cfg.Pass = pass[0]
	}
	return conn.Connect()
}

func (conn *Conn) Connect() error {
	conn.mu.Lock()
	defer conn.mu.Unlock()

	if conn.cfg.Server == "" {
		return fmt.Errorf("irc.Connect(): cfg.Server must be non-empty")
	}
	if conn.connected {
		return fmt.Errorf("irc.Connect(): Cannot connect to %s, already connected.", conn.cfg.Server)
	}
	if conn.cfg.SSL {
		if !hasPort(conn.cfg.Server) {
			conn.cfg.Server = net.JoinHostPort(conn.cfg.Server, "6697")
		}
		logging.Info("irc.Connect(): Connecting to %s with SSL.", conn.cfg.Server)
		if s, err := tls.DialWithDialer(conn.dialer, "tcp", conn.cfg.Server, conn.cfg.SSLConfig); err == nil {
			conn.sock = s
		} else {
			return err
		}
	} else {
		if !hasPort(conn.cfg.Server) {
			conn.cfg.Server = net.JoinHostPort(conn.cfg.Server, "6667")
		}
		logging.Info("irc.Connect(): Connecting to %s without SSL.", conn.cfg.Server)
		if s, err := conn.dialer.Dial("tcp", conn.cfg.Server); err == nil {
			conn.sock = s
		} else {
			return err
		}
	}
	conn.connected = true
	conn.postConnect(true)
	conn.dispatch(&Line{Cmd: REGISTER, Time: time.Now()})
	return nil
}

// Post-connection setup (for ease of testing)
func (conn *Conn) postConnect(start bool) {
	conn.io = bufio.NewReadWriter(
		bufio.NewReader(conn.sock),
		bufio.NewWriter(conn.sock))
	if start {
		conn.wg.Add(3)
		go conn.send()
		go conn.recv()
		go conn.runLoop()
		if conn.cfg.PingFreq > 0 {
			conn.wg.Add(1)
			go conn.ping()
		}
	}
}

// copied from http.client for great justice
func hasPort(s string) bool {
	return strings.LastIndex(s, ":") > strings.LastIndex(s, "]")
}

// goroutine to pass data from output channel to write()
func (conn *Conn) send() {
	for {
		select {
		case line := <-conn.out:
			if err := conn.write(line); err != nil {
				logging.Error("irc.send(): %s", err.Error())
				// We can't defer this, because shutdown() waits for it.
				conn.wg.Done()
				conn.shutdown()
				return
			}
		case <-conn.die:
			// control channel closed, bail out
			conn.wg.Done()
			return
		}
	}
}

// receive one \r\n terminated line from peer, parse and dispatch it
func (conn *Conn) recv() {
	for {
		s, err := conn.io.ReadString('\n')
		if err != nil {
			if err != io.EOF {
				logging.Error("irc.recv(): %s", err.Error())
			}
			// We can't defer this, because shutdown() waits for it.
			conn.wg.Done()
			conn.shutdown()
			return
		}
		s = strings.Trim(s, "\r\n")
		logging.Debug("<- %s", s)

		if line := ParseLine(s); line != nil {
			line.Time = time.Now()
			conn.in <- line
		} else {
			logging.Warn("irc.recv(): problems parsing line:\n  %s", s)
		}
	}
}

// Repeatedly pings the server every PingFreq seconds (no matter what)
func (conn *Conn) ping() {
	defer conn.wg.Done()
	tick := time.NewTicker(conn.cfg.PingFreq)
	for {
		select {
		case <-tick.C:
			conn.Ping(fmt.Sprintf("%d", time.Now().UnixNano()))
		case <-conn.die:
			// control channel closed, bail out
			tick.Stop()
			return
		}
	}
}

// goroutine to dispatch events for lines received on input channel
func (conn *Conn) runLoop() {
	defer conn.wg.Done()
	for {
		select {
		case line := <-conn.in:
			conn.dispatch(line)
		case <-conn.die:
			// control channel closed, bail out
			return
		}
	}
}

// Write a \r\n terminated line of output to the connected server,
// using Hybrid's algorithm to rate limit if conn.cfg.Flood is false.
func (conn *Conn) write(line string) error {
	if !conn.cfg.Flood {
		if t := conn.rateLimit(len(line)); t != 0 {
			// sleep for the current line's time value before sending it
			logging.Info("irc.rateLimit(): Flood! Sleeping for %.2f secs.",
				t.Seconds())
			<-time.After(t)
		}
	}

	if _, err := conn.io.WriteString(line + "\r\n"); err != nil {
		return err
	}
	if err := conn.io.Flush(); err != nil {
		return err
	}
	logging.Debug("-> %s", line)
	return nil
}

// Implement Hybrid's flood control algorithm to rate-limit outgoing lines.
func (conn *Conn) rateLimit(chars int) time.Duration {
	// Hybrid's algorithm allows for 2 seconds per line and an additional
	// 1/120 of a second per character on that line.
	linetime := 2*time.Second + time.Duration(chars)*time.Second/120
	elapsed := time.Now().Sub(conn.lastsent)
	if conn.badness += linetime - elapsed; conn.badness < 0 {
		// negative badness times are badness...
		conn.badness = 0
	}
	conn.lastsent = time.Now()
	// If we've sent more than 10 second's worth of lines according to the
	// calculation above, then we're at risk of "Excess Flood".
	if conn.badness > 10*time.Second {
		return linetime
	}
	return 0
}

func (conn *Conn) shutdown() {
	// Guard against double-call of shutdown() if we get an error in send()
	// as calling sock.Close() will cause recv() to receive EOF in readstring()
	conn.mu.Lock()
	defer conn.mu.Unlock()
	if !conn.connected {
		return
	}
	logging.Info("irc.shutdown(): Disconnected from server.")
	conn.connected = false
	conn.sock.Close()
	close(conn.die)
	conn.wg.Wait()
	// Dispatch after closing connection but before reinit
	// so event handlers can still access state information.
	conn.dispatch(&Line{Cmd: DISCONNECTED, Time: time.Now()})
	// reinit datastructures ready for next connection
	conn.initialise()
}

// Dumps a load of information about the current state of the connection to a
// string for debugging state tracking and other such things.
func (conn *Conn) String() string {
	str := "GoIRC Connection\n"
	str += "----------------\n\n"
	if conn.Connected() {
		str += "Connected to " + conn.cfg.Server + "\n\n"
	} else {
		str += "Not currently connected!\n\n"
	}
	str += conn.Me().String() + "\n"
	if conn.st != nil {
		str += conn.st.String() + "\n"
	}
	return str
}
<|endoftext|>


<commit_before>package db

import (
  . "seeme/models"

	"errors"
  "fmt"
)

func GetAllConnectionsMap(user string) (map[string]bool, error) {
  query := "SELECT connection FROM connections WHERE username = ? UNION SELECT username FROM connections WHERE connection = ?"
  return GetQueryResultsMap(query, user, user)
}

func GetConnectionsMap(user string) (map[string]bool, error) {
  query := "SELECT connection FROM connections WHERE username = ?"
  return GetQueryResultsMap(query, user)
}

func GetNetworkList(user string) ([]User, error) {
  query := "SELECT first_name, last_name, role, users.username, secret, discoverable, ssid, connections.status FROM users LEFT JOIN networks USING (network_id) INNER JOIN connections ON users.username = connections.connection AND connections.username = ? OR users.username = connections.username AND connections.connection = ?"
  userList, err := GetQueryUserList(query, 8, user, user)
  for i := 0; i < len(userList); i++ {
    primaryUser, _, err := getUserRelationship(user, userList[i].Username)
    if err != nil {
      return []User{}, err
    }
    fmt.Println(userList[i].Username + " " + primaryUser)

    if userList[i].Username == primaryUser {
      fmt.Println("pending")
    } else {
      fmt.Println("pending")
    }
  }
  return userList, err
}

func InsertNewConnection(username string, connection string) (error) {
	db := GetDatabaseInstance()
	_, err := db.Exec("INSERT INTO connections VALUES (?, ?, 'pending')", username, connection)
	return err
}

func UpdateConnectionStatus(username string, connection string) (error) {
	db := GetDatabaseInstance()
  primaryUser, connectUser, err := getUserRelationship(username, connection)
  if err != nil {
    return err
  }
	_, err = db.Exec("UPDATE connections SET status = 'connected' WHERE username = ? AND connection = ?",
												primaryUser, connectUser)
	return err
}

func DeleteConnection(username string, connection string) (int64, error) {
  primaryUser, connectUser, err := getUserRelationship(username, connection)
  if err != nil {
    return 0, errors.New("Connection Search Error!")
  }
  return PostDeleteQuery("DELETE FROM connections WHERE username = ? AND connection = ?",
                              primaryUser, connectUser)
}

func getUserRelationship(username string, connection string) (string, string, error) {
  userMap, err := GetConnectionsMap(connection)
  if err != nil {
    return username, connection, err
  }
  if userMap[username] {
    primaryUser := connection
    connectUser := username
    return primaryUser, connectUser, nil
  }

  return username, connection, nil
}

<commit_msg>added empty string returns in getUserRelationship on error && fixed logic issue in print statements in GetNetworkList<commit_after>package db

import (
  . "seeme/models"

	"errors"
  "fmt"
)

func GetAllConnectionsMap(user string) (map[string]bool, error) {
  query := "SELECT connection FROM connections WHERE username = ? UNION SELECT username FROM connections WHERE connection = ?"
  return GetQueryResultsMap(query, user, user)
}

func GetConnectionsMap(user string) (map[string]bool, error) {
  query := "SELECT connection FROM connections WHERE username = ?"
  return GetQueryResultsMap(query, user)
}

func GetNetworkList(user string) ([]User, error) {
  query := "SELECT first_name, last_name, role, users.username, secret, discoverable, ssid, connections.status FROM users LEFT JOIN networks USING (network_id) INNER JOIN connections ON users.username = connections.connection AND connections.username = ? OR users.username = connections.username AND connections.connection = ?"
  userList, err := GetQueryUserList(query, 8, user, user)
  for i := 0; i < len(userList); i++ {
    primaryUser, _, err := getUserRelationship(user, userList[i].Username)
    if err != nil {
      return []User{}, err
    }
    fmt.Println(userList[i].Username + " " + primaryUser)

    if user == primaryUser {
      fmt.Println("pending")
    } else {
      fmt.Println("request")
    }
  }
  return userList, err
}

func InsertNewConnection(username string, connection string) (error) {
	db := GetDatabaseInstance()
	_, err := db.Exec("INSERT INTO connections VALUES (?, ?, 'pending')", username, connection)
	return err
}

func UpdateConnectionStatus(username string, connection string) (error) {
	db := GetDatabaseInstance()
  primaryUser, connectUser, err := getUserRelationship(username, connection)
  if err != nil {
    return err
  }
	_, err = db.Exec("UPDATE connections SET status = 'connected' WHERE username = ? AND connection = ?",
												primaryUser, connectUser)
	return err
}

func DeleteConnection(username string, connection string) (int64, error) {
  primaryUser, connectUser, err := getUserRelationship(username, connection)
  if err != nil {
    return 0, errors.New("Connection Search Error!")
  }
  return PostDeleteQuery("DELETE FROM connections WHERE username = ? AND connection = ?",
                              primaryUser, connectUser)
}

func getUserRelationship(username string, connection string) (string, string, error) {
  userMap, err := GetConnectionsMap(connection)
  if err != nil {
    return "", "", err
  }
  if userMap[username] {
    primaryUser := connection
    connectUser := username
    return primaryUser, connectUser, nil
  }

  return username, connection, nil
}

<|endoftext|>


<commit_before>package stats

import (
	"fmt"
	"log"
	"math"
	"runtime"
	"sync"
	"time"

	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/disk"
	"github.com/shirou/gopsutil/host"
	"github.com/shirou/gopsutil/mem"
)

// HostStats represents resource usage stats of the host running a Nomad client
type HostStats struct {
	Memory           *MemoryStats
	CPU              []*CPUStats
	DiskStats        []*DiskStats
	AllocDirStats    *DiskStats
	Uptime           uint64
	Timestamp        int64
	CPUTicksConsumed float64
}

// MemoryStats represnts stats related to virtual memory usage
type MemoryStats struct {
	Total     uint64
	Available uint64
	Used      uint64
	Free      uint64
}

// CPUStats represents stats related to cpu usage
type CPUStats struct {
	CPU    string
	User   float64
	System float64
	Idle   float64
	Total  float64
}

// DiskStats represents stats related to disk usage
type DiskStats struct {
	Device            string
	Mountpoint        string
	Size              uint64
	Used              uint64
	Available         uint64
	UsedPercent       float64
	InodesUsedPercent float64
}

// NodeStatsCollector is an interface which is used for the puproses of mocking
// the HostStatsCollector in the tests
type NodeStatsCollector interface {
	Collect() error
	Stats() *HostStats
}

// HostStatsCollector collects host resource usage stats
type HostStatsCollector struct {
	numCores        int
	statsCalculator map[string]*HostCpuStatsCalculator
	logger          *log.Logger
	hostStats       *HostStats
	hostStatsLock   sync.RWMutex
	allocDir        string

	// badParts is a set of partitions whose usage cannot be read; used to
	// squelch logspam.
	badParts map[string]struct{}
}

// NewHostStatsCollector returns a HostStatsCollector. The allocDir is passed in
// so that we can present the disk related statistics for the mountpoint where
// the allocation directory lives
func NewHostStatsCollector(logger *log.Logger, allocDir string) *HostStatsCollector {
	numCores := runtime.NumCPU()
	statsCalculator := make(map[string]*HostCpuStatsCalculator)
	collector := &HostStatsCollector{
		statsCalculator: statsCalculator,
		numCores:        numCores,
		logger:          logger,
		allocDir:        allocDir,
		badParts:        make(map[string]struct{}),
	}
	return collector
}

// Collect collects stats related to resource usage of a host
func (h *HostStatsCollector) Collect() error {
	h.hostStatsLock.Lock()
	defer h.hostStatsLock.Unlock()
	return h.collectLocked()
}

// collectLocked collects stats related to resource usage of the host but should
// be called with the lock held.
func (h *HostStatsCollector) collectLocked() error {
	hs := &HostStats{Timestamp: time.Now().UTC().UnixNano()}

	// Determine up-time
	uptime, err := host.Uptime()
	if err != nil {
		return err
	}
	hs.Uptime = uptime

	// Collect memory stats
	mstats, err := h.collectMemoryStats()
	if err != nil {
		return err
	}
	hs.Memory = mstats

	// Collect cpu stats
	cpus, ticks, err := h.collectCPUStats()
	if err != nil {
		return err
	}
	hs.CPU = cpus
	hs.CPUTicksConsumed = ticks

	// Collect disk stats
	diskStats, err := h.collectDiskStats()
	if err != nil {
		return err
	}
	hs.DiskStats = diskStats

	// Getting the disk stats for the allocation directory
	usage, err := disk.Usage(h.allocDir)
	if err != nil {
		return fmt.Errorf("failed to find disk usage of alloc_dir %q: %v", h.allocDir, err)
	}
	hs.AllocDirStats = h.toDiskStats(usage, nil)

	// Update the collected status object.
	h.hostStats = hs

	return nil
}

func (h *HostStatsCollector) collectMemoryStats() (*MemoryStats, error) {
	memStats, err := mem.VirtualMemory()
	if err != nil {
		return nil, err
	}
	mem := &MemoryStats{
		Total:     memStats.Total,
		Available: memStats.Available,
		Used:      memStats.Used,
		Free:      memStats.Free,
	}

	return mem, nil
}

func (h *HostStatsCollector) collectDiskStats() ([]*DiskStats, error) {
	partitions, err := disk.Partitions(false)
	if err != nil {
		return nil, err
	}

	var diskStats []*DiskStats
	for _, partition := range partitions {
		usage, err := disk.Usage(partition.Mountpoint)
		if err != nil {
			if _, ok := h.badParts[partition.Mountpoint]; ok {
				// already known bad, don't log again
				continue
			}

			h.badParts[partition.Mountpoint] = struct{}{}
			h.logger.Printf("[WARN] client: error fetching host disk usage stats for %v: %v", partition.Mountpoint, err)
			continue
		}
		delete(h.badParts, partition.Mountpoint)

		ds := h.toDiskStats(usage, &partition)
		diskStats = append(diskStats, ds)
	}

	return diskStats, nil
}

// Stats returns the host stats that has been collected
func (h *HostStatsCollector) Stats() *HostStats {
	h.hostStatsLock.RLock()
	defer h.hostStatsLock.RUnlock()

	if h.hostStats == nil {
		if err := h.collectLocked(); err != nil {
			h.logger.Printf("[WARN] client: error fetching host resource usage stats: %v", err)
		}
	}

	return h.hostStats
}

// toDiskStats merges UsageStat and PartitionStat to create a DiskStat
func (h *HostStatsCollector) toDiskStats(usage *disk.UsageStat, partitionStat *disk.PartitionStat) *DiskStats {
	ds := DiskStats{
		Size:              usage.Total,
		Used:              usage.Used,
		Available:         usage.Free,
		UsedPercent:       usage.UsedPercent,
		InodesUsedPercent: usage.InodesUsedPercent,
	}
	if math.IsNaN(ds.UsedPercent) {
		ds.UsedPercent = 0.0
	}
	if math.IsNaN(ds.InodesUsedPercent) {
		ds.InodesUsedPercent = 0.0
	}

	if partitionStat != nil {
		ds.Device = partitionStat.Device
		ds.Mountpoint = partitionStat.Mountpoint
	}

	return &ds
}

// HostCpuStatsCalculator calculates cpu usage percentages
type HostCpuStatsCalculator struct {
	prevIdle   float64
	prevUser   float64
	prevSystem float64
	prevBusy   float64
	prevTotal  float64
}

// NewHostCpuStatsCalculator returns a HostCpuStatsCalculator
func NewHostCpuStatsCalculator() *HostCpuStatsCalculator {
	return &HostCpuStatsCalculator{}
}

// Calculate calculates the current cpu usage percentages
func (h *HostCpuStatsCalculator) Calculate(times cpu.TimesStat) (idle float64, user float64, system float64, total float64) {
	currentIdle := times.Idle
	currentUser := times.User
	currentSystem := times.System
	currentTotal := times.Total()
	currentBusy := times.User + times.System + times.Nice + times.Iowait + times.Irq +
		times.Softirq + times.Steal + times.Guest + times.GuestNice + times.Stolen

	deltaTotal := currentTotal - h.prevTotal
	idle = ((currentIdle - h.prevIdle) / deltaTotal) * 100
	user = ((currentUser - h.prevUser) / deltaTotal) * 100
	system = ((currentSystem - h.prevSystem) / deltaTotal) * 100
	total = ((currentBusy - h.prevBusy) / deltaTotal) * 100

	// Protect against any invalid values
	if math.IsNaN(idle) || math.IsInf(idle, 0) {
		idle = 100.0
	}
	if math.IsNaN(user) || math.IsInf(user, 0) {
		user = 0.0
	}
	if math.IsNaN(system) || math.IsInf(system, 0) {
		system = 0.0
	}
	if math.IsNaN(total) || math.IsInf(total, 0) {
		total = 0.0
	}

	h.prevIdle = currentIdle
	h.prevUser = currentUser
	h.prevSystem = currentSystem
	h.prevTotal = currentTotal
	h.prevBusy = currentBusy
	return
}
<commit_msg>spelling: purposes<commit_after>package stats

import (
	"fmt"
	"log"
	"math"
	"runtime"
	"sync"
	"time"

	"github.com/shirou/gopsutil/cpu"
	"github.com/shirou/gopsutil/disk"
	"github.com/shirou/gopsutil/host"
	"github.com/shirou/gopsutil/mem"
)

// HostStats represents resource usage stats of the host running a Nomad client
type HostStats struct {
	Memory           *MemoryStats
	CPU              []*CPUStats
	DiskStats        []*DiskStats
	AllocDirStats    *DiskStats
	Uptime           uint64
	Timestamp        int64
	CPUTicksConsumed float64
}

// MemoryStats represnts stats related to virtual memory usage
type MemoryStats struct {
	Total     uint64
	Available uint64
	Used      uint64
	Free      uint64
}

// CPUStats represents stats related to cpu usage
type CPUStats struct {
	CPU    string
	User   float64
	System float64
	Idle   float64
	Total  float64
}

// DiskStats represents stats related to disk usage
type DiskStats struct {
	Device            string
	Mountpoint        string
	Size              uint64
	Used              uint64
	Available         uint64
	UsedPercent       float64
	InodesUsedPercent float64
}

// NodeStatsCollector is an interface which is used for the purposes of mocking
// the HostStatsCollector in the tests
type NodeStatsCollector interface {
	Collect() error
	Stats() *HostStats
}

// HostStatsCollector collects host resource usage stats
type HostStatsCollector struct {
	numCores        int
	statsCalculator map[string]*HostCpuStatsCalculator
	logger          *log.Logger
	hostStats       *HostStats
	hostStatsLock   sync.RWMutex
	allocDir        string

	// badParts is a set of partitions whose usage cannot be read; used to
	// squelch logspam.
	badParts map[string]struct{}
}

// NewHostStatsCollector returns a HostStatsCollector. The allocDir is passed in
// so that we can present the disk related statistics for the mountpoint where
// the allocation directory lives
func NewHostStatsCollector(logger *log.Logger, allocDir string) *HostStatsCollector {
	numCores := runtime.NumCPU()
	statsCalculator := make(map[string]*HostCpuStatsCalculator)
	collector := &HostStatsCollector{
		statsCalculator: statsCalculator,
		numCores:        numCores,
		logger:          logger,
		allocDir:        allocDir,
		badParts:        make(map[string]struct{}),
	}
	return collector
}

// Collect collects stats related to resource usage of a host
func (h *HostStatsCollector) Collect() error {
	h.hostStatsLock.Lock()
	defer h.hostStatsLock.Unlock()
	return h.collectLocked()
}

// collectLocked collects stats related to resource usage of the host but should
// be called with the lock held.
func (h *HostStatsCollector) collectLocked() error {
	hs := &HostStats{Timestamp: time.Now().UTC().UnixNano()}

	// Determine up-time
	uptime, err := host.Uptime()
	if err != nil {
		return err
	}
	hs.Uptime = uptime

	// Collect memory stats
	mstats, err := h.collectMemoryStats()
	if err != nil {
		return err
	}
	hs.Memory = mstats

	// Collect cpu stats
	cpus, ticks, err := h.collectCPUStats()
	if err != nil {
		return err
	}
	hs.CPU = cpus
	hs.CPUTicksConsumed = ticks

	// Collect disk stats
	diskStats, err := h.collectDiskStats()
	if err != nil {
		return err
	}
	hs.DiskStats = diskStats

	// Getting the disk stats for the allocation directory
	usage, err := disk.Usage(h.allocDir)
	if err != nil {
		return fmt.Errorf("failed to find disk usage of alloc_dir %q: %v", h.allocDir, err)
	}
	hs.AllocDirStats = h.toDiskStats(usage, nil)

	// Update the collected status object.
	h.hostStats = hs

	return nil
}

func (h *HostStatsCollector) collectMemoryStats() (*MemoryStats, error) {
	memStats, err := mem.VirtualMemory()
	if err != nil {
		return nil, err
	}
	mem := &MemoryStats{
		Total:     memStats.Total,
		Available: memStats.Available,
		Used:      memStats.Used,
		Free:      memStats.Free,
	}

	return mem, nil
}

func (h *HostStatsCollector) collectDiskStats() ([]*DiskStats, error) {
	partitions, err := disk.Partitions(false)
	if err != nil {
		return nil, err
	}

	var diskStats []*DiskStats
	for _, partition := range partitions {
		usage, err := disk.Usage(partition.Mountpoint)
		if err != nil {
			if _, ok := h.badParts[partition.Mountpoint]; ok {
				// already known bad, don't log again
				continue
			}

			h.badParts[partition.Mountpoint] = struct{}{}
			h.logger.Printf("[WARN] client: error fetching host disk usage stats for %v: %v", partition.Mountpoint, err)
			continue
		}
		delete(h.badParts, partition.Mountpoint)

		ds := h.toDiskStats(usage, &partition)
		diskStats = append(diskStats, ds)
	}

	return diskStats, nil
}

// Stats returns the host stats that has been collected
func (h *HostStatsCollector) Stats() *HostStats {
	h.hostStatsLock.RLock()
	defer h.hostStatsLock.RUnlock()

	if h.hostStats == nil {
		if err := h.collectLocked(); err != nil {
			h.logger.Printf("[WARN] client: error fetching host resource usage stats: %v", err)
		}
	}

	return h.hostStats
}

// toDiskStats merges UsageStat and PartitionStat to create a DiskStat
func (h *HostStatsCollector) toDiskStats(usage *disk.UsageStat, partitionStat *disk.PartitionStat) *DiskStats {
	ds := DiskStats{
		Size:              usage.Total,
		Used:              usage.Used,
		Available:         usage.Free,
		UsedPercent:       usage.UsedPercent,
		InodesUsedPercent: usage.InodesUsedPercent,
	}
	if math.IsNaN(ds.UsedPercent) {
		ds.UsedPercent = 0.0
	}
	if math.IsNaN(ds.InodesUsedPercent) {
		ds.InodesUsedPercent = 0.0
	}

	if partitionStat != nil {
		ds.Device = partitionStat.Device
		ds.Mountpoint = partitionStat.Mountpoint
	}

	return &ds
}

// HostCpuStatsCalculator calculates cpu usage percentages
type HostCpuStatsCalculator struct {
	prevIdle   float64
	prevUser   float64
	prevSystem float64
	prevBusy   float64
	prevTotal  float64
}

// NewHostCpuStatsCalculator returns a HostCpuStatsCalculator
func NewHostCpuStatsCalculator() *HostCpuStatsCalculator {
	return &HostCpuStatsCalculator{}
}

// Calculate calculates the current cpu usage percentages
func (h *HostCpuStatsCalculator) Calculate(times cpu.TimesStat) (idle float64, user float64, system float64, total float64) {
	currentIdle := times.Idle
	currentUser := times.User
	currentSystem := times.System
	currentTotal := times.Total()
	currentBusy := times.User + times.System + times.Nice + times.Iowait + times.Irq +
		times.Softirq + times.Steal + times.Guest + times.GuestNice + times.Stolen

	deltaTotal := currentTotal - h.prevTotal
	idle = ((currentIdle - h.prevIdle) / deltaTotal) * 100
	user = ((currentUser - h.prevUser) / deltaTotal) * 100
	system = ((currentSystem - h.prevSystem) / deltaTotal) * 100
	total = ((currentBusy - h.prevBusy) / deltaTotal) * 100

	// Protect against any invalid values
	if math.IsNaN(idle) || math.IsInf(idle, 0) {
		idle = 100.0
	}
	if math.IsNaN(user) || math.IsInf(user, 0) {
		user = 0.0
	}
	if math.IsNaN(system) || math.IsInf(system, 0) {
		system = 0.0
	}
	if math.IsNaN(total) || math.IsInf(total, 0) {
		total = 0.0
	}

	h.prevIdle = currentIdle
	h.prevUser = currentUser
	h.prevSystem = currentSystem
	h.prevTotal = currentTotal
	h.prevBusy = currentBusy
	return
}
<|endoftext|>


<commit_before>/*
 Brassica is a simple and resource efficient CMS for low dynamic
 private and small business sites with mostly static pages and simple
 structure.

 brassica/brassica-serve contains a command to start a httpd.
*/
package brassica

import (
	"fmt"
	"github.com/hoisie/mustache"
	"io/ioutil"
	"launchpad.net/goyaml"
	"net/http"
	"os"
	"path/filepath"
)

// Settings for the application and the site.
type Settings struct {
	// Path to the data directory.
	Root string

	// Path to the template directory.
	Templates string

	// Path to the static files.
	Statics string

	// Path to the site specific static files.
	SiteStatics string
}

// GetSettings returns application and site settings.
func GetSettings() Settings {
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	settings := Settings{
		Root:        wd,
		Templates:   filepath.Join(filepath.Dir(wd), "templates"),
		Statics:     filepath.Join(filepath.Dir(wd), "static"),
		SiteStatics: filepath.Join(filepath.Dir(wd), "site-static")}
	return settings
}

type masterTmplEnv struct {
	Node                     Node
	PrimaryNav, SecondaryNav []navLink
}

// Renderer represents a template renderer.
type Renderer interface {
	// RenderInMaster renders the named template with the given context
	// and master template environment in the master template.
	RenderInMaster(name string, context map[string]string,
		env *masterTmplEnv, settings Settings) string
}

type renderer struct {
	// Root is the absolute path to the template directory.
	Root string
	// MasterTemplate holds the parsed master template.
	MasterTemplate *mustache.Template
}

// Render renders the named template with given context.
func (r renderer) Render(name string, context map[string]string) string {
	path := filepath.Join(r.Root, name)
	content := mustache.RenderFile(path, context)
	return content
}

// NewRenderer returns a new Renderer.
//
// root is the absolute path to the template directory.
func NewRenderer(root string) Renderer {
	var r renderer
	r.Root = root
	path := filepath.Join(r.Root, "master.html")
	tmpl, err := mustache.ParseFile(path)
	r.MasterTemplate = tmpl
	if err != nil {
		panic("Could not load master template: " + err.Error())
	}
	return r
}

func (r renderer) RenderInMaster(name string, context map[string]string,
	env *masterTmplEnv, settings Settings) string {
	content := r.Render(name, context)
	sidebarContent := getSidebar(env.Node.Path(), settings.Root)
	showSidebar := len(env.SecondaryNav) > 0 || len(sidebarContent) > 0
	belowHeader := getBelowHeader(env.Node.Path(), settings.Root)
	return r.MasterTemplate.Render(env, map[string]interface{}{
		"ShowBelowHeader":  len(belowHeader) > 0,
		"BelowHeader":      belowHeader,
		"Footer":           getFooter(settings.Root),
		"Sidebar":          sidebarContent,
		"Content":          content,
		"ShowSecondaryNav": len(env.SecondaryNav) > 0,
		"ShowSidebar":      showSidebar})
}

// getFooter retrieves the footer.
//
// root is the path to the data directory
//
// Returns an empty string if there is no footer.
func getFooter(root string) string {
	path := filepath.Join(root, "footer.html")
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return ""
	}
	return string(content)
}

// getBelowHeader retrieves the below header content for the given node.
//
// path is the node's path.
// root is the path to the data directory.
//
// Returns an empty string if there is no below header content.
func getBelowHeader(path, root string) string {
	file := filepath.Join(root, path, "below_header.html")
	content, err := ioutil.ReadFile(file)
	if err != nil {
		return ""
	}
	return string(content)
}

// getSidebar retrieves the sidebar content for the given node.
//
// path is the node's path.
// root is the path to the data directory.
//
// It traverses up to the root until it finds a node with defined sidebar
// content.
//
// Returns an empty string if there is no sidebar content.
func getSidebar(path, root string) string {
	for {
		file := filepath.Join(root, path, "sidebar.html")
		content, err := ioutil.ReadFile(file)
		if err != nil {
			if path == filepath.Dir(path) {
				break
			}
			path = filepath.Dir(path)
			continue
		}
		return string(content)
	}
	return ""
}

// navLink represents a link in the navigation.
type navLink struct {
	Name, Target string
	Active       bool
}

// getNav returns the navigation for the given node.
//
// node is the path of the node for which to get the navigation.
// active is the currently active node.
// root is the path of the data directory.
//
// The keys of the returned map are the link titles, the values are
// the link targets.
func getNav(node, active, root string) []navLink {
	path := filepath.Join(root, node, "navigation.yaml")
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return nil
	}
	var navLinks []navLink
	goyaml.Unmarshal(content, &navLinks)
	for i, link := range navLinks {
		if link.Target == active {
			navLinks[i].Active = true
			break
		}
	}
	return navLinks
}

// Node is the interface implemented by the various node types
// (Documents, Images, ...).
type Node interface {
	// Path returns the node's path (e.g. "/node").
	Path() string

	// Title returns the node's title.
	Title() string

	// Description returns the node's description.
	Description() string

	// Get handles a GET request on the node.
	Get(w http.ResponseWriter, r *http.Request, renderer Renderer,
		settings Settings)

	// Post handles a POST request on the node.
	Post(w http.ResponseWriter, r *http.Request, renderer Renderer,
		settings Settings)
}

// node is the base implementation for nodes.
type node struct {
	path string
	data nodeData
}

// nodeData is used for (un)marshaling from/to node.yaml.
type nodeData struct {
	Description string
	Title       string
	Type        string
}

func (n node) Path() string {
	return n.path
}

func (n node) Title() string {
	return n.data.Title
}

func (n node) Description() string {
	return n.data.Description
}

// Document is a node consisting of a html body.
type Document struct {
	node
	Body string
}

func (n Document) Get(w http.ResponseWriter, r *http.Request,
	renderer Renderer, settings Settings) {
	prinav := getNav("/", n.Path(), settings.Root)
	var secnav []navLink = nil
	if n.Path() != "/" {
		secnav = getNav(n.Path(), n.Path(), settings.Root)
	}
	env := masterTmplEnv{
		Node:         n,
		PrimaryNav:   prinav,
		SecondaryNav: secnav}
	content := renderer.RenderInMaster("view/document.html",
		map[string]string{"body": n.Body}, &env, settings)
	fmt.Fprint(w, content)
}

func (n Document) Post(w http.ResponseWriter, r *http.Request,
	renderer Renderer, settings Settings) {
	http.Error(w, "Implementation missing.", http.StatusInternalServerError)
}

// NodeFile is the filename of node description files.
const NodeFile = "node.yaml"

// lookup_node look ups a node at the given path.
// If no such node exists, return nil.
func LookupNode(root, path string) (Node, error) {
	node_path := filepath.Join(root, path[1:], NodeFile)
	content, err := ioutil.ReadFile(node_path)
	if err != nil {
		return nil, err
	}
	var node = new(Document)
	body_path := filepath.Join(root, path[1:], "body.html")
	body, err := ioutil.ReadFile(body_path)
	if err != nil {
		panic("Body not found: " + body_path)
	}
	node.Body = string(body)
	var data nodeData
	goyaml.Unmarshal(content, &data)
	node.data = data
	node.path = path
	return node, nil
}
<commit_msg>Add node property to hide the sidebar<commit_after>/*
 Brassica is a simple and resource efficient CMS for low dynamic
 private and small business sites with mostly static pages and simple
 structure.

 brassica/brassica-serve contains a command to start a httpd.
*/
package brassica

import (
	"fmt"
	"github.com/hoisie/mustache"
	"io/ioutil"
	"launchpad.net/goyaml"
	"net/http"
	"os"
	"path/filepath"
)

// Settings for the application and the site.
type Settings struct {
	// Path to the data directory.
	Root string

	// Path to the template directory.
	Templates string

	// Path to the static files.
	Statics string

	// Path to the site specific static files.
	SiteStatics string
}

// GetSettings returns application and site settings.
func GetSettings() Settings {
	wd, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	settings := Settings{
		Root:        wd,
		Templates:   filepath.Join(filepath.Dir(wd), "templates"),
		Statics:     filepath.Join(filepath.Dir(wd), "static"),
		SiteStatics: filepath.Join(filepath.Dir(wd), "site-static")}
	return settings
}

type masterTmplEnv struct {
	Node                     Node
	PrimaryNav, SecondaryNav []navLink
}

// Renderer represents a template renderer.
type Renderer interface {
	// RenderInMaster renders the named template with the given context
	// and master template environment in the master template.
	RenderInMaster(name string, context map[string]string,
		env *masterTmplEnv, settings Settings) string
}

type renderer struct {
	// Root is the absolute path to the template directory.
	Root string
	// MasterTemplate holds the parsed master template.
	MasterTemplate *mustache.Template
}

// Render renders the named template with given context.
func (r renderer) Render(name string, context map[string]string) string {
	path := filepath.Join(r.Root, name)
	content := mustache.RenderFile(path, context)
	return content
}

// NewRenderer returns a new Renderer.
//
// root is the absolute path to the template directory.
func NewRenderer(root string) Renderer {
	var r renderer
	r.Root = root
	path := filepath.Join(r.Root, "master.html")
	tmpl, err := mustache.ParseFile(path)
	r.MasterTemplate = tmpl
	if err != nil {
		panic("Could not load master template: " + err.Error())
	}
	return r
}

func (r renderer) RenderInMaster(name string, context map[string]string,
	env *masterTmplEnv, settings Settings) string {
	content := r.Render(name, context)
	sidebarContent := getSidebar(env.Node.Path(), settings.Root)
	showSidebar := (len(env.SecondaryNav) > 0 || len(sidebarContent) > 0) &&
		!env.Node.HideSidebar()
	belowHeader := getBelowHeader(env.Node.Path(), settings.Root)
	return r.MasterTemplate.Render(env, map[string]interface{}{
		"ShowBelowHeader":  len(belowHeader) > 0,
		"BelowHeader":      belowHeader,
		"Footer":           getFooter(settings.Root),
		"Sidebar":          sidebarContent,
		"Content":          content,
		"ShowSecondaryNav": len(env.SecondaryNav) > 0,
		"ShowSidebar":      showSidebar})
}

// getFooter retrieves the footer.
//
// root is the path to the data directory
//
// Returns an empty string if there is no footer.
func getFooter(root string) string {
	path := filepath.Join(root, "footer.html")
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return ""
	}
	return string(content)
}

// getBelowHeader retrieves the below header content for the given node.
//
// path is the node's path.
// root is the path to the data directory.
//
// Returns an empty string if there is no below header content.
func getBelowHeader(path, root string) string {
	file := filepath.Join(root, path, "below_header.html")
	content, err := ioutil.ReadFile(file)
	if err != nil {
		return ""
	}
	return string(content)
}

// getSidebar retrieves the sidebar content for the given node.
//
// path is the node's path.
// root is the path to the data directory.
//
// It traverses up to the root until it finds a node with defined sidebar
// content.
//
// Returns an empty string if there is no sidebar content.
func getSidebar(path, root string) string {
	for {
		file := filepath.Join(root, path, "sidebar.html")
		content, err := ioutil.ReadFile(file)
		if err != nil {
			if path == filepath.Dir(path) {
				break
			}
			path = filepath.Dir(path)
			continue
		}
		return string(content)
	}
	return ""
}

// navLink represents a link in the navigation.
type navLink struct {
	Name, Target string
	Active       bool
}

// getNav returns the navigation for the given node.
//
// node is the path of the node for which to get the navigation.
// active is the currently active node.
// root is the path of the data directory.
//
// The keys of the returned map are the link titles, the values are
// the link targets.
func getNav(node, active, root string) []navLink {
	path := filepath.Join(root, node, "navigation.yaml")
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return nil
	}
	var navLinks []navLink
	goyaml.Unmarshal(content, &navLinks)
	for i, link := range navLinks {
		if link.Target == active {
			navLinks[i].Active = true
			break
		}
	}
	return navLinks
}

// Node is the interface implemented by the various node types
// (Documents, Images, ...).
type Node interface {
	// Path returns the node's path (e.g. "/node").
	Path() string

	// Title returns the node's title.
	Title() string

	// Description returns the node's description.
	Description() string

	// HideSidebar returns if the node's sidebar should be hidden.
	HideSidebar() bool

	// Get handles a GET request on the node.
	Get(w http.ResponseWriter, r *http.Request, renderer Renderer,
		settings Settings)

	// Post handles a POST request on the node.
	Post(w http.ResponseWriter, r *http.Request, renderer Renderer,
		settings Settings)
}

// node is the base implementation for nodes.
type node struct {
	path string
	data nodeData
}

// nodeData is used for (un)marshaling from/to node.yaml.
type nodeData struct {
	Description  string
	Title        string
	Type         string
	Hide_sidebar bool
}

func (n node) Path() string {
	return n.path
}

func (n node) Title() string {
	return n.data.Title
}

func (n node) Description() string {
	return n.data.Description
}

func (n node) HideSidebar() bool {
	return n.data.Hide_sidebar
}

// Document is a node consisting of a html body.
type Document struct {
	node
	Body string
}

func (n Document) Get(w http.ResponseWriter, r *http.Request,
	renderer Renderer, settings Settings) {
	prinav := getNav("/", n.Path(), settings.Root)
	var secnav []navLink = nil
	if n.Path() != "/" {
		secnav = getNav(n.Path(), n.Path(), settings.Root)
	}
	env := masterTmplEnv{
		Node:         n,
		PrimaryNav:   prinav,
		SecondaryNav: secnav}
	content := renderer.RenderInMaster("view/document.html",
		map[string]string{"body": n.Body}, &env, settings)
	fmt.Fprint(w, content)
}

func (n Document) Post(w http.ResponseWriter, r *http.Request,
	renderer Renderer, settings Settings) {
	http.Error(w, "Implementation missing.", http.StatusInternalServerError)
}

// NodeFile is the filename of node description files.
const NodeFile = "node.yaml"

// lookup_node look ups a node at the given path.
// If no such node exists, return nil.
func LookupNode(root, path string) (Node, error) {
	node_path := filepath.Join(root, path[1:], NodeFile)
	content, err := ioutil.ReadFile(node_path)
	if err != nil {
		return nil, err
	}
	var node = new(Document)
	body_path := filepath.Join(root, path[1:], "body.html")
	body, err := ioutil.ReadFile(body_path)
	if err != nil {
		panic("Body not found: " + body_path)
	}
	node.Body = string(body)
	var data nodeData
	goyaml.Unmarshal(content, &data)
	node.data = data
	node.path = path
	return node, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/mholt/archiver"
)

func main() {
	if len(os.Args) < 3 {
		fatal(usage)
	}

	cmd, filename := os.Args[1], os.Args[2]

	for _, ff := range archiver.SupportedFormats {
		if !ff.Match(filename) {
			continue
		}
		var err error
		switch cmd {
		case "make":
			if len(os.Args) < 4 {
				fatal(usage)
			}
			err = ff.Make(filename, os.Args[3:])
		case "open":
			dest := ""
			if len(os.Args) == 4 {
				dest = os.Args[3]
			} else if len(os.Args) > 4 {
				fatal(usage)
			}
			err = ff.Open(filename, dest)
		default:
			fatal(usage)
		}
		if err != nil {
			fatal(err)
		}
		return
	}

	fatalf("%s: Unsupported file extension", filename)
}

func fatal(v ...interface{}) {
	fmt.Fprintln(os.Stderr, v...)
	os.Exit(1)
}

func fatalf(s string, v ...interface{}) {
	fmt.Fprintf(os.Stderr, s+"\n", v...)
	os.Exit(1)
}

const usage = `Usage: archiver {make|open} <archive file> [files...]
  make
    Create a new archive file. List the files/folders
    to include in the archive; at least one required.
  open
    Extract an archive file. Give only the archive to
    open and the destination folder to extract into.

  Specifying archive format:
    The format of the archive is determined by its
    file extension. Supported extensions:
      .zip
      .tar
      .tar.gz
      .tgz
      .tar.bz2
      .tbz2
      .tar.xz
      .txz
      .tar.lz4
      .tlz4
      .tar.sz
      .tsz
      .tar.zst
      .tzst
      .rar (open only)

  Existing files:
    When creating an archive file that already exists,
    archiver will overwrite the existing file. When
    extracting files, archiver will NOT overwrite files
    that already exist in the destination path; this
    is treated as an error and extraction will abort.`
<commit_msg>Fix import path issue<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/railsmechanic/archiver"
)

func main() {
	if len(os.Args) < 3 {
		fatal(usage)
	}

	cmd, filename := os.Args[1], os.Args[2]

	for _, ff := range archiver.SupportedFormats {
		if !ff.Match(filename) {
			continue
		}
		var err error
		switch cmd {
		case "make":
			if len(os.Args) < 4 {
				fatal(usage)
			}
			err = ff.Make(filename, os.Args[3:])
		case "open":
			dest := ""
			if len(os.Args) == 4 {
				dest = os.Args[3]
			} else if len(os.Args) > 4 {
				fatal(usage)
			}
			err = ff.Open(filename, dest)
		default:
			fatal(usage)
		}
		if err != nil {
			fatal(err)
		}
		return
	}

	fatalf("%s: Unsupported file extension", filename)
}

func fatal(v ...interface{}) {
	fmt.Fprintln(os.Stderr, v...)
	os.Exit(1)
}

func fatalf(s string, v ...interface{}) {
	fmt.Fprintf(os.Stderr, s+"\n", v...)
	os.Exit(1)
}

const usage = `Usage: archiver {make|open} <archive file> [files...]
  make
    Create a new archive file. List the files/folders
    to include in the archive; at least one required.
  open
    Extract an archive file. Give only the archive to
    open and the destination folder to extract into.

  Specifying archive format:
    The format of the archive is determined by its
    file extension. Supported extensions:
      .zip
      .tar
      .tar.gz
      .tgz
      .tar.bz2
      .tbz2
      .tar.xz
      .txz
      .tar.lz4
      .tlz4
      .tar.sz
      .tsz
      .tar.zst
      .tzst
      .rar (open only)

  Existing files:
    When creating an archive file that already exists,
    archiver will overwrite the existing file. When
    extracting files, archiver will NOT overwrite files
    that already exist in the destination path; this
    is treated as an error and extraction will abort.`
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"runtime"
	"strings"
)

import "github.com/hdm/golang-mtbl"

var compression_types = map[string]int{
	"none":   mtbl.COMPRESSION_NONE,
	"snappy": mtbl.COMPRESSION_SNAPPY,
	"zlib":   mtbl.COMPRESSION_ZLIB,
	"lz4":    mtbl.COMPRESSION_LZ4,
	"lz4hc":  mtbl.COMPRESSION_LZ4HC,
}

func fail(args ...interface{}) {
	fmt.Fprintln(os.Stderr, args...)
	os.Exit(-1)
}

func warn(args ...interface{}) {
	fmt.Fprintln(os.Stderr, args...)
}

func usage() {
	fmt.Println("Usage: " + os.Args[0] + " [options]")
	fmt.Println("")
	fmt.Println("Creates a MTBL database from a CSV input.")
	fmt.Println("")
	fmt.Println("Options:")
	flag.PrintDefaults()
}

func mergeFunc(key []byte, val0 []byte, val1 []byte) (mergedVal []byte) {
	return []byte(string(val0) + "\n" + string(val1))
}

func reverseKey(s string) string {
	b := make([]byte, len(s))
	var j int = len(s) - 1
	for i := 0; i <= j; i++ {
		b[j-i] = s[i]
	}
	return string(b)
}

func main() {

	runtime.GOMAXPROCS(runtime.NumCPU())

	flag.Usage = func() { usage() }

	index_key := flag.Int("k", 1, "The field index to use as the key")
	index_val := flag.Int("v", 2, "The field index to use as the value")
	reverse_key := flag.Bool("r", false, "Store the key in reverse order")
	max_fields := flag.Int("M", -1, "The maximum number of fields to parse with the delimiter")
	compression := flag.String("c", "snappy", "The compression type to use (none, snappy, zlib, lz4, lz4hc)")
	delimiter := flag.String("d", ",", "The delimiter to use as a field separator")
	sort_skip := flag.Bool("S", false, "Skip the sorting phase and assume keys are in pre-sorted order")
	sort_tmp := flag.String("t", "", "The temporary directory to use for the sorting phase")
	sort_mem := flag.Uint64("m", 1, "The maximum amount of memory to use, in gigabytes, for the sorting phase")

	flag.Parse()

	if len(flag.Args()) != 1 {
		usage()
		os.Exit(1)
	}

	fname := flag.Args()[0]

	sort_opt := mtbl.SorterOptions{Merge: mergeFunc, MaxMemory: 1000000000}
	sort_opt.MaxMemory *= *sort_mem
	if len(*sort_tmp) > 0 {
		sort_opt.TempDir = *sort_tmp
	}

	compression_alg, ok := compression_types[*compression]
	if !ok {
		fmt.Fprintf(os.Stderr, "Invalid compression algorithm: %s\n", *compression)
		os.Exit(1)
	}

	s := mtbl.SorterInit(&sort_opt)
	defer s.Destroy()

	w, we := mtbl.WriterInit(fname, &mtbl.WriterOptions{Compression: compression_alg})
	defer w.Destroy()

	if we != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", we)
		os.Exit(1)
	}

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		raw := strings.TrimSpace(scanner.Text())
		if len(raw) == 0 {
			continue
		}

		bits := strings.SplitN(raw, *delimiter, *max_fields)

		if len(bits) < *index_key {
			fmt.Fprintf(os.Stderr, "No key: %s\n", raw)
			continue
		}

		if len(bits) < *index_val {
			fmt.Fprintf(os.Stderr, "No value: %s\n", raw)
			continue
		}

		kstr := bits[*index_key-1]
		if len(kstr) == 0 {
			continue
		}

		vstr := bits[*index_val-1]
		if len(vstr) == 0 {
			continue
		}

		if *reverse_key {
			kstr = reverseKey(kstr)
		}

		if *sort_skip {
			if e := w.Add([]byte(kstr), []byte(vstr)); e != nil {
				fmt.Printf("Failed to add %v -> %v: %v\n", kstr, vstr, e)
			}
		} else {
			if e := s.Add([]byte(kstr), []byte(vstr)); e != nil {
				fmt.Printf("Failed to add %v -> %v: %v\n", kstr, vstr, e)
			}
		}
	}

	if !*sort_skip {
		if e := s.Write(w); e != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", e)
			os.Exit(1)
		}
	}
}
<commit_msg>Switch back to upstream golang-mtbl<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"os"
	"runtime"
	"strings"
)

import "github.com/edmonds/golang-mtbl"

var compression_types = map[string]int{
	"none":   mtbl.COMPRESSION_NONE,
	"snappy": mtbl.COMPRESSION_SNAPPY,
	"zlib":   mtbl.COMPRESSION_ZLIB,
	"lz4":    mtbl.COMPRESSION_LZ4,
	"lz4hc":  mtbl.COMPRESSION_LZ4HC,
}

func fail(args ...interface{}) {
	fmt.Fprintln(os.Stderr, args...)
	os.Exit(-1)
}

func warn(args ...interface{}) {
	fmt.Fprintln(os.Stderr, args...)
}

func usage() {
	fmt.Println("Usage: " + os.Args[0] + " [options]")
	fmt.Println("")
	fmt.Println("Creates a MTBL database from a CSV input.")
	fmt.Println("")
	fmt.Println("Options:")
	flag.PrintDefaults()
}

func mergeFunc(key []byte, val0 []byte, val1 []byte) (mergedVal []byte) {
	return []byte(string(val0) + "\n" + string(val1))
}

func reverseKey(s string) string {
	b := make([]byte, len(s))
	var j int = len(s) - 1
	for i := 0; i <= j; i++ {
		b[j-i] = s[i]
	}
	return string(b)
}

func main() {

	runtime.GOMAXPROCS(runtime.NumCPU())

	flag.Usage = func() { usage() }

	index_key := flag.Int("k", 1, "The field index to use as the key")
	index_val := flag.Int("v", 2, "The field index to use as the value")
	reverse_key := flag.Bool("r", false, "Store the key in reverse order")
	max_fields := flag.Int("M", -1, "The maximum number of fields to parse with the delimiter")
	compression := flag.String("c", "snappy", "The compression type to use (none, snappy, zlib, lz4, lz4hc)")
	delimiter := flag.String("d", ",", "The delimiter to use as a field separator")
	sort_skip := flag.Bool("S", false, "Skip the sorting phase and assume keys are in pre-sorted order")
	sort_tmp := flag.String("t", "", "The temporary directory to use for the sorting phase")
	sort_mem := flag.Uint64("m", 1, "The maximum amount of memory to use, in gigabytes, for the sorting phase")

	flag.Parse()

	if len(flag.Args()) != 1 {
		usage()
		os.Exit(1)
	}

	fname := flag.Args()[0]

	sort_opt := mtbl.SorterOptions{Merge: mergeFunc, MaxMemory: 1000000000}
	sort_opt.MaxMemory *= *sort_mem
	if len(*sort_tmp) > 0 {
		sort_opt.TempDir = *sort_tmp
	}

	compression_alg, ok := compression_types[*compression]
	if !ok {
		fmt.Fprintf(os.Stderr, "Invalid compression algorithm: %s\n", *compression)
		os.Exit(1)
	}

	s := mtbl.SorterInit(&sort_opt)
	defer s.Destroy()

	w, we := mtbl.WriterInit(fname, &mtbl.WriterOptions{Compression: compression_alg})
	defer w.Destroy()

	if we != nil {
		fmt.Fprintf(os.Stderr, "Error: %s\n", we)
		os.Exit(1)
	}

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		raw := strings.TrimSpace(scanner.Text())
		if len(raw) == 0 {
			continue
		}

		bits := strings.SplitN(raw, *delimiter, *max_fields)

		if len(bits) < *index_key {
			fmt.Fprintf(os.Stderr, "No key: %s\n", raw)
			continue
		}

		if len(bits) < *index_val {
			fmt.Fprintf(os.Stderr, "No value: %s\n", raw)
			continue
		}

		kstr := bits[*index_key-1]
		if len(kstr) == 0 {
			continue
		}

		vstr := bits[*index_val-1]
		if len(vstr) == 0 {
			continue
		}

		if *reverse_key {
			kstr = reverseKey(kstr)
		}

		if *sort_skip {
			if e := w.Add([]byte(kstr), []byte(vstr)); e != nil {
				fmt.Printf("Failed to add %v -> %v: %v\n", kstr, vstr, e)
			}
		} else {
			if e := s.Add([]byte(kstr), []byte(vstr)); e != nil {
				fmt.Printf("Failed to add %v -> %v: %v\n", kstr, vstr, e)
			}
		}
	}

	if !*sort_skip {
		if e := s.Write(w); e != nil {
			fmt.Fprintf(os.Stderr, "Error: %s\n", e)
			os.Exit(1)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"
)

//  ## Sheet "A Test":
//  	                                                          https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1573083976&single=true&output=csv
//  	wget --no-check-certificate --output-document=a-test.csv 'https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1573083976&single=true&output=csv'
//
//
//
//  ## Sheet "Keywords":
//  		                                                        https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=791408233&single=true&output=csv
//  	wget --no-check-certificate --output-document=keywords.csv 'https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=791408233&single=true&output=csv'
//
//  ## Sheet "Enums":
//  	                                                         https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1933632029&single=true&output=csv
//  	wget --no-check-certificate --output-document=enums.csv 'https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1933632029&single=true&output=csv'

//x func main() {
//x 	res, err := http.Get("http://www.google.com/robots.txt")
//x 	if err != nil {
//x 		log.Fatal(err)
//x 	}
//x 	robots, err := ioutil.ReadAll(res.Body)
//x 	res.Body.Close()
//x 	if err != nil {
//x 		log.Fatal(err)
//x 	}
//x 	fmt.Printf("%s", robots)
//x }

// todo download f0
// todo download f1
// todo download f2
// todo
// todo getDateTimeStamp - 20170716_hhmmss
// todo os.MkdirAll(home/.cefmdd_v1/stamp)
// todo
// todo rename ~/.cefmdd_v1/atest.csv, stamp/atest.csv
// todo rename ~/.cefmdd_v1/atest.csv, stamp/keywords.csv
// todo rename ~/.cefmdd_v1/atest.csv, stamp/enums.csv
// todo
// todo save f0 ~/.cefmdd_v1/atest.csv
// todo save f1 ~/.cefmdd_v1/atest.csv
// todo save f2 ~/.cefmdd_v1/atest.csv
// todo

func httpGetFile(fn string) (contents string, err error) {

//	res, err := http.Get("http://www.google.com/robots.txt")
	res, err := http.Get(fn)
	if err != nil {
		log.Fatal(err)
	}
	b, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}
	contents = string(b)

	fmt.Printf("%s\n", contents)

	return
}

func main() {

	files := map[string]string {
		"a-test.csv": 	`https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1573083976&single=true&output=csv`,		// A Test
		"keywords.csv": `https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=791408233&single=true&output=csv`,			// Keywords
		"enums.csv": 	`https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1933632029&single=true&output=csv`,		// Enums
	}

	file_contents := make(map[string]string)

	for fn, url := range files {
		contents, err := httpGetFile(url)
		if err != nil {
			log.Fatal(err)
		}

		file_contents[fn] = contents
	}

	t := time.Now()
	s := fmt.Sprintf("%04d-%02d-%02d_%02d.%02d.%02d",
		t.Year(),
	    t.Month(),
	    t.Day(),
	    t.Hour(),
	    t.Minute(),
	    t.Second())

	fmt.Println(s)

}<commit_msg>csvget<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"

    "github.com/caa-dev-apps/cefmdd_v1/pkg/diag"
)

//  ## Sheet "A Test":
//  	                                                          https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1573083976&single=true&output=csv
//  	wget --no-check-certificate --output-document=a-test.csv 'https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1573083976&single=true&output=csv'
//
//
//
//  ## Sheet "Keywords":
//  		                                                        https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=791408233&single=true&output=csv
//  	wget --no-check-certificate --output-document=keywords.csv 'https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=791408233&single=true&output=csv'
//
//  ## Sheet "Enums":
//  	                                                         https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1933632029&single=true&output=csv
//  	wget --no-check-certificate --output-document=enums.csv 'https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1933632029&single=true&output=csv'

//x func main() {
//x 	res, err := http.Get("http://www.google.com/robots.txt")
//x 	if err != nil {
//x 		log.Fatal(err)
//x 	}
//x 	robots, err := ioutil.ReadAll(res.Body)
//x 	res.Body.Close()
//x 	if err != nil {
//x 		log.Fatal(err)
//x 	}
//x 	fmt.Printf("%s", robots)
//x }

// todo download f0
// todo download f1
// todo download f2
// todo
// todo getDateTimeStamp - 20170716_hhmmss
// todo os.MkdirAll(home/.cefmdd_v1/stamp)
// todo
// todo rename ~/.cefmdd_v1/atest.csv, stamp/atest.csv
// todo rename ~/.cefmdd_v1/atest.csv, stamp/keywords.csv
// todo rename ~/.cefmdd_v1/atest.csv, stamp/enums.csv
// todo
// todo save f0 ~/.cefmdd_v1/atest.csv
// todo save f1 ~/.cefmdd_v1/atest.csv
// todo save f2 ~/.cefmdd_v1/atest.csv
// todo

func httpGetFile(fn string) (contents string, err error) {

//	res, err := http.Get("http://www.google.com/robots.txt")
	res, err := http.Get(fn)
	if err != nil {
		log.Fatal(err)
	}
	b, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}
	contents = string(b)

	fmt.Printf("%s\n", contents)

	return
}

func main() {
    diag.Info(diag.Yellow("csvget v0.0.0, (16 July 2017)"))

	files := map[string]string {
		"a-test.csv": 	`https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1573083976&single=true&output=csv`,		// A Test
		"keywords.csv": `https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=791408233&single=true&output=csv`,			// Keywords
		"enums.csv": 	`https://docs.google.com/spreadsheets/d/1CFFyoD4pKRq0vyaEWm8pnX3hxrjEEfzG69f3j1PTuHw/pub?gid=1933632029&single=true&output=csv`,		// Enums
	}

	file_contents := make(map[string]string)

	for fn, url := range files {
		contents, err := httpGetFile(url)
		if err != nil {
			log.Fatal(err)
		}

		file_contents[fn] = contents
	}

	t := time.Now()
	s := fmt.Sprintf("%04d-%02d-%02d_%02d.%02d.%02d",
		t.Year(),
	    t.Month(),
	    t.Day(),
	    t.Hour(),
	    t.Minute(),
	    t.Second())

	fmt.Println(s)

    diag.Info(diag.Yellow("CAA Rocks!\n"))
}<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

var (
	addr       = flag.String("http", ":8080", "HTTP address to serve on")
	tlsAddr    = flag.String("https", "", "HTTPS address to serve on")
	pemFile    = flag.String("pem-file", "", "HTTPS / SSL certificate .pem file")
	keyFile    = flag.String("key-file", "", "HTTPS / SSL certificate .key file")
	update     = flag.Bool("update", true, "update via Git and shutdown server after pull")
	updateRate = flag.String("update-rate", "60s", "rate at which to check for updates via Git")
	dev        = flag.Bool("dev", false, "reload all templates on each request")

	errorTemplate = "error"      // Template to use for errors.
	templateGlob  = "**/*.tmpl"  // Glob to match all template files.
	templateDir   = "templates/" // Relative directory that templates reside in.

	src = &GitUpdater{
		Dir: ".",
	}
	lastUpdate  = time.Now()
	tmpls       *template.Template
	updateRateT time.Duration
)

func reloadTemplates() error {
	matches, err := filepath.Glob(templateGlob)
	if err != nil {
		return err
	}
	tmpls = template.New("")
	for _, m := range matches {
		name := strings.TrimPrefix(m, templateDir)
		name = strings.TrimSuffix(name, filepath.Ext(name))
		if _, err := tmpls.New(name).Parse(m); err != nil {
			return err
		}
	}
	return nil
}

type TemplateData struct {
	Request *http.Request
	Error   string
}

func handler(w http.ResponseWriter, r *http.Request) error {
	if *dev {
		if err := reloadTemplates(); err != nil {
			return err
		}
	}

	var data = &TemplateData{
		Request: r,
	}

	// Determine template name.
	tmplName := r.URL.Path[1:]
	if stat, err := os.Stat(filepath.Join(templateDir, tmplName)); err != nil {
		return err
	} else {
		if stat.IsDir() {
			tmplName = "index"
		}
	}

	// Find the requested page.
	root := tmpls.Lookup(tmplName)
	if root == nil {
		// Couldn't find the page, render the error page.
		w.WriteHeader(http.StatusNotFound)
		return fmt.Errorf("%v - %v", http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	// Execute the requested template page.
	return root.Execute(w, data)
}

func logHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%v %v\n", r.Method, r.URL)
		h.ServeHTTP(w, r)
	})
}

func errorHandler(h func(w http.ResponseWriter, r *http.Request) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := h(w, r)
		if err == nil {
			return
		}

		// Render the error template.
		data := &TemplateData{
			Request: r,
			Error:   err.Error(),
		}
		if err := tmpls.ExecuteTemplate(w, errorTemplate, data); err != nil {
			panic(err)
		}
	})
}

func checkForUpdates() {
	// If not enough time has elapsed, skip the update.
	if time.Since(lastUpdate) < updateRateT {
		return
	}
	lastUpdate = time.Now()

	// Check for updates.
	updated, err := src.Update()
	if err != nil {
		log.Println("Update error:", err)
		return
	}
	if updated {
		log.Println("Updated source code. Exiting server..")
		os.Exit(0)
	}
	log.Println("checked for updates (none available)")
}

func init() {
	if err := reloadTemplates(); err != nil {
		log.Fatal(err)
	}

	if *update {
		go func() {
			for {
				time.Sleep(updateRateT)
				checkForUpdates()
			}
		}()
	}
}

func main() {
	flag.Parse()

	// Parse update rate flag.
	var err error
	updateRateT, err = time.ParseDuration(*updateRate)
	if err != nil {
		log.Fatal(err)
	}

	// Static file hosting
	http.Handle("/static/", logHandler(http.StripPrefix("/static/", http.FileServer(http.Dir("static/")))))

	// App handler.
	http.Handle("/", logHandler(errorHandler(handler)))

	// Start HTTPS server:
	if *tlsAddr != "" {
		go func() {
			if *pemFile == "" {
				log.Fatal("expected -pem-file flag")
			}
			if *keyFile == "" {
				log.Fatal("expected -key-file flag")
			}
			log.Println("Serving on", *tlsAddr)
			log.Fatal(http.ListenAndServeTLS(*tlsAddr, *pemFile, *keyFile, nil))
		}()
	}

	// Start HTTP server:
	log.Println("Serving on", *addr)
	log.Fatal(http.ListenAndServe(*addr, nil))
}
<commit_msg>cmd/fiz3d-org: add constants for static file directory<commit_after>package main

import (
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

var (
	addr       = flag.String("http", ":8080", "HTTP address to serve on")
	tlsAddr    = flag.String("https", "", "HTTPS address to serve on")
	pemFile    = flag.String("pem-file", "", "HTTPS / SSL certificate .pem file")
	keyFile    = flag.String("key-file", "", "HTTPS / SSL certificate .key file")
	update     = flag.Bool("update", true, "update via Git and shutdown server after pull")
	updateRate = flag.String("update-rate", "60s", "rate at which to check for updates via Git")
	dev        = flag.Bool("dev", false, "reload all templates on each request")

	staticDirPrefix = "/static/" // Path prefix to strip to get static dir root.
	staticDir       = "static/"  // Directory to serve for static files.

	errorTemplate = "error"      // Template to use for errors.
	templateGlob  = "**/*.tmpl"  // Glob to match all template files.
	templateDir   = "templates/" // Relative directory that templates reside in.

	src = &GitUpdater{
		Dir: ".",
	}
	lastUpdate  = time.Now()
	tmpls       *template.Template
	updateRateT time.Duration
)

func reloadTemplates() error {
	matches, err := filepath.Glob(templateGlob)
	if err != nil {
		return err
	}
	tmpls = template.New("")
	for _, m := range matches {
		name := strings.TrimPrefix(m, templateDir)
		name = strings.TrimSuffix(name, filepath.Ext(name))
		if _, err := tmpls.New(name).Parse(m); err != nil {
			return err
		}
	}
	return nil
}

type TemplateData struct {
	Request *http.Request
	Error   string
}

func handler(w http.ResponseWriter, r *http.Request) error {
	if *dev {
		if err := reloadTemplates(); err != nil {
			return err
		}
	}

	var data = &TemplateData{
		Request: r,
	}

	// Determine template name.
	tmplName := r.URL.Path[1:]
	if stat, err := os.Stat(filepath.Join(templateDir, tmplName)); err != nil {
		return err
	} else {
		if stat.IsDir() {
			tmplName = "index"
		}
	}

	// Find the requested page.
	root := tmpls.Lookup(tmplName)
	if root == nil {
		// Couldn't find the page, render the error page.
		w.WriteHeader(http.StatusNotFound)
		return fmt.Errorf("%v - %v", http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	// Execute the requested template page.
	return root.Execute(w, data)
}

func logHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%v %v\n", r.Method, r.URL)
		h.ServeHTTP(w, r)
	})
}

func errorHandler(h func(w http.ResponseWriter, r *http.Request) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := h(w, r)
		if err == nil {
			return
		}

		// Render the error template.
		data := &TemplateData{
			Request: r,
			Error:   err.Error(),
		}
		if err := tmpls.ExecuteTemplate(w, errorTemplate, data); err != nil {
			panic(err)
		}
	})
}

func checkForUpdates() {
	// If not enough time has elapsed, skip the update.
	if time.Since(lastUpdate) < updateRateT {
		return
	}
	lastUpdate = time.Now()

	// Check for updates.
	updated, err := src.Update()
	if err != nil {
		log.Println("Update error:", err)
		return
	}
	if updated {
		log.Println("Updated source code. Exiting server..")
		os.Exit(0)
	}
	log.Println("checked for updates (none available)")
}

func init() {
	if err := reloadTemplates(); err != nil {
		log.Fatal(err)
	}

	if *update {
		go func() {
			for {
				time.Sleep(updateRateT)
				checkForUpdates()
			}
		}()
	}
}

func main() {
	flag.Parse()

	// Parse update rate flag.
	var err error
	updateRateT, err = time.ParseDuration(*updateRate)
	if err != nil {
		log.Fatal(err)
	}

	// Static file hosting
	http.Handle("/static/", logHandler(http.StripPrefix(staticDirPrefix, http.FileServer(http.Dir(staticDir)))))

	// App handler.
	http.Handle("/", logHandler(errorHandler(handler)))

	// Start HTTPS server:
	if *tlsAddr != "" {
		go func() {
			if *pemFile == "" {
				log.Fatal("expected -pem-file flag")
			}
			if *keyFile == "" {
				log.Fatal("expected -key-file flag")
			}
			log.Println("Serving on", *tlsAddr)
			log.Fatal(http.ListenAndServeTLS(*tlsAddr, *pemFile, *keyFile, nil))
		}()
	}

	// Start HTTP server:
	log.Println("Serving on", *addr)
	log.Fatal(http.ListenAndServe(*addr, nil))
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/jpillora/flickr-bot/flickr"
	"github.com/jpillora/opts"
)

type Config struct {
	API    string
	Secret string
	Token  string
	Cmd    string `type:"cmdname"`
	Run    struct {
		Method string   `type:"arg"`
		Args   []string `type:"args"`
	}
	GetToken  struct{}
	Photosets struct{}
	Photos    struct {
		Set string `type:"arg"`
	}
	Merge struct {
		Dest    string   `type:"arg"`
		Sources []string `type:"args" min:"1"`
	}
	MergeMatch struct {
		Dest    string `type:"arg"`
		Matcher string `type:"arg"`
	}
}

func main() {
	conf := Config{}
	opts.Parse(&conf)

	client := flickr.New(conf.API, conf.Secret)

	user, err := client.UserFromToken(conf.Token)
	if conf.Token != "" && err != nil {
		log.Fatal(err)
	}

	log.Printf("Running %s...", conf.Cmd)

	switch conf.Cmd {
	//===================================
	case "run":
		args := flickr.Args{}
		for _, a := range conf.Run.Args {
			kv := strings.SplitN(a, "=", 2)
			if len(kv) != 2 {
				log.Fatalf("Invalid arg '%s'", a)
			}
			args[kv[0]] = kv[1]
		}
		if user == nil {
			client.Test(conf.Run.Method, args)
		} else {
			user.Test(conf.Run.Method, args)
		}
	//===================================
	case "gettoken":
		frob, url, err := client.GetAuthURL()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Please visit\n\n=> %s\n\nand allow access\n", url)
		var user *flickr.User = nil
		for i := 0; i < 30; i++ {
			user, err = client.UserFromFrob(frob)
			if err != nil {
				log.Printf("Attempt #%02d: %s...", i+1, err)
				time.Sleep(3 * time.Second)
				continue
			}
			break
		}
		if user == nil {
			log.Fatal("Timeout")
		}
		log.Printf("Authenticated as %s with token:\n\n=> %s", user.Fullname, user.Token)
	//===================================
	case "photosets":
		if user == nil {
			log.Fatal("Token required")
		}
		sets, err := user.Photosets()
		check(err)
		for i, s := range sets {
			log.Printf("%s #%03d %50s (%04d photos)", s.ID, i+1, s.Title, s.CountPhotos)
		}
	//===================================
	case "photos":
		if user == nil {
			log.Fatal("Token required")
		}
		user.Test("flickr.photosets.getInfo", flickr.Args{"photoset_id": conf.Photos.Set})
	//===================================
	case "merge":
		if user == nil {
			log.Fatal("Token required")
		}

		dest, err := user.Photoset(conf.Merge.Dest)
		check(err)
		for _, srcID := range conf.Merge.Sources {
			src, err := user.Photoset(srcID)
			check(err)
			log.Printf("Moving photos from %s...", src.Title)
			for i, photo := range src.Photos {
				_, err = user.Do("flickr.photosets.addphoto", flickr.Args{"photoset_id": dest.ID, "photo_id": photo.ID})
				check(err)
				_, err = user.Do("flickr.photosets.removephoto", flickr.Args{"photoset_id": src.ID, "photo_id": photo.ID})
				check(err)
				log.Printf("Moved photo %d", i+1)
			}
		}
	//===================================
	case "mergematch":
		if user == nil {
			log.Fatal("Token required")
		}

		re, err := regexp.Compile(conf.MergeMatch.Matcher)
		check(err)

		dest, err := user.Photoset(conf.MergeMatch.Dest)
		check(err)
		log.Printf("Destination photoset %s", dest.Title)

		sets, err := user.Photosets()
		check(err)
		for _, src := range sets {
			if !re.MatchString(string(src.Title)) {
				continue
			}
			err = user.LoadPhotos(src)
			check(err)
			log.Printf("Moving photos from %s...", src.Title)
			for i, photo := range src.Photos {
				log.Printf("Moving photo %d (%s)...", i+1, photo.ID)
				_, err = user.Do("flickr.photosets.addphoto", flickr.Args{"photoset_id": dest.ID, "photo_id": photo.ID})
				if err != nil && err.Error() != "Photo already in set" {
					check(err)
				}
				_, err = user.Do("flickr.photosets.removephoto", flickr.Args{"photoset_id": src.ID, "photo_id": photo.ID})
				check(err)
			}
		}
	}
}

func check(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>skip existing<commit_after>package main

import (
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/jpillora/flickr-bot/flickr"
	"github.com/jpillora/opts"
)

type Config struct {
	API    string
	Secret string
	Token  string
	Cmd    string `type:"cmdname"`
	Run    struct {
		Method string   `type:"arg"`
		Args   []string `type:"args"`
	}
	GetToken  struct{}
	Photosets struct{}
	Photos    struct {
		Set string `type:"arg"`
	}
	Merge struct {
		Dest    string   `type:"arg"`
		Sources []string `type:"args" min:"1"`
	}
	MergeMatch struct {
		Dest    string `type:"arg"`
		Matcher string `type:"arg"`
	}
}

func main() {
	conf := Config{}
	opts.Parse(&conf)

	client := flickr.New(conf.API, conf.Secret)

	user, err := client.UserFromToken(conf.Token)
	if conf.Token != "" && err != nil {
		log.Fatal(err)
	}

	log.Printf("Running %s...", conf.Cmd)

	switch conf.Cmd {
	//===================================
	case "run":
		args := flickr.Args{}
		for _, a := range conf.Run.Args {
			kv := strings.SplitN(a, "=", 2)
			if len(kv) != 2 {
				log.Fatalf("Invalid arg '%s'", a)
			}
			args[kv[0]] = kv[1]
		}
		if user == nil {
			client.Test(conf.Run.Method, args)
		} else {
			user.Test(conf.Run.Method, args)
		}
	//===================================
	case "gettoken":
		frob, url, err := client.GetAuthURL()
		if err != nil {
			log.Fatal(err)
		}
		log.Printf("Please visit\n\n=> %s\n\nand allow access\n", url)
		var user *flickr.User = nil
		for i := 0; i < 30; i++ {
			user, err = client.UserFromFrob(frob)
			if err != nil {
				log.Printf("Attempt #%02d: %s...", i+1, err)
				time.Sleep(3 * time.Second)
				continue
			}
			break
		}
		if user == nil {
			log.Fatal("Timeout")
		}
		log.Printf("Authenticated as %s with token:\n\n=> %s", user.Fullname, user.Token)
	//===================================
	case "photosets":
		if user == nil {
			log.Fatal("Token required")
		}
		sets, err := user.Photosets()
		check(err)
		for i, s := range sets {
			log.Printf("%s #%03d %50s (%04d photos)", s.ID, i+1, s.Title, s.CountPhotos)
		}
	//===================================
	case "photos":
		if user == nil {
			log.Fatal("Token required")
		}
		user.Test("flickr.photosets.getInfo", flickr.Args{"photoset_id": conf.Photos.Set})
	//===================================
	case "merge":
		if user == nil {
			log.Fatal("Token required")
		}

		dest, err := user.Photoset(conf.Merge.Dest)
		check(err)
		for _, srcID := range conf.Merge.Sources {
			src, err := user.Photoset(srcID)
			check(err)
			log.Printf("Moving photos from %s...", src.Title)
			for i, photo := range src.Photos {
				_, err = user.Do("flickr.photosets.addphoto", flickr.Args{"photoset_id": dest.ID, "photo_id": photo.ID})
				if err != nil && err.Error() != "Photo already in set" {
					check(err)
				}
				_, err = user.Do("flickr.photosets.removephoto", flickr.Args{"photoset_id": src.ID, "photo_id": photo.ID})
				check(err)
				log.Printf("Moved photo %d", i+1)
			}
		}
	//===================================
	case "mergematch":
		if user == nil {
			log.Fatal("Token required")
		}

		re, err := regexp.Compile(conf.MergeMatch.Matcher)
		check(err)

		dest, err := user.Photoset(conf.MergeMatch.Dest)
		check(err)
		log.Printf("Destination photoset %s", dest.Title)

		sets, err := user.Photosets()
		check(err)
		for _, src := range sets {
			if !re.MatchString(string(src.Title)) {
				continue
			}
			err = user.LoadPhotos(src)
			check(err)
			log.Printf("Moving photos from %s...", src.Title)
			for i, photo := range src.Photos {
				log.Printf("Moving photo %d (%s)...", i+1, photo.ID)
				_, err = user.Do("flickr.photosets.addphoto", flickr.Args{"photoset_id": dest.ID, "photo_id": photo.ID})
				if err != nil && err.Error() != "Photo already in set" {
					check(err)
				}
				_, err = user.Do("flickr.photosets.removephoto", flickr.Args{"photoset_id": src.ID, "photo_id": photo.ID})
				check(err)
			}
		}
	}
}

func check(err error) {
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// 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 main

import (
	"context"
	"os"
	"os/signal"
	"syscall"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	flag "github.com/spf13/pflag"
	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	habitatclient "github.com/kinvolk/habitat-operator/pkg/habitat/client"
	habitatcontroller "github.com/kinvolk/habitat-operator/pkg/habitat/controller"
)

type Config struct {
	Client kubernetes.Interface
}

func run() int {
	// Parse config flags.
	kubeconfig := flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
	verbose := flag.BoolP("verbose", "v", false, "Enable verbose logging.")
	flag.Parse()

	// Set up logging.
	logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))

	if *verbose {
		logger = level.NewFilter(logger, level.AllowDebug())
	} else {
		logger = level.NewFilter(logger, level.AllowInfo())
	}

	// Build operator config.
	config, err := buildConfig(*kubeconfig)
	if err != nil {
		level.Error(logger).Log("msg", err)
		return 1
	}

	// This is the clientset for interacting with the apiextensions group.
	apiextensionsclientset, err := apiextensionsclient.NewForConfig(config)
	if err != nil {
		level.Error(logger).Log("msg", err)
		return 1
	}

	// Create ServiceGroup CRD.
	_, crdErr := habitatclient.CreateCRD(apiextensionsclientset)
	if crdErr != nil {
		if !apierrors.IsAlreadyExists(crdErr) {
			level.Error(logger).Log("msg", crdErr)
			return 1
		}

		level.Info(logger).Log("msg", "ServiceGroup CRD already exists, continuing")
	} else {
		level.Info(logger).Log("msg", "created ServiceGroup CRD")
	}

	habitatClient, scheme, err := habitatclient.NewClient(config)
	if err != nil {
		level.Error(logger).Log("msg", err)
		return 1
	}

	// This is the clientset for interacting with the stable API group.
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		level.Error(logger).Log("msg", err)
		return 1
	}

	controllerConfig := habitatcontroller.Config{
		HabitatClient:       habitatClient,
		KubernetesClientset: clientset,
		Scheme:              scheme,
	}
	hc, err := habitatcontroller.New(controllerConfig, log.With(logger, "component", "controller"))
	if err != nil {
		level.Error(logger).Log("msg", err)
		return 1
	}

	ctx, cancelFunc := context.WithCancel(context.Background())
	defer cancelFunc()
	go hc.Run(ctx)

	term := make(chan os.Signal)
	// Relay these signals to the `term` channel.
	signal.Notify(term, syscall.SIGINT, syscall.SIGTERM)

	select {
	case <-term:
		level.Info(logger).Log("msg", "received SIGTERM, exiting gracefully...")
	case <-ctx.Done():
		level.Info(logger).Log("msg", "context channel closed, exiting")
	}

	return 0
}

func main() {
	os.Exit(run())
}

func buildConfig(kubeconfig string) (*rest.Config, error) {
	if kubeconfig != "" {
		return clientcmd.BuildConfigFromFlags("", kubeconfig)
	}

	return rest.InClusterConfig()
}
<commit_msg>main: remove unnecessary function<commit_after>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// 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 main

import (
	"context"
	"os"
	"os/signal"
	"syscall"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	flag "github.com/spf13/pflag"
	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"

	habitatclient "github.com/kinvolk/habitat-operator/pkg/habitat/client"
	habitatcontroller "github.com/kinvolk/habitat-operator/pkg/habitat/controller"
)

type Config struct {
	Client kubernetes.Interface
}

func run() int {
	// Parse config flags.
	kubeconfig := flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
	verbose := flag.BoolP("verbose", "v", false, "Enable verbose logging.")
	flag.Parse()

	// Set up logging.
	logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))

	if *verbose {
		logger = level.NewFilter(logger, level.AllowDebug())
	} else {
		logger = level.NewFilter(logger, level.AllowInfo())
	}

	// Build operator config.
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		level.Error(logger).Log("msg", err)
		return 1
	}

	// This is the clientset for interacting with the apiextensions group.
	apiextensionsclientset, err := apiextensionsclient.NewForConfig(config)
	if err != nil {
		level.Error(logger).Log("msg", err)
		return 1
	}

	// Create ServiceGroup CRD.
	_, crdErr := habitatclient.CreateCRD(apiextensionsclientset)
	if crdErr != nil {
		if !apierrors.IsAlreadyExists(crdErr) {
			level.Error(logger).Log("msg", crdErr)
			return 1
		}

		level.Info(logger).Log("msg", "ServiceGroup CRD already exists, continuing")
	} else {
		level.Info(logger).Log("msg", "created ServiceGroup CRD")
	}

	habitatClient, scheme, err := habitatclient.NewClient(config)
	if err != nil {
		level.Error(logger).Log("msg", err)
		return 1
	}

	// This is the clientset for interacting with the stable API group.
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		level.Error(logger).Log("msg", err)
		return 1
	}

	controllerConfig := habitatcontroller.Config{
		HabitatClient:       habitatClient,
		KubernetesClientset: clientset,
		Scheme:              scheme,
	}
	hc, err := habitatcontroller.New(controllerConfig, log.With(logger, "component", "controller"))
	if err != nil {
		level.Error(logger).Log("msg", err)
		return 1
	}

	ctx, cancelFunc := context.WithCancel(context.Background())
	defer cancelFunc()
	go hc.Run(ctx)

	term := make(chan os.Signal)
	// Relay these signals to the `term` channel.
	signal.Notify(term, syscall.SIGINT, syscall.SIGTERM)

	select {
	case <-term:
		level.Info(logger).Log("msg", "received SIGTERM, exiting gracefully...")
	case <-ctx.Done():
		level.Info(logger).Log("msg", "context channel closed, exiting")
	}

	return 0
}

func main() {
	os.Exit(run())
}
<|endoftext|>