<commit_before>package main

import (
	"bufio"
	"fmt"
	"net"
	"strings"
)

type User struct {
	nick       string
	user       string
	ident      string
	ip         string
	dead       bool
	connection net.Conn
	id         int
}

func (user *User) Quit() {
	user.dead = true
	user.connection.Close()
	delete(userlist, user.id)
}

//set a user's nick. Does not do any error checking
func (user *User) SetNick(new string) {
	user.nick = new
}

//returns a user's nick in string format
func (user *User) GetNick() string {
	return user.nick
}

func (user *User) SetConn(in net.Conn) {
	user.connection = in
}

func (user *User) GetConn() net.Conn {
	return user.connection
}

func (user *User) SendLine(msg string) {
	conn := user.GetConn()
	msg = fmt.Sprintf("%s\n", msg)
	conn.Write([]byte(msg))
}

func (user *User) HandleRequests() {
	conn := user.GetConn()
	b := bufio.NewReader(conn)
	for {
		if user.dead {
			break
		}
		line, err := b.ReadString('\n')
		if err != nil {
			fmt.Println("Error reading:", err.Error())
			user.Quit()
		}
		line = strings.TrimSpace(line)
		fmt.Println("Received Line: ", line)
		// Send a response back to person contacting us.
		go ProcessLine(user, line)
	}
}
<commit_msg>remove useless getters and setters<commit_after>package main

import (
	"bufio"
	"fmt"
	"net"
	"strings"
)

type User struct {
	nick       string
	user       string
	ident      string
	ip         string
	dead       bool
	connection net.Conn
	id         int
}

func (user *User) Quit() {
	user.dead = true
	user.connection.Close()
	delete(userlist, user.id)
}

func (user *User) SendLine(msg string) {
	msg = fmt.Sprintf("%s\n", msg)
	user.connection.Write([]byte(msg))
}

func (user *User) HandleRequests() {
	b := bufio.NewReader(user.connection)
	for {
		if user.dead {
			break
		}
		line, err := b.ReadString('\n')
		if err != nil {
			fmt.Println("Error reading:", err.Error())
			user.Quit()
		}
		line = strings.TrimSpace(line)
		fmt.Println("Received Line: ", line)
		// Send a response back to person contacting us.
		go ProcessLine(user, line)
	}
}
<|endoftext|>


<commit_before>/* Provides a low-level Go interface to the systemd journal C API.

All public methods map closely to the sd-journal API functions. See the
sd-journal.h documentation[1] for information about each function.

[1] http://www.freedesktop.org/software/systemd/man/sd-journal.html
*/
package journal

/*
#cgo pkg-config: libsystemd-journal
#include <systemd/sd-journal.h>
#include <stdlib.h>
#include <syslog.h>

int go_sd_journal_print(int priority, char* s) {
	int r;
	r = sd_journal_print(priority, "%s", s);
	return r;
}
*/
import "C"
import (
	"fmt"
	"unsafe"
)

// Journal entry field strings which correspond to:
// http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
const (
	SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT"
)

// Journal event constants
const (
	SD_JOURNAL_NOP        = int(C.SD_JOURNAL_NOP)
	SD_JOURNAL_APPEND     = int(C.SD_JOURNAL_APPEND)
	SD_JOURNAL_INVALIDATE = int(C.SD_JOURNAL_INVALIDATE)
)

// A Journal is a Go wrapper of an sd_journal structure.
type Journal struct {
	cjournal *C.sd_journal
}

// A Match is a convenience wrapper to describe filters supplied to AddMatch.
type Match struct {
	Field string
	Value string
}

// String returns a string representation of a Match suitable for use with AddMatch.
func (m *Match) String() string {
	return m.Field + "=" + m.Value
}

func NewJournal() (*Journal, error) {
	j := &Journal{}
	err := C.sd_journal_open(&j.cjournal, C.SD_JOURNAL_LOCAL_ONLY)

	if err < 0 {
		return nil, fmt.Errorf("failed to open journal: %s", err)
	}

	return j, nil
}

func (j *Journal) Close() error {
	C.sd_journal_close(j.cjournal)
	return nil
}

func (j *Journal) AddMatch(match string) error {
	m := C.CString(match)
	defer C.free(unsafe.Pointer(m))

	C.sd_journal_add_match(j.cjournal, unsafe.Pointer(m), C.size_t(len(match)))
	return nil
}

func (j *Journal) Next() (int, error) {
	r := C.sd_journal_next(j.cjournal)

	if r < 0 {
		return int(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return int(r), nil
}

func (j *Journal) Previous() (uint64, error) {
	r := C.sd_journal_previous(j.cjournal)

	if r < 0 {
		return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return uint64(r), nil
}

func (j *Journal) PreviousSkip(skip uint64) (uint64, error) {
	r := C.sd_journal_previous_skip(j.cjournal, C.uint64_t(skip))

	if r < 0 {
		return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return uint64(r), nil
}

func (j *Journal) GetData(field string) (string, error) {
	f := C.CString(field)
	defer C.free(unsafe.Pointer(f))

	var d unsafe.Pointer
	var l C.size_t

	err := C.sd_journal_get_data(j.cjournal, f, &d, &l)

	if err < 0 {
		return "", fmt.Errorf("failed to read message: %d", err)
	}

	msg := C.GoStringN((*C.char)(d), C.int(l))
	return msg, nil
}

func (j *Journal) GetRealtimeUsec() (uint64, error) {
	var usec C.uint64_t

	r := C.sd_journal_get_realtime_usec(j.cjournal, &usec)

	if r < 0 {
		return 0, fmt.Errorf("error getting timestamp for entry: %d", r)
	}

	return uint64(usec), nil
}

func (j *Journal) Print(priority int, message string) error {
	m := C.CString(message)
	defer C.free(unsafe.Pointer(m))

	err := C.go_sd_journal_print(C.LOG_INFO, m)

	if err != 0 {
		return fmt.Errorf("failed to print message: %s", err)
	}

	return nil
}

func (j *Journal) SeekTail() error {
	err := C.sd_journal_seek_tail(j.cjournal)

	if err != 0 {
		return fmt.Errorf("failed to seek to tail of journal: %s", err)
	}

	return nil
}

func (j *Journal) SeekRealtimeUsec(usec uint64) error {
	err := C.sd_journal_seek_realtime_usec(j.cjournal, C.uint64_t(usec))

	if err != 0 {
		return fmt.Errorf("failed to seek to %d: %d", usec, int(err))
	}

	return nil
}

func (j *Journal) Wait(timeout uint64) int {
	r := C.sd_journal_wait(j.cjournal, C.uint64_t(timeout))

	return int(r)
}
<commit_msg>journal: add some more common fields<commit_after>/* Provides a low-level Go interface to the systemd journal C API.

All public methods map closely to the sd-journal API functions. See the
sd-journal.h documentation[1] for information about each function.

[1] http://www.freedesktop.org/software/systemd/man/sd-journal.html
*/
package journal

/*
#cgo pkg-config: libsystemd-journal
#include <systemd/sd-journal.h>
#include <stdlib.h>
#include <syslog.h>

int go_sd_journal_print(int priority, char* s) {
	int r;
	r = sd_journal_print(priority, "%s", s);
	return r;
}
*/
import "C"
import (
	"fmt"
	"unsafe"
)

// Journal entry field strings which correspond to:
// http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
const (
	SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT"
	SD_JOURNAL_FIELD_MESSAGE      = "MESSAGE"
	SD_JOURNAL_FIELD_PID          = "_PID"
	SD_JOURNAL_FIELD_UID          = "_UID"
	SD_JOURNAL_FIELD_GID          = "_GID"
	SD_JOURNAL_FIELD_HOSTNAME     = "_HOSTNAME"
	SD_JOURNAL_FIELD_MACHINE_ID   = "_MACHINE_ID"
)

// Journal event constants
const (
	SD_JOURNAL_NOP        = int(C.SD_JOURNAL_NOP)
	SD_JOURNAL_APPEND     = int(C.SD_JOURNAL_APPEND)
	SD_JOURNAL_INVALIDATE = int(C.SD_JOURNAL_INVALIDATE)
)

// A Journal is a Go wrapper of an sd_journal structure.
type Journal struct {
	cjournal *C.sd_journal
}

// A Match is a convenience wrapper to describe filters supplied to AddMatch.
type Match struct {
	Field string
	Value string
}

// String returns a string representation of a Match suitable for use with AddMatch.
func (m *Match) String() string {
	return m.Field + "=" + m.Value
}

// NewJournal returns a new Journal instance pointing to the local journal
func NewJournal() (*Journal, error) {
	j := &Journal{}
	err := C.sd_journal_open(&j.cjournal, C.SD_JOURNAL_LOCAL_ONLY)

	if err < 0 {
		return nil, fmt.Errorf("failed to open journal: %s", err)
	}

	return j, nil
}

func (j *Journal) Close() error {
	C.sd_journal_close(j.cjournal)
	return nil
}

func (j *Journal) AddMatch(match string) error {
	m := C.CString(match)
	defer C.free(unsafe.Pointer(m))

	C.sd_journal_add_match(j.cjournal, unsafe.Pointer(m), C.size_t(len(match)))
	return nil
}

func (j *Journal) Next() (int, error) {
	r := C.sd_journal_next(j.cjournal)

	if r < 0 {
		return int(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return int(r), nil
}

func (j *Journal) Previous() (uint64, error) {
	r := C.sd_journal_previous(j.cjournal)

	if r < 0 {
		return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return uint64(r), nil
}

func (j *Journal) PreviousSkip(skip uint64) (uint64, error) {
	r := C.sd_journal_previous_skip(j.cjournal, C.uint64_t(skip))

	if r < 0 {
		return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return uint64(r), nil
}

func (j *Journal) GetData(field string) (string, error) {
	f := C.CString(field)
	defer C.free(unsafe.Pointer(f))

	var d unsafe.Pointer
	var l C.size_t

	err := C.sd_journal_get_data(j.cjournal, f, &d, &l)

	if err < 0 {
		return "", fmt.Errorf("failed to read message: %d", err)
	}

	msg := C.GoStringN((*C.char)(d), C.int(l))
	return msg, nil
}

func (j *Journal) GetRealtimeUsec() (uint64, error) {
	var usec C.uint64_t

	r := C.sd_journal_get_realtime_usec(j.cjournal, &usec)

	if r < 0 {
		return 0, fmt.Errorf("error getting timestamp for entry: %d", r)
	}

	return uint64(usec), nil
}

func (j *Journal) Print(priority int, message string) error {
	m := C.CString(message)
	defer C.free(unsafe.Pointer(m))

	err := C.go_sd_journal_print(C.LOG_INFO, m)

	if err != 0 {
		return fmt.Errorf("failed to print message: %s", err)
	}

	return nil
}

func (j *Journal) SeekTail() error {
	err := C.sd_journal_seek_tail(j.cjournal)

	if err != 0 {
		return fmt.Errorf("failed to seek to tail of journal: %s", err)
	}

	return nil
}

func (j *Journal) SeekRealtimeUsec(usec uint64) error {
	err := C.sd_journal_seek_realtime_usec(j.cjournal, C.uint64_t(usec))

	if err != 0 {
		return fmt.Errorf("failed to seek to %d: %d", usec, int(err))
	}

	return nil
}

func (j *Journal) Wait(timeout uint64) int {
	r := C.sd_journal_wait(j.cjournal, C.uint64_t(timeout))

	return int(r)
}
<|endoftext|>


<commit_before>package consul

import (
	"fmt"
	"strings"

	"github.com/hashicorp/terraform/backend"
	"github.com/hashicorp/terraform/state"
	"github.com/hashicorp/terraform/state/remote"
	"github.com/hashicorp/terraform/terraform"
)

const (
	keyEnvPrefix = "-env:"
)

func (b *Backend) States() ([]string, error) {
	// Get the Consul client
	client, err := b.clientRaw()
	if err != nil {
		return nil, err
	}

	// List our raw path
	prefix := b.configData.Get("path").(string) + keyEnvPrefix
	keys, _, err := client.KV().Keys(prefix, "/", nil)
	if err != nil {
		return nil, err
	}

	// Find the envs, we use a map since we can get duplicates with
	// path suffixes.
	envs := map[string]struct{}{}
	for _, key := range keys {
		// Consul should ensure this but it doesn't hurt to check again
		if strings.HasPrefix(key, prefix) {
			key = strings.TrimPrefix(key, prefix)

			// Ignore anything with a "/" in it since we store the state
			// directly in a key not a directory.
			if idx := strings.IndexRune(key, '/'); idx >= 0 {
				continue
			}

			envs[key] = struct{}{}
		}
	}

	result := make([]string, 1, len(envs)+1)
	result[0] = backend.DefaultStateName
	for k, _ := range envs {
		result = append(result, k)
	}

	return result, nil
}

func (b *Backend) DeleteState(name string) error {
	if name == backend.DefaultStateName {
		return fmt.Errorf("can't delete default state")
	}

	// Get the Consul API client
	client, err := b.clientRaw()
	if err != nil {
		return err
	}

	// Determine the path of the data
	path := b.path(name)

	// Delete it
	_, err = client.KV().Delete(path, nil)
	return err
}

func (b *Backend) State(name string) (state.State, error) {
	// Get the Consul API client
	client, err := b.clientRaw()
	if err != nil {
		return nil, err
	}

	// Determine the path of the data
	path := b.path(name)

	// Build the state client
	stateMgr := &remote.State{
		Client: &RemoteClient{
			Client: client,
			Path:   path,
		},
	}

	// Grab a lock, we use this to write an empty state if one doesn't
	// exist already. We have to write an empty state as a sentinel value
	// so States() knows it exists.
	lockInfo := state.NewLockInfo()
	lockInfo.Operation = "init"
	lockId, err := stateMgr.Lock(lockInfo)
	if err != nil {
		return nil, fmt.Errorf("failed to lock state in Consul: %s", err)
	}

	// Local helper function so we can call it multiple places
	lockUnlock := func(parent error) error {
		if err := stateMgr.Unlock(lockId); err != nil {
			return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
		}

		return parent
	}

	// Grab the value
	if err := stateMgr.RefreshState(); err != nil {
		err = lockUnlock(err)
		return nil, err
	}

	// If we have no state, we have to create an empty state
	if v := stateMgr.State(); v == nil {
		if err := stateMgr.WriteState(terraform.NewState()); err != nil {
			err = lockUnlock(err)
			return nil, err
		}
		if err := stateMgr.PersistState(); err != nil {
			err = lockUnlock(err)
			return nil, err
		}
	}

	// Unlock, the state should now be initialized
	if err := lockUnlock(nil); err != nil {
		return nil, err
	}

	return stateMgr, nil
}

func (b *Backend) path(name string) string {
	path := b.configData.Get("path").(string)
	if name != backend.DefaultStateName {
		path += fmt.Sprintf("%s%s", keyEnvPrefix, name)
	}

	return path
}

const errStateUnlock = `
Error unlocking Consul state. Lock ID: %s

Error: %s

You may have to force-unlock this state in order to use it again.
The Consul backend acquires a lock during initialization to ensure
the minimum required key/values are prepared.
`
<commit_msg>backend/consul: some comments<commit_after>package consul

import (
	"fmt"
	"strings"

	"github.com/hashicorp/terraform/backend"
	"github.com/hashicorp/terraform/state"
	"github.com/hashicorp/terraform/state/remote"
	"github.com/hashicorp/terraform/terraform"
)

const (
	keyEnvPrefix = "-env:"
)

func (b *Backend) States() ([]string, error) {
	// Get the Consul client
	client, err := b.clientRaw()
	if err != nil {
		return nil, err
	}

	// List our raw path
	prefix := b.configData.Get("path").(string) + keyEnvPrefix
	keys, _, err := client.KV().Keys(prefix, "/", nil)
	if err != nil {
		return nil, err
	}

	// Find the envs, we use a map since we can get duplicates with
	// path suffixes.
	envs := map[string]struct{}{}
	for _, key := range keys {
		// Consul should ensure this but it doesn't hurt to check again
		if strings.HasPrefix(key, prefix) {
			key = strings.TrimPrefix(key, prefix)

			// Ignore anything with a "/" in it since we store the state
			// directly in a key not a directory.
			if idx := strings.IndexRune(key, '/'); idx >= 0 {
				continue
			}

			envs[key] = struct{}{}
		}
	}

	result := make([]string, 1, len(envs)+1)
	result[0] = backend.DefaultStateName
	for k, _ := range envs {
		result = append(result, k)
	}

	return result, nil
}

func (b *Backend) DeleteState(name string) error {
	if name == backend.DefaultStateName {
		return fmt.Errorf("can't delete default state")
	}

	// Get the Consul API client
	client, err := b.clientRaw()
	if err != nil {
		return err
	}

	// Determine the path of the data
	path := b.path(name)

	// Delete it. We just delete it without any locking since
	// the DeleteState API is documented as such.
	_, err = client.KV().Delete(path, nil)
	return err
}

func (b *Backend) State(name string) (state.State, error) {
	// Get the Consul API client
	client, err := b.clientRaw()
	if err != nil {
		return nil, err
	}

	// Determine the path of the data
	path := b.path(name)

	// Build the state client
	stateMgr := &remote.State{
		Client: &RemoteClient{
			Client: client,
			Path:   path,
		},
	}

	// Grab a lock, we use this to write an empty state if one doesn't
	// exist already. We have to write an empty state as a sentinel value
	// so States() knows it exists.
	lockInfo := state.NewLockInfo()
	lockInfo.Operation = "init"
	lockId, err := stateMgr.Lock(lockInfo)
	if err != nil {
		return nil, fmt.Errorf("failed to lock state in Consul: %s", err)
	}

	// Local helper function so we can call it multiple places
	lockUnlock := func(parent error) error {
		if err := stateMgr.Unlock(lockId); err != nil {
			return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
		}

		return parent
	}

	// Grab the value
	if err := stateMgr.RefreshState(); err != nil {
		err = lockUnlock(err)
		return nil, err
	}

	// If we have no state, we have to create an empty state
	if v := stateMgr.State(); v == nil {
		if err := stateMgr.WriteState(terraform.NewState()); err != nil {
			err = lockUnlock(err)
			return nil, err
		}
		if err := stateMgr.PersistState(); err != nil {
			err = lockUnlock(err)
			return nil, err
		}
	}

	// Unlock, the state should now be initialized
	if err := lockUnlock(nil); err != nil {
		return nil, err
	}

	return stateMgr, nil
}

func (b *Backend) path(name string) string {
	path := b.configData.Get("path").(string)
	if name != backend.DefaultStateName {
		path += fmt.Sprintf("%s%s", keyEnvPrefix, name)
	}

	return path
}

const errStateUnlock = `
Error unlocking Consul state. Lock ID: %s

Error: %s

You may have to force-unlock this state in order to use it again.
The Consul backend acquires a lock during initialization to ensure
the minimum required key/values are prepared.
`
<|endoftext|>


<commit_before>package dnsr

import (
	"fmt"
	"io"
	"strings"
	"sync"
	"time"

	lru "github.com/hashicorp/golang-lru"
	"github.com/miekg/dns"
)

//go:generate sh generate.sh

var (
	Root        *Resolver
	DebugLogger io.Writer
	Timeout     = 500 * time.Millisecond
)

func init() {
	Root = New(strings.Count(root, "\n"))
	for t := range dns.ParseZone(strings.NewReader(root), "", "") {
		if t.Error == nil {
			Root.saveDNSRR(t.RR)
		}
	}
}

// Resolver implements a primitive, non-recursive, caching DNS resolver.
type Resolver struct {
	cache  *lru.Cache
	client *dns.Client
}

// New initializes a Resolver with the specified cache size. Cache size defaults to 10,000 if size <= 0.
func New(size int) *Resolver {
	if size <= 0 {
		size = 10000
	}
	cache, _ := lru.New(size)
	r := &Resolver{
		client: &dns.Client{
			DialTimeout:  Timeout,
			ReadTimeout:  Timeout,
			WriteTimeout: Timeout,
		},
		cache: cache,
	}
	return r
}

// Resolve finds DNS records of type qtype for the domain qname. It returns a channel of *RR.
// The implementation guarantees that the output channel will close, so it is safe to range over.
// For nonexistent domains (where a DNS server will return NXDOMAIN), it will simply close the output channel.
// Specify an empty string in qtype to receive any DNS records found (currently A, AAAA, NS, CNAME, and TXT).
func (r *Resolver) Resolve(qname string, qtype string) <-chan *RR {
	return r.resolve(qname, qtype, 0)
}

func (r *Resolver) resolve(qname string, qtype string, depth int) <-chan *RR {
	c := make(chan *RR, 20)
	go func() {
		defer close(c)
		logResolveStart(qname, qtype, depth)
		defer logResolveEnd(qname, qtype, depth, time.Now())
		qname = toLowerFQDN(qname)
		if rrs := r.cacheGet(qname, qtype); rrs != nil {
			inject(c, rrs...)
			return
		}

		pname, ok := qname, true
		if qtype == "NS" {
			pname, ok = parent(qname)
			if !ok {
				return
			}
		}

		for ; ok; pname, ok = parent(pname) {
			for nrr := range r.resolve(pname, "NS", depth+1) {
				if qtype != "" {
					if rrs := r.cacheGet(qname, qtype); rrs != nil {
						inject(c, rrs...)
						return
					}
				}
				if nrr.Type != "NS" {
					continue
				}

				if r.exchange(nrr.Value, qname, qtype, depth) {
					r.resolveCNAMEs(c, qname, qtype, depth)
					return
				}
			}
		}
	}()
	return c
}

func (r *Resolver) exchange(host string, qname string, qtype string, depth int) bool {
	dtype := dns.StringToType[qtype]
	if dtype == 0 {
		dtype = dns.TypeA
	}
	qmsg := &dns.Msg{}
	qmsg.SetQuestion(qname, dtype)
	qmsg.MsgHdr.RecursionDesired = false

	// Query each DNS server
	c := make(chan bool, 10)
	timeout := time.After(Timeout)
	for rr := range r.resolve(host, "A", depth+1) {
		if rr.Type != "A" { // FIXME: support AAAA records?
			continue
		}
		go func(addr string) {
			start := time.Now()
			rmsg, _, err := r.client.Exchange(qmsg, addr)
			logExchange(addr, qmsg, depth, start, err)
			if err != nil {
				return
			}
			r.saveDNSRR(rmsg.Answer...)
			r.saveDNSRR(rmsg.Ns...)
			r.saveDNSRR(rmsg.Extra...)
			if rmsg.Rcode == dns.RcodeNameError {
				r.cacheAdd(qname, nil) // FIXME: cache NXDOMAIN responses responsibly
			}
			c <- true
		}(rr.Value + ":53")
	}

	// Wait for the first valid response
	for {
		select {
		case success, ok := <-c:
			if ok {
				return success
			}
		case <-timeout:
			return false
		}
	}

	return false
}

func (r *Resolver) resolveCNAMEs(c chan<- *RR, qname string, qtype string, depth int) {
	rrs := r.cacheGet(qname, "")
	if rrs == nil {
		return
	}
	if !inject(c, rrs...) {
		return
	}
	for _, crr := range rrs {
		if crr.Type != "CNAME" {
			continue
		}
		logCNAME(depth, crr.String())
		for rr := range r.resolve(crr.Value, qtype, depth+1) {
			r.cacheAdd(qname, rr)
			if !inject(c, rr) {
				return
			}
		}
	}
}

func inject(c chan<- *RR, rrs ...*RR) bool {
	for _, rr := range rrs {
		select {
		case c <- rr:
		default:
			// return false
		}
	}
	return true
}

func parent(name string) (string, bool) {
	labels := dns.SplitDomainName(name)
	if labels == nil {
		return "", false
	}
	return toLowerFQDN(strings.Join(labels[1:], ".")), true
}

func toLowerFQDN(name string) string {
	return dns.Fqdn(strings.ToLower(name))
}

func logResolveStart(qname string, qtype string, depth int) {
	if DebugLogger == nil {
		return
	}
	fmt.Fprintf(DebugLogger, "%s┌─── resolve(\"%s\", \"%s\", %d)\n",
		strings.Repeat("│   ", depth), qname, qtype, depth)
}

func logResolveEnd(qname string, qtype string, depth int, start time.Time) {
	if DebugLogger == nil {
		return
	}
	dur := time.Since(start)
	fmt.Fprintf(DebugLogger, "%s└─── %dms: resolve(\"%s\", \"%s\", %d)\n",
		strings.Repeat("│   ", depth), dur/time.Millisecond, qname, qtype, depth)
}

func logCNAME(depth int, cname string) {
	if DebugLogger == nil {
		return
	}
	fmt.Fprintf(DebugLogger, "%s│    CNAME: %s\n", strings.Repeat("│   ", depth), cname)
}

func logExchange(host string, qmsg *dns.Msg, depth int, start time.Time, err error) {
	if DebugLogger == nil {
		return
	}
	dur := time.Since(start)
	fmt.Fprintf(DebugLogger, "%s│    %dms: dig @%s %s %s\n",
		strings.Repeat("│   ", depth), dur/time.Millisecond, host, qmsg.Question[0].Name, dns.TypeToString[qmsg.Question[0].Qtype])
	if err != nil {
		fmt.Fprintf(DebugLogger, "%s│    %dms: ERROR: %s\n",
			strings.Repeat("│   ", depth), dur/time.Millisecond, err.Error())
	}
}

// RR represents a DNS resource record.
type RR struct {
	Name  string
	Type  string
	Value string
}

// String returns a string representation of an RR in zone-file format.
func (rr *RR) String() string {
	return rr.Name + "\t      3600\tIN\t" + rr.Type + "\t" + rr.Value
}

func convertRR(drr dns.RR) *RR {
	switch t := drr.(type) {
	case *dns.NS:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Ns}
	case *dns.CNAME:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Target}
	case *dns.A:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.A.String()}
	case *dns.AAAA:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.AAAA.String()}
	case *dns.TXT:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], strings.Join(t.Txt, "\t")}
	default:
		// fmt.Printf("%s\n", drr.String())
	}
	return nil
}

type key struct {
	Name string
	Type string
}

type entry struct {
	m   sync.RWMutex
	rrs map[RR]struct{}
}

// saveDNSRR saves 1 or more DNS records to the resolver cache.
func (r *Resolver) saveDNSRR(drrs ...dns.RR) {
	for _, drr := range drrs {
		if rr := convertRR(drr); rr != nil {
			r.cacheAdd(rr.Name, rr)
		}
	}
}

// cacheAdd adds 0 or more DNS records to the resolver cache for a specific
// domain name and record type. This ensures the cache entry exists, even
// if empty, for NXDOMAIN responses.
func (r *Resolver) cacheAdd(qname string, rr *RR) {
	qname = toLowerFQDN(qname)
	e := r.getEntry(qname)
	if e == nil {
		e = &entry{rrs: make(map[RR]struct{}, 0)}
		e.m.Lock()
		r.cache.Add(qname, e)
	} else {
		e.m.Lock()
	}
	defer e.m.Unlock()
	if rr != nil {
		e.rrs[*rr] = struct{}{}
	}
}

// cacheGet returns a randomly ordered slice of DNS records.
func (r *Resolver) cacheGet(qname string, qtype string) []*RR {
	e := r.getEntry(qname)
	if e == nil && r != Root {
		e = Root.getEntry(qname)
	}
	if e == nil {
		return nil
	}
	e.m.RLock()
	defer e.m.RUnlock()
	if len(e.rrs) == 0 {
		return []*RR{}
	}
	rrs := make([]*RR, 0, len(e.rrs))
	for rr, _ := range e.rrs {
		// fmt.Printf("%s\n", rr.String())
		if qtype == "" || rr.Type == qtype {
			rrs = append(rrs, &RR{rr.Name, rr.Type, rr.Value})
		}
	}
	if len(rrs) == 0 && (qtype != "" && qtype != "NS") {
		return nil
	}
	return rrs
}

// getEntry returns a single cache entry or nil if an entry does not exist in the cache.
func (r *Resolver) getEntry(qname string) *entry {
	c, ok := r.cache.Get(qname)
	if !ok {
		return nil
	}
	e, ok := c.(*entry)
	if !ok {
		return nil
	}
	return e
}
<commit_msg>refactored cacheGet + inject + test<commit_after>package dnsr

import (
	"fmt"
	"io"
	"strings"
	"sync"
	"time"

	lru "github.com/hashicorp/golang-lru"
	"github.com/miekg/dns"
)

//go:generate sh generate.sh

var (
	Root        *Resolver
	DebugLogger io.Writer
	Timeout     = 500 * time.Millisecond
)

func init() {
	Root = New(strings.Count(root, "\n"))
	for t := range dns.ParseZone(strings.NewReader(root), "", "") {
		if t.Error == nil {
			Root.saveDNSRR(t.RR)
		}
	}
}

// Resolver implements a primitive, non-recursive, caching DNS resolver.
type Resolver struct {
	cache  *lru.Cache
	client *dns.Client
}

// New initializes a Resolver with the specified cache size. Cache size defaults to 10,000 if size <= 0.
func New(size int) *Resolver {
	if size <= 0 {
		size = 10000
	}
	cache, _ := lru.New(size)
	r := &Resolver{
		client: &dns.Client{
			DialTimeout:  Timeout,
			ReadTimeout:  Timeout,
			WriteTimeout: Timeout,
		},
		cache: cache,
	}
	return r
}

// Resolve finds DNS records of type qtype for the domain qname. It returns a channel of *RR.
// The implementation guarantees that the output channel will close, so it is safe to range over.
// For nonexistent domains (where a DNS server will return NXDOMAIN), it will simply close the output channel.
// Specify an empty string in qtype to receive any DNS records found (currently A, AAAA, NS, CNAME, and TXT).
func (r *Resolver) Resolve(qname string, qtype string) <-chan *RR {
	return r.resolve(qname, qtype, 0)
}

func (r *Resolver) resolve(qname string, qtype string, depth int) <-chan *RR {
	c := make(chan *RR, 20)
	go func() {
		defer close(c)
		logResolveStart(qname, qtype, depth)
		defer logResolveEnd(qname, qtype, depth, time.Now())
		qname = toLowerFQDN(qname)
		if r.recall(c, qname, qtype) {
			return
		}

		pname, ok := qname, true
		if qtype == "NS" {
			if pname, ok = parent(qname); !ok {
				return
			}
		}

		for ; ok; pname, ok = parent(pname) {
			for nrr := range r.resolve(pname, "NS", depth+1) {
				if qtype != "" && r.recall(c, qname, qtype) {
					return
				}
				if nrr.Type != "NS" {
					continue
				}

				if r.exchange(nrr.Value, qname, qtype, depth) {
					r.resolveCNAMEs(c, qname, qtype, depth)
					return
				}
			}
		}
	}()
	return c
}

func (r *Resolver) recall(c chan<- *RR, qname string, qtype string) bool {
	if rrs := r.cacheGet(qname, qtype); rrs != nil {
		inject(c, rrs...)
		return true
	}
	return false
}

func (r *Resolver) exchange(host string, qname string, qtype string, depth int) bool {
	dtype := dns.StringToType[qtype]
	if dtype == 0 {
		dtype = dns.TypeA
	}
	qmsg := &dns.Msg{}
	qmsg.SetQuestion(qname, dtype)
	qmsg.MsgHdr.RecursionDesired = false

	// Query each DNS server
	c := make(chan bool, 10)
	timeout := time.After(Timeout)
	for rr := range r.resolve(host, "A", depth+1) {
		if rr.Type != "A" { // FIXME: support AAAA records?
			continue
		}
		go func(addr string) {
			start := time.Now()
			rmsg, _, err := r.client.Exchange(qmsg, addr)
			logExchange(addr, qmsg, depth, start, err)
			if err != nil {
				return
			}
			r.saveDNSRR(rmsg.Answer...)
			r.saveDNSRR(rmsg.Ns...)
			r.saveDNSRR(rmsg.Extra...)
			if rmsg.Rcode == dns.RcodeNameError {
				r.cacheAdd(qname, nil) // FIXME: cache NXDOMAIN responses responsibly
			}
			c <- true
		}(rr.Value + ":53")
	}

	// Wait for the first valid response
	for {
		select {
		case success, ok := <-c:
			if ok {
				return success
			}
		case <-timeout:
			return false
		}
	}

	return false
}

func (r *Resolver) resolveCNAMEs(c chan<- *RR, qname string, qtype string, depth int) {
	rrs := r.cacheGet(qname, "")
	if rrs == nil {
		return
	}
	if !inject(c, rrs...) {
		return
	}
	for _, crr := range rrs {
		if crr.Type != "CNAME" {
			continue
		}
		logCNAME(depth, crr.String())
		for rr := range r.resolve(crr.Value, qtype, depth+1) {
			r.cacheAdd(qname, rr)
			if !inject(c, rr) {
				return
			}
		}
	}
}

func inject(c chan<- *RR, rrs ...*RR) bool {
	for _, rr := range rrs {
		select {
		case c <- rr:
		default:
			// return false
		}
	}
	return true
}

func parent(name string) (string, bool) {
	labels := dns.SplitDomainName(name)
	if labels == nil {
		return "", false
	}
	return toLowerFQDN(strings.Join(labels[1:], ".")), true
}

func toLowerFQDN(name string) string {
	return dns.Fqdn(strings.ToLower(name))
}

func logResolveStart(qname string, qtype string, depth int) {
	if DebugLogger == nil {
		return
	}
	fmt.Fprintf(DebugLogger, "%s┌─── resolve(\"%s\", \"%s\", %d)\n",
		strings.Repeat("│   ", depth), qname, qtype, depth)
}

func logResolveEnd(qname string, qtype string, depth int, start time.Time) {
	if DebugLogger == nil {
		return
	}
	dur := time.Since(start)
	fmt.Fprintf(DebugLogger, "%s└─── %dms: resolve(\"%s\", \"%s\", %d)\n",
		strings.Repeat("│   ", depth), dur/time.Millisecond, qname, qtype, depth)
}

func logCNAME(depth int, cname string) {
	if DebugLogger == nil {
		return
	}
	fmt.Fprintf(DebugLogger, "%s│    CNAME: %s\n", strings.Repeat("│   ", depth), cname)
}

func logExchange(host string, qmsg *dns.Msg, depth int, start time.Time, err error) {
	if DebugLogger == nil {
		return
	}
	dur := time.Since(start)
	fmt.Fprintf(DebugLogger, "%s│    %dms: dig @%s %s %s\n",
		strings.Repeat("│   ", depth), dur/time.Millisecond, host, qmsg.Question[0].Name, dns.TypeToString[qmsg.Question[0].Qtype])
	if err != nil {
		fmt.Fprintf(DebugLogger, "%s│    %dms: ERROR: %s\n",
			strings.Repeat("│   ", depth), dur/time.Millisecond, err.Error())
	}
}

// RR represents a DNS resource record.
type RR struct {
	Name  string
	Type  string
	Value string
}

// String returns a string representation of an RR in zone-file format.
func (rr *RR) String() string {
	return rr.Name + "\t      3600\tIN\t" + rr.Type + "\t" + rr.Value
}

func convertRR(drr dns.RR) *RR {
	switch t := drr.(type) {
	case *dns.NS:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Ns}
	case *dns.CNAME:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.Target}
	case *dns.A:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.A.String()}
	case *dns.AAAA:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], t.AAAA.String()}
	case *dns.TXT:
		return &RR{t.Hdr.Name, dns.TypeToString[t.Hdr.Rrtype], strings.Join(t.Txt, "\t")}
	default:
		// fmt.Printf("%s\n", drr.String())
	}
	return nil
}

type key struct {
	Name string
	Type string
}

type entry struct {
	m   sync.RWMutex
	rrs map[RR]struct{}
}

// saveDNSRR saves 1 or more DNS records to the resolver cache.
func (r *Resolver) saveDNSRR(drrs ...dns.RR) {
	for _, drr := range drrs {
		if rr := convertRR(drr); rr != nil {
			r.cacheAdd(rr.Name, rr)
		}
	}
}

// cacheAdd adds 0 or more DNS records to the resolver cache for a specific
// domain name and record type. This ensures the cache entry exists, even
// if empty, for NXDOMAIN responses.
func (r *Resolver) cacheAdd(qname string, rr *RR) {
	qname = toLowerFQDN(qname)
	e := r.getEntry(qname)
	if e == nil {
		e = &entry{rrs: make(map[RR]struct{}, 0)}
		e.m.Lock()
		r.cache.Add(qname, e)
	} else {
		e.m.Lock()
	}
	defer e.m.Unlock()
	if rr != nil {
		e.rrs[*rr] = struct{}{}
	}
}

// cacheGet returns a randomly ordered slice of DNS records.
func (r *Resolver) cacheGet(qname string, qtype string) []*RR {
	e := r.getEntry(qname)
	if e == nil && r != Root {
		e = Root.getEntry(qname)
	}
	if e == nil {
		return nil
	}
	e.m.RLock()
	defer e.m.RUnlock()
	if len(e.rrs) == 0 {
		return []*RR{}
	}
	rrs := make([]*RR, 0, len(e.rrs))
	for rr, _ := range e.rrs {
		// fmt.Printf("%s\n", rr.String())
		if qtype == "" || rr.Type == qtype {
			rrs = append(rrs, &RR{rr.Name, rr.Type, rr.Value})
		}
	}
	if len(rrs) == 0 && (qtype != "" && qtype != "NS") {
		return nil
	}
	return rrs
}

// getEntry returns a single cache entry or nil if an entry does not exist in the cache.
func (r *Resolver) getEntry(qname string) *entry {
	c, ok := r.cache.Get(qname)
	if !ok {
		return nil
	}
	e, ok := c.(*entry)
	if !ok {
		return nil
	}
	return e
}
<|endoftext|>


<commit_before>//
// Written by Michael Mattioli
//

package main

import (
    "fmt"
)

// It takes one (1) minute to travel from one stop to another, there are eight (8) hours in a work
// day and sixty (60) minutes in an hour which totals four-hundred and eighty (480) minutes and,
// therefore, four-hundred and eighty (480) trips made in a work day.
const MaxTrips int = 480

// A BusDriver has a daily route composed of MaxTrips stops and a collection of gossip which can be
// shared with other BusDrivers.
type BusDriver struct {
    DailyRoute []int
    Gossips []*BusDriver
}

// NewBusDriver creates and initializes a new BusDriver. The BusDriver's route for the entire day is
// filled out and starts with one (1) gossip to share.
func NewBusDriver(r ...int) *BusDriver {

    var bd BusDriver

    // Initial gossip.
    bd.Gossips = append(bd.Gossips, &bd)

    // Determine daily route.
    var t int
    for {
        for s := range r {
            if t == MaxTrips {
                return &bd
            }
            bd.DailyRoute = append(bd.DailyRoute, r[s])
            t++
        }
    }

}

// ExchangeGossip shares unknown gossip between two BusDrivers.
func ExchangeGossip(bd1, bd2 *BusDriver) {
    giveGossip := func(src, dst *BusDriver) {
        for sg := range src.Gossips {
            var known bool
            for dg := range dst.Gossips {
                if src.Gossips[sg] == dst.Gossips[dg] {
                    known = true
                }
            }
            if !known {
                dst.Gossips = append(dst.Gossips, src.Gossips[sg])
            }
        }
    }
    giveGossip(bd1, bd2)
    giveGossip(bd2, bd1)
}

// AllGossipExchanged determines if all of the BusDrivers have shared their gossip with each other.
func AllGossipExchanged(bds ...*BusDriver) bool {
    for i := 0; i < len(bds) - 1; i++ {
        switch {
        // If a BusDriver only has 1 gossip then hasn't receeived anything.
        case len(bds[i].Gossips) == 1:
            return false
        // If any two BusDriver's gossips are not of equal length then everyone has not shared their
        // gossip with everyone.
        case len(bds[i].Gossips) != len(bds[i + 1].Gossips):
            return false
        }
    }
    return true
}

// BusDriverGossipExchange calculates the number of stops each BusDriver must make before they all
// have shared each other's gossip. Returns -1 if all of the BusDrivers have not shared and heard
// all of the gossip there is to share and hear by the end of their routes.
func BusDriverGossipExchange(r ...[]int) int {

    var drvs []*BusDriver

    for br := range r {
        drvs = append(drvs, NewBusDriver(r[br]...))
    }

    for t := 0; t < MaxTrips; t++ {
        for src := range drvs {
            for dst := range drvs {
                switch {
                // Dont't exchange gossip with the same BusDriver.
                case drvs[src] == drvs[dst]:
                    continue
                // Two different BusDrivers at the same bus stop.
                case drvs[src].DailyRoute[t] == drvs[dst].DailyRoute[t]:
                    ExchangeGossip(drvs[src], drvs[dst])
                }
            }
        }
        if AllGossipExchanged(drvs...) {
            return t
        }
    }

    return -1

}

func main() {

    tests := [][][]int{
        [][]int {
            []int{3, 1, 2, 3},
            []int{3, 2, 3, 1},
            []int{4, 2, 3, 4, 5},
        },
        [][]int{
            []int{2, 1, 2},
            []int{5, 2, 8},
        },
        [][]int{
            []int{7, 11, 2, 2, 4, 8, 2, 2},
            []int{3, 0, 11, 8},
            []int{5, 11, 8, 10, 3, 11},
            []int{5, 9, 2, 5, 0, 3},
            []int{7, 4, 8, 2, 8, 1, 0, 5},
            []int{3, 6, 8, 9},
            []int{4, 2, 11, 3, 3},
        },
        [][]int {
            []int{12, 23, 15, 2, 8, 20, 21, 3, 23, 3, 27, 20, 0},
            []int{21, 14, 8, 20, 10, 0, 23, 3, 24, 23, 0, 19, 14, 12, 10, 9, 12, 12, 11, 6, 27, 5},
            []int{8, 18, 27, 10, 11, 22, 29, 23, 14},
            []int{13, 7, 14, 1, 9, 14, 16, 12, 0, 10, 13, 19, 16, 17},
            []int{24, 25, 21, 4, 6, 19, 1, 3, 26, 11, 22, 28, 14, 14, 27, 7, 20, 8, 7, 4, 1, 8, 10, 18, 21},
            []int{13, 20, 26, 22, 6, 5, 6, 23, 26, 2, 21, 16, 26, 24},
            []int{6, 7, 17, 2, 22, 23, 21},
            []int{23, 14, 22, 28, 10, 23, 7, 21, 3, 20, 24, 23, 8, 8, 21, 13, 15, 6, 9, 17, 27, 17, 13, 14},
            []int{23, 13, 1, 15, 5, 16, 7, 26, 22, 29, 17, 3, 14, 16, 16, 18, 6, 10, 3, 14, 10, 17, 27, 25},
            []int{25, 28, 5, 21, 8, 10, 27, 21, 23, 28, 7, 20, 6, 6, 9, 29, 27, 26, 24, 3, 12, 10, 21, 10, 12, 17},
            []int{26, 22, 26, 13, 10, 19, 3, 15, 2, 3, 25, 29, 25, 19, 19, 24, 1, 26, 22, 10, 17, 19, 28, 11, 22, 2, 13},
            []int{8, 4, 25, 15, 20, 9, 11, 3, 19},
            []int{24, 29, 4, 17, 2, 0, 8, 19, 11, 28, 13, 4, 16, 5, 15, 25, 16, 5, 6, 1, 0, 19, 7, 4, 6},
            []int{16, 25, 15, 17, 20, 27, 1, 11, 1, 18, 14, 23, 27, 25, 26, 17, 1},
        },
    }

    for t := range tests {
        fmt.Println(BusDriverGossipExchange(tests[t]...))
    }

}
<commit_msg>Fix counting bug in challenge 264<commit_after>//
// Written by Michael Mattioli
//

package main

import (
    "fmt"
)

// It takes one (1) minute to travel from one stop to another, there are eight (8) hours in a work
// day and sixty (60) minutes in an hour which totals four-hundred and eighty (480) minutes and,
// therefore, four-hundred and eighty (480) trips made in a work day.
const MaxTrips int = 480

// A BusDriver has a daily route composed of MaxTrips stops and a collection of gossip which can be
// shared with other BusDrivers.
type BusDriver struct {
    DailyRoute []int
    Gossips []*BusDriver
}

// NewBusDriver creates and initializes a new BusDriver. The BusDriver's route for the entire day is
// filled out and starts with one (1) gossip to share.
func NewBusDriver(r ...int) *BusDriver {

    var bd BusDriver

    // Initial gossip.
    bd.Gossips = append(bd.Gossips, &bd)

    // Determine daily route.
    var t int
    for {
        for s := range r {
            if t == MaxTrips {
                return &bd
            }
            bd.DailyRoute = append(bd.DailyRoute, r[s])
            t++
        }
    }

}

// ExchangeGossip shares unknown gossip between two BusDrivers.
func ExchangeGossip(bd1, bd2 *BusDriver) {
    giveGossip := func(src, dst *BusDriver) {
        for sg := range src.Gossips {
            var known bool
            for dg := range dst.Gossips {
                if src.Gossips[sg] == dst.Gossips[dg] {
                    known = true
                }
            }
            if !known {
                dst.Gossips = append(dst.Gossips, src.Gossips[sg])
            }
        }
    }
    giveGossip(bd1, bd2)
    giveGossip(bd2, bd1)
}

// AllGossipExchanged determines if all of the BusDrivers have shared their gossip with each other.
func AllGossipExchanged(bds ...*BusDriver) bool {
    for i := 0; i < len(bds) - 1; i++ {
        switch {
        // If a BusDriver only has 1 gossip then hasn't received anything.
        case len(bds[i].Gossips) == 1:
            return false
        // If any two BusDriver's gossips are not of equal length then everyone has not shared their
        // gossip with everyone.
        case len(bds[i].Gossips) != len(bds[i + 1].Gossips):
            return false
        }
    }
    return true
}

// BusDriverGossipExchange calculates the number of stops each BusDriver must make before they all
// have shared each other's gossip. Returns -1 if all of the BusDrivers have not shared and heard
// all of the gossip there is to share and hear by the end of their routes.
func BusDriverGossipExchange(r ...[]int) int {

    var drvs []*BusDriver

    for br := range r {
        drvs = append(drvs, NewBusDriver(r[br]...))
    }

    for t := 0; t < MaxTrips; t++ {
        for src := range drvs {
            for dst := range drvs {
                switch {
                // Dont't exchange gossip with the same BusDriver.
                case drvs[src] == drvs[dst]:
                    continue
                // Two different BusDrivers at the same bus stop.
                case drvs[src].DailyRoute[t] == drvs[dst].DailyRoute[t]:
                    ExchangeGossip(drvs[src], drvs[dst])
                }
            }
        }
        if AllGossipExchanged(drvs...) {
            return t + 1
        }
    }

    return -1

}

func main() {

    tests := [][][]int{
        [][]int {
            []int{3, 1, 2, 3},
            []int{3, 2, 3, 1},
            []int{4, 2, 3, 4, 5},
        },
        [][]int{
            []int{2, 1, 2},
            []int{5, 2, 8},
        },
        [][]int{
            []int{7, 11, 2, 2, 4, 8, 2, 2},
            []int{3, 0, 11, 8},
            []int{5, 11, 8, 10, 3, 11},
            []int{5, 9, 2, 5, 0, 3},
            []int{7, 4, 8, 2, 8, 1, 0, 5},
            []int{3, 6, 8, 9},
            []int{4, 2, 11, 3, 3},
        },
        [][]int {
            []int{12, 23, 15, 2, 8, 20, 21, 3, 23, 3, 27, 20, 0},
            []int{21, 14, 8, 20, 10, 0, 23, 3, 24, 23, 0, 19, 14, 12, 10, 9, 12, 12, 11, 6, 27, 5},
            []int{8, 18, 27, 10, 11, 22, 29, 23, 14},
            []int{13, 7, 14, 1, 9, 14, 16, 12, 0, 10, 13, 19, 16, 17},
            []int{24, 25, 21, 4, 6, 19, 1, 3, 26, 11, 22, 28, 14, 14, 27, 7, 20, 8, 7, 4, 1, 8, 10, 18, 21},
            []int{13, 20, 26, 22, 6, 5, 6, 23, 26, 2, 21, 16, 26, 24},
            []int{6, 7, 17, 2, 22, 23, 21},
            []int{23, 14, 22, 28, 10, 23, 7, 21, 3, 20, 24, 23, 8, 8, 21, 13, 15, 6, 9, 17, 27, 17, 13, 14},
            []int{23, 13, 1, 15, 5, 16, 7, 26, 22, 29, 17, 3, 14, 16, 16, 18, 6, 10, 3, 14, 10, 17, 27, 25},
            []int{25, 28, 5, 21, 8, 10, 27, 21, 23, 28, 7, 20, 6, 6, 9, 29, 27, 26, 24, 3, 12, 10, 21, 10, 12, 17},
            []int{26, 22, 26, 13, 10, 19, 3, 15, 2, 3, 25, 29, 25, 19, 19, 24, 1, 26, 22, 10, 17, 19, 28, 11, 22, 2, 13},
            []int{8, 4, 25, 15, 20, 9, 11, 3, 19},
            []int{24, 29, 4, 17, 2, 0, 8, 19, 11, 28, 13, 4, 16, 5, 15, 25, 16, 5, 6, 1, 0, 19, 7, 4, 6},
            []int{16, 25, 15, 17, 20, 27, 1, 11, 1, 18, 14, 23, 27, 25, 26, 17, 1},
        },
    }

    for t := range tests {
        fmt.Println(BusDriverGossipExchange(tests[t]...))
    }

}
<|endoftext|>


<commit_before>// Copyright 2014 Hajime Hoshi
//
// 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.

// +build darwin freebsd linux windows
// +build !android
// +build !ios

package ebitenutil

import (
	"image"

	"github.com/hajimehoshi/ebiten"
)

// NewImageFromFile loads the file with path and returns ebiten.Image and image.Image.
//
// Image decoders must be imported when using NewImageFromFile. For example,
// if you want to load a PNG image, you'd need to add `_ "image/png"` to the import section.
//
// How to solve path depends on your environment. This varies on your desktop or web browser.
// Note that this doesn't work on mobiles.
//
// For productions, instead of using NewImageFromFile, it is safer to embed your resources, e.g., with github.com/jteeuwen/go-bindata .
func NewImageFromFile(path string, filter ebiten.Filter) (*ebiten.Image, image.Image, error) {
	file, err := OpenFile(path)
	if err != nil {
		return nil, nil, err
	}
	defer func() {
		_ = file.Close()
	}()
	img, _, err := image.Decode(file)
	if err != nil {
		return nil, nil, err
	}
	img2, err := ebiten.NewImageFromImage(img, filter)
	if err != nil {
		return nil, nil, err
	}
	return img2, img, err
}
<commit_msg>docs: Change docs about go-bindata into rakyll/statik (#840)<commit_after>// Copyright 2014 Hajime Hoshi
//
// 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.

// +build darwin freebsd linux windows
// +build !android
// +build !ios

package ebitenutil

import (
	"image"

	"github.com/hajimehoshi/ebiten"
)

// NewImageFromFile loads the file with path and returns ebiten.Image and image.Image.
//
// Image decoders must be imported when using NewImageFromFile. For example,
// if you want to load a PNG image, you'd need to add `_ "image/png"` to the import section.
//
// How to solve path depends on your environment. This varies on your desktop or web browser.
// Note that this doesn't work on mobiles.
//
// For productions, instead of using NewImageFromFile, it is safer to embed your resources, e.g., with github.com/rakyll/statik .
func NewImageFromFile(path string, filter ebiten.Filter) (*ebiten.Image, image.Image, error) {
	file, err := OpenFile(path)
	if err != nil {
		return nil, nil, err
	}
	defer func() {
		_ = file.Close()
	}()
	img, _, err := image.Decode(file)
	if err != nil {
		return nil, nil, err
	}
	img2, err := ebiten.NewImageFromImage(img, filter)
	if err != nil {
		return nil, nil, err
	}
	return img2, img, err
}
<|endoftext|>


<commit_before>package erro

import (
	"fmt"
	"runtime"
)

// デバッグ用エラー。
type Tracer struct {
	cause error
	trace string
}

func (err *Tracer) Error() string {
	return err.cause.Error() + "\n" +
		"--------------------------------------------------\n" +
		err.trace
}

func (err *Tracer) Cause() error {
	return err.cause
}

const traceLen = 8192

// スタックトレースを付加する。
// nil をそのまま返すので、 return Wrap(func() error) みたいな使い方ができる。
// 囲うのは最初だけでも良いし、既に Wrap されている場合はそのまま返すので、
// 最初かどうか分からなければ、毎回囲っても良い。
// スタックトレースの先頭はこの関数になるが、気にしない。
func Wrap(err error) error {
	if err == nil {
		return nil
	} else if _, ok := err.(*Tracer); ok {
		return err
	}

	buff := make([]byte, traceLen)
	len := runtime.Stack(buff, false)

	return &Tracer{err, string(buff[:len])}
}

type Error string

func (err Error) Error() string {
	return string(err)
}

// 引数から 1 つの文字列をつくって最初のエラーにして、それを Wrap して返す。
// 文字列の成形は fmt.Sprint() 形式。
func New(a ...interface{}) error {
	return Wrap(Error(fmt.Sprint(a...)))
}
<commit_msg>余分な改行を削除<commit_after>package erro

import (
	"fmt"
	"runtime"
)

// デバッグ用エラー。
type Tracer struct {
	cause error
	trace string
}

func (err *Tracer) Error() string {
	return err.cause.Error() + "\n" +
		"--------------------------------------------------\n" +
		err.trace
}

func (err *Tracer) Cause() error {
	return err.cause
}

const traceLen = 8192

// スタックトレースを付加する。
// nil をそのまま返すので、 return Wrap(func() error) みたいな使い方ができる。
// 囲うのは最初だけでも良いし、既に Wrap されている場合はそのまま返すので、
// 最初かどうか分からなければ、毎回囲っても良い。
// スタックトレースの先頭はこの関数になるが、気にしない。
func Wrap(err error) error {
	if err == nil {
		return nil
	} else if _, ok := err.(*Tracer); ok {
		return err
	}

	buff := make([]byte, traceLen)
	len := runtime.Stack(buff, false)

	for len > 0 && buff[len-1] == '\n' {
		len--
	}

	return &Tracer{err, string(buff[:len])}
}

type Error string

func (err Error) Error() string {
	return string(err)
}

// 引数から 1 つの文字列をつくって最初のエラーにして、それを Wrap して返す。
// 文字列の成形は fmt.Print() 形式。
func New(a ...interface{}) error {
	return Wrap(Error(fmt.Sprint(a...)))
}
<|endoftext|>


<commit_before>package es

import (
	"time"
)

var timer = time.NewTimer(1 * time.Second)

type IndexerStats struct {
	Runs        int64
	IndexedDocs int64
	TotalTime   time.Duration
}

func (stats *IndexerStats) Add(count int, dur time.Duration) {
	stats.Runs++
	stats.IndexedDocs += int64(count)
	stats.TotalTime += dur
}

type Indexer struct {
	Index *Index

	IndexEvery time.Duration // triggers a new index run after that duration, will be reset when BatchSize reached
	BatchSize  int           // triggers a new index run when the batch reaches that size

	docsBatch   []*Doc
	docsChannel chan *Doc
	timer       *time.Timer
	Stats       IndexerStats
}

func (indexer *Indexer) Finish() error {
	return indexer.indexBatch()
}

func (indexer *Indexer) resetBatch() {
	indexer.docsBatch = make([]*Doc, 0, indexer.BatchSize)
}

func (indexer *Indexer) resetTimer() bool {
	return indexer.timer.Reset(indexer.IndexEvery)
}

func (indexer *Indexer) Start() chan *Doc {
	if indexer.BatchSize == 0 {
		indexer.BatchSize = 100
	}
	if indexer.IndexEvery == 0 {
		indexer.IndexEvery = 1 * time.Hour
	}
	indexer.timer = time.NewTimer(indexer.IndexEvery)
	indexer.docsChannel = make(chan *Doc, indexer.BatchSize)
	indexer.resetBatch()
	go func(indexer *Indexer) {
		for {
			select {
			case <-indexer.timer.C:
				indexer.indexBatch()
				indexer.resetTimer()
			case doc, ok := <-indexer.docsChannel:
				if !ok {
					indexer.timer.Stop()
					indexer.indexBatch()
					return
				}
				if doc.Index == "" {
					doc.Index = indexer.Index.Index
				}
				if doc.Type == "" {
					doc.Type = indexer.Index.Type
				}
				indexer.docsBatch = append(indexer.docsBatch, doc)
				if len(indexer.docsBatch) >= indexer.BatchSize {
					indexer.timer.Stop()
					indexer.indexBatch()
					indexer.resetTimer()
				}
			}
		}
	}(indexer)
	return indexer.docsChannel
}

func (indexer *Indexer) indexBatch() error {
	if len(indexer.docsBatch) < 1 {
		return nil
	}
	started := time.Now()
	e := indexer.Index.IndexDocs(indexer.docsBatch)
	if e != nil {
		return e
	}
	indexer.Stats.Add(len(indexer.docsBatch), time.Now().Sub(started))
	indexer.resetBatch()
	return nil
}
<commit_msg>log indexing errors<commit_after>package es

import (
	"log"
	"os"
	"time"
)

var timer = time.NewTimer(1 * time.Second)

type IndexerStats struct {
	Runs        int64
	IndexedDocs int64
	TotalTime   time.Duration
}

func (stats *IndexerStats) Add(count int, dur time.Duration) {
	stats.Runs++
	stats.IndexedDocs += int64(count)
	stats.TotalTime += dur
}

type Indexer struct {
	Index *Index

	IndexEvery time.Duration // triggers a new index run after that duration, will be reset when BatchSize reached
	BatchSize  int           // triggers a new index run when the batch reaches that size

	docsBatch   []*Doc
	docsChannel chan *Doc
	timer       *time.Timer
	Stats       IndexerStats
}

func (indexer *Indexer) Finish() error {
	return indexer.indexBatch()
}

func (indexer *Indexer) resetBatch() {
	indexer.docsBatch = make([]*Doc, 0, indexer.BatchSize)
}

func (indexer *Indexer) resetTimer() bool {
	return indexer.timer.Reset(indexer.IndexEvery)
}

var logger = log.New(os.Stderr, "", 0)

func (indexer *Indexer) Start() chan *Doc {
	if indexer.BatchSize == 0 {
		indexer.BatchSize = 100
	}
	if indexer.IndexEvery == 0 {
		indexer.IndexEvery = 1 * time.Hour
	}
	indexer.timer = time.NewTimer(indexer.IndexEvery)
	indexer.docsChannel = make(chan *Doc, indexer.BatchSize)
	indexer.resetBatch()
	go func(indexer *Indexer) {
		for {
			select {
			case <-indexer.timer.C:
				e := indexer.indexBatch()
				if e != nil {
					logger.Printf("ERROR=%q", e)
				}
				indexer.resetTimer()
			case doc, ok := <-indexer.docsChannel:
				if !ok {
					indexer.timer.Stop()
					e := indexer.indexBatch()
					if e != nil {
						logger.Printf("ERROR=%q", e)
					}
					return
				}
				if doc.Index == "" {
					doc.Index = indexer.Index.Index
				}
				if doc.Type == "" {
					doc.Type = indexer.Index.Type
				}
				indexer.docsBatch = append(indexer.docsBatch, doc)
				if len(indexer.docsBatch) >= indexer.BatchSize {
					indexer.timer.Stop()
					indexer.indexBatch()
					indexer.resetTimer()
				}
			}
		}
	}(indexer)
	return indexer.docsChannel
}

func (indexer *Indexer) indexBatch() error {
	if len(indexer.docsBatch) < 1 {
		return nil
	}
	started := time.Now()
	e := indexer.Index.IndexDocs(indexer.docsBatch)
	if e != nil {
		return e
	}
	indexer.Stats.Add(len(indexer.docsBatch), time.Now().Sub(started))
	indexer.resetBatch()
	return nil
}
<|endoftext|>


<commit_before>package workloads

import (
	"crypto/md5"
	"encoding/hex"
	"sync"

	"databases"
)

// General workload configuration
type Config struct {
	Type string
	CreatePercentage int  // shorthand "c"
	ReadPercentage int    // shorthand "r"
	UpdatePercentage int  // shorthand "u"
	DeletePercentage int  // shorthand "d"
	QueryPercentage int   // shorthand "q"
	Records int64
	Operations int64
	ValueSize int
	IndexableFields int
	Workers int
	TargetThroughput int
}

type Workload interface {
	Init(config Config)

	RunWorkload(database databases.Database, state *State, wg *sync.WaitGroup)
}

func Hash(inString string) string {
	h := md5.New()
	h.Write([]byte(inString))
	return hex.EncodeToString(h.Sum(nil))
}
<commit_msg>extend Workload interface<commit_after>package workloads

import (
	"crypto/md5"
	"encoding/hex"
	"sync"

	"databases"
)

// General workload configuration
type Config struct {
	Type string
	CreatePercentage int  // shorthand "c"
	ReadPercentage int    // shorthand "r"
	UpdatePercentage int  // shorthand "u"
	DeletePercentage int  // shorthand "d"
	QueryPercentage int   // shorthand "q"
	Records int64
	Operations int64
	ValueSize int
	IndexableFields int
	Workers int
	TargetThroughput int
}

type Workload interface {
	Init(config Config)

	GenerateNewKey(currentRecords int64) string

	GenerateExistingKey(currentRecords int64) string

	GenerateKeyForRemoval() string

	GenerateValue(key string, indexableFields, size int) map[string]interface{}

	GenerateQuery(indexableFields int, currentRecords int64) (fieldName, fieldValue string, limit int)

	RunWorkload(database databases.Database, state *State, wg *sync.WaitGroup)
}

func Hash(inString string) string {
	h := md5.New()
	h.Write([]byte(inString))
	return hex.EncodeToString(h.Sum(nil))
}
<|endoftext|>


<commit_before>package main

import (
	"archive/zip"
	"bytes"
	"errors"
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/moovweb/gokogiri"
	"github.com/moovweb/gokogiri/css"
	"github.com/moovweb/gokogiri/xml"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"runtime"
	"syscall"
	"time"
)

type Item struct {
	Id   string
	Href string
	Type string
}

type Epub struct {
	Zip *zip.Writer
	// TODO identifier / rights
	Title        string
	Subject      string
	Date         string
	Creator      string
	Contributors []string
	Items        []Item
}

const (
	Host           = "linuxfr.org"
	ContentType    = "application/epub+zip"
	XmlDeclaration = `<?xml version="1.0" encoding="utf-8"?>`

	Container = XmlDeclaration + `
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
  <rootfiles>
    <rootfile full-path="EPUB/package.opf" media-type="application/oebps-package+xml"/>
  </rootfiles>
</container>`

	Nav = XmlDeclaration + `

    <title>LinuxFr.org</title>
    <meta charset="utf-8" />

Sommaire

Aller au contenu

`

	HeaderHtml = XmlDeclaration + `

    <title>LinuxFr.org</title>
    <meta charset="utf-8" />

  `

	FooterHtml = ""
)

// TODO cover
var PackageTemplate = template.Must(template.New("package").Parse(`
<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="pub-identifier" xml:lang="fr" version="3.0">
	<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
		<dc:language id="pub-language">fr</dc:language>
		<dc:identifier id="pub-identifier">xxx</dc:identifier>
		<dc:date>{{.Date}}</dc:date>
		<meta property="dcterms:modified">{{.Date}}</meta>
		{{if .Title}}<dc:title id="pub-title">{{.Title}}</dc:title>{{end}}
		{{if .Creator}}<dc:creator id="pub-creator">{{.Creator}}</dc:creator>{{end}}
		{{range .Contributors}}<dc:contributor>{{.}}</dc:contributor>
		{{end}}
		<dc:rights>xxx</dc:rights>
	</metadata>
	<manifest>
		<item id="nav" href="nav.html" media-type="application/xhtml+xml" properties="nav"/>
		{{range .Items}}<item id="{{.Id}}" href="{{.Href}}" media-type="{{.Type}}"/>
		{{end}}
	</manifest>
	<spine>
		{{range .Items}}<itemref idref="{{.Id}}"/>
		{{end}}
	</spine>
</package>`))

func toHtml(node xml.Node) string {
	// TODO fix links to LinuxFr.org
	// TODO embed CSS & images
	return HeaderHtml + node.InnerHtml() + FooterHtml
}

func NewEpub(w io.Writer) (epub *Epub) {
	z := zip.NewWriter(w)
	epub = &Epub{Zip: z, Items: []Item{}}
	epub.AddMimetype()
	epub.AddFile("META-INF/container.xml", Container)
	epub.AddFile("EPUB/nav.html", Nav)
	return
}

func (epub *Epub) AddContent(article xml.Node) {
	xpath := css.Convert(".content", css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil || len(nodes) == 0 {
		return
	}
	html := toHtml(nodes[0])
	filename := "content.html"
	epub.Items = append(epub.Items, Item{"item-content", filename, "application/xhtml+xml"})
	epub.AddFile("EPUB/"+filename, html)
}

func (epub *Epub) AddComments(article xml.Node) {
	list := article.NextSibling()
	xpath := css.Convert(".threads>li", css.LOCAL)
	threads, err := list.Search(xpath)
	if err != nil {
		return
	}
	for _, thread := range threads {
		html := toHtml(thread)
		id := thread.Attr("id")
		filename := id + ".html"
		epub.Items = append(epub.Items, Item{id, filename, "application/xhtml+xml"})
		epub.AddFile("EPUB/"+filename, html)
	}
}

func (epub *Epub) FindMeta(article xml.Node, selector string) string {
	xpath := css.Convert(selector, css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil || len(nodes) == 0 {
		return ""
	}
	return nodes[0].Content()
}

func (epub *Epub) FindMetas(article xml.Node, selector string) []string {
	xpath := css.Convert(selector, css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil {
		return nil
	}
	metas := make([]string, len(nodes))
	for i, node := range nodes {
		metas[i] = node.Content()
	}
	return metas
}

func (epub *Epub) FillMeta(article xml.Node) {
	epub.Title = epub.FindMeta(article, "header h1 a:last-child")
	epub.Subject = epub.FindMeta(article, "header h1 a.topic")
	meta := epub.FindMeta(article, "header time.updated")
	// FIXME ParseInLocation
	date, err := time.Parse("le 02/01/06 à 15:04", meta)
	if err != nil {
		date = time.Now()
	}
	epub.Date = date.Format(time.RFC3339)
	epub.Creator = epub.FindMeta(article, "header .meta a[rel=\"author\"]")
	epub.Contributors = epub.FindMetas(article, "header .meta .edited_by a")
}

func (epub *Epub) AddMimetype() (err error) {
	header := &zip.FileHeader{Name: "mimetype", Method: zip.Store}
	f, err := epub.Zip.CreateHeader(header)
	if err != nil {
		log.Println(err)
		return
	}

	_, err = f.Write([]byte(ContentType))
	if err != nil {
		log.Println(err)
		return
	}

	return
}

func (epub *Epub) AddFile(filename, content string) (err error) {
	f, err := epub.Zip.Create(filename)
	if err != nil {
		log.Println(err)
		return
	}

	_, err = f.Write([]byte(content))
	if err != nil {
		log.Println(err)
		return
	}

	return
}

func (epub *Epub) Close() {
	var opf bytes.Buffer
	err := PackageTemplate.Execute(&opf, epub)
	if err != nil {
		log.Println(err)
		return
	}

	epub.AddFile("EPUB/package.opf", XmlDeclaration+opf.String())
	err = epub.Zip.Close()
	if err != nil {
		log.Println(err)
	}
}

func fetchArticle(uri string) (article xml.Node, err error) {
	resp, err := http.Get(uri)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Printf("Error on ioutil.ReadAll for %s: %s\n", uri, err)
		return
	}

	doc, err := gokogiri.ParseHtml(body)
	if err != nil {
		log.Printf("Gokogiri error: %s\n", err)
		return
	}

	xpath := css.Convert("#contents article", css.LOCAL)
	articles, err := doc.Root().Search(xpath)
	if err != nil {
		log.Printf("Gokogiri error: %s\n", err)
		return
	}

	if len(articles) == 0 {
		err = errors.New("No article found in the page")
		return
	}

	article = articles[0]
	return
}

// Create an epub for a news
func News(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Content-Type", ContentType)

	slug := r.URL.Query().Get(":slug")
	uri := fmt.Sprintf("http://%s/news/%s", Host, slug)
	article, err := fetchArticle(uri)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	// 	buffer := make([]byte, 4096)
	// 	buffer, _ = doc.Root().ToHtml(xml.DefaultEncodingBytes, buffer)

	epub := NewEpub(w)
	epub.FillMeta(article)
	epub.AddContent(article)
	epub.AddComments(article)
	epub.Close()
}

// Returns 200 OK if the server is running (for monitoring)
func Status(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "OK")
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Parse the command-line
	var addr string
	var logs string
	flag.StringVar(&addr, "a", "127.0.0.1:8000", "Bind to this address:port")
	flag.StringVar(&logs, "l", "-", "Use this file for logs")
	flag.Parse()

	// Logging
	if logs != "-" {
		f, err := os.OpenFile(logs, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatal("OpenFile: ", err)
		}
		syscall.Dup2(int(f.Fd()), int(os.Stdout.Fd()))
		syscall.Dup2(int(f.Fd()), int(os.Stderr.Fd()))
	}

	// Routing
	m := pat.New()
	m.Get("/status", http.HandlerFunc(Status))
	m.Get("/news/:slug.epub", http.HandlerFunc(News))
	// TODO accept other content types
	http.Handle("/", m)

	// Start the HTTP server
	log.Printf("Listening on http://%s/\n", addr)
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<commit_msg>Accept other content types<commit_after>package main

import (
	"archive/zip"
	"bytes"
	"errors"
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/moovweb/gokogiri"
	"github.com/moovweb/gokogiri/css"
	"github.com/moovweb/gokogiri/xml"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"runtime"
	"strings"
	"syscall"
	"time"
)

type Item struct {
	Id   string
	Href string
	Type string
}

type Epub struct {
	Zip *zip.Writer
	// TODO identifier / rights
	Title        string
	Subject      string
	Date         string
	Creator      string
	Contributors []string
	Items        []Item
}

const (
	ContentType    = "application/epub+zip"
	XmlDeclaration = `<?xml version="1.0" encoding="utf-8"?>`

	Container = XmlDeclaration + `
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
  <rootfiles>
    <rootfile full-path="EPUB/package.opf" media-type="application/oebps-package+xml"/>
  </rootfiles>
</container>`

	Nav = XmlDeclaration + `

    <title>LinuxFr.org</title>
    <meta charset="utf-8" />

Sommaire

Aller au contenu

`

	HeaderHtml = XmlDeclaration + `

    <title>LinuxFr.org</title>
    <meta charset="utf-8" />

  `

	FooterHtml = ""
)

// TODO cover
var PackageTemplate = template.Must(template.New("package").Parse(`
<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="pub-identifier" xml:lang="fr" version="3.0">
	<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
		<dc:language id="pub-language">fr</dc:language>
		<dc:identifier id="pub-identifier">xxx</dc:identifier>
		<dc:date>{{.Date}}</dc:date>
		<meta property="dcterms:modified">{{.Date}}</meta>
		{{if .Title}}<dc:title id="pub-title">{{.Title}}</dc:title>{{end}}
		{{if .Creator}}<dc:creator id="pub-creator">{{.Creator}}</dc:creator>{{end}}
		{{range .Contributors}}<dc:contributor>{{.}}</dc:contributor>
		{{end}}
		<dc:rights>xxx</dc:rights>
	</metadata>
	<manifest>
		<item id="nav" href="nav.html" media-type="application/xhtml+xml" properties="nav"/>
		{{range .Items}}<item id="{{.Id}}" href="{{.Href}}" media-type="{{.Type}}"/>
		{{end}}
	</manifest>
	<spine>
		{{range .Items}}<itemref idref="{{.Id}}"/>
		{{end}}
	</spine>
</package>`))

var Host string

func toHtml(node xml.Node) string {
	// TODO fix links to LinuxFr.org
	// TODO embed CSS & images
	return HeaderHtml + node.InnerHtml() + FooterHtml
}

func NewEpub(w io.Writer) (epub *Epub) {
	z := zip.NewWriter(w)
	epub = &Epub{Zip: z, Items: []Item{}}
	epub.AddMimetype()
	epub.AddFile("META-INF/container.xml", Container)
	epub.AddFile("EPUB/nav.html", Nav)
	return
}

func (epub *Epub) AddContent(article xml.Node) {
	xpath := css.Convert(".content", css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil || len(nodes) == 0 {
		return
	}
	html := toHtml(nodes[0])
	filename := "content.html"
	epub.Items = append(epub.Items, Item{"item-content", filename, "application/xhtml+xml"})
	epub.AddFile("EPUB/"+filename, html)
}

func (epub *Epub) AddComments(article xml.Node) {
	list := article.NextSibling()
	xpath := css.Convert(".threads>li", css.LOCAL)
	threads, err := list.Search(xpath)
	if err != nil {
		return
	}
	for _, thread := range threads {
		html := toHtml(thread)
		id := thread.Attr("id")
		filename := id + ".html"
		epub.Items = append(epub.Items, Item{id, filename, "application/xhtml+xml"})
		epub.AddFile("EPUB/"+filename, html)
	}
}

func (epub *Epub) FindMeta(article xml.Node, selector string) string {
	xpath := css.Convert(selector, css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil || len(nodes) == 0 {
		return ""
	}
	return nodes[0].Content()
}

func (epub *Epub) FindMetas(article xml.Node, selector string) []string {
	xpath := css.Convert(selector, css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil {
		return nil
	}
	metas := make([]string, len(nodes))
	for i, node := range nodes {
		metas[i] = node.Content()
	}
	return metas
}

func (epub *Epub) FillMeta(article xml.Node) {
	epub.Title = epub.FindMeta(article, "header h1 a:last-child")
	epub.Subject = epub.FindMeta(article, "header h1 a.topic")
	meta := epub.FindMeta(article, "header time.updated")
	// FIXME ParseInLocation
	date, err := time.Parse("le 02/01/06 à 15:04", meta)
	if err != nil {
		date = time.Now()
	}
	epub.Date = date.Format(time.RFC3339)
	epub.Creator = epub.FindMeta(article, "header .meta a[rel=\"author\"]")
	epub.Contributors = epub.FindMetas(article, "header .meta .edited_by a")
}

func (epub *Epub) AddMimetype() (err error) {
	header := &zip.FileHeader{Name: "mimetype", Method: zip.Store}
	f, err := epub.Zip.CreateHeader(header)
	if err != nil {
		log.Println(err)
		return
	}

	_, err = f.Write([]byte(ContentType))
	if err != nil {
		log.Println(err)
		return
	}

	return
}

func (epub *Epub) AddFile(filename, content string) (err error) {
	f, err := epub.Zip.Create(filename)
	if err != nil {
		log.Println(err)
		return
	}

	_, err = f.Write([]byte(content))
	if err != nil {
		log.Println(err)
		return
	}

	return
}

func (epub *Epub) Close() {
	var opf bytes.Buffer
	err := PackageTemplate.Execute(&opf, epub)
	if err != nil {
		log.Println(err)
		return
	}

	epub.AddFile("EPUB/package.opf", XmlDeclaration+opf.String())
	err = epub.Zip.Close()
	if err != nil {
		log.Println(err)
	}
}

func FetchArticle(uri string) (article xml.Node, err error) {
	resp, err := http.Get(uri)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Printf("Error on ioutil.ReadAll for %s: %s\n", uri, err)
		return
	}

	doc, err := gokogiri.ParseHtml(body)
	if err != nil {
		log.Printf("Gokogiri error: %s\n", err)
		return
	}

	xpath := css.Convert("#contents article", css.LOCAL)
	articles, err := doc.Root().Search(xpath)
	if err != nil {
		log.Printf("Gokogiri error: %s\n", err)
		return
	}

	if len(articles) == 0 {
		err = errors.New("No article found in the page")
		return
	}

	article = articles[0]
	return
}

func Content(w http.ResponseWriter, r *http.Request) {
	uri := "http://" + Host + strings.Replace(r.URL.Path, ".epub", "", 1)
	article, err := FetchArticle(uri)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	w.Header().Add("Content-Type", ContentType)

	epub := NewEpub(w)
	epub.FillMeta(article)
	epub.AddContent(article)
	epub.AddComments(article)
	epub.Close()
}

// Returns 200 OK if the server is running (for monitoring)
func Status(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "OK")
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Parse the command-line
	var addr string
	var logs string
	flag.StringVar(&addr, "a", "127.0.0.1:8000", "Bind to this address:port")
	flag.StringVar(&logs, "l", "-", "Use this file for logs")
	flag.StringVar(&Host, "h", "linuxfr.org", "Use this host to fetch pages")
	flag.Parse()

	// Logging
	if logs != "-" {
		f, err := os.OpenFile(logs, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatal("OpenFile: ", err)
		}
		syscall.Dup2(int(f.Fd()), int(os.Stdout.Fd()))
		syscall.Dup2(int(f.Fd()), int(os.Stderr.Fd()))
	}

	// Routing
	m := pat.New()
	m.Get("/status", http.HandlerFunc(Status))
	m.Get("/news/:slug.epub", http.HandlerFunc(Content))
	m.Get("/users/:user/journaux/:slug.epub", http.HandlerFunc(Content))
	m.Get("/forums/:forum/posts/:slug.epub", http.HandlerFunc(Content))
	m.Get("/sondages/:slug.epub", http.HandlerFunc(Content))
	m.Get("/suivi/:slug.epub", http.HandlerFunc(Content))
	m.Get("/wiki/:slug.epub", http.HandlerFunc(Content))
	http.Handle("/", m)

	// Start the HTTP server
	log.Printf("Listening on http://%s/\n", addr)
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"archive/zip"
	"bytes"
	"errors"
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/moovweb/gokogiri"
	"github.com/moovweb/gokogiri/css"
	"github.com/moovweb/gokogiri/xml"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"syscall"
	"time"
)

type Item struct {
	Id   string
	Href string
	Type string
}

type Epub struct {
	Zip *zip.Writer
	// TODO identifier / rights
	Title        string
	Subject      string
	Date         string
	Creator      string
	Contributors []string
	Items        []Item
}

const (
	Host        = "linuxfr.org"
	ContentType = "application/epub+zip"
	Container   = `<?xml version="1.0" encoding="utf-8"?>
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
  <rootfiles>
    <rootfile full-path="EPUB/package.opf" media-type="application/oebps-package+xml"/>
  </rootfiles>
</container>`
)

// FIXME why html/template escapes the first '<' sign?!??
// TODO embed CSS & images
// TODO cover
var PackageTemplate = template.Must(template.New("package").Parse(
	`<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="bookid" xml:lang="fr" version="3.0">
	<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
		<dc:language id="pub-language">fr</dc:language>
		<dc:identifier id="pub-identifier">xxx</dc:identifier>
		{{if .Title}}<dc:title id="pub-title">{{.Title}}</dc:title>{{end}}
		{{if .Date}}<dc:date>{{.Date}}</dc:date>{{end}}
		{{if .Creator}}<dc:creator id="pub-creator">{{.Creator}}</dc:creator>{{end}}
		{{range .Contributors}}<dc:contributor>{{.}}</dc:contributor>
		{{end}}
		<dc:rights>xxx</dc:rights>
	</metadata>
	<manifest>
		{{range .Items}}<item id="{{.Id}}" href="{{.Href}}" media-type="{{.Type}}"/>
		{{end}}
	</manifest>
	<spine>
		{{range .Items}}<itemref idref="{{.Id}}"/>
		{{end}}
	</spine>
</package>`))

func NewEpub(w io.Writer) (epub *Epub) {
	z := zip.NewWriter(w)
	epub = &Epub{Zip: z, Items: []Item{}}
	epub.AddFile("mimetype", ContentType)
	epub.AddFile("META-INF/container.xml", Container)
	return
}

func (epub *Epub) AddContent(article xml.Node) {
	xpath := css.Convert(".content", css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil || len(nodes) == 0 {
		return
	}
	html := nodes[0].InnerHtml() // FIXME should be a complete HTML document
	filename := "EPUB/content.html"
	epub.Items = append(epub.Items, Item{"item-content", filename, "application/xhtml+xml"})
	epub.AddFile(filename, html)
}

func (epub *Epub) AddComments(article xml.Node) {
	list := article.NextSibling()
	xpath := css.Convert(".threads>li", css.LOCAL)
	threads, err := list.Search(xpath)
	if err != nil {
		return
	}
	for _, thread := range threads {
		html := thread.InnerHtml() // FIXME should be a complete HTML document
		id := thread.Attr("id")
		filename := fmt.Sprintf("EPUB/%s.html", id)
		epub.Items = append(epub.Items, Item{id, filename, "application/xhtml+xml"})
		epub.AddFile(filename, html)
	}
}

func (epub *Epub) FindMeta(article xml.Node, selector string) string {
	xpath := css.Convert(selector, css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil || len(nodes) == 0 {
		return ""
	}
	return nodes[0].Content()
}

func (epub *Epub) FindMetas(article xml.Node, selector string) []string {
	xpath := css.Convert(selector, css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil {
		return nil
	}
	metas := make([]string, len(nodes))
	for i, node := range nodes {
		metas[i] = node.Content()
	}
	return metas
}

func (epub *Epub) FillMeta(article xml.Node) {
	epub.Title = epub.FindMeta(article, "header h1 a:last-child")
	epub.Subject = epub.FindMeta(article, "header h1 a.topic")
	meta := epub.FindMeta(article, "header time.updated")
	// FIXME ParseInLocation
	date, err := time.Parse("le 02/01/06 à 15:04", meta)
	if err == nil {
		epub.Date = date.String()
	}
	epub.Creator = epub.FindMeta(article, "header .meta a[rel=\"author\"]")
	epub.Contributors = epub.FindMetas(article, "header .meta .edited_by a")
}

func (epub *Epub) AddFile(filename, content string) (err error) {
	f, err := epub.Zip.Create(filename)
	if err != nil {
		log.Println(err)
		return
	}

	_, err = f.Write([]byte(content))
	if err != nil {
		log.Println(err)
		return
	}

	return
}

func (epub *Epub) Close() {
	var opf bytes.Buffer
	err := PackageTemplate.Execute(&opf, epub)
	if err != nil {
		log.Println(err)
		return
	}

	epub.AddFile("EPUB/package.opf", opf.String())
	err = epub.Zip.Close()
	if err != nil {
		log.Println(err)
	}
}

func fetchArticle(uri string) (article xml.Node, err error) {
	resp, err := http.Get(uri)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Printf("Error on ioutil.ReadAll for %s: %s\n", uri, err)
		return
	}

	doc, err := gokogiri.ParseHtml(body)
	if err != nil {
		log.Printf("Gokogiri error: %s\n", err)
		return
	}

	xpath := css.Convert("#contents article", css.LOCAL)
	articles, err := doc.Root().Search(xpath)
	if err != nil {
		log.Printf("Gokogiri error: %s\n", err)
		return
	}

	if len(articles) == 0 {
		err = errors.New("No article found in the page")
		return
	}

	article = articles[0]
	return
}

// Create an epub for a news
func News(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Content-Type", ContentType)

	slug := r.URL.Query().Get(":slug")
	uri := fmt.Sprintf("http://%s/news/%s", Host, slug)
	article, err := fetchArticle(uri)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	// 	buffer := make([]byte, 4096)
	// 	buffer, _ = doc.Root().ToHtml(xml.DefaultEncodingBytes, buffer)

	epub := NewEpub(w)
	epub.FillMeta(article)
	epub.AddContent(article)
	epub.AddComments(article)
	epub.Close()
}

// Returns 200 OK if the server is running (for monitoring)
func Status(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "OK")
}

func main() {
	// Parse the command-line
	var addr string
	var logs string
	flag.StringVar(&addr, "a", "127.0.0.1:8000", "Bind to this address:port")
	flag.StringVar(&logs, "l", "-", "Use this file for logs")
	flag.Parse()

	// Logging
	if logs != "-" {
		f, err := os.OpenFile(logs, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatal("OpenFile: ", err)
		}
		syscall.Dup2(int(f.Fd()), int(os.Stdout.Fd()))
		syscall.Dup2(int(f.Fd()), int(os.Stderr.Fd()))
	}

	// Routing
	m := pat.New()
	m.Get("/status", http.HandlerFunc(Status))
	m.Get("/news/:slug.epub", http.HandlerFunc(News))
	// TODO accept other content types
	http.Handle("/", m)

	// Start the HTTP server
	log.Printf("Listening on http://%s/\n", addr)
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<commit_msg>Mimetype must be in clear, not deflate<commit_after>package main

import (
	"archive/zip"
	"bytes"
	"errors"
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/moovweb/gokogiri"
	"github.com/moovweb/gokogiri/css"
	"github.com/moovweb/gokogiri/xml"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"syscall"
	"time"
)

type Item struct {
	Id   string
	Href string
	Type string
}

type Epub struct {
	Zip *zip.Writer
	// TODO identifier / rights
	Title        string
	Subject      string
	Date         string
	Creator      string
	Contributors []string
	Items        []Item
}

const (
	Host        = "linuxfr.org"
	ContentType = "application/epub+zip"
	Container   = `<?xml version="1.0" encoding="utf-8"?>
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
  <rootfiles>
    <rootfile full-path="EPUB/package.opf" media-type="application/oebps-package+xml"/>
  </rootfiles>
</container>`
)

// FIXME why html/template escapes the first '<' sign?!??
// TODO embed CSS & images
// TODO cover
var PackageTemplate = template.Must(template.New("package").Parse(
	`<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="bookid" xml:lang="fr" version="3.0">
	<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
		<dc:language id="pub-language">fr</dc:language>
		<dc:identifier id="pub-identifier">xxx</dc:identifier>
		{{if .Title}}<dc:title id="pub-title">{{.Title}}</dc:title>{{end}}
		{{if .Date}}<dc:date>{{.Date}}</dc:date>{{end}}
		{{if .Creator}}<dc:creator id="pub-creator">{{.Creator}}</dc:creator>{{end}}
		{{range .Contributors}}<dc:contributor>{{.}}</dc:contributor>
		{{end}}
		<dc:rights>xxx</dc:rights>
	</metadata>
	<manifest>
		{{range .Items}}<item id="{{.Id}}" href="{{.Href}}" media-type="{{.Type}}"/>
		{{end}}
	</manifest>
	<spine>
		{{range .Items}}<itemref idref="{{.Id}}"/>
		{{end}}
	</spine>
</package>`))

func NewEpub(w io.Writer) (epub *Epub) {
	z := zip.NewWriter(w)
	epub = &Epub{Zip: z, Items: []Item{}}
	epub.AddMimetype()
	epub.AddFile("META-INF/container.xml", Container)
	return
}

func (epub *Epub) AddContent(article xml.Node) {
	xpath := css.Convert(".content", css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil || len(nodes) == 0 {
		return
	}
	html := nodes[0].InnerHtml() // FIXME should be a complete HTML document
	filename := "EPUB/content.html"
	epub.Items = append(epub.Items, Item{"item-content", filename, "application/xhtml+xml"})
	epub.AddFile(filename, html)
}

func (epub *Epub) AddComments(article xml.Node) {
	list := article.NextSibling()
	xpath := css.Convert(".threads>li", css.LOCAL)
	threads, err := list.Search(xpath)
	if err != nil {
		return
	}
	for _, thread := range threads {
		html := thread.InnerHtml() // FIXME should be a complete HTML document
		id := thread.Attr("id")
		filename := fmt.Sprintf("EPUB/%s.html", id)
		epub.Items = append(epub.Items, Item{id, filename, "application/xhtml+xml"})
		epub.AddFile(filename, html)
	}
}

func (epub *Epub) FindMeta(article xml.Node, selector string) string {
	xpath := css.Convert(selector, css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil || len(nodes) == 0 {
		return ""
	}
	return nodes[0].Content()
}

func (epub *Epub) FindMetas(article xml.Node, selector string) []string {
	xpath := css.Convert(selector, css.LOCAL)
	nodes, err := article.Search(xpath)
	if err != nil {
		return nil
	}
	metas := make([]string, len(nodes))
	for i, node := range nodes {
		metas[i] = node.Content()
	}
	return metas
}

func (epub *Epub) FillMeta(article xml.Node) {
	epub.Title = epub.FindMeta(article, "header h1 a:last-child")
	epub.Subject = epub.FindMeta(article, "header h1 a.topic")
	meta := epub.FindMeta(article, "header time.updated")
	// FIXME ParseInLocation
	date, err := time.Parse("le 02/01/06 à 15:04", meta)
	if err == nil {
		epub.Date = date.String()
	}
	epub.Creator = epub.FindMeta(article, "header .meta a[rel=\"author\"]")
	epub.Contributors = epub.FindMetas(article, "header .meta .edited_by a")
}

func (epub *Epub) AddMimetype() (err error) {
	header := &zip.FileHeader{Name: "mimetype", Method: zip.Store}
	f, err := epub.Zip.CreateHeader(header)
	if err != nil {
		log.Println(err)
		return
	}

	_, err = f.Write([]byte(ContentType))
	if err != nil {
		log.Println(err)
		return
	}

	return
}

func (epub *Epub) AddFile(filename, content string) (err error) {
	f, err := epub.Zip.Create(filename)
	if err != nil {
		log.Println(err)
		return
	}

	_, err = f.Write([]byte(content))
	if err != nil {
		log.Println(err)
		return
	}

	return
}

func (epub *Epub) Close() {
	var opf bytes.Buffer
	err := PackageTemplate.Execute(&opf, epub)
	if err != nil {
		log.Println(err)
		return
	}

	epub.AddFile("EPUB/package.opf", opf.String())
	err = epub.Zip.Close()
	if err != nil {
		log.Println(err)
	}
}

func fetchArticle(uri string) (article xml.Node, err error) {
	resp, err := http.Get(uri)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Printf("Error on ioutil.ReadAll for %s: %s\n", uri, err)
		return
	}

	doc, err := gokogiri.ParseHtml(body)
	if err != nil {
		log.Printf("Gokogiri error: %s\n", err)
		return
	}

	xpath := css.Convert("#contents article", css.LOCAL)
	articles, err := doc.Root().Search(xpath)
	if err != nil {
		log.Printf("Gokogiri error: %s\n", err)
		return
	}

	if len(articles) == 0 {
		err = errors.New("No article found in the page")
		return
	}

	article = articles[0]
	return
}

// Create an epub for a news
func News(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Content-Type", ContentType)

	slug := r.URL.Query().Get(":slug")
	uri := fmt.Sprintf("http://%s/news/%s", Host, slug)
	article, err := fetchArticle(uri)
	if err != nil {
		http.NotFound(w, r)
		return
	}

	// 	buffer := make([]byte, 4096)
	// 	buffer, _ = doc.Root().ToHtml(xml.DefaultEncodingBytes, buffer)

	epub := NewEpub(w)
	epub.FillMeta(article)
	epub.AddContent(article)
	epub.AddComments(article)
	epub.Close()
}

// Returns 200 OK if the server is running (for monitoring)
func Status(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "OK")
}

func main() {
	// Parse the command-line
	var addr string
	var logs string
	flag.StringVar(&addr, "a", "127.0.0.1:8000", "Bind to this address:port")
	flag.StringVar(&logs, "l", "-", "Use this file for logs")
	flag.Parse()

	// Logging
	if logs != "-" {
		f, err := os.OpenFile(logs, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatal("OpenFile: ", err)
		}
		syscall.Dup2(int(f.Fd()), int(os.Stdout.Fd()))
		syscall.Dup2(int(f.Fd()), int(os.Stderr.Fd()))
	}

	// Routing
	m := pat.New()
	m.Get("/status", http.HandlerFunc(Status))
	m.Get("/news/:slug.epub", http.HandlerFunc(News))
	// TODO accept other content types
	http.Handle("/", m)

	// Start the HTTP server
	log.Printf("Listening on http://%s/\n", addr)
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"os"
	"os/exec"
	"syscall"
	"time"
)

type Cmd struct {
	*exec.Cmd
	Name string
	done chan bool
	dead chan struct{}
}

func NewCmd(command string) *Cmd {
	return &Cmd{
		// -c is the POSIX switch to run a command
		Cmd:  exec.Command(*shell, "-c", command),
		Name: command,
		// send on done must always succeed so the Runnable can proceed to cleanup
		done: make(chan bool, 1),
		dead: make(chan struct{}),
	}
}

type Runnable func(chan struct{}) (chan bool, chan struct{})

func NewRunWait(command string) Runnable {
	return func(kill chan struct{}) (chan bool, chan struct{}) {
		log.Info("Running command, waiting:", command)

		cmd := NewCmd(command)

		go cmd.RunWait(kill)

		return cmd.done, cmd.dead
	}
}

func (cmd *Cmd) RunWait(kill chan struct{}) {
	defer close(cmd.done)
	defer close(cmd.dead)

	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err := cmd.Start()
	if err != nil {
		// this is a program, system or environment error (shell is set wrong)
		// because it is not recoverable between builds, it is fatal
		log.Fatal("Error starting command:", err)(6)
	}

	proc := make(chan error)
	go func() {
		proc <- cmd.Wait()
		close(proc)
	}()

	select {
	case err := <-proc:
		if err != nil {
			log.Err("Command error:", err)
			cmd.done <- false
		} else {
			cmd.done <- true
		}
	case <-kill:
		cmd.Kill()
	}

	// we can not return until the process has exited
	// while this is guarunteed in a RunWait(), it is necessary for daemons
	// and so standardized here as well
	<-proc
}

// This should only be called from within the Runnable
// which ensures that the process has started and so can be killed
func (cmd *Cmd) Kill() {
	if *exitWait < 1 {
		log.Info("Killing command:", cmd.Name)
		if err := cmd.Process.Kill(); err != nil {
			log.Err("Failed to kill command ("+cmd.Name+"), error:", err)
		}
		return
	}

	log.Info("Sending exit signal (SIGINT) to command:", cmd.Name)

	if err := cmd.Process.Signal(syscall.SIGINT); err != nil {
		log.Err("Failed to kill command ("+cmd.Name+"):", err)
		return
	}

	// give the process time to cleanup, check again if it has finished (ProcessState is set)
	time.Sleep(time.Duration(*exitWait) * time.Millisecond)
	if cmd.ProcessState != nil {
		return
	}

	log.Info("Command still alive, killing…")
	if err := cmd.Process.Kill(); err != nil {
		log.Err("Failed to kill command ("+cmd.Name+"):", err)
	}
}

func NewDaemonTimer(command string, period int) Runnable {
	return func(kill chan struct{}) (chan bool, chan struct{}) {
		log.Info("Starting daemon:", command)

		cmd := NewCmd(command)

		go cmd.RunDaemonTimer(kill, period)

		return cmd.done, cmd.dead
	}
}
func (cmd *Cmd) RunDaemonTimer(kill chan struct{}, period int) {
	defer close(cmd.done)
	defer close(cmd.dead)

	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err := cmd.Start()
	if err != nil {
		log.Fatal("Error starting daemon:", err)(7)
	}

	proc := make(chan error)
	go func() {
		proc <- cmd.Wait()
		close(proc)
	}()

	log.Debug("Waiting miliseconds:", period)
	timerDone := make(chan struct{})
	timer := time.AfterFunc(time.Duration(period)*time.Millisecond, func() {
		close(timerDone)
	})

	select {
	case <-timerDone:
		log.Debug("Daemon timer done")
		cmd.done <- true
	case err := <-proc:
		timer.Stop()
		if err != nil {
			log.Err("Daemon error:", err)
			cmd.done <- false
		} else {
			// A daemon probably shouldn't be exiting
			log.Warn("Daemon exited cleanly")
			cmd.done <- true
		}
	case <-kill:
		cmd.Kill()
	}

	// we can not return until the process has exited
	<-proc
}

func NewDaemonTrigger(command string, trigger string) Runnable {
	return func(kill chan struct{}) (chan bool, chan struct{}) {
		log.Info("Starting daemon:", command)

		cmd := NewCmd(command)

		go cmd.RunDaemonTrigger(kill, trigger)

		return cmd.done, cmd.dead
	}
}

func (cmd *Cmd) RunDaemonTrigger(kill chan struct{}, trigger string) {
	defer close(cmd.done)
	defer close(cmd.dead)

	cmd.Stdin = os.Stdin

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal("Error opening stdout:", err)(8)
	}
	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		log.Fatal("Error opening stderr:", err)(8)
	}

	err = cmd.Start()
	if err != nil {
		log.Fatal("Error starting daemon:", err)(7)
	}

	proc := make(chan error)
	go func() {
		proc <- cmd.Wait()
		close(proc)
	}()

	stop := false
	key := []byte(trigger)
	match := make(chan struct{})

	watchPipe := func(in io.Reader, out io.Writer) {
		b := make([]byte, 1)
		spot := 0

		for {
			// check if the trigger has been pulled and shift to copy mode
			if stop {
				_, err := io.Copy(out, in)
				if err != nil {
					log.Err("Unwatched pipe has errored:", err)
				}
				return
			}

			n, err := in.Read(b)
			if n > 0 {
				out.Write(b[:n])
				if b[0] == key[spot] {
					spot++
					if spot == len(key) {
						log.Debug("Trigger match")
						stop = true
						close(match)
					}
				}
			}
			if err != nil {
				if err.Error() != "EOF" {
					log.Err("Watched pipe error:", err)
				}
				return
			}
		}
	}

	go watchPipe(stdoutPipe, os.Stdout)
	go watchPipe(stderrPipe, os.Stderr)

	select {
	case <-match:
		log.Debug("Daemon trigger matched")
		cmd.done <- true
	case err := <-proc:
		if err != nil {
			log.Err("Daemon error:", err)
			cmd.done <- false
		} else {
			// A daemon probably shouldn't be exiting
			log.Warn("Daemon exited cleanly")
			cmd.done <- true
		}
	case <-kill:
		cmd.Kill()
	}

	// we can not return until the process has exited
	<-proc
}
<commit_msg>fix 0 timer<commit_after>package main

import (
	"io"
	"os"
	"os/exec"
	"syscall"
	"time"
)

type Cmd struct {
	*exec.Cmd
	Name string
	done chan bool
	dead chan struct{}
}

func NewCmd(command string) *Cmd {
	return &Cmd{
		// -c is the POSIX switch to run a command
		Cmd:  exec.Command(*shell, "-c", command),
		Name: command,
		// send on done must always succeed so the Runnable can proceed to cleanup
		done: make(chan bool, 1),
		dead: make(chan struct{}),
	}
}

type Runnable func(chan struct{}) (chan bool, chan struct{})

func NewRunWait(command string) Runnable {
	return func(kill chan struct{}) (chan bool, chan struct{}) {
		log.Info("Running command, waiting:", command)

		cmd := NewCmd(command)

		go cmd.RunWait(kill)

		return cmd.done, cmd.dead
	}
}

func (cmd *Cmd) RunWait(kill chan struct{}) {
	defer close(cmd.done)
	defer close(cmd.dead)

	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err := cmd.Start()
	if err != nil {
		// this is a program, system or environment error (shell is set wrong)
		// because it is not recoverable between builds, it is fatal
		log.Fatal("Error starting command:", err)(6)
	}

	proc := make(chan error)
	go func() {
		proc <- cmd.Wait()
		close(proc)
	}()

	select {
	case err := <-proc:
		if err != nil {
			log.Err("Command error:", err)
			cmd.done <- false
		} else {
			cmd.done <- true
		}
	case <-kill:
		cmd.Kill()
	}

	// we can not return until the process has exited
	// while this is guarunteed in a RunWait(), it is necessary for daemons
	// and so standardized here as well
	<-proc
}

// This should only be called from within the Runnable
// which ensures that the process has started and so can be killed
func (cmd *Cmd) Kill() {
	if *exitWait < 1 {
		log.Info("Killing command:", cmd.Name)
		if err := cmd.Process.Kill(); err != nil {
			log.Err("Failed to kill command ("+cmd.Name+"), error:", err)
		}
		return
	}

	log.Info("Sending exit signal (SIGINT) to command:", cmd.Name)

	if err := cmd.Process.Signal(syscall.SIGINT); err != nil {
		log.Err("Failed to kill command ("+cmd.Name+"):", err)
		return
	}

	// give the process time to cleanup, check again if it has finished (ProcessState is set)
	time.Sleep(time.Duration(*exitWait) * time.Millisecond)
	if cmd.ProcessState != nil {
		return
	}

	log.Info("Command still alive, killing…")
	if err := cmd.Process.Kill(); err != nil {
		log.Err("Failed to kill command ("+cmd.Name+"):", err)
	}
}

func NewDaemonTimer(command string, period int) Runnable {
	return func(kill chan struct{}) (chan bool, chan struct{}) {
		log.Info("Starting daemon:", command)

		cmd := NewCmd(command)

		go cmd.RunDaemonTimer(kill, period)

		return cmd.done, cmd.dead
	}
}
func (cmd *Cmd) RunDaemonTimer(kill chan struct{}, period int) {
	defer close(cmd.done)
	defer close(cmd.dead)

	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err := cmd.Start()
	if err != nil {
		log.Fatal("Error starting daemon:", err)(7)
	}

	proc := make(chan error)
	go func() {
		proc <- cmd.Wait()
		close(proc)
	}()

	timerDone := make(chan struct{})

	var timer *time.Timer
	if period > 0 {
		log.Debug("Waiting miliseconds:", period)
		timer = time.AfterFunc(time.Duration(period)*time.Millisecond, func() {
			close(timerDone)
		})
	}

	select {
	case <-timerDone:
		log.Debug("Daemon timer done")
		cmd.done <- true
	case err := <-proc:
		if period > 0 {
			timer.Stop()
		}
		if err != nil {
			log.Err("Daemon error:", err)
			cmd.done <- false
		} else {
			// A daemon probably shouldn't be exiting
			log.Warn("Daemon exited cleanly")
			cmd.done <- true
		}
	case <-kill:
		cmd.Kill()
	}

	// we can not return until the process has exited
	<-proc
}

func NewDaemonTrigger(command string, trigger string) Runnable {
	return func(kill chan struct{}) (chan bool, chan struct{}) {
		log.Info("Starting daemon:", command)

		cmd := NewCmd(command)

		go cmd.RunDaemonTrigger(kill, trigger)

		return cmd.done, cmd.dead
	}
}

func (cmd *Cmd) RunDaemonTrigger(kill chan struct{}, trigger string) {
	defer close(cmd.done)
	defer close(cmd.dead)

	cmd.Stdin = os.Stdin

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal("Error opening stdout:", err)(8)
	}
	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		log.Fatal("Error opening stderr:", err)(8)
	}

	err = cmd.Start()
	if err != nil {
		log.Fatal("Error starting daemon:", err)(7)
	}

	proc := make(chan error)
	go func() {
		proc <- cmd.Wait()
		close(proc)
	}()

	stop := false
	key := []byte(trigger)
	match := make(chan struct{})

	watchPipe := func(in io.Reader, out io.Writer) {
		b := make([]byte, 1)
		spot := 0

		for {
			// check if the trigger has been pulled and shift to copy mode
			if stop {
				_, err := io.Copy(out, in)
				if err != nil {
					log.Err("Unwatched pipe has errored:", err)
				}
				return
			}

			n, err := in.Read(b)
			if n > 0 {
				out.Write(b[:n])
				if b[0] == key[spot] {
					spot++
					if spot == len(key) {
						log.Debug("Trigger match")
						stop = true
						close(match)
					}
				}
			}
			if err != nil {
				if err.Error() != "EOF" {
					log.Err("Watched pipe error:", err)
				}
				return
			}
		}
	}

	go watchPipe(stdoutPipe, os.Stdout)
	go watchPipe(stderrPipe, os.Stderr)

	select {
	case <-match:
		log.Debug("Daemon trigger matched")
		cmd.done <- true
	case err := <-proc:
		if err != nil {
			log.Err("Daemon error:", err)
			cmd.done <- false
		} else {
			// A daemon probably shouldn't be exiting
			log.Warn("Daemon exited cleanly")
			cmd.done <- true
		}
	case <-kill:
		cmd.Kill()
	}

	// we can not return until the process has exited
	<-proc
}
<|endoftext|>