<commit_before>package main

import (
	"fmt"
	"github.com/fhs/gompd/mpd"
	"sort"
	"strconv"
	"sync"
	"time"
)

type playerMessageType int

const (
	updateLibrary playerMessageType = iota
	updatePlaylist
	updateCurrent
	sortPlaylist
	pause
	prev
	play
	next
	ping
)

type playerMessage struct {
	request     playerMessageType
	requestData []string
	err         chan error
}

/*Dial Connects to mpd server.*/
func Dial(network, addr string) (*Player, error) {
	p := new(Player)
	p.mutex = new(sync.Mutex)
	p.daemonStop = make(chan bool)
	p.daemonRequest = make(chan *playerMessage)
	p.network = network
	p.addr = addr
	return p, p.start()
}

/*Player represents mpd control interface.*/
type Player struct {
	network          string
	addr             string
	mpc              MpdClient
	watcher          mpd.Watcher
	watcherResponse  chan error
	daemonStop       chan bool
	daemonRequest    chan *playerMessage
	mutex            *sync.Mutex
	current          mpd.Attrs
	currentModified  time.Time
	status           PlayerStatus
	comments         mpd.Attrs
	commentsModified time.Time
	library          []mpd.Attrs
	libraryModified  time.Time
	playlist         []mpd.Attrs
	playlistModified time.Time
}

/*PlayerStatus represents mpd status.*/
type PlayerStatus struct {
	Volume       int     `json:"volume"`
	Repeat       bool    `json:"repeat"`
	Random       bool    `json:"random"`
	Single       bool    `json:"single"`
	Consume      bool    `json:"consume"`
	State        string  `json:"state"`
	SongPos      int     `json:"song_pos"`
	SongElapsed  float32 `json:"song_elapsed"`
	SongLength   int     `json:"song_length"`
	LastModified int64   `json:"last_modified"`
}

/*MpdClient represents mpd.Client for Player.*/
type MpdClient interface {
	Play(int) error
	Pause(bool) error
	Previous() error
	Next() error
	Ping() error
	Close() error
	ReadComments(string) (mpd.Attrs, error)
	CurrentSong() (mpd.Attrs, error)
	Status() (mpd.Attrs, error)
	ListAllInfo(string) ([]mpd.Attrs, error)
	PlaylistInfo(int, int) ([]mpd.Attrs, error)
	BeginCommandList() *mpd.CommandList
}

/*Close mpd connection.*/
func (p *Player) Close() error {
	p.daemonStop <- true
	p.mpc.Close()
	return p.watcher.Close()
}

/*Comments returns mpd current song raw meta data.*/
func (p *Player) Comments() (mpd.Attrs, time.Time) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	return p.comments, p.commentsModified
}

/*Current returns mpd current song data.*/
func (p *Player) Current() (mpd.Attrs, time.Time) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	return p.current, p.currentModified
}

/*Status returns mpd current song data.*/
func (p *Player) Status() (PlayerStatus, time.Time) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	return p.status, p.currentModified
}

/*Library returns mpd library song data list.*/
func (p *Player) Library() ([]mpd.Attrs, time.Time) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	return p.library, p.libraryModified
}

/*Playlist returns mpd playlist song data list.*/
func (p *Player) Playlist() ([]mpd.Attrs, time.Time) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	return p.playlist, p.playlistModified
}

/*Pause song.*/
func (p *Player) Pause() error {
	return p.request(pause)
}

/*Play or resume song.*/
func (p *Player) Play() error {
	return p.request(play)
}

/*Prev song.*/
func (p *Player) Prev() error {
	return p.request(prev)
}

/*Next song.*/
func (p *Player) Next() error {
	return p.request(next)
}

func (p *Player) start() (err error) {
	err = p.connect()
	if err != nil {
		return err
	}
	err = p.updateLibrary()
	if err != nil {
		p.Close()
		return
	}
	err = p.updatePlaylist()
	if err != nil {
		p.Close()
		return
	}
	err = p.updateCurrent()
	if err != nil {
		p.Close()
		return
	}
	go p.daemon()
	go p.watch()
	go p.ping()
	return
}

func (p *Player) daemon() {
	sendErr := func(ec chan error, err error) {
		if ec != nil {
			ec <- err
		}
	}
loop:
	for {
		select {
		case <-p.daemonStop:
			break loop
		case m := <-p.daemonRequest:
			switch m.request {
			case prev:
				sendErr(m.err, p.mpc.Previous())
			case pause:
				sendErr(m.err, p.mpc.Pause(true))
			case play:
				sendErr(m.err, p.mpc.Play(-1))
			case next:
				sendErr(m.err, p.mpc.Next())
			case updateLibrary:
				sendErr(m.err, p.updateLibrary())
			case updatePlaylist:
				sendErr(m.err, p.updatePlaylist())
			case updateCurrent:
				sendErr(m.err, p.updateCurrent())
			case sortPlaylist:
				sendErr(m.err, p.sortPlaylist(m.requestData))
			case ping:
				sendErr(m.err, p.mpc.Ping())
			}
		}
	}
}

func (p *Player) ping() {
	for {
		time.Sleep(1)
		p.request(ping)
	}
}

func (p *Player) watch() {
	for subsystem := range p.watcher.Event {
		switch subsystem {
		case "database":
			p.requestAsync(updateLibrary, p.watcherResponse)
		case "playlist":
			p.requestAsync(updatePlaylist, p.watcherResponse)
		case "player":
			p.requestAsync(updateCurrent, p.watcherResponse)
		}
	}
}

func (p *Player) reconnect() error {
	p.watcher.Close()
	p.mpc.Close()
	return p.connect()
}

func (p *Player) connect() error {
	mpc, err := mpd.Dial(p.network, p.addr)
	if err != nil {
		return err
	}
	p.mpc = mpc
	watcher, err := mpd.NewWatcher(p.network, p.addr, "")
	if err != nil {
		mpc.Close()
		return err
	}
	p.watcher = *watcher
	return nil
}

func (p *Player) request(req playerMessageType) error {
	ec := make(chan error)
	p.requestAsync(req, ec)
	return <-ec
}

func (p *Player) requestAsync(req playerMessageType, ec chan error) {
	r := new(playerMessage)
	r.request = req
	r.err = ec
	p.daemonRequest <- r
}

func (p *Player) requestData(req playerMessageType, data []string) error {
	r := new(playerMessage)
	r.request = req
	r.requestData = data
	r.err = make(chan error)
	p.daemonRequest <- r
	return <-r.err
}

func convStatus(song, status mpd.Attrs) PlayerStatus {
	volume, err := strconv.Atoi(status["volume"])
	if err != nil {
		volume = -1
	}
	repeat := status["repeat"] == "1"
	random := status["random"] == "1"
	single := status["single"] == "1"
	consume := status["consume"] == "1"
	state := status["state"]
	if state == "" {
		state = "stopped"
	}
	songpos, err := strconv.Atoi(status["song"])
	if err != nil {
		songpos = 0
	}
	elapsed, err := strconv.ParseFloat(status["elapsed"], 64)
	if err != nil {
		elapsed = 0.0
	}
	songlength, err := strconv.Atoi(song["Time"])
	if err != nil {
		songlength = 0
	}
	lastModified := time.Now().Unix()
	return PlayerStatus{
		volume,
		repeat,
		random,
		single,
		consume,
		state,
		songpos,
		float32(elapsed),
		songlength,
		lastModified,
	}

}

/*SortPlaylist sorts playlist by song tag name.*/
func (p *Player) SortPlaylist(keys []string, uri string) (err error) {
	keys = append(keys, uri)
	return p.requestData(sortPlaylist, keys)
}

func (p *Player) sortPlaylist(keys []string) (err error) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	uri := keys[len(keys)-1]
	keys = keys[:len(keys)-1]
	err = nil
	sort.Slice(p.library, func(i, j int) bool {
		return songSortKey(p.library[i], keys) < songSortKey(p.library[j], keys)
	})
	update := false
	if len(p.library) != len(p.playlist) {
		update = true
		fmt.Printf("length not match")
	} else {
		for i := range p.library {
			n := p.library[i]["file"]
			o := p.playlist[i]["file"]
			if n != o {
				fmt.Printf("index %d not match:\n'new:%s'\n'old:%s'", i, n, o)
				update = true
				break
			}
		}
	}
	if update {
		cl := p.mpc.BeginCommandList()
		cl.Clear()
		for i := range p.library {
			cl.Add(p.library[i]["file"])
		}
		err = cl.End()
	}
	if err != nil {
		return
	}
	for i := range p.playlist {
		if p.playlist[i]["file"] == uri {
			err = p.mpc.Play(i)
			return
		}
	}
	return
}

func (p *Player) updateCurrent() error {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	song, err := p.mpc.CurrentSong()
	if err != nil {
		return err
	}
	c := songAddReadableData(song)
	cm := time.Now()

	status, err := p.mpc.Status()
	if err != nil {
		return err
	}
	s := convStatus(song, status)

	if song["file"] != "" && p.comments == nil || p.current["file"] != song["file"] {
		comments, err := p.mpc.ReadComments(song["file"])
		if err != nil {
			return err
		}
		p.commentsModified = time.Now()
		p.comments = comments
	}

	p.current = c
	p.currentModified = cm
	p.status = s
	return nil
}

func (p *Player) updateLibrary() error {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	library, err := p.mpc.ListAllInfo("/")
	if err != nil {
		return err
	}
	p.library = songsAddReadableData(library)
	p.libraryModified = time.Now()
	return nil
}

func (p *Player) updatePlaylist() error {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	playlist, err := p.mpc.PlaylistInfo(-1, -1)
	if err != nil {
		return err
	}
	p.playlist = songsAddReadableData(playlist)
	p.playlistModified = time.Now()
	return nil
}
<commit_msg>rename MpdClient to mpdClient<commit_after>package main

import (
	"fmt"
	"github.com/fhs/gompd/mpd"
	"sort"
	"strconv"
	"sync"
	"time"
)

type playerMessageType int

const (
	updateLibrary playerMessageType = iota
	updatePlaylist
	updateCurrent
	sortPlaylist
	pause
	prev
	play
	next
	ping
)

type playerMessage struct {
	request     playerMessageType
	requestData []string
	err         chan error
}

/*Dial Connects to mpd server.*/
func Dial(network, addr string) (*Player, error) {
	p := new(Player)
	p.mutex = new(sync.Mutex)
	p.daemonStop = make(chan bool)
	p.daemonRequest = make(chan *playerMessage)
	p.network = network
	p.addr = addr
	return p, p.start()
}

/*Player represents mpd control interface.*/
type Player struct {
	network          string
	addr             string
	mpc              mpdClient
	watcher          mpd.Watcher
	watcherResponse  chan error
	daemonStop       chan bool
	daemonRequest    chan *playerMessage
	mutex            *sync.Mutex
	current          mpd.Attrs
	currentModified  time.Time
	status           PlayerStatus
	comments         mpd.Attrs
	commentsModified time.Time
	library          []mpd.Attrs
	libraryModified  time.Time
	playlist         []mpd.Attrs
	playlistModified time.Time
}

/*PlayerStatus represents mpd status.*/
type PlayerStatus struct {
	Volume       int     `json:"volume"`
	Repeat       bool    `json:"repeat"`
	Random       bool    `json:"random"`
	Single       bool    `json:"single"`
	Consume      bool    `json:"consume"`
	State        string  `json:"state"`
	SongPos      int     `json:"song_pos"`
	SongElapsed  float32 `json:"song_elapsed"`
	SongLength   int     `json:"song_length"`
	LastModified int64   `json:"last_modified"`
}

type mpdClient interface {
	Play(int) error
	Pause(bool) error
	Previous() error
	Next() error
	Ping() error
	Close() error
	ReadComments(string) (mpd.Attrs, error)
	CurrentSong() (mpd.Attrs, error)
	Status() (mpd.Attrs, error)
	ListAllInfo(string) ([]mpd.Attrs, error)
	PlaylistInfo(int, int) ([]mpd.Attrs, error)
	BeginCommandList() *mpd.CommandList
}

/*Close mpd connection.*/
func (p *Player) Close() error {
	p.daemonStop <- true
	p.mpc.Close()
	return p.watcher.Close()
}

/*Comments returns mpd current song raw meta data.*/
func (p *Player) Comments() (mpd.Attrs, time.Time) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	return p.comments, p.commentsModified
}

/*Current returns mpd current song data.*/
func (p *Player) Current() (mpd.Attrs, time.Time) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	return p.current, p.currentModified
}

/*Status returns mpd current song data.*/
func (p *Player) Status() (PlayerStatus, time.Time) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	return p.status, p.currentModified
}

/*Library returns mpd library song data list.*/
func (p *Player) Library() ([]mpd.Attrs, time.Time) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	return p.library, p.libraryModified
}

/*Playlist returns mpd playlist song data list.*/
func (p *Player) Playlist() ([]mpd.Attrs, time.Time) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	return p.playlist, p.playlistModified
}

/*Pause song.*/
func (p *Player) Pause() error {
	return p.request(pause)
}

/*Play or resume song.*/
func (p *Player) Play() error {
	return p.request(play)
}

/*Prev song.*/
func (p *Player) Prev() error {
	return p.request(prev)
}

/*Next song.*/
func (p *Player) Next() error {
	return p.request(next)
}

func (p *Player) start() (err error) {
	err = p.connect()
	if err != nil {
		return err
	}
	err = p.updateLibrary()
	if err != nil {
		p.Close()
		return
	}
	err = p.updatePlaylist()
	if err != nil {
		p.Close()
		return
	}
	err = p.updateCurrent()
	if err != nil {
		p.Close()
		return
	}
	go p.daemon()
	go p.watch()
	go p.ping()
	return
}

func (p *Player) daemon() {
	sendErr := func(ec chan error, err error) {
		if ec != nil {
			ec <- err
		}
	}
loop:
	for {
		select {
		case <-p.daemonStop:
			break loop
		case m := <-p.daemonRequest:
			switch m.request {
			case prev:
				sendErr(m.err, p.mpc.Previous())
			case pause:
				sendErr(m.err, p.mpc.Pause(true))
			case play:
				sendErr(m.err, p.mpc.Play(-1))
			case next:
				sendErr(m.err, p.mpc.Next())
			case updateLibrary:
				sendErr(m.err, p.updateLibrary())
			case updatePlaylist:
				sendErr(m.err, p.updatePlaylist())
			case updateCurrent:
				sendErr(m.err, p.updateCurrent())
			case sortPlaylist:
				sendErr(m.err, p.sortPlaylist(m.requestData))
			case ping:
				sendErr(m.err, p.mpc.Ping())
			}
		}
	}
}

func (p *Player) ping() {
	for {
		time.Sleep(1)
		p.request(ping)
	}
}

func (p *Player) watch() {
	for subsystem := range p.watcher.Event {
		switch subsystem {
		case "database":
			p.requestAsync(updateLibrary, p.watcherResponse)
		case "playlist":
			p.requestAsync(updatePlaylist, p.watcherResponse)
		case "player":
			p.requestAsync(updateCurrent, p.watcherResponse)
		}
	}
}

func (p *Player) reconnect() error {
	p.watcher.Close()
	p.mpc.Close()
	return p.connect()
}

func (p *Player) connect() error {
	mpc, err := mpd.Dial(p.network, p.addr)
	if err != nil {
		return err
	}
	p.mpc = mpc
	watcher, err := mpd.NewWatcher(p.network, p.addr, "")
	if err != nil {
		mpc.Close()
		return err
	}
	p.watcher = *watcher
	return nil
}

func (p *Player) request(req playerMessageType) error {
	ec := make(chan error)
	p.requestAsync(req, ec)
	return <-ec
}

func (p *Player) requestAsync(req playerMessageType, ec chan error) {
	r := new(playerMessage)
	r.request = req
	r.err = ec
	p.daemonRequest <- r
}

func (p *Player) requestData(req playerMessageType, data []string) error {
	r := new(playerMessage)
	r.request = req
	r.requestData = data
	r.err = make(chan error)
	p.daemonRequest <- r
	return <-r.err
}

func convStatus(song, status mpd.Attrs) PlayerStatus {
	volume, err := strconv.Atoi(status["volume"])
	if err != nil {
		volume = -1
	}
	repeat := status["repeat"] == "1"
	random := status["random"] == "1"
	single := status["single"] == "1"
	consume := status["consume"] == "1"
	state := status["state"]
	if state == "" {
		state = "stopped"
	}
	songpos, err := strconv.Atoi(status["song"])
	if err != nil {
		songpos = 0
	}
	elapsed, err := strconv.ParseFloat(status["elapsed"], 64)
	if err != nil {
		elapsed = 0.0
	}
	songlength, err := strconv.Atoi(song["Time"])
	if err != nil {
		songlength = 0
	}
	lastModified := time.Now().Unix()
	return PlayerStatus{
		volume,
		repeat,
		random,
		single,
		consume,
		state,
		songpos,
		float32(elapsed),
		songlength,
		lastModified,
	}

}

/*SortPlaylist sorts playlist by song tag name.*/
func (p *Player) SortPlaylist(keys []string, uri string) (err error) {
	keys = append(keys, uri)
	return p.requestData(sortPlaylist, keys)
}

func (p *Player) sortPlaylist(keys []string) (err error) {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	uri := keys[len(keys)-1]
	keys = keys[:len(keys)-1]
	err = nil
	sort.Slice(p.library, func(i, j int) bool {
		return songSortKey(p.library[i], keys) < songSortKey(p.library[j], keys)
	})
	update := false
	if len(p.library) != len(p.playlist) {
		update = true
		fmt.Printf("length not match")
	} else {
		for i := range p.library {
			n := p.library[i]["file"]
			o := p.playlist[i]["file"]
			if n != o {
				fmt.Printf("index %d not match:\n'new:%s'\n'old:%s'", i, n, o)
				update = true
				break
			}
		}
	}
	if update {
		cl := p.mpc.BeginCommandList()
		cl.Clear()
		for i := range p.library {
			cl.Add(p.library[i]["file"])
		}
		err = cl.End()
	}
	if err != nil {
		return
	}
	for i := range p.playlist {
		if p.playlist[i]["file"] == uri {
			err = p.mpc.Play(i)
			return
		}
	}
	return
}

func (p *Player) updateCurrent() error {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	song, err := p.mpc.CurrentSong()
	if err != nil {
		return err
	}
	c := songAddReadableData(song)
	cm := time.Now()

	status, err := p.mpc.Status()
	if err != nil {
		return err
	}
	s := convStatus(song, status)

	if song["file"] != "" && p.comments == nil || p.current["file"] != song["file"] {
		comments, err := p.mpc.ReadComments(song["file"])
		if err != nil {
			return err
		}
		p.commentsModified = time.Now()
		p.comments = comments
	}

	p.current = c
	p.currentModified = cm
	p.status = s
	return nil
}

func (p *Player) updateLibrary() error {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	library, err := p.mpc.ListAllInfo("/")
	if err != nil {
		return err
	}
	p.library = songsAddReadableData(library)
	p.libraryModified = time.Now()
	return nil
}

func (p *Player) updatePlaylist() error {
	p.mutex.Lock()
	defer p.mutex.Unlock()
	playlist, err := p.mpc.PlaylistInfo(-1, -1)
	if err != nil {
		return err
	}
	p.playlist = songsAddReadableData(playlist)
	p.playlistModified = time.Now()
	return nil
}
<|endoftext|>


<commit_before>package genericsite

import (
	"strings"

	"github.com/hoisie/web"
	"github.com/xyproto/onthefly"
	"github.com/xyproto/permissions"
	"github.com/xyproto/webhandle"
)

type MenuEntry struct {
	id   string
	text string
	url  string
}

type MenuEntries []*MenuEntry

// Generate a menu ID based on the first word in the menu text
func (me *MenuEntry) AutoId() {
	firstword := me.text
	if strings.Contains(me.text, " ") {
		// Get the first word of the menu text
		firstword = strings.SplitN(me.text, " ", 2)[0]
	}
	me.id = firstword
}

// Takes something like "Admin:/admin" and returns a *MenuEntry
func NewMenuEntry(text_and_url string) *MenuEntry {
	var me MenuEntry
	me.text, me.url = permissions.ColonSplit(text_and_url)
	me.AutoId()
	return &me
}

func Links2menuEntries(links []string) MenuEntries {
	menuEntries := make(MenuEntries, len(links))
	for i, text_and_url := range links {
		menuEntries[i] = NewMenuEntry(text_and_url)
	}
	return menuEntries
}

// Generate tags for the menu based on a list of "MenuDescription:/menu/url"
func MenuSnippet(menuEntries MenuEntries) *onthefly.Page {
	var a, li, sep *onthefly.Tag

	page, ul := onthefly.StandaloneTag("ul")
	ul.AddAttrib("class", "menuList")
	//ul.AddStyle("list-style-type", "none")
	//ul.AddStyle("float", "left")
	//ul.AddStyle("margin", "0")

	for i, menuEntry := range menuEntries {

		li = ul.AddNewTag("li")
		li.AddAttrib("class", "menuEntry")

		// TODO: Make sure not duplicate ids are added for two menu entries named "Hi there" and "Hi you"
		menuId := "menu" + menuEntry.id
		li.AddAttrib("id", menuId)

		// All menu entries are now hidden by default!
		//li.AddStyle("display", "none")
		//li.AddStyle("display", "inline")

		li.SansSerif()
		//li.CustomSansSerif("Armata")

		// For every element, but not the first one
		if i > 0 {
			// Insert a '|' character in a div
			sep = li.AddNewTag("div")
			sep.AddContent("|")
			sep.AddAttrib("class", "separator")
		}

		a = li.AddNewTag("a")
		a.AddAttrib("class", "menulink")
		a.AddAttrib("href", menuEntry.url)
		a.AddContent(menuEntry.text)

	}

	return page
}

// Checks if a *MenuEntry exists in a []*MenuEntry (MenuEntries)
func HasEntry(checkEntry *MenuEntry, menuEntries MenuEntries) bool {
	for _, menuEntry := range menuEntries {
		if menuEntry.url == checkEntry.url {
			return true
		}
	}
	return false
}

func AddIfNotAdded(url string, filteredMenuEntries *MenuEntries, menuEntry *MenuEntry) {
	//if currentMenuURL != url {
	if menuEntry.url == url {
		if !HasEntry(menuEntry, *filteredMenuEntries) {
			*filteredMenuEntries = append(*filteredMenuEntries, menuEntry)
		}
	}
	//}
}

/*
 * Functions that generate functions that generate content that can be used in templates.
 * type TemplateValues map[string]string
 * type TemplateValueGenerator func(*web.Context) TemplateValues
 * type TemplateValueGeneratorFactory func(*UserState) TemplateValueGenerator
 */
// TODO: Take the same parameters as the old menu generating code
// TODO: Put one if these in each engine then combine them somehow
// TODO: Check for the menyEntry.url first, then check the rights, not the other way around
// TODO: Fix and refactor this one
// TODO: Check the user status _once_, and the admin status _once_, then generate the menu
// TODO: Some way of marking menu entries as user, admin or other rights. Add a group system?
func DynamicMenuFactoryGenerator(menuEntries MenuEntries) TemplateValueGeneratorFactory {
	return func(state *permissions.UserState) webhandle.TemplateValueGenerator {
		return func(ctx *web.Context) onthefly.TemplateValues {

			userRights := state.UserRights(ctx.Request)
			adminRights := state.AdminRights(ctx.Request)

			var filteredMenuEntries MenuEntries
			var logoutEntry *MenuEntry = nil

			// Build up filteredMenuEntries based on what should be shown or not
			for _, menuEntry := range menuEntries {

				// Don't add duplicates
				if HasEntry(menuEntry, filteredMenuEntries) {
					continue
				}

				// Add this one last
				if menuEntry.url == "/logout" {
					if userRights {
						logoutEntry = menuEntry
					}
					continue
				}

				// Always show the Overview menu
				AddIfNotAdded("/", &filteredMenuEntries, menuEntry)
				//if menuEntry.url == "/" {
				//	if !HasEntry(menuEntry, filteredMenuEntries) {
				//		filteredMenuEntries = append(filteredMenuEntries, menuEntry)
				//	}
				//}

				// If logged in, show Logout and the content
				if userRights {

					// Add every link except the current page we're on
					//if menuEntry.url != currentMenuURL {
					if !HasEntry(menuEntry, filteredMenuEntries) {
						if (menuEntry.url != "/login") && (menuEntry.url != "/register") && (menuEntry.url != "/admin") {
							filteredMenuEntries = append(filteredMenuEntries, menuEntry)
						}
					}
					//}

					// Show admin content
					if adminRights {
						AddIfNotAdded("/admin", &filteredMenuEntries, menuEntry)
					}
				} else {
					// Only show Login and Register
					AddIfNotAdded("/login", &filteredMenuEntries, menuEntry)
					AddIfNotAdded("/register", &filteredMenuEntries, menuEntry)
				}

			}

			if logoutEntry != nil {
				AddIfNotAdded("/logout", &filteredMenuEntries, logoutEntry)
			}

			page := MenuSnippet(filteredMenuEntries)
			retval := page.String()

			// TODO: Return the CSS as well somehow
			//css := page.CSS()

			return onthefly.TemplateValues{"menu": retval}
		}
	}
}

// Combines two TemplateValueGenerators into one TemplateValueGenerator by adding the strings per key
func TemplateValueGeneratorCombinator(tvg1, tvg2 webhandle.TemplateValueGenerator) webhandle.TemplateValueGenerator {
	return func(ctx *web.Context) onthefly.TemplateValues {
		tv1 := tvg1(ctx)
		tv2 := tvg2(ctx)
		for key, value := range tv2 {
			// TODO: Check if key exists in tv1 first
			tv1[key] += value
		}
		return tv1
	}
}
<commit_msg>Function was moved to webhandle<commit_after>package genericsite

import (
	"strings"

	"github.com/hoisie/web"
	"github.com/xyproto/onthefly"
	"github.com/xyproto/permissions"
	"github.com/xyproto/webhandle"
)

type MenuEntry struct {
	id   string
	text string
	url  string
}

type MenuEntries []*MenuEntry

// Generate a menu ID based on the first word in the menu text
func (me *MenuEntry) AutoId() {
	firstword := me.text
	if strings.Contains(me.text, " ") {
		// Get the first word of the menu text
		firstword = strings.SplitN(me.text, " ", 2)[0]
	}
	me.id = firstword
}

// Takes something like "Admin:/admin" and returns a *MenuEntry
func NewMenuEntry(text_and_url string) *MenuEntry {
	var me MenuEntry
	me.text, me.url = webhandle.ColonSplit(text_and_url)
	me.AutoId()
	return &me
}

func Links2menuEntries(links []string) MenuEntries {
	menuEntries := make(MenuEntries, len(links))
	for i, text_and_url := range links {
		menuEntries[i] = NewMenuEntry(text_and_url)
	}
	return menuEntries
}

// Generate tags for the menu based on a list of "MenuDescription:/menu/url"
func MenuSnippet(menuEntries MenuEntries) *onthefly.Page {
	var a, li, sep *onthefly.Tag

	page, ul := onthefly.StandaloneTag("ul")
	ul.AddAttrib("class", "menuList")
	//ul.AddStyle("list-style-type", "none")
	//ul.AddStyle("float", "left")
	//ul.AddStyle("margin", "0")

	for i, menuEntry := range menuEntries {

		li = ul.AddNewTag("li")
		li.AddAttrib("class", "menuEntry")

		// TODO: Make sure not duplicate ids are added for two menu entries named "Hi there" and "Hi you"
		menuId := "menu" + menuEntry.id
		li.AddAttrib("id", menuId)

		// All menu entries are now hidden by default!
		//li.AddStyle("display", "none")
		//li.AddStyle("display", "inline")

		li.SansSerif()
		//li.CustomSansSerif("Armata")

		// For every element, but not the first one
		if i > 0 {
			// Insert a '|' character in a div
			sep = li.AddNewTag("div")
			sep.AddContent("|")
			sep.AddAttrib("class", "separator")
		}

		a = li.AddNewTag("a")
		a.AddAttrib("class", "menulink")
		a.AddAttrib("href", menuEntry.url)
		a.AddContent(menuEntry.text)

	}

	return page
}

// Checks if a *MenuEntry exists in a []*MenuEntry (MenuEntries)
func HasEntry(checkEntry *MenuEntry, menuEntries MenuEntries) bool {
	for _, menuEntry := range menuEntries {
		if menuEntry.url == checkEntry.url {
			return true
		}
	}
	return false
}

func AddIfNotAdded(url string, filteredMenuEntries *MenuEntries, menuEntry *MenuEntry) {
	//if currentMenuURL != url {
	if menuEntry.url == url {
		if !HasEntry(menuEntry, *filteredMenuEntries) {
			*filteredMenuEntries = append(*filteredMenuEntries, menuEntry)
		}
	}
	//}
}

/*
 * Functions that generate functions that generate content that can be used in templates.
 * type TemplateValues map[string]string
 * type TemplateValueGenerator func(*web.Context) TemplateValues
 * type TemplateValueGeneratorFactory func(*UserState) TemplateValueGenerator
 */
// TODO: Take the same parameters as the old menu generating code
// TODO: Put one if these in each engine then combine them somehow
// TODO: Check for the menyEntry.url first, then check the rights, not the other way around
// TODO: Fix and refactor this one
// TODO: Check the user status _once_, and the admin status _once_, then generate the menu
// TODO: Some way of marking menu entries as user, admin or other rights. Add a group system?
func DynamicMenuFactoryGenerator(menuEntries MenuEntries) TemplateValueGeneratorFactory {
	return func(state *permissions.UserState) webhandle.TemplateValueGenerator {
		return func(ctx *web.Context) onthefly.TemplateValues {

			userRights := state.UserRights(ctx.Request)
			adminRights := state.AdminRights(ctx.Request)

			var filteredMenuEntries MenuEntries
			var logoutEntry *MenuEntry = nil

			// Build up filteredMenuEntries based on what should be shown or not
			for _, menuEntry := range menuEntries {

				// Don't add duplicates
				if HasEntry(menuEntry, filteredMenuEntries) {
					continue
				}

				// Add this one last
				if menuEntry.url == "/logout" {
					if userRights {
						logoutEntry = menuEntry
					}
					continue
				}

				// Always show the Overview menu
				AddIfNotAdded("/", &filteredMenuEntries, menuEntry)
				//if menuEntry.url == "/" {
				//	if !HasEntry(menuEntry, filteredMenuEntries) {
				//		filteredMenuEntries = append(filteredMenuEntries, menuEntry)
				//	}
				//}

				// If logged in, show Logout and the content
				if userRights {

					// Add every link except the current page we're on
					//if menuEntry.url != currentMenuURL {
					if !HasEntry(menuEntry, filteredMenuEntries) {
						if (menuEntry.url != "/login") && (menuEntry.url != "/register") && (menuEntry.url != "/admin") {
							filteredMenuEntries = append(filteredMenuEntries, menuEntry)
						}
					}
					//}

					// Show admin content
					if adminRights {
						AddIfNotAdded("/admin", &filteredMenuEntries, menuEntry)
					}
				} else {
					// Only show Login and Register
					AddIfNotAdded("/login", &filteredMenuEntries, menuEntry)
					AddIfNotAdded("/register", &filteredMenuEntries, menuEntry)
				}

			}

			if logoutEntry != nil {
				AddIfNotAdded("/logout", &filteredMenuEntries, logoutEntry)
			}

			page := MenuSnippet(filteredMenuEntries)
			retval := page.String()

			// TODO: Return the CSS as well somehow
			//css := page.CSS()

			return onthefly.TemplateValues{"menu": retval}
		}
	}
}

// Combines two TemplateValueGenerators into one TemplateValueGenerator by adding the strings per key
func TemplateValueGeneratorCombinator(tvg1, tvg2 webhandle.TemplateValueGenerator) webhandle.TemplateValueGenerator {
	return func(ctx *web.Context) onthefly.TemplateValues {
		tv1 := tvg1(ctx)
		tv2 := tvg2(ctx)
		for key, value := range tv2 {
			// TODO: Check if key exists in tv1 first
			tv1[key] += value
		}
		return tv1
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Vic Demuzere
//
// Use of this source code is governed by the MIT license.

package irc

import (
	"bytes"
	"strings"
)

// Various constants used for formatting IRC messages.
const (
	prefix     byte = 0x3A // Prefix or last argument
	prefixUser byte = 0x21 // Username
	prefixHost byte = 0x40 // Hostname
	space      byte = 0x20 // Separator

	maxLength = 510 // Maximum length is 512 - 2 for the line endings.
)

func cutsetFunc(r rune) bool {
	// Characters to trim from prefixes/messages.
	return r == '\r' || r == '\n' || r == '\x20' || r == '\x00'
}

// Objects implementing the Sender interface are able to send messages to an IRC server.
//
// As there might be a message queue, it is possible that Send returns a nil
// error, but the message is not sent (yet). The error value is only used when
// it is certain that sending the message is impossible.
//
// This interface is not used inside this package, and shouldn't have been
// defined here in the first place. For backwards compatibility only.
type Sender interface {
	Send(*Message) error
}

// Prefix represents the prefix (sender) of an IRC message.
// See RFC1459 section 2.3.1.
//
//    <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
//
type Prefix struct {
	Name string // Nick- or servername
	User string // Username
	Host string // Hostname
}

// ParsePrefix takes a string and attempts to create a Prefix struct.
func ParsePrefix(raw string) (p *Prefix) {

	p = new(Prefix)

	user := indexByte(raw, prefixUser)
	host := indexByte(raw, prefixHost)

	switch {

	case user > 0 && host > user:
		p.Name = raw[:user]
		p.User = raw[user+1 : host]
		p.Host = raw[host+1:]

	case user > 0:
		p.Name = raw[:user]
		p.User = raw[user+1:]

	case host > 0:
		p.Name = raw[:host]
		p.Host = raw[host+1:]

	default:
		p.Name = raw

	}

	return p
}

// Len calculates the length of the string representation of this prefix.
func (p *Prefix) Len() (length int) {
	length = len(p.Name)
	if len(p.User) > 0 {
		length = length + len(p.User) + 1
	}
	if len(p.Host) > 0 {
		length = length + len(p.Host) + 1
	}
	return
}

// String returns a string representation of this prefix.
func (p *Prefix) String() (s string) {
	// Benchmarks revealed that in this case simple string concatenation
	// is actually faster than using a ByteBuffer as in (*Message).String()
	s = p.Name
	if len(p.User) > 0 {
		s = s + string(prefixUser) + p.User
	}
	if len(p.Host) > 0 {
		s = s + string(prefixHost) + p.Host
	}
	return
}

// IsHostmask returns true if this prefix looks like a user hostmask.
func (p *Prefix) IsHostmask() bool {
	return len(p.User) > 0 && len(p.Host) > 0
}

// IsServer returns true if this prefix looks like a server name.
func (p *Prefix) IsServer() bool {
	return len(p.User) <= 0 && len(p.Host) <= 0 // && indexByte(p.Name, '.') > 0
}

// writeTo is an utility function to write the prefix to the bytes.Buffer in Message.String().
func (p *Prefix) writeTo(buffer *bytes.Buffer) {
	buffer.WriteString(p.Name)
	if len(p.User) > 0 {
		buffer.WriteByte(prefixUser)
		buffer.WriteString(p.User)
	}
	if len(p.Host) > 0 {
		buffer.WriteByte(prefixHost)
		buffer.WriteString(p.Host)
	}
	return
}

// Message represents an IRC protocol message.
// See RFC1459 section 2.3.1.
//
//    <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
//    <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
//    <command>  ::= <letter> { <letter> } | <number> <number> <number>
//    <SPACE>    ::= ' ' { ' ' }
//    <params>   ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
//
//    <middle>   ::= <Any *non-empty* sequence of octets not including SPACE
//                   or NUL or CR or LF, the first of which may not be ':'>
//    <trailing> ::= <Any, possibly *empty*, sequence of octets not including
//                   NUL or CR or LF>
//
//    <crlf>     ::= CR LF
type Message struct {
	*Prefix
	Command  string
	Params   []string
	Trailing string

	// When set to true, the trailing prefix (:) will be added even if the trailing message is empty.
	EmptyTrailing bool
}

// ParseMessage takes a string and attempts to create a Message struct.
// Returns nil if the Message is invalid.
func ParseMessage(raw string) (m *Message) {

	// Ignore empty messages.
	if raw = strings.TrimFunc(raw, cutsetFunc); len(raw) < 2 {
		return nil
	}

	i, j := 0, 0

	m = new(Message)

	if raw[0] == prefix {

		// Prefix ends with a space.
		i = indexByte(raw, space)

		// Prefix string must not be empty if the indicator is present.
		if i < 2 {
			return nil
		}

		m.Prefix = ParsePrefix(raw[1:i])

		// Skip space at the end of the prefix
		i++
	}

	// Find end of command
	j = i + indexByte(raw[i:], space)

	// Extract command
	if j > i {
		m.Command = raw[i:j]
	} else {
		m.Command = raw[i:]

		// We're done here!
		return m
	}

	// Skip space after command
	j++

	// Find prefix for trailer
	i = indexByte(raw[j:], prefix)

	if i < 0 {

		// There is no trailing argument!
		m.Params = strings.Split(raw[j:], string(space))

		// We're done here!
		return m
	}

	// Compensate for index on substring
	i = i + j

	// Check if we need to parse arguments.
	if i > j {
		m.Params = strings.Split(raw[j:i-1], string(space))
	}

	m.Trailing = raw[i+1:]

	// We need to re-encode the trailing argument even if it was empty.
	if len(m.Trailing) <= 0 {
		m.EmptyTrailing = true
	}

	return m

}

// Len calculates the length of the string representation of this message.
func (m *Message) Len() (length int) {

	if m.Prefix != nil {
		length = m.Prefix.Len() + 2 // Include prefix and trailing space
	}

	length = length + len(m.Command)

	if len(m.Params) > 0 {
		length = length + len(m.Params)
		for _, param := range m.Params {
			length = length + len(param)
		}
	}

	if len(m.Trailing) > 0 || m.EmptyTrailing {
		length = length + len(m.Trailing) + 2 // Include prefix and space
	}

	return
}

// Bytes returns a []byte representation of this message.
//
// As noted in rfc2812 section 2.3, messages should not exceed 512 characters
// in length. This method forces that limit by discarding any characters
// exceeding the length limit.
func (m *Message) Bytes() []byte {

	buffer := new(bytes.Buffer)

	// Message prefix
	if m.Prefix != nil {
		buffer.WriteByte(prefix)
		m.Prefix.writeTo(buffer)
		buffer.WriteByte(space)
	}

	// Command is required
	buffer.WriteString(m.Command)

	// Space separated list of arguments
	if len(m.Params) > 0 {
		buffer.WriteByte(space)
		buffer.WriteString(strings.Join(m.Params, string(space)))
	}

	if len(m.Trailing) > 0 || m.EmptyTrailing {
		buffer.WriteByte(space)
		buffer.WriteByte(prefix)
		buffer.WriteString(m.Trailing)
	}

	// We need the limit the buffer length.
	if buffer.Len() > (maxLength) {
		buffer.Truncate(maxLength)
	}

	return buffer.Bytes()
}

// String returns a string representation of this message.
//
// As noted in rfc2812 section 2.3, messages should not exceed 512 characters
// in length. This method forces that limit by discarding any characters
// exceeding the length limit.
func (m *Message) String() string {
	return string(m.Bytes())
}
<commit_msg>Add Bytes() method to the Prefix struct.<commit_after>// Copyright 2014 Vic Demuzere
//
// Use of this source code is governed by the MIT license.

package irc

import (
	"bytes"
	"strings"
)

// Various constants used for formatting IRC messages.
const (
	prefix     byte = 0x3A // Prefix or last argument
	prefixUser byte = 0x21 // Username
	prefixHost byte = 0x40 // Hostname
	space      byte = 0x20 // Separator

	maxLength = 510 // Maximum length is 512 - 2 for the line endings.
)

func cutsetFunc(r rune) bool {
	// Characters to trim from prefixes/messages.
	return r == '\r' || r == '\n' || r == '\x20' || r == '\x00'
}

// Objects implementing the Sender interface are able to send messages to an IRC server.
//
// As there might be a message queue, it is possible that Send returns a nil
// error, but the message is not sent (yet). The error value is only used when
// it is certain that sending the message is impossible.
//
// This interface is not used inside this package, and shouldn't have been
// defined here in the first place. For backwards compatibility only.
type Sender interface {
	Send(*Message) error
}

// Prefix represents the prefix (sender) of an IRC message.
// See RFC1459 section 2.3.1.
//
//    <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
//
type Prefix struct {
	Name string // Nick- or servername
	User string // Username
	Host string // Hostname
}

// ParsePrefix takes a string and attempts to create a Prefix struct.
func ParsePrefix(raw string) (p *Prefix) {

	p = new(Prefix)

	user := indexByte(raw, prefixUser)
	host := indexByte(raw, prefixHost)

	switch {

	case user > 0 && host > user:
		p.Name = raw[:user]
		p.User = raw[user+1 : host]
		p.Host = raw[host+1:]

	case user > 0:
		p.Name = raw[:user]
		p.User = raw[user+1:]

	case host > 0:
		p.Name = raw[:host]
		p.Host = raw[host+1:]

	default:
		p.Name = raw

	}

	return p
}

// Len calculates the length of the string representation of this prefix.
func (p *Prefix) Len() (length int) {
	length = len(p.Name)
	if len(p.User) > 0 {
		length = length + len(p.User) + 1
	}
	if len(p.Host) > 0 {
		length = length + len(p.Host) + 1
	}
	return
}

// Bytes returns a []byte representation of this prefix.
func (p *Prefix) Bytes() []byte {
	buffer := new(bytes.Buffer)
	p.writeTo(buffer)
	return buffer.Bytes()
}

// String returns a string representation of this prefix.
func (p *Prefix) String() (s string) {
	// Benchmarks revealed that in this case simple string concatenation
	// is actually faster than using a ByteBuffer as in (*Message).String()
	s = p.Name
	if len(p.User) > 0 {
		s = s + string(prefixUser) + p.User
	}
	if len(p.Host) > 0 {
		s = s + string(prefixHost) + p.Host
	}
	return
}

// IsHostmask returns true if this prefix looks like a user hostmask.
func (p *Prefix) IsHostmask() bool {
	return len(p.User) > 0 && len(p.Host) > 0
}

// IsServer returns true if this prefix looks like a server name.
func (p *Prefix) IsServer() bool {
	return len(p.User) <= 0 && len(p.Host) <= 0 // && indexByte(p.Name, '.') > 0
}

// writeTo is an utility function to write the prefix to the bytes.Buffer in Message.String().
func (p *Prefix) writeTo(buffer *bytes.Buffer) {
	buffer.WriteString(p.Name)
	if len(p.User) > 0 {
		buffer.WriteByte(prefixUser)
		buffer.WriteString(p.User)
	}
	if len(p.Host) > 0 {
		buffer.WriteByte(prefixHost)
		buffer.WriteString(p.Host)
	}
	return
}

// Message represents an IRC protocol message.
// See RFC1459 section 2.3.1.
//
//    <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
//    <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
//    <command>  ::= <letter> { <letter> } | <number> <number> <number>
//    <SPACE>    ::= ' ' { ' ' }
//    <params>   ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
//
//    <middle>   ::= <Any *non-empty* sequence of octets not including SPACE
//                   or NUL or CR or LF, the first of which may not be ':'>
//    <trailing> ::= <Any, possibly *empty*, sequence of octets not including
//                   NUL or CR or LF>
//
//    <crlf>     ::= CR LF
type Message struct {
	*Prefix
	Command  string
	Params   []string
	Trailing string

	// When set to true, the trailing prefix (:) will be added even if the trailing message is empty.
	EmptyTrailing bool
}

// ParseMessage takes a string and attempts to create a Message struct.
// Returns nil if the Message is invalid.
func ParseMessage(raw string) (m *Message) {

	// Ignore empty messages.
	if raw = strings.TrimFunc(raw, cutsetFunc); len(raw) < 2 {
		return nil
	}

	i, j := 0, 0

	m = new(Message)

	if raw[0] == prefix {

		// Prefix ends with a space.
		i = indexByte(raw, space)

		// Prefix string must not be empty if the indicator is present.
		if i < 2 {
			return nil
		}

		m.Prefix = ParsePrefix(raw[1:i])

		// Skip space at the end of the prefix
		i++
	}

	// Find end of command
	j = i + indexByte(raw[i:], space)

	// Extract command
	if j > i {
		m.Command = raw[i:j]
	} else {
		m.Command = raw[i:]

		// We're done here!
		return m
	}

	// Skip space after command
	j++

	// Find prefix for trailer
	i = indexByte(raw[j:], prefix)

	if i < 0 {

		// There is no trailing argument!
		m.Params = strings.Split(raw[j:], string(space))

		// We're done here!
		return m
	}

	// Compensate for index on substring
	i = i + j

	// Check if we need to parse arguments.
	if i > j {
		m.Params = strings.Split(raw[j:i-1], string(space))
	}

	m.Trailing = raw[i+1:]

	// We need to re-encode the trailing argument even if it was empty.
	if len(m.Trailing) <= 0 {
		m.EmptyTrailing = true
	}

	return m

}

// Len calculates the length of the string representation of this message.
func (m *Message) Len() (length int) {

	if m.Prefix != nil {
		length = m.Prefix.Len() + 2 // Include prefix and trailing space
	}

	length = length + len(m.Command)

	if len(m.Params) > 0 {
		length = length + len(m.Params)
		for _, param := range m.Params {
			length = length + len(param)
		}
	}

	if len(m.Trailing) > 0 || m.EmptyTrailing {
		length = length + len(m.Trailing) + 2 // Include prefix and space
	}

	return
}

// Bytes returns a []byte representation of this message.
//
// As noted in rfc2812 section 2.3, messages should not exceed 512 characters
// in length. This method forces that limit by discarding any characters
// exceeding the length limit.
func (m *Message) Bytes() []byte {

	buffer := new(bytes.Buffer)

	// Message prefix
	if m.Prefix != nil {
		buffer.WriteByte(prefix)
		m.Prefix.writeTo(buffer)
		buffer.WriteByte(space)
	}

	// Command is required
	buffer.WriteString(m.Command)

	// Space separated list of arguments
	if len(m.Params) > 0 {
		buffer.WriteByte(space)
		buffer.WriteString(strings.Join(m.Params, string(space)))
	}

	if len(m.Trailing) > 0 || m.EmptyTrailing {
		buffer.WriteByte(space)
		buffer.WriteByte(prefix)
		buffer.WriteString(m.Trailing)
	}

	// We need the limit the buffer length.
	if buffer.Len() > (maxLength) {
		buffer.Truncate(maxLength)
	}

	return buffer.Bytes()
}

// String returns a string representation of this message.
//
// As noted in rfc2812 section 2.3, messages should not exceed 512 characters
// in length. This method forces that limit by discarding any characters
// exceeding the length limit.
func (m *Message) String() string {
	return string(m.Bytes())
}
<|endoftext|>


<commit_before>package analytics

import (
	"encoding/json"
	"time"
)

// Values implementing this interface are used by analytics clients to notify
// the application when a message send succeeded or failed.
type Callback interface {

	// This method is called for every message that was successfully sent to
	// the API.
	Success(Message)

	// This method is called for every message that failed to be sent to the
	// API and will be discarded by the client.
	Failure(Message, error)
}

// This interface is used to represent analytics objects that can be sent via
// a client.
//
// Types like analytics.Track, analytics.Page, etc... implement this interface
// and therefore can be passed to the analytics.Client.Send method.
type Message interface {

	// Validates the internal structure of the message, the method must return
	// nil if the message is valid, or an error describing what went wrong.
	validate() error
}

// Takes a message id as first argument and returns it, unless it's the zero-
// value, in that case the default id passed as second argument is returned.
func makeMessageId(id string, def string) string {
	if len(id) == 0 {
		return def
	}
	return id
}

// Returns the time value passed as first argument, unless it's the zero-value,
// in that case the default value passed as second argument is returned.
func makeTimestamp(t time.Time, def time.Time) time.Time {
	if t == (time.Time{}) {
		return def
	}
	return t
}

// This structure represents objects sent to the /v1/batch endpoint. We don't
// export this type because it's only meant to be used internally to send groups
// of messages in one API call.
type batch struct {
	MessageId string    `json:"messageId"`
	SentAt    time.Time `json:"sentAt"`
	Messages  []message `json:"batch"`
	Context   *Context  `json:"context"`
}

type message struct {
	msg  Message
	json []byte
}

func makeMessage(m Message, maxBytes int) (msg message, err error) {
	if msg.json, err = json.Marshal(m); err == nil {
		if len(msg.json) > maxBytes {
			err = ErrMessageTooBig
		} else {
			msg.msg = m
		}
	}
	return
}

func (m message) MarshalJSON() ([]byte, error) {
	return m.json, nil
}

func (m message) size() int {
	// The `+ 1` is for the comma that sits between each items of a JSON array.
	return len(m.json) + 1
}

type messageQueue struct {
	pending       []message
	bytes         int
	maxBatchSize  int
	maxBatchBytes int
}

func (q *messageQueue) push(m message) (b []message) {
	if (q.bytes + m.size()) > q.maxBatchBytes {
		b = q.flush()
	}

	if q.pending == nil {
		q.pending = make([]message, 0, q.maxBatchSize)
	}

	q.pending = append(q.pending, m)
	q.bytes += len(m.json)

	if b == nil && len(q.pending) == q.maxBatchSize {
		b = q.flush()
	}

	return
}

func (q *messageQueue) flush() (msgs []message) {
	msgs, q.pending, q.bytes = q.pending, nil, 0
	return
}

const (
	maxBatchBytes   = 500000
	maxMessageBytes = 15000
)
<commit_msg>define callback policy<commit_after>package analytics

import (
	"encoding/json"
	"time"
)

// Values implementing this interface are used by analytics clients to notify
// the application when a message send succeeded or failed.
//
// Callback methods are called by a client's internal goroutines, there are no
// guarantees on which goroutine will trigger the callbacks, they could be made
// sequentially or in parallel, the order doesn't depend on the order of the
// calls to `Enqueue`.
//
// Callback methods should return quickly and not cause long blocking operations
// to avoid interferring with the client's internal work flow.
type Callback interface {

	// This method is called for every message that was successfully sent to
	// the API.
	Success(Message)

	// This method is called for every message that failed to be sent to the
	// API and will be discarded by the client.
	Failure(Message, error)
}

// This interface is used to represent analytics objects that can be sent via
// a client.
//
// Types like analytics.Track, analytics.Page, etc... implement this interface
// and therefore can be passed to the analytics.Client.Send method.
type Message interface {

	// Validates the internal structure of the message, the method must return
	// nil if the message is valid, or an error describing what went wrong.
	validate() error
}

// Takes a message id as first argument and returns it, unless it's the zero-
// value, in that case the default id passed as second argument is returned.
func makeMessageId(id string, def string) string {
	if len(id) == 0 {
		return def
	}
	return id
}

// Returns the time value passed as first argument, unless it's the zero-value,
// in that case the default value passed as second argument is returned.
func makeTimestamp(t time.Time, def time.Time) time.Time {
	if t == (time.Time{}) {
		return def
	}
	return t
}

// This structure represents objects sent to the /v1/batch endpoint. We don't
// export this type because it's only meant to be used internally to send groups
// of messages in one API call.
type batch struct {
	MessageId string    `json:"messageId"`
	SentAt    time.Time `json:"sentAt"`
	Messages  []message `json:"batch"`
	Context   *Context  `json:"context"`
}

type message struct {
	msg  Message
	json []byte
}

func makeMessage(m Message, maxBytes int) (msg message, err error) {
	if msg.json, err = json.Marshal(m); err == nil {
		if len(msg.json) > maxBytes {
			err = ErrMessageTooBig
		} else {
			msg.msg = m
		}
	}
	return
}

func (m message) MarshalJSON() ([]byte, error) {
	return m.json, nil
}

func (m message) size() int {
	// The `+ 1` is for the comma that sits between each items of a JSON array.
	return len(m.json) + 1
}

type messageQueue struct {
	pending       []message
	bytes         int
	maxBatchSize  int
	maxBatchBytes int
}

func (q *messageQueue) push(m message) (b []message) {
	if (q.bytes + m.size()) > q.maxBatchBytes {
		b = q.flush()
	}

	if q.pending == nil {
		q.pending = make([]message, 0, q.maxBatchSize)
	}

	q.pending = append(q.pending, m)
	q.bytes += len(m.json)

	if b == nil && len(q.pending) == q.maxBatchSize {
		b = q.flush()
	}

	return
}

func (q *messageQueue) flush() (msgs []message) {
	msgs, q.pending, q.bytes = q.pending, nil, 0
	return
}

const (
	maxBatchBytes   = 500000
	maxMessageBytes = 15000
)
<|endoftext|>


<commit_before>package mitake

import (
	"bufio"
	"errors"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"golang.org/x/text/encoding/traditionalchinese"
	"golang.org/x/text/transform"
)

// StatusCode of Mitake API.
type StatusCode string

func (c StatusCode) String() string {
	return statusCodeMap[c]
}

// List of Mitake API status codes
const (
	StatusServiceError                  = StatusCode("*")
	StatusSMSTemporarilyUnavailable     = StatusCode("a")
	StatusSMSTemporarilyUnavailableB    = StatusCode("b")
	StatusUsernameRequired              = StatusCode("c")
	StatusPasswordRequired              = StatusCode("d")
	StatusUsernameOrPasswordError       = StatusCode("e")
	StatusAccountExpired                = StatusCode("f")
	StatusAccountDisabled               = StatusCode("h")
	StatusInvalidConnectionAddress      = StatusCode("k")
	StatusChangePasswordRequired        = StatusCode("m")
	StatusPasswordExpired               = StatusCode("n")
	StatusPermissionDenied              = StatusCode("p")
	StatusServiceTemporarilyUnavailable = StatusCode("r")
	StatusAccountingFailure             = StatusCode("s")
	StatusSMSExpired                    = StatusCode("t")
	StatusSMSBodyEmpty                  = StatusCode("u")
	StatusInvalidPhoneNumber            = StatusCode("v")
	StatusReservationForDelivery        = StatusCode("0")
	StatusCarrierAccepted               = StatusCode("1")
	StatusCarrierAccepted2              = StatusCode("2")
	StatusCarrierAccepted3              = StatusCode("3")
	StatusDelivered                     = StatusCode("4")
	StatusContentError                  = StatusCode("5")
	StatusPhoneNumberError              = StatusCode("6")
	StatusSMSDisable                    = StatusCode("7")
	StatusDeliveryTimeout               = StatusCode("8")
	StatusReservationCanceled           = StatusCode("9")
)

var statusCodeMap = map[StatusCode]string{
	StatusServiceError:                  "系統發生錯誤，請聯絡三竹資訊窗口人員",
	StatusSMSTemporarilyUnavailable:     "簡訊發送功能暫時停止服務，請稍候再試",
	StatusSMSTemporarilyUnavailableB:    "簡訊發送功能暫時停止服務，請稍候再試",
	StatusUsernameRequired:              "請輸入帳號",
	StatusPasswordRequired:              "請輸入密碼",
	StatusUsernameOrPasswordError:       "帳號、密碼錯誤",
	StatusAccountExpired:                "帳號已過期",
	StatusAccountDisabled:               "帳號已被停用",
	StatusInvalidConnectionAddress:      "無效的連線位址",
	StatusChangePasswordRequired:        "必須變更密碼，在變更密碼前，無法使用簡訊發送服務",
	StatusPasswordExpired:               "密碼已逾期，在變更密碼前，將無法使用簡訊發送服務",
	StatusPermissionDenied:              "沒有權限使用外部Http程式",
	StatusServiceTemporarilyUnavailable: "系統暫停服務，請稍後再試",
	StatusAccountingFailure:             "帳務處理失敗，無法發送簡訊",
	StatusSMSExpired:                    "簡訊已過期",
	StatusSMSBodyEmpty:                  "簡訊內容不得為空白",
	StatusInvalidPhoneNumber:            "無效的手機號碼",
	StatusReservationForDelivery:        "預約傳送中",
	StatusCarrierAccepted:               "已送達業者",
	StatusCarrierAccepted2:              "已送達業者",
	StatusCarrierAccepted3:              "已送達業者",
	StatusDelivered:                     "已送達手機",
	StatusContentError:                  "內容有錯誤",
	StatusPhoneNumberError:              "門號有錯誤",
	StatusSMSDisable:                    "簡訊已停用",
	StatusDeliveryTimeout:               "逾時無送達",
	StatusReservationCanceled:           "預約已取消",
}

// Message represents an SMS object.
type Message struct {
	ID       string `json:"id"`       // Default ID of the message
	Dstaddr  string `json:"dstaddr"`  // Destination phone number
	Destname string `json:"destname"` // Destination receiver name
	Smbody   string `json:"smbody"`   // The text of the message you want to send
	Dlvtime  string `json:"dlvtime"`  // Optional, Delivery time
	Vldtime  string `json:"vldtime"`  // Optional
	Response string `json:"response"` // Optional, Callback URL to receive the delivery receipt of the message
}

// ToINI returns the INI format string from the message fields.
func (m Message) ToINI() string {
	smbody := strings.Replace(m.Smbody, "\n", string(byte(6)), -1)

	var ini string
	ini += "dstaddr=" + m.Dstaddr + "\n"
	ini += "smbody=" + smbody + "\n"
	if m.Dlvtime != "" {
		ini += "dlvtime=" + m.Dlvtime + "\n"
	}
	if m.Vldtime != "" {
		ini += "vldtime=" + m.Vldtime + "\n"
	}
	if m.Response != "" {
		ini += "response=" + m.Response + "\n"
	}
	return ini
}

// ToLongMessage returns the format string for Long SMS.
func (m Message) ToLongMessage() string {
	var ini string
	ini += m.Dstaddr + "$$"
	if m.Dlvtime != "" {
		ini += m.Dlvtime
	}
	ini += "$$"
	if m.Vldtime != "" {
		ini += m.Vldtime
	}
	ini += "$$"
	if m.Destname != "" {
		ini += m.Destname
	}
	ini += "$$"
	if m.Response != "" {
		ini += m.Response
	}
	ini += "$$"
	ini += m.Smbody + "\n"
	return ini
}

type MessageResult struct {
	Msgid        string     `json:"msgid"`
	Statuscode   string     `json:"statuscode"`
	Statusstring StatusCode `json:"statusstring"`
}

type MessageResponse struct {
	Results      []*MessageResult
	AccountPoint int
	INI          string `json:"-"`
}

func parseMessageResponse(body io.Reader) (*MessageResponse, error) {
	var (
		scanner  = bufio.NewScanner(transform.NewReader(body, traditionalchinese.Big5.NewDecoder()))
		response = new(MessageResponse)
		result   *MessageResult
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())
		response.INI += text + "\n"

		if matched, _ := regexp.MatchString(`^\[\d+]$`, text); matched {
			result = new(MessageResult)
			response.Results = append(response.Results, result)
		} else {
			strs := strings.Split(text, "=")
			switch strs[0] {
			case "msgid":
				result.Msgid = strs[1]
			case "statuscode":
				result.Statusstring = StatusCode(strs[1])
				result.Statuscode = strs[1]
			case "AccountPoint":
				response.AccountPoint, _ = strconv.Atoi(strs[1])
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

func parseLongMessageResponse(body io.Reader) (*MessageResponse, error) {
	var (
		scanner  = bufio.NewScanner(transform.NewReader(body, traditionalchinese.Big5.NewDecoder()))
		response = new(MessageResponse)
		result   *MessageResult
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())
		response.INI += text + "\n"

		if matched, _ := regexp.MatchString(`^\[[a-zA-z0-9]+\]$`, text); matched {
			result = new(MessageResult)
			response.Results = append(response.Results, result)
		} else {
			strs := strings.Split(text, "=")
			switch strs[0] {
			case "msgid":
				result.Msgid = strs[1]
			case "statuscode":
				result.Statusstring = StatusCode(strs[1])
				result.Statuscode = strs[1]
			case "AccountPoint":
				response.AccountPoint, _ = strconv.Atoi(strs[1])
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

type MessageStatus struct {
	MessageResult
	StatusTime string `json:"statustime"`
}

type MessageStatusResponse struct {
	Statuses []*MessageStatus
	INI      string `json:"-"`
}

func parseMessageStatusResponse(body io.Reader) (*MessageStatusResponse, error) {
	var (
		scanner  = bufio.NewScanner(transform.NewReader(body, traditionalchinese.Big5.NewDecoder()))
		response = new(MessageStatusResponse)
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())
		response.INI += text + "\n"

		strs := strings.Split(text, "\t")
		response.Statuses = append(response.Statuses, &MessageStatus{
			MessageResult: MessageResult{
				Msgid:        strs[0],
				Statusstring: StatusCode(strs[1]),
				Statuscode:   strs[1],
			},
			StatusTime: strs[2],
		})
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

func parseCancelMessageStatusResponse(body io.Reader) (*MessageStatusResponse, error) {
	var (
		scanner  = bufio.NewScanner(transform.NewReader(body, traditionalchinese.Big5.NewDecoder()))
		response = new(MessageStatusResponse)
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())
		response.INI += text + "\n"

		strs := strings.Split(text, "=")
		response.Statuses = append(response.Statuses, &MessageStatus{
			MessageResult: MessageResult{
				Msgid:        strs[0],
				Statusstring: StatusCode(strs[1]),
				Statuscode:   strs[1],
			},
		})
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

// MessageReceipt represents a message delivery receipt.
type MessageReceipt struct {
	Msgid        string     `json:"msgid"`
	Dstaddr      string     `json:"dstaddr"`
	Dlvtime      string     `json:"dlvtime"`
	Donetime     string     `json:"donetime"`
	Statuscode   string     `json:"statuscode"`
	Statusstring StatusCode `json:"statusstring"`
	Statusstr    string     `json:"statusstr"`
	StatusFlag   string     `json:"StatusFlag"`
}

// ParseMessageReceipt parse an incoming Mitake callback request and return the MessageReceipt.
//
// Example usage:
//
// 	func Callback(w http.ResponseWriter, r *http.Request) {
// 		receipt, err := mitake.ParseMessageReceipt(r)
// 		if err != nil { ... }
//		// Process message receipt
// 	}
//
func ParseMessageReceipt(r *http.Request) (*MessageReceipt, error) {
	values := r.URL.Query()
	if values.Get("msgid") == "" {
		return nil, errors.New("receipt not found")
	}
	return &MessageReceipt{
		Msgid:        values.Get("msgid"),
		Dstaddr:      values.Get("dstaddr"),
		Dlvtime:      values.Get("dlvtime"),
		Donetime:     values.Get("donetime"),
		Statuscode:   values.Get("statuscode"),
		Statusstring: StatusCode(values.Get("statuscode")),
		Statusstr:    values.Get("statusstr"),
		StatusFlag:   values.Get("StatusFlag"),
	}, nil
}
<commit_msg>Add comment for exported types<commit_after>package mitake

import (
	"bufio"
	"errors"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"golang.org/x/text/encoding/traditionalchinese"
	"golang.org/x/text/transform"
)

// StatusCode of Mitake API.
type StatusCode string

func (c StatusCode) String() string {
	return statusCodeMap[c]
}

// List of Mitake API status codes
const (
	StatusServiceError                  = StatusCode("*")
	StatusSMSTemporarilyUnavailable     = StatusCode("a")
	StatusSMSTemporarilyUnavailableB    = StatusCode("b")
	StatusUsernameRequired              = StatusCode("c")
	StatusPasswordRequired              = StatusCode("d")
	StatusUsernameOrPasswordError       = StatusCode("e")
	StatusAccountExpired                = StatusCode("f")
	StatusAccountDisabled               = StatusCode("h")
	StatusInvalidConnectionAddress      = StatusCode("k")
	StatusChangePasswordRequired        = StatusCode("m")
	StatusPasswordExpired               = StatusCode("n")
	StatusPermissionDenied              = StatusCode("p")
	StatusServiceTemporarilyUnavailable = StatusCode("r")
	StatusAccountingFailure             = StatusCode("s")
	StatusSMSExpired                    = StatusCode("t")
	StatusSMSBodyEmpty                  = StatusCode("u")
	StatusInvalidPhoneNumber            = StatusCode("v")
	StatusReservationForDelivery        = StatusCode("0")
	StatusCarrierAccepted               = StatusCode("1")
	StatusCarrierAccepted2              = StatusCode("2")
	StatusCarrierAccepted3              = StatusCode("3")
	StatusDelivered                     = StatusCode("4")
	StatusContentError                  = StatusCode("5")
	StatusPhoneNumberError              = StatusCode("6")
	StatusSMSDisable                    = StatusCode("7")
	StatusDeliveryTimeout               = StatusCode("8")
	StatusReservationCanceled           = StatusCode("9")
)

var statusCodeMap = map[StatusCode]string{
	StatusServiceError:                  "系統發生錯誤，請聯絡三竹資訊窗口人員",
	StatusSMSTemporarilyUnavailable:     "簡訊發送功能暫時停止服務，請稍候再試",
	StatusSMSTemporarilyUnavailableB:    "簡訊發送功能暫時停止服務，請稍候再試",
	StatusUsernameRequired:              "請輸入帳號",
	StatusPasswordRequired:              "請輸入密碼",
	StatusUsernameOrPasswordError:       "帳號、密碼錯誤",
	StatusAccountExpired:                "帳號已過期",
	StatusAccountDisabled:               "帳號已被停用",
	StatusInvalidConnectionAddress:      "無效的連線位址",
	StatusChangePasswordRequired:        "必須變更密碼，在變更密碼前，無法使用簡訊發送服務",
	StatusPasswordExpired:               "密碼已逾期，在變更密碼前，將無法使用簡訊發送服務",
	StatusPermissionDenied:              "沒有權限使用外部Http程式",
	StatusServiceTemporarilyUnavailable: "系統暫停服務，請稍後再試",
	StatusAccountingFailure:             "帳務處理失敗，無法發送簡訊",
	StatusSMSExpired:                    "簡訊已過期",
	StatusSMSBodyEmpty:                  "簡訊內容不得為空白",
	StatusInvalidPhoneNumber:            "無效的手機號碼",
	StatusReservationForDelivery:        "預約傳送中",
	StatusCarrierAccepted:               "已送達業者",
	StatusCarrierAccepted2:              "已送達業者",
	StatusCarrierAccepted3:              "已送達業者",
	StatusDelivered:                     "已送達手機",
	StatusContentError:                  "內容有錯誤",
	StatusPhoneNumberError:              "門號有錯誤",
	StatusSMSDisable:                    "簡訊已停用",
	StatusDeliveryTimeout:               "逾時無送達",
	StatusReservationCanceled:           "預約已取消",
}

// Message represents an SMS object.
type Message struct {
	ID       string `json:"id"`       // Default ID of the message
	Dstaddr  string `json:"dstaddr"`  // Destination phone number
	Destname string `json:"destname"` // Destination receiver name
	Smbody   string `json:"smbody"`   // The text of the message you want to send
	Dlvtime  string `json:"dlvtime"`  // Optional, Delivery time
	Vldtime  string `json:"vldtime"`  // Optional
	Response string `json:"response"` // Optional, Callback URL to receive the delivery receipt of the message
}

// ToINI returns the INI format string from the message fields.
func (m Message) ToINI() string {
	smbody := strings.Replace(m.Smbody, "\n", string(byte(6)), -1)

	var ini string
	ini += "dstaddr=" + m.Dstaddr + "\n"
	ini += "smbody=" + smbody + "\n"
	if m.Dlvtime != "" {
		ini += "dlvtime=" + m.Dlvtime + "\n"
	}
	if m.Vldtime != "" {
		ini += "vldtime=" + m.Vldtime + "\n"
	}
	if m.Response != "" {
		ini += "response=" + m.Response + "\n"
	}
	return ini
}

// ToLongMessage returns the format string for Long SMS.
func (m Message) ToLongMessage() string {
	var ini string
	ini += m.Dstaddr + "$$"
	if m.Dlvtime != "" {
		ini += m.Dlvtime
	}
	ini += "$$"
	if m.Vldtime != "" {
		ini += m.Vldtime
	}
	ini += "$$"
	if m.Destname != "" {
		ini += m.Destname
	}
	ini += "$$"
	if m.Response != "" {
		ini += m.Response
	}
	ini += "$$"
	ini += m.Smbody + "\n"
	return ini
}

// MessageResult represents result of send SMS.
type MessageResult struct {
	Msgid        string     `json:"msgid"`
	Statuscode   string     `json:"statuscode"`
	Statusstring StatusCode `json:"statusstring"`
}

// MessageResponse represents response of send SMS.
type MessageResponse struct {
	Results      []*MessageResult
	AccountPoint int
	INI          string `json:"-"`
}

func parseMessageResponse(body io.Reader) (*MessageResponse, error) {
	var (
		scanner  = bufio.NewScanner(transform.NewReader(body, traditionalchinese.Big5.NewDecoder()))
		response = new(MessageResponse)
		result   *MessageResult
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())
		response.INI += text + "\n"

		if matched, _ := regexp.MatchString(`^\[\d+]$`, text); matched {
			result = new(MessageResult)
			response.Results = append(response.Results, result)
		} else {
			strs := strings.Split(text, "=")
			switch strs[0] {
			case "msgid":
				result.Msgid = strs[1]
			case "statuscode":
				result.Statusstring = StatusCode(strs[1])
				result.Statuscode = strs[1]
			case "AccountPoint":
				response.AccountPoint, _ = strconv.Atoi(strs[1])
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

func parseLongMessageResponse(body io.Reader) (*MessageResponse, error) {
	var (
		scanner  = bufio.NewScanner(transform.NewReader(body, traditionalchinese.Big5.NewDecoder()))
		response = new(MessageResponse)
		result   *MessageResult
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())
		response.INI += text + "\n"

		if matched, _ := regexp.MatchString(`^\[[a-zA-z0-9]+\]$`, text); matched {
			result = new(MessageResult)
			response.Results = append(response.Results, result)
		} else {
			strs := strings.Split(text, "=")
			switch strs[0] {
			case "msgid":
				result.Msgid = strs[1]
			case "statuscode":
				result.Statusstring = StatusCode(strs[1])
				result.Statuscode = strs[1]
			case "AccountPoint":
				response.AccountPoint, _ = strconv.Atoi(strs[1])
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

// MessageStatus represents status of message.
type MessageStatus struct {
	MessageResult
	StatusTime string `json:"statustime"`
}

// MessageStatusResponse represents response of query message status.
type MessageStatusResponse struct {
	Statuses []*MessageStatus
	INI      string `json:"-"`
}

func parseMessageStatusResponse(body io.Reader) (*MessageStatusResponse, error) {
	var (
		scanner  = bufio.NewScanner(transform.NewReader(body, traditionalchinese.Big5.NewDecoder()))
		response = new(MessageStatusResponse)
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())
		response.INI += text + "\n"

		strs := strings.Split(text, "\t")
		response.Statuses = append(response.Statuses, &MessageStatus{
			MessageResult: MessageResult{
				Msgid:        strs[0],
				Statusstring: StatusCode(strs[1]),
				Statuscode:   strs[1],
			},
			StatusTime: strs[2],
		})
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

func parseCancelMessageStatusResponse(body io.Reader) (*MessageStatusResponse, error) {
	var (
		scanner  = bufio.NewScanner(transform.NewReader(body, traditionalchinese.Big5.NewDecoder()))
		response = new(MessageStatusResponse)
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())
		response.INI += text + "\n"

		strs := strings.Split(text, "=")
		response.Statuses = append(response.Statuses, &MessageStatus{
			MessageResult: MessageResult{
				Msgid:        strs[0],
				Statusstring: StatusCode(strs[1]),
				Statuscode:   strs[1],
			},
		})
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

// MessageReceipt represents a message delivery receipt.
type MessageReceipt struct {
	Msgid        string     `json:"msgid"`
	Dstaddr      string     `json:"dstaddr"`
	Dlvtime      string     `json:"dlvtime"`
	Donetime     string     `json:"donetime"`
	Statuscode   string     `json:"statuscode"`
	Statusstring StatusCode `json:"statusstring"`
	Statusstr    string     `json:"statusstr"`
	StatusFlag   string     `json:"StatusFlag"`
}

// ParseMessageReceipt parse an incoming Mitake callback request and return the MessageReceipt.
//
// Example usage:
//
// 	func Callback(w http.ResponseWriter, r *http.Request) {
// 		receipt, err := mitake.ParseMessageReceipt(r)
// 		if err != nil { ... }
//		// Process message receipt
// 	}
//
func ParseMessageReceipt(r *http.Request) (*MessageReceipt, error) {
	values := r.URL.Query()
	if values.Get("msgid") == "" {
		return nil, errors.New("receipt not found")
	}
	return &MessageReceipt{
		Msgid:        values.Get("msgid"),
		Dstaddr:      values.Get("dstaddr"),
		Dlvtime:      values.Get("dlvtime"),
		Donetime:     values.Get("donetime"),
		Statuscode:   values.Get("statuscode"),
		Statusstring: StatusCode(values.Get("statuscode")),
		Statusstr:    values.Get("statusstr"),
		StatusFlag:   values.Get("StatusFlag"),
	}, nil
}
<|endoftext|>


<commit_before>package sarama

import (
	"fmt"
	"time"
)

// The lowest 3 bits contain the compression codec used for the message
const compressionCodecMask int8 = 0x07

// Bit 3 set for "LogAppend" timestamps
const timestampTypeMask = 0x08

// CompressionCodec represents the various compression codecs recognized by Kafka in messages.
type CompressionCodec int8

const (
	CompressionNone   CompressionCodec = 0
	CompressionGZIP   CompressionCodec = 1
	CompressionSnappy CompressionCodec = 2
	CompressionLZ4    CompressionCodec = 3
	CompressionZSTD   CompressionCodec = 4
)

func (cc CompressionCodec) String() string {
	return []string{
		"none",
		"gzip",
		"snappy",
		"lz4",
	}[int(cc)]
}

// CompressionLevelDefault is the constant to use in CompressionLevel
// to have the default compression level for any codec. The value is picked
// that we don't use any existing compression levels.
const CompressionLevelDefault = -1000

type Message struct {
	Codec            CompressionCodec // codec used to compress the message contents
	CompressionLevel int              // compression level
	LogAppendTime    bool             // the used timestamp is LogAppendTime
	Key              []byte           // the message key, may be nil
	Value            []byte           // the message contents
	Set              *MessageSet      // the message set a message might wrap
	Version          int8             // v1 requires Kafka 0.10
	Timestamp        time.Time        // the timestamp of the message (version 1+ only)

	compressedCache []byte
	compressedSize  int // used for computing the compression ratio metrics
}

func (m *Message) encode(pe packetEncoder) error {
	pe.push(newCRC32Field(crcIEEE))

	pe.putInt8(m.Version)

	attributes := int8(m.Codec) & compressionCodecMask
	if m.LogAppendTime {
		attributes |= timestampTypeMask
	}
	pe.putInt8(attributes)

	if m.Version >= 1 {
		if err := (Timestamp{&m.Timestamp}).encode(pe); err != nil {
			return err
		}
	}

	err := pe.putBytes(m.Key)
	if err != nil {
		return err
	}

	var payload []byte

	if m.compressedCache != nil {
		payload = m.compressedCache
		m.compressedCache = nil
	} else if m.Value != nil {

		payload, err = compress(m.Codec, m.CompressionLevel, m.Value)
		if err != nil {
			return err
		}
		m.compressedCache = payload
		// Keep in mind the compressed payload size for metric gathering
		m.compressedSize = len(payload)
	}

	if err = pe.putBytes(payload); err != nil {
		return err
	}

	return pe.pop()
}

func (m *Message) decode(pd packetDecoder) (err error) {
	err = pd.push(newCRC32Field(crcIEEE))
	if err != nil {
		return err
	}

	m.Version, err = pd.getInt8()
	if err != nil {
		return err
	}

	if m.Version > 1 {
		return PacketDecodingError{fmt.Sprintf("unknown magic byte (%v)", m.Version)}
	}

	attribute, err := pd.getInt8()
	if err != nil {
		return err
	}
	m.Codec = CompressionCodec(attribute & compressionCodecMask)
	m.LogAppendTime = attribute&timestampTypeMask == timestampTypeMask

	if m.Version == 1 {
		if err := (Timestamp{&m.Timestamp}).decode(pd); err != nil {
			return err
		}
	}

	m.Key, err = pd.getBytes()
	if err != nil {
		return err
	}

	m.Value, err = pd.getBytes()
	if err != nil {
		return err
	}

	// Required for deep equal assertion during tests but might be useful
	// for future metrics about the compression ratio in fetch requests
	m.compressedSize = len(m.Value)

	switch m.Codec {
	case CompressionNone:
		// nothing to do
	default:
		if m.Value == nil {
			break
		}

		m.Value, err = decompress(m.Codec, m.Value)
		if err != nil {
			return err
		}
		if err := m.decodeSet(); err != nil {
			return err
		}
	}

	return pd.pop()
}

// decodes a message set from a previousy encoded bulk-message
func (m *Message) decodeSet() (err error) {
	pd := realDecoder{raw: m.Value}
	m.Set = &MessageSet{}
	return m.Set.decode(&pd)
}
<commit_msg>Fix previousy/previously typo<commit_after>package sarama

import (
	"fmt"
	"time"
)

// The lowest 3 bits contain the compression codec used for the message
const compressionCodecMask int8 = 0x07

// Bit 3 set for "LogAppend" timestamps
const timestampTypeMask = 0x08

// CompressionCodec represents the various compression codecs recognized by Kafka in messages.
type CompressionCodec int8

const (
	CompressionNone   CompressionCodec = 0
	CompressionGZIP   CompressionCodec = 1
	CompressionSnappy CompressionCodec = 2
	CompressionLZ4    CompressionCodec = 3
	CompressionZSTD   CompressionCodec = 4
)

func (cc CompressionCodec) String() string {
	return []string{
		"none",
		"gzip",
		"snappy",
		"lz4",
	}[int(cc)]
}

// CompressionLevelDefault is the constant to use in CompressionLevel
// to have the default compression level for any codec. The value is picked
// that we don't use any existing compression levels.
const CompressionLevelDefault = -1000

type Message struct {
	Codec            CompressionCodec // codec used to compress the message contents
	CompressionLevel int              // compression level
	LogAppendTime    bool             // the used timestamp is LogAppendTime
	Key              []byte           // the message key, may be nil
	Value            []byte           // the message contents
	Set              *MessageSet      // the message set a message might wrap
	Version          int8             // v1 requires Kafka 0.10
	Timestamp        time.Time        // the timestamp of the message (version 1+ only)

	compressedCache []byte
	compressedSize  int // used for computing the compression ratio metrics
}

func (m *Message) encode(pe packetEncoder) error {
	pe.push(newCRC32Field(crcIEEE))

	pe.putInt8(m.Version)

	attributes := int8(m.Codec) & compressionCodecMask
	if m.LogAppendTime {
		attributes |= timestampTypeMask
	}
	pe.putInt8(attributes)

	if m.Version >= 1 {
		if err := (Timestamp{&m.Timestamp}).encode(pe); err != nil {
			return err
		}
	}

	err := pe.putBytes(m.Key)
	if err != nil {
		return err
	}

	var payload []byte

	if m.compressedCache != nil {
		payload = m.compressedCache
		m.compressedCache = nil
	} else if m.Value != nil {

		payload, err = compress(m.Codec, m.CompressionLevel, m.Value)
		if err != nil {
			return err
		}
		m.compressedCache = payload
		// Keep in mind the compressed payload size for metric gathering
		m.compressedSize = len(payload)
	}

	if err = pe.putBytes(payload); err != nil {
		return err
	}

	return pe.pop()
}

func (m *Message) decode(pd packetDecoder) (err error) {
	err = pd.push(newCRC32Field(crcIEEE))
	if err != nil {
		return err
	}

	m.Version, err = pd.getInt8()
	if err != nil {
		return err
	}

	if m.Version > 1 {
		return PacketDecodingError{fmt.Sprintf("unknown magic byte (%v)", m.Version)}
	}

	attribute, err := pd.getInt8()
	if err != nil {
		return err
	}
	m.Codec = CompressionCodec(attribute & compressionCodecMask)
	m.LogAppendTime = attribute&timestampTypeMask == timestampTypeMask

	if m.Version == 1 {
		if err := (Timestamp{&m.Timestamp}).decode(pd); err != nil {
			return err
		}
	}

	m.Key, err = pd.getBytes()
	if err != nil {
		return err
	}

	m.Value, err = pd.getBytes()
	if err != nil {
		return err
	}

	// Required for deep equal assertion during tests but might be useful
	// for future metrics about the compression ratio in fetch requests
	m.compressedSize = len(m.Value)

	switch m.Codec {
	case CompressionNone:
		// nothing to do
	default:
		if m.Value == nil {
			break
		}

		m.Value, err = decompress(m.Codec, m.Value)
		if err != nil {
			return err
		}
		if err := m.decodeSet(); err != nil {
			return err
		}
	}

	return pd.pop()
}

// decodes a message set from a previously encoded bulk-message
func (m *Message) decodeSet() (err error) {
	pd := realDecoder{raw: m.Value}
	m.Set = &MessageSet{}
	return m.Set.decode(&pd)
}
<|endoftext|>


<commit_before>// An internal metrics channel.
// l2met internal components can publish their metrics
// here and they will be outletted to Librato.
package metchan

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/ryandotsmith/metchan/bucket"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"
)

type libratoGauge struct {
	Gauges []*bucket.LibratoMetric `json:"gauges"`
}

type Channel struct {
	// The time by which metchan will aggregate internal metrics.
	FlushInterval time.Duration
	// This will enable writting all metrics to a logger.
	Verbose  bool
	// Short short circiut all calls to metchan. Useful for development.
	Enabled  bool
	// The URL for librato. Must include username and password.
	Url *url.URL
	// The default source value for metrics submitted to Librato.
	Source   string
	// All Librato metrics will be prefixed with this value.
	MetricPrefix  string
	// Number of goroutines to use for sending HTTP requests to Librato.
	Concurrency int
	// Number of HTTP requests to buffer before sending to Librato API.
	BufferSize int
	// Internal Data structures
	sync.Mutex
	buffer   map[string]*bucket.Bucket
	outbox   chan *bucket.LibratoMetric
	username string
	password string
}

func (c *Channel) Start() {
	if c.Enabled {
		c.setup()
		go c.scheduleFlush()
		for i := 0; i < c.Concurrency; i++ {
			go c.outlet()
		}
	}
}

// Returns an initialized Metchan Channel.
// Creates a new HTTP client for direct access to Librato.
// This channel is orthogonal with other librato http clients in l2met.
// If a blank URL is given, no metric posting attempt will be made.
// If verbose is set to true, the metric will be printed to STDOUT
// regardless of whether the metric is sent to Librato.
func (c *Channel) setup() {
	if c.Url == nil {
		panic("metchan: Must set metchan.Url")
	}
	c.username = c.Url.User.Username()
	c.password, _ = c.Url.User.Password()
	c.Url.User = nil
	c.Enabled = true

	// Internal Datastructures.
	c.buffer = make(map[string]*bucket.Bucket)
	c.outbox = make(chan *bucket.LibratoMetric, c.BufferSize)

	// Default flush interval.
	if c.FlushInterval.Seconds() < 1.0 {
		c.FlushInterval = time.Minute
	}

	host, err := os.Hostname()
	if err == nil {
		c.Source = host
	}
}

// Provide the time at which you started your measurement.
// Places the measurement in a buffer to be aggregated and
// eventually flushed to Librato.
func (c *Channel) Time(name string, t time.Time) {
	elapsed := time.Since(t) / time.Millisecond
	c.Measure(name, float64(elapsed))
}

func (c *Channel) Measure(name string, v float64) {
	if c.Verbose {
		fmt.Printf("source=%s measure#%s=%f\n", c.Source, name, v)
	}
	if !c.Enabled {
		return
	}
	id := &bucket.Id{
		Resolution: c.FlushInterval,
		Name:       c.MetricPrefix + "." + name,
		Units:      "ms",
		Source:     c.Source,
		Type:       "measurement",
	}
	b := c.getBucket(id)
	b.Append(v)
}

func (c *Channel) Count(name string) {
	if !c.Enabled {
		return
	}
	id := &bucket.Id{
		Resolution: c.FlushInterval,
		Name:       c.MetricPrefix + "." + name,
		Units:      "requests",
		Source:     c.Source,
		Type:       "counter",
	}
	b := c.getBucket(id)
	b.Incr(1)
}

func (c *Channel) getBucket(id *bucket.Id) *bucket.Bucket {
	c.Lock()
	defer c.Unlock()
	key := id.Name + ":" + id.Source
	b, ok := c.buffer[key]
	if !ok {
		b = &bucket.Bucket{Id: id}
		b.Vals = make([]float64, 1, 10000)
		c.buffer[key] = b
	}
	// Instead of creating a new bucket struct with a new Vals slice
	// We will re-use the old bucket and reset the slice. This
	// dramatically decreases the amount of arrays created and thus
	// led to better memory utilization.
	latest := time.Now().Truncate(c.FlushInterval)
	if b.Id.Time != latest {
		b.Id.Time = latest
		b.Reset()
	}
	return b
}

func (c *Channel) scheduleFlush() {
	for _ = range time.Tick(c.FlushInterval) {
		c.flush()
	}
}

func (c *Channel) flush() {
	c.Lock()
	defer c.Unlock()
	for _, b := range c.buffer {
		for _, m := range b.Metrics() {
			select {
			case c.outbox <- m:
			default:
				fmt.Printf("error=metchan-drop\n")
			}
		}
	}
}

func (c *Channel) outlet() {
	for met := range c.outbox {
		if err := c.post(met); err != nil {
			fmt.Printf("at=metchan-post error=%s\n", err)
		}
	}
}

func (c *Channel) post(m *bucket.LibratoMetric) error {
	p := &libratoGauge{[]*bucket.LibratoMetric{m}}
	j, err := json.Marshal(p)
	if err != nil {
		return err
	}
	body := bytes.NewBuffer(j)
	req, err := http.NewRequest("POST", c.Url.String(), body)
	if err != nil {
		return err
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("User-Agent", "l2met-metchan/0")
	req.Header.Add("Connection", "Keep-Alive")
	req.SetBasicAuth(c.username, c.password)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode/100 != 2 {
		var m string
		s, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			m = fmt.Sprintf("code=%d", resp.StatusCode)
		} else {
			m = fmt.Sprintf("code=%d resp=body=%s req-body=%s",
				resp.StatusCode, s, body)
		}
		return errors.New(m)
	}
	return nil
}
<commit_msg>set user-agent as pkg name<commit_after>// An internal metrics channel.
// l2met internal components can publish their metrics
// here and they will be outletted to Librato.
package metchan

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/ryandotsmith/metchan/bucket"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"
)

type libratoGauge struct {
	Gauges []*bucket.LibratoMetric `json:"gauges"`
}

type Channel struct {
	// The time by which metchan will aggregate internal metrics.
	FlushInterval time.Duration
	// This will enable writting all metrics to a logger.
	Verbose  bool
	// Short short circiut all calls to metchan. Useful for development.
	Enabled  bool
	// The URL for librato. Must include username and password.
	Url *url.URL
	// The default source value for metrics submitted to Librato.
	Source   string
	// All Librato metrics will be prefixed with this value.
	MetricPrefix  string
	// Number of goroutines to use for sending HTTP requests to Librato.
	Concurrency int
	// Number of HTTP requests to buffer before sending to Librato API.
	BufferSize int
	// Internal Data structures
	sync.Mutex
	buffer   map[string]*bucket.Bucket
	outbox   chan *bucket.LibratoMetric
	username string
	password string
}

func (c *Channel) Start() {
	if c.Enabled {
		c.setup()
		go c.scheduleFlush()
		for i := 0; i < c.Concurrency; i++ {
			go c.outlet()
		}
	}
}

// Returns an initialized Metchan Channel.
// Creates a new HTTP client for direct access to Librato.
// This channel is orthogonal with other librato http clients in l2met.
// If a blank URL is given, no metric posting attempt will be made.
// If verbose is set to true, the metric will be printed to STDOUT
// regardless of whether the metric is sent to Librato.
func (c *Channel) setup() {
	if c.Url == nil {
		panic("metchan: Must set metchan.Url")
	}
	c.username = c.Url.User.Username()
	c.password, _ = c.Url.User.Password()
	c.Url.User = nil
	c.Enabled = true

	// Internal Datastructures.
	c.buffer = make(map[string]*bucket.Bucket)
	c.outbox = make(chan *bucket.LibratoMetric, c.BufferSize)

	// Default flush interval.
	if c.FlushInterval.Seconds() < 1.0 {
		c.FlushInterval = time.Minute
	}

	host, err := os.Hostname()
	if err == nil {
		c.Source = host
	}
}

// Provide the time at which you started your measurement.
// Places the measurement in a buffer to be aggregated and
// eventually flushed to Librato.
func (c *Channel) Time(name string, t time.Time) {
	elapsed := time.Since(t) / time.Millisecond
	c.Measure(name, float64(elapsed))
}

func (c *Channel) Measure(name string, v float64) {
	if c.Verbose {
		fmt.Printf("source=%s measure#%s=%f\n", c.Source, name, v)
	}
	if !c.Enabled {
		return
	}
	id := &bucket.Id{
		Resolution: c.FlushInterval,
		Name:       c.MetricPrefix + "." + name,
		Units:      "ms",
		Source:     c.Source,
		Type:       "measurement",
	}
	b := c.getBucket(id)
	b.Append(v)
}

func (c *Channel) Count(name string) {
	if !c.Enabled {
		return
	}
	id := &bucket.Id{
		Resolution: c.FlushInterval,
		Name:       c.MetricPrefix + "." + name,
		Units:      "requests",
		Source:     c.Source,
		Type:       "counter",
	}
	b := c.getBucket(id)
	b.Incr(1)
}

func (c *Channel) getBucket(id *bucket.Id) *bucket.Bucket {
	c.Lock()
	defer c.Unlock()
	key := id.Name + ":" + id.Source
	b, ok := c.buffer[key]
	if !ok {
		b = &bucket.Bucket{Id: id}
		b.Vals = make([]float64, 1, 10000)
		c.buffer[key] = b
	}
	// Instead of creating a new bucket struct with a new Vals slice
	// We will re-use the old bucket and reset the slice. This
	// dramatically decreases the amount of arrays created and thus
	// led to better memory utilization.
	latest := time.Now().Truncate(c.FlushInterval)
	if b.Id.Time != latest {
		b.Id.Time = latest
		b.Reset()
	}
	return b
}

func (c *Channel) scheduleFlush() {
	for _ = range time.Tick(c.FlushInterval) {
		c.flush()
	}
}

func (c *Channel) flush() {
	c.Lock()
	defer c.Unlock()
	for _, b := range c.buffer {
		for _, m := range b.Metrics() {
			select {
			case c.outbox <- m:
			default:
				fmt.Printf("error=metchan-drop\n")
			}
		}
	}
}

func (c *Channel) outlet() {
	for met := range c.outbox {
		if err := c.post(met); err != nil {
			fmt.Printf("at=metchan-post error=%s\n", err)
		}
	}
}

func (c *Channel) post(m *bucket.LibratoMetric) error {
	p := &libratoGauge{[]*bucket.LibratoMetric{m}}
	j, err := json.Marshal(p)
	if err != nil {
		return err
	}
	body := bytes.NewBuffer(j)
	req, err := http.NewRequest("POST", c.Url.String(), body)
	if err != nil {
		return err
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("User-Agent", "metchan/0")
	req.Header.Add("Connection", "Keep-Alive")
	req.SetBasicAuth(c.username, c.password)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode/100 != 2 {
		var m string
		s, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			m = fmt.Sprintf("code=%d", resp.StatusCode)
		} else {
			m = fmt.Sprintf("code=%d resp=body=%s req-body=%s",
				resp.StatusCode, s, body)
		}
		return errors.New(m)
	}
	return nil
}
<|endoftext|>