<commit_before>package wal

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"
)

type WriteOptions struct {
	// The maximum size in bytes of each segment. When it reaches near this size,
	// a new segment will be created.
	SegmentSize int64

	// The maximum number of segments to keep on disk.
	MaxSegments int

	// If 0, sync is done after every write. Otherwise this controls
	// how often the WAL is sync'd to disk. Setting this can speed
	// up the WAL by sacrifing safety.
	SyncRate time.Duration
}

const MaxSegmentSize = 16 * (1024 * 1024)

// Defaults to using 160MB of disk
var DefaultWriteOptions = WriteOptions{
	SegmentSize: MaxSegmentSize,
	MaxSegments: 10,
}

// Calculate the WriteOptions based on how much disk space the WAL
// should consume in total. The true on disk size might be more
// slightly more than this because the value is calculate against
// MaxSegmentSize, which is 16MB. If you wish to use a larger segment
// size (or more accurate one), then set SegmentSize and MaxSegments
// directly.
func (wo *WriteOptions) CalculateFromTotal(total int64) {
	if wo.MaxSegments == 0 {
		switch {
		case total < MaxSegmentSize:
			wo.MaxSegments = 1
			wo.SegmentSize = total
		default:
			wo.SegmentSize = MaxSegmentSize

			segments := total / wo.SegmentSize

			// Round up, not down.
			if total%wo.SegmentSize != 0 {
				segments++
			}

			wo.MaxSegments = int(segments)
		}
	} else {
		wo.SegmentSize = total / int64(wo.MaxSegments)
	}
}

type tagCache struct {
	Tags map[string]Position `json:"tags"`
}

type WALWriter struct {
	opts WriteOptions

	lock    sync.Mutex
	root    string
	current string

	first int
	index int

	segment *SegmentWriter

	cache     tagCache
	cacheFile *os.File
	cacheEnc  *json.Encoder
}

func rangeSegments(path string) (int, int, error) {
	f, err := os.Open(path)
	if err != nil {
		return 0, 0, err
	}

	defer f.Close()

	files, err := f.Readdirnames(-1)
	if err != nil {
		return 0, 0, err
	}

	var (
		first = -1
		last  = -1
	)

	for _, file := range files {
		i, err := strconv.Atoi(file)
		if err == nil {
			if first == -1 || i < first {
				first = i
			}

			if last == -1 || i > last {
				last = i
			}
		}
	}

	return first, last, nil
}

func New(root string) (*WALWriter, error) {
	return NewWithOptions(root, DefaultWriteOptions)
}

func NewWithOptions(root string, opts WriteOptions) (*WALWriter, error) {
	err := os.Mkdir(root, 0755)
	if err != nil {
		if !os.IsExist(err) {
			return nil, err
		}
	}

	first, last, err := rangeSegments(root)
	if err != nil {
		return nil, err
	}

	if last == -1 {
		last = 0
	}

	if first == -1 {
		first = 0
	}

	cache, err := os.Create(filepath.Join(root, "tags"))
	if err != nil {
		return nil, err
	}

	wal := &WALWriter{
		root:      root,
		current:   filepath.Join(root, fmt.Sprintf("%d", last)),
		first:     first,
		index:     last,
		opts:      opts,
		cacheFile: cache,
		cacheEnc:  json.NewEncoder(cache),
	}

	wal.cache.Tags = make(map[string]Position)

	seg, err := NewSegmentWriter(wal.current)
	if err != nil {
		return nil, err
	}

	wal.segment = seg

	if opts.SyncRate > 0 {
		seg.SetSyncRate(opts.SyncRate)
	}

	return wal, nil
}

func (wal *WALWriter) rotateSegment() error {
	err := wal.segment.Close()
	if err != nil {
		return err
	}

	wal.index++

	wal.current = filepath.Join(wal.root, fmt.Sprintf("%d", wal.index))

	seg, err := NewSegmentWriter(wal.current)
	if err != nil {
		return err
	}

	wal.segment = seg

	return nil
}

func (wal *WALWriter) pruneSegments(total int) error {
	startAt := wal.index - total

	for i := startAt; i >= wal.first; i-- {
		err := os.Remove(filepath.Join(wal.root, fmt.Sprintf("%d", i)))
		if err != nil {
			return err
		}
	}

	return nil
}

const averageOverhead = 4 + 1 + 2

func (wal *WALWriter) Write(data []byte) error {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	newSize := int64(len(data)) + averageOverhead + wal.segment.Size()

	if newSize > wal.opts.SegmentSize {
		err := wal.rotateSegment()
		if err != nil {
			return err
		}

		err = wal.pruneSegments(wal.opts.MaxSegments)
		if err != nil {
			return err
		}
	}

	_, err := wal.segment.Write(data)
	return err
}

type Position struct {
	Segment int   `json:"segment"`
	Offset  int64 `json:"offset"`
}

func (p *Position) None() bool {
	return p.Segment == -1
}

func (wal *WALWriter) Pos() (Position, error) {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	pos := wal.segment.Pos()

	return Position{wal.index, pos}, nil
}

func (wal *WALWriter) WriteTag(tag []byte) error {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	// We truncate the cache and rewrite it after the segment
	// has confirmed the tag so the cache is either absent
	// or correct, never present but out of date.
	truncErr := wal.cacheFile.Truncate(0)

	segPos := wal.segment.Pos()

	err := wal.segment.WriteTag(tag)
	if err != nil {
		return err
	}

	if truncErr == nil {
		key := base64.URLEncoding.EncodeToString(tag)
		wal.cache.Tags[key] = Position{wal.index, segPos}

		err = wal.cacheEnc.Encode(&wal.cache)
		if err == nil {
			wal.cacheFile.Sync()
		}
	}

	return nil
}

func (wal *WALWriter) Close() error {
	return wal.segment.Close()
}

type WALReader struct {
	root    string
	current string

	first int
	last  int
	index int

	seg *SegmentReader

	lastSegPos int64

	err error
}

var ErrNoSegments = errors.New("no segments")

func NewReader(root string) (*WALReader, error) {
	r := &WALReader{root: root}

	err := r.Reset()
	if err != nil {
		return nil, err
	}

	return r, nil
}

func (wal *WALReader) Reset() error {
	if wal.seg != nil {
		wal.seg.Close()
	}

	first, last, err := rangeSegments(wal.root)
	if err != nil {
		return err
	}

	if first == -1 {
		return ErrNoSegments
	}

	cur := filepath.Join(wal.root, fmt.Sprintf("%d", first))

	r, err := NewSegmentReader(cur)
	if err != nil {
		return err
	}

	wal.current = cur
	wal.first = first
	wal.last = last
	wal.index = first
	wal.seg = r

	return nil
}

func (wal *WALReader) Pos() (Position, error) {
	if wal.seg == nil {
		return Position{wal.index, wal.lastSegPos}, nil
	}

	return Position{wal.index, wal.seg.Pos()}, nil
}

func (wal *WALReader) Seek(p Position) error {
	path := filepath.Join(wal.root, fmt.Sprintf("%d", p.Segment))

	seg, err := NewSegmentReader(path)
	if err != nil {
		return err
	}

	err = seg.Seek(p.Offset)
	if err != nil {
		return err
	}

	wal.seg.Close()

	wal.index = p.Segment
	wal.seg = seg

	return nil
}

func (wal *WALReader) SeekTag(tag []byte) (Position, error) {
	lastPos := Position{-1, -1}

	index := wal.first

	for {
		path := filepath.Join(wal.root, fmt.Sprintf("%d", index))

		seg, err := NewSegmentReader(path)
		if err != nil {
			if os.IsNotExist(err) {
				return lastPos, nil
			}

			return lastPos, err
		}

		wal.seg = seg

		pos, err := seg.SeekTag(tag)
		if err != nil {
			return lastPos, err
		}

		if pos >= 0 {
			lastPos = Position{index, pos}
		}

		index++
	}

	return lastPos, nil
}

func (r *WALReader) Close() error {
	if r.seg == nil {
		return nil
	}

	return r.seg.Close()
}

func (r *WALReader) Next() bool {
	if r.seg.Next() {
		return true
	}

	r.lastSegPos = r.seg.Pos()
	idx := r.index

	for {
		idx++
		if idx > r.last {
			_, last, err := rangeSegments(r.root)
			if err != nil {
				r.err = err
				return false
			}
			r.last = last
			if idx > r.last {
				return false
			}
		}

		r.index = idx

		path := filepath.Join(r.root, fmt.Sprintf("%d", r.index))

		seg, err := NewSegmentReader(path)
		if err != nil {
			r.err = err
			return false
		}

		if seg.Next() {
			if r.seg != nil {
				r.seg.Close()
			}
			r.seg = seg
			break
		}
	}

	return true
}

func (r *WALReader) Value() []byte {
	if r.seg == nil {
		return nil
	}

	return r.seg.Value()
}

func (r *WALReader) Error() error {
	if r.err != nil {
		return r.err
	}

	if r.seg != nil {
		return r.seg.Error()
	}

	return nil
}
<commit_msg>Ignore not exist errors when removing segments<commit_after>package wal

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"time"
)

type WriteOptions struct {
	// The maximum size in bytes of each segment. When it reaches near this size,
	// a new segment will be created.
	SegmentSize int64

	// The maximum number of segments to keep on disk.
	MaxSegments int

	// If 0, sync is done after every write. Otherwise this controls
	// how often the WAL is sync'd to disk. Setting this can speed
	// up the WAL by sacrifing safety.
	SyncRate time.Duration
}

const MaxSegmentSize = 16 * (1024 * 1024)

// Defaults to using 160MB of disk
var DefaultWriteOptions = WriteOptions{
	SegmentSize: MaxSegmentSize,
	MaxSegments: 10,
}

// Calculate the WriteOptions based on how much disk space the WAL
// should consume in total. The true on disk size might be more
// slightly more than this because the value is calculate against
// MaxSegmentSize, which is 16MB. If you wish to use a larger segment
// size (or more accurate one), then set SegmentSize and MaxSegments
// directly.
func (wo *WriteOptions) CalculateFromTotal(total int64) {
	if wo.MaxSegments == 0 {
		switch {
		case total < MaxSegmentSize:
			wo.MaxSegments = 1
			wo.SegmentSize = total
		default:
			wo.SegmentSize = MaxSegmentSize

			segments := total / wo.SegmentSize

			// Round up, not down.
			if total%wo.SegmentSize != 0 {
				segments++
			}

			wo.MaxSegments = int(segments)
		}
	} else {
		wo.SegmentSize = total / int64(wo.MaxSegments)
	}
}

type tagCache struct {
	Tags map[string]Position `json:"tags"`
}

type WALWriter struct {
	opts WriteOptions

	lock    sync.Mutex
	root    string
	current string

	first int
	index int

	segment *SegmentWriter

	cache     tagCache
	cacheFile *os.File
	cacheEnc  *json.Encoder
}

func rangeSegments(path string) (int, int, error) {
	f, err := os.Open(path)
	if err != nil {
		return 0, 0, err
	}

	defer f.Close()

	files, err := f.Readdirnames(-1)
	if err != nil {
		return 0, 0, err
	}

	var (
		first = -1
		last  = -1
	)

	for _, file := range files {
		i, err := strconv.Atoi(file)
		if err == nil {
			if first == -1 || i < first {
				first = i
			}

			if last == -1 || i > last {
				last = i
			}
		}
	}

	return first, last, nil
}

func New(root string) (*WALWriter, error) {
	return NewWithOptions(root, DefaultWriteOptions)
}

func NewWithOptions(root string, opts WriteOptions) (*WALWriter, error) {
	err := os.Mkdir(root, 0755)
	if err != nil {
		if !os.IsExist(err) {
			return nil, err
		}
	}

	first, last, err := rangeSegments(root)
	if err != nil {
		return nil, err
	}

	if last == -1 {
		last = 0
	}

	if first == -1 {
		first = 0
	}

	cache, err := os.Create(filepath.Join(root, "tags"))
	if err != nil {
		return nil, err
	}

	wal := &WALWriter{
		root:      root,
		current:   filepath.Join(root, fmt.Sprintf("%d", last)),
		first:     first,
		index:     last,
		opts:      opts,
		cacheFile: cache,
		cacheEnc:  json.NewEncoder(cache),
	}

	wal.cache.Tags = make(map[string]Position)

	seg, err := NewSegmentWriter(wal.current)
	if err != nil {
		return nil, err
	}

	wal.segment = seg

	if opts.SyncRate > 0 {
		seg.SetSyncRate(opts.SyncRate)
	}

	return wal, nil
}

func (wal *WALWriter) rotateSegment() error {
	err := wal.segment.Close()
	if err != nil {
		return err
	}

	wal.index++

	wal.current = filepath.Join(wal.root, fmt.Sprintf("%d", wal.index))

	seg, err := NewSegmentWriter(wal.current)
	if err != nil {
		return err
	}

	wal.segment = seg

	return nil
}

func (wal *WALWriter) pruneSegments(total int) error {
	startAt := wal.index - total

	for i := startAt; i >= wal.first; i-- {
		err := os.Remove(filepath.Join(wal.root, fmt.Sprintf("%d", i)))
		if err != nil {
			if !os.IsNotExist(err) {
				return err
			}
		}
	}

	return nil
}

const averageOverhead = 4 + 1 + 2

func (wal *WALWriter) Write(data []byte) error {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	newSize := int64(len(data)) + averageOverhead + wal.segment.Size()

	if newSize > wal.opts.SegmentSize {
		err := wal.rotateSegment()
		if err != nil {
			return err
		}

		err = wal.pruneSegments(wal.opts.MaxSegments)
		if err != nil {
			return err
		}
	}

	_, err := wal.segment.Write(data)
	return err
}

type Position struct {
	Segment int   `json:"segment"`
	Offset  int64 `json:"offset"`
}

func (p *Position) None() bool {
	return p.Segment == -1
}

func (wal *WALWriter) Pos() (Position, error) {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	pos := wal.segment.Pos()

	return Position{wal.index, pos}, nil
}

func (wal *WALWriter) WriteTag(tag []byte) error {
	wal.lock.Lock()
	defer wal.lock.Unlock()

	// We truncate the cache and rewrite it after the segment
	// has confirmed the tag so the cache is either absent
	// or correct, never present but out of date.
	truncErr := wal.cacheFile.Truncate(0)

	segPos := wal.segment.Pos()

	err := wal.segment.WriteTag(tag)
	if err != nil {
		return err
	}

	if truncErr == nil {
		key := base64.URLEncoding.EncodeToString(tag)
		wal.cache.Tags[key] = Position{wal.index, segPos}

		err = wal.cacheEnc.Encode(&wal.cache)
		if err == nil {
			wal.cacheFile.Sync()
		}
	}

	return nil
}

func (wal *WALWriter) Close() error {
	return wal.segment.Close()
}

type WALReader struct {
	root    string
	current string

	first int
	last  int
	index int

	seg *SegmentReader

	lastSegPos int64

	err error
}

var ErrNoSegments = errors.New("no segments")

func NewReader(root string) (*WALReader, error) {
	r := &WALReader{root: root}

	err := r.Reset()
	if err != nil {
		return nil, err
	}

	return r, nil
}

func (wal *WALReader) Reset() error {
	if wal.seg != nil {
		wal.seg.Close()
	}

	first, last, err := rangeSegments(wal.root)
	if err != nil {
		return err
	}

	if first == -1 {
		return ErrNoSegments
	}

	cur := filepath.Join(wal.root, fmt.Sprintf("%d", first))

	r, err := NewSegmentReader(cur)
	if err != nil {
		return err
	}

	wal.current = cur
	wal.first = first
	wal.last = last
	wal.index = first
	wal.seg = r

	return nil
}

func (wal *WALReader) Pos() (Position, error) {
	if wal.seg == nil {
		return Position{wal.index, wal.lastSegPos}, nil
	}

	return Position{wal.index, wal.seg.Pos()}, nil
}

func (wal *WALReader) Seek(p Position) error {
	path := filepath.Join(wal.root, fmt.Sprintf("%d", p.Segment))

	seg, err := NewSegmentReader(path)
	if err != nil {
		return err
	}

	err = seg.Seek(p.Offset)
	if err != nil {
		return err
	}

	wal.seg.Close()

	wal.index = p.Segment
	wal.seg = seg

	return nil
}

func (wal *WALReader) SeekTag(tag []byte) (Position, error) {
	lastPos := Position{-1, -1}

	index := wal.first

	for {
		path := filepath.Join(wal.root, fmt.Sprintf("%d", index))

		seg, err := NewSegmentReader(path)
		if err != nil {
			if os.IsNotExist(err) {
				return lastPos, nil
			}

			return lastPos, err
		}

		wal.seg = seg

		pos, err := seg.SeekTag(tag)
		if err != nil {
			return lastPos, err
		}

		if pos >= 0 {
			lastPos = Position{index, pos}
		}

		index++
	}

	return lastPos, nil
}

func (r *WALReader) Close() error {
	if r.seg == nil {
		return nil
	}

	return r.seg.Close()
}

func (r *WALReader) Next() bool {
	if r.seg.Next() {
		return true
	}

	r.lastSegPos = r.seg.Pos()
	idx := r.index

	for {
		idx++
		if idx > r.last {
			_, last, err := rangeSegments(r.root)
			if err != nil {
				r.err = err
				return false
			}
			r.last = last
			if idx > r.last {
				return false
			}
		}

		r.index = idx

		path := filepath.Join(r.root, fmt.Sprintf("%d", r.index))

		seg, err := NewSegmentReader(path)
		if err != nil {
			r.err = err
			return false
		}

		if seg.Next() {
			if r.seg != nil {
				r.seg.Close()
			}
			r.seg = seg
			break
		}
	}

	return true
}

func (r *WALReader) Value() []byte {
	if r.seg == nil {
		return nil
	}

	return r.seg.Value()
}

func (r *WALReader) Error() error {
	if r.err != nil {
		return r.err
	}

	if r.seg != nil {
		return r.seg.Error()
	}

	return nil
}
<|endoftext|>


<commit_before>package main

// Slack outgoing and incoming webhooks are handled here. Requests come in and
// are examined to see if we need to respond. If we do, we set a timer to check
// if a response was already posted by calling the history api. Responses are
// sent back using an incoming webhook.
//
// Create an outgoing webhook in your Slack here:
// https://my.slack.com/services/new/outgoing-webhook
//
// Create an incoming webhook in your Slack here:
// https://my.slack.com/services/new/incoming-webhook

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type WebhookResponse struct {
	Username string `json:"username"`
	Text     string `json:"text"`
	Channel  string `json:"channel"`
}

type ChannelMessage struct {
	Type     string
	Subtype  string
	Username string
	TS       string
	User     string
	Text     string
}

type HistoryResponse struct {
	Ok       bool
	Error    string
	Messages []ChannelMessage
	Latest   string
	Oldest   string
	HasMore  bool
}

func init() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		incomingText := r.PostFormValue("text")
		log.Printf("Handling incoming request: %s", incomingText)

		if incomingText == ":point_right:" {
			// Wait 1 minute and then see if anyone replied
			log.Print("Waiting to bump")
			go func() {
				time.Sleep(30 * time.Second)

				log.Print("Checking history")
				messages, err := MakeHistoryCall(r.PostFormValue("channel_id"), r.PostFormValue("timestamp"))
				if err == nil {
					log.Printf("History returned %d new messages", len(messages))
					needsResponse := true
					for _, m := range messages {
						if strings.Contains(m.Text, ":point_right") {
							needsResponse = false
							break
						}
					}

					if needsResponse {
						log.Print("Completing bump")
						err := MakeIncomingWebhookCall(r.PostFormValue("team_domain"), r.PostFormValue("channel_id"), ":point_left:")
						if err != nil {
							log.Fatal(err)
						}
					} else {
						log.Print("Bump has already been completed")
					}
				} else {
					log.Fatal(err)
				}
			}()
		}
	})
}

func StartServer(port int) {
	log.Printf("Starting HTTP server on %d", port)
	err := http.ListenAndServe(":"+strconv.Itoa(port), nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func MakeHistoryCall(channel_id string, ts string) ([]ChannelMessage, error) {
	var method string
	if strings.HasPrefix(channel_id, "C") {
		method = "channels.history"
	} else if strings.HasPrefix(channel_id, "G") {
		method = "groups.history"
	} else if strings.HasPrefix(channel_id, "D") {
		method = "im.history"
	} else {
		return nil, errors.New("Unknown channel type")
	}

	// Build our query
	apiUrl := url.URL{
		Scheme: "https",
		Host:   "slack.com",
		Path:   "/api/" + method,
	}

	apiParams := url.Values{}
	apiParams.Set("token", apiKey)
	apiParams.Set("channel", channel_id)
	apiParams.Set("oldest", ts)

	apiUrl.RawQuery = apiParams.Encode()

	// Execute call
	log.Print(apiUrl.String())
	res, err := http.Get(apiUrl.String())
	if err != nil {
		return nil, err
	}

	// read response
	body, _ := ioutil.ReadAll(res.Body)

	// parse into json
	var h HistoryResponse
	err = json.Unmarshal(body, &h)
	if err != nil {
		return nil, err
	}

	if h.Ok != true {
		return nil, errors.New("API Error: " + h.Error)
	}

	return h.Messages, nil
}

func MakeIncomingWebhookCall(domain string, channel_id string, text string) error {
	// Build our query
	webhookUrl := url.URL{
		Scheme: "https",
		Host:   domain + ".slack.com",
		Path:   "/services/hooks/incoming-webhook",
	}

	// Construct response payload
	var response WebhookResponse
	response.Username = botUsername
	response.Text = text
	response.Channel = channel_id

	payload, err := json.Marshal(response)
	if err != nil {
		return err
	}

	webhookParams := url.Values{}
	webhookParams.Set("token", webhookToken)
	webhookParams.Set("payload", string(payload))

	webhookUrl.RawQuery = webhookParams.Encode()

	// Execute call
	_, err = http.PostForm(webhookUrl.String(), webhookParams)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Proper check for bump completion<commit_after>package main

// Slack outgoing and incoming webhooks are handled here. Requests come in and
// are examined to see if we need to respond. If we do, we set a timer to check
// if a response was already posted by calling the history api. Responses are
// sent back using an incoming webhook.
//
// Create an outgoing webhook in your Slack here:
// https://my.slack.com/services/new/outgoing-webhook
//
// Create an incoming webhook in your Slack here:
// https://my.slack.com/services/new/incoming-webhook

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type WebhookResponse struct {
	Username string `json:"username"`
	Text     string `json:"text"`
	Channel  string `json:"channel"`
}

type ChannelMessage struct {
	Type     string
	Subtype  string
	Username string
	TS       string
	User     string
	Text     string
}

type HistoryResponse struct {
	Ok       bool
	Error    string
	Messages []ChannelMessage
	Latest   string
	Oldest   string
	HasMore  bool
}

func init() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		incomingText := r.PostFormValue("text")
		log.Printf("Handling incoming request: %s", incomingText)

		if incomingText == ":point_right:" {
			// Wait 1 minute and then see if anyone replied
			log.Print("Waiting to bump")
			go func() {
				time.Sleep(30 * time.Second)

				log.Print("Checking history")
				messages, err := MakeHistoryCall(r.PostFormValue("channel_id"), r.PostFormValue("timestamp"))
				if err == nil {
					log.Printf("History returned %d new messages", len(messages))
					needsResponse := true
					for _, m := range messages {
						if strings.Contains(m.Text, ":point_left") {
							needsResponse = false
							break
						}
					}

					if needsResponse {
						log.Print("Completing bump")
						err := MakeIncomingWebhookCall(r.PostFormValue("team_domain"), r.PostFormValue("channel_id"), ":point_left:")
						if err != nil {
							log.Fatal(err)
						}
					} else {
						log.Print("Bump has already been completed")
					}
				} else {
					log.Fatal(err)
				}
			}()
		}
	})
}

func StartServer(port int) {
	log.Printf("Starting HTTP server on %d", port)
	err := http.ListenAndServe(":"+strconv.Itoa(port), nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func MakeHistoryCall(channel_id string, ts string) ([]ChannelMessage, error) {
	var method string
	if strings.HasPrefix(channel_id, "C") {
		method = "channels.history"
	} else if strings.HasPrefix(channel_id, "G") {
		method = "groups.history"
	} else if strings.HasPrefix(channel_id, "D") {
		method = "im.history"
	} else {
		return nil, errors.New("Unknown channel type")
	}

	// Build our query
	apiUrl := url.URL{
		Scheme: "https",
		Host:   "slack.com",
		Path:   "/api/" + method,
	}

	apiParams := url.Values{}
	apiParams.Set("token", apiKey)
	apiParams.Set("channel", channel_id)
	apiParams.Set("oldest", ts)

	apiUrl.RawQuery = apiParams.Encode()

	// Execute call
	log.Print(apiUrl.String())
	res, err := http.Get(apiUrl.String())
	if err != nil {
		return nil, err
	}

	// read response
	body, _ := ioutil.ReadAll(res.Body)

	// parse into json
	var h HistoryResponse
	err = json.Unmarshal(body, &h)
	if err != nil {
		return nil, err
	}

	if h.Ok != true {
		return nil, errors.New("API Error: " + h.Error)
	}

	return h.Messages, nil
}

func MakeIncomingWebhookCall(domain string, channel_id string, text string) error {
	// Build our query
	webhookUrl := url.URL{
		Scheme: "https",
		Host:   domain + ".slack.com",
		Path:   "/services/hooks/incoming-webhook",
	}

	// Construct response payload
	var response WebhookResponse
	response.Username = botUsername
	response.Text = text
	response.Channel = channel_id

	payload, err := json.Marshal(response)
	if err != nil {
		return err
	}

	webhookParams := url.Values{}
	webhookParams.Set("token", webhookToken)
	webhookParams.Set("payload", string(payload))

	webhookUrl.RawQuery = webhookParams.Encode()

	// Execute call
	_, err = http.PostForm(webhookUrl.String(), webhookParams)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>/*
 Webserver
*/

package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	r "github.com/dancannon/gorethink"
	"github.com/gorilla/context"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/mssola/user_agent"
	"log"
	"net/http"
	"net/url"
	"strconv"
)

func startServer() {
	router := mux.NewRouter()
	router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
	router.StrictSlash(true)
	router.HandleFunc("/", redirectToDefault)

	const board = `/{board:\w+}`
	const thread = `/{thread:\d+}`

	index := router.PathPrefix(board).Subrouter()
	index.HandleFunc("/", wrapHandler(false, boardPage))
	index.HandleFunc(thread, wrapHandler(false, threadPage))

	api := router.PathPrefix("/api").Subrouter()
	api.NotFoundHandler = http.NotFoundHandler() // Default 404 handler for JSON
	api.HandleFunc("/config", serveConfigs)
	api.HandleFunc(`/post/{post:\d+}`, servePost)
	posts := api.PathPrefix(board).Subrouter()
	posts.HandleFunc("/", wrapHandler(true, boardPage))
	posts.HandleFunc(thread, wrapHandler(true, threadPage))

	// Serve static assets
	if config.Hard.HTTP.ServeStatic {
		router.PathPrefix("/").Handler(http.FileServer(http.Dir("./www")))
	}

	// Infer IP from header, if configured to
	var handler http.Handler
	if config.Hard.HTTP.TrustProxies {
		handler = handlers.ProxyHeaders(router)
	} else {
		handler = router
	}

	handler = getIdent(handler)

	// Return status 500 on goroutine panic
	handler = handlers.RecoveryHandler(
		handlers.PrintRecoveryStack(true),
	)(handler)

	log.Println("Listening on " + config.Hard.HTTP.Addr)
	http.ListenAndServe(config.Hard.HTTP.Addr, handler)
}

// Attach client access rights to request
func getIdent(handler http.Handler) http.Handler {
	fn := func(res http.ResponseWriter, req *http.Request) {
		context.Set(req, "ident", lookUpIdent(req.RemoteAddr))

		// Call the next handler in the chain
		handler.ServeHTTP(res, req)
	}

	return http.HandlerFunc(fn)
}

// Redirects to frontpage, if set, or the default board
func redirectToDefault(res http.ResponseWriter, req *http.Request) {
	if config.Frontpage != "" {
		http.ServeFile(res, req, config.Frontpage)
	} else {
		http.Redirect(res, req, "/"+config.Boards.Default+"/", 302)
	}
}

type handlerFunction func(http.ResponseWriter, *http.Request)
type handlerWrapper func(bool, http.ResponseWriter, *http.Request)

// wrapHandler returns a function with the first bool argument already assigned
func wrapHandler(json bool, handler handlerWrapper) handlerFunction {
	return func(res http.ResponseWriter, req *http.Request) {
		handler(json, res, req)
	}
}

// Handles `/board/` page requests
func boardPage(jsonOnly bool, res http.ResponseWriter, req *http.Request) {
	in := indexPage{res: res, req: req, json: jsonOnly}
	board := mux.Vars(req)["board"]

	in.validate = func() bool {
		return canAccessBoard(board, in.ident)
	}

	in.getCounter = func() (counter int) {
		rGet(r.Table("main").
			Get("histCounts").
			Field("board").
			Default(0),
		).
			One(counter)
		return
	}

	in.getPostData = func() []byte {
		data := NewReader(board, in.ident).GetBoard()
		encoded, err := json.Marshal(data)
		throw(err)
		return encoded
	}

	in.process(board)
}

// Handles `/board/thread` requests
func threadPage(jsonOnly bool, res http.ResponseWriter, req *http.Request) {
	in := indexPage{res: res, req: req, json: jsonOnly}
	vars := mux.Vars(req)
	board := vars["board"]
	id, err := strconv.Atoi(vars["thread"])
	throw(err)
	in.lastN = detectLastN(req)

	in.validate = func() bool {
		return validateOP(id, board) && canAccessThread(id, board, in.ident)
	}

	in.getCounter = func() (counter int) {
		rGet(getThread(id).Field("histCtr")).One(&counter)
		return
	}

	in.getPostData = func() []byte {
		data := NewReader(board, in.ident).GetThread(id, in.lastN)
		encoded, err := json.Marshal(data)
		throw(err)
		return encoded
	}

	in.process(board)
}

// Stores common variables anf methods for both board and thread pages
type indexPage struct {
	res         http.ResponseWriter
	req         *http.Request
	validate    func() bool
	getCounter  func() int    // Progress counter used for building etags
	getPostData func() []byte // Post model JSON data
	lastN       int
	json        bool // Serve HTML from template or just JSON
	isMobile    bool
	template    templateStore
	ident       Ident
}

// Shared logic for handling both board and thread pages
func (in *indexPage) process(board string) {
	in.ident = extractIdent(in.res, in.req)
	if !in.validate() {
		in.res.WriteHeader(404)
		if !in.json {
			custom404(in.res)
		}
		return
	}
	in.isMobile = user_agent.New(in.req.UserAgent()).Mobile()

	// Choose template to use
	if !in.json {
		if !in.isMobile {
			in.template = resources["index"]
		} else {
			in.template = resources["mobile"]
		}
	}
	if in.validateEtag() {
		postData := in.getPostData()
		if in.json { //Only the JSON
			in.res.Write(postData)
			return
		}

		// Concatenate post JSON with template and write to client
		parts := in.template.Parts
		html := new(bytes.Buffer)
		html.Write(parts[0])
		html.Write(in.getPostData())
		html.Write(parts[1])
		html.Write(loginCredentials(in.ident))
		html.Write(parts[2])
		in.res.Write(html.Bytes())
	}
}

// Build an etag and check if it mathces the one provided by the client. If yes,
// send 304 and return false, otherwise set headers and return true.
func (in *indexPage) validateEtag() bool {
	etag := in.buildEtag()
	hasAuth := in.ident.Auth != ""
	if hasAuth {
		etag += "-" + in.ident.Auth
	}
	if in.lastN != 0 {
		etag += fmt.Sprintf("-last%v", in.lastN)
	}

	// If etags match, no need to rerender
	if checkClientEtags(in.res, in.req, etag) {
		return false
	}

	setHeaders(in.res, etag, in.json)
	if hasAuth {
		in.res.Header().Add("Cache-Control", ", private")
	}
	return true
}

// Build the main part of the etag
func (in *indexPage) buildEtag() string {
	etag := "W/" + strconv.Itoa(in.getCounter())
	if !in.json {
		etag += "-" + in.template.Hash
		if in.isMobile {
			etag += "-mobile"
		}
	}
	return etag
}

/*
 Check is any of the etags the client provides in the "If-None-Match" header
 match the generated etag. If yes, write 304 and return true.
*/
func checkClientEtags(
	res http.ResponseWriter,
	req *http.Request,
	etag string,
) bool {
	if ifNoneMatch, ok := req.Header["If-None-Match"]; ok {
		for _, clientEtag := range ifNoneMatch {
			if clientEtag == etag {
				res.WriteHeader(304)
				return true
			}
		}
	}
	return false
}

// Read client Identity struct, which was attached to the requests further
// upstream
func extractIdent(res http.ResponseWriter, req *http.Request) Ident {
	ident, ok := context.Get(req, "ident").(Ident)
	if !ok {
		res.WriteHeader(500)
		throw(errors.New("Failed Ident type assertion"))
	}
	return ident
}

// Cutom error page for requests that don't match a router
func notFoundHandler(res http.ResponseWriter, req *http.Request) {
	res.WriteHeader(404)
	custom404(res)
}

// Serve the custom error page
func custom404(res http.ResponseWriter) {
	copyFile("www/404.html", res)
}

// Set HTTP headers to the response object
func setHeaders(res http.ResponseWriter, etag string, json bool) {
	var vanillaHeaders = stringMap{
		"X-Frame-Options": "sameorigin",
		"Cache-Control":   "max-age=0, must-revalidate",
		"Expires":         "Fri, 01 Jan 1990 00:00:00 GMT",
	}
	head := res.Header()
	for key, val := range vanillaHeaders {
		head.Set(key, val)
	}

	head.Set("ETag", etag)

	var contentType string
	if json {
		contentType = "application/json"
	} else {
		contentType = "text/html"
	}
	head.Set("Content-Type", contentType+"; charset=UTF-8")
}

// Inject staff login credentials, if any. These will be used to download the
// moderation JS client bundle.
func loginCredentials(ident Ident) []byte {
	// TODO: Inject the variables for our new login system

	return []byte{}
}

// Validate the client's last N posts to display setting
func detectLastN(req *http.Request) int {
	parsed, err := url.ParseRequestURI(req.RequestURI)
	throw(err)
	lastNSlice, ok := parsed.Query()["lastN"]
	if ok && len(lastNSlice) > 0 {
		lastN, err := strconv.Atoi(lastNSlice[0])
		throw(err)
		if lastN <= 500 {
			return lastN
		}
	}
	return 0
}

// Serve public configuration information as JSON
func serveConfigs(res http.ResponseWriter, req *http.Request) {
	etag := "W/" + configHash
	if checkClientEtags(res, req, etag) {
		return
	}
	data, err := json.Marshal(clientConfig)
	throw(err)
	setHeaders(res, etag, true)
	res.Write(data)
}

// Serve a single post as JSON
func servePost(res http.ResponseWriter, req *http.Request) {
	id, err := strconv.Atoi(mux.Vars(req)["post"])
	throw(err)
	board := parentBoard(id)
	thread := parentThread(id)
	ident := extractIdent(res, req)
	if board == "" || thread == 0 || !canAccessThread(thread, board, ident) {
		res.WriteHeader(404)
		return
	}
	data, err := json.Marshal(NewReader(board, ident).GetPost(id))
	throw(err)
	etag := "W/" + hashBuffer(data)
	if checkClientEtags(res, req, etag) {
		return
	}
	setHeaders(res, etag, true)
	res.Write(data)
}
<commit_msg>Fix comment typos<commit_after>/*
 Webserver
*/

package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	r "github.com/dancannon/gorethink"
	"github.com/gorilla/context"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/mssola/user_agent"
	"log"
	"net/http"
	"net/url"
	"strconv"
)

func startServer() {
	router := mux.NewRouter()
	router.NotFoundHandler = http.HandlerFunc(notFoundHandler)
	router.StrictSlash(true)
	router.HandleFunc("/", redirectToDefault)

	const board = `/{board:\w+}`
	const thread = `/{thread:\d+}`

	index := router.PathPrefix(board).Subrouter()
	index.HandleFunc("/", wrapHandler(false, boardPage))
	index.HandleFunc(thread, wrapHandler(false, threadPage))

	api := router.PathPrefix("/api").Subrouter()
	api.NotFoundHandler = http.NotFoundHandler() // Default 404 handler for JSON
	api.HandleFunc("/config", serveConfigs)
	api.HandleFunc(`/post/{post:\d+}`, servePost)
	posts := api.PathPrefix(board).Subrouter()
	posts.HandleFunc("/", wrapHandler(true, boardPage))
	posts.HandleFunc(thread, wrapHandler(true, threadPage))

	// Serve static assets
	if config.Hard.HTTP.ServeStatic {
		router.PathPrefix("/").Handler(http.FileServer(http.Dir("./www")))
	}

	// Infer IP from header, if configured to
	var handler http.Handler
	if config.Hard.HTTP.TrustProxies {
		handler = handlers.ProxyHeaders(router)
	} else {
		handler = router
	}

	handler = getIdent(handler)

	// Return status 500 on goroutine panic
	handler = handlers.RecoveryHandler(
		handlers.PrintRecoveryStack(true),
	)(handler)

	log.Println("Listening on " + config.Hard.HTTP.Addr)
	http.ListenAndServe(config.Hard.HTTP.Addr, handler)
}

// Attach client access rights to request
func getIdent(handler http.Handler) http.Handler {
	fn := func(res http.ResponseWriter, req *http.Request) {
		context.Set(req, "ident", lookUpIdent(req.RemoteAddr))

		// Call the next handler in the chain
		handler.ServeHTTP(res, req)
	}

	return http.HandlerFunc(fn)
}

// Redirects to frontpage, if set, or the default board
func redirectToDefault(res http.ResponseWriter, req *http.Request) {
	if config.Frontpage != "" {
		http.ServeFile(res, req, config.Frontpage)
	} else {
		http.Redirect(res, req, "/"+config.Boards.Default+"/", 302)
	}
}

type handlerFunction func(http.ResponseWriter, *http.Request)
type handlerWrapper func(bool, http.ResponseWriter, *http.Request)

// wrapHandler returns a function with the first bool argument already assigned
func wrapHandler(json bool, handler handlerWrapper) handlerFunction {
	return func(res http.ResponseWriter, req *http.Request) {
		handler(json, res, req)
	}
}

// Handles `/board/` page requests
func boardPage(jsonOnly bool, res http.ResponseWriter, req *http.Request) {
	in := indexPage{res: res, req: req, json: jsonOnly}
	board := mux.Vars(req)["board"]

	in.validate = func() bool {
		return canAccessBoard(board, in.ident)
	}

	in.getCounter = func() (counter int) {
		rGet(r.Table("main").
			Get("histCounts").
			Field("board").
			Default(0),
		).
			One(counter)
		return
	}

	in.getPostData = func() []byte {
		data := NewReader(board, in.ident).GetBoard()
		encoded, err := json.Marshal(data)
		throw(err)
		return encoded
	}

	in.process(board)
}

// Handles `/board/thread` requests
func threadPage(jsonOnly bool, res http.ResponseWriter, req *http.Request) {
	in := indexPage{res: res, req: req, json: jsonOnly}
	vars := mux.Vars(req)
	board := vars["board"]
	id, err := strconv.Atoi(vars["thread"])
	throw(err)
	in.lastN = detectLastN(req)

	in.validate = func() bool {
		return validateOP(id, board) && canAccessThread(id, board, in.ident)
	}

	in.getCounter = func() (counter int) {
		rGet(getThread(id).Field("histCtr")).One(&counter)
		return
	}

	in.getPostData = func() []byte {
		data := NewReader(board, in.ident).GetThread(id, in.lastN)
		encoded, err := json.Marshal(data)
		throw(err)
		return encoded
	}

	in.process(board)
}

// Stores common variables and methods for both board and thread pages
type indexPage struct {
	res         http.ResponseWriter
	req         *http.Request
	validate    func() bool
	getCounter  func() int    // Progress counter used for building etags
	getPostData func() []byte // Post model JSON data
	lastN       int
	json        bool // Serve HTML from template or just JSON
	isMobile    bool
	template    templateStore
	ident       Ident
}

// Shared logic for handling both board and thread pages
func (in *indexPage) process(board string) {
	in.ident = extractIdent(in.res, in.req)
	if !in.validate() {
		in.res.WriteHeader(404)
		if !in.json {
			custom404(in.res)
		}
		return
	}
	in.isMobile = user_agent.New(in.req.UserAgent()).Mobile()

	// Choose template to use
	if !in.json {
		if !in.isMobile {
			in.template = resources["index"]
		} else {
			in.template = resources["mobile"]
		}
	}
	if in.validateEtag() {
		postData := in.getPostData()
		if in.json { //Only the JSON
			in.res.Write(postData)
			return
		}

		// Concatenate post JSON with template and write to client
		parts := in.template.Parts
		html := new(bytes.Buffer)
		html.Write(parts[0])
		html.Write(in.getPostData())
		html.Write(parts[1])
		html.Write(loginCredentials(in.ident))
		html.Write(parts[2])
		in.res.Write(html.Bytes())
	}
}

// Build an etag and check if it matches the one provided by the client. If yes,
// send 304 and return false, otherwise set headers and return true.
func (in *indexPage) validateEtag() bool {
	etag := in.buildEtag()
	hasAuth := in.ident.Auth != ""
	if hasAuth {
		etag += "-" + in.ident.Auth
	}
	if in.lastN != 0 {
		etag += fmt.Sprintf("-last%v", in.lastN)
	}

	// If etags match, no need to rerender
	if checkClientEtags(in.res, in.req, etag) {
		return false
	}

	setHeaders(in.res, etag, in.json)
	if hasAuth {
		in.res.Header().Add("Cache-Control", ", private")
	}
	return true
}

// Build the main part of the etag
func (in *indexPage) buildEtag() string {
	etag := "W/" + strconv.Itoa(in.getCounter())
	if !in.json {
		etag += "-" + in.template.Hash
		if in.isMobile {
			etag += "-mobile"
		}
	}
	return etag
}

/*
 Check is any of the etags the client provides in the "If-None-Match" header
 match the generated etag. If yes, write 304 and return true.
*/
func checkClientEtags(
	res http.ResponseWriter,
	req *http.Request,
	etag string,
) bool {
	if ifNoneMatch, ok := req.Header["If-None-Match"]; ok {
		for _, clientEtag := range ifNoneMatch {
			if clientEtag == etag {
				res.WriteHeader(304)
				return true
			}
		}
	}
	return false
}

// Read client Identity struct, which was attached to the requests further
// upstream
func extractIdent(res http.ResponseWriter, req *http.Request) Ident {
	ident, ok := context.Get(req, "ident").(Ident)
	if !ok {
		res.WriteHeader(500)
		throw(errors.New("Failed Ident type assertion"))
	}
	return ident
}

// Custom error page for requests that don't match a router
func notFoundHandler(res http.ResponseWriter, req *http.Request) {
	res.WriteHeader(404)
	custom404(res)
}

// Serve the custom error page
func custom404(res http.ResponseWriter) {
	copyFile("www/404.html", res)
}

// Set HTTP headers to the response object
func setHeaders(res http.ResponseWriter, etag string, json bool) {
	vanillaHeaders := stringMap{
		"X-Frame-Options": "sameorigin",
		"Cache-Control":   "max-age=0, must-revalidate",
		"Expires":         "Fri, 01 Jan 1990 00:00:00 GMT",
	}
	head := res.Header()
	for key, val := range vanillaHeaders {
		head.Set(key, val)
	}

	head.Set("ETag", etag)

	var contentType string
	if json {
		contentType = "application/json"
	} else {
		contentType = "text/html"
	}
	head.Set("Content-Type", contentType+"; charset=UTF-8")
}

// Inject staff login credentials, if any. These will be used to download the
// moderation JS client bundle.
func loginCredentials(ident Ident) []byte {
	// TODO: Inject the variables for our new login system

	return []byte{}
}

// Validate the client's last N posts to display setting
func detectLastN(req *http.Request) int {
	parsed, err := url.ParseRequestURI(req.RequestURI)
	throw(err)
	lastNSlice, ok := parsed.Query()["lastN"]
	if ok && len(lastNSlice) > 0 {
		lastN, err := strconv.Atoi(lastNSlice[0])
		throw(err)
		if lastN <= 500 {
			return lastN
		}
	}
	return 0
}

// Serve public configuration information as JSON
func serveConfigs(res http.ResponseWriter, req *http.Request) {
	etag := "W/" + configHash
	if checkClientEtags(res, req, etag) {
		return
	}
	data, err := json.Marshal(clientConfig)
	throw(err)
	setHeaders(res, etag, true)
	res.Write(data)
}

// Serve a single post as JSON
func servePost(res http.ResponseWriter, req *http.Request) {
	id, err := strconv.Atoi(mux.Vars(req)["post"])
	throw(err)
	board := parentBoard(id)
	thread := parentThread(id)
	ident := extractIdent(res, req)
	if board == "" || thread == 0 || !canAccessThread(thread, board, ident) {
		res.WriteHeader(404)
		return
	}
	data, err := json.Marshal(NewReader(board, ident).GetPost(id))
	throw(err)
	etag := "W/" + hashBuffer(data)
	if checkClientEtags(res, req, etag) {
		return
	}
	setHeaders(res, etag, true)
	res.Write(data)
}
<|endoftext|>


<commit_before>package cadastre

import "fmt"
import "log"
import "os"
import "io"
import "strings"
import "sync/atomic"
import "time"
import "bytes"
import "path/filepath"
import "html/template"
import "encoding/base64"
import "encoding/json"
import "compress/gzip"
import "net/http"
import "github.com/tobz/fsnotify"
import "github.com/tobz/go-cache"
import "github.com/gorilla/mux"

type WebUI struct {
	Configuration *Configuration

	server *http.Server

	templateCache   *cache.Cache
	templateWatcher *fsnotify.Watcher

	RequestsServed uint64
	RequestErrors  uint64
	CacheRequests  uint64
	CacheHits      uint64
}

func (me *WebUI) StartListening() error {
	var err error

	// Initiailize our statistics.
	me.RequestsServed = 0
	me.RequestErrors = 0
	me.CacheRequests = 0
	me.CacheHits = 0

	log.Printf("Instantiating template cache...")

	// Set up our template cache and template watcher.
	me.templateCache = cache.New((time.Minute * 30), (time.Second * 30))
	me.templateWatcher, err = fsnotify.NewWatcher()
	if err != nil {
		return fmt.Errorf("Unable to instantiate template watcher! %s", err)
	}

	go func() {
		for {
			select {
			// Something happened in our template directory.  Invalidate the cache.
			case ev := <-me.templateWatcher.Event:
				log.Printf("Detected change to template file '%s'.  Flushing template cache.", ev.Name)
				me.templateCache.Flush()
			}
		}
	}()

	absTemplateDirectory, err := filepath.Abs(me.Configuration.TemplateDirectory)
	if err != nil {
		return fmt.Errorf("Caught an error trying to get the absolute path to the template directory! %s", err)
	}

	// Watch our template directory for changes so that we invalidate the cache  Only watch for modifications and deletes.
	me.templateWatcher.WatchFlags(absTemplateDirectory, fsnotify.FSN_MODIFY)

	log.Printf("Creating HTTP server...")

	// Set up our request multiplexer.
	requestMultiplexer := mux.NewRouter()

	// Create our server instance using our request multiplexer.
	me.server = &http.Server{
		Addr:    me.Configuration.ListenAddress,
		Handler: requestMultiplexer,
	}

	log.Printf("Configuring request router...")

	// Define our routes.
	requestMultiplexer.Handle("/favicon.ico", CadastreHandler(me, me.serveFavicon))
	requestMultiplexer.Handle("/", CadastreHandler(me, me.serveIndex))
	requestMultiplexer.Handle("/_getServerGroups", CadastreHandler(me, me.serveServerGroups))
	requestMultiplexer.Handle("/_getCurrentSnapshot/{serverName}", CadastreHandler(me, me.serveCurrentSnapshot))

	absStaticAssetPath, err := filepath.Abs(me.Configuration.StaticAssetDirectory)
	if err != nil {
		return fmt.Errorf("Caught an error trying to get the absolute path to the static asset directory! %s", err)
	}

	requestMultiplexer.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(absStaticAssetPath))))

	// Start listening!
	go func() {
		err := me.server.ListenAndServe()
		if err != nil {
			log.Printf("Caught an error while serving requests! %s", err)
		}
	}()

	return nil
}

func (me *WebUI) loadTemplate(templatePath string) (*template.Template, error) {
	incrementCounter(&(me.CacheRequests))

	// Try the cache first.
	cachedTemplate, found := me.templateCache.Get(templatePath)
	if found {
		incrementCounter(&(me.CacheHits))

		return cachedTemplate.(*template.Template), nil
	}

	// Not in the cache, so we need to go to disk for it.  Build our path.
	absTemplatePath, err := filepath.Abs(filepath.Join(me.Configuration.TemplateDirectory, templatePath))
	if err != nil {
		return nil, fmt.Errorf("Error building the proper path to load the template file! %s", err)
	}

	// Open the template.
	fileHandle, err := os.Open(absTemplatePath)
	if err != nil {
		return nil, fmt.Errorf("Error loading template file! %s", err)
	}
	defer fileHandle.Close()

	// Read out the template.
	templateBuffer := bytes.NewBuffer([]byte{})
	_, err = templateBuffer.ReadFrom(fileHandle)
	if err != nil {
		return nil, fmt.Errorf("Error reading the template file! %s", err)
	}

	// Create our actual template.
	newTemplate := template.New(templatePath)
	newTemplate, err = newTemplate.Parse(templateBuffer.String())
	if err != nil {
		return nil, fmt.Errorf("Error creating template object! %s", err)
	}

	// Put this in the cache.
	me.templateCache.Set(templatePath, newTemplate, -1)

	return newTemplate, nil
}

func (me *WebUI) serveFavicon(response http.ResponseWriter, request *http.Request) error {
	response.Header().Add("Content-Type", "image/x-icon")

	imageData, err := base64.StdEncoding.DecodeString(CadastreEncodedFavicon)
	if err != nil {
		serveErrorPage(response)
		return fmt.Errorf("request error: couldn't decode favicon data")
	}

	response.Write(imageData)

	return nil
}

func (me *WebUI) serveIndex(response http.ResponseWriter, request *http.Request) error {
	return me.renderTemplate(response, "index.goml", nil)
}

func (me *WebUI) serveServerGroups(response http.ResponseWriter, request *http.Request) error {
	// Go through all the configured servers and organize them by category.
	groups := make(map[string]map[string]interface{}, 0)

	for _, s := range me.Configuration.Servers {
		// Create the group entry if we don't already have one.
		if _, ok := groups[s.Category]; !ok {
			group := make(map[string]interface{}, 0)

			servers := make([]map[string]string, 0)
			group["servers"] = servers

			groups[s.Category] = group
		}

		// Create a server entry and add it to the group.
		server := make(map[string]string, 0)
		server["internalName"] = s.InternalName
		server["displayName"] = s.DisplayName

		existingServers := groups[s.Category]["servers"].([]map[string]string)
		existingServers = append(existingServers, server)
		groups[s.Category]["servers"] = existingServers
	}

	// Wrap our result.
	result := make(map[string]interface{}, 0)
	result["groups"] = groups

	return me.renderJson(response, result)
}

func incrementCounter(addr *uint64) {
	atomic.AddUint64(addr, 1)
}

func (me *WebUI) serveCurrentSnapshot(response http.ResponseWriter, request *http.Request) error {
	// Get the specified server name.
	requestVars := mux.Vars(request)
	internalName := requestVars["serverName"]

	// Try and find the given server in our list of servers.
	//for _, server := range me.Configuration.Servers {
	//}

	// We never found the server, so inform the UI.  This is definitely a bug.
	return me.renderJsonError(response, fmt.Sprintf("Failed to find server %s in the configured list of servers!", internalName))
}

func (me *WebUI) renderJson(response http.ResponseWriter, data interface{}) error {
	response.Header().Set("Content-Type", "application/json")

	result := make(map[string]interface{}, 0)
	result["success"] = true
	result["payload"] = data

	jsonEncoder := json.NewEncoder(response)
	err := jsonEncoder.Encode(result)
	if err != nil {
		serveErrorPage(response)
		return fmt.Errorf("request error: failed to encode object to JSON! %s", err)
	}

	return nil
}

func (me *WebUI) renderJsonError(response http.ResponseWriter, errorMessage string) error {
	response.Header().Set("Content-Type", "application/json")

	result := make(map[string]interface{}, 0)
	result["success"] = false
	result["errorMessage"] = errorMessage

	jsonEncoder := json.NewEncoder(response)
	err := jsonEncoder.Encode(result)
	if err != nil {
		serveErrorPage(response)
		return fmt.Errorf("request error: failed to encode error object to JSON! %s", err)
	}

	return nil
}

func (me *WebUI) renderTemplate(response http.ResponseWriter, templatePath string, content map[string]interface{}) error {
	response.Header().Set("Content-Type", "text/html")

	// Load the specified template.
	templateData, err := me.loadTemplate(templatePath)
	if err != nil {
		serveErrorPage(response)
		return fmt.Errorf("request error: couldn't load template '%s': %s", templatePath, err)
	}

	// Generate our template output and write it to the response.
	err = templateData.Execute(response, content)
	if err != nil {
		serveErrorPage(response)
		return fmt.Errorf("request error: couldn't render template '%s': %s", templatePath, err)
	}

	return nil
}

func serveErrorPage(response http.ResponseWriter) {
	response.Header().Set("Content-Type", "text/plain")

	errorOutput := "Sorry, something went terribly wrong here. :("
	response.Write([]byte(errorOutput))
}

type gzipResponseWriter struct {
	io.Writer
	http.ResponseWriter
}

func (me *gzipResponseWriter) Write(b []byte) (int, error) {
	return me.Writer.Write(b)
}

type CadastreWebHandler struct {
	Handler func(http.ResponseWriter, *http.Request) error
	Server  *WebUI
}

func (me *CadastreWebHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	var err error

	// If the browser accepts gzip'd content, use that.
	if strings.Contains(request.Header.Get("Accept-Encoding"), "gzip") {
		response.Header().Set("Content-Encoding", "gzip")

		gzipWriter := gzip.NewWriter(response)
		defer gzipWriter.Close()

		gzipResponse := &gzipResponseWriter{Writer: gzipWriter, ResponseWriter: response}
		err = me.Handler(gzipResponse, request)
	} else {
		err = me.Handler(response, request)
	}

	if err != nil {
		incrementCounter(&(me.Server.RequestErrors))
	}

	incrementCounter(&(me.Server.RequestsServed))
}

func CadastreHandler(server *WebUI, handlerFunc func(http.ResponseWriter, *http.Request) error) *CadastreWebHandler {
	return &CadastreWebHandler{Handler: handlerFunc, Server: server}
}
<commit_msg>Added the logic to take a snapshot for a specific database server.  Right now, it returns the JSON encoded version of the Snapshot struct but the UI doesn't handle it yet.<commit_after>package cadastre

import "fmt"
import "log"
import "os"
import "io"
import "strings"
import "sync/atomic"
import "time"
import "bytes"
import "path/filepath"
import "html/template"
import "encoding/base64"
import "encoding/json"
import "compress/gzip"
import "net/http"
import "github.com/tobz/fsnotify"
import "github.com/tobz/go-cache"
import "github.com/gorilla/mux"

type WebUI struct {
	Configuration *Configuration

	server *http.Server

	templateCache   *cache.Cache
	templateWatcher *fsnotify.Watcher

	RequestsServed uint64
	RequestErrors  uint64
	CacheRequests  uint64
	CacheHits      uint64
}

func (me *WebUI) StartListening() error {
	var err error

	// Initiailize our statistics.
	me.RequestsServed = 0
	me.RequestErrors = 0
	me.CacheRequests = 0
	me.CacheHits = 0

	log.Printf("Instantiating template cache...")

	// Set up our template cache and template watcher.
	me.templateCache = cache.New((time.Minute * 30), (time.Second * 30))
	me.templateWatcher, err = fsnotify.NewWatcher()
	if err != nil {
		return fmt.Errorf("Unable to instantiate template watcher! %s", err)
	}

	go func() {
		for {
			select {
			// Something happened in our template directory.  Invalidate the cache.
			case ev := <-me.templateWatcher.Event:
				log.Printf("Detected change to template file '%s'.  Flushing template cache.", ev.Name)
				me.templateCache.Flush()
			}
		}
	}()

	absTemplateDirectory, err := filepath.Abs(me.Configuration.TemplateDirectory)
	if err != nil {
		return fmt.Errorf("Caught an error trying to get the absolute path to the template directory! %s", err)
	}

	// Watch our template directory for changes so that we invalidate the cache  Only watch for modifications and deletes.
	me.templateWatcher.WatchFlags(absTemplateDirectory, fsnotify.FSN_MODIFY)

	log.Printf("Creating HTTP server...")

	// Set up our request multiplexer.
	requestMultiplexer := mux.NewRouter()

	// Create our server instance using our request multiplexer.
	me.server = &http.Server{
		Addr:    me.Configuration.ListenAddress,
		Handler: requestMultiplexer,
	}

	log.Printf("Configuring request router...")

	// Define our routes.
	requestMultiplexer.Handle("/favicon.ico", CadastreHandler(me, me.serveFavicon))
	requestMultiplexer.Handle("/", CadastreHandler(me, me.serveIndex))
	requestMultiplexer.Handle("/_getServerGroups", CadastreHandler(me, me.serveServerGroups))
	requestMultiplexer.Handle("/_getCurrentSnapshot/{serverName}", CadastreHandler(me, me.serveCurrentSnapshot))

	absStaticAssetPath, err := filepath.Abs(me.Configuration.StaticAssetDirectory)
	if err != nil {
		return fmt.Errorf("Caught an error trying to get the absolute path to the static asset directory! %s", err)
	}

	requestMultiplexer.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(absStaticAssetPath))))

	// Start listening!
	go func() {
		err := me.server.ListenAndServe()
		if err != nil {
			log.Printf("Caught an error while serving requests! %s", err)
		}
	}()

	return nil
}

func (me *WebUI) loadTemplate(templatePath string) (*template.Template, error) {
	incrementCounter(&(me.CacheRequests))

	// Try the cache first.
	cachedTemplate, found := me.templateCache.Get(templatePath)
	if found {
		incrementCounter(&(me.CacheHits))

		return cachedTemplate.(*template.Template), nil
	}

	// Not in the cache, so we need to go to disk for it.  Build our path.
	absTemplatePath, err := filepath.Abs(filepath.Join(me.Configuration.TemplateDirectory, templatePath))
	if err != nil {
		return nil, fmt.Errorf("Error building the proper path to load the template file! %s", err)
	}

	// Open the template.
	fileHandle, err := os.Open(absTemplatePath)
	if err != nil {
		return nil, fmt.Errorf("Error loading template file! %s", err)
	}
	defer fileHandle.Close()

	// Read out the template.
	templateBuffer := bytes.NewBuffer([]byte{})
	_, err = templateBuffer.ReadFrom(fileHandle)
	if err != nil {
		return nil, fmt.Errorf("Error reading the template file! %s", err)
	}

	// Create our actual template.
	newTemplate := template.New(templatePath)
	newTemplate, err = newTemplate.Parse(templateBuffer.String())
	if err != nil {
		return nil, fmt.Errorf("Error creating template object! %s", err)
	}

	// Put this in the cache.
	me.templateCache.Set(templatePath, newTemplate, -1)

	return newTemplate, nil
}

func (me *WebUI) serveFavicon(response http.ResponseWriter, request *http.Request) error {
	response.Header().Add("Content-Type", "image/x-icon")

	imageData, err := base64.StdEncoding.DecodeString(CadastreEncodedFavicon)
	if err != nil {
		serveErrorPage(response)
		return fmt.Errorf("request error: couldn't decode favicon data")
	}

	response.Write(imageData)

	return nil
}

func (me *WebUI) serveIndex(response http.ResponseWriter, request *http.Request) error {
	return me.renderTemplate(response, "index.goml", nil)
}

func (me *WebUI) serveServerGroups(response http.ResponseWriter, request *http.Request) error {
	// Go through all the configured servers and organize them by category.
	groups := make(map[string]map[string]interface{}, 0)

	for _, s := range me.Configuration.Servers {
		// Create the group entry if we don't already have one.
		if _, ok := groups[s.Category]; !ok {
			group := make(map[string]interface{}, 0)

			servers := make([]map[string]string, 0)
			group["servers"] = servers

			groups[s.Category] = group
		}

		// Create a server entry and add it to the group.
		server := make(map[string]string, 0)
		server["internalName"] = s.InternalName
		server["displayName"] = s.DisplayName

		existingServers := groups[s.Category]["servers"].([]map[string]string)
		existingServers = append(existingServers, server)
		groups[s.Category]["servers"] = existingServers
	}

	// Wrap our result.
	result := make(map[string]interface{}, 0)
	result["groups"] = groups

	return me.renderJson(response, result)
}

func incrementCounter(addr *uint64) {
	atomic.AddUint64(addr, 1)
}

func (me *WebUI) serveCurrentSnapshot(response http.ResponseWriter, request *http.Request) error {
	// Get the specified server name.
	requestVars := mux.Vars(request)
	internalName := requestVars["serverName"]

	// Try and find the given server in our list of servers.
	for _, server := range me.Configuration.Servers {
		if server.InternalName == internalName {
			// Found our server, so try and take a snapshot.
			newSnapshot := &Snapshot{}
			err := newSnapshot.TakeSnapshot(server)
			if err != nil {
				return me.renderJsonError(response, fmt.Sprintf("Failed to take processlist snapshot for %s! %s", internalName, err.Error()))
			}

			return me.renderJson(response, newSnapshot)
		}
	}

	// We never found the server, so inform the UI.  This is definitely a bug.
	return me.renderJsonError(response, fmt.Sprintf("Failed to find server %s in the configured list of servers!", internalName))
}

func (me *WebUI) renderJson(response http.ResponseWriter, data interface{}) error {
	response.Header().Set("Content-Type", "application/json")

	result := make(map[string]interface{}, 0)
	result["success"] = true
	result["payload"] = data

	jsonEncoder := json.NewEncoder(response)
	err := jsonEncoder.Encode(result)
	if err != nil {
		serveErrorPage(response)
		return fmt.Errorf("request error: failed to encode object to JSON! %s", err)
	}

	return nil
}

func (me *WebUI) renderJsonError(response http.ResponseWriter, errorMessage string) error {
	response.Header().Set("Content-Type", "application/json")

	result := make(map[string]interface{}, 0)
	result["success"] = false
	result["errorMessage"] = errorMessage

	jsonEncoder := json.NewEncoder(response)
	err := jsonEncoder.Encode(result)
	if err != nil {
		serveErrorPage(response)
		return fmt.Errorf("request error: failed to encode error object to JSON! %s", err)
	}

	return nil
}

func (me *WebUI) renderTemplate(response http.ResponseWriter, templatePath string, content map[string]interface{}) error {
	response.Header().Set("Content-Type", "text/html")

	// Load the specified template.
	templateData, err := me.loadTemplate(templatePath)
	if err != nil {
		serveErrorPage(response)
		return fmt.Errorf("request error: couldn't load template '%s': %s", templatePath, err)
	}

	// Generate our template output and write it to the response.
	err = templateData.Execute(response, content)
	if err != nil {
		serveErrorPage(response)
		return fmt.Errorf("request error: couldn't render template '%s': %s", templatePath, err)
	}

	return nil
}

func serveErrorPage(response http.ResponseWriter) {
	response.Header().Set("Content-Type", "text/plain")

	errorOutput := "Sorry, something went terribly wrong here. :("
	response.Write([]byte(errorOutput))
}

type gzipResponseWriter struct {
	io.Writer
	http.ResponseWriter
}

func (me *gzipResponseWriter) Write(b []byte) (int, error) {
	return me.Writer.Write(b)
}

type CadastreWebHandler struct {
	Handler func(http.ResponseWriter, *http.Request) error
	Server  *WebUI
}

func (me *CadastreWebHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	var err error

	// If the browser accepts gzip'd content, use that.
	if strings.Contains(request.Header.Get("Accept-Encoding"), "gzip") {
		response.Header().Set("Content-Encoding", "gzip")

		gzipWriter := gzip.NewWriter(response)
		defer gzipWriter.Close()

		gzipResponse := &gzipResponseWriter{Writer: gzipWriter, ResponseWriter: response}
		err = me.Handler(gzipResponse, request)
	} else {
		err = me.Handler(response, request)
	}

	if err != nil {
		incrementCounter(&(me.Server.RequestErrors))
	}

	incrementCounter(&(me.Server.RequestsServed))
}

func CadastreHandler(server *WebUI, handlerFunc func(http.ResponseWriter, *http.Request) error) *CadastreWebHandler {
	return &CadastreWebHandler{Handler: handlerFunc, Server: server}
}
<|endoftext|>


<commit_before>package slab

import (
	"testing"
)

func TestBasics(t *testing.T) {
	s := SynchronizedArena(NewSlabArena(1, 1024, 2))
	if s == nil {
		t.Errorf("expected new slab arena to work")
	}
	a := s.Alloc(1)
	if a == nil {
		t.Errorf("expected alloc to work")
	}
	if len(a) != 1 {
		t.Errorf("expected alloc to give right size buf")
	}
	if cap(a) != 1 + SLAB_MEMORY_FOOTER_LEN {
		t.Errorf("expected alloc cap to match algorithm, got: %v vs %v",
			cap(a), 1 + SLAB_MEMORY_FOOTER_LEN)
	}
	a[0] = 66
	s.DecRef(a)
	b := s.Alloc(1)
	if b == nil {
		t.Errorf("expected alloc to work")
	}
	if len(b) != 1 {
		t.Errorf("expected alloc to give right size buf")
	}
	if cap(b) != 1 + SLAB_MEMORY_FOOTER_LEN {
		t.Errorf("expected alloc cap to match algorithm, got: %v vs %v",
			cap(b), 1 + SLAB_MEMORY_FOOTER_LEN)
	}
	if b[0] != 66 {
		t.Errorf("expected alloc to return last freed buf")
	}
	s.AddRef(b)
	s.DecRef(b)
	s.DecRef(b)
	c := s.Alloc(1)
	if c[0] != 66 {
		t.Errorf("expected alloc to return last freed buf")
	}
}

func TestSlabClassGrowth(t *testing.T) {
	s := NewSlabArena(1, 8, 2).(*slabArena)
	expectSlabClasses := func(numSlabClasses int) {
		if len(s.slabClasses) != numSlabClasses {
			t.Errorf("expected %v slab classses, got: %v",
				numSlabClasses, len(s.slabClasses))
		}
	}
	expectSlabClasses(1)
	s.Alloc(1)
	expectSlabClasses(1)
	s.Alloc(1)
	expectSlabClasses(1)
	s.Alloc(2)
	expectSlabClasses(2)
	s.Alloc(1)
	s.Alloc(2)
	expectSlabClasses(2)
	s.Alloc(3)
	s.Alloc(4)
	expectSlabClasses(3)
	s.Alloc(5)
	s.Alloc(8)
	expectSlabClasses(4)
}

func TestDecRef(t *testing.T) {
	s := NewSlabArena(1, 8, 2).(*slabArena)
	expectSlabClasses := func(numSlabClasses int) {
		if len(s.slabClasses) != numSlabClasses {
			t.Errorf("expected %v slab classses, got: %v",
				numSlabClasses, len(s.slabClasses))
		}
	}
	a := make([][]byte, 128)
	for j := 0; j < 100; j++ {
		for i := 0; i < len(a); i++ {
			a[i] = s.Alloc(i % 8)
		}
		for i := 0; i < len(a); i++ {
			s.DecRef(a[i])
		}
	}
	expectSlabClasses(4)
}

func TestAddRef(t *testing.T) {
	s := NewSlabArena(1, 1, 2).(*slabArena)
	if !s.slabClasses[0].chunkFree.isEmpty() {
		t.Errorf("expected no free chunks")
	}
	a := s.Alloc(1)
	a[0] = 123
	if !s.slabClasses[0].chunkFree.isEmpty() {
		t.Errorf("expected no free chunks")
	}
	s.AddRef(a)
	if !s.slabClasses[0].chunkFree.isEmpty() {
		t.Errorf("expected no free chunks")
	}
	s.DecRef(a)
	if !s.slabClasses[0].chunkFree.isEmpty() {
		t.Errorf("expected no free chunks")
	}
	s.DecRef(a)
	if s.slabClasses[0].chunkFree.isEmpty() {
		t.Errorf("expected 1 free chunk")
	}
	b := s.Alloc(1)
	if b[0] != 123 {
		t.Errorf("expected chunk to be reused")
	}
}

func TestLargeAlloc(t *testing.T) {
	s := NewSlabArena(1, 1, 2).(*slabArena)
	if s.Alloc(2) != nil {
		t.Errorf("expected alloc larger than slab size to fail")
	}
}
<commit_msg>TestEmptyChunk().<commit_after>package slab

import (
	"testing"
)

func TestBasics(t *testing.T) {
	s := SynchronizedArena(NewSlabArena(1, 1024, 2))
	if s == nil {
		t.Errorf("expected new slab arena to work")
	}
	a := s.Alloc(1)
	if a == nil {
		t.Errorf("expected alloc to work")
	}
	if len(a) != 1 {
		t.Errorf("expected alloc to give right size buf")
	}
	if cap(a) != 1 + SLAB_MEMORY_FOOTER_LEN {
		t.Errorf("expected alloc cap to match algorithm, got: %v vs %v",
			cap(a), 1 + SLAB_MEMORY_FOOTER_LEN)
	}
	a[0] = 66
	s.DecRef(a)
	b := s.Alloc(1)
	if b == nil {
		t.Errorf("expected alloc to work")
	}
	if len(b) != 1 {
		t.Errorf("expected alloc to give right size buf")
	}
	if cap(b) != 1 + SLAB_MEMORY_FOOTER_LEN {
		t.Errorf("expected alloc cap to match algorithm, got: %v vs %v",
			cap(b), 1 + SLAB_MEMORY_FOOTER_LEN)
	}
	if b[0] != 66 {
		t.Errorf("expected alloc to return last freed buf")
	}
	s.AddRef(b)
	s.DecRef(b)
	s.DecRef(b)
	c := s.Alloc(1)
	if c[0] != 66 {
		t.Errorf("expected alloc to return last freed buf")
	}
}

func TestSlabClassGrowth(t *testing.T) {
	s := NewSlabArena(1, 8, 2).(*slabArena)
	expectSlabClasses := func(numSlabClasses int) {
		if len(s.slabClasses) != numSlabClasses {
			t.Errorf("expected %v slab classses, got: %v",
				numSlabClasses, len(s.slabClasses))
		}
	}
	expectSlabClasses(1)
	s.Alloc(1)
	expectSlabClasses(1)
	s.Alloc(1)
	expectSlabClasses(1)
	s.Alloc(2)
	expectSlabClasses(2)
	s.Alloc(1)
	s.Alloc(2)
	expectSlabClasses(2)
	s.Alloc(3)
	s.Alloc(4)
	expectSlabClasses(3)
	s.Alloc(5)
	s.Alloc(8)
	expectSlabClasses(4)
}

func TestDecRef(t *testing.T) {
	s := NewSlabArena(1, 8, 2).(*slabArena)
	expectSlabClasses := func(numSlabClasses int) {
		if len(s.slabClasses) != numSlabClasses {
			t.Errorf("expected %v slab classses, got: %v",
				numSlabClasses, len(s.slabClasses))
		}
	}
	a := make([][]byte, 128)
	for j := 0; j < 100; j++ {
		for i := 0; i < len(a); i++ {
			a[i] = s.Alloc(i % 8)
		}
		for i := 0; i < len(a); i++ {
			s.DecRef(a[i])
		}
	}
	expectSlabClasses(4)
}

func TestAddRef(t *testing.T) {
	s := NewSlabArena(1, 1, 2).(*slabArena)
	if !s.slabClasses[0].chunkFree.isEmpty() {
		t.Errorf("expected no free chunks")
	}
	a := s.Alloc(1)
	a[0] = 123
	if !s.slabClasses[0].chunkFree.isEmpty() {
		t.Errorf("expected no free chunks")
	}
	s.AddRef(a)
	if !s.slabClasses[0].chunkFree.isEmpty() {
		t.Errorf("expected no free chunks")
	}
	s.DecRef(a)
	if !s.slabClasses[0].chunkFree.isEmpty() {
		t.Errorf("expected no free chunks")
	}
	s.DecRef(a)
	if s.slabClasses[0].chunkFree.isEmpty() {
		t.Errorf("expected 1 free chunk")
	}
	b := s.Alloc(1)
	if b[0] != 123 {
		t.Errorf("expected chunk to be reused")
	}
}

func TestLargeAlloc(t *testing.T) {
	s := NewSlabArena(1, 1, 2).(*slabArena)
	if s.Alloc(2) != nil {
		t.Errorf("expected alloc larger than slab size to fail")
	}
}

func TestEmptyChunk(t *testing.T) {
	s := NewSlabArena(1, 1, 2).(*slabArena)
	sc := s.slabClasses[0]
	if sc.chunk(empty_chunkLoc) != nil {
		t.Errorf("expected empty chunk to not have a chunk()")
	}
}
<|endoftext|>


<commit_before>package main

import ("github.com/nsf/termbox-go";"time")

// unicode constants
const (
	v = '\u2502'
	h = '\u2500'
	box_ul = '\u256d'
	box_ur = '\u256e'
	box_ll = '\u2570'
	box_lr = '\u256f'
	name = " SAMAYAM "
)

type BOX struct {

	width,height int
	x_start,y_start int

}

func draw_vertical(x,y,h int){

	for i := y ; i <= y+h-1 ; i++ {
		termbox.SetCell(x,i,v,termbox.ColorRed,termbox.ColorBlack)
	}
}

func draw_horizontal(x,y,w int){

	for i := x ; i <= x+w-1 ; i++ {
		termbox.SetCell(i,y,h,termbox.ColorRed,termbox.ColorBlack)
	}
}

func box_filler(x,y,w int){
	// debug function
	a := [5] termbox.Attribute{ termbox.ColorRed,termbox.ColorGreen,termbox.ColorYellow }
	for i := x ; i <= x+w-1 ; i++ {
		termbox.SetCell(i,y,h,termbox.ColorWhite,a[i%3])
	}
}

func (r *BOX) draw_mainbox(){

	//draw_vertical(r.x_start,r.y_start+1,r.height-1)

	termbox.SetCell(r.x_start,r.y_start,box_ul,termbox.ColorRed,termbox.ColorBlack)
	draw_horizontal(r.x_start+1,r.y_start,r.width-2)
	termbox.SetCell(r.x_start+r.width-1,r.y_start,box_ur,termbox.ColorRed,termbox.ColorBlack)

	termbox.SetCell(r.x_start,r.y_start+r.height-1,box_ll,termbox.ColorRed,termbox.ColorBlack)
	draw_horizontal(r.x_start+1,r.y_start+r.height-1,r.width-2)
	termbox.SetCell(r.x_start+r.width-1,r.y_start+r.height-1,box_lr,termbox.ColorRed,termbox.ColorBlack)

	draw_vertical(r.x_start,r.y_start+1,r.height-2)
	draw_vertical(r.x_start+r.width-1,r.y_start+1,r.height-2)

}

func main(){

	errit := termbox.Init()
	if errit != nil {
		panic(" Trouble somewhere")
	} else {
		x := BOX{width:20,x_start:3,y_start:3,height:20}
		x.draw_mainbox()
		termbox.Flush()
		time.Sleep(10*time.Second)
	}
	defer termbox.Close()

}

<commit_msg>more drawing functions ... tree structure has yet to be added<commit_after>package main

import ("github.com/nsf/termbox-go";"time";"strings")

// unicode constants
const (
	v = '\u2502'
	h = '\u2500'
	box_ul = '\u256d'
	box_ur = '\u256e'
	box_ll = '\u2570'
	box_lr = '\u256f'
	name = " SAMAYAM "
	FG = termbox.ColorRed
	BG = termbox.ColorBlack
)

type BOX struct {

	width,height int
	x_start,y_start int

}

type TASK struct {

	task string
	start time.Time
	end time.Time
}

func draw_string_box(fill string,x,y,padding int){

	actual_width := len(fill) + 2*padding
	actual_height :=  1 + 2*padding
	actual_start_x := x + padding
	actual_start_y := y + padding

	for i,j := range fill {
		termbox.SetCell(actual_start_x+i,actual_start_y,j,FG,BG)
	}

	rect := BOX{ x_start:x,y_start:y,width:actual_width,height:actual_height}
	rect.draw_mainbox()
}

func draw_vertical(x,y,h int){

	for i := y ; i <= y+h-1 ; i++ {
		termbox.SetCell(x,i,v,FG,BG)
	}
}

func draw_horizontal(x,y,w int){

	for i := x ; i <= x+w-1 ; i++ {
		termbox.SetCell(i,y,h,FG,BG)
	}
}

func box_filler(x,y,w int){
	// debug function
	a := [5] termbox.Attribute{ termbox.ColorRed,termbox.ColorGreen,termbox.ColorYellow }
	for i := x ; i <= x+w-1 ; i++ {
		termbox.SetCell(i,y,h,termbox.ColorWhite,a[i%3])
	}
}

func (r *BOX) draw_mainbox(){

	termbox.SetCell(r.x_start,r.y_start,box_ul,FG,BG)
	draw_horizontal(r.x_start+1,r.y_start,r.width-2)
	termbox.SetCell(r.x_start+r.width-1,r.y_start,box_ur,FG,BG)

	termbox.SetCell(r.x_start,r.y_start+r.height-1,box_ll,FG,BG)
	draw_horizontal(r.x_start+1,r.y_start+r.height-1,r.width-2)
	termbox.SetCell(r.x_start+r.width-1,r.y_start+r.height-1,box_lr,FG,BG)

	draw_vertical(r.x_start,r.y_start+1,r.height-2)
	draw_vertical(r.x_start+r.width-1,r.y_start+1,r.height-2)

}

func ( t *TASK) draw_task (x,y,padding int){
	s := []string{t.task,t.start.String()}
	string_to_draw :=  strings.Join(s, " --- " )
	draw_string_box(string_to_draw,x,y,padding )

}

func main(){

	errit := termbox.Init()
	if errit != nil {
		panic(" Trouble somewhere")
	} else {
		//x := BOX{width:20,x_start:3,y_start:3,height:20}
		//x.draw_mainbox()
		task_1 := TASK{task:" finish samayam",start:time.Now()}
		task_1.draw_task(10,10,2)
		termbox.Flush()
		time.Sleep(10*time.Second)
	}
	defer termbox.Close()

}

<|endoftext|>


<commit_before>package irckit

import (
	"errors"
	"html"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/nlopes/slack"
)

type SlackInfo struct {
	Token      string
	sc         *slack.Client
	rtm        *slack.RTM
	sinfo      *slack.Info
	susers     map[string]slack.User
	connected  bool
	inprogress bool
	sync.RWMutex
}

func (u *User) loginToSlack() (*slack.Client, error) {
	u.sc = slack.New(u.Token)
	u.rtm = u.sc.NewRTM()
	u.Lock()
	u.susers = make(map[string]slack.User)
	u.Unlock()
	go u.rtm.ManageConnection()
	//time.Sleep(time.Second * 2)
	u.sinfo = u.rtm.GetInfo()
	count := 0
	for u.sinfo == nil {
		time.Sleep(time.Millisecond * 500)
		logger.Debug("still waiting for sinfo")
		u.sinfo = u.rtm.GetInfo()
		count++
		if count == 20 {
			return nil, errors.New("couldn't connect in 10 seconds. Check your credentials")
		}
	}
	go u.handleSlack()
	u.addSlackUsersToChannels()
	u.connected = true
	return u.sc, nil
}

func (u *User) logoutFromSlack() error {
	logger.Debug("calling logout from slack")
	err := u.rtm.Disconnect()
	if err != nil {
		logger.Debug("logoutfrom slack", err)
		return err
	}
	u.Srv.Logout(u)
	u.sc = nil
	logger.Info("logout succeeded")
	u.connected = false
	return nil
}

func (u *User) createSlackUser(slackuser *slack.User) *User {
	if slackuser == nil {
		return nil
	}
	if ghost, ok := u.Srv.HasUser(slackuser.Name); ok {
		return ghost
	}
	ghost := &User{Nick: slackuser.Name, User: slackuser.ID, Real: slackuser.RealName, Host: "host", Roles: "", channels: map[Channel]struct{}{}, DisplayName: slackuser.Profile.DisplayName}
	ghost.MmGhostUser = true
	u.Srv.Add(ghost)
	return ghost
}

func (u *User) addSlackUserToChannel(user *slack.User, channel string, channelId string) {
	if user == nil {
		return
	}
	ghost := u.createSlackUser(user)
	if ghost == nil {
		logger.Warnf("Cannot join %v into %s", user, channel)
		return
	}
	logger.Debugf("adding %s to %s (%s)", ghost.Nick, channel, channelId)
	ch := u.Srv.Channel(channelId)
	logger.Debugf("channel: %#v %#v", ch.String(), ch.ID())
	ch.Join(ghost)
}

func (u *User) addSlackUsersToChannels() {
	srv := u.Srv
	throttle := time.Tick(time.Millisecond * 100)
	logger.Debug("in addUsersToChannels()")
	// add all users, also who are not on channels
	ch := srv.Channel("&users")
	users, _ := u.sc.GetUsers()
	for _, mmuser := range users {
		// do not add our own nick
		if mmuser.ID == u.sinfo.User.ID {
			continue
		}
		u.createSlackUser(&mmuser)
		u.addSlackUserToChannel(&mmuser, "&users", "&users")
		u.Lock()
		u.susers[mmuser.ID] = mmuser
		u.Unlock()
	}
	ch.Join(u)

	channels := make(chan interface{}, 10)
	for i := 0; i < 10; i++ {
		go u.addSlackUserToChannelWorker(channels, throttle)
	}
	groups, _ := u.sc.GetGroups(true)
	mmchannels, _ := u.sc.GetChannels(true)
	for _, mmchannel := range mmchannels {
		if mmchannel.IsMember {
			logger.Debug("Adding channel", mmchannel)
			channels <- mmchannel
		}
	}
	for _, mmchannel := range groups {
		logger.Debug("Adding private channel", mmchannel)
		channels <- mmchannel
	}
	close(channels)
}

func (u *User) addSlackUserToChannelWorker(channels <-chan interface{}, throttle <-chan time.Time) {
	var ID, name string
	for {
		mmchannel, ok := <-channels
		if !ok {
			logger.Debug("Done adding user to channels")
			return
		}
		<-throttle
		switch mmchannel.(type) {
		case slack.Channel:
			ID = mmchannel.(slack.Channel).ID
			name = mmchannel.(slack.Channel).Name
			u.syncSlackChannel(ID, name)
		case slack.Group:
			ID = mmchannel.(slack.Group).ID
			name = mmchannel.(slack.Group).Name
			logger.Debugf("GROUP %#v", mmchannel.(slack.Group))
			u.syncSlackGroup(ID, name)

		}
		// exclude direct messages
		//var spoof func(string, string)
		//ch := u.Srv.Channel(mmchannel.ID)
		// post everything to the channel you haven't seen yet
	}
}

func (u *User) handleSlack() {
	for {
		/*
			if u.mc.WsQuit {
				logger.Debug("exiting handleWsMessage")
				return
			}
		*/
		logger.Debug("in handleSlack")
		for msg := range u.rtm.IncomingEvents {
			switch ev := msg.Data.(type) {
			case *slack.MessageEvent:
				u.handleSlackActionPost(ev)
			case *slack.DisconnectedEvent:
				logger.Debug("disconnected event received, we should reconnect now..")
				//return
			}
		}
	}
	/*
			logger.Debugf("MMUser WsReceiver: %#v", message.Raw)
			// check if we have the users/channels in our cache. If not update
			u.checkWsActionMessage(message.Raw, updateChannelsThrottle)
			switch message.Raw.Event {
			case model.WEBSOCKET_EVENT_POSTED:
				u.handleWsActionPost(message.Raw)
			case model.WEBSOCKET_EVENT_POST_EDITED:
				u.handleWsActionPost(message.Raw)
			case model.WEBSOCKET_EVENT_USER_REMOVED:
				u.handleWsActionUserRemoved(message.Raw)
			case model.WEBSOCKET_EVENT_USER_ADDED:
				u.handleWsActionUserAdded(message.Raw)
			}
		}
	*/
}

func (u *User) handleSlackActionPost(rmsg *slack.MessageEvent) {
	var ch Channel
	logger.Debugf("handleSlackActionPost() receiving msg %#v", rmsg)
	if len(rmsg.Attachments) > 0 {
		// skip messages we made ourselves
		if rmsg.Attachments[0].CallbackID == "matterircd" {
			return
		}
	}

	user, err := u.rtm.GetUserInfo(rmsg.User)
	if err != nil {
		if rmsg.BotID == "" {
			return
		}
	}

	// handle bot messages
	botname := ""
	if rmsg.User == "" && rmsg.BotID != "" {
		bot, _ := u.rtm.GetBotInfo(rmsg.BotID)
		if bot.Name != "" {
			botname = bot.Name
		} else if rmsg.Username != "" {
			botname = rmsg.Username
		}
	}

	// create new "ghost" user
	ghost := u.createSlackUser(user)

	spoofUsername := ""
	if user != nil {
		spoofUsername = user.ID
		if ghost != nil {
			spoofUsername = ghost.Nick
			if ghost.DisplayName != "" && ghost.DisplayName != ghost.Nick {
				spoofUsername = "|"
				//	spoofUsername = ghost.DisplayName
			}
		}
	}

	// if we have a botname, use it
	if botname != "" {
		spoofUsername = botname
	}

	msgs := strings.Split(rmsg.Text, "\n")
	// direct message

	ch = u.Srv.Channel(rmsg.Channel)

	if ghost != nil {
		// join if not in channel
		if !ch.HasUser(ghost) {
			ch.Join(ghost)
		}
	}

	for _, m := range msgs {
		// cleanup the message
		m = u.replaceMention(m)
		m = u.replaceVariable(m)
		m = u.replaceChannel(m)
		m = u.replaceURL(m)
		m = html.UnescapeString(m)

		// look in attachments if we have no text
		if m == "" {
			for _, attach := range rmsg.Attachments {
				if attach.Text != "" {
					m = attach.Text
				} else {
					m = attach.Fallback
				}
			}
		}

		// still no text, ignore this message
		if m == "" {
			continue
		}

		if strings.HasPrefix(rmsg.Channel, "D") {
			u.MsgSpoofUser(spoofUsername, m)
		} else {
			if ghost != nil && ghost.DisplayName != "" {
				m = "<" + ghost.DisplayName + "> " + m
			}
			ch.SpoofMessage(spoofUsername, m)
		}
	}
}

// sync IRC with mattermost channel state
func (u *User) syncSlackChannel(id string, name string) {
	srv := u.Srv
	info, err := u.sc.GetChannelInfo(id)
	if err != nil {
		logger.Info(err)
	}

	for _, user := range info.Members {
		if u.sinfo.User.ID != user {
			//slackuser, _ := u.sc.GetUserInfo(user)
			slackuser := u.getSlackUser(user)
			if slackuser != nil {
				u.addSlackUserToChannel(slackuser, "#"+name, id)
			}
		}
	}

	ch := srv.Channel(id)
	ch.Topic(u, info.Topic.Value)
	if !ch.HasUser(u) {
		logger.Debugf("syncSlackchannel adding myself to %s (id: %s)", name, id)
		ch.Join(u)
	}
}

// sync IRC with mattermost channel state
func (u *User) syncSlackGroup(id string, name string) {
	srv := u.Srv
	info, err := u.sc.GetGroupInfo(id)
	if err != nil {
		logger.Info(err)
	}

	for _, user := range info.Members {
		if u.sinfo.User.ID != user {
			//slackuser, _ := u.sc.GetUserInfo(user)
			slackuser := u.getSlackUser(user)
			if slackuser != nil {
				u.addSlackUserToChannel(slackuser, "#"+name, id)
			}
		}
	}

	ch := srv.Channel(id)
	ch.Topic(u, info.Topic.Value)
	if !ch.HasUser(u) {
		logger.Debugf("syncSlackchannel adding myself to %s (id: %s)", name, id)
		ch.Join(u)
	}
}

// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
func (u *User) replaceMention(text string) string {
	results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, "<@"+r[1]+">", "@"+u.userName(r[1]), -1)
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
func (u *User) replaceChannel(text string) string {
	results := regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, r[0], "#"+r[1], -1)
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#variables
func (u *User) replaceVariable(text string) string {
	results := regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		if r[2] != "" {
			text = strings.Replace(text, r[0], "@"+r[2], -1)
		} else {
			text = strings.Replace(text, r[0], "@"+r[1], -1)
		}
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#linking_to_urls
func (u *User) replaceURL(text string) string {
	results := regexp.MustCompile(`<(.*?)(\|.*?)?>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, r[0], r[1], -1)
	}
	return text
}

func (u *User) getSlackUser(name string) *slack.User {
	u.RLock()
	defer u.RUnlock()
	if user, ok := u.susers[name]; ok {
		return &user
	}
	return nil
}

func (u *User) userName(id string) string {
	u.RLock()
	defer u.RUnlock()
	// TODO dynamically update when new users are joining slack
	for _, us := range u.susers {
		if us.ID == id {
			if us.Profile.DisplayName != "" {
				return us.Profile.DisplayName
			}
			return us.Name
		}
	}
	if id == u.sinfo.User.ID {
		return u.sinfo.User.Name
	}
	return ""
}

func (u *User) isConnected() bool {
	return u.connected
}
<commit_msg>Fix double nick<commit_after>package irckit

import (
	"errors"
	"html"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/nlopes/slack"
)

type SlackInfo struct {
	Token      string
	sc         *slack.Client
	rtm        *slack.RTM
	sinfo      *slack.Info
	susers     map[string]slack.User
	connected  bool
	inprogress bool
	sync.RWMutex
}

func (u *User) loginToSlack() (*slack.Client, error) {
	u.sc = slack.New(u.Token)
	u.rtm = u.sc.NewRTM()
	u.Lock()
	u.susers = make(map[string]slack.User)
	u.Unlock()
	go u.rtm.ManageConnection()
	//time.Sleep(time.Second * 2)
	u.sinfo = u.rtm.GetInfo()
	count := 0
	for u.sinfo == nil {
		time.Sleep(time.Millisecond * 500)
		logger.Debug("still waiting for sinfo")
		u.sinfo = u.rtm.GetInfo()
		count++
		if count == 20 {
			return nil, errors.New("couldn't connect in 10 seconds. Check your credentials")
		}
	}
	go u.handleSlack()
	u.addSlackUsersToChannels()
	u.connected = true
	return u.sc, nil
}

func (u *User) logoutFromSlack() error {
	logger.Debug("calling logout from slack")
	err := u.rtm.Disconnect()
	if err != nil {
		logger.Debug("logoutfrom slack", err)
		return err
	}
	u.Srv.Logout(u)
	u.sc = nil
	logger.Info("logout succeeded")
	u.connected = false
	return nil
}

func (u *User) createSlackUser(slackuser *slack.User) *User {
	if slackuser == nil {
		return nil
	}
	if ghost, ok := u.Srv.HasUser(slackuser.Name); ok {
		return ghost
	}
	ghost := &User{Nick: slackuser.Name, User: slackuser.ID, Real: slackuser.RealName, Host: "host", Roles: "", channels: map[Channel]struct{}{}, DisplayName: slackuser.Profile.DisplayName}
	ghost.MmGhostUser = true
	u.Srv.Add(ghost)
	return ghost
}

func (u *User) addSlackUserToChannel(user *slack.User, channel string, channelId string) {
	if user == nil {
		return
	}
	ghost := u.createSlackUser(user)
	if ghost == nil {
		logger.Warnf("Cannot join %v into %s", user, channel)
		return
	}
	logger.Debugf("adding %s to %s (%s)", ghost.Nick, channel, channelId)
	ch := u.Srv.Channel(channelId)
	logger.Debugf("channel: %#v %#v", ch.String(), ch.ID())
	ch.Join(ghost)
}

func (u *User) addSlackUsersToChannels() {
	srv := u.Srv
	throttle := time.Tick(time.Millisecond * 100)
	logger.Debug("in addUsersToChannels()")
	// add all users, also who are not on channels
	ch := srv.Channel("&users")
	users, _ := u.sc.GetUsers()
	for _, mmuser := range users {
		// do not add our own nick
		if mmuser.ID == u.sinfo.User.ID {
			continue
		}
		u.createSlackUser(&mmuser)
		u.addSlackUserToChannel(&mmuser, "&users", "&users")
		u.Lock()
		u.susers[mmuser.ID] = mmuser
		u.Unlock()
	}
	ch.Join(u)

	channels := make(chan interface{}, 10)
	for i := 0; i < 10; i++ {
		go u.addSlackUserToChannelWorker(channels, throttle)
	}
	groups, _ := u.sc.GetGroups(true)
	mmchannels, _ := u.sc.GetChannels(true)
	for _, mmchannel := range mmchannels {
		if mmchannel.IsMember {
			logger.Debug("Adding channel", mmchannel)
			channels <- mmchannel
		}
	}
	for _, mmchannel := range groups {
		logger.Debug("Adding private channel", mmchannel)
		channels <- mmchannel
	}
	close(channels)
}

func (u *User) addSlackUserToChannelWorker(channels <-chan interface{}, throttle <-chan time.Time) {
	var ID, name string
	for {
		mmchannel, ok := <-channels
		if !ok {
			logger.Debug("Done adding user to channels")
			return
		}
		<-throttle
		switch mmchannel.(type) {
		case slack.Channel:
			ID = mmchannel.(slack.Channel).ID
			name = mmchannel.(slack.Channel).Name
			u.syncSlackChannel(ID, name)
		case slack.Group:
			ID = mmchannel.(slack.Group).ID
			name = mmchannel.(slack.Group).Name
			logger.Debugf("GROUP %#v", mmchannel.(slack.Group))
			u.syncSlackGroup(ID, name)

		}
		// exclude direct messages
		//var spoof func(string, string)
		//ch := u.Srv.Channel(mmchannel.ID)
		// post everything to the channel you haven't seen yet
	}
}

func (u *User) handleSlack() {
	for {
		/*
			if u.mc.WsQuit {
				logger.Debug("exiting handleWsMessage")
				return
			}
		*/
		logger.Debug("in handleSlack")
		for msg := range u.rtm.IncomingEvents {
			switch ev := msg.Data.(type) {
			case *slack.MessageEvent:
				u.handleSlackActionPost(ev)
			case *slack.DisconnectedEvent:
				logger.Debug("disconnected event received, we should reconnect now..")
				//return
			}
		}
	}
	/*
			logger.Debugf("MMUser WsReceiver: %#v", message.Raw)
			// check if we have the users/channels in our cache. If not update
			u.checkWsActionMessage(message.Raw, updateChannelsThrottle)
			switch message.Raw.Event {
			case model.WEBSOCKET_EVENT_POSTED:
				u.handleWsActionPost(message.Raw)
			case model.WEBSOCKET_EVENT_POST_EDITED:
				u.handleWsActionPost(message.Raw)
			case model.WEBSOCKET_EVENT_USER_REMOVED:
				u.handleWsActionUserRemoved(message.Raw)
			case model.WEBSOCKET_EVENT_USER_ADDED:
				u.handleWsActionUserAdded(message.Raw)
			}
		}
	*/
}

func (u *User) handleSlackActionPost(rmsg *slack.MessageEvent) {
	var ch Channel
	logger.Debugf("handleSlackActionPost() receiving msg %#v", rmsg)
	if len(rmsg.Attachments) > 0 {
		// skip messages we made ourselves
		if rmsg.Attachments[0].CallbackID == "matterircd" {
			return
		}
	}

	user, err := u.rtm.GetUserInfo(rmsg.User)
	if err != nil {
		if rmsg.BotID == "" {
			return
		}
	}

	// handle bot messages
	botname := ""
	if rmsg.User == "" && rmsg.BotID != "" {
		bot, _ := u.rtm.GetBotInfo(rmsg.BotID)
		if bot.Name != "" {
			botname = bot.Name
		} else if rmsg.Username != "" {
			botname = rmsg.Username
		}
	}

	// create new "ghost" user
	ghost := u.createSlackUser(user)

	spoofUsername := ""
	if user != nil {
		spoofUsername = user.ID
		if ghost != nil {
			spoofUsername = ghost.Nick
			if ghost.DisplayName != "" && ghost.DisplayName != ghost.Nick {
				spoofUsername = "|"
				//	spoofUsername = ghost.DisplayName
			}
		}
	}

	// if we have a botname, use it
	if botname != "" {
		spoofUsername = botname
	}

	msgs := strings.Split(rmsg.Text, "\n")
	// direct message

	ch = u.Srv.Channel(rmsg.Channel)

	if ghost != nil {
		// join if not in channel
		if !ch.HasUser(ghost) {
			ch.Join(ghost)
		}
	}

	for _, m := range msgs {
		// cleanup the message
		m = u.replaceMention(m)
		m = u.replaceVariable(m)
		m = u.replaceChannel(m)
		m = u.replaceURL(m)
		m = html.UnescapeString(m)

		// look in attachments if we have no text
		if m == "" {
			for _, attach := range rmsg.Attachments {
				if attach.Text != "" {
					m = attach.Text
				} else {
					m = attach.Fallback
				}
			}
		}

		// still no text, ignore this message
		if m == "" {
			continue
		}

		if strings.HasPrefix(rmsg.Channel, "D") {
			u.MsgSpoofUser(spoofUsername, m)
		} else {
			if ghost != nil && ghost.DisplayName != "" && ghost.DisplayName != ghost.Nick {
				m = "<" + ghost.DisplayName + "> " + m
			}
			ch.SpoofMessage(spoofUsername, m)
		}
	}
}

// sync IRC with mattermost channel state
func (u *User) syncSlackChannel(id string, name string) {
	srv := u.Srv
	info, err := u.sc.GetChannelInfo(id)
	if err != nil {
		logger.Info(err)
	}

	for _, user := range info.Members {
		if u.sinfo.User.ID != user {
			//slackuser, _ := u.sc.GetUserInfo(user)
			slackuser := u.getSlackUser(user)
			if slackuser != nil {
				u.addSlackUserToChannel(slackuser, "#"+name, id)
			}
		}
	}

	ch := srv.Channel(id)
	ch.Topic(u, info.Topic.Value)
	if !ch.HasUser(u) {
		logger.Debugf("syncSlackchannel adding myself to %s (id: %s)", name, id)
		ch.Join(u)
	}
}

// sync IRC with mattermost channel state
func (u *User) syncSlackGroup(id string, name string) {
	srv := u.Srv
	info, err := u.sc.GetGroupInfo(id)
	if err != nil {
		logger.Info(err)
	}

	for _, user := range info.Members {
		if u.sinfo.User.ID != user {
			//slackuser, _ := u.sc.GetUserInfo(user)
			slackuser := u.getSlackUser(user)
			if slackuser != nil {
				u.addSlackUserToChannel(slackuser, "#"+name, id)
			}
		}
	}

	ch := srv.Channel(id)
	ch.Topic(u, info.Topic.Value)
	if !ch.HasUser(u) {
		logger.Debugf("syncSlackchannel adding myself to %s (id: %s)", name, id)
		ch.Join(u)
	}
}

// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
func (u *User) replaceMention(text string) string {
	results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, "<@"+r[1]+">", "@"+u.userName(r[1]), -1)
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
func (u *User) replaceChannel(text string) string {
	results := regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, r[0], "#"+r[1], -1)
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#variables
func (u *User) replaceVariable(text string) string {
	results := regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		if r[2] != "" {
			text = strings.Replace(text, r[0], "@"+r[2], -1)
		} else {
			text = strings.Replace(text, r[0], "@"+r[1], -1)
		}
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#linking_to_urls
func (u *User) replaceURL(text string) string {
	results := regexp.MustCompile(`<(.*?)(\|.*?)?>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, r[0], r[1], -1)
	}
	return text
}

func (u *User) getSlackUser(name string) *slack.User {
	u.RLock()
	defer u.RUnlock()
	if user, ok := u.susers[name]; ok {
		return &user
	}
	return nil
}

func (u *User) userName(id string) string {
	u.RLock()
	defer u.RUnlock()
	// TODO dynamically update when new users are joining slack
	for _, us := range u.susers {
		if us.ID == id {
			if us.Profile.DisplayName != "" {
				return us.Profile.DisplayName
			}
			return us.Name
		}
	}
	if id == u.sinfo.User.ID {
		return u.sinfo.User.Name
	}
	return ""
}

func (u *User) isConnected() bool {
	return u.connected
}
<|endoftext|>