<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"time"
)

type BasicAuthConfigurationItem struct {
	UserName string `json:"username"`
	Password string `json:"password"`
	Url      string `json:"url"`
}

type DnspodConfigurationItem struct {
	UserName  string `json:"username"`
	Password  string `json:"password"`
	Domain    string `json:"domain"`
	SubDomain string `json:"sub_domain"`
}

type CloudflareConfigurationItem struct {
	UserName  string `json:"username"`
	Token     string `json:"token"`
	Domain    string `json:"domain"`
	SubDomain string `json:"sub_domain"`
}

type Setting struct {
	BasicAuthItems  []BasicAuthConfigurationItem  `json:"basic"`
	DnspodItems     []DnspodConfigurationItem     `json:"dnspod"`
	CloudflareItems []CloudflareConfigurationItem `json:"cloudflare"`
}

type DnspodDomainItem struct {
	Id   int    `json:"id"`
	Name string `json:"name"`
}

type DnspodDomainList struct {
	Domains []DnspodDomainItem `json:"domains"`
}

type DnspodRecordItem struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

type DnspodRecordList struct {
	Records []DnspodRecordItem `json:"records"`
}

type CloudflareRecordItem struct {
	Id   string `json:"rec_id"`
	Name string `json:"name"`
	Type string `json:"type"`
}

type CloudflareRecords struct {
	Objs []CloudflareRecordItem `json:"objs"`
}

type CloudflareResponse struct {
	Recs CloudflareRecords `json:"recs"`
}

type CloudflareRecordList struct {
	Response CloudflareResponse `json:"response"`
}

type CloudflareNewRecords struct {
	Obj CloudflareRecordItem `json:"obj"`
}

type CloudflareNewRecordResponse struct {
	Rec CloudflareNewRecords `json:"rec"`
}

type CloudflareNewRecordResponseBody struct {
	Response CloudflareNewRecordResponse `json:"response"`
}

var (
	currentExternalIP string
	lastExternalIP    string
	dnspodDomainList  = &DnspodDomainList{}
)

func getCurrentExternalIP() string {
	client := &http.Client{}
	ifconfigUrl := "https://ifconfig.minidump.info"
	req, err := http.NewRequest("GET", ifconfigUrl, nil)
	req.Header.Set("User-Agent", "curl/7.41.0")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("request %s failed", ifconfigUrl)
		return ""
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("reading ifconfig response failed\n")
		return ""
	}

	for i := len(body); i > 0 && (body[i-1] < '0' || body[i-1] > '9'); i = len(body) {
		body = body[:i-1]
	}
	return string(body)
}

func basicAuthorizeHttpRequest(user string, password string, requestUrl string) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", requestUrl, nil)
	req.SetBasicAuth(user, password)
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("request %s failed\n", requestUrl)
		return
	}
	defer resp.Body.Close()
	_, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("reading response failed\n")
		return
	}
}

func cloudflareRequest(user string, token string, domain string, sub_domain string) {
	// get domain all records
	cloudflareAPIUrl := "https://www.cloudflare.com/api_json.html"
	client := &http.Client{}
	resp, err := client.PostForm(cloudflareAPIUrl, url.Values{
		"a":     {"rec_load_all"},
		"tkn":   {token},
		"email": {user},
		"z":     {domain},
	})
	if err != nil {
		fmt.Printf("request cloudflare all records failed\n")
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("reading cloudflare all records failed\n")
		return
	}

	recordList := new(CloudflareRecordList)
	if err = json.Unmarshal(body, &recordList); err != nil {
		fmt.Printf("unmarshalling cloudflare all records %s failed\n", string(body))
		return
	}

	// insert or update
	foundRecord := false
	var recordId string
	for _, v := range recordList.Response.Recs.Objs {
		if v.Type == "A" && v.Name == sub_domain {
			recordId = v.Id
			foundRecord = true
			break
		}
	}
	if foundRecord == false {
		// insert a new record
		resp, err := client.PostForm(cloudflareAPIUrl, url.Values{
			"a":       {"rec_new"},
			"tkn":     {token},
			"email":   {user},
			"z":       {domain},
			"ttl":     {"1"},
			"type":    {"A"},
			"name":    {sub_domain},
			"content": {getCurrentExternalIP()},
		})
		if err != nil {
			fmt.Printf("request cloudflare new record failed\n")
			return
		}

		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Printf("reading cloudflare new record failed\n")
			return
		}
		// extract the new record id
		respBody := new(CloudflareNewRecordResponseBody)
		if err = json.Unmarshal(body, respBody); err != nil {
			fmt.Printf("unmarshalling cloudflare new record response body failed\n")
			return
		}
		recordId = respBody.Response.Rec.Obj.Id
		fmt.Printf("A record inserted into cloudflare: %s.%s => %s\n", sub_domain, domain, currentExternalIP)
	}
	// update the record
	resp, err = client.PostForm(cloudflareAPIUrl, url.Values{
		"a":            {"rec_edit"},
		"tkn":          {token},
		"email":        {user},
		"z":            {domain},
		"type":         {"A"},
		"service_mode": {"0"},
		"ttl":          {"1"},
		"id":           {recordId},
		"name":         {sub_domain},
		"content":      {getCurrentExternalIP()},
	})
	if err != nil {
		fmt.Printf("request cloudflare records edit failed\n")
		return
	}
	defer resp.Body.Close()

	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("reading cloudflare record edit response failed\n")
		return
	}
	fmt.Printf("A record updated to cloudflare: %s.%s => %s\n", sub_domain, domain, currentExternalIP)
}

func dnspodRequest(user string, password string, domain string, sub_domain string) {
	needDomainList := false
	if len(dnspodDomainList.Domains) == 0 {
		needDomainList = true
	}
	var domainId int = 0
	if needDomainList == false {
		needDomainList = true
		for _, v := range dnspodDomainList.Domains {
			if v.Name == domain {
				needDomainList = false
				domainId = v.Id
				break
			}
		}
	}

	client := &http.Client{}
	if needDomainList {
		// get domainn id first
		domainListUrl := "https://dnsapi.cn/Domain.List"
		resp, err := client.PostForm(domainListUrl, url.Values{
			"login_email":    {user},
			"login_password": {password},
			"format":         {"json"},
		})
		if err != nil {
			fmt.Printf("request domain list failed\n")
			return
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Printf("reading domain list failed\n")
			return
		}

		if err = json.Unmarshal(body, &dnspodDomainList); err != nil {
			fmt.Printf("unmarshalling domain list %s failed\n", string(body))
			return
		}
	}
	foundDomain := false
	for _, v := range dnspodDomainList.Domains {
		if v.Name == domain {
			foundDomain = true
			domainId = v.Id
			break
		}
	}

	if foundDomain == false {
		fmt.Printf("domain %s doesn't exists\n", domain)
		return
	}

	// check record list
	recordListUrl := "https://dnsapi.cn/Record.List"
	resp, err := client.PostForm(recordListUrl, url.Values{
		"login_email":    {user},
		"login_password": {password},
		"format":         {"json"},
		"domain_id":      {strconv.Itoa(domainId)},
	})
	if err != nil {
		fmt.Printf("request record list failed\n")
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("reading record list failed\n")
		return
	}

	recordList := new(DnspodRecordList)
	if err = json.Unmarshal(body, recordList); err != nil {
		fmt.Printf("unmarshalling record list %s failed\n", string(body))
		fmt.Println(err)
		return
	}
	foundRecord := false
	var recordId string
	for _, v := range recordList.Records {
		if v.Name == sub_domain {
			foundRecord = true
			recordId = v.Id
			break
		}
	}

	if foundRecord == false {
		// if the sub domain doesn't exist, add one
		addRecordUrl := "https://dnsapi.cn/Record.Create"
		resp, err := client.PostForm(addRecordUrl, url.Values{
			"login_email":    {user},
			"login_password": {password},
			"format":         {"json"},
			"domain_id":      {strconv.Itoa(domainId)},
			"sub_domain":     {sub_domain},
			"record_type":    {"A"},
			"record_line":    {"默认"},
			"value":          {currentExternalIP},
		})
		if err != nil {
			fmt.Printf("request record insert failed\n")
			return
		}
		defer resp.Body.Close()

		if _, err = ioutil.ReadAll(resp.Body); err != nil {
			fmt.Printf("reading record insert response failed\n")
			return
		}

		fmt.Printf("A record inserted into DNSPOD: %s.%s => %s\n", sub_domain, domain, currentExternalIP)
	} else {
		// otherwise just update it
		modifyRecordUrl := "https://dnsapi.cn/Record.Modify"
		resp, err := client.PostForm(modifyRecordUrl, url.Values{
			"login_email":    {user},
			"login_password": {password},
			"format":         {"json"},
			"record_id":      {recordId},
			"domain_id":      {strconv.Itoa(domainId)},
			"sub_domain":     {sub_domain},
			"record_type":    {"A"},
			"record_line":    {"默认"},
			"value":          {currentExternalIP},
		})
		if err != nil {
			fmt.Printf("request record modify failed\n")
			return
		}
		defer resp.Body.Close()

		if _, err = ioutil.ReadAll(resp.Body); err != nil {
			fmt.Printf("reading record modify response failed\n")
			return
		}
		fmt.Printf("A record updated to DNSPOD: %s.%s => %s\n", sub_domain, domain, currentExternalIP)
	}
}

func updateDDNS(setting *Setting) {
	currentExternalIP = getCurrentExternalIP()
	if len(currentExternalIP) != 0 && lastExternalIP != currentExternalIP {
		for _, v := range setting.BasicAuthItems {
			basicAuthorizeHttpRequest(v.UserName, v.Password, v.Url)
		}

		for _, v := range setting.DnspodItems {
			dnspodRequest(v.UserName, v.Password, v.Domain, v.SubDomain)
		}
		lastExternalIP = currentExternalIP

		for _, v := range setting.CloudflareItems {
			cloudflareRequest(v.UserName, v.Token, v.Domain, v.SubDomain)
		}
	}
}

func main() {
	fmt.Println("Dynamic DNS client")

	appConf, err := os.Open("app.conf")
	if err != nil {
		fmt.Println("opening app.conf failed:", err)
		return
	}

	defer func() {
		appConf.Close()
	}()

	b, err := ioutil.ReadAll(appConf)
	if err != nil {
		fmt.Println("reading app.conf failed:", err)
		return
	}
	setting := new(Setting)
	err = json.Unmarshal(b, &setting)
	if err != nil {
		fmt.Println("unmarshalling app.conf failed:", err)
		return
	}

	updateDDNS(setting)
	timer := time.NewTicker(time.Duration(1) * time.Minute) // every 1 minute
	for {
		select {
		case <-timer.C:
			updateDDNS(setting)
		}
	}
	timer.Stop()
}
<commit_msg>(*)more strict IP check<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"time"
)

type BasicAuthConfigurationItem struct {
	UserName string `json:"username"`
	Password string `json:"password"`
	Url      string `json:"url"`
}

type DnspodConfigurationItem struct {
	UserName  string `json:"username"`
	Password  string `json:"password"`
	Domain    string `json:"domain"`
	SubDomain string `json:"sub_domain"`
}

type CloudflareConfigurationItem struct {
	UserName  string `json:"username"`
	Token     string `json:"token"`
	Domain    string `json:"domain"`
	SubDomain string `json:"sub_domain"`
}

type Setting struct {
	BasicAuthItems  []BasicAuthConfigurationItem  `json:"basic"`
	DnspodItems     []DnspodConfigurationItem     `json:"dnspod"`
	CloudflareItems []CloudflareConfigurationItem `json:"cloudflare"`
}

type DnspodDomainItem struct {
	Id   int    `json:"id"`
	Name string `json:"name"`
}

type DnspodDomainList struct {
	Domains []DnspodDomainItem `json:"domains"`
}

type DnspodRecordItem struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

type DnspodRecordList struct {
	Records []DnspodRecordItem `json:"records"`
}

type CloudflareRecordItem struct {
	Id   string `json:"rec_id"`
	Name string `json:"name"`
	Type string `json:"type"`
}

type CloudflareRecords struct {
	Objs []CloudflareRecordItem `json:"objs"`
}

type CloudflareResponse struct {
	Recs CloudflareRecords `json:"recs"`
}

type CloudflareRecordList struct {
	Response CloudflareResponse `json:"response"`
}

type CloudflareNewRecords struct {
	Obj CloudflareRecordItem `json:"obj"`
}

type CloudflareNewRecordResponse struct {
	Rec CloudflareNewRecords `json:"rec"`
}

type CloudflareNewRecordResponseBody struct {
	Response CloudflareNewRecordResponse `json:"response"`
}

var (
	currentExternalIP string
	lastExternalIP    string
	dnspodDomainList  = &DnspodDomainList{}
)

func getCurrentExternalIP() string {
	client := &http.Client{}
	ifconfigUrl := "https://ifconfig.minidump.info"
	req, err := http.NewRequest("GET", ifconfigUrl, nil)
	req.Header.Set("User-Agent", "curl/7.41.0")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("request %s failed", ifconfigUrl)
		return ""
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("reading ifconfig response failed\n")
		return ""
	}

	for i := len(body); i > 0 && (body[i-1] < '0' || body[i-1] > '9'); i = len(body) {
		body = body[:i-1]
	}

	if matched, err := regexp.Match(`^([0-9]{1,3}\.){3,3}[0-9]{1,3}$`, body); err == nil && matched == true {
		return string(body)
	}

	return ""
}

func basicAuthorizeHttpRequest(user string, password string, requestUrl string) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", requestUrl, nil)
	req.SetBasicAuth(user, password)
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("request %s failed\n", requestUrl)
		return
	}
	defer resp.Body.Close()
	_, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("reading response failed\n")
		return
	}
}

func cloudflareRequest(user string, token string, domain string, sub_domain string) {
	// get domain all records
	cloudflareAPIUrl := "https://www.cloudflare.com/api_json.html"
	client := &http.Client{}
	resp, err := client.PostForm(cloudflareAPIUrl, url.Values{
		"a":     {"rec_load_all"},
		"tkn":   {token},
		"email": {user},
		"z":     {domain},
	})
	if err != nil {
		fmt.Printf("request cloudflare all records failed\n")
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("reading cloudflare all records failed\n")
		return
	}

	recordList := new(CloudflareRecordList)
	if err = json.Unmarshal(body, &recordList); err != nil {
		fmt.Printf("unmarshalling cloudflare all records %s failed\n", string(body))
		return
	}

	// insert or update
	foundRecord := false
	var recordId string
	for _, v := range recordList.Response.Recs.Objs {
		if v.Type == "A" && v.Name == sub_domain {
			recordId = v.Id
			foundRecord = true
			break
		}
	}
	if foundRecord == false {
		// insert a new record
		resp, err := client.PostForm(cloudflareAPIUrl, url.Values{
			"a":       {"rec_new"},
			"tkn":     {token},
			"email":   {user},
			"z":       {domain},
			"ttl":     {"1"},
			"type":    {"A"},
			"name":    {sub_domain},
			"content": {getCurrentExternalIP()},
		})
		if err != nil {
			fmt.Printf("request cloudflare new record failed\n")
			return
		}

		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Printf("reading cloudflare new record failed\n")
			return
		}
		// extract the new record id
		respBody := new(CloudflareNewRecordResponseBody)
		if err = json.Unmarshal(body, respBody); err != nil {
			fmt.Printf("unmarshalling cloudflare new record response body failed\n")
			return
		}
		recordId = respBody.Response.Rec.Obj.Id
		fmt.Printf("A record inserted into cloudflare: %s.%s => %s\n", sub_domain, domain, currentExternalIP)
	}
	// update the record
	resp, err = client.PostForm(cloudflareAPIUrl, url.Values{
		"a":            {"rec_edit"},
		"tkn":          {token},
		"email":        {user},
		"z":            {domain},
		"type":         {"A"},
		"service_mode": {"0"},
		"ttl":          {"1"},
		"id":           {recordId},
		"name":         {sub_domain},
		"content":      {getCurrentExternalIP()},
	})
	if err != nil {
		fmt.Printf("request cloudflare records edit failed\n")
		return
	}
	defer resp.Body.Close()

	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("reading cloudflare record edit response failed\n")
		return
	}
	fmt.Printf("A record updated to cloudflare: %s.%s => %s\n", sub_domain, domain, currentExternalIP)
}

func dnspodRequest(user string, password string, domain string, sub_domain string) {
	needDomainList := false
	if len(dnspodDomainList.Domains) == 0 {
		needDomainList = true
	}
	var domainId int = 0
	if needDomainList == false {
		needDomainList = true
		for _, v := range dnspodDomainList.Domains {
			if v.Name == domain {
				needDomainList = false
				domainId = v.Id
				break
			}
		}
	}

	client := &http.Client{}
	if needDomainList {
		// get domainn id first
		domainListUrl := "https://dnsapi.cn/Domain.List"
		resp, err := client.PostForm(domainListUrl, url.Values{
			"login_email":    {user},
			"login_password": {password},
			"format":         {"json"},
		})
		if err != nil {
			fmt.Printf("request domain list failed\n")
			return
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Printf("reading domain list failed\n")
			return
		}

		if err = json.Unmarshal(body, &dnspodDomainList); err != nil {
			fmt.Printf("unmarshalling domain list %s failed\n", string(body))
			return
		}
	}
	foundDomain := false
	for _, v := range dnspodDomainList.Domains {
		if v.Name == domain {
			foundDomain = true
			domainId = v.Id
			break
		}
	}

	if foundDomain == false {
		fmt.Printf("domain %s doesn't exists\n", domain)
		return
	}

	// check record list
	recordListUrl := "https://dnsapi.cn/Record.List"
	resp, err := client.PostForm(recordListUrl, url.Values{
		"login_email":    {user},
		"login_password": {password},
		"format":         {"json"},
		"domain_id":      {strconv.Itoa(domainId)},
	})
	if err != nil {
		fmt.Printf("request record list failed\n")
		return
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("reading record list failed\n")
		return
	}

	recordList := new(DnspodRecordList)
	if err = json.Unmarshal(body, recordList); err != nil {
		fmt.Printf("unmarshalling record list %s failed\n", string(body))
		fmt.Println(err)
		return
	}
	foundRecord := false
	var recordId string
	for _, v := range recordList.Records {
		if v.Name == sub_domain {
			foundRecord = true
			recordId = v.Id
			break
		}
	}

	if foundRecord == false {
		// if the sub domain doesn't exist, add one
		addRecordUrl := "https://dnsapi.cn/Record.Create"
		resp, err := client.PostForm(addRecordUrl, url.Values{
			"login_email":    {user},
			"login_password": {password},
			"format":         {"json"},
			"domain_id":      {strconv.Itoa(domainId)},
			"sub_domain":     {sub_domain},
			"record_type":    {"A"},
			"record_line":    {"默认"},
			"value":          {currentExternalIP},
		})
		if err != nil {
			fmt.Printf("request record insert failed\n")
			return
		}
		defer resp.Body.Close()

		if _, err = ioutil.ReadAll(resp.Body); err != nil {
			fmt.Printf("reading record insert response failed\n")
			return
		}

		fmt.Printf("A record inserted into DNSPOD: %s.%s => %s\n", sub_domain, domain, currentExternalIP)
	} else {
		// otherwise just update it
		modifyRecordUrl := "https://dnsapi.cn/Record.Modify"
		resp, err := client.PostForm(modifyRecordUrl, url.Values{
			"login_email":    {user},
			"login_password": {password},
			"format":         {"json"},
			"record_id":      {recordId},
			"domain_id":      {strconv.Itoa(domainId)},
			"sub_domain":     {sub_domain},
			"record_type":    {"A"},
			"record_line":    {"默认"},
			"value":          {currentExternalIP},
		})
		if err != nil {
			fmt.Printf("request record modify failed\n")
			return
		}
		defer resp.Body.Close()

		if _, err = ioutil.ReadAll(resp.Body); err != nil {
			fmt.Printf("reading record modify response failed\n")
			return
		}
		fmt.Printf("A record updated to DNSPOD: %s.%s => %s\n", sub_domain, domain, currentExternalIP)
	}
}

func updateDDNS(setting *Setting) {
	currentExternalIP = getCurrentExternalIP()
	if len(currentExternalIP) != 0 && lastExternalIP != currentExternalIP {
		for _, v := range setting.BasicAuthItems {
			basicAuthorizeHttpRequest(v.UserName, v.Password, v.Url)
		}

		for _, v := range setting.DnspodItems {
			dnspodRequest(v.UserName, v.Password, v.Domain, v.SubDomain)
		}
		lastExternalIP = currentExternalIP

		for _, v := range setting.CloudflareItems {
			cloudflareRequest(v.UserName, v.Token, v.Domain, v.SubDomain)
		}
	}
}

func main() {
	fmt.Println("Dynamic DNS client")

	appConf, err := os.Open("app.conf")
	if err != nil {
		fmt.Println("opening app.conf failed:", err)
		return
	}

	defer func() {
		appConf.Close()
	}()

	b, err := ioutil.ReadAll(appConf)
	if err != nil {
		fmt.Println("reading app.conf failed:", err)
		return
	}
	setting := new(Setting)
	err = json.Unmarshal(b, &setting)
	if err != nil {
		fmt.Println("unmarshalling app.conf failed:", err)
		return
	}

	updateDDNS(setting)
	timer := time.NewTicker(time.Duration(1) * time.Minute) // every 1 minute
	for {
		select {
		case <-timer.C:
			updateDDNS(setting)
		}
	}
	timer.Stop()
}
<|endoftext|>


<commit_before>package roaring

// to run just these tests: go test -run TestParAggregations

import (
	"testing"
)

func testAggregations(t *testing.T,
	and func(bitmaps ...*Bitmap) *Bitmap,
	or func(bitmaps ...*Bitmap) *Bitmap,
	xor func(bitmaps ...*Bitmap) *Bitmap) {

	t.Run("simple case", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb1.Add(1)
		rb2.Add(2)

		if and != nil && and(rb1, rb2).GetCardinality() != 0 {
			t.Error()
		}
		if and != nil && or(rb1, rb2).GetCardinality() != 2 {
			t.Error()
		}
		if xor != nil && xor(rb1, rb2).GetCardinality() != 2 {
			t.Error()
		}
	})

	t.Run("aggregate nothing", func(t *testing.T) {
		if and != nil && and().GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or().GetCardinality() != 0 {
			t.Error()
		}

		if xor != nil && xor().GetCardinality() != 0 {
			t.Error()
		}
	})

	t.Run("single bitmap", func(t *testing.T) {
		rb := BitmapOf(1, 2, 3)

		if and != nil && and(rb).GetCardinality() != 3 {
			t.Error()
		}

		if or != nil && or(rb).GetCardinality() != 3 {
			t.Error()
		}

		if xor != nil && xor(rb).GetCardinality() != 3 {
			t.Error()
		}
	})

	t.Run("empty and single elem bitmaps", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := BitmapOf(1)

		if and != nil && and(rb1, rb2).GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or(rb1, rb2).GetCardinality() != 1 {
			t.Error()
		}

		if xor != nil && xor(rb1, rb2).GetCardinality() != 1 {
			t.Error()
		}
	})

	t.Run("two single elem disjoint sets", func(t *testing.T) {
		rb1 := BitmapOf(1)
		rb2 := BitmapOf(2)

		if and != nil && and(rb1, rb2).Stats().Containers != 0 {
			t.Error()
		}

		if or != nil && or(rb1, rb2).GetCardinality() != 2 {
			t.Error()
		}
	})

	t.Run("3 bitmaps with CoW set (not in order of definition)", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		rb1.SetCopyOnWrite(true)
		rb2.SetCopyOnWrite(true)
		rb3.SetCopyOnWrite(true)
		rb1.Add(1)
		rb1.Add(100000)
		rb2.Add(200000)
		rb3.Add(1)
		rb3.Add(300000)

		if and != nil && and(rb2, rb1, rb3).GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or(rb2, rb1, rb3).GetCardinality() != 4 {
			t.Error()
		}

		if xor != nil && xor(rb2, rb1, rb3).GetCardinality() != 3 {
			t.Error()
		}
	})

	t.Run("3 bitmaps (not in order of definition)", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		rb1.Add(1)
		rb1.Add(100000)
		rb2.Add(200000)
		rb3.Add(1)
		rb3.Add(300000)

		if and != nil && and(rb2, rb1, rb3).GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or(rb2, rb1, rb3).GetCardinality() != 4 {
			t.Error()
		}

		if xor != nil && xor(rb2, rb1, rb3).GetCardinality() != 3 {
			t.Error()
		}
	})

	t.Run("3 bitmaps", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		rb1.Add(1)
		rb1.Add(100000)
		rb2.Add(200000)
		rb3.Add(1)
		rb3.Add(300000)

		if and != nil && and(rb1, rb2, rb3).GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or(rb1, rb2, rb3).GetCardinality() != 4 {
			t.Error()
		}

		if xor != nil && xor(rb1, rb2, rb3).GetCardinality() != 3 {
			t.Error()
		}
	})

	t.Run("3 bitmaps with CoW set", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		rb1.SetCopyOnWrite(true)
		rb2.SetCopyOnWrite(true)
		rb3.SetCopyOnWrite(true)
		rb1.Add(1)
		rb1.Add(100000)
		rb2.Add(200000)
		rb3.Add(1)
		rb3.Add(300000)

		if and != nil && and(rb1, rb2, rb3).GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or(rb1, rb2, rb3).GetCardinality() != 4 {
			t.Error()
		}

		if xor != nil && xor(rb1, rb2, rb3).GetCardinality() != 3 {
			t.Error()
		}
	})

	t.Run("advanced case", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		for i := uint32(0); i < 1000000; i += 3 {
			rb1.Add(i)
		}
		for i := uint32(0); i < 1000000; i += 7 {
			rb2.Add(i)
		}
		for i := uint32(0); i < 1000000; i += 1001 {
			rb3.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 1001 {
			rb1.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 3 {
			rb2.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 7 {
			rb3.Add(i)
		}

		rb1.Or(rb2)
		rb1.Or(rb3)
		bigand := And(And(rb1, rb2), rb3)
		bigxor := Xor(Xor(rb1, rb2), rb3)

		if or != nil && !or(rb1, rb2, rb3).Equals(rb1) {
			t.Error()
		}

		if and != nil && !and(rb1, rb2, rb3).Equals(bigand) {
			t.Error()
		}

		if xor != nil && !xor(rb1, rb2, rb3).Equals(bigxor) {
			t.Error()
		}
	})

	t.Run("advanced case with runs", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		for i := uint32(500); i < 75000; i++ {
			rb1.Add(i)
		}
		for i := uint32(0); i < 1000000; i += 7 {
			rb2.Add(i)
		}
		for i := uint32(0); i < 1000000; i += 1001 {
			rb3.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 1001 {
			rb1.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 3 {
			rb2.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 7 {
			rb3.Add(i)
		}
		rb1.RunOptimize()

		rb1.Or(rb2)
		rb1.Or(rb3)
		bigand := And(And(rb1, rb2), rb3)
		bigxor := Xor(Xor(rb1, rb2), rb3)

		if or != nil && !or(rb1, rb2, rb3).Equals(rb1) {
			t.Error()
		}

		if and != nil && !and(rb1, rb2, rb3).Equals(bigand) {
			t.Error()
		}

		if xor != nil && !xor(rb1, rb2, rb3).Equals(bigxor) {
			t.Error()
		}
	})
}

func TestParAggregations(t *testing.T) {
	andFunc := func(bitmaps ...*Bitmap) *Bitmap {
		return ParAnd(0, bitmaps...)
	}
	orFunc := func(bitmaps ...*Bitmap) *Bitmap {
		return ParOr(0, bitmaps...)
	}

	testAggregations(t, andFunc, orFunc, nil)
}

func TestFastAggregations(t *testing.T) {
	testAggregations(t, FastAnd, FastOr, nil)
}

func TestHeapAggregations(t *testing.T) {
	testAggregations(t, nil, HeapOr, HeapXor)
}
<commit_msg>TestParAggregations with different parallelism levels<commit_after>package roaring

// to run just these tests: go test -run TestParAggregations

import (
	"testing"
	"fmt"
)

func testAggregations(t *testing.T,
	and func(bitmaps ...*Bitmap) *Bitmap,
	or func(bitmaps ...*Bitmap) *Bitmap,
	xor func(bitmaps ...*Bitmap) *Bitmap) {

	t.Run("simple case", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb1.Add(1)
		rb2.Add(2)

		if and != nil && and(rb1, rb2).GetCardinality() != 0 {
			t.Error()
		}
		if and != nil && or(rb1, rb2).GetCardinality() != 2 {
			t.Error()
		}
		if xor != nil && xor(rb1, rb2).GetCardinality() != 2 {
			t.Error()
		}
	})

	t.Run("aggregate nothing", func(t *testing.T) {
		if and != nil && and().GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or().GetCardinality() != 0 {
			t.Error()
		}

		if xor != nil && xor().GetCardinality() != 0 {
			t.Error()
		}
	})

	t.Run("single bitmap", func(t *testing.T) {
		rb := BitmapOf(1, 2, 3)

		if and != nil && and(rb).GetCardinality() != 3 {
			t.Error()
		}

		if or != nil && or(rb).GetCardinality() != 3 {
			t.Error()
		}

		if xor != nil && xor(rb).GetCardinality() != 3 {
			t.Error()
		}
	})

	t.Run("empty and single elem bitmaps", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := BitmapOf(1)

		if and != nil && and(rb1, rb2).GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or(rb1, rb2).GetCardinality() != 1 {
			t.Error()
		}

		if xor != nil && xor(rb1, rb2).GetCardinality() != 1 {
			t.Error()
		}
	})

	t.Run("two single elem disjoint sets", func(t *testing.T) {
		rb1 := BitmapOf(1)
		rb2 := BitmapOf(2)

		if and != nil && and(rb1, rb2).Stats().Containers != 0 {
			t.Error()
		}

		if or != nil && or(rb1, rb2).GetCardinality() != 2 {
			t.Error()
		}
	})

	t.Run("3 bitmaps with CoW set (not in order of definition)", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		rb1.SetCopyOnWrite(true)
		rb2.SetCopyOnWrite(true)
		rb3.SetCopyOnWrite(true)
		rb1.Add(1)
		rb1.Add(100000)
		rb2.Add(200000)
		rb3.Add(1)
		rb3.Add(300000)

		if and != nil && and(rb2, rb1, rb3).GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or(rb2, rb1, rb3).GetCardinality() != 4 {
			t.Error()
		}

		if xor != nil && xor(rb2, rb1, rb3).GetCardinality() != 3 {
			t.Error()
		}
	})

	t.Run("3 bitmaps (not in order of definition)", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		rb1.Add(1)
		rb1.Add(100000)
		rb2.Add(200000)
		rb3.Add(1)
		rb3.Add(300000)

		if and != nil && and(rb2, rb1, rb3).GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or(rb2, rb1, rb3).GetCardinality() != 4 {
			t.Error()
		}

		if xor != nil && xor(rb2, rb1, rb3).GetCardinality() != 3 {
			t.Error()
		}
	})

	t.Run("3 bitmaps", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		rb1.Add(1)
		rb1.Add(100000)
		rb2.Add(200000)
		rb3.Add(1)
		rb3.Add(300000)

		if and != nil && and(rb1, rb2, rb3).GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or(rb1, rb2, rb3).GetCardinality() != 4 {
			t.Error()
		}

		if xor != nil && xor(rb1, rb2, rb3).GetCardinality() != 3 {
			t.Error()
		}
	})

	t.Run("3 bitmaps with CoW set", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		rb1.SetCopyOnWrite(true)
		rb2.SetCopyOnWrite(true)
		rb3.SetCopyOnWrite(true)
		rb1.Add(1)
		rb1.Add(100000)
		rb2.Add(200000)
		rb3.Add(1)
		rb3.Add(300000)

		if and != nil && and(rb1, rb2, rb3).GetCardinality() != 0 {
			t.Error()
		}

		if or != nil && or(rb1, rb2, rb3).GetCardinality() != 4 {
			t.Error()
		}

		if xor != nil && xor(rb1, rb2, rb3).GetCardinality() != 3 {
			t.Error()
		}
	})

	t.Run("advanced case", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		for i := uint32(0); i < 1000000; i += 3 {
			rb1.Add(i)
		}
		for i := uint32(0); i < 1000000; i += 7 {
			rb2.Add(i)
		}
		for i := uint32(0); i < 1000000; i += 1001 {
			rb3.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 1001 {
			rb1.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 3 {
			rb2.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 7 {
			rb3.Add(i)
		}

		rb1.Or(rb2)
		rb1.Or(rb3)
		bigand := And(And(rb1, rb2), rb3)
		bigxor := Xor(Xor(rb1, rb2), rb3)

		if or != nil && !or(rb1, rb2, rb3).Equals(rb1) {
			t.Error()
		}

		if and != nil && !and(rb1, rb2, rb3).Equals(bigand) {
			t.Error()
		}

		if xor != nil && !xor(rb1, rb2, rb3).Equals(bigxor) {
			t.Error()
		}
	})

	t.Run("advanced case with runs", func(t *testing.T) {
		rb1 := NewBitmap()
		rb2 := NewBitmap()
		rb3 := NewBitmap()
		for i := uint32(500); i < 75000; i++ {
			rb1.Add(i)
		}
		for i := uint32(0); i < 1000000; i += 7 {
			rb2.Add(i)
		}
		for i := uint32(0); i < 1000000; i += 1001 {
			rb3.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 1001 {
			rb1.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 3 {
			rb2.Add(i)
		}
		for i := uint32(1000000); i < 2000000; i += 7 {
			rb3.Add(i)
		}
		rb1.RunOptimize()

		rb1.Or(rb2)
		rb1.Or(rb3)
		bigand := And(And(rb1, rb2), rb3)
		bigxor := Xor(Xor(rb1, rb2), rb3)

		if or != nil && !or(rb1, rb2, rb3).Equals(rb1) {
			t.Error()
		}

		if and != nil && !and(rb1, rb2, rb3).Equals(bigand) {
			t.Error()
		}

		if xor != nil && !xor(rb1, rb2, rb3).Equals(bigxor) {
			t.Error()
		}
	})
}

func TestParAggregations(t *testing.T) {
	for _, p := range [...]int{1, 2, 4} {
		andFunc := func(bitmaps ...*Bitmap) *Bitmap {
			return ParAnd(p, bitmaps...)
		}
		orFunc := func(bitmaps ...*Bitmap) *Bitmap {
			return ParOr(p, bitmaps...)
		}

		t.Run(fmt.Sprintf("par%d", p), func(t *testing.T) {
			testAggregations(t, andFunc, orFunc, nil)
		})
	}
}

func TestFastAggregations(t *testing.T) {
	testAggregations(t, FastAnd, FastOr, nil)
}

func TestHeapAggregations(t *testing.T) {
	testAggregations(t, nil, HeapOr, HeapXor)
}
<|endoftext|>


<commit_before>package overcurrent

import (
	"errors"
	"math/rand"
	"time"
)

type CircuitState int
type CircuitEvent int

const (
	OpenState CircuitState = iota
	ClosedState
	HalfClosedState

	TripEvent CircuitEvent = iota
	ResetEvent

	DefaultInvocationTimeout          = 0.01
	DefaultResetTimeout               = 0.1
	DefaultHalfClosedRetryProbability = 0.5
)

var (
	CircuitOpenError       = errors.New("Circuit is open.")
	InvocationTimeoutError = errors.New("Invocation has timed out.")
)

//
//

type BreakerConfig struct {
	InvocationTimeout          float64
	ResetTimeout               float64
	HalfClosedRetryProbability float64
	TripCondition              TripCondition
	FailureInterpreter         FailureInterpreter
}

func NewBreakerConfig() BreakerConfig {
	return BreakerConfig{
		InvocationTimeout:          DefaultInvocationTimeout,
		ResetTimeout:               DefaultResetTimeout,
		HalfClosedRetryProbability: DefaultHalfClosedRetryProbability,
		TripCondition:              NewConsecutiveFailureTripCondition(5),
		FailureInterpreter:         NewAnyErrorFailureInterpreter(),
	}
}

//
//

type Breaker struct {
	circuit func() error
	config  BreakerConfig
	Events  chan CircuitEvent

	hardTrip        bool
	lastFailureTime *time.Time
}

func NewBreaker(circuit func() error, config BreakerConfig) *Breaker {
	return &Breaker{
		circuit: circuit,
		config:  config,
		Events:  make(chan CircuitEvent),
	}
}

func (b *Breaker) State() CircuitState {
	if b.config.TripCondition.ShouldTrip() {
		if time.Now().Sub(*b.lastFailureTime) > time.Duration(b.config.ResetTimeout) {
			return HalfClosedState
		} else {
			return OpenState
		}
	} else {
		return ClosedState
	}
}

func (b *Breaker) Call() error {
	if !b.shouldTry() {
		return CircuitOpenError
	}

	if err := b.callWithTimeout(); err != nil && b.config.FailureInterpreter.ShouldTrip(err) {
		b.recordError()
		return err
	}

	b.Reset()
	return nil
}

func (b *Breaker) shouldTry() bool {
	if b.hardTrip || b.State() == OpenState {
		return false
	}

	if b.State() == HalfClosedState {
		return rand.Float64() <= b.config.HalfClosedRetryProbability
	}

	return true
}

func (b *Breaker) callWithTimeout() error {
	if b.config.InvocationTimeout == 0 {
		return b.circuit()
	}

	c := make(chan error)
	go func() {
		c <- b.circuit()
		close(c)
	}()

	select {
	case err := <-c:
		return err
	case <-time.After(time.Duration(b.config.InvocationTimeout)):
		return InvocationTimeoutError
	}
}

func (b *Breaker) recordError() {
	now := time.Now()
	b.lastFailureTime = &now

	b.config.TripCondition.Failure()
	b.sendEvent(TripEvent)
}

func (b *Breaker) sendEvent(event CircuitEvent) {
	select {
	case b.Events <- event:
	}
}

func (b *Breaker) Trip() {
	b.hardTrip = true
	b.recordError()
}

func (b *Breaker) Reset() {
	b.lastFailureTime = nil
	b.hardTrip = false

	b.config.TripCondition.Success()
	b.sendEvent(ResetEvent)
}
<commit_msg>Remove circuit from configuration.<commit_after>package overcurrent

import (
	"errors"
	"math/rand"
	"time"
)

type CircuitState int
type CircuitEvent int

const (
	OpenState CircuitState = iota
	ClosedState
	HalfClosedState

	TripEvent CircuitEvent = iota
	ResetEvent

	DefaultInvocationTimeout          = 0.01
	DefaultResetTimeout               = 0.1
	DefaultHalfClosedRetryProbability = 0.5
)

var (
	CircuitOpenError       = errors.New("Circuit is open.")
	InvocationTimeoutError = errors.New("Invocation has timed out.")
)

//
//

type BreakerConfig struct {
	InvocationTimeout          float64
	ResetTimeout               float64
	HalfClosedRetryProbability float64
	TripCondition              TripCondition
	FailureInterpreter         FailureInterpreter
}

func NewBreakerConfig() BreakerConfig {
	return BreakerConfig{
		InvocationTimeout:          DefaultInvocationTimeout,
		ResetTimeout:               DefaultResetTimeout,
		HalfClosedRetryProbability: DefaultHalfClosedRetryProbability,
		TripCondition:              NewConsecutiveFailureTripCondition(5),
		FailureInterpreter:         NewAnyErrorFailureInterpreter(),
	}
}

//
//

type Breaker struct {
	config BreakerConfig
	Events chan CircuitEvent

	hardTrip        bool
	lastFailureTime *time.Time
}

func NewBreaker(config BreakerConfig) *Breaker {
	return &Breaker{
		config: config,
		Events: make(chan CircuitEvent),
	}
}

func (b *Breaker) State() CircuitState {
	if b.config.TripCondition.ShouldTrip() {
		if time.Now().Sub(*b.lastFailureTime) > time.Duration(b.config.ResetTimeout) {
			return HalfClosedState
		} else {
			return OpenState
		}
	} else {
		return ClosedState
	}
}

func (b *Breaker) Call(f func() error) error {
	if !b.shouldTry() {
		return CircuitOpenError
	}

	if err := b.callWithTimeout(f); err != nil && b.config.FailureInterpreter.ShouldTrip(err) {
		b.recordError()
		return err
	}

	b.Reset()
	return nil
}

func (b *Breaker) shouldTry() bool {
	if b.hardTrip || b.State() == OpenState {
		return false
	}

	if b.State() == HalfClosedState {
		return rand.Float64() <= b.config.HalfClosedRetryProbability
	}

	return true
}

func (b *Breaker) callWithTimeout(f func() error) error {
	if b.config.InvocationTimeout == 0 {
		return f()
	}

	c := make(chan error)
	go func() {
		c <- f()
		close(c)
	}()

	select {
	case err := <-c:
		return err
	case <-time.After(time.Duration(b.config.InvocationTimeout)):
		return InvocationTimeoutError
	}
}

func (b *Breaker) recordError() {
	now := time.Now()
	b.lastFailureTime = &now

	b.config.TripCondition.Failure()
	b.sendEvent(TripEvent)
}

func (b *Breaker) sendEvent(event CircuitEvent) {
	select {
	case b.Events <- event:
	}
}

func (b *Breaker) Trip() {
	b.hardTrip = true
	b.recordError()
}

func (b *Breaker) Reset() {
	b.lastFailureTime = nil
	b.hardTrip = false

	b.config.TripCondition.Success()
	b.sendEvent(ResetEvent)
}
<|endoftext|>


<commit_before>package surf

import (
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"github.com/headzoo/surf/errors"
	"github.com/headzoo/surf/jars"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"runtime"
	"strings"
	"syscall"
	"time"
)

const (
	// Name is used as the browser name in the default user agent.
	Name = "Surf"
	// Version is used as the version in the default user agent.
	Version = "0.4.2"
)

// Attribute represents a Browser capability.
type Attribute int

// AttributeMap represents a map of Attribute values.
type AttributeMap map[Attribute]bool

const (
	// SendRefererAttribute instructs a Browser to send the Referer header.
	SendRefererAttribute Attribute = iota
	// MetaRefreshHandlingAttribute instructs a Browser to handle the refresh meta tag.
	MetaRefreshHandlingAttribute
	// FollowRedirectsAttribute instructs a Browser to follow Location headers.
	FollowRedirectsAttribute
)

var (
	// DefaultUserAgent is the global user agent value.
	DefaultUserAgent string = fmt.Sprintf("%s/%s (%s; %s)", Name, Version, runtime.Version(), osRelease())
	// DefaultSendRefererAttribute is the global value for the AttributeSendReferer attribute.
	DefaultSendRefererAttribute bool = true
	// DefaultMetaRefreshHandlingAttribute is the global value for the AttributeHandleRefresh attribute.
	DefaultMetaRefreshHandlingAttribute bool = true
	// DefaultFollowRedirectsAttribute is the global value for the AttributeFollowRedirects attribute.
	DefaultFollowRedirectsAttribute bool = true
)

// exprPrefixesImplied are strings a selection expr may start with, and the tag is implied.
var exprPrefixesImplied = []string{":", ".", "["}

// Browsable represents an HTTP web browser.
type Browsable interface {
	Document
	Get(url string) error
	GetForm(url string, data url.Values) error
	GetBookmark(name string) error
	Post(url string, bodyType string, body io.Reader) error
	PostForm(url string, data url.Values) error
	Bookmark(name string) error
	FollowLink(expr string) error
	Links() []string
	Form(expr string) (FormElement, error)
	Forms() []FormElement
	Back() bool
	Reload() error
	Cookies() []*http.Cookie
	SetAttribute(a Attribute, v bool)
	ResolveUrl(u *url.URL) *url.URL
	Stop() error
}

// Browser is the default Browser implementation.
type Browser struct {
	*Page
	UserAgent   string
	CookieJar   http.CookieJar
	Bookmarks   jars.BookmarksJar
	History     *PageStack
	lastRequest *http.Request
	attributes  AttributeMap
	refresh     *time.Timer
	closed      bool
}

// NewBrowser creates and returns a *Browser type.
func NewBrowser() (*Browser, error) {
	cookies, err := cookiejar.New(nil)
	if err != nil {
		return nil, err
	}
	bookmarks := jars.NewMemoryBookmarks()
	err = bookmarks.Open()
	if err != nil {
		return nil, err
	}

	b := &Browser{
		UserAgent: DefaultUserAgent,
		CookieJar: cookies,
		Bookmarks: bookmarks,
		History:   NewPageStack(),
		attributes: AttributeMap{
			SendRefererAttribute:         DefaultSendRefererAttribute,
			MetaRefreshHandlingAttribute: DefaultMetaRefreshHandlingAttribute,
			FollowRedirectsAttribute:     DefaultFollowRedirectsAttribute,
		},
	}
	runtime.SetFinalizer(b, b.Stop())

	return b, nil
}

// Stop releases resources held by the browser.
//
// This method is called automatically by the runtime, but is safe to call repeatedly
// without any errors.
//
// The browser should not be used after Stop is called. Doing so will cause
// unexpected behavior.
func (b *Browser) Stop() error {
	if !b.closed {
		err := b.Bookmarks.Close()
		if err != nil {
			return err
		}
		b.closed = true
	}
	return nil
}

// Get requests the given URL using the GET method.
func (b *Browser) Get(u string) error {
	return b.sendGet(u, nil)
}

// GetForm appends the data values to the given URL and sends a GET request.
func (b *Browser) GetForm(u string, data url.Values) error {
	ul, err := url.Parse(u)
	if err != nil {
		return err
	}
	ul.RawQuery = data.Encode()

	return b.Get(ul.String())
}

// GetBookmark calls Get() with the URL for the bookmark with the given name.
func (b *Browser) GetBookmark(name string) error {
	url, err := b.Bookmarks.Read(name)
	if err != nil {
		return err
	}
	return b.Get(url)
}

// Post requests the given URL using the POST method.
func (b *Browser) Post(u string, bodyType string, body io.Reader) error {
	return b.sendPost(u, bodyType, body, nil)
}

// PostForm requests the given URL using the POST method with the given data.
func (b *Browser) PostForm(u string, data url.Values) error {
	return b.Post(u, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}

// Bookmark saves the page URL in the bookmarks with the given name.
func (b *Browser) Bookmark(name string) error {
	url := b.ResolveUrl(b.Page.Url())
	return b.Bookmarks.Save(name, url.String())
}

// FollowLink finds an anchor tag within the current document matching the expr,
// and calls Get() using the anchor href attribute value.
//
// The expr can be any valid goquery expression, and the "a" tag is implied. The
// method can be called using only ":contains('foo')" and the expr is automatically
// converted to "a:contains('foo')". A complete expression can still be used, for
// instance "p.title a.foo".
func (b *Browser) FollowLink(expr string) error {
	sel := b.Page.doc.Find(prefixSelection(expr, "a"))
	if sel.Length() == 0 {
		return errors.NewElementNotFound(
			"Anchor not found matching expr '%s'.", expr)
	}
	if !sel.Is("a") {
		return errors.NewElementNotFound(
			"Expr '%s' does not match an anchor tag.", expr)
	}

	href, ok := sel.Attr("href")
	if !ok {
		return errors.NewLinkNotFound("No link found matching expr %s.", expr)
	}
	hurl, err := url.Parse(href)
	if err != nil {
		return err
	}
	hurl = b.ResolveUrl(hurl)

	return b.sendGet(hurl.String(), b.Page)
}

// Links returns an array of every anchor tag href value found in the current page.
func (b *Browser) Links() []string {
	sel := b.Page.doc.Find("a")
	links := make([]string, 0, sel.Length())
	sel.Each(func(_ int, s *goquery.Selection) {
		href, ok := s.Attr("href")
		if ok {
			links = append(links, href)
		}
	})

	return links
}

// Cookies returns the cookies for the current page.
func (b *Browser) Cookies() []*http.Cookie {
	return b.CookieJar.Cookies(b.Page.Url())
}

// Form returns the form in the current page that matches the given expr.
func (b *Browser) Form(expr string) (FormElement, error) {
	sel := b.Page.doc.Find(prefixSelection(expr, "form"))
	if sel.Length() == 0 {
		return nil, errors.NewElementNotFound(
			"Form not found matching expr '%s'.", expr)
	}
	if !sel.Is("form") {
		return nil, errors.NewElementNotFound(
			"Expr '%s' does not match a form tag.", expr)
	}

	return NewForm(b, sel), nil
}

// Forms returns an array of every form in the page.
func (b *Browser) Forms() []FormElement {
	sel := b.Page.doc.Find("form")
	len := sel.Length()
	if len == 0 {
		return nil
	}

	forms := make([]FormElement, len)
	sel.Each(func(_ int, s *goquery.Selection) {
		forms = append(forms, NewForm(b, s))
	})
	return forms
}

// Back loads the previously requested page.
func (b *Browser) Back() bool {
	if b.History.Len() > 0 {
		b.Page = b.History.Pop()
		return true
	}
	return false
}

// Reload duplicates the last successful request.
func (b *Browser) Reload() error {
	if b.lastRequest != nil {
		return b.send(b.lastRequest)
	}
	return errors.NewPageNotLoaded("Cannot reload, the previous request failed.")
}

// SetAttribute sets a browser instruction attribute.
func (b *Browser) SetAttribute(a Attribute, v bool) {
	b.attributes[a] = v
}

// ResolveUrl returns an absolute URL for a possibly relative URL.
func (b *Browser) ResolveUrl(u *url.URL) *url.URL {
	return b.Url().ResolveReference(u)
}

// client creates, configures, and returns a *http.Client type.
func (b *Browser) client() *http.Client {
	client := &http.Client{}
	client.Jar = b.CookieJar
	client.CheckRedirect = b.shouldRedirect
	return client
}

// request creates and returns a *http.Request type.
// Sets any headers that need to be sent with the request.
func (b *Browser) request(method, url string) (*http.Request, error) {
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header["User-Agent"] = []string{b.UserAgent}

	return req, nil
}

// sendGet makes an HTTP GET request for the given URL.
// When via is not nil, and AttributeSendReferer is true, the Referer header will
// be set to via's URL.
func (b *Browser) sendGet(url string, via *Page) error {
	req, err := b.request("GET", url)
	if err != nil {
		return err
	}
	if b.attributes[SendRefererAttribute] && via != nil {
		req.Header["Referer"] = []string{via.Url().String()}
	}

	return b.send(req)
}

// sendPost makes an HTTP POST request for the given URL.
// When via is not nil, and AttributeSendReferer is true, the Referer header will
// be set to via's URL.
func (b *Browser) sendPost(url string, bodyType string, body io.Reader, via *Page) error {
	req, err := b.request("POST", url)
	if err != nil {
		return err
	}
	rc, ok := body.(io.ReadCloser)
	if !ok && body != nil {
		rc = ioutil.NopCloser(body)
	}
	req.Body = rc
	req.Header["Content-Type"] = []string{bodyType}
	if b.attributes[SendRefererAttribute] && via != nil {
		req.Header["Referer"] = []string{via.Url().String()}
	}

	return b.send(req)
}

// send uses the given *http.Request to make an HTTP request.
func (b *Browser) send(req *http.Request) error {
	b.preSend()
	resp, err := b.client().Do(req)
	if err != nil {
		return err
	}
	body, err := goquery.NewDocumentFromResponse(resp)
	if err != nil {
		return err
	}

	b.lastRequest = req
	b.History.Push(b.Page)
	b.Page = NewPage(resp, body)
	b.postSend()

	return nil
}

// preSend sets browser state before sending a request.
func (b *Browser) preSend() {
	if b.refresh != nil {
		b.refresh.Stop()
	}
}

// postSend sets browser state after sending a request.
func (b *Browser) postSend() {
	if b.attributes[MetaRefreshHandlingAttribute] {
		sel := b.Page.doc.Find("meta[http-equiv='refresh']")
		if sel.Length() > 0 {
			attr, ok := sel.Attr("content")
			if ok {
				dur, err := time.ParseDuration(attr + "s")
				if err == nil {
					b.refresh = time.NewTimer(dur)
					go func() {
						<-b.refresh.C
						b.Reload()
					}()
				}
			}
		}
	}
}

// shouldRedirect is used as the value to http.Client.CheckRedirect.
func (b *Browser) shouldRedirect(req *http.Request, _ []*http.Request) error {
	if b.attributes[FollowRedirectsAttribute] {
		return nil
	}
	return errors.NewLocation(
		"Redirects are disabled. Cannot follow '%s'.", req.URL.String())
}

// prefixSelection prefixes a selection expr with elm when sel is prefixed with
// one of the values from exprPrefixesImplied.
func prefixSelection(sel, elm string) string {
	for _, prefix := range exprPrefixesImplied {
		if strings.HasPrefix(sel, prefix) {
			return elm + sel
		}
	}
	return sel
}

// osRelease returns the name of the OS and it's release version.
func osRelease() string {
	buf := &syscall.Utsname{}
	err := syscall.Uname(buf)
	if err != nil {
		return "0.0"
	}

	return charsToString(buf.Sysname) + "/" + charsToString(buf.Release)
}

// charsToString converts a [65]int8 byte array into a string.
func charsToString(ca [65]int8) string {
	s := make([]byte, len(ca))
	var lens int
	for ; lens < len(ca); lens++ {
		if ca[lens] == 0 {
			break
		}
		s[lens] = uint8(ca[lens])
	}
	return string(s[0:lens])
}
<commit_msg>Removed browser stop<commit_after>package surf

import (
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"github.com/headzoo/surf/errors"
	"github.com/headzoo/surf/jars"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"runtime"
	"strings"
	"syscall"
	"time"
)

const (
	// Name is used as the browser name in the default user agent.
	Name = "Surf"
	// Version is used as the version in the default user agent.
	Version = "0.4.2"
)

// Attribute represents a Browser capability.
type Attribute int

// AttributeMap represents a map of Attribute values.
type AttributeMap map[Attribute]bool

const (
	// SendRefererAttribute instructs a Browser to send the Referer header.
	SendRefererAttribute Attribute = iota
	// MetaRefreshHandlingAttribute instructs a Browser to handle the refresh meta tag.
	MetaRefreshHandlingAttribute
	// FollowRedirectsAttribute instructs a Browser to follow Location headers.
	FollowRedirectsAttribute
)

var (
	// DefaultUserAgent is the global user agent value.
	DefaultUserAgent string = fmt.Sprintf("%s/%s (%s; %s)", Name, Version, runtime.Version(), osRelease())
	// DefaultSendRefererAttribute is the global value for the AttributeSendReferer attribute.
	DefaultSendRefererAttribute bool = true
	// DefaultMetaRefreshHandlingAttribute is the global value for the AttributeHandleRefresh attribute.
	DefaultMetaRefreshHandlingAttribute bool = true
	// DefaultFollowRedirectsAttribute is the global value for the AttributeFollowRedirects attribute.
	DefaultFollowRedirectsAttribute bool = true
)

// exprPrefixesImplied are strings a selection expr may start with, and the tag is implied.
var exprPrefixesImplied = []string{":", ".", "["}

// Browsable represents an HTTP web browser.
type Browsable interface {
	Document
	Get(url string) error
	GetForm(url string, data url.Values) error
	GetBookmark(name string) error
	Post(url string, bodyType string, body io.Reader) error
	PostForm(url string, data url.Values) error
	Bookmark(name string) error
	FollowLink(expr string) error
	Links() []string
	Form(expr string) (FormElement, error)
	Forms() []FormElement
	Back() bool
	Reload() error
	Cookies() []*http.Cookie
	SetAttribute(a Attribute, v bool)
	ResolveUrl(u *url.URL) *url.URL
}

// Browser is the default Browser implementation.
type Browser struct {
	*Page
	UserAgent   string
	CookieJar   http.CookieJar
	Bookmarks   jars.BookmarksJar
	History     *PageStack
	lastRequest *http.Request
	attributes  AttributeMap
	refresh     *time.Timer
}

// NewBrowser creates and returns a *Browser type.
func NewBrowser() (*Browser, error) {
	cookies, err := cookiejar.New(nil)
	if err != nil {
		return nil, err
	}

	return &Browser{
		UserAgent: DefaultUserAgent,
		CookieJar: cookies,
		Bookmarks: jars.NewMemoryBookmarks(),
		History:   NewPageStack(),
		attributes: AttributeMap{
			SendRefererAttribute:         DefaultSendRefererAttribute,
			MetaRefreshHandlingAttribute: DefaultMetaRefreshHandlingAttribute,
			FollowRedirectsAttribute:     DefaultFollowRedirectsAttribute,
		},
	}, nil
}

// Get requests the given URL using the GET method.
func (b *Browser) Get(u string) error {
	return b.sendGet(u, nil)
}

// GetForm appends the data values to the given URL and sends a GET request.
func (b *Browser) GetForm(u string, data url.Values) error {
	ul, err := url.Parse(u)
	if err != nil {
		return err
	}
	ul.RawQuery = data.Encode()

	return b.Get(ul.String())
}

// GetBookmark calls Get() with the URL for the bookmark with the given name.
func (b *Browser) GetBookmark(name string) error {
	url, err := b.Bookmarks.Read(name)
	if err != nil {
		return err
	}
	return b.Get(url)
}

// Post requests the given URL using the POST method.
func (b *Browser) Post(u string, bodyType string, body io.Reader) error {
	return b.sendPost(u, bodyType, body, nil)
}

// PostForm requests the given URL using the POST method with the given data.
func (b *Browser) PostForm(u string, data url.Values) error {
	return b.Post(u, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
}

// Bookmark saves the page URL in the bookmarks with the given name.
func (b *Browser) Bookmark(name string) error {
	url := b.ResolveUrl(b.Page.Url())
	return b.Bookmarks.Save(name, url.String())
}

// FollowLink finds an anchor tag within the current document matching the expr,
// and calls Get() using the anchor href attribute value.
//
// The expr can be any valid goquery expression, and the "a" tag is implied. The
// method can be called using only ":contains('foo')" and the expr is automatically
// converted to "a:contains('foo')". A complete expression can still be used, for
// instance "p.title a.foo".
func (b *Browser) FollowLink(expr string) error {
	sel := b.Page.doc.Find(prefixSelection(expr, "a"))
	if sel.Length() == 0 {
		return errors.NewElementNotFound(
			"Anchor not found matching expr '%s'.", expr)
	}
	if !sel.Is("a") {
		return errors.NewElementNotFound(
			"Expr '%s' does not match an anchor tag.", expr)
	}

	href, ok := sel.Attr("href")
	if !ok {
		return errors.NewLinkNotFound("No link found matching expr %s.", expr)
	}
	hurl, err := url.Parse(href)
	if err != nil {
		return err
	}
	hurl = b.ResolveUrl(hurl)

	return b.sendGet(hurl.String(), b.Page)
}

// Links returns an array of every anchor tag href value found in the current page.
func (b *Browser) Links() []string {
	sel := b.Page.doc.Find("a")
	links := make([]string, 0, sel.Length())
	sel.Each(func(_ int, s *goquery.Selection) {
		href, ok := s.Attr("href")
		if ok {
			links = append(links, href)
		}
	})

	return links
}

// Cookies returns the cookies for the current page.
func (b *Browser) Cookies() []*http.Cookie {
	return b.CookieJar.Cookies(b.Page.Url())
}

// Form returns the form in the current page that matches the given expr.
func (b *Browser) Form(expr string) (FormElement, error) {
	sel := b.Page.doc.Find(prefixSelection(expr, "form"))
	if sel.Length() == 0 {
		return nil, errors.NewElementNotFound(
			"Form not found matching expr '%s'.", expr)
	}
	if !sel.Is("form") {
		return nil, errors.NewElementNotFound(
			"Expr '%s' does not match a form tag.", expr)
	}

	return NewForm(b, sel), nil
}

// Forms returns an array of every form in the page.
func (b *Browser) Forms() []FormElement {
	sel := b.Page.doc.Find("form")
	len := sel.Length()
	if len == 0 {
		return nil
	}

	forms := make([]FormElement, len)
	sel.Each(func(_ int, s *goquery.Selection) {
		forms = append(forms, NewForm(b, s))
	})
	return forms
}

// Back loads the previously requested page.
func (b *Browser) Back() bool {
	if b.History.Len() > 0 {
		b.Page = b.History.Pop()
		return true
	}
	return false
}

// Reload duplicates the last successful request.
func (b *Browser) Reload() error {
	if b.lastRequest != nil {
		return b.send(b.lastRequest)
	}
	return errors.NewPageNotLoaded("Cannot reload, the previous request failed.")
}

// SetAttribute sets a browser instruction attribute.
func (b *Browser) SetAttribute(a Attribute, v bool) {
	b.attributes[a] = v
}

// ResolveUrl returns an absolute URL for a possibly relative URL.
func (b *Browser) ResolveUrl(u *url.URL) *url.URL {
	return b.Url().ResolveReference(u)
}

// client creates, configures, and returns a *http.Client type.
func (b *Browser) client() *http.Client {
	client := &http.Client{}
	client.Jar = b.CookieJar
	client.CheckRedirect = b.shouldRedirect
	return client
}

// request creates and returns a *http.Request type.
// Sets any headers that need to be sent with the request.
func (b *Browser) request(method, url string) (*http.Request, error) {
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header["User-Agent"] = []string{b.UserAgent}

	return req, nil
}

// sendGet makes an HTTP GET request for the given URL.
// When via is not nil, and AttributeSendReferer is true, the Referer header will
// be set to via's URL.
func (b *Browser) sendGet(url string, via *Page) error {
	req, err := b.request("GET", url)
	if err != nil {
		return err
	}
	if b.attributes[SendRefererAttribute] && via != nil {
		req.Header["Referer"] = []string{via.Url().String()}
	}

	return b.send(req)
}

// sendPost makes an HTTP POST request for the given URL.
// When via is not nil, and AttributeSendReferer is true, the Referer header will
// be set to via's URL.
func (b *Browser) sendPost(url string, bodyType string, body io.Reader, via *Page) error {
	req, err := b.request("POST", url)
	if err != nil {
		return err
	}
	rc, ok := body.(io.ReadCloser)
	if !ok && body != nil {
		rc = ioutil.NopCloser(body)
	}
	req.Body = rc
	req.Header["Content-Type"] = []string{bodyType}
	if b.attributes[SendRefererAttribute] && via != nil {
		req.Header["Referer"] = []string{via.Url().String()}
	}

	return b.send(req)
}

// send uses the given *http.Request to make an HTTP request.
func (b *Browser) send(req *http.Request) error {
	b.preSend()
	resp, err := b.client().Do(req)
	if err != nil {
		return err
	}
	body, err := goquery.NewDocumentFromResponse(resp)
	if err != nil {
		return err
	}

	b.lastRequest = req
	b.History.Push(b.Page)
	b.Page = NewPage(resp, body)
	b.postSend()

	return nil
}

// preSend sets browser state before sending a request.
func (b *Browser) preSend() {
	if b.refresh != nil {
		b.refresh.Stop()
	}
}

// postSend sets browser state after sending a request.
func (b *Browser) postSend() {
	if b.attributes[MetaRefreshHandlingAttribute] {
		sel := b.Page.doc.Find("meta[http-equiv='refresh']")
		if sel.Length() > 0 {
			attr, ok := sel.Attr("content")
			if ok {
				dur, err := time.ParseDuration(attr + "s")
				if err == nil {
					b.refresh = time.NewTimer(dur)
					go func() {
						<-b.refresh.C
						b.Reload()
					}()
				}
			}
		}
	}
}

// shouldRedirect is used as the value to http.Client.CheckRedirect.
func (b *Browser) shouldRedirect(req *http.Request, _ []*http.Request) error {
	if b.attributes[FollowRedirectsAttribute] {
		return nil
	}
	return errors.NewLocation(
		"Redirects are disabled. Cannot follow '%s'.", req.URL.String())
}

// prefixSelection prefixes a selection expr with elm when sel is prefixed with
// one of the values from exprPrefixesImplied.
func prefixSelection(sel, elm string) string {
	for _, prefix := range exprPrefixesImplied {
		if strings.HasPrefix(sel, prefix) {
			return elm + sel
		}
	}
	return sel
}

// osRelease returns the name of the OS and it's release version.
func osRelease() string {
	buf := &syscall.Utsname{}
	err := syscall.Uname(buf)
	if err != nil {
		return "0.0"
	}

	return charsToString(buf.Sysname) + "/" + charsToString(buf.Release)
}

// charsToString converts a [65]int8 byte array into a string.
func charsToString(ca [65]int8) string {
	s := make([]byte, len(ca))
	var lens int
	for ; lens < len(ca); lens++ {
		if ca[lens] == 0 {
			break
		}
		s[lens] = uint8(ca[lens])
	}
	return string(s[0:lens])
}
<|endoftext|>


<commit_before>package helpers

import (
	"net/http"

	"github.com/pkg/errors"
)

func SniffMime(data []byte) (mimetype string, err error) {
	if len(data) < 512 {
		return mimetype, errors.New("file too small to sniff mime type")
	}

	// sniff filetype from first 512 bytes
	contentType := http.DetectContentType(data[0:511])

	return contentType, nil
}
<commit_msg>[core] improves file type sniffing<commit_after>package helpers

import (
	"net/http"

	"github.com/pkg/errors"
)

func SniffMime(data []byte) (mimetype string, err error) {
	mimetype = http.DetectContentType(data)
	if mimetype == "application/octet-stream" {
		return mimetype, errors.New("unable to sniff filetype")
	}

	return mimetype, nil
}
<|endoftext|>


<commit_before>package sessmemory

import (
	"github.com/gabstv/goboots"
)

type MemoryDbSession struct {
	gcsid     chan string
	gcs       chan *goboots.Session
	scs       chan *goboots.Session
	rcs       chan *goboots.Session
	sessions  map[string]*goboots.Session
	connected bool
}

func (m *MemoryDbSession) GetSession(sid string) (*goboots.Session, error) {
	m.connect()
	m.gcsid <- sid
	return <-m.gcs, nil
}

func (m *MemoryDbSession) PutSession(session *goboots.Session) error {
	m.connect()
	m.scs <- session
	return nil
}

func (m *MemoryDbSession) NewSession(session *goboots.Session) error {
	return m.PutSession(session)
}

func (m *MemoryDbSession) RemoveSession(session *goboots.Session) error {
	if session == nil {
		return nil
	}
	m.connect()
	m.rcs <- session
	return nil
}

func (m *MemoryDbSession) getSessionWorker() {
	for m.connected {
		sid := <-gcsid
		m.gcs <- m.sessions[sid]
	}
}

func (m *MemoryDbSession) setSessionWorker() {
	for m.connected {
		session := <-m.scs
		m.sessions[session.SID] = session
	}
}

func (m *MemoryDbSession) delSessionWorker() {
	for m.connected {
		session := <-m.rcs
		delete(m.sessions, session.SID)
	}
}

func (m *MemoryDbSession) connect() error {
	if m.connected {
		return nil
	}
	m.sessions = make(map[string]*goboots.Session, 0)
	m.gcsid = new(chan string)
	m.gcs = new(chan *goboots.Session)
	m.scs = new(chan *goboots.Session)
	m.rcs = new(chan *goboots.Session)
	m.connected = true
	go m.getSessionWorker()
	go m.setSessionWorker()
	go m.delSessionWorker()
	return nil
}

func init() {
	goboots.RegisterSessionStorageDriver("sessmemory", &MemoryDbSession{})
}
<commit_msg>added init function<commit_after>package sessmemory

import (
	"github.com/gabstv/goboots"
)

type MemoryDbSession struct {
	gcsid     chan string
	gcs       chan *goboots.Session
	scs       chan *goboots.Session
	rcs       chan *goboots.Session
	sessions  map[string]*goboots.Session
	connected bool
}

func (m *MemoryDbSession) GetSession(sid string) (*goboots.Session, error) {
	m.connect()
	m.gcsid <- sid
	return <-m.gcs, nil
}

func (m *MemoryDbSession) PutSession(session *goboots.Session) error {
	m.connect()
	m.scs <- session
	return nil
}

func (m *MemoryDbSession) NewSession(session *goboots.Session) error {
	return m.PutSession(session)
}

func (m *MemoryDbSession) RemoveSession(session *goboots.Session) error {
	if session == nil {
		return nil
	}
	m.connect()
	m.rcs <- session
	return nil
}

func (m *MemoryDbSession) getSessionWorker() {
	for m.connected {
		sid := <-m.gcsid
		m.gcs <- m.sessions[sid]
	}
}

func (m *MemoryDbSession) setSessionWorker() {
	for m.connected {
		session := <-m.scs
		m.sessions[session.SID] = session
	}
}

func (m *MemoryDbSession) delSessionWorker() {
	for m.connected {
		session := <-m.rcs
		delete(m.sessions, session.SID)
	}
}

func (m *MemoryDbSession) connect() error {
	if m.connected {
		return nil
	}
	m.sessions = make(map[string]*goboots.Session, 0)
	m.connected = true
	go m.getSessionWorker()
	go m.setSessionWorker()
	go m.delSessionWorker()
	return nil
}

func init() {
	goboots.RegisterSessionStorageDriver("sessmemory", &MemoryDbSession{})
}
<|endoftext|>


<commit_before>//
// Copyright 2015, Sander van Harmelen
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package git

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"
	"time"

	"github.com/xanzy/go-gitlab"
)

const (
	invalidGitLabToken = "The token configured for GitLab group %s is not valid!"
)

// GetContent implements the Git interface
func (g *GitLab) GetContent(project, path string) (*File, interface{}, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	treeOpts := &gitlab.ListTreeOptions{
		Path: gitlab.String(path),
	}
	tree, resp, err := g.client.Repositories.ListTree(ns, treeOpts)
	if err != nil {
		if resp != nil {
			switch resp.StatusCode {
			case http.StatusNotFound:
				return nil, nil, nil
			case http.StatusUnauthorized:
				return nil, nil, fmt.Errorf(invalidGitLabToken, g.group)
			}
		}
		return nil, nil, fmt.Errorf("Error retrieving tree for %s: %v", path, err)
	}

	if len(tree) > 0 {
		var files []string
		for _, file := range tree {
			files = append(files, filepath.Join(path, file.Name))
		}

		return nil, files, nil
	}

	fileOpts := &gitlab.GetFileOptions{
		Ref: gitlab.String("master"),
	}
	file, resp, err := g.client.RepositoryFiles.GetFile(ns, path, fileOpts)
	if err != nil {
		if resp != nil {
			switch resp.StatusCode {
			case http.StatusNotFound:
				return nil, nil, nil
			case http.StatusUnauthorized:
				return nil, nil, fmt.Errorf(invalidGitLabToken, g.group)
			}
		}
		return nil, nil, fmt.Errorf("Error retrieving file %s: %v", path, err)
	}

	f := &File{
		Content: file.Content,
		SHA:     file.CommitID,
	}

	if file.Encoding == "base64" {
		content, err := base64.StdEncoding.DecodeString(file.Content)
		if err != nil {
			return nil, nil, fmt.Errorf("Error decoding file %s, %v", path, err)
		}

		f.Content = string(content)
	}

	return f, nil, nil
}

// CreateFile implements the Git interface
func (g *GitLab) CreateFile(project, path, msg string, usr *User, content []byte) (string, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	opts := &gitlab.CreateFileOptions{
		Branch:        gitlab.String("master"),
		AuthorEmail:   &usr.Mail,
		AuthorName:    &usr.Name,
		Content:       gitlab.String(string(content)),
		CommitMessage: gitlab.String(msg),
	}
	_, resp, err := g.client.RepositoryFiles.CreateFile(ns, path, opts)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return "", fmt.Errorf(invalidGitLabToken, g.group)
		}
		return "", fmt.Errorf("Error creating file %s: %v", path, err)
	}

	return g.shaOfLatestCommit(project)
}

// UpdateFile implements the Git interface
func (g *GitLab) UpdateFile(project, path, sha, msg string, usr *User, content []byte) (string, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	opts := &gitlab.UpdateFileOptions{
		Branch:        gitlab.String("master"),
		AuthorEmail:   &usr.Mail,
		AuthorName:    &usr.Name,
		Content:       gitlab.String(string(content)),
		CommitMessage: gitlab.String(msg),
	}
	_, resp, err := g.client.RepositoryFiles.UpdateFile(ns, path, opts)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return "", fmt.Errorf(invalidGitLabToken, g.group)
		}
		return "", fmt.Errorf("Error updating file %s: %v", path, err)
	}

	return g.shaOfLatestCommit(project)
}

// DeleteFile implements the Git interface
func (g *GitLab) DeleteFile(project, path, sha, msg string, usr *User) (string, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	opts := &gitlab.DeleteFileOptions{
		Branch:        gitlab.String("master"),
		AuthorEmail:   &usr.Mail,
		AuthorName:    &usr.Name,
		CommitMessage: gitlab.String(msg),
	}
	resp, err := g.client.RepositoryFiles.DeleteFile(ns, path, opts)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return "", fmt.Errorf(invalidGitLabToken, g.group)
		}
		return "", fmt.Errorf("Error deleting file %s: %v", path, err)
	}

	return g.shaOfLatestCommit(project)
}

// DeleteDirectory implements the Git interface
func (g *GitLab) DeleteDirectory(project, msg string, dir interface{}, usr *User) error {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	for _, file := range dir.([]string) {
		// Need a special case for when deleting data bag items
		fn := strings.TrimPrefix(file, "data_bags/")
		msg := fmt.Sprintf(msg, strings.TrimSuffix(fn, ".json"))

		opts := &gitlab.DeleteFileOptions{
			Branch:        gitlab.String("master"),
			AuthorEmail:   &usr.Mail,
			AuthorName:    &usr.Name,
			CommitMessage: gitlab.String(msg),
		}
		resp, err := g.client.RepositoryFiles.DeleteFile(ns, file, opts)
		if err != nil {
			if resp != nil && resp.StatusCode == http.StatusUnauthorized {
				return fmt.Errorf(invalidGitLabToken, g.group)
			}
			return fmt.Errorf("Error deleting file %s: %v", file, err)
		}
	}

	return nil
}

// GetDiff implements the Git interface
func (g *GitLab) GetDiff(project, user, sha string) (string, error) {
	u := fmt.Sprintf("/%s/%s/commit/%s.diff", g.group, project, sha)

	req, err := g.client.NewRequest("GET", u, nil, nil)
	if err != nil {
		return "", err
	}

	// Make sure we do not use the API path here!
	req.URL, err = req.URL.Parse(u)
	if err != nil {
		return "", err
	}

	var diff bytes.Buffer
	resp, err := g.client.Do(req, &diff)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return "", fmt.Errorf(invalidGitLabToken, g.group)
		}
		return "", fmt.Errorf("Error retrieving commit %s: %v", sha, err)
	}

	if diff.Len() == 0 {
		return "", nil
	}

	const layout = "Mon Jan 2 3:04 2006"
	t := time.Now()

	msg := fmt.Sprintf("Commit : %s\nDate   : %s\nUser   : %s\n
%s",
		sha,
		t.Format(layout),
		user,
		diff.String(),
	)

	return msg, nil
}

// GetArchiveLink implements the Git interface
func (g *GitLab) GetArchiveLink(project, tag string) (*url.URL, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	_, resp, err := g.client.Projects.GetProject(ns)
	if err != nil {
		if resp != nil {
			switch resp.StatusCode {
			case http.StatusNotFound:
				return nil, nil
			case http.StatusUnauthorized:
				return nil, fmt.Errorf(invalidGitLabToken, g.group)
			}
		}
		return nil, fmt.Errorf("Error retrieving archive link of project %s: %v", project, err)
	}

	u, err := url.Parse(
		fmt.Sprintf("/%s/repository/archive.tar.gz?ref=%s&private_token=%s",
			ns,
			tag,
			g.token,
		),
	)
	if err != nil {
		return nil, fmt.Errorf("Failed to parse archive link: %v", err)
	}

	return g.client.BaseURL().ResolveReference(u), nil
}

// TagRepo implements the Git interface
func (g *GitLab) TagRepo(project, tag string, usr *User) error {
	ns := fmt.Sprintf("%s/%s", g.group, project)
	message := fmt.Sprint("Tagged by Chef-Guard\n")

	opts := &gitlab.CreateTagOptions{
		TagName: gitlab.String(tag),
		Ref:     gitlab.String("master"),
		Message: gitlab.String(message),
	}
	_, resp, err := g.client.Tags.CreateTag(ns, opts)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return fmt.Errorf(invalidGitLabToken, g.group)
		}
		return fmt.Errorf("Error creating tag for project %s: %v", project, err)
	}

	return nil
}

// TagExists implements the Git interface
func (g *GitLab) TagExists(project, tag string) (bool, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	tags, resp, err := g.client.Tags.ListTags(ns)
	if err != nil {
		if resp != nil {
			switch resp.StatusCode {
			case http.StatusNotFound:
				return false, nil
			case http.StatusUnauthorized:
				return false, fmt.Errorf(invalidGitLabToken, g.group)
			}
		}
		return false, fmt.Errorf("Error retrieving tags of project %s: %v", project, err)
	}

	for _, t := range tags {
		if t.Name == tag {
			return true, nil
		}
	}

	return false, nil
}

// UntagRepo implements the Git interface
func (g *GitLab) UntagRepo(project, tag string) error {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	resp, err := g.client.Tags.DeleteTag(ns, tag)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return fmt.Errorf(invalidGitLabToken, g.group)
		}
		return fmt.Errorf("Error deleting tag %s: %v", tag, err)
	}

	return nil
}

func (g *GitLab) shaOfLatestCommit(project string) (string, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	commit, resp, err := g.client.Commits.GetCommit(ns, "master")
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return "", fmt.Errorf(invalidGitLabToken, g.group)
		}
		return "", fmt.Errorf("Error retrieving SHA of latest commit: %v", err)
	}

	return commit.ID, nil
}
<commit_msg>Patching chef guard to work with new gitlab 11.4.10<commit_after>//
// Copyright 2015, Sander van Harmelen
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package git

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"
	"time"

	"github.com/xanzy/go-gitlab"
)

const (
	invalidGitLabToken = "The token configured for GitLab group %s is not valid!"
)

// GetContent implements the Git interface
func (g *GitLab) GetContent(project, path string) (*File, interface{}, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	treeOpts := &gitlab.ListTreeOptions{
		Path: gitlab.String(path),
	}
	tree, resp, err := g.client.Repositories.ListTree(ns, treeOpts)
	if err != nil {
		if resp != nil {
			switch resp.StatusCode {
			case http.StatusNotFound:
				return nil, nil, nil
			case http.StatusUnauthorized:
				return nil, nil, fmt.Errorf(invalidGitLabToken, g.group)
			}
		}
		return nil, nil, fmt.Errorf("Error retrieving tree for %s: %v", path, err)
	}

	if len(tree) > 0 {
		var files []string
		for _, file := range tree {
			files = append(files, filepath.Join(path, file.Name))
		}

		return nil, files, nil
	}

	fileOpts := &gitlab.GetFileOptions{
		Ref: gitlab.String("master"),
	}
	file, resp, err := g.client.RepositoryFiles.GetFile(ns, path, fileOpts)
	if err != nil {
		if resp != nil {
			switch resp.StatusCode {
			case http.StatusNotFound:
				return nil, nil, nil
			case http.StatusUnauthorized:
				return nil, nil, fmt.Errorf(invalidGitLabToken, g.group)
			}
		}
		return nil, nil, fmt.Errorf("Error retrieving file %s: %v", path, err)
	}

	f := &File{
		Content: file.Content,
		SHA:     file.CommitID,
	}

	if file.Encoding == "base64" {
		content, err := base64.StdEncoding.DecodeString(file.Content)
		if err != nil {
			return nil, nil, fmt.Errorf("Error decoding file %s, %v", path, err)
		}

		f.Content = string(content)
	}

	return f, nil, nil
}

// CreateFile implements the Git interface
func (g *GitLab) CreateFile(project, path, msg string, usr *User, content []byte) (string, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	opts := &gitlab.CreateFileOptions{
		Branch:        gitlab.String("master"),
		AuthorEmail:   &usr.Mail,
		AuthorName:    &usr.Name,
		Content:       gitlab.String(string(content)),
		CommitMessage: gitlab.String(msg),
	}
	_, resp, err := g.client.RepositoryFiles.CreateFile(ns, path, opts)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return "", fmt.Errorf(invalidGitLabToken, g.group)
		}
		return "", fmt.Errorf("Error creating file %s: %v", path, err)
	}

	return g.shaOfLatestCommit(project)
}

// UpdateFile implements the Git interface
func (g *GitLab) UpdateFile(project, path, sha, msg string, usr *User, content []byte) (string, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	opts := &gitlab.UpdateFileOptions{
		Branch:        gitlab.String("master"),
		AuthorEmail:   &usr.Mail,
		AuthorName:    &usr.Name,
		Content:       gitlab.String(string(content)),
		CommitMessage: gitlab.String(msg),
	}
	_, resp, err := g.client.RepositoryFiles.UpdateFile(ns, path, opts)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return "", fmt.Errorf(invalidGitLabToken, g.group)
		}
		return "", fmt.Errorf("Error updating file %s: %v", path, err)
	}

	return g.shaOfLatestCommit(project)
}

// DeleteFile implements the Git interface
func (g *GitLab) DeleteFile(project, path, sha, msg string, usr *User) (string, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	opts := &gitlab.DeleteFileOptions{
		Branch:        gitlab.String("master"),
		AuthorEmail:   &usr.Mail,
		AuthorName:    &usr.Name,
		CommitMessage: gitlab.String(msg),
	}
	resp, err := g.client.RepositoryFiles.DeleteFile(ns, path, opts)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return "", fmt.Errorf(invalidGitLabToken, g.group)
		}
		return "", fmt.Errorf("Error deleting file %s: %v", path, err)
	}

	return g.shaOfLatestCommit(project)
}

// DeleteDirectory implements the Git interface
func (g *GitLab) DeleteDirectory(project, msg string, dir interface{}, usr *User) error {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	for _, file := range dir.([]string) {
		// Need a special case for when deleting data bag items
		fn := strings.TrimPrefix(file, "data_bags/")
		msg := fmt.Sprintf(msg, strings.TrimSuffix(fn, ".json"))

		opts := &gitlab.DeleteFileOptions{
			Branch:        gitlab.String("master"),
			AuthorEmail:   &usr.Mail,
			AuthorName:    &usr.Name,
			CommitMessage: gitlab.String(msg),
		}
		resp, err := g.client.RepositoryFiles.DeleteFile(ns, file, opts)
		if err != nil {
			if resp != nil && resp.StatusCode == http.StatusUnauthorized {
				return fmt.Errorf(invalidGitLabToken, g.group)
			}
			return fmt.Errorf("Error deleting file %s: %v", file, err)
		}
	}

	return nil
}

// GetDiff implements the Git interface
func (g *GitLab) GetDiff(project, user, sha string) (string, error) {
	u := fmt.Sprintf("/%s/%s/commit/%s.diff", g.group, project, sha)

	req, err := g.client.NewRequest("GET", u, nil, nil)
	if err != nil {
		return "", err
	}

	// Make sure we do not use the API path here!
	req.URL, err = req.URL.Parse(u)
	if err != nil {
		return "", err
	}

	var diff bytes.Buffer
	resp, err := g.client.Do(req, &diff)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return "", fmt.Errorf(invalidGitLabToken, g.group)
		}
		return "", fmt.Errorf("Error retrieving commit %s: %v", sha, err)
	}

	if diff.Len() == 0 {
		return "", nil
	}

	const layout = "Mon Jan 2 3:04 2006"
	t := time.Now()

	msg := fmt.Sprintf("Commit : %s\nDate   : %s\nUser   : %s\n
%s",
		sha,
		t.Format(layout),
		user,
		diff.String(),
	)

	return msg, nil
}

// GetArchiveLink implements the Git interface
func (g *GitLab) GetArchiveLink(project, tag string) (*url.URL, error) {
	ns := fmt.Sprintf("%s%2F%s", g.group, project)

	_, resp, err := g.client.Projects.GetProject(ns)
	if err != nil {
		if resp != nil {
			switch resp.StatusCode {
			case http.StatusNotFound:
				return nil, nil
			case http.StatusUnauthorized:
				return nil, fmt.Errorf(invalidGitLabToken, g.group)
			}
		}
		return nil, fmt.Errorf("Error retrieving archive link of project %s: %v", project, err)
	}

	u, err := url.Parse(
		fmt.Sprintf("/api/v4/projects/%s/repository/archive.tar.gz?ref=%s&private_token=%s",
			ns,
			tag,
			g.token,
		),
	)
	if err != nil {
		return nil, fmt.Errorf("Failed to parse archive link: %v", err)
	}

	return g.client.BaseURL().ResolveReference(u), nil
}

// TagRepo implements the Git interface
func (g *GitLab) TagRepo(project, tag string, usr *User) error {
	ns := fmt.Sprintf("%s/%s", g.group, project)
	message := fmt.Sprint("Tagged by Chef-Guard\n")

	opts := &gitlab.CreateTagOptions{
		TagName: gitlab.String(tag),
		Ref:     gitlab.String("master"),
		Message: gitlab.String(message),
	}
	_, resp, err := g.client.Tags.CreateTag(ns, opts)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return fmt.Errorf(invalidGitLabToken, g.group)
		}
		return fmt.Errorf("Error creating tag for project %s: %v", project, err)
	}

	return nil
}

// TagExists implements the Git interface
func (g *GitLab) TagExists(project, tag string) (bool, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	tags, resp, err := g.client.Tags.ListTags(ns)
	if err != nil {
		if resp != nil {
			switch resp.StatusCode {
			case http.StatusNotFound:
				return false, nil
			case http.StatusUnauthorized:
				return false, fmt.Errorf(invalidGitLabToken, g.group)
			}
		}
		return false, fmt.Errorf("Error retrieving tags of project %s: %v", project, err)
	}

	for _, t := range tags {
		if t.Name == tag {
			return true, nil
		}
	}

	return false, nil
}

// UntagRepo implements the Git interface
func (g *GitLab) UntagRepo(project, tag string) error {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	resp, err := g.client.Tags.DeleteTag(ns, tag)
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return fmt.Errorf(invalidGitLabToken, g.group)
		}
		return fmt.Errorf("Error deleting tag %s: %v", tag, err)
	}

	return nil
}

func (g *GitLab) shaOfLatestCommit(project string) (string, error) {
	ns := fmt.Sprintf("%s/%s", g.group, project)

	commit, resp, err := g.client.Commits.GetCommit(ns, "master")
	if err != nil {
		if resp != nil && resp.StatusCode == http.StatusUnauthorized {
			return "", fmt.Errorf(invalidGitLabToken, g.group)
		}
		return "", fmt.Errorf("Error retrieving SHA of latest commit: %v", err)
	}

	return commit.ID, nil
}
<|endoftext|>


<commit_before>package session

import (
	"fmt"
	"io"
	"kmud/database"
	"kmud/model"
	"kmud/utils"
	"labix.org/v2/mgo/bson"
	// "log"
	// "os"
	"strings"
	"time"
)

type Session struct {
	conn   io.ReadWriter
	user   *database.User
	player *database.Character
	room   *database.Room
	zone   *database.Zone

	prompt string

	userInputChannel chan string
	promptChannel    chan string
	inputModeChannel chan userInputMode
	panicChannel     chan interface{}
	eventChannel     chan model.Event

	silentMode bool

	// These handlers encapsulate all of the functions that handle user
	// input into a single struct. This makes it so that we can use reflection
	// to lookup the function that should handle the user's input without fear
	// of them calling some function that we didn't intent to expose to them.
	commander commandHandler
	actioner  actionHandler

	replyId bson.ObjectId

	// logger *log.Logger
}

func NewSession(conn io.ReadWriter, user *database.User, player *database.Character) *Session {
	var session Session
	session.conn = conn
	session.user = user
	session.player = player
	session.room = model.M.GetRoom(player.GetRoomId())
	session.zone = model.M.GetZone(session.room.GetZoneId())

	session.prompt = "> "

	session.userInputChannel = make(chan string)
	session.promptChannel = make(chan string)
	session.inputModeChannel = make(chan userInputMode)
	session.panicChannel = make(chan interface{})
	session.eventChannel = model.Register(session.player)

	session.silentMode = false
	session.commander.session = &session
	session.actioner.session = &session

	// file, err := os.OpenFile(player.PrettyName()+".log", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
	// utils.PanicIfError(err)

	// session.logger = log.New(file, player.PrettyName()+" ", log.LstdFlags)

	return &session
}

type userInputMode int

const (
	CleanUserInput userInputMode = iota
	RawUserInput   userInputMode = iota
)

func toggleExitMenu(cm utils.ColorMode, room *database.Room) utils.Menu {
	onOrOff := func(direction database.ExitDirection) string {
		text := "Off"
		if room.HasExit(direction) {
			text = "On"
		}
		return utils.Colorize(cm, utils.ColorBlue, text)
	}

	menu := utils.NewMenu("Edit Exits")

	menu.AddAction("n", "[N]orth: "+onOrOff(database.DirectionNorth))
	menu.AddAction("ne", "[NE]North East: "+onOrOff(database.DirectionNorthEast))
	menu.AddAction("e", "[E]ast: "+onOrOff(database.DirectionEast))
	menu.AddAction("se", "[SE]South East: "+onOrOff(database.DirectionSouthEast))
	menu.AddAction("s", "[S]outh: "+onOrOff(database.DirectionSouth))
	menu.AddAction("sw", "[SW]South West: "+onOrOff(database.DirectionSouthWest))
	menu.AddAction("w", "[W]est: "+onOrOff(database.DirectionWest))
	menu.AddAction("nw", "[NW]North West: "+onOrOff(database.DirectionNorthWest))
	menu.AddAction("u", "[U]p: "+onOrOff(database.DirectionUp))
	menu.AddAction("d", "[D]own: "+onOrOff(database.DirectionDown))

	return menu
}

func npcMenu(room *database.Room) utils.Menu {
	npcs := model.M.NpcsIn(room)

	menu := utils.NewMenu("NPCs")

	menu.AddAction("n", "[N]ew")

	for i, npc := range npcs {
		index := i + 1
		actionText := fmt.Sprintf("[%v]%v", index, npc.PrettyName())
		menu.AddActionData(index, actionText, npc.GetId())
	}

	return menu
}

func specificNpcMenu(npcId bson.ObjectId) utils.Menu {
	npc := model.M.GetCharacter(npcId)
	menu := utils.NewMenu(npc.PrettyName())
	menu.AddAction("r", "[R]ename")
	menu.AddAction("d", "[D]elete")
	menu.AddAction("c", "[C]onversation")
	return menu
}

func (session *Session) Exec() {
	defer model.Unregister(session.eventChannel)

	session.printLineColor(utils.ColorWhite, "Welcome, "+session.player.PrettyName())
	session.printRoom()

	// Main routine in charge of actually reading input from the connection object,
	// also has built in throttling to limit how fast we are allowed to process
	// commands from the user.
	go func() {
		defer func() {
			if r := recover(); r != nil {
				session.panicChannel <- r
			}
		}()

		lastTime := time.Now()

		delay := time.Duration(200) * time.Millisecond

		for {
			mode := <-session.inputModeChannel
			prompt := utils.Colorize(session.user.GetColorMode(), utils.ColorWhite, <-session.promptChannel)
			input := ""

			switch mode {
			case CleanUserInput:
				input = utils.GetUserInput(session.conn, prompt)
			case RawUserInput:
				input = utils.GetRawUserInput(session.conn, prompt)
			default:
				panic("Unhandled case in switch statement (userInputMode)")
			}

			diff := time.Since(lastTime)

			if diff < delay {
				time.Sleep(delay - diff)
			}

			session.userInputChannel <- input
			lastTime = time.Now()
		}
	}()

	// Main loop
	for {
		input := session.getUserInput(RawUserInput, session.prompt)
		if input == "" || input == "logout" {
			return
		}
		if strings.HasPrefix(input, "/") {
			session.commander.handleCommand(utils.Argify(input[1:]))
		} else {
			session.actioner.handleAction(utils.Argify(input))
		}
	}
}

func (session *Session) printString(data string) {
	io.WriteString(session.conn, data)
}

func (session *Session) printLineColor(color utils.Color, line string, a ...interface{}) {
	utils.WriteLine(session.conn, utils.Colorize(session.user.GetColorMode(), color, fmt.Sprintf(line, a...)))
}

func (session *Session) printLine(line string, a ...interface{}) {
	session.printLineColor(utils.ColorWhite, line, a...)
}

func (session *Session) printError(err string, a ...interface{}) {
	session.printLineColor(utils.ColorRed, err, a...)
}

func (session *Session) printRoom() {
	playerList := model.M.PlayersIn(session.room, session.player)
	npcList := model.M.NpcsIn(session.room)
	session.printLine(session.room.ToString(database.ReadMode, session.user.GetColorMode(),
		playerList, npcList, model.M.GetItems(session.room.GetItemIds())))
}

func (session *Session) printRoomEditor() {
	session.printLine(session.room.ToString(database.EditMode, session.user.GetColorMode(), nil, nil, nil))
}

func (session *Session) clearLine() {
	utils.ClearLine(session.conn)
}

func (session *Session) asyncMessage(message string) {
	session.clearLine()
	session.printLine(message)
}

// Same behavior as menu.Exec(), except that it uses getUserInput
// which doesn't block the event loop while waiting for input
func (session *Session) execMenu(menu utils.Menu) (string, bson.ObjectId) {
	choice := ""
	var data bson.ObjectId

	for {
		menu.Print(session.conn, session.user.GetColorMode())
		choice = session.getUserInput(CleanUserInput, menu.GetPrompt())
		if menu.HasAction(choice) || choice == "" {
			data = menu.GetData(choice)
			break
		}
	}
	return choice, data
}

// getUserInput allows us to retrieve user input in a way that doesn't block the
// event loop by using channels and a separate Go routine to grab
// either the next user input or the next event.
func (session *Session) getUserInput(inputMode userInputMode, prompt string) string {
	session.inputModeChannel <- inputMode
	session.promptChannel <- prompt

	for {
		select {
		case input := <-session.userInputChannel:
			return input
		case event := <-session.eventChannel:
			if session.silentMode {
				continue
			}

			message := event.ToString(session.player)
			if message != "" {
				session.asyncMessage(message)
				session.printString(session.prompt)
			}

			if event.Type() == model.TellEventType {
				tellEvent := event.(model.TellEvent)
				session.replyId = tellEvent.From.GetId()
			}

		case quitMessage := <-session.panicChannel:
			panic(quitMessage)
		}
	}
	panic("Unexpected code path")
}

// vim: nocindent
<commit_msg>Added "quit" command<commit_after>package session

import (
	"fmt"
	"io"
	"kmud/database"
	"kmud/model"
	"kmud/utils"
	"labix.org/v2/mgo/bson"
	// "log"
	// "os"
	"strings"
	"time"
)

type Session struct {
	conn   io.ReadWriter
	user   *database.User
	player *database.Character
	room   *database.Room
	zone   *database.Zone

	prompt string

	userInputChannel chan string
	promptChannel    chan string
	inputModeChannel chan userInputMode
	panicChannel     chan interface{}
	eventChannel     chan model.Event

	silentMode bool

	// These handlers encapsulate all of the functions that handle user
	// input into a single struct. This makes it so that we can use reflection
	// to lookup the function that should handle the user's input without fear
	// of them calling some function that we didn't intent to expose to them.
	commander commandHandler
	actioner  actionHandler

	replyId bson.ObjectId

	// logger *log.Logger
}

func NewSession(conn io.ReadWriter, user *database.User, player *database.Character) *Session {
	var session Session
	session.conn = conn
	session.user = user
	session.player = player
	session.room = model.M.GetRoom(player.GetRoomId())
	session.zone = model.M.GetZone(session.room.GetZoneId())

	session.prompt = "> "

	session.userInputChannel = make(chan string)
	session.promptChannel = make(chan string)
	session.inputModeChannel = make(chan userInputMode)
	session.panicChannel = make(chan interface{})
	session.eventChannel = model.Register(session.player)

	session.silentMode = false
	session.commander.session = &session
	session.actioner.session = &session

	// file, err := os.OpenFile(player.PrettyName()+".log", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
	// utils.PanicIfError(err)

	// session.logger = log.New(file, player.PrettyName()+" ", log.LstdFlags)

	return &session
}

type userInputMode int

const (
	CleanUserInput userInputMode = iota
	RawUserInput   userInputMode = iota
)

func toggleExitMenu(cm utils.ColorMode, room *database.Room) utils.Menu {
	onOrOff := func(direction database.ExitDirection) string {
		text := "Off"
		if room.HasExit(direction) {
			text = "On"
		}
		return utils.Colorize(cm, utils.ColorBlue, text)
	}

	menu := utils.NewMenu("Edit Exits")

	menu.AddAction("n", "[N]orth: "+onOrOff(database.DirectionNorth))
	menu.AddAction("ne", "[NE]North East: "+onOrOff(database.DirectionNorthEast))
	menu.AddAction("e", "[E]ast: "+onOrOff(database.DirectionEast))
	menu.AddAction("se", "[SE]South East: "+onOrOff(database.DirectionSouthEast))
	menu.AddAction("s", "[S]outh: "+onOrOff(database.DirectionSouth))
	menu.AddAction("sw", "[SW]South West: "+onOrOff(database.DirectionSouthWest))
	menu.AddAction("w", "[W]est: "+onOrOff(database.DirectionWest))
	menu.AddAction("nw", "[NW]North West: "+onOrOff(database.DirectionNorthWest))
	menu.AddAction("u", "[U]p: "+onOrOff(database.DirectionUp))
	menu.AddAction("d", "[D]own: "+onOrOff(database.DirectionDown))

	return menu
}

func npcMenu(room *database.Room) utils.Menu {
	npcs := model.M.NpcsIn(room)

	menu := utils.NewMenu("NPCs")

	menu.AddAction("n", "[N]ew")

	for i, npc := range npcs {
		index := i + 1
		actionText := fmt.Sprintf("[%v]%v", index, npc.PrettyName())
		menu.AddActionData(index, actionText, npc.GetId())
	}

	return menu
}

func specificNpcMenu(npcId bson.ObjectId) utils.Menu {
	npc := model.M.GetCharacter(npcId)
	menu := utils.NewMenu(npc.PrettyName())
	menu.AddAction("r", "[R]ename")
	menu.AddAction("d", "[D]elete")
	menu.AddAction("c", "[C]onversation")
	return menu
}

func (session *Session) Exec() {
	defer model.Unregister(session.eventChannel)

	session.printLineColor(utils.ColorWhite, "Welcome, "+session.player.PrettyName())
	session.printRoom()

	// Main routine in charge of actually reading input from the connection object,
	// also has built in throttling to limit how fast we are allowed to process
	// commands from the user.
	go func() {
		defer func() {
			if r := recover(); r != nil {
				session.panicChannel <- r
			}
		}()

		lastTime := time.Now()

		delay := time.Duration(200) * time.Millisecond

		for {
			mode := <-session.inputModeChannel
			prompt := utils.Colorize(session.user.GetColorMode(), utils.ColorWhite, <-session.promptChannel)
			input := ""

			switch mode {
			case CleanUserInput:
				input = utils.GetUserInput(session.conn, prompt)
			case RawUserInput:
				input = utils.GetRawUserInput(session.conn, prompt)
			default:
				panic("Unhandled case in switch statement (userInputMode)")
			}

			diff := time.Since(lastTime)

			if diff < delay {
				time.Sleep(delay - diff)
			}

			session.userInputChannel <- input
			lastTime = time.Now()
		}
	}()

	// Main loop
	for {
		input := session.getUserInput(RawUserInput, session.prompt)
		if input == "" || input == "logout" || input == "quit" {
			return
		}

		if strings.HasPrefix(input, "/") {
			session.commander.handleCommand(utils.Argify(input[1:]))
		} else {
			session.actioner.handleAction(utils.Argify(input))
		}
	}
}

func (session *Session) printString(data string) {
	io.WriteString(session.conn, data)
}

func (session *Session) printLineColor(color utils.Color, line string, a ...interface{}) {
	utils.WriteLine(session.conn, utils.Colorize(session.user.GetColorMode(), color, fmt.Sprintf(line, a...)))
}

func (session *Session) printLine(line string, a ...interface{}) {
	session.printLineColor(utils.ColorWhite, line, a...)
}

func (session *Session) printError(err string, a ...interface{}) {
	session.printLineColor(utils.ColorRed, err, a...)
}

func (session *Session) printRoom() {
	playerList := model.M.PlayersIn(session.room, session.player)
	npcList := model.M.NpcsIn(session.room)
	session.printLine(session.room.ToString(database.ReadMode, session.user.GetColorMode(),
		playerList, npcList, model.M.GetItems(session.room.GetItemIds())))
}

func (session *Session) printRoomEditor() {
	session.printLine(session.room.ToString(database.EditMode, session.user.GetColorMode(), nil, nil, nil))
}

func (session *Session) clearLine() {
	utils.ClearLine(session.conn)
}

func (session *Session) asyncMessage(message string) {
	session.clearLine()
	session.printLine(message)
}

// Same behavior as menu.Exec(), except that it uses getUserInput
// which doesn't block the event loop while waiting for input
func (session *Session) execMenu(menu utils.Menu) (string, bson.ObjectId) {
	choice := ""
	var data bson.ObjectId

	for {
		menu.Print(session.conn, session.user.GetColorMode())
		choice = session.getUserInput(CleanUserInput, menu.GetPrompt())
		if menu.HasAction(choice) || choice == "" {
			data = menu.GetData(choice)
			break
		}
	}
	return choice, data
}

// getUserInput allows us to retrieve user input in a way that doesn't block the
// event loop by using channels and a separate Go routine to grab
// either the next user input or the next event.
func (session *Session) getUserInput(inputMode userInputMode, prompt string) string {
	session.inputModeChannel <- inputMode
	session.promptChannel <- prompt

	for {
		select {
		case input := <-session.userInputChannel:
			return input
		case event := <-session.eventChannel:
			if session.silentMode {
				continue
			}

			message := event.ToString(session.player)
			if message != "" {
				session.asyncMessage(message)
				session.printString(session.prompt)
			}

			if event.Type() == model.TellEventType {
				tellEvent := event.(model.TellEvent)
				session.replyId = tellEvent.From.GetId()
			}

		case quitMessage := <-session.panicChannel:
			panic(quitMessage)
		}
	}
	panic("Unexpected code path")
}

// vim: nocindent
<|endoftext|>