<commit_before>package gotwilio

import (
	"context"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"
)

// SmsResponse is returned after a text/sms message is posted to Twilio
type SmsResponse struct {
	Sid         string  `json:"sid"`
	DateCreated string  `json:"date_created"`
	DateUpdate  string  `json:"date_updated"`
	DateSent    string  `json:"date_sent"`
	AccountSid  string  `json:"account_sid"`
	To          string  `json:"to"`
	From        string  `json:"from"`
	NumMedia    string  `json:"num_media"`
	Body        string  `json:"body"`
	Status      string  `json:"status"`
	Direction   string  `json:"direction"`
	ApiVersion  string  `json:"api_version"`
	Price       *string `json:"price,omitempty"`
	Url         string  `json:"uri"`
}

// Optional SMS parameters
var (
	// Settings for what Twilio should do with addresses in message logs
	SmsAddressRetentionObfuscate = &Option{"AddressRetention", "obfuscate"}
	SmsAddressRetentionRetain    = &Option{"AddressRetention", "retain"}
	// Settings for what Twilio should do with message content in message logs
	SmsContentRetentionDiscard = &Option{"ContentRetention", "discard"}
	SmsContentRetentionRetain  = &Option{"ContentRetention", "retain"}
)

// DateCreatedAsTime returns SmsResponse.DateCreated as a time.Time object
// instead of a string.
func (sms *SmsResponse) DateCreatedAsTime() (time.Time, error) {
	return time.Parse(time.RFC1123Z, sms.DateCreated)
}

// DateUpdateAsTime returns SmsResponse.DateUpdate as a time.Time object
// instead of a string.
func (sms *SmsResponse) DateUpdateAsTime() (time.Time, error) {
	return time.Parse(time.RFC1123Z, sms.DateUpdate)
}

// DateSentAsTime returns SmsResponse.DateSent as a time.Time object
// instead of a string.
func (sms *SmsResponse) DateSentAsTime() (time.Time, error) {
	return time.Parse(time.RFC1123Z, sms.DateSent)
}

func whatsapp(phone string) string {
	return "whatsapp:" + phone
}

// SendWhatsApp uses Twilio to send a WhatsApp message.
// See https://www.twilio.com/docs/sms/whatsapp/tutorial/send-and-receive-media-messages-whatsapp-python
func (twilio *Twilio) SendWhatsApp(from, to, body, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendSMS(whatsapp(from), whatsapp(to), body, statusCallback, applicationSid)
}

// SendWhatsAppMedia uses Twilio to send a WhatsApp message with Media enabled.
// See https://www.twilio.com/docs/sms/whatsapp/tutorial/send-and-receive-media-messages-whatsapp-python
func (twilio *Twilio) SendWhatsAppMedia(from, to, body string, mediaURL []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendWhatsAppMediaWithContext(context.Background(), from, to, body, mediaURL, statusCallback, applicationSid)
}

func (twilio *Twilio) SendWhatsAppMediaWithContext(ctx context.Context, from, to, body string, mediaURL []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	formValues := initFormValues(whatsapp(to), body, mediaURL, statusCallback, applicationSid)
	formValues.Set("From", whatsapp(from))

	return twilio.sendMessage(ctx, formValues)
}

// SendSMS uses Twilio to send a text message.
// See http://www.twilio.com/docs/api/rest/sending-sms for more information.
func (twilio *Twilio) SendSMS(from, to, body, statusCallback, applicationSid string, opts ...*Option) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendSMSWithContext(context.Background(), from, to, body, statusCallback, applicationSid, opts...)
}

func (twilio *Twilio) SendSMSWithContext(ctx context.Context, from, to, body, statusCallback, applicationSid string, opts ...*Option) (smsResponse *SmsResponse, exception *Exception, err error) {
	formValues := initFormValues(to, body, nil, statusCallback, applicationSid)
	formValues.Set("From", from)

	for _, opt := range opts {
		if opt != nil {
			formValues.Set(opt.Key, opt.Value)
		}
	}

	smsResponse, exception, err = twilio.sendMessage(ctx, formValues)
	return
}

// GetSMS uses Twilio to get information about a text message.
// See https://www.twilio.com/docs/api/rest/sms for more information.
func (twilio *Twilio) GetSMS(sid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.GetSMSWithContext(context.Background(), sid)
}

func (twilio *Twilio) GetSMSWithContext(ctx context.Context, sid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	twilioUrl := twilio.BaseUrl + "/Accounts/" + twilio.AccountSid + "/SMS/Messages/" + sid + ".json"

	res, err := twilio.get(ctx, twilioUrl)
	if err != nil {
		return smsResponse, exception, err
	}
	defer res.Body.Close()

	responseBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return smsResponse, exception, err
	}

	if res.StatusCode != http.StatusOK {
		exception = new(Exception)
		err = json.Unmarshal(responseBody, exception)

		// We aren't checking the error because we don't actually care.
		// It's going to be passed to the client either way.
		return smsResponse, exception, err
	}

	smsResponse = new(SmsResponse)
	err = json.Unmarshal(responseBody, smsResponse)
	return smsResponse, exception, err
}

// SendSMSWithCopilot uses Twilio Copilot to send a text message.
// See https://www.twilio.com/docs/api/rest/sending-messages-copilot
func (twilio *Twilio) SendSMSWithCopilot(messagingServiceSid, to, body, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendSMSWithCopilotWithContext(context.Background(), messagingServiceSid, to, body, statusCallback, applicationSid)
}

func (twilio *Twilio) SendSMSWithCopilotWithContext(ctx context.Context, messagingServiceSid, to, body, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	formValues := initFormValues(to, body, nil, statusCallback, applicationSid)
	formValues.Set("MessagingServiceSid", messagingServiceSid)

	smsResponse, exception, err = twilio.sendMessage(ctx, formValues)
	return
}

// SendMMS uses Twilio to send a multimedia message.
func (twilio *Twilio) SendMMS(from, to, body string, mediaUrl []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendMMSWithContext(context.Background(), from, to, body, mediaUrl, statusCallback, applicationSid)
}

func (twilio *Twilio) SendMMSWithContext(ctx context.Context, from, to, body string, mediaUrl []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	formValues := initFormValues(to, body, mediaUrl, statusCallback, applicationSid)
	formValues.Set("From", from)

	smsResponse, exception, err = twilio.sendMessage(ctx, formValues)
	return
}

// SendMMSWithCopilot uses Twilio Copilot to send a multimedia message.
// See https://www.twilio.com/docs/api/rest/sending-messages-copilot
func (twilio *Twilio) SendMMSWithCopilot(messagingServiceSid, to, body string, mediaUrl []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendMMSWithCopilotWithContext(context.Background(), messagingServiceSid, to, body, mediaUrl, statusCallback, applicationSid)
}

func (twilio *Twilio) SendMMSWithCopilotWithContext(ctx context.Context, messagingServiceSid, to, body string, mediaUrl []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	formValues := initFormValues(to, body, mediaUrl, statusCallback, applicationSid)
	formValues.Set("MessagingServiceSid", messagingServiceSid)

	smsResponse, exception, err = twilio.sendMessage(ctx, formValues)
	return
}

// Core method to send message
func (twilio *Twilio) sendMessage(ctx context.Context, formValues url.Values) (smsResponse *SmsResponse, exception *Exception, err error) {
	twilioUrl := twilio.BaseUrl + "/Accounts/" + twilio.AccountSid + "/Messages.json"

	res, err := twilio.post(ctx, formValues, twilioUrl)
	if err != nil {
		return smsResponse, exception, err
	}
	defer res.Body.Close()

	responseBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return smsResponse, exception, err
	}

	if res.StatusCode != http.StatusCreated {
		exception = new(Exception)
		err = json.Unmarshal(responseBody, exception)

		// We aren't checking the error because we don't actually care.
		// It's going to be passed to the client either way.
		return smsResponse, exception, err
	}

	smsResponse = new(SmsResponse)
	err = json.Unmarshal(responseBody, smsResponse)
	return smsResponse, exception, err
}

// Form values initialization
func initFormValues(to, body string, mediaUrl []string, statusCallback, applicationSid string) url.Values {
	formValues := url.Values{}

	formValues.Set("To", to)
	formValues.Set("Body", body)

	if len(mediaUrl) > 0 {
		for _, value := range mediaUrl {
			formValues.Add("MediaUrl", value)
		}
	}

	if statusCallback != "" {
		formValues.Set("StatusCallback", statusCallback)
	}

	if applicationSid != "" {
		formValues.Set("ApplicationSid", applicationSid)
	}

	return formValues
}
<commit_msg>Add a new GetMessage function (#87)<commit_after>package gotwilio

import (
	"context"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"time"
)

// SmsResponse is returned after a text/sms message is posted to Twilio
type SmsResponse struct {
	Sid                 string  `json:"sid"`
	DateCreated         string  `json:"date_created"`
	DateUpdate          string  `json:"date_updated"`
	DateSent            string  `json:"date_sent"`
	AccountSid          string  `json:"account_sid"`
	To                  string  `json:"to"`
	From                string  `json:"from"`
	NumMedia            string  `json:"num_media"`
	Body                string  `json:"body"`
	NumSegments         string  `json:"num_segments"`
	Status              string  `json:"status"`
	MessagingServiceSid *string `json:"messaging_service_sid,omitempty"`
	Direction           string  `json:"direction"`
	ApiVersion          string  `json:"api_version"`
	Price               *string `json:"price,omitempty"`
	PriceUnit           *string `json:"price_unit,omitempty"`
	Url                 string  `json:"uri"`
}

// MessageResponse is returned when checking on the status of a message.
// See: https://www.twilio.com/docs/sms/api/message-resource#fetch-a-message-resource
type MessageResponse struct {
	Sid                 string            `json:"sid"`
	DateCreated         string            `json:"date_created"`
	DateUpdate          string            `json:"date_updated"`
	DateSent            string            `json:"date_sent"`
	AccountSid          string            `json:"account_sid"`
	To                  string            `json:"to"`
	From                string            `json:"from"`
	NumMedia            string            `json:"num_media"`
	Body                string            `json:"body"`
	NumSegments         string            `json:"num_segments"`
	Status              string            `json:"status"`
	MessagingServiceSid *string           `json:"messaging_service_sid,omitempty"`
	Direction           string            `json:"direction"`
	ApiVersion          string            `json:"api_version"`
	Price               *string           `json:"price,omitempty"`
	PriceUnit           *string           `json:"price_unit,omitempty"`
	Url                 string            `json:"uri"`
	ErrorCode           *int              `json:"error_code,omitempty"`
	ErrorMessage        *string           `json:"error_message,omitempty"`
	SubresourceURIs     map[string]string `json:"subresource_uris,omitempty"`
}

// Optional SMS parameters
var (
	// Settings for what Twilio should do with addresses in message logs
	SmsAddressRetentionObfuscate = &Option{"AddressRetention", "obfuscate"}
	SmsAddressRetentionRetain    = &Option{"AddressRetention", "retain"}
	// Settings for what Twilio should do with message content in message logs
	SmsContentRetentionDiscard = &Option{"ContentRetention", "discard"}
	SmsContentRetentionRetain  = &Option{"ContentRetention", "retain"}
)

// DateCreatedAsTime returns SmsResponse.DateCreated as a time.Time object
// instead of a string.
func (sms *SmsResponse) DateCreatedAsTime() (time.Time, error) {
	return time.Parse(time.RFC1123Z, sms.DateCreated)
}

// DateUpdateAsTime returns SmsResponse.DateUpdate as a time.Time object
// instead of a string.
func (sms *SmsResponse) DateUpdateAsTime() (time.Time, error) {
	return time.Parse(time.RFC1123Z, sms.DateUpdate)
}

// DateSentAsTime returns SmsResponse.DateSent as a time.Time object
// instead of a string.
func (sms *SmsResponse) DateSentAsTime() (time.Time, error) {
	return time.Parse(time.RFC1123Z, sms.DateSent)
}

func whatsapp(phone string) string {
	return "whatsapp:" + phone
}

// SendWhatsApp uses Twilio to send a WhatsApp message.
// See https://www.twilio.com/docs/sms/whatsapp/tutorial/send-and-receive-media-messages-whatsapp-python
func (twilio *Twilio) SendWhatsApp(from, to, body, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendSMS(whatsapp(from), whatsapp(to), body, statusCallback, applicationSid)
}

// SendWhatsAppMedia uses Twilio to send a WhatsApp message with Media enabled.
// See https://www.twilio.com/docs/sms/whatsapp/tutorial/send-and-receive-media-messages-whatsapp-python
func (twilio *Twilio) SendWhatsAppMedia(from, to, body string, mediaURL []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendWhatsAppMediaWithContext(context.Background(), from, to, body, mediaURL, statusCallback, applicationSid)
}

func (twilio *Twilio) SendWhatsAppMediaWithContext(ctx context.Context, from, to, body string, mediaURL []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	formValues := initFormValues(whatsapp(to), body, mediaURL, statusCallback, applicationSid)
	formValues.Set("From", whatsapp(from))

	return twilio.sendMessage(ctx, formValues)
}

// SendSMS uses Twilio to send a text message.
// See http://www.twilio.com/docs/api/rest/sending-sms for more information.
func (twilio *Twilio) SendSMS(from, to, body, statusCallback, applicationSid string, opts ...*Option) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendSMSWithContext(context.Background(), from, to, body, statusCallback, applicationSid, opts...)
}

func (twilio *Twilio) SendSMSWithContext(ctx context.Context, from, to, body, statusCallback, applicationSid string, opts ...*Option) (smsResponse *SmsResponse, exception *Exception, err error) {
	formValues := initFormValues(to, body, nil, statusCallback, applicationSid)
	formValues.Set("From", from)

	for _, opt := range opts {
		if opt != nil {
			formValues.Set(opt.Key, opt.Value)
		}
	}

	smsResponse, exception, err = twilio.sendMessage(ctx, formValues)
	return
}

// GetSMS uses Twilio to get information about a text message.
// See https://www.twilio.com/docs/api/rest/sms for more information.
func (twilio *Twilio) GetSMS(sid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.GetSMSWithContext(context.Background(), sid)
}

func (twilio *Twilio) GetSMSWithContext(ctx context.Context, sid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	twilioUrl := twilio.BaseUrl + "/Accounts/" + twilio.AccountSid + "/SMS/Messages/" + sid + ".json"

	res, err := twilio.get(ctx, twilioUrl)
	if err != nil {
		return smsResponse, exception, err
	}
	defer res.Body.Close()

	responseBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return smsResponse, exception, err
	}

	if res.StatusCode != http.StatusOK {
		exception = new(Exception)
		err = json.Unmarshal(responseBody, exception)

		// We aren't checking the error because we don't actually care.
		// It's going to be passed to the client either way.
		return smsResponse, exception, err
	}

	smsResponse = new(SmsResponse)
	err = json.Unmarshal(responseBody, smsResponse)
	return smsResponse, exception, err
}

// GetMessage uses Twilio to get information about a text message.
//
// This can be used to check to see if a message has been successfully delivered, or if there was an error delivering the message.
//
// See https://www.twilio.com/docs/api/rest/sms for more information.
func (twilio *Twilio) GetMessage(sid string) (messageResponse *MessageResponse, exception *Exception, err error) {
	twilioUrl := twilio.BaseUrl + "/Accounts/" + twilio.AccountSid + "/Messages/" + sid + ".json"

	res, err := twilio.get(twilioUrl)
	if err != nil {
		return messageResponse, exception, err
	}
	defer res.Body.Close()

	responseBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return messageResponse, exception, err
	}

	if res.StatusCode != http.StatusOK {
		exception = new(Exception)
		err = json.Unmarshal(responseBody, exception)

		// We aren't checking the error because we don't actually care.
		// It's going to be passed to the client either way.
		return messageResponse, exception, err
	}

	messageResponse = new(MessageResponse)
	err = json.Unmarshal(responseBody, messageResponse)
	return messageResponse, exception, err
}

// SendSMSWithCopilot uses Twilio Copilot to send a text message.
// See https://www.twilio.com/docs/api/rest/sending-messages-copilot
func (twilio *Twilio) SendSMSWithCopilot(messagingServiceSid, to, body, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendSMSWithCopilotWithContext(context.Background(), messagingServiceSid, to, body, statusCallback, applicationSid)
}

func (twilio *Twilio) SendSMSWithCopilotWithContext(ctx context.Context, messagingServiceSid, to, body, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	formValues := initFormValues(to, body, nil, statusCallback, applicationSid)
	formValues.Set("MessagingServiceSid", messagingServiceSid)

	smsResponse, exception, err = twilio.sendMessage(ctx, formValues)
	return
}

// SendMMS uses Twilio to send a multimedia message.
func (twilio *Twilio) SendMMS(from, to, body string, mediaUrl []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendMMSWithContext(context.Background(), from, to, body, mediaUrl, statusCallback, applicationSid)
}

func (twilio *Twilio) SendMMSWithContext(ctx context.Context, from, to, body string, mediaUrl []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	formValues := initFormValues(to, body, mediaUrl, statusCallback, applicationSid)
	formValues.Set("From", from)

	smsResponse, exception, err = twilio.sendMessage(ctx, formValues)
	return
}

// SendMMSWithCopilot uses Twilio Copilot to send a multimedia message.
// See https://www.twilio.com/docs/api/rest/sending-messages-copilot
func (twilio *Twilio) SendMMSWithCopilot(messagingServiceSid, to, body string, mediaUrl []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	return twilio.SendMMSWithCopilotWithContext(context.Background(), messagingServiceSid, to, body, mediaUrl, statusCallback, applicationSid)
}

func (twilio *Twilio) SendMMSWithCopilotWithContext(ctx context.Context, messagingServiceSid, to, body string, mediaUrl []string, statusCallback, applicationSid string) (smsResponse *SmsResponse, exception *Exception, err error) {
	formValues := initFormValues(to, body, mediaUrl, statusCallback, applicationSid)
	formValues.Set("MessagingServiceSid", messagingServiceSid)

	smsResponse, exception, err = twilio.sendMessage(ctx, formValues)
	return
}

// Core method to send message
func (twilio *Twilio) sendMessage(ctx context.Context, formValues url.Values) (smsResponse *SmsResponse, exception *Exception, err error) {
	twilioUrl := twilio.BaseUrl + "/Accounts/" + twilio.AccountSid + "/Messages.json"

	res, err := twilio.post(ctx, formValues, twilioUrl)
	if err != nil {
		return smsResponse, exception, err
	}
	defer res.Body.Close()

	responseBody, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return smsResponse, exception, err
	}

	if res.StatusCode != http.StatusCreated {
		exception = new(Exception)
		err = json.Unmarshal(responseBody, exception)

		// We aren't checking the error because we don't actually care.
		// It's going to be passed to the client either way.
		return smsResponse, exception, err
	}

	smsResponse = new(SmsResponse)
	err = json.Unmarshal(responseBody, smsResponse)
	return smsResponse, exception, err
}

// Form values initialization
func initFormValues(to, body string, mediaUrl []string, statusCallback, applicationSid string) url.Values {
	formValues := url.Values{}

	formValues.Set("To", to)
	formValues.Set("Body", body)

	if len(mediaUrl) > 0 {
		for _, value := range mediaUrl {
			formValues.Add("MediaUrl", value)
		}
	}

	if statusCallback != "" {
		formValues.Set("StatusCallback", statusCallback)
	}

	if applicationSid != "" {
		formValues.Set("ApplicationSid", applicationSid)
	}

	return formValues
}
<|endoftext|>


<commit_before>package perpetua

import (
	"database/sql"
	"log"

	_ "github.com/mattn/go-sqlite3"
)

type Store struct {
	db *sql.DB
}

func (s *Store) open(database string) {
	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	s.db = db
}

func (s *Store) close() {
	s.db.Close()
}

func (s *Store) createDatabase() {

	// TODO: create all tables in one variable when multiple statements will
	// be supported see https://github.com/mattn/go-sqlite3/issues/60
	sql_people_table := `CREATE TABLE people (
	id INTEGER NOT NULL PRIMARY KEY autoincrement,
	name TEXT
);
	`
	sql_quotes_table := `CREATE TABLE quotes (
	person_id INTEGER NOT NULL,
	quote TEXT,
	FOREIGN KEY(person_id) REFERENCES people(id)
);
	`
	s.db.Exec(sql_people_table)
	s.db.Exec(sql_quotes_table)
}
<commit_msg>Added a method to get a random quote<commit_after>package perpetua

import (
	"database/sql"
	"log"

	_ "github.com/mattn/go-sqlite3"
)

type Store struct {
	db *sql.DB
}

func (s *Store) open(database string) {
	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	s.db = db
}

func (s *Store) close() {
	s.db.Close()
}

func (s *Store) createDatabase() {

	// TODO: create all tables in one variable when multiple statements will
	// be supported see https://github.com/mattn/go-sqlite3/issues/60
	sql_people_table := `CREATE TABLE people (
	id INTEGER NOT NULL PRIMARY KEY autoincrement,
	name TEXT
);
	`
	sql_quotes_table := `CREATE TABLE quotes (
	person_id INTEGER NOT NULL,
	quote TEXT,
	FOREIGN KEY(person_id) REFERENCES people(id)
);
	`
	s.db.Exec(sql_people_table)
	s.db.Exec(sql_quotes_table)
}

func (s *Store) getQuote(person string) (quote string) {
	var q string
	query := `SELECT quote FROM quotes WHERE person_id =
		(SELECT id FROM people WHERE name = ?) ORDER BY RANDOM() LIMIT 1;`
	s.db.QueryRow(query, person).Scan(&q)
	return q
}
<|endoftext|>


<commit_before><commit_msg>Send messages to remote node (not working)<commit_after><|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"sort"
	"time"

	"github.com/BurntSushi/xgb"
	"github.com/BurntSushi/xgb/screensaver"
	"github.com/BurntSushi/xgb/xproto"
)

const (
	port    = ":8001"
	file    = "dump.gob"
	unknown = "unknown"
)

var (
	tracks = make(Tracker)
	tmpl   = template.Must(template.ParseFiles("index.html"))
	zzz    bool
)

type Tracker map[Window]*Track

type Track struct {
	Seen  time.Time
	Spent time.Duration
}

type Window struct {
	Class string
	Name  string
}

type Xorg struct {
	X           *xgb.Conn
	root        xproto.Window
	activeAtom  *xproto.InternAtomReply
	netNameAtom *xproto.InternAtomReply
	nameAtom    *xproto.InternAtomReply
	classAtom   *xproto.InternAtomReply
}

func (t Track) String() string {
	return fmt.Sprint(t.Spent)
}

func (w Window) String() string {
	return fmt.Sprintf("%s %s", w.Class, w.Name)
}

func (x Xorg) atom(aname string) *xproto.InternAtomReply {
	a, err := xproto.InternAtom(x.X, true, uint16(len(aname)), aname).Reply()
	if err != nil {
		log.Fatal("atom: ", err)
	}
	return a
}

func (x Xorg) property(w xproto.Window, a *xproto.InternAtomReply) (*xproto.GetPropertyReply, error) {
	return xproto.GetProperty(x.X, false, w, a.Atom,
		xproto.GetPropertyTypeAny, 0, (1<<32)-1).Reply()
}

func (x Xorg) active() xproto.Window {
	p, err := x.property(x.root, x.activeAtom)
	if err != nil {
		return x.root
	}
	return xproto.Window(xgb.Get32(p.Value))
}

func (x Xorg) name(w xproto.Window) string {
	name, err := x.property(w, x.netNameAtom)
	if err != nil {
		return unknown
	}
	if string(name.Value) == "" {
		name, err = x.property(w, x.nameAtom)
		if err != nil || string(name.Value) == "" {
			return unknown
		}
	}
	return string(name.Value)
}

func (x Xorg) class(w xproto.Window) string {
	class, err := x.property(w, x.classAtom)
	if err != nil {
		return unknown
	}
	i := bytes.IndexByte(class.Value, 0)
	if i == -1 || string(class.Value[:i]) == "" {
		return unknown
	}
	return string(class.Value[:i])
}

func (x Xorg) winName() (Window, bool) {
	windowId := x.active()
	/* skip invalid window id */
	if windowId == 0 {
		return Window{}, false
	}
	x.spy(windowId)
	return Window{
		Class: x.class(windowId),
		Name:  x.name(windowId),
	}, true
}

func (x Xorg) spy(w xproto.Window) {
	xproto.ChangeWindowAttributes(x.X, w, xproto.CwEventMask,
		[]uint32{xproto.EventMaskPropertyChange})
}

func (x Xorg) update(t Tracker) (prev *Track) {
	if win, ok := x.winName(); ok {
		if _, ok := t[win]; !ok {
			t[win] = new(Track)
		}
		t[win].Seen = time.Now()
		prev = t[win]
	}
	return
}

func connect() Xorg {
	var x Xorg
	var err error

	x.X, err = xgb.NewConn()
	if err != nil {
		log.Fatal("xgb: ", err)
	}

	err = screensaver.Init(x.X)
	if err != nil {
		log.Fatal("screensaver: ", err)
	}

	setup := xproto.Setup(x.X)
	x.root = setup.DefaultScreen(x.X).Root

	drw := xproto.Drawable(x.root)
	screensaver.SelectInput(x.X, drw, screensaver.EventNotifyMask)

	x.activeAtom = x.atom("_NET_ACTIVE_WINDOW")
	x.netNameAtom = x.atom("_NET_WM_NAME")
	x.nameAtom = x.atom("WM_NAME")
	x.classAtom = x.atom("WM_CLASS")

	x.spy(x.root)

	return x
}

func (t Tracker) collect() {
	x := connect()
	defer x.X.Close()

	prev := x.update(t)
	for {
		ev, everr := x.X.WaitForEvent()
		if everr != nil {
			log.Println("wait for event:", everr)
			continue
		}
		switch event := ev.(type) {
		case xproto.PropertyNotifyEvent:
			if prev != nil {
				prev.Spent += time.Since(prev.Seen)
			}
			prev = x.update(t)
		case screensaver.NotifyEvent:
			switch event.State {
			case screensaver.StateOn:
				fmt.Println("away from keyboard")
				prev = nil
				zzz = true
			default:
				fmt.Println("back to keyboard")
				zzz = false
			}
		}
	}
}

func (t Tracker) cleanup(d time.Duration) {
	for k, v := range t {
		if time.Since(v.Seen) > d {
			log.Println("removing", k)
			delete(t, k)
		}
	}
}

func (t Tracker) load(fname string) {
	dump, err := os.Open(fname)
	if err != nil {
		log.Println(err)
		return
	}
	defer dump.Close()
	dec := gob.NewDecoder(dump)
	err = dec.Decode(&t)
	if err != nil {
		log.Println(err)
	}
}

func (t Tracker) store(fname string) {
	dump, err := os.Create(fname)
	if err != nil {
		log.Println(err)
		return
	}
	defer dump.Close()
	enc := gob.NewEncoder(dump)
	err = enc.Encode(t)
	if err != nil {
		log.Println(err)
	}
}

type Index struct {
	Title   string
	Records Records
	Classes Records
	Total   time.Duration
	Zzz     bool
}

type Records []Record

type Record struct {
	Class    string
	Name     string
	Spent    time.Duration
	LastSeen time.Time
	Odd      bool `json:"-"`
}

func (r Records) Len() int           { return len(r) }
func (r Records) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
func (r Records) Less(i, j int) bool { return r[i].Spent < r[j].Spent }

func indexHandler(w http.ResponseWriter, r *http.Request) {
	var idx Index
	idx.Title = "Gone Time Tracker"
	idx.Zzz = zzz
	class := r.URL.Path[1:]

	classtotal := make(map[string]time.Duration)

	for k, v := range tracks {
		classtotal[k.Class] += v.Spent
		idx.Total += v.Spent
		if class != "" && class != k.Class {
			continue
		}
		idx.Records = append(idx.Records, Record{
			Class: k.Class,
			Name:  k.Name,
			Spent: v.Spent})
	}
	for k, v := range classtotal {
		idx.Classes = append(idx.Classes, Record{Class: k, Spent: v})
	}
	sort.Sort(sort.Reverse(idx.Classes))
	sort.Sort(sort.Reverse(idx.Records))
	for j := range idx.Records {
		idx.Records[j].Odd = j%2 == 0
	}
	err := tmpl.Execute(w, idx)
	if err != nil {
		log.Println(err)
	}
}

func dumpHandler(w http.ResponseWriter, r *http.Request) {
	var rec Records

	for k, v := range tracks {
		rec = append(rec, Record{
			Class: k.Class,
			Name:  k.Name,
			Spent: v.Spent,
			Seen:  v.Seen})
	}

	data, err := json.MarshalIndent(rec, "", "\t")
	if err != nil {
		log.Println("dump:", err)
	}
	w.Write(data)
}

func main() {
	tracks.load(file)
	go tracks.collect()
	go func() {
		for {
			tracks.cleanup(8 * time.Hour)
			tracks.store(file)
			time.Sleep(time.Minute)
		}
	}()
	log.Println("listen on", port)
	http.HandleFunc("/", indexHandler)
	http.HandleFunc("/dump.json", dumpHandler)
	http.ListenAndServe(port, nil)
}
<commit_msg>rename LastSeen to just Seen<commit_after>package main

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"sort"
	"time"

	"github.com/BurntSushi/xgb"
	"github.com/BurntSushi/xgb/screensaver"
	"github.com/BurntSushi/xgb/xproto"
)

const (
	port    = ":8001"
	file    = "dump.gob"
	unknown = "unknown"
)

var (
	tracks = make(Tracker)
	tmpl   = template.Must(template.ParseFiles("index.html"))
	zzz    bool
)

type Tracker map[Window]*Track

type Track struct {
	Seen  time.Time
	Spent time.Duration
}

type Window struct {
	Class string
	Name  string
}

type Xorg struct {
	X           *xgb.Conn
	root        xproto.Window
	activeAtom  *xproto.InternAtomReply
	netNameAtom *xproto.InternAtomReply
	nameAtom    *xproto.InternAtomReply
	classAtom   *xproto.InternAtomReply
}

func (t Track) String() string {
	return fmt.Sprint(t.Spent)
}

func (w Window) String() string {
	return fmt.Sprintf("%s %s", w.Class, w.Name)
}

func (x Xorg) atom(aname string) *xproto.InternAtomReply {
	a, err := xproto.InternAtom(x.X, true, uint16(len(aname)), aname).Reply()
	if err != nil {
		log.Fatal("atom: ", err)
	}
	return a
}

func (x Xorg) property(w xproto.Window, a *xproto.InternAtomReply) (*xproto.GetPropertyReply, error) {
	return xproto.GetProperty(x.X, false, w, a.Atom,
		xproto.GetPropertyTypeAny, 0, (1<<32)-1).Reply()
}

func (x Xorg) active() xproto.Window {
	p, err := x.property(x.root, x.activeAtom)
	if err != nil {
		return x.root
	}
	return xproto.Window(xgb.Get32(p.Value))
}

func (x Xorg) name(w xproto.Window) string {
	name, err := x.property(w, x.netNameAtom)
	if err != nil {
		return unknown
	}
	if string(name.Value) == "" {
		name, err = x.property(w, x.nameAtom)
		if err != nil || string(name.Value) == "" {
			return unknown
		}
	}
	return string(name.Value)
}

func (x Xorg) class(w xproto.Window) string {
	class, err := x.property(w, x.classAtom)
	if err != nil {
		return unknown
	}
	i := bytes.IndexByte(class.Value, 0)
	if i == -1 || string(class.Value[:i]) == "" {
		return unknown
	}
	return string(class.Value[:i])
}

func (x Xorg) winName() (Window, bool) {
	windowId := x.active()
	/* skip invalid window id */
	if windowId == 0 {
		return Window{}, false
	}
	x.spy(windowId)
	return Window{
		Class: x.class(windowId),
		Name:  x.name(windowId),
	}, true
}

func (x Xorg) spy(w xproto.Window) {
	xproto.ChangeWindowAttributes(x.X, w, xproto.CwEventMask,
		[]uint32{xproto.EventMaskPropertyChange})
}

func (x Xorg) update(t Tracker) (prev *Track) {
	if win, ok := x.winName(); ok {
		if _, ok := t[win]; !ok {
			t[win] = new(Track)
		}
		t[win].Seen = time.Now()
		prev = t[win]
	}
	return
}

func connect() Xorg {
	var x Xorg
	var err error

	x.X, err = xgb.NewConn()
	if err != nil {
		log.Fatal("xgb: ", err)
	}

	err = screensaver.Init(x.X)
	if err != nil {
		log.Fatal("screensaver: ", err)
	}

	setup := xproto.Setup(x.X)
	x.root = setup.DefaultScreen(x.X).Root

	drw := xproto.Drawable(x.root)
	screensaver.SelectInput(x.X, drw, screensaver.EventNotifyMask)

	x.activeAtom = x.atom("_NET_ACTIVE_WINDOW")
	x.netNameAtom = x.atom("_NET_WM_NAME")
	x.nameAtom = x.atom("WM_NAME")
	x.classAtom = x.atom("WM_CLASS")

	x.spy(x.root)

	return x
}

func (t Tracker) collect() {
	x := connect()
	defer x.X.Close()

	prev := x.update(t)
	for {
		ev, everr := x.X.WaitForEvent()
		if everr != nil {
			log.Println("wait for event:", everr)
			continue
		}
		switch event := ev.(type) {
		case xproto.PropertyNotifyEvent:
			if prev != nil {
				prev.Spent += time.Since(prev.Seen)
			}
			prev = x.update(t)
		case screensaver.NotifyEvent:
			switch event.State {
			case screensaver.StateOn:
				fmt.Println("away from keyboard")
				prev = nil
				zzz = true
			default:
				fmt.Println("back to keyboard")
				zzz = false
			}
		}
	}
}

func (t Tracker) cleanup(d time.Duration) {
	for k, v := range t {
		if time.Since(v.Seen) > d {
			log.Println("removing", k)
			delete(t, k)
		}
	}
}

func (t Tracker) load(fname string) {
	dump, err := os.Open(fname)
	if err != nil {
		log.Println(err)
		return
	}
	defer dump.Close()
	dec := gob.NewDecoder(dump)
	err = dec.Decode(&t)
	if err != nil {
		log.Println(err)
	}
}

func (t Tracker) store(fname string) {
	dump, err := os.Create(fname)
	if err != nil {
		log.Println(err)
		return
	}
	defer dump.Close()
	enc := gob.NewEncoder(dump)
	err = enc.Encode(t)
	if err != nil {
		log.Println(err)
	}
}

type Index struct {
	Title   string
	Records Records
	Classes Records
	Total   time.Duration
	Zzz     bool
}

type Records []Record

type Record struct {
	Class string
	Name  string
	Spent time.Duration
	Seen  time.Time
	Odd   bool `json:"-"`
}

func (r Records) Len() int           { return len(r) }
func (r Records) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }
func (r Records) Less(i, j int) bool { return r[i].Spent < r[j].Spent }

func indexHandler(w http.ResponseWriter, r *http.Request) {
	var idx Index
	idx.Title = "Gone Time Tracker"
	idx.Zzz = zzz
	class := r.URL.Path[1:]

	classtotal := make(map[string]time.Duration)

	for k, v := range tracks {
		classtotal[k.Class] += v.Spent
		idx.Total += v.Spent
		if class != "" && class != k.Class {
			continue
		}
		idx.Records = append(idx.Records, Record{
			Class: k.Class,
			Name:  k.Name,
			Spent: v.Spent})
	}
	for k, v := range classtotal {
		idx.Classes = append(idx.Classes, Record{Class: k, Spent: v})
	}
	sort.Sort(sort.Reverse(idx.Classes))
	sort.Sort(sort.Reverse(idx.Records))
	for j := range idx.Records {
		idx.Records[j].Odd = j%2 == 0
	}
	err := tmpl.Execute(w, idx)
	if err != nil {
		log.Println(err)
	}
}

func dumpHandler(w http.ResponseWriter, r *http.Request) {
	var rec Records

	for k, v := range tracks {
		rec = append(rec, Record{
			Class: k.Class,
			Name:  k.Name,
			Spent: v.Spent,
			Seen:  v.Seen})
	}

	data, err := json.MarshalIndent(rec, "", "\t")
	if err != nil {
		log.Println("dump:", err)
	}
	w.Write(data)
}

func main() {
	tracks.load(file)
	go tracks.collect()
	go func() {
		for {
			tracks.cleanup(8 * time.Hour)
			tracks.store(file)
			time.Sleep(time.Minute)
		}
	}()
	log.Println("listen on", port)
	http.HandleFunc("/", indexHandler)
	http.HandleFunc("/dump.json", dumpHandler)
	http.ListenAndServe(port, nil)
}
<|endoftext|>


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

// Program gops is a tool to list currently running Go processes.
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"

	"hello/gops/agent/signal"
	"hello/gops/internal/objfile"

	ps "github.com/keybase/go-ps"
)

const helpText = `Usage: gops is a tool to list and diagnose Go processes.
    gops              Lists all Go processes currently running.
    gops [options...] See the section below.

Options: (All requires the agent and the -p=<pid> flag.)
    -stack    Prints the stack trace.
    -gc       Runs the garbage collector and blocks until successful.
    -gcstats  Prints the garbage collection stats.
    -version  Prints the Go version used to build the program.
`

var (
	pid     = flag.Int("p", -1, "")
	stack   = flag.Bool("stack", false, "")
	gc      = flag.Bool("gc", false, "")
	gcstats = flag.Bool("gcstats", false, "")
	version = flag.Bool("version", false, "")
	help    = flag.Bool("help", false, "")
)

func main() {
	flag.Usage = usage
	flag.Parse()

	if len(os.Args) < 2 {
		goProcesses()
		return
	}
	if *pid == -1 || *help {
		usage()
	}
	if *stack {
		out, err := cmd(signal.Stack)
		exit(err)
		fmt.Println(out)
	}
	if *gc {
		_, err := cmd(signal.GC)
		exit(err)
	}
	if *gcstats {
		out, err := cmd(signal.GCStats)
		exit(err)
		fmt.Printf(out)
	}
	if *version {
		out, err := cmd(signal.Version)
		exit(err)
		fmt.Printf(out)
	}
}

func cmd(c byte) (string, error) {
	sock := fmt.Sprintf("/tmp/gops%d.sock", *pid)
	conn, err := net.Dial("unix", sock)
	if err != nil {
		return "", err
	}
	if _, err := conn.Write([]byte{c}); err != nil {
		return "", err
	}
	all, err := ioutil.ReadAll(conn)
	if err != nil {
		return "", err
	}
	return string(all), nil
}

func goProcesses() {
	pss, err := ps.Processes()
	if err != nil {
		log.Fatal(err)
	}
	var undetermined int
	for _, pr := range pss {
		name, err := pr.Path()
		if err != nil {
			undetermined++
			continue
		}
		ok, err := isGo(name)
		if err != nil {
			// TODO(jbd): worth to report the number?
			continue
		}
		if ok {
			fmt.Printf("%d\t%v\t(%v)\n", pr.Pid(), pr.Executable(), name)
		}
	}
	if undetermined > 0 {
		fmt.Printf("\n%d processes left undetermined\n", undetermined)
	}
}

func isGo(filename string) (ok bool, err error) {
	obj, err := objfile.Open(filename)
	if err != nil {
		return false, err
	}
	defer obj.Close()

	symbols, err := obj.Symbols()
	if err != nil {
		return false, err
	}

	// TODO(jbd): find a faster way to determine Go programs
	// looping over the symbols is a joke.
	for _, s := range symbols {
		if s.Name == "runtime.buildVersion" {
			return true, nil
		}
	}
	return false, nil
}

func usage() {
	fmt.Fprintf(os.Stderr, "%v\n", helpText)
	os.Exit(1)
}

func exit(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}
<commit_msg>reword the help text<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Program gops is a tool to list currently running Go processes.
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"

	"hello/gops/agent/signal"
	"hello/gops/internal/objfile"

	ps "github.com/keybase/go-ps"
)

const helpText = `Usage: gops is a tool to list and diagnose Go processes.

    gops              Lists all Go processes currently running.
    gops [options...] See the section below.

Options:
    -stack    Prints the stack trace.
    -gc       Runs the garbage collector and blocks until successful.
    -gcstats  Prints the garbage collection stats.
    -version  Prints the Go version used to build the program.

All options require the agent and the -p=<pid> flag.
`

// TODO(jbd): add link that explains the use of agent.

var (
	pid     = flag.Int("p", -1, "")
	stack   = flag.Bool("stack", false, "")
	gc      = flag.Bool("gc", false, "")
	gcstats = flag.Bool("gcstats", false, "")
	version = flag.Bool("version", false, "")
	help    = flag.Bool("help", false, "")
)

func main() {
	flag.Usage = usage
	flag.Parse()

	if len(os.Args) < 2 {
		goProcesses()
		return
	}
	if *pid == -1 || *help {
		usage()
	}
	if *stack {
		out, err := cmd(signal.Stack)
		exit(err)
		fmt.Println(out)
	}
	if *gc {
		_, err := cmd(signal.GC)
		exit(err)
	}
	if *gcstats {
		out, err := cmd(signal.GCStats)
		exit(err)
		fmt.Printf(out)
	}
	if *version {
		out, err := cmd(signal.Version)
		exit(err)
		fmt.Printf(out)
	}
}

func cmd(c byte) (string, error) {
	sock := fmt.Sprintf("/tmp/gops%d.sock", *pid)
	conn, err := net.Dial("unix", sock)
	if err != nil {
		return "", err
	}
	if _, err := conn.Write([]byte{c}); err != nil {
		return "", err
	}
	all, err := ioutil.ReadAll(conn)
	if err != nil {
		return "", err
	}
	return string(all), nil
}

func goProcesses() {
	pss, err := ps.Processes()
	if err != nil {
		log.Fatal(err)
	}
	var undetermined int
	for _, pr := range pss {
		name, err := pr.Path()
		if err != nil {
			undetermined++
			continue
		}
		ok, err := isGo(name)
		if err != nil {
			// TODO(jbd): worth to report the number?
			continue
		}
		if ok {
			fmt.Printf("%d\t%v\t(%v)\n", pr.Pid(), pr.Executable(), name)
		}
	}
	if undetermined > 0 {
		fmt.Printf("\n%d processes left undetermined\n", undetermined)
	}
}

func isGo(filename string) (ok bool, err error) {
	obj, err := objfile.Open(filename)
	if err != nil {
		return false, err
	}
	defer obj.Close()

	symbols, err := obj.Symbols()
	if err != nil {
		return false, err
	}

	// TODO(jbd): find a faster way to determine Go programs
	// looping over the symbols is a joke.
	for _, s := range symbols {
		if s.Name == "runtime.buildVersion" {
			return true, nil
		}
	}
	return false, nil
}

func usage() {
	fmt.Fprintf(os.Stderr, "%v\n", helpText)
	os.Exit(1)
}

func exit(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/xml" // gelbooru parsing
	"errors"
	"fmt"
	"github.com/jcline/goty"
	"html"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"time"
)

var user = ""

var matchGelbooru = regexp.MustCompile(`.*\Qhttp://gelbooru.com/index.php?page=post&s=view&id=\E([\d]+).*`)
var matchYouTube = regexp.MustCompile(`.*(https?://(?:www\.|)youtu(?:\.be|be\.com)/[^ ]+).*`)
var matchAmiAmi = regexp.MustCompile(`(https?://(?:www\.|)amiami.com/[^/]+/detail/.*)`)

func auth(con *goty.IRCConn, writeMessage chan IRCMessage) {
	var pswd string
	_, err := fmt.Scanf("%s", &pswd)
	if err != nil {
		os.Exit(1)
	}

	msg := IRCMessage{channel: "NickServ", msg: "IDENTIFY Laala " + pswd}
	writeMessage <- msg
}

func main() {
	args := os.Args
	if len(args) < 4 {
		os.Exit(1)
	}

	con, err := goty.Dial(args[1], args[2], args[3])
	user = args[2]
	if err != nil {
		fmt.Fprintf(os.Stderr, "err: %s\n", err.Error())
	}

	gelbooruEvent := make(chan unparsedMessage, 1000)
	youtubeEvent := make(chan unparsedMessage, 1000)
	amiAmiEvent := make(chan unparsedMessage, 1000)
	bastilleEvent := make(chan unparsedMessage, 1000)
	writeMessage := make(chan IRCMessage, 1000)

	go messageHandler(con, writeMessage)
	go gelbooru(gelbooruEvent, writeMessage)
	go youtube(youtubeEvent, writeMessage)
	go amiami(amiAmiEvent, writeMessage)
	go bastille(bastilleEvent, writeMessage)

	auth(con, writeMessage)
	con.Write <- "JOIN " + args[4]

	for msg := range con.Read {
		prepared := unparsedMessage{msg, time.Now()}
		fmt.Printf("%s||%s\n", prepared.when, prepared.msg)

		switch {
		case matchGelbooru.MatchString(msg):
			//gelbooruEvent <- matchGelbooru.FindAllStringSubmatch(msg, -1)[0][1]
		case matchYouTube.MatchString(msg):
			youtubeEvent <- prepared
		case matchAmiAmi.MatchString(msg):
			amiAmiEvent <- prepared
		default:
		}
	}
	con.Close()
}

type unparsedMessage struct {
	msg  string
	when time.Time
}

type IRCMessage struct {
	channel string
	msg     string
	user    string
}

func message(con *goty.IRCConn, msg IRCMessage) {
	con.Write <- "PRIVMSG " + msg.channel + " :" + msg.msg + "\r\n"
}

func messageHandler(con *goty.IRCConn, event chan IRCMessage) {
	allBooks := map[string]time.Time{}
	chanBooks := map[string]time.Time{}
	for msg := range event {
		now := time.Now()
		key := msg.channel + ":" + msg.user
		if now.Sub(allBooks[key]) < time.Second*10 || now.Sub(chanBooks[key]) < time.Second*2 {
			continue
		}
		allBooks[key] = now
		chanBooks[key] = now
		message(con, msg)
	}
}

var PRIVMSG = regexp.MustCompile(`:(.+)![^ ]+ PRIVMSG ([^ ]+) :(.*)`)

func getMsgInfo(msg string) (*IRCMessage, error) {
	// :nick!~realname@0.0.0.0 PRIVMSG #chan :msg
	imsg := new(IRCMessage)
	match := PRIVMSG.FindAllStringSubmatch(msg, -1)
	if len(match) < 1 {
		return imsg, errors.New("could not parse message")
	}
	if len(match[0]) < 3 {
		return imsg, errors.New("could not parse message")
	}
	imsg.user = user
	imsg.channel = match[0][2]
	if imsg.channel == user {
		imsg.channel = match[0][1]
	}
	imsg.msg = match[0][3]
	return imsg, nil
}

func bastille(event chan unparsedMessage, writeMessage chan IRCMessage) {
	msgs := []string{
		"Bastille, yo brodudedudebro!!!!1",
		"Bastille, wat up homie",
		"Bastille, word",
		"Bastille, duuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuude",
		"'sup Bastille?",
	}

	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			continue
		}
		writeMessage <- IRCMessage{parsed.channel, msgs[rand.Intn(len(msgs))-1], parsed.user}
	}
}

type uriFunc func(*string) (*string, error)
type writeFunc func(*IRCMessage, *string) error

func scrapeAndSend(event chan unparsedMessage, findUri uriFunc, write writeFunc) {
	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			continue
		}

		uri, err := findUri(&parsed.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}
		resp, err := http.Get(*uri)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		bodyBytes, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}
		body := string(bodyBytes)

		if write(parsed, &body) != nil {
			continue
		}
	}
}

func getFirstMatch(re *regexp.Regexp, matchee *string) (*string, error) {
	match := re.FindAllStringSubmatch(*matchee, -1)
	if len(match) < 1 {
		return nil, errors.New("Could not match")
	}
	if len(match[0]) < 2 {
		return nil, errors.New("Could not match")
	}
	return &match[0][1], nil
}

func amiami(event chan unparsedMessage, writeMessage chan IRCMessage) {
	matchTitle := regexp.MustCompile(`.*<meta property="og:title" content="(.+)" />.*`)
	matchDiscount := regexp.MustCompile(`[0-9]+\%OFF `)
	scrapeAndSend(event, func(msg *string) (*string, error) { return getFirstMatch(matchAmiAmi, msg) },
		func(msg *IRCMessage, body *string) error {
			title, err := getFirstMatch(matchTitle, body)
			if err != nil {
				return err
			}

			writeMessage <- IRCMessage{msg.channel, matchDiscount.ReplaceAllLiteralString(*title, ""), msg.user}
			return nil
		})
}

func youtube(event chan unparsedMessage, writeMessage chan IRCMessage) {
	matchTitle := regexp.MustCompile(`.*<title>(.+)(?: - YouTube){1}</title>.*`)
	matchUser := regexp.MustCompile(`.*<a[^>]+class="[^"]+yt-user-name[^>]+>([^<]+)</a>.*`)

	scrapeAndSend(event, func(msg *string) (*string, error) { return getFirstMatch(matchYouTube, msg) },
		func(msg *IRCMessage, body *string) error {
			title, err := getFirstMatch(matchTitle, body)
			if err != nil {
				return err
			}
			user, err := getFirstMatch(matchUser, body)
			if err != nil {
				return err
			}
			writeMessage <- IRCMessage{msg.channel, html.UnescapeString(*title + " uploaded by " + *user), msg.user}
			return nil
		})
}

func gelbooru(event chan unparsedMessage, writeMessage chan IRCMessage) {
	type Post struct {
		post string
		tags string `xml:",attr"`
	}

	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			continue
		}

		resp, err := http.Get("http://gelbooru.com/index.php?page=dapi&s=post&q=index&tags&id=" + msg.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		body, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

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

		var result Post

		err = xml.Unmarshal(body, &result)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		fmt.Printf("%s\n", result.tags)
		writeMessage <- IRCMessage{parsed.channel, "tobedone", parsed.user}
	}
}
<commit_msg>Print more errors<commit_after>package main

import (
	"encoding/xml" // gelbooru parsing
	"errors"
	"fmt"
	"github.com/jcline/goty"
	"html"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"time"
)

var user = ""

var matchGelbooru = regexp.MustCompile(`.*\Qhttp://gelbooru.com/index.php?page=post&s=view&id=\E([\d]+).*`)
var matchYouTube = regexp.MustCompile(`.*(https?://(?:www\.|)youtu(?:\.be|be\.com)/[^ ]+).*`)
var matchAmiAmi = regexp.MustCompile(`(https?://(?:www\.|)amiami.com/[^/]+/detail/.*)`)

func auth(con *goty.IRCConn, writeMessage chan IRCMessage) {
	var pswd string
	_, err := fmt.Scanf("%s", &pswd)
	if err != nil {
		os.Exit(1)
	}

	msg := IRCMessage{channel: "NickServ", msg: "IDENTIFY Laala " + pswd}
	writeMessage <- msg
}

func main() {
	args := os.Args
	if len(args) < 4 {
		os.Exit(1)
	}

	con, err := goty.Dial(args[1], args[2], args[3])
	user = args[2]
	if err != nil {
		fmt.Fprintf(os.Stderr, "err: %s\n", err.Error())
	}

	gelbooruEvent := make(chan unparsedMessage, 1000)
	youtubeEvent := make(chan unparsedMessage, 1000)
	amiAmiEvent := make(chan unparsedMessage, 1000)
	bastilleEvent := make(chan unparsedMessage, 1000)
	writeMessage := make(chan IRCMessage, 1000)

	go messageHandler(con, writeMessage)
	go gelbooru(gelbooruEvent, writeMessage)
	go youtube(youtubeEvent, writeMessage)
	go amiami(amiAmiEvent, writeMessage)
	go bastille(bastilleEvent, writeMessage)

	auth(con, writeMessage)
	con.Write <- "JOIN " + args[4]

	for msg := range con.Read {
		prepared := unparsedMessage{msg, time.Now()}
		fmt.Printf("%s||%s\n", prepared.when, prepared.msg)

		switch {
		case matchGelbooru.MatchString(msg):
			//gelbooruEvent <- matchGelbooru.FindAllStringSubmatch(msg, -1)[0][1]
		case matchYouTube.MatchString(msg):
			youtubeEvent <- prepared
		case matchAmiAmi.MatchString(msg):
			amiAmiEvent <- prepared
		default:
		}
	}
	con.Close()
}

type unparsedMessage struct {
	msg  string
	when time.Time
}

type IRCMessage struct {
	channel string
	msg     string
	user    string
}

func message(con *goty.IRCConn, msg IRCMessage) {
	con.Write <- "PRIVMSG " + msg.channel + " :" + msg.msg + "\r\n"
}

func messageHandler(con *goty.IRCConn, event chan IRCMessage) {
	allBooks := map[string]time.Time{}
	chanBooks := map[string]time.Time{}
	for msg := range event {
		now := time.Now()
		key := msg.channel + ":" + msg.user
		if now.Sub(allBooks[key]) < time.Second*10 || now.Sub(chanBooks[key]) < time.Second*2 {
			continue
		}
		allBooks[key] = now
		chanBooks[key] = now
		message(con, msg)
	}
}

var PRIVMSG = regexp.MustCompile(`:(.+)![^ ]+ PRIVMSG ([^ ]+) :(.*)`)

func getMsgInfo(msg string) (*IRCMessage, error) {
	// :nick!~realname@0.0.0.0 PRIVMSG #chan :msg
	imsg := new(IRCMessage)
	match := PRIVMSG.FindAllStringSubmatch(msg, -1)
	if len(match) < 1 {
		return imsg, errors.New("could not parse message")
	}
	if len(match[0]) < 3 {
		return imsg, errors.New("could not parse message")
	}
	imsg.user = user
	imsg.channel = match[0][2]
	if imsg.channel == user {
		imsg.channel = match[0][1]
	}
	imsg.msg = match[0][3]
	return imsg, nil
}

func bastille(event chan unparsedMessage, writeMessage chan IRCMessage) {
	msgs := []string{
		"Bastille, yo brodudedudebro!!!!1",
		"Bastille, wat up homie",
		"Bastille, word",
		"Bastille, duuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuude",
		"'sup Bastille?",
	}

	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			continue
		}
		writeMessage <- IRCMessage{parsed.channel, msgs[rand.Intn(len(msgs))-1], parsed.user}
	}
}

type uriFunc func(*string) (*string, error)
type writeFunc func(*IRCMessage, *string) error

func scrapeAndSend(event chan unparsedMessage, findUri uriFunc, write writeFunc) {
	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		uri, err := findUri(&parsed.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}
		resp, err := http.Get(*uri)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		bodyBytes, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}
		body := string(bodyBytes)

		if write(parsed, &body) != nil {
			fmt.Printf("%v\n", err)
			continue
		}
	}
}

func getFirstMatch(re *regexp.Regexp, matchee *string) (*string, error) {
	match := re.FindAllStringSubmatch(*matchee, -1)
	if len(match) < 1 {
		return nil, errors.New("Could not match")
	}
	if len(match[0]) < 2 {
		return nil, errors.New("Could not match")
	}
	return &match[0][1], nil
}

func amiami(event chan unparsedMessage, writeMessage chan IRCMessage) {
	matchTitle := regexp.MustCompile(`.*<meta property="og:title" content="(.+)" />.*`)
	matchDiscount := regexp.MustCompile(`[0-9]+\%OFF `)
	scrapeAndSend(event, func(msg *string) (*string, error) { return getFirstMatch(matchAmiAmi, msg) },
		func(msg *IRCMessage, body *string) error {
			title, err := getFirstMatch(matchTitle, body)
			if err != nil {
				return err
			}

			writeMessage <- IRCMessage{msg.channel, matchDiscount.ReplaceAllLiteralString(*title, ""), msg.user}
			return nil
		})
}

func youtube(event chan unparsedMessage, writeMessage chan IRCMessage) {
	matchTitle := regexp.MustCompile(`.*<title>(.+)(?: - YouTube){1}</title>.*`)
	matchUser := regexp.MustCompile(`.*<a[^>]+class="[^"]+yt-user-name[^>]+>([^<]+)</a>.*`)

	scrapeAndSend(event, func(msg *string) (*string, error) { return getFirstMatch(matchYouTube, msg) },
		func(msg *IRCMessage, body *string) error {
			title, err := getFirstMatch(matchTitle, body)
			if err != nil {
				return err
			}
			user, err := getFirstMatch(matchUser, body)
			if err != nil {
				return err
			}
			writeMessage <- IRCMessage{msg.channel, html.UnescapeString(*title + " uploaded by " + *user), msg.user}
			return nil
		})
}

func gelbooru(event chan unparsedMessage, writeMessage chan IRCMessage) {
	type Post struct {
		post string
		tags string `xml:",attr"`
	}

	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			continue
		}

		resp, err := http.Get("http://gelbooru.com/index.php?page=dapi&s=post&q=index&tags&id=" + msg.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		body, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

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

		var result Post

		err = xml.Unmarshal(body, &result)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		fmt.Printf("%s\n", result.tags)
		writeMessage <- IRCMessage{parsed.channel, "tobedone", parsed.user}
	}
}
<|endoftext|>


<commit_before>// Copyright 2012 Jesse van den Kieboom. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package flags

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"strings"
	"unicode/utf8"
)

type alignmentInfo struct {
	maxLongLen      int
	hasShort        bool
	hasValueName    bool
	terminalColumns int
}

func (p *Parser) getAlignmentInfo() alignmentInfo {
	ret := alignmentInfo{
		maxLongLen:      0,
		hasShort:        false,
		hasValueName:    false,
		terminalColumns: getTerminalColumns(),
	}

	if ret.terminalColumns <= 0 {
		ret.terminalColumns = 80
	}

	p.EachGroup(func(index int, grp *Group) {
		for _, info := range grp.Options {
			if info.ShortName != 0 {
				ret.hasShort = true
			}

			lv := utf8.RuneCountInString(info.ValueName)

			if lv != 0 {
				ret.hasValueName = true
			}

			l := utf8.RuneCountInString(info.LongName) + lv

			if l > ret.maxLongLen {
				ret.maxLongLen = l
			}
		}
	})

	return ret
}

func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alignmentInfo) {
	line := &bytes.Buffer{}

	distanceBetweenOptionAndDescription := 2
	paddingBeforeOption := 2

	line.WriteString(strings.Repeat(" ", paddingBeforeOption))

	if option.ShortName != 0 {
		line.WriteString("-")
		line.WriteRune(option.ShortName)
	} else if info.hasShort {
		line.WriteString("  ")
	}

	descstart := info.maxLongLen + paddingBeforeOption + distanceBetweenOptionAndDescription

	if info.hasShort {
		descstart += 2
	}

	if info.maxLongLen > 0 {
		descstart += 4
	}

	if info.hasValueName {
		descstart += 3
	}

	if len(option.LongName) > 0 {
		if option.ShortName != 0 {
			line.WriteString(", ")
		} else if info.hasShort {
			line.WriteString("  ")
		}

		line.WriteString("--")
		line.WriteString(option.LongName)
	}

	if !option.isBool() {
		line.WriteString("=")

		if len(option.ValueName) > 0 {
			line.WriteString(option.ValueName)
		}
	}

	written := line.Len()
	line.WriteTo(writer)

	if option.Description != "" {
		dw := descstart - written
		writer.WriteString(strings.Repeat(" ", dw))

		def := option.Default

		if def == "" && !option.isBool() {
			def = convertToString(option.Value, option.Field.Tag)
		}

		var desc string

		if def != "" {
			desc = fmt.Sprintf("%s (%v)", option.Description, def)
		} else {
			desc = option.Description
		}

		writer.WriteString(wrapText(desc,
			info.terminalColumns-descstart,
			strings.Repeat(" ", descstart)))
	}

	writer.WriteString("\n")
}

// WriteHelp writes a help message containing all the possible options and
// their descriptions to the provided writer. Note that the HelpFlag parser
// option provides a convenient way to add a -h/--help option group to the
// command line parser which will automatically show the help messages using
// this method.
func (p *Parser) WriteHelp(writer io.Writer) {
	if writer == nil {
		return
	}

	wr := bufio.NewWriter(writer)

	if p.ApplicationName != "" {
		wr.WriteString("Usage:\n")
		fmt.Fprintf(wr, "  %s", p.ApplicationName)

		if p.Usage != "" {
			fmt.Fprintf(wr, " %s", p.Usage)
		}

		if len(p.currentCommandString) > 0 {
			fmt.Fprintf(wr, " %s [%s-OPTIONS]",
				strings.Join(p.currentCommandString, " "),
				p.currentCommandString[len(p.currentCommandString)-1])
		}

		fmt.Fprintln(wr)

		if p.currentCommand != nil && len(p.currentCommand.LongDescription) != 0 {
			fmt.Fprintln(wr)
			fmt.Fprintln(wr, p.currentCommand.LongDescription)
		}
	}

	aligninfo := p.getAlignmentInfo()

	seen := make(map[*Group]bool)

	writeHelp := func(index int, grp *Group) {
		if len(grp.Options) == 0 || seen[grp] {
			return
		}

		seen[grp] = true

		wr.WriteString("\n")

		fmt.Fprintf(wr, "%s:\n", grp.Name)

		for _, info := range grp.Options {
			p.writeHelpOption(wr, info, aligninfo)
		}
	}

	// If there is a command, still write all the toplevel help too
	if p.currentCommand != nil {
		p.eachTopLevelGroup(writeHelp)
	}

	p.EachGroup(writeHelp)

	commander := p.currentCommander()
	names := commander.sortedNames()

	if len(names) > 0 {
		maxnamelen := len(names[0])

		for i := 1; i < len(names); i++ {
			l := len(names[i])

			if l > maxnamelen {
				maxnamelen = l
			}
		}

		fmt.Fprintln(wr)
		fmt.Fprintln(wr, "Available commands:")

		for _, name := range names {
			fmt.Fprintf(wr, "  %s", name)

			cmd := commander.Commands[name]

			if len(cmd.Name) > 0 {
				pad := strings.Repeat(" ", maxnamelen-len(name))
				fmt.Fprintf(wr, "%s  %s", pad, cmd.Name)
			}

			fmt.Fprintln(wr)
		}
	}

	wr.Flush()
}
<commit_msg>Do not show default value in help if it is the zero value<commit_after>// Copyright 2012 Jesse van den Kieboom. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package flags

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"reflect"
	"strings"
	"unicode/utf8"
)

type alignmentInfo struct {
	maxLongLen      int
	hasShort        bool
	hasValueName    bool
	terminalColumns int
}

func (p *Parser) getAlignmentInfo() alignmentInfo {
	ret := alignmentInfo{
		maxLongLen:      0,
		hasShort:        false,
		hasValueName:    false,
		terminalColumns: getTerminalColumns(),
	}

	if ret.terminalColumns <= 0 {
		ret.terminalColumns = 80
	}

	p.EachGroup(func(index int, grp *Group) {
		for _, info := range grp.Options {
			if info.ShortName != 0 {
				ret.hasShort = true
			}

			lv := utf8.RuneCountInString(info.ValueName)

			if lv != 0 {
				ret.hasValueName = true
			}

			l := utf8.RuneCountInString(info.LongName) + lv

			if l > ret.maxLongLen {
				ret.maxLongLen = l
			}
		}
	})

	return ret
}

func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alignmentInfo) {
	line := &bytes.Buffer{}

	distanceBetweenOptionAndDescription := 2
	paddingBeforeOption := 2

	line.WriteString(strings.Repeat(" ", paddingBeforeOption))

	if option.ShortName != 0 {
		line.WriteString("-")
		line.WriteRune(option.ShortName)
	} else if info.hasShort {
		line.WriteString("  ")
	}

	descstart := info.maxLongLen + paddingBeforeOption + distanceBetweenOptionAndDescription

	if info.hasShort {
		descstart += 2
	}

	if info.maxLongLen > 0 {
		descstart += 4
	}

	if info.hasValueName {
		descstart += 3
	}

	if len(option.LongName) > 0 {
		if option.ShortName != 0 {
			line.WriteString(", ")
		} else if info.hasShort {
			line.WriteString("  ")
		}

		line.WriteString("--")
		line.WriteString(option.LongName)
	}

	if !option.isBool() {
		line.WriteString("=")

		if len(option.ValueName) > 0 {
			line.WriteString(option.ValueName)
		}
	}

	written := line.Len()
	line.WriteTo(writer)

	if option.Description != "" {
		dw := descstart - written
		writer.WriteString(strings.Repeat(" ", dw))

		def := option.Default

		if def == "" && !option.isBool() {
			var showdef bool

			switch option.Field.Type.Kind() {
			case reflect.Func, reflect.Ptr:
				showdef = !option.Value.IsNil()
			case reflect.Slice, reflect.String, reflect.Array:
				showdef = option.Value.Len() > 0
			case reflect.Map:
				showdef = !option.Value.IsNil() && option.Value.Len() > 0
			default:
				zeroval := reflect.Zero(option.Field.Type)
				showdef = !reflect.DeepEqual(zeroval.Interface(), option.Value.Interface())
			}

			if showdef {
				def = convertToString(option.Value, option.Field.Tag)
			}
		}

		var desc string

		if def != "" {
			desc = fmt.Sprintf("%s (%v)", option.Description, def)
		} else {
			desc = option.Description
		}

		writer.WriteString(wrapText(desc,
			info.terminalColumns-descstart,
			strings.Repeat(" ", descstart)))
	}

	writer.WriteString("\n")
}

// WriteHelp writes a help message containing all the possible options and
// their descriptions to the provided writer. Note that the HelpFlag parser
// option provides a convenient way to add a -h/--help option group to the
// command line parser which will automatically show the help messages using
// this method.
func (p *Parser) WriteHelp(writer io.Writer) {
	if writer == nil {
		return
	}

	wr := bufio.NewWriter(writer)

	if p.ApplicationName != "" {
		wr.WriteString("Usage:\n")
		fmt.Fprintf(wr, "  %s", p.ApplicationName)

		if p.Usage != "" {
			fmt.Fprintf(wr, " %s", p.Usage)
		}

		if len(p.currentCommandString) > 0 {
			fmt.Fprintf(wr, " %s [%s-OPTIONS]",
				strings.Join(p.currentCommandString, " "),
				p.currentCommandString[len(p.currentCommandString)-1])
		}

		fmt.Fprintln(wr)

		if p.currentCommand != nil && len(p.currentCommand.LongDescription) != 0 {
			fmt.Fprintln(wr)
			fmt.Fprintln(wr, p.currentCommand.LongDescription)
		}
	}

	aligninfo := p.getAlignmentInfo()

	seen := make(map[*Group]bool)

	writeHelp := func(index int, grp *Group) {
		if len(grp.Options) == 0 || seen[grp] {
			return
		}

		seen[grp] = true

		wr.WriteString("\n")

		fmt.Fprintf(wr, "%s:\n", grp.Name)

		for _, info := range grp.Options {
			p.writeHelpOption(wr, info, aligninfo)
		}
	}

	// If there is a command, still write all the toplevel help too
	if p.currentCommand != nil {
		p.eachTopLevelGroup(writeHelp)
	}

	p.EachGroup(writeHelp)

	commander := p.currentCommander()
	names := commander.sortedNames()

	if len(names) > 0 {
		maxnamelen := len(names[0])

		for i := 1; i < len(names); i++ {
			l := len(names[i])

			if l > maxnamelen {
				maxnamelen = l
			}
		}

		fmt.Fprintln(wr)
		fmt.Fprintln(wr, "Available commands:")

		for _, name := range names {
			fmt.Fprintf(wr, "  %s", name)

			cmd := commander.Commands[name]

			if len(cmd.Name) > 0 {
				pad := strings.Repeat(" ", maxnamelen-len(name))
				fmt.Fprintf(wr, "%s  %s", pad, cmd.Name)
			}

			fmt.Fprintln(wr)
		}
	}

	wr.Flush()
}
<|endoftext|>


<commit_before>package knuckles

import (
	"fmt"
	"github.com/fiorix/go-redis/redis"
	"io"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
)

type HTTPProxy struct {
	mtx         sync.RWMutex
	redisClient *redis.Client
	HostMap     map[string]*Frontend
	Frontends   []*Frontend
	quitChan    chan bool
	Server      http.Server
	Settings    HTTPProxySettings
	HttpStatus  http.Server
	status      *HTTPStats
	running     bool
}

type HTTPProxySettings struct {
	DiscEndpoint           []string
	DiscKeyspace           string
	Endpoint               string
	StatusEndpoint         string
	StatusPrefix           string
	CheckInterval          time.Duration
	SSL                    bool
	RedirectOnHostnameMiss string
	RedirectOnBackendMiss  string
	RedirectOnError        string
}

func NewHTTPProxy(settings HTTPProxySettings) (*HTTPProxy, error) {
	var err error

	proxy := new(HTTPProxy)
	proxy.HostMap = make(map[string]*Frontend)
	proxy.Frontends = make([]*Frontend, 0)
	proxy.Settings = settings
	proxy.Server.Addr = settings.Endpoint
	mux := http.NewServeMux()
	mux.Handle("/", proxy)
	proxy.Server.Handler = mux

	proxy.status, err = NewHTTPStats(settings.StatusEndpoint, settings.StatusPrefix)
	if err != nil {
		return nil, err
	}

	proxy.redisClient = redis.New(settings.DiscEndpoint...)

	proxy.quitChan = make(chan bool)

	ok := proxy.Reload()
	if !ok {
		return nil, fmt.Errorf("Failed to load initial configuration from discovery engine")
	}
	return proxy, nil
}

func (self *HTTPProxy) Start() {
	self.running = true

	ch := make(chan redis.PubSubMessage)
	stop := make(chan bool)

	go self.Server.ListenAndServe()
	go self.status.Start()

	go self.Watch(ch, stop)

	for self.running {
		select {
		case <-self.quitChan:
			self.running = false
			stop <- true
		case resp := <-ch:
			// only reload on legitimate notifications
			if resp.Error == nil {
				self.Reload()
			}
		}
	}
	self.quitChan <- true
}

// also step locked to main thread
func (self *HTTPProxy) Watch(callerCh chan redis.PubSubMessage, stop chan bool) {
	ch := make(chan redis.PubSubMessage)
	channel := fmt.Sprintf("%s:reload", self.Settings.DiscKeyspace)

	for self.running {
		err := self.redisClient.Subscribe(channel, ch, stop)

		if err != nil {
			log.Println("Discovery error ", err)
			time.Sleep(1 * time.Second)
			continue
		}

		for {
			resp := <-ch
			if resp.Error != nil {
				log.Println(resp.Error)
				log.Println("Discovery notification error ", err)
				break
			} else {
				callerCh <- resp
			}
		}

	}
}

func (self *HTTPProxy) Stop() {
	self.quitChan <- true
	<-self.quitChan
	for _, f := range self.HostMap {
		f.Stop()
	}
	self.status.Stop()
	close(self.quitChan)
}

func (self *HTTPProxy) keySpaced(key string) string {
	return fmt.Sprintf("%s:%s", self.Settings.DiscKeyspace, key)
}

func (self *HTTPProxy) discGet(key string) (string, error) {
	return self.redisClient.Get(self.keySpaced(key))
}

func (self *HTTPProxy) discList(key string) ([]string, error) {
	return self.redisClient.SMembers(self.keySpaced(key))
}

func (self *HTTPProxy) discFind(key string) ([]string, error) {
	return self.redisClient.Keys(fmt.Sprintf("%s:*", self.keySpaced(key)))
}

func (self *HTTPProxy) loadHosts(appKey string, frontend *Frontend, newHostMap map[string]*Frontend) error {
	hostList, err := self.discList(fmt.Sprintf("%s:hostnames", appKey))
	if err != nil || len(hostList) < 1 {
		return fmt.Errorf("No hostnames")
	}

	for _, hostEntry := range hostList {
		hostKey := lastSep(hostEntry)
		_, present := newHostMap[hostKey]
		if present {
			log.Println("Ignoring duplicated hostname ", hostKey)
			continue
		}
		newHostMap[hostKey] = frontend
	}
	return nil
}

func (self *HTTPProxy) loadBackends(appKey string, frontend *Frontend) error {
	backendList, err := self.discFind(fmt.Sprintf("%s:backends", appKey))
	if err != nil || len(backendList) < 1 {
		return fmt.Errorf("No backends")
	}

	for _, backendEntry := range backendList {
		beKey := lastSep(backendEntry)
		endpoint, err := self.discGet(fmt.Sprintf("%s:backends:%s", appKey, beKey))
		if err != nil {
			log.Println("Skipping invalid backend ", beKey)
			continue
		}

		settings := BackendSettings{
			Endpoint:      endpoint,
			CheckInterval: self.Settings.CheckInterval,
			Updates:       frontend.NotifyChan,
			CheckUrl:      fmt.Sprintf("http://%s", endpoint),
		}
		backend, err := NewBackend(beKey, settings)
		if err != nil {
			log.Println("Skipping invalid backend ", beKey)
			continue
		}
		frontend.AddBackend(backend)
	}
	return nil
}

func (self *HTTPProxy) Reload() bool {
	// not optimal, but still decent
	// traverse disc structure
	// lock/unlock proxy for least amount of time
	log.Println("Config loading")
	appList, err := self.discList("applications")
	if err != nil {
		log.Println("Could not load application list")
		return false
	}

	newHostMap := make(map[string]*Frontend)
	newFrontends := make([]*Frontend, 0)

	for _, app := range appList {
		appKey := lastSep(app)
		log.Println("Loading app ", appKey)

		frontend := NewFrontend(appKey)

		err = self.loadHosts(appKey, frontend, newHostMap)
		if err != nil {
			log.Println(err)
			continue
		}

		err = self.loadBackends(appKey, frontend)
		if err != nil {
			log.Println(err)
			continue
		}

		newFrontends = append(newFrontends, frontend)
	}

	for _, fr := range newFrontends {
		log.Println("Starting new frontend ", fr.Name)
		go fr.Start()
	}

	log.Println("Replacing configuration")
	self.mtx.Lock()
	oldFrontends := self.Frontends
	self.Frontends = newFrontends
	self.HostMap = newHostMap
	self.mtx.Unlock()

	log.Println("Stopping old frontends")
	for _, frontend := range oldFrontends {
		log.Println("Stopping old frontend ", frontend.Name)
		frontend.Stop()
	}

	log.Println("Load Complete")
	self.status.Increment(MetricReload)

	return true
}

func (self *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	self.mtx.RLock()
	defer self.mtx.RUnlock()

	self.status.Increment(MetricRequest)

	hostname := r.Host

	if sep := strings.Index(hostname, ":"); sep >= 0 {
		hostname = hostname[:sep]
	}

	frontend, ok := self.HostMap[hostname]
	if !ok {
		self.status.Increment(MetricNoHostname)
		http.Redirect(w, r, self.Settings.RedirectOnHostnameMiss, http.StatusTemporaryRedirect)
		return
	}

	backend, err := frontend.PickBackend()

	if err != nil {
		self.status.Increment(MetricNoBackend)
		http.Redirect(w, r, self.Settings.RedirectOnBackendMiss, http.StatusTemporaryRedirect)
		return
	}

	r.Header.Set("X-Request-Start", requestStart())

	proto := "http"
	if self.Settings.SSL {
		proto = "https"
		//stunnel already adds X-Forwarded-For
	} else {
		r.Header.Set("X-Forwarded-For", r.RemoteAddr)
	}

	r.Header.Set("X-Forwarded-Proto", proto)

	r.URL.Host = backend.Endpoint
	r.URL.Scheme = "http"

	connection := r.Header.Get("Connection")
	if strings.ToLower(connection) == "upgrade" {
		self.upgradeConnection(w, r)
	} else {
		respStatus := self.simpleProxy(w, r)
		self.status.IncrementBackend(frontend.Name, backend.Name, respStatus)
	}

}

func (self *HTTPProxy) simpleProxy(w http.ResponseWriter, r *http.Request) int {
	resp, err := http.DefaultTransport.RoundTrip(r)

	if err != nil {
		self.status.Increment(MetricError)
		http.Redirect(w, r, self.Settings.RedirectOnError, http.StatusTemporaryRedirect)
		log.Println(err)
		return http.StatusTemporaryRedirect
	}
	defer resp.Body.Close()

	for k, v := range resp.Header {
		for _, vv := range v {
			w.Header().Add(k, vv)
		}
	}

	w.WriteHeader(resp.StatusCode)
	io.Copy(w, resp.Body)
	return resp.StatusCode
}

func (self *HTTPProxy) upgradeConnection(w http.ResponseWriter, r *http.Request) {
	hj, ok := w.(http.Hijacker)

	if !ok {
		self.status.Increment(MetricError)
		http.Redirect(w, r, self.Settings.RedirectOnError, http.StatusTemporaryRedirect)
		return
	}

	client, _, err := hj.Hijack()
	if err != nil {
		self.status.Increment(MetricError)
		http.Redirect(w, r, self.Settings.RedirectOnError, http.StatusTemporaryRedirect)
		client.Close()
		return
	}

	server, err := net.Dial("tcp", r.URL.Host)
	if err != nil {
		self.status.Increment(MetricError)
		http.Redirect(w, r, self.Settings.RedirectOnError, http.StatusTemporaryRedirect)
		client.Close()
		return
	}

	err = r.Write(server)
	if err != nil {
		self.status.Increment(MetricError)
		log.Printf("writing WebSocket request to backend server failed: %v", err)
		server.Close()
		client.Close()
		return
	}

	// we spawn a goroutine so the caller can unlock (and reload)
	go passBytes(client, server)
}

func passBytes(client, server net.Conn) {
	pass := func(from, to net.Conn, done chan error) {
		//TODO check frame sizes
		//TODO half writes
		_, err := io.Copy(to, from)
		done <- err
	}

	done := make(chan error, 2)
	go pass(client, server, done)
	go pass(server, client, done)

	<-done
	//first to error kills both sides
	client.Close()
	server.Close()
	//wait for second side to exit
	<-done
	close(done)
}

func lastSep(k string) string {
	parts := strings.Split(k, ":")
	return parts[len(parts)-1]
}

func requestStart() string {
	return strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
}
<commit_msg>handle redis EOF in case redis-server has been configured with keepalive timeouts.<commit_after>package knuckles

import (
	"fmt"
	"github.com/fiorix/go-redis/redis"
	"io"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
)

type HTTPProxy struct {
	mtx         sync.RWMutex
	redisClient *redis.Client
	HostMap     map[string]*Frontend
	Frontends   []*Frontend
	quitChan    chan bool
	Server      http.Server
	Settings    HTTPProxySettings
	HttpStatus  http.Server
	status      *HTTPStats
	running     bool
}

type HTTPProxySettings struct {
	DiscEndpoint           []string
	DiscKeyspace           string
	Endpoint               string
	StatusEndpoint         string
	StatusPrefix           string
	CheckInterval          time.Duration
	SSL                    bool
	RedirectOnHostnameMiss string
	RedirectOnBackendMiss  string
	RedirectOnError        string
}

func NewHTTPProxy(settings HTTPProxySettings) (*HTTPProxy, error) {
	var err error

	proxy := new(HTTPProxy)
	proxy.HostMap = make(map[string]*Frontend)
	proxy.Frontends = make([]*Frontend, 0)
	proxy.Settings = settings
	proxy.Server.Addr = settings.Endpoint
	mux := http.NewServeMux()
	mux.Handle("/", proxy)
	proxy.Server.Handler = mux

	proxy.status, err = NewHTTPStats(settings.StatusEndpoint, settings.StatusPrefix)
	if err != nil {
		return nil, err
	}

	proxy.redisClient = redis.New(settings.DiscEndpoint...)

	proxy.quitChan = make(chan bool)

	ok := proxy.Reload()
	if !ok {
		return nil, fmt.Errorf("Failed to load initial configuration from discovery engine")
	}
	return proxy, nil
}

func (self *HTTPProxy) Start() {
	self.running = true

	ch := make(chan redis.PubSubMessage)
	stop := make(chan bool)

	go self.Server.ListenAndServe()
	go self.status.Start()

	go self.Watch(ch, stop)

	for self.running {
		select {
		case <-self.quitChan:
			self.running = false
			stop <- true
		case resp := <-ch:
			// only reload on legitimate notifications
			if resp.Error == nil {
				self.Reload()
			}
		}
	}
	self.quitChan <- true
}

// also step locked to main thread
func (self *HTTPProxy) Watch(callerCh chan redis.PubSubMessage, stop chan bool) {
	ch := make(chan redis.PubSubMessage)
	channel := fmt.Sprintf("%s:reload", self.Settings.DiscKeyspace)

	for self.running {
		err := self.redisClient.Subscribe(channel, ch, stop)

		if err != nil {
			log.Println("Discovery error ", err)
			time.Sleep(1 * time.Second)
			continue
		}

		for {
			resp := <-ch
			if resp.Error != nil {
				log.Println(resp.Error)
				log.Println("Discovery notification error ", err)
				break
			} else {
				callerCh <- resp
			}
		}

	}
}

func (self *HTTPProxy) Stop() {
	self.quitChan <- true
	<-self.quitChan
	for _, f := range self.HostMap {
		f.Stop()
	}
	self.status.Stop()
	close(self.quitChan)
}

func (self *HTTPProxy) keySpaced(key string) string {
	return fmt.Sprintf("%s:%s", self.Settings.DiscKeyspace, key)
}

func (self *HTTPProxy) discGet(key string) (string, error) {
	return self.redisClient.Get(self.keySpaced(key))
}

func (self *HTTPProxy) discList(key string) ([]string, error) {
	return self.redisClient.SMembers(self.keySpaced(key))
}

func (self *HTTPProxy) discFind(key string) ([]string, error) {
	return self.redisClient.Keys(fmt.Sprintf("%s:*", self.keySpaced(key)))
}

func (self *HTTPProxy) loadHosts(appKey string, frontend *Frontend, newHostMap map[string]*Frontend) error {
	hostList, err := self.discList(fmt.Sprintf("%s:hostnames", appKey))
	if err != nil || len(hostList) < 1 {
		return fmt.Errorf("No hostnames")
	}

	for _, hostEntry := range hostList {
		hostKey := lastSep(hostEntry)
		_, present := newHostMap[hostKey]
		if present {
			log.Println("Ignoring duplicated hostname ", hostKey)
			continue
		}
		newHostMap[hostKey] = frontend
	}
	return nil
}

func (self *HTTPProxy) loadBackends(appKey string, frontend *Frontend) error {
	backendList, err := self.discFind(fmt.Sprintf("%s:backends", appKey))
	if err != nil || len(backendList) < 1 {
		return fmt.Errorf("No backends")
	}

	for _, backendEntry := range backendList {
		beKey := lastSep(backendEntry)
		endpoint, err := self.discGet(fmt.Sprintf("%s:backends:%s", appKey, beKey))
		if err != nil {
			log.Println("Skipping invalid backend ", beKey)
			continue
		}

		settings := BackendSettings{
			Endpoint:      endpoint,
			CheckInterval: self.Settings.CheckInterval,
			Updates:       frontend.NotifyChan,
			CheckUrl:      fmt.Sprintf("http://%s", endpoint),
		}
		backend, err := NewBackend(beKey, settings)
		if err != nil {
			log.Println("Skipping invalid backend ", beKey)
			continue
		}
		frontend.AddBackend(backend)
	}
	return nil
}

func (self *HTTPProxy) Reload() bool {
	// not optimal, but still decent
	// traverse disc structure
	// lock/unlock proxy for least amount of time
	log.Println("Config loading")
	var err error
	var appList []string

	for retries := 0; retries < 2; retries++ {
		appList, err = self.discList("applications")
		if err != nil {
			log.Println("Failed to load application list, retry count", retries)
		} else {
			break
		}
	}

	if err != nil {
		log.Println("Could not load application list:", err)
		return false
	}

	newHostMap := make(map[string]*Frontend)
	newFrontends := make([]*Frontend, 0)

	for _, app := range appList {
		appKey := lastSep(app)
		log.Println("Loading app ", appKey)

		frontend := NewFrontend(appKey)

		err = self.loadHosts(appKey, frontend, newHostMap)
		if err != nil {
			log.Println(err)
			continue
		}

		err = self.loadBackends(appKey, frontend)
		if err != nil {
			log.Println(err)
			continue
		}

		newFrontends = append(newFrontends, frontend)
	}

	for _, fr := range newFrontends {
		log.Println("Starting new frontend ", fr.Name)
		go fr.Start()
	}

	log.Println("Replacing configuration")
	self.mtx.Lock()
	oldFrontends := self.Frontends
	self.Frontends = newFrontends
	self.HostMap = newHostMap
	self.mtx.Unlock()

	log.Println("Stopping old frontends")
	for _, frontend := range oldFrontends {
		log.Println("Stopping old frontend ", frontend.Name)
		frontend.Stop()
	}

	log.Println("Load Complete")
	self.status.Increment(MetricReload)

	return true
}

func (self *HTTPProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	self.mtx.RLock()
	defer self.mtx.RUnlock()

	self.status.Increment(MetricRequest)

	hostname := r.Host

	if sep := strings.Index(hostname, ":"); sep >= 0 {
		hostname = hostname[:sep]
	}

	frontend, ok := self.HostMap[hostname]
	if !ok {
		self.status.Increment(MetricNoHostname)
		http.Redirect(w, r, self.Settings.RedirectOnHostnameMiss, http.StatusTemporaryRedirect)
		return
	}

	backend, err := frontend.PickBackend()

	if err != nil {
		self.status.Increment(MetricNoBackend)
		http.Redirect(w, r, self.Settings.RedirectOnBackendMiss, http.StatusTemporaryRedirect)
		return
	}

	r.Header.Set("X-Request-Start", requestStart())

	proto := "http"
	if self.Settings.SSL {
		proto = "https"
		//stunnel already adds X-Forwarded-For
	} else {
		r.Header.Set("X-Forwarded-For", r.RemoteAddr)
	}

	r.Header.Set("X-Forwarded-Proto", proto)

	r.URL.Host = backend.Endpoint
	r.URL.Scheme = "http"

	connection := r.Header.Get("Connection")
	if strings.ToLower(connection) == "upgrade" {
		self.upgradeConnection(w, r)
	} else {
		respStatus := self.simpleProxy(w, r)
		self.status.IncrementBackend(frontend.Name, backend.Name, respStatus)
	}

}

func (self *HTTPProxy) simpleProxy(w http.ResponseWriter, r *http.Request) int {
	resp, err := http.DefaultTransport.RoundTrip(r)

	if err != nil {
		self.status.Increment(MetricError)
		http.Redirect(w, r, self.Settings.RedirectOnError, http.StatusTemporaryRedirect)
		log.Println(err)
		return http.StatusTemporaryRedirect
	}
	defer resp.Body.Close()

	for k, v := range resp.Header {
		for _, vv := range v {
			w.Header().Add(k, vv)
		}
	}

	w.WriteHeader(resp.StatusCode)
	io.Copy(w, resp.Body)
	return resp.StatusCode
}

func (self *HTTPProxy) upgradeConnection(w http.ResponseWriter, r *http.Request) {
	hj, ok := w.(http.Hijacker)

	if !ok {
		self.status.Increment(MetricError)
		http.Redirect(w, r, self.Settings.RedirectOnError, http.StatusTemporaryRedirect)
		return
	}

	client, _, err := hj.Hijack()
	if err != nil {
		self.status.Increment(MetricError)
		http.Redirect(w, r, self.Settings.RedirectOnError, http.StatusTemporaryRedirect)
		client.Close()
		return
	}

	server, err := net.Dial("tcp", r.URL.Host)
	if err != nil {
		self.status.Increment(MetricError)
		http.Redirect(w, r, self.Settings.RedirectOnError, http.StatusTemporaryRedirect)
		client.Close()
		return
	}

	err = r.Write(server)
	if err != nil {
		self.status.Increment(MetricError)
		log.Printf("writing WebSocket request to backend server failed: %v", err)
		server.Close()
		client.Close()
		return
	}

	// we spawn a goroutine so the caller can unlock (and reload)
	go passBytes(client, server)
}

func passBytes(client, server net.Conn) {
	pass := func(from, to net.Conn, done chan error) {
		//TODO check frame sizes
		//TODO half writes
		_, err := io.Copy(to, from)
		done <- err
	}

	done := make(chan error, 2)
	go pass(client, server, done)
	go pass(server, client, done)

	<-done
	//first to error kills both sides
	client.Close()
	server.Close()
	//wait for second side to exit
	<-done
	close(done)
}

func lastSep(k string) string {
	parts := strings.Split(k, ":")
	return parts[len(parts)-1]
}

func requestStart() string {
	return strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
}
<|endoftext|>


<commit_before>package dry

import (
	"compress/flate"
	"compress/gzip"
	"io"
	"net/http"
	"strings"
)

type wrappedResponseWriter struct {
	io.Writer
	http.ResponseWriter
}

func (wrapped wrappedResponseWriter) Write(data []byte) (int, error) {
	return wrapped.Writer.Write(data)
}

// HTTPCompressHandlerFunc wraps a http.HandlerFunc so that the response gets
// gzip or deflate compressed if the Accept-Encoding header of the request allows it.
func HTTPCompressHandlerFunc(handlerFunc http.HandlerFunc) http.HandlerFunc {
	return func(response http.ResponseWriter, request *http.Request) {
		accept := request.Header.Get("Accept-Encoding")
		if strings.Contains(accept, "gzip") {
			response.Header().Set("Content-Encoding", "gzip")
			writer := gzip.NewWriter(response)
			defer writer.Close()
			response = wrappedResponseWriter{Writer: writer, ResponseWriter: response}
		} else if strings.Contains(accept, "deflate") {
			response.Header().Set("Content-Encoding", "deflate")
			writer, _ := flate.NewWriter(response, flate.BestCompression)
			defer writer.Close()
			response = wrappedResponseWriter{Writer: writer, ResponseWriter: response}
		}
		handlerFunc(response, request)
	}
}

func HTTPDelete(url string) (statusCode int, statusText string, err error) {
	request, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return 0, "", err
	}
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		return 0, "", err
	}
	return response.StatusCode, response.Status, nil
}
<commit_msg>added HTTPRespondMarshalJSON, HTTPRespondMarshalXML<commit_after>package dry

import (
	"compress/flate"
	"compress/gzip"
	"encoding/json"
	"encoding/xml"
	"io"
	"net/http"
	"strings"
)

type wrappedResponseWriter struct {
	io.Writer
	http.ResponseWriter
}

func (wrapped wrappedResponseWriter) Write(data []byte) (int, error) {
	return wrapped.Writer.Write(data)
}

// HTTPCompressHandlerFunc wraps a http.HandlerFunc so that the response gets
// gzip or deflate compressed if the Accept-Encoding header of the request allows it.
func HTTPCompressHandlerFunc(handlerFunc http.HandlerFunc) http.HandlerFunc {
	return func(response http.ResponseWriter, request *http.Request) {
		accept := request.Header.Get("Accept-Encoding")
		if strings.Contains(accept, "gzip") {
			response.Header().Set("Content-Encoding", "gzip")
			writer := gzip.NewWriter(response)
			defer writer.Close()
			response = wrappedResponseWriter{Writer: writer, ResponseWriter: response}
		} else if strings.Contains(accept, "deflate") {
			response.Header().Set("Content-Encoding", "deflate")
			writer, _ := flate.NewWriter(response, flate.BestCompression)
			defer writer.Close()
			response = wrappedResponseWriter{Writer: writer, ResponseWriter: response}
		}
		handlerFunc(response, request)
	}
}

func HTTPDelete(url string) (statusCode int, statusText string, err error) {
	request, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return 0, "", err
	}
	response, err := http.DefaultClient.Do(request)
	if err != nil {
		return 0, "", err
	}
	return response.StatusCode, response.Status, nil
}

// HTTPRespondMarshalJSON marshals response as JSON to responseWriter, sets Content-Type to application/json
// and compresses the response if Content-Encoding from the request allows it.
func HTTPRespondMarshalJSON(response interface{}, responseWriter http.ResponseWriter, request *http.Request) (err error) {
	handlerFunc := HTTPCompressHandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
		var data []byte
		if data, err = json.Marshal(response); err == nil {
			responseWriter.Header().Set("Content-Type", "application/json")
			_, err = responseWriter.Write(data)
		}
	})
	handlerFunc(responseWriter, request)
	return err
}

// HTTPRespondMarshalIndentJSON marshals response as JSON to responseWriter, sets Content-Type to application/json
// and compresses the response if Content-Encoding from the request allows it.
// The JSON will be marshalled indented according to json.MarshalIndent
func HTTPRespondMarshalIndentJSON(response interface{}, prefix, indent string, responseWriter http.ResponseWriter, request *http.Request) (err error) {
	handlerFunc := HTTPCompressHandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
		var data []byte
		if data, err = json.MarshalIndent(response, prefix, indent); err == nil {
			responseWriter.Header().Set("Content-Type", "application/json")
			_, err = responseWriter.Write(data)
		}
	})
	handlerFunc(responseWriter, request)
	return err
}

// HTTPRespondMarshalXML marshals response as XML to responseWriter, sets Content-Type to application/xml
// and compresses the response if Content-Encoding from the request allows it.
func HTTPRespondMarshalXML(response interface{}, responseWriter http.ResponseWriter, request *http.Request) (err error) {
	handlerFunc := HTTPCompressHandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
		var data []byte
		if data, err = xml.Marshal(response); err == nil {
			responseWriter.Header().Set("Content-Type", "application/xml")
			_, err = responseWriter.Write(data)
		}
	})
	handlerFunc(responseWriter, request)
	return err
}

// HTTPRespondMarshalIndentXML marshals response as XML to responseWriter, sets Content-Type to application/xml
// and compresses the response if Content-Encoding from the request allows it.
// The XML will be marshalled indented according to xml.MarshalIndent
func HTTPRespondMarshalIndentXML(response interface{}, prefix, indent string, responseWriter http.ResponseWriter, request *http.Request) (err error) {
	handlerFunc := HTTPCompressHandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) {
		var data []byte
		if data, err = xml.MarshalIndent(response, prefix, indent); err == nil {
			responseWriter.Header().Set("Content-Type", "application/xml")
			_, err = responseWriter.Write(data)
		}
	})
	handlerFunc(responseWriter, request)
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/jzacsh/netwtcpip-cmp405/parseip4"
)

var hosts = []parseip4.Addr{
	{IP: parseip4.NewAddr(9, 201, 195, 84), Mask: parseip4.NewAddr(255, 255, 240, 0)},
	{IP: parseip4.NewAddr(128, 10, 189, 215), Mask: parseip4.NewAddr(255, 255, 248, 0)},
	{IP: parseip4.NewAddr(135, 21, 243, 82), Mask: parseip4.NewAddr(255, 255, 224, 0)},
	{IP: parseip4.NewAddr(75, 149, 205, 61), Mask: parseip4.NewAddr(255, 255, 192, 0)},
	{IP: parseip4.NewAddr(7, 105, 198, 111), Mask: parseip4.NewAddr(255, 255, 252, 0)},

	// TODO(zacsh) remove this sample from the last slide
	{IP: parseip4.NewAddr(128, 10, 211, 78), Mask: parseip4.NewAddr(255, 255, 240, 0)},
}

func main() {
	fmt.Printf("analyzing %d hosts ...\n", len(hosts))
	for _, addr := range hosts {
		classMask, _, klass := parseip4.Classful(addr.IP)

		fmt.Printf(
			"  network: %v (class %s masked)\n\t%v\n\tnetwork id:\t%d\n\t subnet id:\t%d\n\t   host id:\t%d\n\n",
			(addr.IP.Pack() & classMask.Pack()).List(), klass,
			addr.String(),
			addr.NetworkIndex(),
			addr.SubnetIndex(),
			addr.HostIndex())
	}
}
<commit_msg>noop(refactor) rename to allow for first-half<commit_after>package main

import (
	"fmt"

	"github.com/jzacsh/netwtcpip-cmp405/parseip4"
)

var partTwoHosts = []parseip4.Addr{
	{IP: parseip4.NewAddr(9, 201, 195, 84), Mask: parseip4.NewAddr(255, 255, 240, 0)},
	{IP: parseip4.NewAddr(128, 10, 189, 215), Mask: parseip4.NewAddr(255, 255, 248, 0)},
	{IP: parseip4.NewAddr(135, 21, 243, 82), Mask: parseip4.NewAddr(255, 255, 224, 0)},
	{IP: parseip4.NewAddr(75, 149, 205, 61), Mask: parseip4.NewAddr(255, 255, 192, 0)},
	{IP: parseip4.NewAddr(7, 105, 198, 111), Mask: parseip4.NewAddr(255, 255, 252, 0)},

	// TODO(zacsh) remove this sample from the last slide
	{IP: parseip4.NewAddr(128, 10, 211, 78), Mask: parseip4.NewAddr(255, 255, 240, 0)},
}

func main() {
	fmt.Printf("\n\npart 2: analyzing %d hosts ...\n", len(partTwoHosts))
	for _, addr := range partTwoHosts {
		classMask, _, klass := parseip4.Classful(addr.IP)

		fmt.Printf(
			"  network: %v (class %s masked)\n\t%v\n\tnetwork id:\t%d\n\t subnet id:\t%d\n\t   host id:\t%d\n",
			(addr.IP.Pack() & classMask.Pack()).List(), klass,
			addr.String(),
			addr.NetworkIndex(),
			addr.SubnetIndex(),
			addr.HostIndex())
	}
}
<|endoftext|>