<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package net provides a portable interface for network I/O, including
TCP/IP, UDP, domain name resolution, and Unix domain sockets.

Although the package provides access to low-level networking
primitives, most clients will need only the basic interface
provided by the Dial, Listen, and Accept functions.

The Dial function connects to a server:

	conn, err := net.Dial("tcp", "google.com:80")
	if err != nil {
		// handle error
	}
	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
	status, err := bufio.NewReader(conn).ReadString('\n')
	// ...

The Listen function creates servers:

	ln, err := net.Listen("tcp", ":8080")
	if err != nil {
		// handle error
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			// handle error
			continue
		}
		go handleConnection(conn)
	}
*/
package net

// TODO(rsc):
//	support for raw ethernet sockets

import (
	"errors"
	"time"
)

// Addr represents a network end point address.
type Addr interface {
	Network() string // name of the network
	String() string  // string form of address
}

// Conn is a generic stream-oriented network connection.
type Conn interface {
	// Read reads data from the connection.
	// Read can be made to time out and return a Error with Timeout() == true
	// after a fixed time limit; see SetDeadline and SetReadDeadline.
	Read(b []byte) (n int, err error)

	// Write writes data to the connection.
	// Write can be made to time out and return a Error with Timeout() == true
	// after a fixed time limit; see SetDeadline and SetWriteDeadline.
	Write(b []byte) (n int, err error)

	// Close closes the connection.
	Close() error

	// LocalAddr returns the local network address.
	LocalAddr() Addr

	// RemoteAddr returns the remote network address.
	RemoteAddr() Addr

	// SetDeadline sets the read and write deadlines associated
	// with the connection.
	SetDeadline(t time.Time) error

	// SetReadDeadline sets the deadline for all Read calls to return.
	// If the deadline is reached, Read will fail with a timeout
	// (see type Error) instead of blocking.
	// A zero value for t means Read will not time out.
	SetReadDeadline(t time.Time) error

	// SetWriteDeadline sets the deadline for all Write calls to return.
	// If the deadline is reached, Write will fail with a timeout
	// (see type Error) instead of blocking.
	// A zero value for t means Write will not time out.
	// Even if write times out, it may return n > 0, indicating that
	// some of the data was successfully written.
	SetWriteDeadline(t time.Time) error
}

// An Error represents a network error.
type Error interface {
	error
	Timeout() bool   // Is the error a timeout?
	Temporary() bool // Is the error temporary?
}

// PacketConn is a generic packet-oriented network connection.
type PacketConn interface {
	// ReadFrom reads a packet from the connection,
	// copying the payload into b.  It returns the number of
	// bytes copied into b and the return address that
	// was on the packet.
	// ReadFrom can be made to time out and return
	// an error with Timeout() == true after a fixed time limit;
	// see SetDeadline and SetReadDeadline.
	ReadFrom(b []byte) (n int, addr Addr, err error)

	// WriteTo writes a packet with payload b to addr.
	// WriteTo can be made to time out and return
	// an error with Timeout() == true after a fixed time limit;
	// see SetDeadline and SetWriteDeadline.
	// On packet-oriented connections, write timeouts are rare.
	WriteTo(b []byte, addr Addr) (n int, err error)

	// Close closes the connection.
	Close() error

	// LocalAddr returns the local network address.
	LocalAddr() Addr

	// SetDeadline sets the read and write deadlines associated
	// with the connection.
	SetDeadline(t time.Time) error

	// SetReadDeadline sets the deadline for all Read calls to return.
	// If the deadline is reached, Read will fail with a timeout
	// (see type Error) instead of blocking.
	// A zero value for t means Read will not time out.
	SetReadDeadline(t time.Time) error

	// SetWriteDeadline sets the deadline for all Write calls to return.
	// If the deadline is reached, Write will fail with a timeout
	// (see type Error) instead of blocking.
	// A zero value for t means Write will not time out.
	// Even if write times out, it may return n > 0, indicating that
	// some of the data was successfully written.
	SetWriteDeadline(t time.Time) error
}

// A Listener is a generic network listener for stream-oriented protocols.
type Listener interface {
	// Accept waits for and returns the next connection to the listener.
	Accept() (c Conn, err error)

	// Close closes the listener.
	Close() error

	// Addr returns the listener's network address.
	Addr() Addr
}

var errMissingAddress = errors.New("missing address")

type OpError struct {
	Op   string
	Net  string
	Addr Addr
	Err  error
}

func (e *OpError) Error() string {
	if e == nil {
		return "<nil>"
	}
	s := e.Op
	if e.Net != "" {
		s += " " + e.Net
	}
	if e.Addr != nil {
		s += " " + e.Addr.String()
	}
	s += ": " + e.Err.Error()
	return s
}

type temporary interface {
	Temporary() bool
}

func (e *OpError) Temporary() bool {
	t, ok := e.Err.(temporary)
	return ok && t.Temporary()
}

type timeout interface {
	Timeout() bool
}

func (e *OpError) Timeout() bool {
	t, ok := e.Err.(timeout)
	return ok && t.Timeout()
}

type timeoutError struct{}

func (e *timeoutError) Error() string   { return "i/o timeout" }
func (e *timeoutError) Timeout() bool   { return true }
func (e *timeoutError) Temporary() bool { return true }

var errTimeout error = &timeoutError{}

type AddrError struct {
	Err  string
	Addr string
}

func (e *AddrError) Error() string {
	if e == nil {
		return "<nil>"
	}
	s := e.Err
	if e.Addr != "" {
		s += " " + e.Addr
	}
	return s
}

func (e *AddrError) Temporary() bool {
	return false
}

func (e *AddrError) Timeout() bool {
	return false
}

type UnknownNetworkError string

func (e UnknownNetworkError) Error() string   { return "unknown network " + string(e) }
func (e UnknownNetworkError) Temporary() bool { return false }
func (e UnknownNetworkError) Timeout() bool   { return false }

// DNSConfigError represents an error reading the machine's DNS configuration.
type DNSConfigError struct {
	Err error
}

func (e *DNSConfigError) Error() string {
	return "error reading DNS config: " + e.Err.Error()
}

func (e *DNSConfigError) Timeout() bool   { return false }
func (e *DNSConfigError) Temporary() bool { return false }
<commit_msg>net: more docs on deadlines<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package net provides a portable interface for network I/O, including
TCP/IP, UDP, domain name resolution, and Unix domain sockets.

Although the package provides access to low-level networking
primitives, most clients will need only the basic interface provided
by the Dial, Listen, and Accept functions and the associated
Conn and Listener interfaces. The crypto/tls package uses
the same interfaces and similar Dial and Listen functions.

The Dial function connects to a server:

	conn, err := net.Dial("tcp", "google.com:80")
	if err != nil {
		// handle error
	}
	fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
	status, err := bufio.NewReader(conn).ReadString('\n')
	// ...

The Listen function creates servers:

	ln, err := net.Listen("tcp", ":8080")
	if err != nil {
		// handle error
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			// handle error
			continue
		}
		go handleConnection(conn)
	}
*/
package net

// TODO(rsc):
//	support for raw ethernet sockets

import (
	"errors"
	"time"
)

// Addr represents a network end point address.
type Addr interface {
	Network() string // name of the network
	String() string  // string form of address
}

// Conn is a generic stream-oriented network connection.
type Conn interface {
	// Read reads data from the connection.
	// Read can be made to time out and return a Error with Timeout() == true
	// after a fixed time limit; see SetDeadline and SetReadDeadline.
	Read(b []byte) (n int, err error)

	// Write writes data to the connection.
	// Write can be made to time out and return a Error with Timeout() == true
	// after a fixed time limit; see SetDeadline and SetWriteDeadline.
	Write(b []byte) (n int, err error)

	// Close closes the connection.
	Close() error

	// LocalAddr returns the local network address.
	LocalAddr() Addr

	// RemoteAddr returns the remote network address.
	RemoteAddr() Addr

	// SetDeadline sets the read and write deadlines associated
	// with the connection. It is equivalent to calling both
	// SetReadDeadline and SetWriteDeadline.
	//
	// A deadline is an absolute time after which I/O operations
	// fail with a timeout (see type Error) instead of
	// blocking. The deadline applies to all future I/O, not just
	// the immediately following call to Read or Write.
	//
	// An idle timeout can be implemented by repeatedly extending
	// the deadline after successful Read or Write calls.
	//
	// A zero value for t means I/O operations will not time out.
	SetDeadline(t time.Time) error

	// SetReadDeadline sets the deadline for Read calls.
	// A zero value for t means Read will not time out.
	SetReadDeadline(t time.Time) error

	// SetWriteDeadline sets the deadline for Write calls.
	// Even if write times out, it may return n > 0, indicating that
	// some of the data was successfully written.
	// A zero value for t means Write will not time out.
	SetWriteDeadline(t time.Time) error
}

// An Error represents a network error.
type Error interface {
	error
	Timeout() bool   // Is the error a timeout?
	Temporary() bool // Is the error temporary?
}

// PacketConn is a generic packet-oriented network connection.
type PacketConn interface {
	// ReadFrom reads a packet from the connection,
	// copying the payload into b.  It returns the number of
	// bytes copied into b and the return address that
	// was on the packet.
	// ReadFrom can be made to time out and return
	// an error with Timeout() == true after a fixed time limit;
	// see SetDeadline and SetReadDeadline.
	ReadFrom(b []byte) (n int, addr Addr, err error)

	// WriteTo writes a packet with payload b to addr.
	// WriteTo can be made to time out and return
	// an error with Timeout() == true after a fixed time limit;
	// see SetDeadline and SetWriteDeadline.
	// On packet-oriented connections, write timeouts are rare.
	WriteTo(b []byte, addr Addr) (n int, err error)

	// Close closes the connection.
	Close() error

	// LocalAddr returns the local network address.
	LocalAddr() Addr

	// SetDeadline sets the read and write deadlines associated
	// with the connection.
	SetDeadline(t time.Time) error

	// SetReadDeadline sets the deadline for all Read calls to return.
	// If the deadline is reached, Read will fail with a timeout
	// (see type Error) instead of blocking.
	// A zero value for t means Read will not time out.
	SetReadDeadline(t time.Time) error

	// SetWriteDeadline sets the deadline for all Write calls to return.
	// If the deadline is reached, Write will fail with a timeout
	// (see type Error) instead of blocking.
	// A zero value for t means Write will not time out.
	// Even if write times out, it may return n > 0, indicating that
	// some of the data was successfully written.
	SetWriteDeadline(t time.Time) error
}

// A Listener is a generic network listener for stream-oriented protocols.
type Listener interface {
	// Accept waits for and returns the next connection to the listener.
	Accept() (c Conn, err error)

	// Close closes the listener.
	Close() error

	// Addr returns the listener's network address.
	Addr() Addr
}

var errMissingAddress = errors.New("missing address")

type OpError struct {
	Op   string
	Net  string
	Addr Addr
	Err  error
}

func (e *OpError) Error() string {
	if e == nil {
		return "<nil>"
	}
	s := e.Op
	if e.Net != "" {
		s += " " + e.Net
	}
	if e.Addr != nil {
		s += " " + e.Addr.String()
	}
	s += ": " + e.Err.Error()
	return s
}

type temporary interface {
	Temporary() bool
}

func (e *OpError) Temporary() bool {
	t, ok := e.Err.(temporary)
	return ok && t.Temporary()
}

type timeout interface {
	Timeout() bool
}

func (e *OpError) Timeout() bool {
	t, ok := e.Err.(timeout)
	return ok && t.Timeout()
}

type timeoutError struct{}

func (e *timeoutError) Error() string   { return "i/o timeout" }
func (e *timeoutError) Timeout() bool   { return true }
func (e *timeoutError) Temporary() bool { return true }

var errTimeout error = &timeoutError{}

type AddrError struct {
	Err  string
	Addr string
}

func (e *AddrError) Error() string {
	if e == nil {
		return "<nil>"
	}
	s := e.Err
	if e.Addr != "" {
		s += " " + e.Addr
	}
	return s
}

func (e *AddrError) Temporary() bool {
	return false
}

func (e *AddrError) Timeout() bool {
	return false
}

type UnknownNetworkError string

func (e UnknownNetworkError) Error() string   { return "unknown network " + string(e) }
func (e UnknownNetworkError) Temporary() bool { return false }
func (e UnknownNetworkError) Timeout() bool   { return false }

// DNSConfigError represents an error reading the machine's DNS configuration.
type DNSConfigError struct {
	Err error
}

func (e *DNSConfigError) Error() string {
	return "error reading DNS config: " + e.Err.Error()
}

func (e *DNSConfigError) Timeout() bool   { return false }
func (e *DNSConfigError) Temporary() bool { return false }
<|endoftext|>


<commit_before>package main

import (
	"net/http"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

var collections = prometheus.NewSummary(prometheus.SummaryOpts{
	Name: "kube-saml-collector_collection_seconds",
	Help: "A summary of the metadata aggregation performed by kube-saml-collector",
})

func registerAndServeMetrics() {
	prometheus.MustRegister(collections)
	http.Handle("/metrics", promhttp.Handler())
	go http.ListenAndServe(*metricsAddr, nil)
}
<commit_msg>Update to valid metric name<commit_after>package main

import (
	"net/http"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

var collections = prometheus.NewSummary(prometheus.SummaryOpts{
	Name: "kube_saml_collector_collection_seconds",
	Help: "A summary of the metadata aggregation performed by kube-saml-collector",
})

func registerAndServeMetrics() {
	prometheus.MustRegister(collections)
	http.Handle("/metrics", promhttp.Handler())
	go http.ListenAndServe(*metricsAddr, nil)
}
<|endoftext|>


<commit_before>package anidb

import (
	"bytes"
	"compress/gzip"
	"encoding/gob"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"reflect"
	"regexp"
	"sync"
	"time"
)

var _ log.Logger

type Cacheable interface {
	// Updates the last modified time
	Touch()
	// Returns true if the Cacheable is nil, or if the last modified time is too old.
	IsStale() bool
}

func init() {
	gob.RegisterName("*github.com/Kovensky/go-anidb.invalidKeyCache", &invalidKeyCache{})
}

type invalidKeyCache struct{ time.Time }

func (c *invalidKeyCache) Touch() {
	c.Time = time.Now()
}
func (c *invalidKeyCache) IsStale() bool {
	return time.Now().Sub(c.Time) > InvalidKeyCacheDuration
}

type cacheDir struct {
	*sync.RWMutex

	CacheDir string
}

func init() {
	if err := SetCacheDir(path.Join(os.TempDir(), "anidb", "cache")); err != nil {
		panic(err)
	}
}

var cache cacheDir

// Sets the cache directory to the given path.
//
// go-anidb needs a valid cache directory to function, so, during module
// initialization, it uses os.TempDir() to set a default cache dir.
// go-anidb panics if it's unable to set the default cache dir.
func SetCacheDir(path string) (err error) {
	m := cache.RWMutex
	if m == nil {
		m = &sync.RWMutex{}
		cache.RWMutex = m
	}
	cache.Lock()

	if err = os.MkdirAll(path, 0755|os.ModeDir); err != nil {
		cache.Unlock()
		return err
	}

	cache = cacheDir{
		RWMutex:  m,
		CacheDir: path,
	}

	cache.Unlock()
	RefreshTitles()
	return nil
}

// Returns the current cache dir.
func GetCacheDir() (path string) {
	cache.RLock()
	defer cache.RUnlock()

	return cache.CacheDir
}

type cacheKey interface{}

// All "bad characters" that can't go in Windows paths.
// It's a superset of the "bad characters" on other OSes, so this works.
var badPath = regexp.MustCompile(`[\\/:\*\?\"<>\|]`)

func stringify(stuff ...cacheKey) []string {
	ret := make([]string, len(stuff))
	for i := range stuff {
		s := fmt.Sprint(stuff[i])
		ret[i] = badPath.ReplaceAllLiteralString(s, "_")
	}
	return ret
}

// Each key but the last is treated as a directory.
// The last key is treated as a regular file.
//
// This also means that cache keys that are file-backed
// cannot have subkeys.
func cachePath(keys ...cacheKey) string {
	parts := append([]string{GetCacheDir()}, stringify(keys...)...)
	p := path.Join(parts...)
	return p
}

// Opens the file that backs the specified keys.
func (c *cacheDir) Open(keys ...cacheKey) (fh *os.File, err error) {
	subItem := cachePath(keys...)
	return os.Open(subItem)
}

// Creates a new file to back the specified keys.
func (c *cacheDir) Create(keys ...cacheKey) (fh *os.File, err error) {
	subItem := cachePath(keys...)
	subDir := path.Dir(subItem)

	if err = os.MkdirAll(subDir, 0755|os.ModeDir); err != nil {
		return nil, err
	}
	return os.Create(subItem)
}

// Deletes the file that backs the specified keys.
func (c *cacheDir) Delete(keys ...cacheKey) (err error) {
	return os.Remove(cachePath(keys...))
}

// Deletes the specified key and all subkeys.
func (c *cacheDir) DeleteAll(keys ...cacheKey) (err error) {
	return os.RemoveAll(cachePath(keys...))
}

func (c *cacheDir) Get(v Cacheable, keys ...cacheKey) (err error) {
	defer func() {
		log.Println("Got entry", keys, "(error", err, ")")
	}()

	val := reflect.ValueOf(v)
	if k := val.Kind(); k == reflect.Ptr || k == reflect.Interface {
		val = val.Elem()
	}
	if !val.CanSet() {
		// panic because this is an internal coding mistake
		panic("(*cacheDir).Get(): given Cacheable is not setable")
	}

	flock := lockFile(cachePath(keys...))
	if flock != nil {
		flock.Lock()
	}
	defer func() {
		if flock != nil {
			flock.Unlock()
		}
	}()

	fh, err := c.Open(keys...)
	if err != nil {
		return err
	}

	buf := bytes.Buffer{}
	if _, err = io.Copy(&buf, fh); err != nil {
		fh.Close()
		return err
	}
	if err = fh.Close(); err != nil {
		return err
	}

	if flock != nil {
		flock.Unlock()
		flock = nil
	}

	gz, err := gzip.NewReader(&buf)
	if err != nil {
		return err
	}
	defer func() {
		if e := gz.Close(); err == nil {
			err = e
		}
	}()

	switch f := gz.Header.Comment; f {
	case "encoding/gob":
		dec := gob.NewDecoder(gz)
		err = dec.Decode(v)
	default:
		return errors.New(fmt.Sprintf("Cached data (format %q) is not in a known format", f))
	}

	return
}

func (c *cacheDir) Set(v Cacheable, keys ...cacheKey) (n int64, err error) {
	if v := reflect.ValueOf(v); !v.IsValid() {
		panic("reflect.ValueOf() returned invaled value")
	} else if k := v.Kind(); k == reflect.Ptr || k == reflect.Interface {
		if v.IsNil() {
			return // no point in saving nil
		}
	}
	defer func() {
		log.Println("Set entry", keys, "(error", err, ")")
	}()

	// First we encode to memory -- we don't want to create/truncate a file and put bad data in it.
	buf := bytes.Buffer{}
	gz, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
	if err != nil {
		return 0, err
	}
	gz.Header.Comment = "encoding/gob"

	// it doesn't matter if the caller doesn't see this,
	// the important part is that the cache does.
	v.Touch()

	enc := gob.NewEncoder(gz)
	err = enc.Encode(v)

	if e := gz.Close(); err == nil {
		err = e
	}

	if err != nil {
		return 0, err
	}

	// We have good data, time to actually put it in the cache
	if flock := lockFile(cachePath(keys...)); flock != nil {
		flock.Lock()
		defer flock.Unlock()
	}

	fh, err := c.Create(keys...)
	if err != nil {
		return 0, err
	}
	defer func() {
		if e := fh.Close(); err == nil {
			err = e
		}
	}()
	n, err = io.Copy(fh, &buf)
	return
}

// Checks if the given keys are not marked as invalid.
//
// If the key was marked as invalid but is no longer considered
// so, deletes the invalid marker.
func (c *cacheDir) CheckValid(keys ...cacheKey) bool {
	invKeys := append([]cacheKey{"invalid"}, keys...)
	inv := invalidKeyCache{}

	if cache.Get(&inv, invKeys...) == nil {
		if inv.IsStale() {
			cache.Delete(invKeys...)
		} else {
			return false
		}
	}
	return true
}

// Deletes the given keys and marks them as invalid.
//
// They are considered invalid for InvalidKeyCacheDuration.
func (c *cacheDir) MarkInvalid(keys ...cacheKey) error {
	invKeys := append([]cacheKey{"invalid"}, keys...)

	cache.Delete(keys...)
	_, err := cache.Set(&invalidKeyCache{}, invKeys...)
	return err
}
<commit_msg>anidb: Remove debug logging from cache<commit_after>package anidb

import (
	"bytes"
	"compress/gzip"
	"encoding/gob"
	"errors"
	"fmt"
	"io"
	"os"
	"path"
	"reflect"
	"regexp"
	"sync"
	"time"
)

type Cacheable interface {
	// Updates the last modified time
	Touch()
	// Returns true if the Cacheable is nil, or if the last modified time is too old.
	IsStale() bool
}

func init() {
	gob.RegisterName("*github.com/Kovensky/go-anidb.invalidKeyCache", &invalidKeyCache{})
}

type invalidKeyCache struct{ time.Time }

func (c *invalidKeyCache) Touch() {
	c.Time = time.Now()
}
func (c *invalidKeyCache) IsStale() bool {
	return time.Now().Sub(c.Time) > InvalidKeyCacheDuration
}

type cacheDir struct {
	*sync.RWMutex

	CacheDir string
}

func init() {
	if err := SetCacheDir(path.Join(os.TempDir(), "anidb", "cache")); err != nil {
		panic(err)
	}
}

var cache cacheDir

// Sets the cache directory to the given path.
//
// go-anidb needs a valid cache directory to function, so, during module
// initialization, it uses os.TempDir() to set a default cache dir.
// go-anidb panics if it's unable to set the default cache dir.
func SetCacheDir(path string) (err error) {
	m := cache.RWMutex
	if m == nil {
		m = &sync.RWMutex{}
		cache.RWMutex = m
	}
	cache.Lock()

	if err = os.MkdirAll(path, 0755|os.ModeDir); err != nil {
		cache.Unlock()
		return err
	}

	cache = cacheDir{
		RWMutex:  m,
		CacheDir: path,
	}

	cache.Unlock()
	RefreshTitles()
	return nil
}

// Returns the current cache dir.
func GetCacheDir() (path string) {
	cache.RLock()
	defer cache.RUnlock()

	return cache.CacheDir
}

type cacheKey interface{}

// All "bad characters" that can't go in Windows paths.
// It's a superset of the "bad characters" on other OSes, so this works.
var badPath = regexp.MustCompile(`[\\/:\*\?\"<>\|]`)

func stringify(stuff ...cacheKey) []string {
	ret := make([]string, len(stuff))
	for i := range stuff {
		s := fmt.Sprint(stuff[i])
		ret[i] = badPath.ReplaceAllLiteralString(s, "_")
	}
	return ret
}

// Each key but the last is treated as a directory.
// The last key is treated as a regular file.
//
// This also means that cache keys that are file-backed
// cannot have subkeys.
func cachePath(keys ...cacheKey) string {
	parts := append([]string{GetCacheDir()}, stringify(keys...)...)
	p := path.Join(parts...)
	return p
}

// Opens the file that backs the specified keys.
func (c *cacheDir) Open(keys ...cacheKey) (fh *os.File, err error) {
	subItem := cachePath(keys...)
	return os.Open(subItem)
}

// Creates a new file to back the specified keys.
func (c *cacheDir) Create(keys ...cacheKey) (fh *os.File, err error) {
	subItem := cachePath(keys...)
	subDir := path.Dir(subItem)

	if err = os.MkdirAll(subDir, 0755|os.ModeDir); err != nil {
		return nil, err
	}
	return os.Create(subItem)
}

// Deletes the file that backs the specified keys.
func (c *cacheDir) Delete(keys ...cacheKey) (err error) {
	return os.Remove(cachePath(keys...))
}

// Deletes the specified key and all subkeys.
func (c *cacheDir) DeleteAll(keys ...cacheKey) (err error) {
	return os.RemoveAll(cachePath(keys...))
}

func (c *cacheDir) Get(v Cacheable, keys ...cacheKey) (err error) {

	val := reflect.ValueOf(v)
	if k := val.Kind(); k == reflect.Ptr || k == reflect.Interface {
		val = val.Elem()
	}
	if !val.CanSet() {
		// panic because this is an internal coding mistake
		panic("(*cacheDir).Get(): given Cacheable is not setable")
	}

	flock := lockFile(cachePath(keys...))
	if flock != nil {
		flock.Lock()
	}
	defer func() {
		if flock != nil {
			flock.Unlock()
		}
	}()

	fh, err := c.Open(keys...)
	if err != nil {
		return err
	}

	buf := bytes.Buffer{}
	if _, err = io.Copy(&buf, fh); err != nil {
		fh.Close()
		return err
	}
	if err = fh.Close(); err != nil {
		return err
	}

	if flock != nil {
		flock.Unlock()
		flock = nil
	}

	gz, err := gzip.NewReader(&buf)
	if err != nil {
		return err
	}
	defer func() {
		if e := gz.Close(); err == nil {
			err = e
		}
	}()

	switch f := gz.Header.Comment; f {
	case "encoding/gob":
		dec := gob.NewDecoder(gz)
		err = dec.Decode(v)
	default:
		return errors.New(fmt.Sprintf("Cached data (format %q) is not in a known format", f))
	}

	return
}

func (c *cacheDir) Set(v Cacheable, keys ...cacheKey) (n int64, err error) {
	if v := reflect.ValueOf(v); !v.IsValid() {
		panic("reflect.ValueOf() returned invaled value")
	} else if k := v.Kind(); k == reflect.Ptr || k == reflect.Interface {
		if v.IsNil() {
			return // no point in saving nil
		}
	}

	// First we encode to memory -- we don't want to create/truncate a file and put bad data in it.
	buf := bytes.Buffer{}
	gz, err := gzip.NewWriterLevel(&buf, gzip.BestCompression)
	if err != nil {
		return 0, err
	}
	gz.Header.Comment = "encoding/gob"

	// it doesn't matter if the caller doesn't see this,
	// the important part is that the cache does.
	v.Touch()

	enc := gob.NewEncoder(gz)
	err = enc.Encode(v)

	if e := gz.Close(); err == nil {
		err = e
	}

	if err != nil {
		return 0, err
	}

	// We have good data, time to actually put it in the cache
	if flock := lockFile(cachePath(keys...)); flock != nil {
		flock.Lock()
		defer flock.Unlock()
	}

	fh, err := c.Create(keys...)
	if err != nil {
		return 0, err
	}
	defer func() {
		if e := fh.Close(); err == nil {
			err = e
		}
	}()
	n, err = io.Copy(fh, &buf)
	return
}

// Checks if the given keys are not marked as invalid.
//
// If the key was marked as invalid but is no longer considered
// so, deletes the invalid marker.
func (c *cacheDir) CheckValid(keys ...cacheKey) bool {
	invKeys := append([]cacheKey{"invalid"}, keys...)
	inv := invalidKeyCache{}

	if cache.Get(&inv, invKeys...) == nil {
		if inv.IsStale() {
			cache.Delete(invKeys...)
		} else {
			return false
		}
	}
	return true
}

// Deletes the given keys and marks them as invalid.
//
// They are considered invalid for InvalidKeyCacheDuration.
func (c *cacheDir) MarkInvalid(keys ...cacheKey) error {
	invKeys := append([]cacheKey{"invalid"}, keys...)

	cache.Delete(keys...)
	_, err := cache.Set(&invalidKeyCache{}, invKeys...)
	return err
}
<|endoftext|>


<commit_before>package message

import (
	"fmt"
	"regexp"

	"slackbot_atlassian/atlassian"
	"slackbot_atlassian/config"
	"slackbot_atlassian/log"
)

type Message struct {
	SlackChannel string
	AsUser       config.SlackUser
	Text         string
}

type MessageMatcher interface {
	GetMatchingMessages([]*config.MessageTrigger, ...atlassian.ActivityIssue) []Message
}

type matcher struct {
	cfg                config.SlackConfig
	custom_jira_fields []config.CustomJiraFieldConfig
	user_image_urls    map[string]string
}

func NewMessageMatcher(cfg config.SlackConfig, user_image_urls map[string]string, custom_jira_fields ...config.CustomJiraFieldConfig) MessageMatcher {
	return matcher{cfg, custom_jira_fields, user_image_urls}
}

func (m matcher) GetMatchingMessages(triggers []*config.MessageTrigger, activity_issues ...atlassian.ActivityIssue) []Message {
	messages := make([]Message, 0)

	for _, activity_issue := range activity_issues {
		for _, trigger := range triggers {
			if match, ok, err := m.get_match(trigger, activity_issue); ok && err == nil {
				messages = append(messages, match.get_messages()...)
			} else if err != nil {
				log.LogF("Error matching issue %v: %s", activity_issue, err)
			}
		}
	}

	return messages
}

func (m matcher) get_match(trigger *config.MessageTrigger, activity_issue atlassian.ActivityIssue) (*match, bool, error) {
	for name, match := range trigger.GetCompiledMatches() {
		// Look up the value for this field
		field_val, ok, err := m.get_trigger_field_value(name, activity_issue)
		if err != nil || !ok {
			return nil, ok, err
		}

		if !match.MatchString(field_val) {
			return nil, false, nil
		}
	}

	return &match{m.user_image_urls, trigger, activity_issue}, true, nil
}

func (m matcher) get_trigger_field_value(name string, activity_issue atlassian.ActivityIssue) (string, bool, error) {
	// First, check if this is a custom field defined by the JSON

	lookup_field := func(name string) (string, bool, error) {
		val, ok := activity_issue.Issue.Fields[name]
		if !ok {
			return "", false, nil
		} else if val == nil {
			return "", false, nil
		}
		switch val.(type) {
		case map[string]interface{}:
			value, ok := val.(map[string]interface{})["value"].(string)
			if !ok {
				return "", false, fmt.Errorf("Wrong type for val inside %s: want string, have %T", name, val)
			}
			return value, ok, nil
		case string:
			return val.(string), true, nil
		default:
			return "", false, fmt.Errorf("Wrong type for %s: want map or string, have %T", name, val)
		}
	}

	// First try to look up for each of our custom fields
	for _, cf := range m.custom_jira_fields {
		if cf.Name == name {
			val, ok, err := lookup_field(cf.JiraField)
			if ok {
				return val, ok, err
			}
		}
	}

	// Now try with built-in fields
	return lookup_field(name)
}

type match struct {
	user_image_urls map[string]string
	trigger         *config.MessageTrigger
	activity_issue  atlassian.ActivityIssue
}

func (m match) get_messages() []Message {
	message := Message{
		m.trigger.SlackChannel,
		config.SlackUser{
			Name:    m.activity_issue.Activity.Author.Name,
			IconUrl: m.user_image_urls[m.activity_issue.Activity.Author.Username],
		},
		GetTextFromActivityItem(m.activity_issue.Activity),
	}
	return []Message{message}
}

func GetTextFromActivityItem(activity *atlassian.ActivityItem) string {
	// Strip name from start of title
	re := regexp.MustCompile("^<a.+?")
	text := re.ReplaceAllString(activity.Title, "")

	// Convert resolved links
	re = regexp.MustCompile(`<span class='resolved-link'>(.+?)</span>`)
	text = re.ReplaceAllString(text, "~$1~")

	// Convert HTML links
	re = regexp.MustCompile(`<a .*?href="(.+?)".*?>(.+?)</a>`)
	text = re.ReplaceAllString(text, "<$1|$2>")

	// Strip duplicate whitespace
	re = regexp.MustCompile("\\s+")
	text = re.ReplaceAllString(text, " ")

	return text
}
<commit_msg>[FEATURE] Hacky support for arrays<commit_after>package message

import (
	"fmt"
	"regexp"
	"strings"

	"slackbot_atlassian/atlassian"
	"slackbot_atlassian/config"
	"slackbot_atlassian/log"
)

type Message struct {
	SlackChannel string
	AsUser       config.SlackUser
	Text         string
}

type MessageMatcher interface {
	GetMatchingMessages([]*config.MessageTrigger, ...atlassian.ActivityIssue) []Message
}

type matcher struct {
	cfg                config.SlackConfig
	custom_jira_fields []config.CustomJiraFieldConfig
	user_image_urls    map[string]string
}

func NewMessageMatcher(cfg config.SlackConfig, user_image_urls map[string]string, custom_jira_fields ...config.CustomJiraFieldConfig) MessageMatcher {
	return matcher{cfg, custom_jira_fields, user_image_urls}
}

func (m matcher) GetMatchingMessages(triggers []*config.MessageTrigger, activity_issues ...atlassian.ActivityIssue) []Message {
	messages := make([]Message, 0)

	for _, activity_issue := range activity_issues {
		for _, trigger := range triggers {
			if match, ok, err := m.get_match(trigger, activity_issue); ok && err == nil {
				messages = append(messages, match.get_messages()...)
			} else if err != nil {
				log.LogF("Error matching issue %v: %s", activity_issue, err)
			}
		}
	}

	return messages
}

func (m matcher) get_match(trigger *config.MessageTrigger, activity_issue atlassian.ActivityIssue) (*match, bool, error) {
	for name, match := range trigger.GetCompiledMatches() {
		// Look up the value for this field
		field_val, ok, err := m.get_trigger_field_value(name, activity_issue)
		if err != nil || !ok {
			return nil, ok, err
		}

		if !match.MatchString(field_val) {
			return nil, false, nil
		}
	}

	return &match{m.user_image_urls, trigger, activity_issue}, true, nil
}

func (m matcher) get_trigger_field_value(name string, activity_issue atlassian.ActivityIssue) (string, bool, error) {
	// First, check if this is a custom field defined by the JSON

	lookup_field := func(name string) (string, bool, error) {
		val, ok := activity_issue.Issue.Fields[name]
		if !ok {
			return "", false, nil
		} else if val == nil {
			return "", false, nil
		}
		switch val.(type) {
		case map[string]interface{}:
			value, ok := val.(map[string]interface{})["value"].(string)
			if !ok {
				return "", false, fmt.Errorf("Wrong type for val inside %s: want string, have %T", name, val)
			}
			return value, ok, nil
		case string:
			return val.(string), true, nil
		case []interface{}:
			// For now we just join them all with strings
			var strs []string
			for _, v := range val.([]interface{}) {
				strs = append(strs, fmt.Sprintf("%s", v))
			}
			return strings.Join(strs, ","), true, nil
		default:
			return "", false, fmt.Errorf("Wrong type for %s: want map or string, have %T", name, val)
		}
	}

	// First try to look up for each of our custom fields
	for _, cf := range m.custom_jira_fields {
		if cf.Name == name {
			val, ok, err := lookup_field(cf.JiraField)
			if ok {
				return val, ok, err
			}
		}
	}

	// Now try with built-in fields
	return lookup_field(name)
}

type match struct {
	user_image_urls map[string]string
	trigger         *config.MessageTrigger
	activity_issue  atlassian.ActivityIssue
}

func (m match) get_messages() []Message {
	message := Message{
		m.trigger.SlackChannel,
		config.SlackUser{
			Name:    m.activity_issue.Activity.Author.Name,
			IconUrl: m.user_image_urls[m.activity_issue.Activity.Author.Username],
		},
		GetTextFromActivityItem(m.activity_issue.Activity),
	}
	return []Message{message}
}

func GetTextFromActivityItem(activity *atlassian.ActivityItem) string {
	// Strip name from start of title
	re := regexp.MustCompile("^<a.+?")
	text := re.ReplaceAllString(activity.Title, "")

	// Convert resolved links
	re = regexp.MustCompile(`<span class='resolved-link'>(.+?)</span>`)
	text = re.ReplaceAllString(text, "~$1~")

	// Convert HTML links
	re = regexp.MustCompile(`<a .*?href="(.+?)".*?>(.+?)</a>`)
	text = re.ReplaceAllString(text, "<$1|$2>")

	// Strip duplicate whitespace
	re = regexp.MustCompile("\\s+")
	text = re.ReplaceAllString(text, " ")

	return text
}
<|endoftext|>


<commit_before>package slf4go

import (
    "log"
    "os"
    "fmt"
    "time"
    "runtime"
    "io"
    "strings"
)

const (
    l_TRACE    = "TRACE"
    l_DEBUG    = "DEBUG"
    l_INFO     = "INFO"
    l_WARN     = "WARN"
    l_ERROR    = "ERROR"
    l_FATAL    = "FATAL"
    call_depth = 2
)

//------------------------------------------------------------------------------------------------------------
// simple l that use log package
type logger_adaptor_native struct {
    LoggerAdaptor
    tf   string
    out  io.Writer
    flag int
}

// it should be private
func newNativeLogger(name string) *logger_adaptor_native {
    logger := new(logger_adaptor_native)
    logger.name = name
    logger.out = Writer
    logger.level = LEVEL_DEBUG
    logger.tf = "2006-01-02 15:04:05.999"
    logger.flag = log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile
    return logger
}

func (l *logger_adaptor_native) Trace(args ...interface{}) {
    if l.level <= LEVEL_TRACE {
        str := fmt.Sprint(args...)
        l.output(call_depth, l_TRACE, str)
    }
}

func (l *logger_adaptor_native) TraceF(format string, args ...interface{}) {
    if l.level <= LEVEL_TRACE {
        str := fmt.Sprintf(format, args...)
        l.output(call_depth, l_TRACE, str)
    }
}

func (l *logger_adaptor_native) Debug(args ...interface{}) {
    if l.level <= LEVEL_DEBUG {
        str := fmt.Sprint(args...)
        l.output(call_depth, l_DEBUG, str)
    }
}

func (l *logger_adaptor_native) DebugF(format string, args ...interface{}) {
    if l.level <= LEVEL_DEBUG {
        str := fmt.Sprintf(format, args...)
        l.output(call_depth, l_DEBUG, str)
    }
}

func (l *logger_adaptor_native) Info(args ...interface{}) {
    if l.level <= LEVEL_INFO {
        str := fmt.Sprint(args...)
        l.output(call_depth, l_INFO, str)
    }
}

func (l *logger_adaptor_native) InfoF(format string, args ...interface{}) {
    if l.level <= LEVEL_INFO {
        str := fmt.Sprintf(format, args...)
        l.output(call_depth, l_INFO, str)
    }
}

func (l *logger_adaptor_native) Warn(args ...interface{}) {
    if l.level <= LEVEL_WARN {
        str := fmt.Sprint(args...)
        l.output(call_depth, l_WARN, str)
    }
}

func (l *logger_adaptor_native) WarnF(format string, args ...interface{}) {
    if l.level <= LEVEL_WARN {
        str := fmt.Sprintf(format, args...)
        l.output(call_depth, l_WARN, str)
    }
}

func (l *logger_adaptor_native) Error(args ...interface{}) {
    if l.level <= LEVEL_ERROR {
        str := fmt.Sprint(args...)
        l.output(call_depth, l_ERROR, str)
    }
}

func (l *logger_adaptor_native) ErrorF(format string, args ...interface{}) {
    if l.level <= LEVEL_ERROR {
        str := fmt.Sprintf(format, args...)
        l.output(call_depth, l_ERROR, str)
    }
}

func (l *logger_adaptor_native) Fatal(args ...interface{}) {
    str := fmt.Sprint(args...)
    l.output(call_depth, l_FATAL, str)
    os.Exit(1)
}

func (l *logger_adaptor_native) FatalF(format string, args ...interface{}) {
    str := fmt.Sprintf(format, args...)
    l.output(call_depth, l_FATAL, str)
    os.Exit(1)
}

func (l *logger_adaptor_native) output(calldepth int, level, s string) error {
    var file string
    var line int
    var ts = time.Now().Format(l.tf)
    if l.flag&(log.Lshortfile|log.Llongfile) != 0 {
        var ok bool
        _, file, line, ok = runtime.Caller(calldepth)
        if !ok {
            file = "???"
            line = 0
        }
        lastIndex := strings.LastIndex(file, "/")
        if lastIndex > 0 {
            file = file[lastIndex+1:]
        }
    }
    result := fmt.Sprintf("%-29s [%-5s] %s:%d %s\n", ts, level, file, line, s)
    _, err := l.out.Write([]byte(result))
    return err
}

//------------------------------------------------------------------------------------------------------------
// factory
type nativeLoggerFactory struct {
}

func newNativeLoggerFactory() LoggerFactory {
    factory := &nativeLoggerFactory{}
    return factory
}

func (factory *nativeLoggerFactory) GetLogger(name string) Logger {
    return newNativeLogger(name)
}
<commit_msg>use Writer globally<commit_after>package slf4go

import (
    "log"
    "os"
    "fmt"
    "time"
    "runtime"
    "strings"
)

const (
    l_TRACE    = "TRACE"
    l_DEBUG    = "DEBUG"
    l_INFO     = "INFO"
    l_WARN     = "WARN"
    l_ERROR    = "ERROR"
    l_FATAL    = "FATAL"
    call_depth = 2
)

//------------------------------------------------------------------------------------------------------------
// simple l that use log package
type logger_adaptor_native struct {
    LoggerAdaptor
    tf   string
    flag int
}

// it should be private
func newNativeLogger(name string) *logger_adaptor_native {
    logger := new(logger_adaptor_native)
    logger.name = name
    logger.level = LEVEL_DEBUG
    logger.tf = "2006-01-02 15:04:05.999"
    logger.flag = log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile
    return logger
}

func (l *logger_adaptor_native) Trace(args ...interface{}) {
    if l.level <= LEVEL_TRACE {
        str := fmt.Sprint(args...)
        l.output(call_depth, l_TRACE, str)
    }
}

func (l *logger_adaptor_native) TraceF(format string, args ...interface{}) {
    if l.level <= LEVEL_TRACE {
        str := fmt.Sprintf(format, args...)
        l.output(call_depth, l_TRACE, str)
    }
}

func (l *logger_adaptor_native) Debug(args ...interface{}) {
    if l.level <= LEVEL_DEBUG {
        str := fmt.Sprint(args...)
        l.output(call_depth, l_DEBUG, str)
    }
}

func (l *logger_adaptor_native) DebugF(format string, args ...interface{}) {
    if l.level <= LEVEL_DEBUG {
        str := fmt.Sprintf(format, args...)
        l.output(call_depth, l_DEBUG, str)
    }
}

func (l *logger_adaptor_native) Info(args ...interface{}) {
    if l.level <= LEVEL_INFO {
        str := fmt.Sprint(args...)
        l.output(call_depth, l_INFO, str)
    }
}

func (l *logger_adaptor_native) InfoF(format string, args ...interface{}) {
    if l.level <= LEVEL_INFO {
        str := fmt.Sprintf(format, args...)
        l.output(call_depth, l_INFO, str)
    }
}

func (l *logger_adaptor_native) Warn(args ...interface{}) {
    if l.level <= LEVEL_WARN {
        str := fmt.Sprint(args...)
        l.output(call_depth, l_WARN, str)
    }
}

func (l *logger_adaptor_native) WarnF(format string, args ...interface{}) {
    if l.level <= LEVEL_WARN {
        str := fmt.Sprintf(format, args...)
        l.output(call_depth, l_WARN, str)
    }
}

func (l *logger_adaptor_native) Error(args ...interface{}) {
    if l.level <= LEVEL_ERROR {
        str := fmt.Sprint(args...)
        l.output(call_depth, l_ERROR, str)
    }
}

func (l *logger_adaptor_native) ErrorF(format string, args ...interface{}) {
    if l.level <= LEVEL_ERROR {
        str := fmt.Sprintf(format, args...)
        l.output(call_depth, l_ERROR, str)
    }
}

func (l *logger_adaptor_native) Fatal(args ...interface{}) {
    str := fmt.Sprint(args...)
    l.output(call_depth, l_FATAL, str)
    os.Exit(1)
}

func (l *logger_adaptor_native) FatalF(format string, args ...interface{}) {
    str := fmt.Sprintf(format, args...)
    l.output(call_depth, l_FATAL, str)
    os.Exit(1)
}

func (l *logger_adaptor_native) output(calldepth int, level, s string) error {
    var file string
    var line int
    var ts = time.Now().Format(l.tf)
    if l.flag&(log.Lshortfile|log.Llongfile) != 0 {
        var ok bool
        _, file, line, ok = runtime.Caller(calldepth)
        if !ok {
            file = "???"
            line = 0
        }
        lastIndex := strings.LastIndex(file, "/")
        if lastIndex > 0 {
            file = file[lastIndex+1:]
        }
    }
    result := fmt.Sprintf("%-29s [%-5s] %s:%d %s\n", ts, level, file, line, s)
    _, err := Writer.Write([]byte(result))
    return err
}

//------------------------------------------------------------------------------------------------------------
// factory
type nativeLoggerFactory struct {
}

func newNativeLoggerFactory() LoggerFactory {
    factory := &nativeLoggerFactory{}
    return factory
}

func (factory *nativeLoggerFactory) GetLogger(name string) Logger {
    return newNativeLogger(name)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/evmar/smash/bash"
	"github.com/evmar/smash/proto"
	"github.com/evmar/smash/vt100"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
)

var completer *bash.Bash
var globalLastTermForCmd *vt100.Terminal
var globalSockPathForEnv string

var upgrader = websocket.Upgrader{
	ReadBufferSize:    1024,
	WriteBufferSize:   1024,
	EnableCompression: true,
}

// conn wraps a websocket.Conn with a lock.
type conn struct {
	sync.Mutex
	ws *websocket.Conn
}

func (c *conn) writeMsg(msg proto.Msg) error {
	m := &proto.ServerMsg{msg}
	w := &bytes.Buffer{}
	if err := m.Write(w); err != nil {
		return err
	}
	c.Lock()
	defer c.Unlock()
	return c.ws.WriteMessage(websocket.BinaryMessage, w.Bytes())
}

// isPtyEOFError tests for a pty close error.
// When a pty closes, you get an EIO error instead of an EOF.
func isPtyEOFError(err error) bool {
	const EIO syscall.Errno = 5
	if perr, ok := err.(*os.PathError); ok {
		if errno, ok := perr.Err.(syscall.Errno); ok && errno == EIO {
			// read /dev/ptmx: input/output error
			return true
		}
	}
	return false
}

// command represents a subprocess running on behalf of the user.
// req.Cell has the id of the command for use in protocol messages.
type command struct {
	conn *conn
	// req is the initial request that caused the command to be spawned.
	req *proto.RunRequest
	cmd *exec.Cmd

	// stdin accepts input keys and forwards them to the subprocess.
	stdin chan []byte
}

func newCmd(conn *conn, req *proto.RunRequest) *command {
	cmd := &exec.Cmd{Path: req.Argv[0], Args: req.Argv}
	// TODO: accept environment from the client
	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, "SMASH_SOCK="+globalSockPathForEnv)
	cmd.Dir = req.Cwd
	return &command{
		conn: conn,
		req:  req,
		cmd:  cmd,
	}
}

func (cmd *command) send(msg proto.Msg) error {
	return cmd.conn.writeMsg(&proto.CellOutput{
		Cell:   cmd.req.Cell,
		Output: proto.Output{msg},
	})
}

func (cmd *command) sendError(msg string) error {
	return cmd.send(&proto.CmdError{msg})
}

func termLoop(tr *vt100.TermReader, r io.Reader) error {
	br := bufio.NewReader(r)
	for {
		if err := tr.Read(br); err != nil {
			if isPtyEOFError(err) {
				err = io.EOF
			}
			return err
		}
	}
}

// run synchronously runs the subprocess to completion, sending terminal
// updates as it progresses.  It may return errors if the subprocess failed
// to run for whatever reason (e.g. no such path), and otherwise returns
// the subprocess exit code.
func (cmd *command) run() (int, error) {
	if cmd.cmd.Path == "cd" {
		if len(cmd.cmd.Args) != 2 {
			return 0, fmt.Errorf("bad arguments to cd")
		}
		dir := cmd.cmd.Args[1]
		st, err := os.Stat(dir)
		if err != nil {
			return 0, err
		}
		if !st.IsDir() {
			return 0, fmt.Errorf("%s: not a directory", dir)
		}
		return 0, nil
	}

	if filepath.Base(cmd.cmd.Path) == cmd.cmd.Path {
		// TODO: should use shell env $PATH.
		if p, err := exec.LookPath(cmd.cmd.Path); err != nil {
			return 0, err
		} else {
			cmd.cmd.Path = p
		}
	}

	size := pty.Winsize{
		Rows: 24,
		Cols: 80,
	}
	f, err := pty.StartWithSize(cmd.cmd, &size)
	if err != nil {
		return 0, err
	}

	cmd.stdin = make(chan []byte)
	go func() {
		for input := range cmd.stdin {
			f.Write(input)
		}
	}()

	var mu sync.Mutex // protects term, drawPending, and done
	wake := sync.NewCond(&mu)
	term := vt100.NewTerminal()
	drawPending := false
	var done error

	var tr *vt100.TermReader
	renderFromDirty := func() {
		// Called with mu held.
		allDirty := tr.Dirty.Lines[-1]
		update := &proto.TermUpdate{}
		if tr.Dirty.Cursor {
			update.Cursor = proto.Cursor{
				Row:    term.Row,
				Col:    term.Col,
				Hidden: term.HideCursor,
			}
		}
		for row, l := range term.Lines {
			if !(allDirty || tr.Dirty.Lines[row]) {
				continue
			}
			rowSpans := proto.RowSpans{
				Row: row,
			}
			span := proto.Span{}
			var attr vt100.Attr
			for _, cell := range l {
				if cell.Attr != attr {
					attr = cell.Attr
					rowSpans.Spans = append(rowSpans.Spans, span)
					span = proto.Span{Attr: int(attr)}
				}
				// TODO: super inefficient.
				span.Text += fmt.Sprintf("%c", cell.Ch)
			}
			if len(span.Text) > 0 {
				rowSpans.Spans = append(rowSpans.Spans, span)
			}
			update.Rows = append(update.Rows, rowSpans)
		}

		err := cmd.send(update)
		if err != nil {
			done = err
		}
	}

	tr = vt100.NewTermReader(func(f func(t *vt100.Terminal)) {
		// This is called from the 'go termLoop' goroutine,
		// when the vt100 impl wants to update the terminal.
		mu.Lock()
		f(term)
		if !drawPending {
			drawPending = true
			wake.Signal()
		}
		mu.Unlock()
	})

	go func() {
		err := termLoop(tr, f)
		mu.Lock()
		done = err
		wake.Signal()
		mu.Unlock()
	}()

	for {
		mu.Lock()
		for !drawPending && done == nil {
			wake.Wait()
		}

		if done == nil {
			mu.Unlock()
			// Allow more pending paints to enqueue.
			time.Sleep(10 * time.Millisecond)
			mu.Lock()
		}

		renderFromDirty()
		tr.Dirty.Reset()
		drawPending = false
		mu.Unlock()

		if done != nil {
			break
		}
	}

	mu.Lock()
	globalLastTermForCmd = term

	// done is the error reported by the terminal.
	// We expect EOF in normal execution.
	if done != io.EOF {
		return 0, err
	}

	// Reap the subprocess and report the exit code.
	if err := cmd.cmd.Wait(); err != nil {
		if eerr, ok := err.(*exec.ExitError); ok {
			serr := eerr.Sys().(syscall.WaitStatus)
			return serr.ExitStatus(), nil
		} else {
			return 0, err
		}
	}
	return 0, nil
}

// runHandlingErrors calls run() and forwards any subprocess errors
// on to the client.
func (cmd *command) runHandlingErrors() {
	exitCode, err := cmd.run()
	if err != nil {
		cmd.sendError(err.Error())
		exitCode = 1
	}
	cmd.send(&proto.Exit{exitCode})
}

var localCommands = map[string]func(w io.Writer) error{
	"that": func(w io.Writer) error {
		if globalLastTermForCmd == nil {
			return nil
		}
		_, err := io.WriteString(w, globalLastTermForCmd.ToString())
		return err
	},
}

func getEnv() map[string]string {
	env := map[string]string{}
	for _, keyval := range os.Environ() {
		eq := strings.Index(keyval, "=")
		if eq < 0 {
			panic("bad env?")
		}
		env[keyval[:eq]] = keyval[eq+1:]
	}
	return env
}

func mapPairs(m map[string]string) []proto.Pair {
	pairs := []proto.Pair{}
	for k, v := range m {
		pairs = append(pairs, proto.Pair{k, v})
	}
	return pairs
}

func serveWS(w http.ResponseWriter, r *http.Request) error {
	wsConn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return err
	}
	conn := &conn{
		ws: wsConn,
	}

	smashPath, err := os.Readlink("/proc/self/exe")
	if err != nil {
		return err
	}

	aliases, err := bash.GetAliases()
	if err != nil {
		return err
	}
	env := getEnv()
	env["SMASH"] = smashPath
	env["SMASH_SOCK"] = globalSockPathForEnv
	hello := &proto.Hello{
		Alias: mapPairs(aliases),
		Env:   mapPairs(env),
	}
	if err = conn.writeMsg(hello); err != nil {
		return err
	}

	commands := map[int]*command{}
	for {
		_, buf, err := conn.ws.ReadMessage()
		if err != nil {
			return fmt.Errorf("reading client message: %s", err)
		}
		var msg proto.ClientMessage
		if err := msg.Read(bufio.NewReader(bytes.NewBuffer(buf))); err != nil {
			return fmt.Errorf("parsing client message: %s", err)
		}

		switch msg := msg.Alt.(type) {
		case *proto.RunRequest:
			cmd := newCmd(conn, msg)
			commands[int(msg.Cell)] = cmd
			go cmd.runHandlingErrors()
		case *proto.KeyEvent:
			cmd := commands[int(msg.Cell)]
			if cmd == nil {
				log.Println("got key msg for unknown command", msg.Cell)
				continue
			}
			// TODO: what if cmd failed?
			// TODO: what if pipe is blocked?
			cmd.stdin <- []byte(msg.Keys)
		case *proto.CompleteRequest:
			if msg.Cwd == "" {
				panic("incomplete complete request")
			}
			go func() {
				if err := completer.Chdir(msg.Cwd); err != nil {
					log.Println(err) // TODO
				}
				pos, completions, err := completer.Complete(msg.Input[0:msg.Pos])
				if err != nil {
					log.Println(err) // TODO
				}
				err = conn.writeMsg(&proto.CompleteResponse{
					Id:          msg.Id,
					Pos:         pos,
					Completions: completions,
				})
				if err != nil {
					log.Println(err) // TODO
				}
			}()
		default:
			log.Println("unhandled msg", msg)
		}
	}
}

func serve() error {
	sockPath, localSock, err := setupLocalCommandSock()
	if err != nil {
		return err
	}
	go func() {
		if err := readLocalCommands(localSock); err != nil {
			fmt.Fprintf(os.Stderr, "local sock: %s\n", err)
		}
	}()
	globalSockPathForEnv = sockPath

	b, err := bash.StartBash()
	if err != nil {
		return err
	}
	completer = b

	http.Handle("/", http.FileServer(http.Dir("../web/dist")))
	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		if err := serveWS(w, r); err != nil {
			log.Printf("error: %s", err)
		}
	})
	addr := ":8080"
	fmt.Printf("listening on %q\n", addr)
	return http.ListenAndServe(addr, nil)
}

func localCommand(cmd string) error {
	sockPath := os.Getenv("SMASH_SOCK")
	if sockPath == "" {
		return fmt.Errorf("no $SMASH_SOCK; are you running under smash?")
	}

	conn, err := net.Dial("unix", sockPath)
	if err != nil {
		return err
	}
	defer conn.Close()
	if _, err = fmt.Fprintf(conn, "%s\n", cmd); err != nil {
		return err
	}
	if _, err = io.Copy(os.Stdout, conn); err != nil {
		return err
	}
	return nil
}

func main() {
	var cmd = "serve"
	if len(os.Args) > 1 {
		cmd = os.Args[1]
	}

	var err error
	if _, isLocal := localCommands[cmd]; isLocal {
		err = localCommand(cmd)
	} else {
		switch cmd {
		case "serve":
			err = serve()
		case "help":
		default:
			fmt.Println("TODO: usage")
		}
	}

	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
		os.Exit(1)
	}
}
<commit_msg>don't send empty updates<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"syscall"
	"time"

	"github.com/evmar/smash/bash"
	"github.com/evmar/smash/proto"
	"github.com/evmar/smash/vt100"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
)

var completer *bash.Bash
var globalLastTermForCmd *vt100.Terminal
var globalSockPathForEnv string

var upgrader = websocket.Upgrader{
	ReadBufferSize:    1024,
	WriteBufferSize:   1024,
	EnableCompression: true,
}

// conn wraps a websocket.Conn with a lock.
type conn struct {
	sync.Mutex
	ws *websocket.Conn
}

func (c *conn) writeMsg(msg proto.Msg) error {
	m := &proto.ServerMsg{msg}
	w := &bytes.Buffer{}
	if err := m.Write(w); err != nil {
		return err
	}
	c.Lock()
	defer c.Unlock()
	return c.ws.WriteMessage(websocket.BinaryMessage, w.Bytes())
}

// isPtyEOFError tests for a pty close error.
// When a pty closes, you get an EIO error instead of an EOF.
func isPtyEOFError(err error) bool {
	const EIO syscall.Errno = 5
	if perr, ok := err.(*os.PathError); ok {
		if errno, ok := perr.Err.(syscall.Errno); ok && errno == EIO {
			// read /dev/ptmx: input/output error
			return true
		}
	}
	return false
}

// command represents a subprocess running on behalf of the user.
// req.Cell has the id of the command for use in protocol messages.
type command struct {
	conn *conn
	// req is the initial request that caused the command to be spawned.
	req *proto.RunRequest
	cmd *exec.Cmd

	// stdin accepts input keys and forwards them to the subprocess.
	stdin chan []byte
}

func newCmd(conn *conn, req *proto.RunRequest) *command {
	cmd := &exec.Cmd{Path: req.Argv[0], Args: req.Argv}
	// TODO: accept environment from the client
	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, "SMASH_SOCK="+globalSockPathForEnv)
	cmd.Dir = req.Cwd
	return &command{
		conn: conn,
		req:  req,
		cmd:  cmd,
	}
}

func (cmd *command) send(msg proto.Msg) error {
	return cmd.conn.writeMsg(&proto.CellOutput{
		Cell:   cmd.req.Cell,
		Output: proto.Output{msg},
	})
}

func (cmd *command) sendError(msg string) error {
	return cmd.send(&proto.CmdError{msg})
}

func termLoop(tr *vt100.TermReader, r io.Reader) error {
	br := bufio.NewReader(r)
	for {
		if err := tr.Read(br); err != nil {
			if isPtyEOFError(err) {
				err = io.EOF
			}
			return err
		}
	}
}

// run synchronously runs the subprocess to completion, sending terminal
// updates as it progresses.  It may return errors if the subprocess failed
// to run for whatever reason (e.g. no such path), and otherwise returns
// the subprocess exit code.
func (cmd *command) run() (int, error) {
	if cmd.cmd.Path == "cd" {
		if len(cmd.cmd.Args) != 2 {
			return 0, fmt.Errorf("bad arguments to cd")
		}
		dir := cmd.cmd.Args[1]
		st, err := os.Stat(dir)
		if err != nil {
			return 0, err
		}
		if !st.IsDir() {
			return 0, fmt.Errorf("%s: not a directory", dir)
		}
		return 0, nil
	}

	if filepath.Base(cmd.cmd.Path) == cmd.cmd.Path {
		// TODO: should use shell env $PATH.
		if p, err := exec.LookPath(cmd.cmd.Path); err != nil {
			return 0, err
		} else {
			cmd.cmd.Path = p
		}
	}

	size := pty.Winsize{
		Rows: 24,
		Cols: 80,
	}
	f, err := pty.StartWithSize(cmd.cmd, &size)
	if err != nil {
		return 0, err
	}

	cmd.stdin = make(chan []byte)
	go func() {
		for input := range cmd.stdin {
			f.Write(input)
		}
	}()

	var mu sync.Mutex // protects term, drawPending, and done
	wake := sync.NewCond(&mu)
	term := vt100.NewTerminal()
	drawPending := false
	var done error

	var tr *vt100.TermReader
	renderFromDirty := func() {
		// Called with mu held.
		allDirty := tr.Dirty.Lines[-1]
		update := &proto.TermUpdate{}
		if tr.Dirty.Cursor {
			update.Cursor = proto.Cursor{
				Row:    term.Row,
				Col:    term.Col,
				Hidden: term.HideCursor,
			}
		}
		for row, l := range term.Lines {
			if !(allDirty || tr.Dirty.Lines[row]) {
				continue
			}
			rowSpans := proto.RowSpans{
				Row: row,
			}
			span := proto.Span{}
			var attr vt100.Attr
			for _, cell := range l {
				if cell.Attr != attr {
					attr = cell.Attr
					rowSpans.Spans = append(rowSpans.Spans, span)
					span = proto.Span{Attr: int(attr)}
				}
				// TODO: super inefficient.
				span.Text += fmt.Sprintf("%c", cell.Ch)
			}
			if len(span.Text) > 0 {
				rowSpans.Spans = append(rowSpans.Spans, span)
			}
			update.Rows = append(update.Rows, rowSpans)
		}

		err := cmd.send(update)
		if err != nil {
			done = err
		}
	}

	tr = vt100.NewTermReader(func(f func(t *vt100.Terminal)) {
		// This is called from the 'go termLoop' goroutine,
		// when the vt100 impl wants to update the terminal.
		mu.Lock()
		f(term)
		if !drawPending {
			drawPending = true
			wake.Signal()
		}
		mu.Unlock()
	})

	go func() {
		err := termLoop(tr, f)
		mu.Lock()
		done = err
		wake.Signal()
		mu.Unlock()
	}()

	for {
		mu.Lock()
		for !drawPending && done == nil {
			wake.Wait()
		}

		if done == nil {
			mu.Unlock()
			// Allow more pending paints to enqueue.
			time.Sleep(10 * time.Millisecond)
			mu.Lock()
		}

		if drawPending { // There can be no draw pending if done != nil.
			renderFromDirty()
			tr.Dirty.Reset()
			drawPending = false
		}

		mu.Unlock()

		if done != nil {
			break
		}
	}

	mu.Lock()
	globalLastTermForCmd = term

	// done is the error reported by the terminal.
	// We expect EOF in normal execution.
	if done != io.EOF {
		return 0, err
	}

	// Reap the subprocess and report the exit code.
	if err := cmd.cmd.Wait(); err != nil {
		if eerr, ok := err.(*exec.ExitError); ok {
			serr := eerr.Sys().(syscall.WaitStatus)
			return serr.ExitStatus(), nil
		} else {
			return 0, err
		}
	}
	return 0, nil
}

// runHandlingErrors calls run() and forwards any subprocess errors
// on to the client.
func (cmd *command) runHandlingErrors() {
	exitCode, err := cmd.run()
	if err != nil {
		cmd.sendError(err.Error())
		exitCode = 1
	}
	cmd.send(&proto.Exit{exitCode})
}

var localCommands = map[string]func(w io.Writer) error{
	"that": func(w io.Writer) error {
		if globalLastTermForCmd == nil {
			return nil
		}
		_, err := io.WriteString(w, globalLastTermForCmd.ToString())
		return err
	},
}

func getEnv() map[string]string {
	env := map[string]string{}
	for _, keyval := range os.Environ() {
		eq := strings.Index(keyval, "=")
		if eq < 0 {
			panic("bad env?")
		}
		env[keyval[:eq]] = keyval[eq+1:]
	}
	return env
}

func mapPairs(m map[string]string) []proto.Pair {
	pairs := []proto.Pair{}
	for k, v := range m {
		pairs = append(pairs, proto.Pair{k, v})
	}
	return pairs
}

func serveWS(w http.ResponseWriter, r *http.Request) error {
	wsConn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return err
	}
	conn := &conn{
		ws: wsConn,
	}

	smashPath, err := os.Readlink("/proc/self/exe")
	if err != nil {
		return err
	}

	aliases, err := bash.GetAliases()
	if err != nil {
		return err
	}
	env := getEnv()
	env["SMASH"] = smashPath
	env["SMASH_SOCK"] = globalSockPathForEnv
	hello := &proto.Hello{
		Alias: mapPairs(aliases),
		Env:   mapPairs(env),
	}
	if err = conn.writeMsg(hello); err != nil {
		return err
	}

	commands := map[int]*command{}
	for {
		_, buf, err := conn.ws.ReadMessage()
		if err != nil {
			return fmt.Errorf("reading client message: %s", err)
		}
		var msg proto.ClientMessage
		if err := msg.Read(bufio.NewReader(bytes.NewBuffer(buf))); err != nil {
			return fmt.Errorf("parsing client message: %s", err)
		}

		switch msg := msg.Alt.(type) {
		case *proto.RunRequest:
			cmd := newCmd(conn, msg)
			commands[int(msg.Cell)] = cmd
			go cmd.runHandlingErrors()
		case *proto.KeyEvent:
			cmd := commands[int(msg.Cell)]
			if cmd == nil {
				log.Println("got key msg for unknown command", msg.Cell)
				continue
			}
			// TODO: what if cmd failed?
			// TODO: what if pipe is blocked?
			cmd.stdin <- []byte(msg.Keys)
		case *proto.CompleteRequest:
			if msg.Cwd == "" {
				panic("incomplete complete request")
			}
			go func() {
				if err := completer.Chdir(msg.Cwd); err != nil {
					log.Println(err) // TODO
				}
				pos, completions, err := completer.Complete(msg.Input[0:msg.Pos])
				if err != nil {
					log.Println(err) // TODO
				}
				err = conn.writeMsg(&proto.CompleteResponse{
					Id:          msg.Id,
					Pos:         pos,
					Completions: completions,
				})
				if err != nil {
					log.Println(err) // TODO
				}
			}()
		default:
			log.Println("unhandled msg", msg)
		}
	}
}

func serve() error {
	sockPath, localSock, err := setupLocalCommandSock()
	if err != nil {
		return err
	}
	go func() {
		if err := readLocalCommands(localSock); err != nil {
			fmt.Fprintf(os.Stderr, "local sock: %s\n", err)
		}
	}()
	globalSockPathForEnv = sockPath

	b, err := bash.StartBash()
	if err != nil {
		return err
	}
	completer = b

	http.Handle("/", http.FileServer(http.Dir("../web/dist")))
	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		if err := serveWS(w, r); err != nil {
			log.Printf("error: %s", err)
		}
	})
	addr := ":8080"
	fmt.Printf("listening on %q\n", addr)
	return http.ListenAndServe(addr, nil)
}

func localCommand(cmd string) error {
	sockPath := os.Getenv("SMASH_SOCK")
	if sockPath == "" {
		return fmt.Errorf("no $SMASH_SOCK; are you running under smash?")
	}

	conn, err := net.Dial("unix", sockPath)
	if err != nil {
		return err
	}
	defer conn.Close()
	if _, err = fmt.Fprintf(conn, "%s\n", cmd); err != nil {
		return err
	}
	if _, err = io.Copy(os.Stdout, conn); err != nil {
		return err
	}
	return nil
}

func main() {
	var cmd = "serve"
	if len(os.Args) > 1 {
		cmd = os.Args[1]
	}

	var err error
	if _, isLocal := localCommands[cmd]; isLocal {
		err = localCommand(cmd)
	} else {
		switch cmd {
		case "serve":
			err = serve()
		case "help":
		default:
			fmt.Println("TODO: usage")
		}
	}

	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package minhash

import (
	"errors"

	"github.com/dgryski/go-farm"
	"github.com/dgryski/go-spooky"
)

// MinHash is a data structure for generating a min-wise independent
// parametric family of hash functions of the form h1 + i*h2 for i=1, ..., k
// in order to to compute a MinHash signature.  Each instance is tied to a single
// streamed set and hence single signature.  As the instance
// ingests elements it keeps track of the minimum for the ith hash function.
type MinHash struct {
	mins []uint64 // mins[i] is the current min-value of ith hash func.
	h1   HashFunc
	h2   HashFunc
}

// New constructs a new MinHash instance.
func New(h1, h2 HashFunc, size int) *MinHash {
	return NewMinHash(spooky.Hash64, farm.Hash64, size)
}

// NewMinHash is deprecated.  Use New instead.
func NewMinHash(h1, h2 HashFunc, size int) *MinHash {
	mw := &MinHash{
		mins: emptySetSignature(size), // running set of min values
		h1:   h1,
		h2:   h2,
	}

	return mw
}

// NewFromSignature creates a MinHash initialzed with the input signature.
func NewhFromSignature(h1, h2 HashFunc, signature []uint64) *MinHash {
	return &MinHash{
		mins: signature,
		h1:   h1,
		h2:   h2,
	}
}

// NewMinHashFromSignature is deprecated.  Use NewFromSignature instead.
func NewMinHashFromSignature(h1, h2 HashFunc, sig []uint64) *MinHash {
	csig := make([]uint64, len(sig))
	copy(csig, sig)
	mw := MinHash{
		mins: csig,
		h1:   h1,
		h2:   h2,
	}
	return &mw
}

// SetHashes sets the two hash functions the MinHash uses to generate
// a parametric family of hash functions from which the set signature is
// derived.
func (m *MinHash) SetHashes(h1 HashFunc, h2 HashFunc) {
	m.h1, m.h2 = h1, h2
}

// SetSignature sets the instance's underlying signature.  This can change
// the type of the MinHash if the input signature length differs from the
// length of the instance's signature.
func (m *MinHash) SetSignature(signature []uint64) {
	m.mins = signature
}

// Signature returns the underlying signature slice.
func (m *MinHash) Signature() []uint64 {
	return m.mins
}

// Merge combines the signatures of the second set,
// creating the signature of their union.
func (m *MinHash) Merge(m2 Interface) error {

	if len(m.Signature()) != len(m2.Signature()) {
		return errors.New("Cannot merge signatures due to size mismatch.")
	}

	for i, v := range m2.Signature() {
		if v < m.mins[i] {
			m.mins[i] = v
		}
	}
	return nil
}

// IsEmpty reports whether the MinHash instance represents a signature
// of the empty set.  Note that it's possible for a signature of a
// non-empty set to equal the signature for the empty set in rare
// circumstances (e.g., when the hash family is not min-wise independent).
func (m *MinHash) IsEmpty() bool {
	return IsEmpty(m)
}

// Copy returns a new MinHash instance with the same type and data.
func (m *MinHash) Copy() *MinHash {
	return NewMinHashFromSignature(m.h1, m.h2, m.Signature())
}

// Push deals with generic data by handling byte conversion.
// It first hashes the input with each function in the instance's family,
// and then compares these values to the set of current mins,
// updating them as necessary.
func (m *MinHash) Push(x interface{}) {
	m.PushBytes(toBytes(x))
}

// PushBytes updates the set's signature.  It hashes the input
// with each function in the family and compares these values
// with the current set of mins, replacing them as necessary.
func (m *MinHash) PushBytes(b []byte) {

	v1 := m.h1(b)
	v2 := m.h2(b)

	// Compare minimal values
	for i, min := range m.mins {
		// Compute hi(b) for ith hash function hi
		hb := v1 + uint64(i)*v2
		// Ensure 0 is never pushed.
		if 0 < hb && hb < min {
			m.mins[i] = hb
		}
	}
}

// PushStringInt first converts a string into a uint64 before pushing.
func (m *MinHash) PushStringInt(s string) {
	m.PushBytes(stringIntToBytes(s))
}

// PushString casts the input as a []byte and pushes the element.
func (m *MinHash) PushString(s string) {
	m.PushBytes([]byte(s))
}

func (m *MinHash) PushStrings(ss ...string) {
	for _, s := range ss {
		m.PushString(s)
	}
}
<commit_msg>Fix typo.<commit_after>package minhash

import (
	"errors"

	"github.com/dgryski/go-farm"
	"github.com/dgryski/go-spooky"
)

// MinHash is a data structure for generating a min-wise independent
// parametric family of hash functions of the form h1 + i*h2 for i=1, ..., k
// in order to to compute a MinHash signature.  Each instance is tied to a single
// streamed set and hence single signature.  As the instance
// ingests elements it keeps track of the minimum for the ith hash function.
type MinHash struct {
	mins []uint64 // mins[i] is the current min-value of ith hash func.
	h1   HashFunc
	h2   HashFunc
}

// New constructs a new MinHash instance.
func New(h1, h2 HashFunc, size int) *MinHash {
	return NewMinHash(spooky.Hash64, farm.Hash64, size)
}

// NewMinHash is deprecated.  Use New instead.
func NewMinHash(h1, h2 HashFunc, size int) *MinHash {
	mw := &MinHash{
		mins: emptySetSignature(size), // running set of min values
		h1:   h1,
		h2:   h2,
	}

	return mw
}

// NewFromSignature creates a MinHash initialzed with the input signature.
func NewFromSignature(h1, h2 HashFunc, signature []uint64) *MinHash {
	return &MinHash{
		mins: signature,
		h1:   h1,
		h2:   h2,
	}
}

// NewMinHashFromSignature is deprecated.  Use NewFromSignature instead.
func NewMinHashFromSignature(h1, h2 HashFunc, sig []uint64) *MinHash {
	csig := make([]uint64, len(sig))
	copy(csig, sig)
	mw := MinHash{
		mins: csig,
		h1:   h1,
		h2:   h2,
	}
	return &mw
}

// SetHashes sets the two hash functions the MinHash uses to generate
// a parametric family of hash functions from which the set signature is
// derived.
func (m *MinHash) SetHashes(h1 HashFunc, h2 HashFunc) {
	m.h1, m.h2 = h1, h2
}

// SetSignature sets the instance's underlying signature.  This can change
// the type of the MinHash if the input signature length differs from the
// length of the instance's signature.
func (m *MinHash) SetSignature(signature []uint64) {
	m.mins = signature
}

// Signature returns the underlying signature slice.
func (m *MinHash) Signature() []uint64 {
	return m.mins
}

// Merge combines the signatures of the second set,
// creating the signature of their union.
func (m *MinHash) Merge(m2 Interface) error {

	if len(m.Signature()) != len(m2.Signature()) {
		return errors.New("Cannot merge signatures due to size mismatch.")
	}

	for i, v := range m2.Signature() {
		if v < m.mins[i] {
			m.mins[i] = v
		}
	}
	return nil
}

// IsEmpty reports whether the MinHash instance represents a signature
// of the empty set.  Note that it's possible for a signature of a
// non-empty set to equal the signature for the empty set in rare
// circumstances (e.g., when the hash family is not min-wise independent).
func (m *MinHash) IsEmpty() bool {
	return IsEmpty(m)
}

// Copy returns a new MinHash instance with the same type and data.
func (m *MinHash) Copy() *MinHash {
	return NewMinHashFromSignature(m.h1, m.h2, m.Signature())
}

// Push deals with generic data by handling byte conversion.
// It first hashes the input with each function in the instance's family,
// and then compares these values to the set of current mins,
// updating them as necessary.
func (m *MinHash) Push(x interface{}) {
	m.PushBytes(toBytes(x))
}

// PushBytes updates the set's signature.  It hashes the input
// with each function in the family and compares these values
// with the current set of mins, replacing them as necessary.
func (m *MinHash) PushBytes(b []byte) {

	v1 := m.h1(b)
	v2 := m.h2(b)

	// Compare minimal values
	for i, min := range m.mins {
		// Compute hi(b) for ith hash function hi
		hb := v1 + uint64(i)*v2
		// Ensure 0 is never pushed.
		if 0 < hb && hb < min {
			m.mins[i] = hb
		}
	}
}

// PushStringInt first converts a string into a uint64 before pushing.
func (m *MinHash) PushStringInt(s string) {
	m.PushBytes(stringIntToBytes(s))
}

// PushString casts the input as a []byte and pushes the element.
func (m *MinHash) PushString(s string) {
	m.PushBytes([]byte(s))
}

func (m *MinHash) PushStrings(ss ...string) {
	for _, s := range ss {
		m.PushString(s)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os/exec"
	"strings"
	"time"

	"github.com/streadway/amqp"
)

type RadiodanCommand struct {
	Action        string
	CorrelationId string
}

var dryRun bool

func main() {
	host, port := parseArgs()
	listenForCommand(host, port)
}

func parseArgs() (host string, port int) {
	flag.StringVar(&host, "host", "localhost", "Hostname for RabbitMQ")
	flag.IntVar(&port, "port", 5672, "Port for RabbitMQ")
	flag.BoolVar(&dryRun, "dry-run", false, "Dry Run (do not execute command)")

	flag.Parse()

	return
}

func listenForCommand(host string, port int) {
	amqpUri := fmt.Sprintf("amqp://%s:%d", host, port)
	exchangeName := "radiodan"
	routingKey := "command.device.shutdown"
	connected := false

	var conn *amqp.Connection

	for connected != true {
		tryConn, err := amqp.Dial(amqpUri)
		if err != nil {
			log.Printf("[!] Cannot connect", err)
			log.Printf("[*] Retry in 3 seconds")
			time.Sleep(3 * time.Second)
		} else {
			log.Printf("[*] Connected to %s", amqpUri)

			connected = true
			conn = tryConn
			defer conn.Close()
		}
	}

	ch, err := conn.Channel()
	failOnError(err, "Failed to open a channel")
	defer ch.Close()

	err = ch.ExchangeDeclare(
		exchangeName, // name
		"topic",      // type
		true,         // durable
		false,        // auto-deleted
		false,        // internal
		false,        // no-wait
		nil,          // arguments
	)
	failOnError(err, "Failed to declare an exchange")

	q, err := ch.QueueDeclare(
		"",    // name
		false, // durable
		false, // delete when usused
		true,  // exclusive
		false, // no-wait
		nil,   // arguments
	)
	failOnError(err, "Failed to declare a queue")

	err = ch.QueueBind(
		q.Name,     // queue name
		routingKey, // routing key
		"radiodan", // exchange
		false,
		nil)
	failOnError(err, "Failed to bind a queue")

	msgs, err := ch.Consume(
		q.Name, // queue
		"",     // consumer
		true,   // auto ack
		false,  // exclusive
		false,  // no local
		false,  // no wait
		nil,    // args
	)
	failOnError(err, "Failed to register a consumer")

	log.Println("[*] Consuming", q.Name)

	forever := make(chan bool)

	go func() {
		for m := range msgs {
			processMessage(m)
		}
	}()

	log.Printf("[*] Waiting for commands")
	<-forever
}

func processMessage(msg amqp.Delivery) {
	defer func() {
		if r := recover(); r != nil {
			log.Println("[!] Msg processing failed:", r)
		}
	}()

	cmd := RadiodanCommand{}

	err := json.Unmarshal(msg.Body, &cmd)
	failOnError(err, "Malformed Radiodan Command")

	log.Printf("[x] Received action: %s", cmd.Action)

	execCmd(cmd)
}

func execCmd(cmd RadiodanCommand) {
	var shutdownFlag, path string
	var args []string

	switch cmd.Action {
	case "restart":
		shutdownFlag = "-r"
	case "shutdown":
		shutdownFlag = "-h"
	default:
		panic("Action " + cmd.Action + " is neither restart nor shutdown")
	}

	if dryRun {
		path = "/bin/echo"
		args = []string{"shutdown", path, shutdownFlag, "now"}
	} else {
		path = "/sbin/shutdown"
		args = []string{path, shutdownFlag, "now"}
	}

	shutdown := exec.Cmd{
		Path: path,
		Args: args,
	}

	output, err := shutdown.CombinedOutput()
	outputStr := strings.TrimRight(string(output), "\n")

	failOnError(err, "Could not exec shutdown")
	log.Println("[x] exec:", outputStr)
}

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
		panic(fmt.Sprintf("%s: %s", msg, err))
	}
}
<commit_msg>Reply to commands<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os/exec"
	"strings"
	"time"

	"github.com/streadway/amqp"
)

type RadiodanCommand struct {
	Action        string
	CorrelationId string
}

var dryRun bool
var conn *amqp.Connection

func main() {
	host, port := parseArgs()
	listenForCommand(host, port)
}

func parseArgs() (host string, port int) {
	flag.StringVar(&host, "host", "localhost", "Hostname for RabbitMQ")
	flag.IntVar(&port, "port", 5672, "Port for RabbitMQ")
	flag.BoolVar(&dryRun, "dry-run", false, "Dry Run (do not execute command)")

	flag.Parse()

	return
}

func listenForCommand(host string, port int) {
	amqpUri := fmt.Sprintf("amqp://%s:%d", host, port)
	exchangeName := "radiodan"
	routingKey := "command.device.shutdown"
	connected := false

	for connected != true {
		tryConn, err := amqp.Dial(amqpUri)
		if err != nil {
			log.Printf("[!] Cannot connect", err)
			log.Printf("[*] Retry in 3 seconds")
			time.Sleep(3 * time.Second)
		} else {
			log.Printf("[*] Connected to %s", amqpUri)

			connected = true
			conn = tryConn
			defer conn.Close()
		}
	}

	ch, err := conn.Channel()
	failOnError(err, "Failed to open a channel")
	defer ch.Close()

	err = ch.ExchangeDeclare(
		exchangeName, // name
		"topic",      // type
		true,         // durable
		false,        // auto-deleted
		false,        // internal
		false,        // no-wait
		nil,          // arguments
	)
	failOnError(err, "Failed to declare an exchange")

	q, err := ch.QueueDeclare(
		"",    // name
		false, // durable
		false, // delete when usused
		true,  // exclusive
		false, // no-wait
		nil,   // arguments
	)
	failOnError(err, "Failed to declare a queue")

	err = ch.QueueBind(
		q.Name,     // queue name
		routingKey, // routing key
		"radiodan", // exchange
		false,
		nil)
	failOnError(err, "Failed to bind a queue")

	msgs, err := ch.Consume(
		q.Name, // queue
		"",     // consumer
		true,   // auto ack
		false,  // exclusive
		false,  // no local
		false,  // no wait
		nil,    // args
	)
	failOnError(err, "Failed to register a consumer")

	log.Println("[*] Consuming", q.Name)

	forever := make(chan bool)

	go func() {
		for m := range msgs {
			cmd, err := processMessage(m)

			if err == nil {
				replyToMessage(m, cmd)
				execCmd(cmd)
			}
		}
	}()

	log.Printf("[*] Waiting for commands")
	<-forever
}

func processMessage(msg amqp.Delivery) (RadiodanCommand, error) {
	defer func() {
		if r := recover(); r != nil {
			log.Println("[!] Msg processing failed:", r)
		}
	}()

	cmd := RadiodanCommand{}

	err := json.Unmarshal(msg.Body, &cmd)
	failOnError(err, "Malformed Radiodan Command")

	log.Printf("[x] Received action: %s", cmd.Action)

	return cmd, err
}

func replyToMessage(msg amqp.Delivery, cmd RadiodanCommand) {
	var err error

	response := "{\"error\": false, \"correlationId\": \"" + cmd.CorrelationId + "\"}"

	replyChannel, err := conn.Channel()
	failOnError(err, "[!] Could not create reply channel")

	reply := amqp.Publishing{
		DeliveryMode: amqp.Persistent,
		Timestamp:    time.Now(),
		ContentType:  "text/plain",
		Body:         []byte(response),
	}

	err = replyChannel.Publish(
		"",          // exchange
		msg.ReplyTo, // key
		false,       // mandatory
		false,       // thing
		reply,       // immediate
	)

	failOnError(err, "[!] Could not reply to command")
	log.Println("[*] Replying to message", response)
}

func execCmd(cmd RadiodanCommand) {
	var shutdownFlag, path string
	var args []string

	switch cmd.Action {
	case "restart":
		shutdownFlag = "-r"
	case "shutdown":
		shutdownFlag = "-h"
	default:
		panic("Action " + cmd.Action + " is neither restart nor shutdown")
	}

	if dryRun {
		path = "/bin/echo"
		args = []string{"shutdown", path, shutdownFlag, "now"}
	} else {
		path = "/sbin/shutdown"
		args = []string{path, shutdownFlag, "now"}
	}

	shutdown := exec.Cmd{
		Path: path,
		Args: args,
	}

	output, err := shutdown.CombinedOutput()
	outputStr := strings.TrimRight(string(output), "\n")

	failOnError(err, "Could not exec shutdown")
	log.Println("[x] exec:", outputStr)
}

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
		panic(fmt.Sprintf("%s: %s", msg, err))
	}
}
<|endoftext|>


<commit_before>/*
 * Concert (C) 2016 Minio, Inc.
 *
 * 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 (
	"crypto/rand"
	"crypto/rsa"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/xenolf/lego/acme"
)

// renewDaysLimit - renewal is not initated if cert is valid with in this limit.
const renewDaysLimit = 45 // Number of days.

// getCert expiration time.
func getCertExpTime(certsDir string) (time.Time, error) {
	certBytes, err := loadCert(certsDir)
	if err != nil {
		return time.Time{}, err
	}
	return acme.GetPEMCertExpiration(certBytes)
}

// isValidDomain validates if input string is a valid domain name.
func isValidDomain(host string) bool {
	// See RFC 1035, RFC 3696.
	host = strings.TrimSpace(host)
	if len(host) == 0 || len(host) > 255 {
		return false
	}
	// host cannot start or end with "-"
	if host[len(host)-1:] == "-" || host[:1] == "-" {
		return false
	}
	// host cannot start or end with "_"
	if host[len(host)-1:] == "_" || host[:1] == "_" {
		return false
	}
	// host cannot start or end with a "."
	if host[len(host)-1:] == "." || host[:1] == "." {
		return false
	}
	// All non alphanumeric characters are invalid.
	if strings.ContainsAny(host, "`~!@#$%^&*()+={}[]|\\\"';:><?/") {
		return false
	}
	// No need to regexp match, since the list is non-exhaustive.
	// We let it valid and fail later.
	return true
}

// isSubDomain - is domain a subdomain.
func isSubDomain(domain string) bool {
	domainParts := strings.Split(domain, ".")
	// More than 2 parts in a domain name, its a subdomain.
	return len(domainParts) > 2
}

// generate certificates.
func genCerts(email, domain string, subDomains []string) (acme.CertificateResource, error) {
	// Create a user. New accounts need an email and private key to start with.
	const rsaKeySize = 2048
	privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	// Initialize user.
	user := conUser{
		Email: email,
		key:   privateKey,
	}

	// A client facilitates communication with the CA server.
	client, err := acme.NewClient(acmeServer, &user, acme.RSA2048)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	client.ExcludeChallenges([]acme.Challenge{acme.DNS01})

	// New users will need to register; be sure to save it
	reg, err := client.Register()
	if err != nil {
		return acme.CertificateResource{}, err
	}
	user.Registration = reg

	// The client has a URL to the current Let's Encrypt Subscriber
	// Agreement. The user will need to agree to it.
	err = client.AgreeToTOS()
	if err != nil {
		return acme.CertificateResource{}, err
	}

	domains := []string{domain}
	if !isSubDomain(domain) {
		for _, subDomain := range subDomains {
			domains = append(domains, subDomain+"."+domain)
		}
	}
	// The acme library takes care of completing the challenges to
	// obtain the certificate(s). Of course, the hostnames must
	// resolve to this machine or it will fail.
	isBundle := true // Bundle all domains into one.
	newCertificates, failures := client.ObtainCertificate(domains, isBundle, nil)
	if len(failures) > 0 {
		var failedDomains []string
		var failedDomainsErrors []error
		for failedDomain, failedDomainErr := range failures {
			failedDomains = append(failedDomains, failedDomain)
			failedDomainsErrors = append(failedDomainsErrors, failedDomainErr)
		}
		failure := fmt.Errorf("Failed to obtain certificates for Domains: %s, with following errors %s respectively.", failedDomains, failedDomainsErrors)
		return acme.CertificateResource{}, failure
	}
	return newCertificates, nil
}

// Renew certificates.
func renewCerts(certsDir, email string) (acme.CertificateResource, error) {
	certBytes, err := loadCert(certsDir)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	expTime, err := acme.GetPEMCertExpiration(certBytes)
	expTimeDays := int(expTime.Sub(time.Now()).Hours() / 24.0)
	if expTimeDays > renewDaysLimit {
		return acme.CertificateResource{}, fmt.Errorf("Keys have not expired yet, please renew in %d days.", expTimeDays)
	}

	// Create a user. New accounts need an email and private key to start with.
	const rsaKeySize = 2048
	privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	// Initialize user.
	user := conUser{
		Email: email,
		key:   privateKey,
	}

	// A client facilitates communication with the CA server. This CA
	// URL is configured for a local dev instance of Boulder running
	// in Docker in a VM.
	client, err := acme.NewClient(acmeServer, &user, acme.RSA2048)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	client.ExcludeChallenges([]acme.Challenge{acme.DNS01})

	certMeta, err := loadCertMeta(certsDir)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	// Save current cert bytes.
	certMeta.Certificate = certBytes

	isBundle := true // Bundle all domains into one.
	newCertificates, err := client.RenewCertificate(certMeta, isBundle)
	if err != nil {
		return acme.CertificateResource{}, err
	}
	return newCertificates, nil
}

// load certificate meta resource.
func loadCertMeta(certsDir string) (acme.CertificateResource, error) {
	metaBytes, err := ioutil.ReadFile(filepath.Join(certsDir, "certs.json"))
	if err != nil {
		return acme.CertificateResource{}, err
	}
	var certRes acme.CertificateResource
	err = json.Unmarshal(metaBytes, &certRes)
	if err != nil {
		return acme.CertificateResource{}, err
	}
	return certRes, nil
}

// load certs.
func loadCert(certsDir string) ([]byte, error) {
	return ioutil.ReadFile(filepath.Join(certsDir, "public.crt"))
}

// saveCerts saves the certificates to disk. This includes the
// certificate file itself, the private key, and the json metadata file.
func saveCerts(certsDir string, cert acme.CertificateResource) error {
	// Save cert file.
	err := ioutil.WriteFile(filepath.Join(certsDir, "public.crt"), cert.Certificate, 0600)
	if err != nil {
		return err
	}

	// Save private key.
	err = ioutil.WriteFile(filepath.Join(certsDir, "private.key"), cert.PrivateKey, 0600)
	if err != nil {
		return err
	}

	// Save cert metadata.
	jsonBytes, err := json.MarshalIndent(&cert, "", "\t")
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(filepath.Join(certsDir, "certs.json"), jsonBytes, 0600)
	if err != nil {
		return err
	}

	// Return success.
	return nil
}

// Verify if certs are available in a certs dir.
func isCertAvailable(certsDir string) bool {
	_, crtErr := os.Stat(filepath.Join(certsDir, "public.crt"))
	_, keyErr := os.Stat(filepath.Join(certsDir, "private.key"))
	return crtErr == nil && keyErr == nil
}
<commit_msg>Fix `not enough arguments` errors (#16)<commit_after>/*
 * Concert (C) 2016 Minio, Inc.
 *
 * 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 (
	"crypto/rand"
	"crypto/rsa"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/xenolf/lego/acme"
)

// renewDaysLimit - renewal is not initated if cert is valid with in this limit.
const renewDaysLimit = 45 // Number of days.

// getCert expiration time.
func getCertExpTime(certsDir string) (time.Time, error) {
	certBytes, err := loadCert(certsDir)
	if err != nil {
		return time.Time{}, err
	}
	return acme.GetPEMCertExpiration(certBytes)
}

// isValidDomain validates if input string is a valid domain name.
func isValidDomain(host string) bool {
	// See RFC 1035, RFC 3696.
	host = strings.TrimSpace(host)
	if len(host) == 0 || len(host) > 255 {
		return false
	}
	// host cannot start or end with "-"
	if host[len(host)-1:] == "-" || host[:1] == "-" {
		return false
	}
	// host cannot start or end with "_"
	if host[len(host)-1:] == "_" || host[:1] == "_" {
		return false
	}
	// host cannot start or end with a "."
	if host[len(host)-1:] == "." || host[:1] == "." {
		return false
	}
	// All non alphanumeric characters are invalid.
	if strings.ContainsAny(host, "`~!@#$%^&*()+={}[]|\\\"';:><?/") {
		return false
	}
	// No need to regexp match, since the list is non-exhaustive.
	// We let it valid and fail later.
	return true
}

// isSubDomain - is domain a subdomain.
func isSubDomain(domain string) bool {
	domainParts := strings.Split(domain, ".")
	// More than 2 parts in a domain name, its a subdomain.
	return len(domainParts) > 2
}

// generate certificates.
func genCerts(email, domain string, subDomains []string) (acme.CertificateResource, error) {
	// Create a user. New accounts need an email and private key to start with.
	const rsaKeySize = 2048
	privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	// Initialize user.
	user := conUser{
		Email: email,
		key:   privateKey,
	}

	// A client facilitates communication with the CA server.
	client, err := acme.NewClient(acmeServer, &user, acme.RSA2048)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	client.ExcludeChallenges([]acme.Challenge{acme.DNS01})

	// New users will need to register; be sure to save it
	reg, err := client.Register()
	if err != nil {
		return acme.CertificateResource{}, err
	}
	user.Registration = reg

	// The client has a URL to the current Let's Encrypt Subscriber
	// Agreement. The user will need to agree to it.
	err = client.AgreeToTOS()
	if err != nil {
		return acme.CertificateResource{}, err
	}

	domains := []string{domain}
	if !isSubDomain(domain) {
		for _, subDomain := range subDomains {
			domains = append(domains, subDomain+"."+domain)
		}
	}
	// The acme library takes care of completing the challenges to
	// obtain the certificate(s). Of course, the hostnames must
	// resolve to this machine or it will fail.
	isBundle := true // Bundle all domains into one.
	newCertificates, failures := client.ObtainCertificate(domains, isBundle, nil, true)
	if len(failures) > 0 {
		var failedDomains []string
		var failedDomainsErrors []error
		for failedDomain, failedDomainErr := range failures {
			failedDomains = append(failedDomains, failedDomain)
			failedDomainsErrors = append(failedDomainsErrors, failedDomainErr)
		}
		failure := fmt.Errorf("Failed to obtain certificates for Domains: %s, with following errors %s respectively.", failedDomains, failedDomainsErrors)
		return acme.CertificateResource{}, failure
	}
	return newCertificates, nil
}

// Renew certificates.
func renewCerts(certsDir, email string) (acme.CertificateResource, error) {
	certBytes, err := loadCert(certsDir)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	expTime, err := acme.GetPEMCertExpiration(certBytes)
	expTimeDays := int(expTime.Sub(time.Now()).Hours() / 24.0)
	if expTimeDays > renewDaysLimit {
		return acme.CertificateResource{}, fmt.Errorf("Keys have not expired yet, please renew in %d days.", expTimeDays)
	}

	// Create a user. New accounts need an email and private key to start with.
	const rsaKeySize = 2048
	privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	// Initialize user.
	user := conUser{
		Email: email,
		key:   privateKey,
	}

	// A client facilitates communication with the CA server. This CA
	// URL is configured for a local dev instance of Boulder running
	// in Docker in a VM.
	client, err := acme.NewClient(acmeServer, &user, acme.RSA2048)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	client.ExcludeChallenges([]acme.Challenge{acme.DNS01})

	certMeta, err := loadCertMeta(certsDir)
	if err != nil {
		return acme.CertificateResource{}, err
	}

	// Save current cert bytes.
	certMeta.Certificate = certBytes

	isBundle := true // Bundle all domains into one.
	newCertificates, err := client.RenewCertificate(certMeta, isBundle, true)
	if err != nil {
		return acme.CertificateResource{}, err
	}
	return newCertificates, nil
}

// load certificate meta resource.
func loadCertMeta(certsDir string) (acme.CertificateResource, error) {
	metaBytes, err := ioutil.ReadFile(filepath.Join(certsDir, "certs.json"))
	if err != nil {
		return acme.CertificateResource{}, err
	}
	var certRes acme.CertificateResource
	err = json.Unmarshal(metaBytes, &certRes)
	if err != nil {
		return acme.CertificateResource{}, err
	}
	return certRes, nil
}

// load certs.
func loadCert(certsDir string) ([]byte, error) {
	return ioutil.ReadFile(filepath.Join(certsDir, "public.crt"))
}

// saveCerts saves the certificates to disk. This includes the
// certificate file itself, the private key, and the json metadata file.
func saveCerts(certsDir string, cert acme.CertificateResource) error {
	// Save cert file.
	err := ioutil.WriteFile(filepath.Join(certsDir, "public.crt"), cert.Certificate, 0600)
	if err != nil {
		return err
	}

	// Save private key.
	err = ioutil.WriteFile(filepath.Join(certsDir, "private.key"), cert.PrivateKey, 0600)
	if err != nil {
		return err
	}

	// Save cert metadata.
	jsonBytes, err := json.MarshalIndent(&cert, "", "\t")
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(filepath.Join(certsDir, "certs.json"), jsonBytes, 0600)
	if err != nil {
		return err
	}

	// Return success.
	return nil
}

// Verify if certs are available in a certs dir.
func isCertAvailable(certsDir string) bool {
	_, crtErr := os.Stat(filepath.Join(certsDir, "public.crt"))
	_, keyErr := os.Stat(filepath.Join(certsDir, "private.key"))
	return crtErr == nil && keyErr == nil
}
<|endoftext|>