<commit_before>// Copyright 2013 Beego Samples authors
//
// 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 controllers

import (
	"container/list"
	"encoding/hex"
	"strconv"
	"time"

	"github.com/astaxie/beego"
	"github.com/gorilla/websocket"

	"chatroom/models"
)

func newEvent(ep models.EventType, user, msg string, room int) models.Event {
	return models.Event{ep, user, int(time.Now().Unix()), msg, room}
}

func Join(clientId string, ws *websocket.Conn, room int) {
	subscribe <- Subscriber{ClientId: clientId, Conn: ws, Room: room}
}

func Leave(clientId string, room int) {
	unsubscribe <- UnSubscriber{ClientId: clientId, Room: room}
}

func NewClientId(room int, RemoteAddr string) string {
	return "h_l_" + strconv.Itoa(room) + "_" + hex.EncodeToString([]byte(RemoteAddr))
}

type Subscriber struct {
	ClientId string
	Conn     *websocket.Conn // Only for WebSocket users; otherwise nil.
	Room     int
}

type UnSubscriber struct {
	ClientId string
	Room     int
}

var (
	// Channel for new join users.
	subscribe = make(chan Subscriber, 10)
	// Channel for exit users.
	unsubscribe = make(chan UnSubscriber, 10)
	// Send events here to publish them.
	publish = make(chan models.Event, 10)

	subscribers = make(map[int]*list.List)
)

// This function handles all incoming chan messages.
func chatroom() {
	for {
		select {
		case sub := <-subscribe:
			if isRoomExist(subscribers, sub.Room) {
				subscribers[sub.Room].PushBack(sub) // Add user to the end of list.
				// Publish a JOIN event.
				publish <- newEvent(models.EVENT_JOIN, sub.ClientId, "", sub.Room)
				beego.Info("New user:", sub.ClientId, ";WebSocket:", sub.Conn != nil)
			}
		case event := <-publish:
			broadcastWebSocket(event)

			if event.Type == models.EVENT_MESSAGE {
				beego.Info("Message from", event.ClientId, ";Content:", event.Content)
			}
		case unsub := <-unsubscribe:
			for sub := subscribers[unsub.Room].Front(); sub != nil; sub = sub.Next() {
				if sub.Value.(Subscriber).ClientId == unsub.ClientId {
					subscribers[unsub.Room].Remove(sub)
					// Clone connection.
					ws := sub.Value.(Subscriber).Conn
					if ws != nil {
						ws.Close()
						beego.Error("WebSocket closed:", unsub.ClientId)
					}
					break
				}
			}
		}
	}
}

func init() {
	go chatroom()
}

//检查房间是否存在
func isRoomExist(subscribers map[int]*list.List, room int) bool {
	if subscribers[room] == nil {
		subscribers[room] = list.New()
	}
	return true
}
<commit_msg>相关channel缓冲长度调整<commit_after>// Copyright 2013 Beego Samples authors
//
// 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 controllers

import (
	"container/list"
	"encoding/hex"
	"strconv"
	"time"

	"github.com/astaxie/beego"
	"github.com/gorilla/websocket"

	"chatroom/models"
)

func newEvent(ep models.EventType, user, msg string, room int) models.Event {
	return models.Event{ep, user, int(time.Now().Unix()), msg, room}
}

func Join(clientId string, ws *websocket.Conn, room int) {
	subscribe <- Subscriber{ClientId: clientId, Conn: ws, Room: room}
}

func Leave(clientId string, room int) {
	unsubscribe <- UnSubscriber{ClientId: clientId, Room: room}
}

func NewClientId(room int, RemoteAddr string) string {
	return "h_l_" + strconv.Itoa(room) + "_" + hex.EncodeToString([]byte(RemoteAddr))
}

type Subscriber struct {
	ClientId string
	Conn     *websocket.Conn // Only for WebSocket users; otherwise nil.
	Room     int
}

type UnSubscriber struct {
	ClientId string
	Room     int
}

var (
	// Channel for new join users.
	subscribe = make(chan Subscriber, 50)
	// Channel for exit users.
	unsubscribe = make(chan UnSubscriber, 50)
	// Send events here to publish them.
	publish = make(chan models.Event, 100)

	subscribers = make(map[int]*list.List)
)

// This function handles all incoming chan messages.
func chatroom() {
	for {
		select {
		case sub := <-subscribe:
			if isRoomExist(subscribers, sub.Room) {
				subscribers[sub.Room].PushBack(sub) // Add user to the end of list.
				// Publish a JOIN event.
				publish <- newEvent(models.EVENT_JOIN, sub.ClientId, "", sub.Room)
				beego.Info("New user:", sub.ClientId, ";WebSocket:", sub.Conn != nil)
			}
		case event := <-publish:
			broadcastWebSocket(event)

			if event.Type == models.EVENT_MESSAGE {
				beego.Info("Message from", event.ClientId, ";Content:", event.Content)
			}
		case unsub := <-unsubscribe:
			for sub := subscribers[unsub.Room].Front(); sub != nil; sub = sub.Next() {
				if sub.Value.(Subscriber).ClientId == unsub.ClientId {
					subscribers[unsub.Room].Remove(sub)
					// Clone connection.
					ws := sub.Value.(Subscriber).Conn
					if ws != nil {
						ws.Close()
						beego.Error("WebSocket closed:", unsub.ClientId)
					}
					break
				}
			}
		}
	}
}

func init() {
	go chatroom()
}

//检查房间是否存在
func isRoomExist(subscribers map[int]*list.List, room int) bool {
	if subscribers[room] == nil {
		subscribers[room] = list.New()
	}
	return true
}
<|endoftext|>


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

package toggl

import (
	"errors"
	"fmt"
	"time"
)

// ClientsService handles communication with the client related
// methods of the Toggl API.
//
// Toggl API docs: https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md
type ClientsService struct {
	client *Client
}

// Client represents client of user's workspace.
type WorkspaceClient struct {
	ID          int        `json:"id,omitempty"`
	WorkspaceID int        `json:"wid,omitempty"`
	Name        string     `json:"name,omitempty"`
	Notes       string     `json:"notes,omitempty"`
	HourlyRate  float64    `json:"hrate,omitempty"`
	Currency    string     `json:"cur,omitempty"`
	At          *time.Time `json:"at,omitempty"` // indicates the time client was last updated
}

// WorkspaceClientResponse acts as a response wrapper where response returns
// in format of "data": Client's object.
type WorkspaceClientResponse struct {
	Data *WorkspaceClient `json:"data,omitempty"`
}

// WorkspaceClientCreate represents posted data to be sent to clients endpoint.
type WorkspaceClientCreate struct {
	User *WorkspaceClient `json:"client,omitempty"`
}

// List visible clients to the user.
//
// Toggl API docs: https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#get-clients-visible-to-user
func (s *ClientsService) List() ([]WorkspaceClient, error) {
	u := "clients"

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

	data := new([]WorkspaceClient)
	_, err = s.client.Do(req, data)

	return *data, err
}

// Get client details by client_id.
//
// Toggl API docs: https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#get-client-details
func (s *ClientsService) Get(id int) (*WorkspaceClient, error) {
	u := fmt.Sprintf("clients/%v", id)
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, err
	}

	data := new(WorkspaceClientResponse)
	_, err = s.client.Do(req, data)

	return data.Data, err
}

// Create a new client in specified workspace.
//
// https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#create-a-client
func (s *ClientsService) Create(c *WorkspaceClient) (*WorkspaceClient, error) {
	u := "clients"
	us := &WorkspaceClientCreate{c}
	req, err := s.client.NewRequest("POST", u, us)
	if err != nil {
		return nil, err
	}

	data := new(WorkspaceClientResponse)
	_, err = s.client.Do(req, data)

	return data.Data, err
}

// Update a client.
//
// Toggl API docs: https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#update-a-client
func (s *ClientsService) Update(c *WorkspaceClient) (*WorkspaceClient, error) {
	if c == nil {
		return nil, errors.New("WorkspaceClient cannot be nil")
	}
	if c.ID <= 0 {
		return nil, errors.New("Invalid WorkspaceClient.ID")
	}

	u := fmt.Sprintf("clients/%v", c.ID)

	us := &WorkspaceClientCreate{c}
	req, err := s.client.NewRequest("PUT", u, us)
	if err != nil {
		return nil, err
	}

	data := new(WorkspaceClientResponse)
	_, err = s.client.Do(req, data)

	return data.Data, err
}

// Delete a client.
//
// Toggl API docs: https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#delete-a-client
func (s *ClientsService) Delete(id int) error {
	u := fmt.Sprintf("clients/%v", id)
	req, err := s.client.NewRequest("DELETE", u, nil)
	if err != nil {
		return err
	}

	_, err = s.client.Do(req, nil)
	return err
}
<commit_msg>Added ListClientProjects method.<commit_after>// Copyright 2013 The go-toggl AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package toggl

import (
	"errors"
	"fmt"
	"time"
)

// ClientsService handles communication with the client related
// methods of the Toggl API.
//
// Toggl API docs: https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md
type ClientsService struct {
	client *Client
}

// Client represents client of user's workspace.
type WorkspaceClient struct {
	ID          int        `json:"id,omitempty"`
	WorkspaceID int        `json:"wid,omitempty"`
	Name        string     `json:"name,omitempty"`
	Notes       string     `json:"notes,omitempty"`
	HourlyRate  float64    `json:"hrate,omitempty"`
	Currency    string     `json:"cur,omitempty"`
	At          *time.Time `json:"at,omitempty"` // indicates the time client was last updated
}

// WorkspaceClientResponse acts as a response wrapper where response returns
// in format of "data": Client's object.
type WorkspaceClientResponse struct {
	Data *WorkspaceClient `json:"data,omitempty"`
}

// WorkspaceClientCreate represents posted data to be sent to clients endpoint.
type WorkspaceClientCreate struct {
	User *WorkspaceClient `json:"client,omitempty"`
}

// List visible clients to the user.
//
// Toggl API docs: https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#get-clients-visible-to-user
func (s *ClientsService) List() ([]WorkspaceClient, error) {
	u := "clients"

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

	data := new([]WorkspaceClient)
	_, err = s.client.Do(req, data)

	return *data, err
}

func (s *ClientsService) ListClientProjects(id int) ([]Project, error) {
	u := fmt.Sprintf("clients/%v/projects", id)

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

	data := new([]Project)
	_, err = s.client.Do(req, data)

	return *data, err
}

// Get client details by client_id.
//
// Toggl API docs: https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#get-client-details
func (s *ClientsService) Get(id int) (*WorkspaceClient, error) {
	u := fmt.Sprintf("clients/%v", id)
	req, err := s.client.NewRequest("GET", u, nil)
	if err != nil {
		return nil, err
	}

	data := new(WorkspaceClientResponse)
	_, err = s.client.Do(req, data)

	return data.Data, err
}

// Create a new client in specified workspace.
//
// https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#create-a-client
func (s *ClientsService) Create(c *WorkspaceClient) (*WorkspaceClient, error) {
	u := "clients"
	us := &WorkspaceClientCreate{c}
	req, err := s.client.NewRequest("POST", u, us)
	if err != nil {
		return nil, err
	}

	data := new(WorkspaceClientResponse)
	_, err = s.client.Do(req, data)

	return data.Data, err
}

// Update a client.
//
// Toggl API docs: https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#update-a-client
func (s *ClientsService) Update(c *WorkspaceClient) (*WorkspaceClient, error) {
	if c == nil {
		return nil, errors.New("WorkspaceClient cannot be nil")
	}
	if c.ID <= 0 {
		return nil, errors.New("Invalid WorkspaceClient.ID")
	}

	u := fmt.Sprintf("clients/%v", c.ID)

	us := &WorkspaceClientCreate{c}
	req, err := s.client.NewRequest("PUT", u, us)
	if err != nil {
		return nil, err
	}

	data := new(WorkspaceClientResponse)
	_, err = s.client.Do(req, data)

	return data.Data, err
}

// Delete a client.
//
// Toggl API docs: https://github.com/toggl/toggl_api_docs/blob/master/chapters/clients.md#delete-a-client
func (s *ClientsService) Delete(id int) error {
	u := fmt.Sprintf("clients/%v", id)
	req, err := s.client.NewRequest("DELETE", u, nil)
	if err != nil {
		return err
	}

	_, err = s.client.Do(req, nil)
	return err
}
<|endoftext|>


<commit_before>// errchk $G $D/$F.go

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

package main
import os "os"
func f() (os int) {
     // In the next line "os" should refer to the result variable, not
     // to the package.
     v := os.Open("", 0, 0)	// ERROR "undefined"
     return 0
}
<commit_msg>- added missing ';' according to new rules (no effect on the test, but now parseable)<commit_after>// errchk $G $D/$F.go

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

package main
import os "os"
func f() (os int) {
     // In the next line "os" should refer to the result variable, not
     // to the package.
     v := os.Open("", 0, 0);	// ERROR "undefined"
     return 0
}
<|endoftext|>


<commit_before>package subdomains

import (
	"strings"

	"github.com/admpub/log"

	"github.com/webx-top/echo"
	"github.com/webx-top/echo/engine"
	"github.com/webx-top/echo/engine/fasthttp"
	"github.com/webx-top/echo/engine/standard"
)

var Default = New()

func New() *Subdomains {
	s := &Subdomains{
		Hosts:    map[string]string{},
		Alias:    map[string]*Info{},
		Default:  ``,
		Protocol: `http`,
	}
	s.dispatcher = s.DefaultDispatcher
	return s
}

func SetBaseURL(url string) {
	echo.Set(`subdomains.url`, url)
}

type Info struct {
	Protocol string
	Name     string
	Host     string
	*echo.Echo
}

func (info *Info) URL(s *Subdomains, uri string) string {
	var generatedURL string
	if s.Default == info.Name {
		generatedURL = info.Prefix() + uri
	} else {
		generatedURL = info.Prefix() + `/` + info.Name + uri
	}
	if domain := echo.String(`subdomains.url`); len(domain) > 0 {
		return domain + generatedURL
	}
	if len(info.Host) == 0 {
		return generatedURL
	}
	protocol := info.Protocol
	if len(protocol) == 0 {
		protocol = s.Protocol
		if len(protocol) == 0 {
			protocol = `http`
		}
	}
	return protocol + `://` + info.Host + generatedURL
}

func (info *Info) URLByName(s *Subdomains, name string, args ...interface{}) string {
	return info.URL(s, info.Echo.URI(name, args...))
}

type Dispatcher func(r engine.Request, w engine.Response) (*echo.Echo, bool)

type Subdomains struct {
	Hosts      map[string]string //{host:name}
	Alias      map[string]*Info
	Default    string //default name
	Protocol   string //http/https
	Boot       string
	dispatcher Dispatcher
}

func (s *Subdomains) SetDispatcher(dispatcher Dispatcher) *Subdomains {
	s.dispatcher = dispatcher
	return s
}

// Add 添加子域名，name的值支持以下三种格式：
// 1. 别名@域名 ———— 一个别名可以对应多个域名，每个域名之间用半角逗号“,”分隔
// 2. 域名 ———— 可以添加多个域名，每个域名之间用半角逗号“,”分隔。这里会自动将第一个域名中的首个点号“.”前面的部分作为别名，例如“blog.webx.top,news.webx.top”会自动将“blog”作为别名
// 3. 别名 ———— 在不指定域名的情况下将无法访问，除非“默认访问别名”（Subdomains.Default的值）与此相同
func (s *Subdomains) Add(name string, e *echo.Echo) *Subdomains {
	log.Debug(`[Subdomains] `, `Register subdomain: `, name)
	r := strings.SplitN(name, `@`, 2)
	var hosts []string
	if len(r) > 1 { //blog@1.webx.top,2.webx.top
		name = r[0]
		hosts = strings.Split(r[1], `,`)
	} else {
		p := strings.Index(name, `.`)
		if p > 0 { //blog.webx.top
			hosts = strings.Split(name, `,`)
			name = name[0:p]
		} else { //blog
			hosts = append(hosts, ``)
		}
	}
	for _, host := range hosts {
		s.Hosts[host] = name
	}
	info := &Info{
		Protocol: `http`,
		Name:     name,
		Host:     hosts[0],
		Echo:     e,
	}
	info2 := strings.SplitN(info.Host, `://`, 2)
	if len(info2) == 2 {
		info.Protocol = info2[0]
		info.Host = info2[1]
		if len(info.Protocol) == 0 {
			info.Protocol = "http"
		}
	}
	s.Alias[name] = info
	return s
}

func (s *Subdomains) Get(args ...string) *Info {
	name := s.Default
	if len(args) > 0 {
		name = args[0]
	}
	if e, ok := s.Alias[name]; ok {
		return e
	}
	return nil
}

func (s *Subdomains) SetDebug(on bool) *Subdomains {
	for _, info := range s.Alias {
		info.SetDebug(on)
	}
	return s
}

// URL 多域名场景下的网址生成功能
// URL(网址路径,域名别名)，如果这里不传递域名别名，将使用默认别名的域名
// 例如：URL("/list?cid=1","blog")
// 对于一个别名对应有多个域名的情况，将总是使用第一个域名
func (s *Subdomains) URL(uri string, args ...string) string {
	info := s.Get(args...)
	if info == nil {
		return uri
	}
	return info.URL(s, uri)
}

// URLByName 根据路由别名生成网址
// 可以在名称中采用 #backend#name 的方式来获取子域名别名为bakcend的网址
func (s *Subdomains) URLByName(name string, params ...interface{}) string {
	var args []string
	if strings.HasPrefix(name, `#`) {
		name = strings.TrimPrefix(name, `#`)
		arr := strings.SplitN(name, `#`, 2)
		if len(arr) == 2 {
			args = append(args, arr[0])
			name = arr[1]
		}
	}
	info := s.Get(args...)
	if info == nil {
		return `/not-found:` + name
	}
	return info.URLByName(s, name, params...)
}

func (s *Subdomains) FindByDomain(host string) (*echo.Echo, bool) {
	name, exists := s.Hosts[host]
	if !exists {
		if p := strings.LastIndexByte(host, ':'); p > -1 {
			name, exists = s.Hosts[host[0:p]]
		}
		if !exists {
			name = s.Default
		}
	}
	var info *Info
	info, exists = s.Alias[name]
	if exists {
		return info.Echo, true
	}
	return nil, false
}

func (s *Subdomains) DefaultDispatcher(r engine.Request, w engine.Response) (*echo.Echo, bool) {
	return s.FindByDomain(r.Host())
}

func (s *Subdomains) ServeHTTP(r engine.Request, w engine.Response) {
	handler, exists := s.dispatcher(r, w)
	if exists && handler != nil {
		handler.ServeHTTP(r, w)
	} else {
		w.NotFound()
	}
}

func (s *Subdomains) Run(args ...interface{}) {
	if s.dispatcher == nil {
		s.dispatcher = s.DefaultDispatcher
	}
	var eng engine.Engine
	var arg interface{}
	size := len(args)
	if size > 0 {
		arg = args[0]
	}
	if size > 1 {
		if conf, ok := arg.(*engine.Config); ok {
			if v, ok := args[1].(string); ok {
				if v == `fast` {
					eng = fasthttp.NewWithConfig(conf)
				} else {
					eng = standard.NewWithConfig(conf)
				}
			} else {
				eng = fasthttp.NewWithConfig(conf)
			}
		} else {
			addr := `:80`
			if v, ok := arg.(string); ok && len(v) > 0 {
				addr = v
			}
			if v, ok := args[1].(string); ok {
				if v == `fast` {
					eng = fasthttp.New(addr)
				} else {
					eng = standard.New(addr)
				}
			} else {
				eng = fasthttp.New(addr)
			}
		}
	} else {
		switch v := arg.(type) {
		case string:
			eng = fasthttp.New(v)
		case engine.Engine:
			eng = v
		default:
			eng = fasthttp.New(`:80`)
		}
	}
	e := s.Get(s.Boot)
	if e == nil {
		for _, info := range s.Alias {
			e = info
			break
		}
	}
	for _, info := range s.Alias {
		if e == info {
			continue
		}
		info.Commit()
	}
	e.Logger().Info(`Server has been launched.`)
	e.Run(eng, s)
	e.Logger().Info(`Server has been closed.`)
}
<commit_msg>update<commit_after>package subdomains

import (
	"strings"

	"github.com/admpub/log"

	"github.com/webx-top/echo"
	"github.com/webx-top/echo/engine"
	"github.com/webx-top/echo/engine/fasthttp"
	"github.com/webx-top/echo/engine/standard"
)

var Default = New()

func New() *Subdomains {
	s := &Subdomains{
		Hosts:    map[string]string{},
		Alias:    map[string]*Info{},
		Default:  ``,
		Protocol: `http`,
	}
	s.dispatcher = s.DefaultDispatcher
	return s
}

func SetBaseURL(name string, url string) {
	echo.Set(`subdomains.`+name+`.url`, url)
}

type Info struct {
	Protocol string
	Name     string
	Host     string
	*echo.Echo
}

func (info *Info) URL(s *Subdomains, uri string) string {
	var generatedURL string
	if s.Default == info.Name {
		generatedURL = info.Prefix() + uri
	} else {
		generatedURL = info.Prefix() + `/` + info.Name + uri
	}
	if domain := echo.String(`subdomains.` + info.Name + `.url`); len(domain) > 0 {
		return domain + generatedURL
	}
	if len(info.Host) == 0 {
		return generatedURL
	}
	protocol := info.Protocol
	if len(protocol) == 0 {
		protocol = s.Protocol
		if len(protocol) == 0 {
			protocol = `http`
		}
	}
	return protocol + `://` + info.Host + generatedURL
}

func (info *Info) URLByName(s *Subdomains, name string, args ...interface{}) string {
	return info.URL(s, info.Echo.URI(name, args...))
}

type Dispatcher func(r engine.Request, w engine.Response) (*echo.Echo, bool)

type Subdomains struct {
	Hosts      map[string]string //{host:name}
	Alias      map[string]*Info
	Default    string //default name
	Protocol   string //http/https
	Boot       string
	dispatcher Dispatcher
}

func (s *Subdomains) SetDispatcher(dispatcher Dispatcher) *Subdomains {
	s.dispatcher = dispatcher
	return s
}

// Add 添加子域名，name的值支持以下三种格式：
// 1. 别名@域名 ———— 一个别名可以对应多个域名，每个域名之间用半角逗号“,”分隔
// 2. 域名 ———— 可以添加多个域名，每个域名之间用半角逗号“,”分隔。这里会自动将第一个域名中的首个点号“.”前面的部分作为别名，例如“blog.webx.top,news.webx.top”会自动将“blog”作为别名
// 3. 别名 ———— 在不指定域名的情况下将无法访问，除非“默认访问别名”（Subdomains.Default的值）与此相同
func (s *Subdomains) Add(name string, e *echo.Echo) *Subdomains {
	log.Debug(`[Subdomains] `, `Register subdomain: `, name)
	r := strings.SplitN(name, `@`, 2)
	var hosts []string
	if len(r) > 1 { //blog@1.webx.top,2.webx.top
		name = r[0]
		hosts = strings.Split(r[1], `,`)
	} else {
		p := strings.Index(name, `.`)
		if p > 0 { //blog.webx.top
			hosts = strings.Split(name, `,`)
			name = name[0:p]
		} else { //blog
			hosts = append(hosts, ``)
		}
	}
	for _, host := range hosts {
		s.Hosts[host] = name
	}
	info := &Info{
		Protocol: `http`,
		Name:     name,
		Host:     hosts[0],
		Echo:     e,
	}
	info2 := strings.SplitN(info.Host, `://`, 2)
	if len(info2) == 2 {
		info.Protocol = info2[0]
		info.Host = info2[1]
		if len(info.Protocol) == 0 {
			info.Protocol = "http"
		}
	}
	s.Alias[name] = info
	return s
}

func (s *Subdomains) Get(args ...string) *Info {
	name := s.Default
	if len(args) > 0 {
		name = args[0]
	}
	if e, ok := s.Alias[name]; ok {
		return e
	}
	return nil
}

func (s *Subdomains) SetDebug(on bool) *Subdomains {
	for _, info := range s.Alias {
		info.SetDebug(on)
	}
	return s
}

// URL 多域名场景下的网址生成功能
// URL(网址路径,域名别名)，如果这里不传递域名别名，将使用默认别名的域名
// 例如：URL("/list?cid=1","blog")
// 对于一个别名对应有多个域名的情况，将总是使用第一个域名
func (s *Subdomains) URL(uri string, args ...string) string {
	info := s.Get(args...)
	if info == nil {
		return uri
	}
	return info.URL(s, uri)
}

// URLByName 根据路由别名生成网址
// 可以在名称中采用 #backend#name 的方式来获取子域名别名为bakcend的网址
func (s *Subdomains) URLByName(name string, params ...interface{}) string {
	var args []string
	if strings.HasPrefix(name, `#`) {
		name = strings.TrimPrefix(name, `#`)
		arr := strings.SplitN(name, `#`, 2)
		if len(arr) == 2 {
			args = append(args, arr[0])
			name = arr[1]
		}
	}
	info := s.Get(args...)
	if info == nil {
		return `/not-found:` + name
	}
	return info.URLByName(s, name, params...)
}

func (s *Subdomains) FindByDomain(host string) (*echo.Echo, bool) {
	name, exists := s.Hosts[host]
	if !exists {
		if p := strings.LastIndexByte(host, ':'); p > -1 {
			name, exists = s.Hosts[host[0:p]]
		}
		if !exists {
			name = s.Default
		}
	}
	var info *Info
	info, exists = s.Alias[name]
	if exists {
		return info.Echo, true
	}
	return nil, false
}

func (s *Subdomains) DefaultDispatcher(r engine.Request, w engine.Response) (*echo.Echo, bool) {
	return s.FindByDomain(r.Host())
}

func (s *Subdomains) ServeHTTP(r engine.Request, w engine.Response) {
	handler, exists := s.dispatcher(r, w)
	if exists && handler != nil {
		handler.ServeHTTP(r, w)
	} else {
		w.NotFound()
	}
}

func (s *Subdomains) Run(args ...interface{}) {
	if s.dispatcher == nil {
		s.dispatcher = s.DefaultDispatcher
	}
	var eng engine.Engine
	var arg interface{}
	size := len(args)
	if size > 0 {
		arg = args[0]
	}
	if size > 1 {
		if conf, ok := arg.(*engine.Config); ok {
			if v, ok := args[1].(string); ok {
				if v == `fast` {
					eng = fasthttp.NewWithConfig(conf)
				} else {
					eng = standard.NewWithConfig(conf)
				}
			} else {
				eng = fasthttp.NewWithConfig(conf)
			}
		} else {
			addr := `:80`
			if v, ok := arg.(string); ok && len(v) > 0 {
				addr = v
			}
			if v, ok := args[1].(string); ok {
				if v == `fast` {
					eng = fasthttp.New(addr)
				} else {
					eng = standard.New(addr)
				}
			} else {
				eng = fasthttp.New(addr)
			}
		}
	} else {
		switch v := arg.(type) {
		case string:
			eng = fasthttp.New(v)
		case engine.Engine:
			eng = v
		default:
			eng = fasthttp.New(`:80`)
		}
	}
	e := s.Get(s.Boot)
	if e == nil {
		for _, info := range s.Alias {
			e = info
			break
		}
	}
	for _, info := range s.Alias {
		if e == info {
			continue
		}
		info.Commit()
	}
	e.Logger().Info(`Server has been launched.`)
	e.Run(eng, s)
	e.Logger().Info(`Server has been closed.`)
}
<|endoftext|>


<commit_before>// @Copyright (c) 2016 mparaiso <mparaiso@online.fr>  All rights reserved.

package gonews

import (
	"database/sql"
	"log"
	"net/http"
	"os"
	"path"

	"github.com/gorilla/sessions"
)

// GetApp returns an application ready to be handled by a server
func GetApp(appOptions AppOptions) http.Handler {
	// Normalize appOptions
	if appOptions.PublicDirectory == "" {
		wd, err := os.Getwd()
		if err != nil {
			log.Fatal(err)
		}
		appOptions.PublicDirectory = path.Join(wd, "public")
	}
	// The containerFactory will be used to create a new container
	// for each request, the container is then passed to all middlewares
	if appOptions.ContainerFactory == nil {
		appOptions.ContainerFactory = func() *Container {
			container := &Container{
				ContainerOptions: appOptions.ContainerOptions,
			}
			container.SessionProvider = NewDefaultSessionProvider(appOptions.ContainerOptions.Session.Name, container, container, container)
			container.CSRFGeneratorProvider = NewDefaultCSRFGeneratorProvider(container, container)
			container.TemplateProvider = NewDefaultTemplateProvider(container.ContainerOptions.TemplateDirectory,
				container.ContainerOptions.TemplateFileExtension,
				container.ContainerOptions.Debug)
			return container
		}
	}

	DefaultStack := &Stack{
		Middlewares: []Middleware{
			StopWatchMiddleware,   // Times how long it takes for the request to be handled
			LoggerMiddleware,      // Logs each request using the common log format
			SessionMiddleware,     // Initializes the session
			RefreshUserMiddleware, // Refresh an authenticated user if user.ID exists in session
			TemplateMiddleware,    // Configures template environment
		}, ContainerFactory: appOptions.ContainerFactory}

	Default := DefaultStack.Clone().Build()
	// Usef for authenticated routes
	AuthenticatedUsersOnly := DefaultStack.Clone().Push(AuthenticatedUserOnlyMiddleware).Build()
	// A middleware stack that extends Zero and handles requests for missing pages
	app := http.NewServeMux()
	// homepage
	app.HandleFunc("/", Default(NotFoundMiddleware, ThreadIndexController))
	// thread
	app.HandleFunc("/item", Default(ThreadShowController))
	// thread by host
	app.HandleFunc("/from", Default(ThreadByHostController))
	// login
	app.HandleFunc("/login", Default(LoginController))
	// logout
	app.HandleFunc("/logout", Default(PostOnlyMiddleware, LogoutController))
	// user
	app.HandleFunc("/user", Default(UserShowController))
	// submit
	app.HandleFunc("/submit", AuthenticatedUsersOnly(SubmissionController))
	// submitted
	app.HandleFunc("/submitted", Default(ThreadListByAuthorIDController))
	// threads : author's comments
	app.HandleFunc("/threads", Default(CommentsByAuthorController))
	// registration
	app.HandleFunc("/register", Default(PostOnlyMiddleware, RegistrationController))
	//public static files
	app.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir(appOptions.PublicDirectory))))
	// not found
	return app
}

// DefaultContainerOptions returns the default ContainerOptions
// A closure is used to generate the function which allows us
// to have a few global variables ,like the session store or the db
var DefaultContainerOptions = func() func() ContainerOptions {
	//secret := securecookie.GenerateRandomKey(64)
	connection, connectionErr := sql.Open("sqlite3", "db.sqlite3")
	secret := []byte("some secret key for debugging purposes")
	sessionCookieStore := sessions.NewCookieStore(secret)
	sessionCookieStore.Options = &sessions.Options{
		Path:     "/",
		HttpOnly: true,
		Secure:   true,
		MaxAge:   60 * 60 * 24,
		Domain:   "localhost",
	}

	return func() ContainerOptions {
		options := ContainerOptions{
			Debug:                 false,
			LogLevel:              INFO,
			Title:                 "gonews",
			Slogan:                "the news site for gophers",
			Description:           "gonews is a site where gophers publish and discuss news about the go language",
			DataSource:            "db.sqlite3",
			Driver:                "sqlite3",
			TemplateDirectory:     "templates",
			TemplateFileExtension: "tpl.html",
			Secret:                string(secret),
			Session: struct {
				Name         string
				StoreFactory func() (sessions.Store, error)
			}{
				Name: "go-news",
				StoreFactory: func() (sessions.Store, error) {
					return sessionCookieStore, nil
				},
			},
			ConnectionFactory: func() (*sql.DB, error) {
				return connection, connectionErr
			},
		}
		return options
	}
}()

// AppOptions gather all the configuration options
type AppOptions struct {
	PublicDirectory string
	ContainerOptions
	ContainerFactory func() *Container
}
<commit_msg>session provider<commit_after>// @Copyright (c) 2016 mparaiso <mparaiso@online.fr>  All rights reserved.

package gonews

import (
	"database/sql"
	"log"
	"net/http"
	"os"
	"path"

	"github.com/gorilla/sessions"
)

// GetApp returns an application ready to be handled by a server
func GetApp(appOptions AppOptions) http.Handler {
	// Normalize appOptions
	if appOptions.PublicDirectory == "" {
		wd, err := os.Getwd()
		if err != nil {
			log.Fatal(err)
		}
		appOptions.PublicDirectory = path.Join(wd, "public")
	}
	// The containerFactory will be used to create a new container
	// for each request, the container is then passed to all middlewares in the middleware stack
	if appOptions.ContainerFactory == nil {
		appOptions.ContainerFactory = func() *Container {
			container := &Container{
				ContainerOptions: appOptions.ContainerOptions,
			}
			container.LoggerProvider = NewDefaultLoggerProvider(container.ContainerOptions.LoggerFactory,
				container.ContainerOptions.LogLevel,
				container.ContainerOptions.Debug)

			container.SessionProvider = NewDefaultSessionProvider(appOptions.ContainerOptions.Session.Name,
				container, container, container)

			container.CSRFGeneratorProvider = NewDefaultCSRFGeneratorProvider(container, container)

			container.TemplateProvider = NewDefaultTemplateProvider(container.ContainerOptions.TemplateDirectory,
				container.ContainerOptions.TemplateFileExtension,
				container.ContainerOptions.Debug, container)

			return container
		}
	}
	// This is the default middleware stack each requests pass through all these middlewares
	// before being handled by a controller (which is also a middleware FYI )
	DefaultStack := &Stack{
		Middlewares: []Middleware{
			StopWatchMiddleware,   // Times how long it takes for the request to be handled
			LoggerMiddleware,      // Logs each request using the common log format
			SessionMiddleware,     // Initializes the session
			RefreshUserMiddleware, // Refresh an authenticated user if user.ID exists in session
			TemplateMiddleware,    // Configures template environment
		}, ContainerFactory: appOptions.ContainerFactory}

	Default := DefaultStack.Clone().Build()
	// Usef for authenticated routes
	AuthenticatedUsersOnly := DefaultStack.Clone().Push(AuthenticatedUserOnlyMiddleware).Build()
	app := http.NewServeMux()
	// homepage
	app.HandleFunc("/", Default(NotFoundMiddleware, ThreadIndexController))
	// thread
	app.HandleFunc("/item", Default(ThreadShowController))
	// thread by host
	app.HandleFunc("/from", Default(ThreadByHostController))
	// login
	app.HandleFunc("/login", Default(LoginController))
	// logout
	app.HandleFunc("/logout", Default(PostOnlyMiddleware, LogoutController))
	// user
	app.HandleFunc("/user", Default(UserShowController))
	// submit
	app.HandleFunc("/submit", AuthenticatedUsersOnly(SubmissionController))
	// submitted
	app.HandleFunc("/submitted", Default(ThreadListByAuthorIDController))
	// threads : author's comments
	app.HandleFunc("/threads", Default(CommentsByAuthorController))
	// registration
	app.HandleFunc("/register", Default(PostOnlyMiddleware, RegistrationController))
	//public static files
	app.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir(appOptions.PublicDirectory))))
	// not found
	return app
}

// DefaultContainerOptions returns the default ContainerOptions
// A closure is used to generate the function which allows us
// to have a few global variables ,like the session store or the db
var DefaultContainerOptions = func() func() ContainerOptions {
	//secret := securecookie.GenerateRandomKey(64)
	connection, connectionErr := sql.Open("sqlite3", "db.sqlite3")
	secret := []byte("some secret key for debugging purposes")
	sessionCookieStore := sessions.NewCookieStore(secret)
	sessionCookieStore.Options = &sessions.Options{
		Path:     "/",
		HttpOnly: true,
		Secure:   true,
		MaxAge:   60 * 60 * 24,
		Domain:   "localhost",
	}

	return func() ContainerOptions {
		options := ContainerOptions{
			Debug:                 false,
			LogLevel:              INFO,
			Title:                 "gonews",
			Slogan:                "the news site for gophers",
			Description:           "gonews is a site where gophers publish and discuss news about the go language",
			DataSource:            "db.sqlite3",
			Driver:                "sqlite3",
			TemplateDirectory:     "templates",
			TemplateFileExtension: "tpl.html",
			Secret:                string(secret),
			Session: struct {
				Name         string
				StoreFactory func() (sessions.Store, error)
			}{
				Name: "go-news",
				StoreFactory: func() (sessions.Store, error) {
					return sessionCookieStore, nil
				},
			},
			ConnectionFactory: func() (*sql.DB, error) {
				return connection, connectionErr
			},
		}
		return options
	}
}()

// AppOptions gather all the configuration options
type AppOptions struct {
	PublicDirectory string
	ContainerOptions
	ContainerFactory func() *Container
}
<|endoftext|>


<commit_before>package http

import (
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/Cepave/open-falcon-backend/modules/query/g"
	log "github.com/Sirupsen/logrus"
	"github.com/astaxie/beego/orm"
	"github.com/jasonlvhit/gocron"
)

type Contacts struct {
	Id      int
	Name    string
	Phone   string
	Email   string
	Updated string
}

type Hosts struct {
	Id        int
	Hostname  string
	Exist     int
	Activate  int
	Platform  string
	Platforms string
	Idc       string
	Ip        string
	Isp       string
	Province  string
	City      string
	Status    string
	Updated   string
}

type Idcs struct {
	Id        int
	Popid     int
	Idc       string
	Bandwidth int
	Count     int
	Area      string
	Province  string
	City      string
	Updated   string
}

type Ips struct {
	Id       int
	Ip       string
	Exist    int
	Status   int
	Hostname string
	Platform string
	Updated  string
}

type Platforms struct {
	Id        int
	Platform  string
	Contacts  string
	Principal string
	Deputy    string
	Upgrader  string
	Count     int
	Updated   string
}

func SyncHostsAndContactsTable() {
	if g.Config().Hosts.Enabled || g.Config().Contacts.Enabled {
		if g.Config().Hosts.Enabled {
			updateMapData()
			syncHostsTable()
			intervalToSyncHostsTable := uint64(g.Config().Hosts.Interval)
			gocron.Every(intervalToSyncHostsTable).Seconds().Do(syncHostsTable)
		}
		if g.Config().Contacts.Enabled {
			syncContactsTable()
			intervalToSyncContactsTable := uint64(g.Config().Contacts.Interval)
			gocron.Every(intervalToSyncContactsTable).Seconds().Do(syncContactsTable)
		}
		<-gocron.Start()
	}
}

func getIDCMap() map[string]interface{} {
	idcMap := map[string]interface{}{}
	o := orm.NewOrm()
	var idcs []Idc
	sqlcommand := "SELECT pop_id, name, province, city FROM grafana.idc ORDER BY pop_id ASC"
	_, err := o.Raw(sqlcommand).QueryRows(&idcs)
	if err != nil {
		log.Errorf(err.Error())
	}
	for _, idc := range idcs {
		idcMap[strconv.Itoa(idc.Pop_id)] = idc
	}
	return idcMap
}

func updateHostsTable(hostnames []string, hostsMap map[string]map[string]string) {
	log.Debugf("func updateHostsTable()")
	var hosts []Hosts
	o := orm.NewOrm()
	o.Using("boss")
	_, err := o.QueryTable("hosts").Limit(10000).All(&hosts)
	if err != nil {
		log.Errorf(err.Error())
	} else {
		format := "2006-01-02 15:04:05"
		for _, host := range hosts {
			updatedTime, _ := time.Parse(format, host.Updated)
			currentTime, _ := time.Parse(format, getNow())
			diff := currentTime.Unix() - updatedTime.Unix()
			if diff > 600 {
				host.Exist = 0
				_, err := o.Update(&host)
				if err != nil {
					log.Errorf(err.Error())
				}
			}
		}
	}

	hosts = []Hosts{}
	idcMap := getIDCMap()
	var host Hosts
	for _, hostname := range hostnames {
		item := hostsMap[hostname]
		activate, _ := strconv.Atoi(item["activate"])
		host.Hostname = item["hostname"]
		host.Exist = 1
		host.Activate = activate
		host.Platform = item["platform"]
		host.Ip = item["ip"]
		host.Isp = strings.Split(item["hostname"], "-")[0]
		host.Updated = getNow()
		idcID := item["idcID"]
		if _, ok := idcMap[idcID]; ok {
			idc := idcMap[idcID]
			host.Idc = idc.(Idc).Name
			host.Province = idc.(Idc).Province
			host.City = idc.(Idc).City
		}
		hosts = append(hosts, host)
	}
	for _, item := range hosts {
		err := o.QueryTable("hosts").Limit(10000).Filter("hostname", item.Hostname).One(&host)
		if err == orm.ErrNoRows {
			sql := "INSERT INTO boss.hosts("
			sql += "hostname, exist, activate, platform, idc, ip, "
			sql += "isp, province, city, updated) "
			sql += "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
			_, err := o.Raw(sql, item.Hostname, item.Exist, item.Activate, item.Platform, item.Idc, item.Ip, item.Isp, item.Province, item.City, item.Updated).Exec()
			if err != nil {
				log.Errorf(err.Error())
			}
		} else if err != nil {
			log.Errorf(err.Error())
		} else {
			item.Id = host.Id
			_, err := o.Update(&item)
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}
}

func updatePlatformsTable(platformNames []string, platformsMap map[string]map[string]interface{}) {
	log.Debugf("func updatePlatformsTable()")
	now := getNow()
	o := orm.NewOrm()
	o.Using("boss")
	var platform Platforms
	for _, platformName := range platformNames {
		count, err := o.QueryTable("ips").Filter("platform", platformName).Filter("exist", 1).Filter("status", 1).Exclude("hostname__isnull", true).Count()
		if err != nil {
			count = 0
		}
		group := platformsMap[platformName]
		err = o.QueryTable("platforms").Filter("platform", group["platformName"]).One(&platform)
		if err == orm.ErrNoRows {
			sql := "INSERT INTO boss.platforms(platform, count, updated) VALUES(?, ?, ?)"
			_, err := o.Raw(sql, group["platformName"], count, now).Exec()
			if err != nil {
				log.Errorf(err.Error())
			}
		} else if err != nil {
			log.Errorf(err.Error())
		} else {
			platform.Platform = group["platformName"].(string)
			platform.Count = int(count)
			platform.Updated = now
			_, err := o.Update(&platform)
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}
}

func updateContactsTable(contactNames []string, contactsMap map[string]map[string]string) {
	log.Debugf("func updateContactsTable()")
	o := orm.NewOrm()
	o.Using("boss")
	var contact Contacts
	for _, contactName := range contactNames {
		user := contactsMap[contactName]
		err := o.QueryTable("contacts").Filter("name", user["name"]).One(&contact)
		if err == orm.ErrNoRows {
			sql := "INSERT INTO boss.contacts(name, phone, email, updated) VALUES(?, ?, ?, ?)"
			_, err := o.Raw(sql, user["name"], user["phone"], user["email"], getNow()).Exec()
			if err != nil {
				log.Errorf(err.Error())
			}
		} else if err != nil {
			log.Errorf(err.Error())
		} else {
			contact.Email = user["email"]
			contact.Phone = user["phone"]
			contact.Updated = getNow()
			_, err := o.Update(&contact)
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}
}

func addContactsToPlatformsTable(contacts map[string]interface{}) {
	log.Debugf("func addContactsToPlatformsTable()")
	now := getNow()
	o := orm.NewOrm()
	o.Using("boss")
	var platforms []Platforms
	_, err := o.QueryTable("platforms").All(&platforms)
	if err != nil {
		log.Errorf(err.Error())
	} else {
		for _, platform := range platforms {
			platformName := platform.Platform
			if items, ok := contacts[platformName]; ok {
				contacts := []string{}
				for role, user := range items.(map[string]map[string]string) {
					if (role == "principal") {
						platform.Principal = user["name"]
					} else if (role == "deputy") {
						platform.Deputy = user["name"]
					} else if (role == "upgrader") {
						platform.Upgrader = user["name"]
					}
				}
				if (len(platform.Principal) > 0) {
					contacts = append(contacts, platform.Principal)
				}
				if (len(platform.Deputy) > 0) {
					contacts = append(contacts, platform.Deputy)
				}
				if (len(platform.Upgrader) > 0) {
					contacts = append(contacts, platform.Upgrader)
				}
				platform.Contacts = strings.Join(contacts, ",")
			}
			platform.Updated = now
			_, err := o.Update(&platform)
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}
}

func syncHostsTable() {
	o := orm.NewOrm()
	o.Using("boss")
	var rows []orm.Params
	sql := "SELECT updated FROM boss.hosts WHERE exist = 1 ORDER BY updated DESC LIMIT 1"
	num, err := o.Raw(sql).Values(&rows)
	if err != nil {
		log.Errorf(err.Error())
		return
	} else if num > 0 {
		format := "2006-01-02 15:04:05"
		updatedTime, _ := time.Parse(format, rows[0]["updated"].(string))
		currentTime, _ := time.Parse(format, getNow())
		diff := currentTime.Unix() - updatedTime.Unix()
		if int(diff) < g.Config().Hosts.Interval {
			return
		}
	}

	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	getPlatformJSON(nodes, result)
	if nodes["status"] == nil {
		return
	} else if int(nodes["status"].(float64)) != 1 {
		return
	}
	platformNames := []string{}
	platformsMap := map[string]map[string]interface{}{}
	hostnames := []string{}
	hostsMap := map[string]map[string]string{}
	hostnamesMap := map[string]int{}
	idcIDs := []string{}
	hostname := ""
	for _, platform := range nodes["result"].([]interface{}) {
		countOfHosts := 0
		platformName := platform.(map[string]interface{})["platform"].(string)
		platformNames = appendUniqueString(platformNames, platformName)
		for _, device := range platform.(map[string]interface{})["ip_list"].([]interface{}) {
			hostname = device.(map[string]interface{})["hostname"].(string)
			ip := device.(map[string]interface{})["ip"].(string)
			if len(ip) > 0 && ip == getIPFromHostname(hostname, result) {
				if _, ok := hostnamesMap[hostname]; !ok {
					hostnames = append(hostnames, hostname)
					idcID := device.(map[string]interface{})["pop_id"].(string)
					host := map[string]string{
						"hostname": hostname,
						"activate": device.(map[string]interface{})["ip_status"].(string),
						"platform": platformName,
						"idcID":    idcID,
						"ip":       ip,
					}
					hostsMap[hostname] = host
					idcIDs = appendUniqueString(idcIDs, idcID)
					hostnamesMap[hostname] = 1
					countOfHosts++
				}
			}
		}
		platformsMap[platformName] = map[string]interface{}{
			"platformName": platformName,
			"count":        countOfHosts,
			"contacts":     "",
		}
	}
	sort.Strings(hostnames)
	sort.Strings(platformNames)
	log.Debugf("platformNames =", platformNames)
	updateHostsTable(hostnames, hostsMap)
	updatePlatformsTable(platformNames, platformsMap)
}

func syncContactsTable() {
	log.Debugf("func syncContactsTable()")
	o := orm.NewOrm()
	o.Using("boss")
	var rows []orm.Params
	sql := "SELECT updated FROM boss.contacts ORDER BY updated DESC LIMIT 1"
	num, err := o.Raw(sql).Values(&rows)
	if err != nil {
		log.Errorf(err.Error())
		return
	} else if num > 0 {
		format := "2006-01-02 15:04:05"
		updatedTime, _ := time.Parse(format, rows[0]["updated"].(string))
		currentTime, _ := time.Parse(format, getNow())
		diff := currentTime.Unix() - updatedTime.Unix()
		if int(diff) < g.Config().Contacts.Interval {
			return
		}
	}
	platformNames := []string{}
	sql = "SELECT DISTINCT platform FROM boss.platforms ORDER BY platform ASC"
	num, err = o.Raw(sql).Values(&rows)
	if err != nil {
		log.Errorf(err.Error())
		return
	} else if num > 0 {
		for _, row := range rows {
			platformNames = append(platformNames, row["platform"].(string))
		}
	}

	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	getPlatformContact(strings.Join(platformNames, ","), nodes)
	contactNames := []string{}
	contactsMap := map[string]map[string]string{}
	contacts := nodes["result"].(map[string]interface{})["items"].(map[string]interface{})
	for _, platformName := range platformNames {
		if items, ok := contacts[platformName]; ok {
			for _, user := range items.(map[string]map[string]string) {
				contactName := user["name"]
				if _, ok := contactsMap[contactName]; !ok {
					contactsMap[contactName] = user
					contactNames = append(contactNames, contactName)
				}
			}
		}
	}
	sort.Strings(contactNames)
	updateContactsTable(contactNames, contactsMap)
	addContactsToPlatformsTable(contacts)
}
<commit_msg>[OWL-1199][query] add func queryIDCsHostsCount()<commit_after>package http

import (
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/Cepave/open-falcon-backend/modules/query/g"
	log "github.com/Sirupsen/logrus"
	"github.com/astaxie/beego/orm"
	"github.com/jasonlvhit/gocron"
)

type Contacts struct {
	Id      int
	Name    string
	Phone   string
	Email   string
	Updated string
}

type Hosts struct {
	Id        int
	Hostname  string
	Exist     int
	Activate  int
	Platform  string
	Platforms string
	Idc       string
	Ip        string
	Isp       string
	Province  string
	City      string
	Status    string
	Updated   string
}

type Idcs struct {
	Id        int
	Popid     int
	Idc       string
	Bandwidth int
	Count     int
	Area      string
	Province  string
	City      string
	Updated   string
}

type Ips struct {
	Id       int
	Ip       string
	Exist    int
	Status   int
	Hostname string
	Platform string
	Updated  string
}

type Platforms struct {
	Id        int
	Platform  string
	Contacts  string
	Principal string
	Deputy    string
	Upgrader  string
	Count     int
	Updated   string
}

func SyncHostsAndContactsTable() {
	if g.Config().Hosts.Enabled || g.Config().Contacts.Enabled {
		if g.Config().Hosts.Enabled {
			updateMapData()
			syncHostsTable()
			intervalToSyncHostsTable := uint64(g.Config().Hosts.Interval)
			gocron.Every(intervalToSyncHostsTable).Seconds().Do(syncHostsTable)
		}
		if g.Config().Contacts.Enabled {
			syncContactsTable()
			intervalToSyncContactsTable := uint64(g.Config().Contacts.Interval)
			gocron.Every(intervalToSyncContactsTable).Seconds().Do(syncContactsTable)
		}
		<-gocron.Start()
	}
}

func getIDCMap() map[string]interface{} {
	idcMap := map[string]interface{}{}
	o := orm.NewOrm()
	var idcs []Idc
	sqlcommand := "SELECT pop_id, name, province, city FROM grafana.idc ORDER BY pop_id ASC"
	_, err := o.Raw(sqlcommand).QueryRows(&idcs)
	if err != nil {
		log.Errorf(err.Error())
	}
	for _, idc := range idcs {
		idcMap[strconv.Itoa(idc.Pop_id)] = idc
	}
	return idcMap
}

func queryIDCsHostsCount(IDCName string) int64 {
	o := orm.NewOrm()
	o.Using("boss")
	count, err := o.QueryTable("hosts").Limit(10000).Filter("idc", IDCName).Count()
	if err != nil {
		count = int64(0)
	}
	return count
}

func updateHostsTable(hostnames []string, hostsMap map[string]map[string]string) {
	log.Debugf("func updateHostsTable()")
	var hosts []Hosts
	o := orm.NewOrm()
	o.Using("boss")
	_, err := o.QueryTable("hosts").Limit(10000).All(&hosts)
	if err != nil {
		log.Errorf(err.Error())
	} else {
		format := "2006-01-02 15:04:05"
		for _, host := range hosts {
			updatedTime, _ := time.Parse(format, host.Updated)
			currentTime, _ := time.Parse(format, getNow())
			diff := currentTime.Unix() - updatedTime.Unix()
			if diff > 600 {
				host.Exist = 0
				_, err := o.Update(&host)
				if err != nil {
					log.Errorf(err.Error())
				}
			}
		}
	}

	hosts = []Hosts{}
	idcMap := getIDCMap()
	var host Hosts
	for _, hostname := range hostnames {
		item := hostsMap[hostname]
		activate, _ := strconv.Atoi(item["activate"])
		host.Hostname = item["hostname"]
		host.Exist = 1
		host.Activate = activate
		host.Platform = item["platform"]
		host.Ip = item["ip"]
		host.Isp = strings.Split(item["hostname"], "-")[0]
		host.Updated = getNow()
		idcID := item["idcID"]
		if _, ok := idcMap[idcID]; ok {
			idc := idcMap[idcID]
			host.Idc = idc.(Idc).Name
			host.Province = idc.(Idc).Province
			host.City = idc.(Idc).City
		}
		hosts = append(hosts, host)
	}
	for _, item := range hosts {
		err := o.QueryTable("hosts").Limit(10000).Filter("hostname", item.Hostname).One(&host)
		if err == orm.ErrNoRows {
			sql := "INSERT INTO boss.hosts("
			sql += "hostname, exist, activate, platform, idc, ip, "
			sql += "isp, province, city, updated) "
			sql += "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
			_, err := o.Raw(sql, item.Hostname, item.Exist, item.Activate, item.Platform, item.Idc, item.Ip, item.Isp, item.Province, item.City, item.Updated).Exec()
			if err != nil {
				log.Errorf(err.Error())
			}
		} else if err != nil {
			log.Errorf(err.Error())
		} else {
			item.Id = host.Id
			_, err := o.Update(&item)
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}
}

func updatePlatformsTable(platformNames []string, platformsMap map[string]map[string]interface{}) {
	log.Debugf("func updatePlatformsTable()")
	now := getNow()
	o := orm.NewOrm()
	o.Using("boss")
	var platform Platforms
	for _, platformName := range platformNames {
		count, err := o.QueryTable("ips").Filter("platform", platformName).Filter("exist", 1).Filter("status", 1).Exclude("hostname__isnull", true).Count()
		if err != nil {
			count = 0
		}
		group := platformsMap[platformName]
		err = o.QueryTable("platforms").Filter("platform", group["platformName"]).One(&platform)
		if err == orm.ErrNoRows {
			sql := "INSERT INTO boss.platforms(platform, count, updated) VALUES(?, ?, ?)"
			_, err := o.Raw(sql, group["platformName"], count, now).Exec()
			if err != nil {
				log.Errorf(err.Error())
			}
		} else if err != nil {
			log.Errorf(err.Error())
		} else {
			platform.Platform = group["platformName"].(string)
			platform.Count = int(count)
			platform.Updated = now
			_, err := o.Update(&platform)
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}
}

func updateContactsTable(contactNames []string, contactsMap map[string]map[string]string) {
	log.Debugf("func updateContactsTable()")
	o := orm.NewOrm()
	o.Using("boss")
	var contact Contacts
	for _, contactName := range contactNames {
		user := contactsMap[contactName]
		err := o.QueryTable("contacts").Filter("name", user["name"]).One(&contact)
		if err == orm.ErrNoRows {
			sql := "INSERT INTO boss.contacts(name, phone, email, updated) VALUES(?, ?, ?, ?)"
			_, err := o.Raw(sql, user["name"], user["phone"], user["email"], getNow()).Exec()
			if err != nil {
				log.Errorf(err.Error())
			}
		} else if err != nil {
			log.Errorf(err.Error())
		} else {
			contact.Email = user["email"]
			contact.Phone = user["phone"]
			contact.Updated = getNow()
			_, err := o.Update(&contact)
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}
}

func addContactsToPlatformsTable(contacts map[string]interface{}) {
	log.Debugf("func addContactsToPlatformsTable()")
	now := getNow()
	o := orm.NewOrm()
	o.Using("boss")
	var platforms []Platforms
	_, err := o.QueryTable("platforms").All(&platforms)
	if err != nil {
		log.Errorf(err.Error())
	} else {
		for _, platform := range platforms {
			platformName := platform.Platform
			if items, ok := contacts[platformName]; ok {
				contacts := []string{}
				for role, user := range items.(map[string]map[string]string) {
					if (role == "principal") {
						platform.Principal = user["name"]
					} else if (role == "deputy") {
						platform.Deputy = user["name"]
					} else if (role == "upgrader") {
						platform.Upgrader = user["name"]
					}
				}
				if (len(platform.Principal) > 0) {
					contacts = append(contacts, platform.Principal)
				}
				if (len(platform.Deputy) > 0) {
					contacts = append(contacts, platform.Deputy)
				}
				if (len(platform.Upgrader) > 0) {
					contacts = append(contacts, platform.Upgrader)
				}
				platform.Contacts = strings.Join(contacts, ",")
			}
			platform.Updated = now
			_, err := o.Update(&platform)
			if err != nil {
				log.Errorf(err.Error())
			}
		}
	}
}

func syncHostsTable() {
	o := orm.NewOrm()
	o.Using("boss")
	var rows []orm.Params
	sql := "SELECT updated FROM boss.hosts WHERE exist = 1 ORDER BY updated DESC LIMIT 1"
	num, err := o.Raw(sql).Values(&rows)
	if err != nil {
		log.Errorf(err.Error())
		return
	} else if num > 0 {
		format := "2006-01-02 15:04:05"
		updatedTime, _ := time.Parse(format, rows[0]["updated"].(string))
		currentTime, _ := time.Parse(format, getNow())
		diff := currentTime.Unix() - updatedTime.Unix()
		if int(diff) < g.Config().Hosts.Interval {
			return
		}
	}

	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	getPlatformJSON(nodes, result)
	if nodes["status"] == nil {
		return
	} else if int(nodes["status"].(float64)) != 1 {
		return
	}
	platformNames := []string{}
	platformsMap := map[string]map[string]interface{}{}
	hostnames := []string{}
	hostsMap := map[string]map[string]string{}
	hostnamesMap := map[string]int{}
	idcIDs := []string{}
	hostname := ""
	for _, platform := range nodes["result"].([]interface{}) {
		countOfHosts := 0
		platformName := platform.(map[string]interface{})["platform"].(string)
		platformNames = appendUniqueString(platformNames, platformName)
		for _, device := range platform.(map[string]interface{})["ip_list"].([]interface{}) {
			hostname = device.(map[string]interface{})["hostname"].(string)
			ip := device.(map[string]interface{})["ip"].(string)
			if len(ip) > 0 && ip == getIPFromHostname(hostname, result) {
				if _, ok := hostnamesMap[hostname]; !ok {
					hostnames = append(hostnames, hostname)
					idcID := device.(map[string]interface{})["pop_id"].(string)
					host := map[string]string{
						"hostname": hostname,
						"activate": device.(map[string]interface{})["ip_status"].(string),
						"platform": platformName,
						"idcID":    idcID,
						"ip":       ip,
					}
					hostsMap[hostname] = host
					idcIDs = appendUniqueString(idcIDs, idcID)
					hostnamesMap[hostname] = 1
					countOfHosts++
				}
			}
		}
		platformsMap[platformName] = map[string]interface{}{
			"platformName": platformName,
			"count":        countOfHosts,
			"contacts":     "",
		}
	}
	sort.Strings(hostnames)
	sort.Strings(platformNames)
	log.Debugf("platformNames =", platformNames)
	updateHostsTable(hostnames, hostsMap)
	updatePlatformsTable(platformNames, platformsMap)
}

func syncContactsTable() {
	log.Debugf("func syncContactsTable()")
	o := orm.NewOrm()
	o.Using("boss")
	var rows []orm.Params
	sql := "SELECT updated FROM boss.contacts ORDER BY updated DESC LIMIT 1"
	num, err := o.Raw(sql).Values(&rows)
	if err != nil {
		log.Errorf(err.Error())
		return
	} else if num > 0 {
		format := "2006-01-02 15:04:05"
		updatedTime, _ := time.Parse(format, rows[0]["updated"].(string))
		currentTime, _ := time.Parse(format, getNow())
		diff := currentTime.Unix() - updatedTime.Unix()
		if int(diff) < g.Config().Contacts.Interval {
			return
		}
	}
	platformNames := []string{}
	sql = "SELECT DISTINCT platform FROM boss.platforms ORDER BY platform ASC"
	num, err = o.Raw(sql).Values(&rows)
	if err != nil {
		log.Errorf(err.Error())
		return
	} else if num > 0 {
		for _, row := range rows {
			platformNames = append(platformNames, row["platform"].(string))
		}
	}

	var nodes = make(map[string]interface{})
	errors := []string{}
	var result = make(map[string]interface{})
	result["error"] = errors
	getPlatformContact(strings.Join(platformNames, ","), nodes)
	contactNames := []string{}
	contactsMap := map[string]map[string]string{}
	contacts := nodes["result"].(map[string]interface{})["items"].(map[string]interface{})
	for _, platformName := range platformNames {
		if items, ok := contacts[platformName]; ok {
			for _, user := range items.(map[string]map[string]string) {
				contactName := user["name"]
				if _, ok := contactsMap[contactName]; !ok {
					contactsMap[contactName] = user
					contactNames = append(contactNames, contactName)
				}
			}
		}
	}
	sort.Strings(contactNames)
	updateContactsTable(contactNames, contactsMap)
	addContactsToPlatformsTable(contacts)
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/abh/dns"
	. "launchpad.net/gocheck"
	"net"
	"strings"
	"time"
)

const (
	PORT = ":8853"
)

type ServeSuite struct {
}

var _ = Suite(&ServeSuite{})

func (s *ServeSuite) SetUpSuite(c *C) {

	// log.Println("Setting up serve test suite")

	Zones := make(Zones)
	setupPgeodnsZone(Zones)
	zonesReadDir("dns", Zones)

	go listenAndServe(PORT)

	time.Sleep(200 * time.Millisecond)
}

func (s *ServeSuite) TestServing(c *C) {

	r := exchange(c, "_status.pgeodns.", dns.TypeTXT)
	txt := r.Answer[0].(*dns.TXT).Txt[0]
	if !strings.HasPrefix(txt, "{") {
		c.Log("Unexpected result for _status.pgeodns", txt)
		c.Fail()
	}

	// Allow _country and _status queries as long as the first label is that
	r = exchange(c, "_country.foo.pgeodns.", dns.TypeTXT)
	txt = r.Answer[0].(*dns.TXT).Txt[0]
	// Got appropriate response for _country txt query
	if !strings.HasPrefix(txt, "127.0.0.1:") {
		c.Log("Unexpected result for _country.foo.pgeodns", txt)
		c.Fail()
	}

	// Make sure A requests for _status doesn't NXDOMAIN
	r = exchange(c, "_status.pgeodns.", dns.TypeA)
	c.Check(r.Answer, HasLen, 0)
	// Got one SOA record
	c.Check(r.Ns, HasLen, 1)
	// NOERROR for A request
	c.Check(r.Rcode, Equals, dns.RcodeSuccess)

	r = exchange(c, "bar.test.example.com.", dns.TypeA)
	ip := r.Answer[0].(*dns.A).A
	c.Check(ip.String(), Equals, "192.168.1.2")

	r = exchange(c, "test.example.com.", dns.TypeSOA)
	soa := r.Answer[0].(*dns.SOA)
	serial := soa.Serial
	c.Check(int(serial), Equals, 3)

	// no AAAA records for 'bar', so check we get a soa record back
	r = exchange(c, "test.example.com.", dns.TypeAAAA)
	soa2 := r.Ns[0].(*dns.SOA)
	c.Check(soa, DeepEquals, soa2)

	// CNAMEs
	r = exchange(c, "www.test.example.com.", dns.TypeA)
	c.Check(r.Answer[0].(*dns.CNAME).Target, Equals, "geo.bitnames.com.")

	// MX
	r = exchange(c, "test.example.com.", dns.TypeMX)
	c.Check(r.Answer[0].(*dns.MX).Mx, Equals, "mx.example.net.")
	c.Check(r.Answer[1].(*dns.MX).Mx, Equals, "mx2.example.net.")
	c.Check(r.Answer[1].(*dns.MX).Preference, Equals, uint16(20))
}

func (s *ServeSuite) TestServingAliases(c *C) {
	// Alias, no geo matches
	r := exchange(c, "bar-alias.test.example.com.", dns.TypeA)
	ip := r.Answer[0].(*dns.A).A
	c.Check(ip.String(), Equals, "192.168.1.2")

	// Alias to a cname record
	r = exchange(c, "www-alias.test.example.com.", dns.TypeA)
	c.Check(r.Answer[0].(*dns.CNAME).Target, Equals, "geo.bitnames.com.")

	// Alias returning a cname, with geo overrides
	r = exchangeSubnet(c, "www-alias.test.example.com.", dns.TypeA, "194.239.134.1")
	c.Check(r.Answer, HasLen, 1)
	if len(r.Answer) > 0 {
		c.Check(r.Answer[0].(*dns.CNAME).Target, Equals, "geo-europe.bitnames.com.")
	}

	// Alias to Ns records
	r = exchange(c, "sub-alias.test.example.org.", dns.TypeNS)
	c.Check(r.Answer[0].(*dns.NS).Ns, Equals, "ns1.example.com.")

}

func (s *ServeSuite) TestServingEDNS(c *C) {
	// MX test
	r := exchangeSubnet(c, "test.example.com.", dns.TypeMX, "194.239.134.1")
	c.Check(r.Answer, HasLen, 1)
	if len(r.Answer) > 0 {
		c.Check(r.Answer[0].(*dns.MX).Mx, Equals, "mx-eu.example.net.")
	}

	c.Log("Testing www.test.example.com from .dk, should match www.europe (a cname)")

	r = exchangeSubnet(c, "www.test.example.com.", dns.TypeA, "194.239.134.0")
	c.Check(r.Answer, HasLen, 1)
	if len(r.Answer) > 0 {
		// EDNS-SUBNET test (request A, respond CNAME)
		c.Check(r.Answer[0].(*dns.CNAME).Target, Equals, "geo-europe.bitnames.com.")
	}

}

func exchangeSubnet(c *C, name string, dnstype uint16, ip string) *dns.Msg {
	msg := new(dns.Msg)

	msg.SetQuestion(name, dnstype)

	o := new(dns.OPT)
	o.Hdr.Name = "."
	o.Hdr.Rrtype = dns.TypeOPT
	e := new(dns.EDNS0_SUBNET)
	e.Code = dns.EDNS0SUBNET
	e.SourceScope = 0
	e.Address = net.ParseIP(ip)
	e.Family = 1 // IP4
	e.SourceNetmask = net.IPv4len * 8
	o.Option = append(o.Option, e)
	msg.Extra = append(msg.Extra, o)

	c.Log("msg", msg)

	return dorequest(c, msg)
}

func exchange(c *C, name string, dnstype uint16) *dns.Msg {
	msg := new(dns.Msg)

	msg.SetQuestion(name, dnstype)
	return dorequest(c, msg)
}

func dorequest(c *C, msg *dns.Msg) *dns.Msg {
	cli := new(dns.Client)
	r, _, err := cli.Exchange(msg, "127.0.0.1"+PORT)
	if err != nil {
		c.Log("err", err)
		c.Fail()
	}
	return r
}
<commit_msg>Add test for mIXed-caSE queries<commit_after>package main

import (
	"github.com/abh/dns"
	. "launchpad.net/gocheck"
	"net"
	"strings"
	"time"
)

const (
	PORT = ":8853"
)

type ServeSuite struct {
}

var _ = Suite(&ServeSuite{})

func (s *ServeSuite) SetUpSuite(c *C) {

	// log.Println("Setting up serve test suite")

	Zones := make(Zones)
	setupPgeodnsZone(Zones)
	zonesReadDir("dns", Zones)

	go listenAndServe(PORT)

	time.Sleep(200 * time.Millisecond)
}

func (s *ServeSuite) TestServing(c *C) {

	r := exchange(c, "_status.pgeodns.", dns.TypeTXT)
	txt := r.Answer[0].(*dns.TXT).Txt[0]
	if !strings.HasPrefix(txt, "{") {
		c.Log("Unexpected result for _status.pgeodns", txt)
		c.Fail()
	}

	// Allow _country and _status queries as long as the first label is that
	r = exchange(c, "_country.foo.pgeodns.", dns.TypeTXT)
	txt = r.Answer[0].(*dns.TXT).Txt[0]
	// Got appropriate response for _country txt query
	if !strings.HasPrefix(txt, "127.0.0.1:") {
		c.Log("Unexpected result for _country.foo.pgeodns", txt)
		c.Fail()
	}

	// Make sure A requests for _status doesn't NXDOMAIN
	r = exchange(c, "_status.pgeodns.", dns.TypeA)
	c.Check(r.Answer, HasLen, 0)
	// Got one SOA record
	c.Check(r.Ns, HasLen, 1)
	// NOERROR for A request
	c.Check(r.Rcode, Equals, dns.RcodeSuccess)

	r = exchange(c, "bar.test.example.com.", dns.TypeA)
	ip := r.Answer[0].(*dns.A).A
	c.Check(ip.String(), Equals, "192.168.1.2")

	r = exchange(c, "test.example.com.", dns.TypeSOA)
	soa := r.Answer[0].(*dns.SOA)
	serial := soa.Serial
	c.Check(int(serial), Equals, 3)

	// no AAAA records for 'bar', so check we get a soa record back
	r = exchange(c, "test.example.com.", dns.TypeAAAA)
	soa2 := r.Ns[0].(*dns.SOA)
	c.Check(soa, DeepEquals, soa2)

	// CNAMEs
	r = exchange(c, "www.test.example.com.", dns.TypeA)
	c.Check(r.Answer[0].(*dns.CNAME).Target, Equals, "geo.bitnames.com.")

	// MX
	r = exchange(c, "test.example.com.", dns.TypeMX)
	c.Check(r.Answer[0].(*dns.MX).Mx, Equals, "mx.example.net.")
	c.Check(r.Answer[1].(*dns.MX).Mx, Equals, "mx2.example.net.")
	c.Check(r.Answer[1].(*dns.MX).Preference, Equals, uint16(20))
}

func (s *ServeSuite) TestServingMixedCase(c *C) {

	r := exchange(c, "_sTaTUs.pGEOdns.", dns.TypeTXT)
	c.Assert(r.Rcode, Equals, dns.RcodeSuccess)
	txt := r.Answer[0].(*dns.TXT).Txt[0]
	if !strings.HasPrefix(txt, "{") {
		c.Log("Unexpected result for _status.pgeodns", txt)
		c.Fail()
	}

	r = exchange(c, "baR.test.eXAmPLe.cOM.", dns.TypeA)
	ip := r.Answer[0].(*dns.A).A
	c.Check(ip.String(), Equals, "192.168.1.2")

}

func (s *ServeSuite) TestServingAliases(c *C) {
	// Alias, no geo matches
	r := exchange(c, "bar-alias.test.example.com.", dns.TypeA)
	ip := r.Answer[0].(*dns.A).A
	c.Check(ip.String(), Equals, "192.168.1.2")

	// Alias to a cname record
	r = exchange(c, "www-alias.test.example.com.", dns.TypeA)
	c.Check(r.Answer[0].(*dns.CNAME).Target, Equals, "geo.bitnames.com.")

	// Alias returning a cname, with geo overrides
	r = exchangeSubnet(c, "www-alias.test.example.com.", dns.TypeA, "194.239.134.1")
	c.Check(r.Answer, HasLen, 1)
	if len(r.Answer) > 0 {
		c.Check(r.Answer[0].(*dns.CNAME).Target, Equals, "geo-europe.bitnames.com.")
	}

	// Alias to Ns records
	r = exchange(c, "sub-alias.test.example.org.", dns.TypeNS)
	c.Check(r.Answer[0].(*dns.NS).Ns, Equals, "ns1.example.com.")

}

func (s *ServeSuite) TestServingEDNS(c *C) {
	// MX test
	r := exchangeSubnet(c, "test.example.com.", dns.TypeMX, "194.239.134.1")
	c.Check(r.Answer, HasLen, 1)
	if len(r.Answer) > 0 {
		c.Check(r.Answer[0].(*dns.MX).Mx, Equals, "mx-eu.example.net.")
	}

	c.Log("Testing www.test.example.com from .dk, should match www.europe (a cname)")

	r = exchangeSubnet(c, "www.test.example.com.", dns.TypeA, "194.239.134.0")
	c.Check(r.Answer, HasLen, 1)
	if len(r.Answer) > 0 {
		// EDNS-SUBNET test (request A, respond CNAME)
		c.Check(r.Answer[0].(*dns.CNAME).Target, Equals, "geo-europe.bitnames.com.")
	}

}

func exchangeSubnet(c *C, name string, dnstype uint16, ip string) *dns.Msg {
	msg := new(dns.Msg)

	msg.SetQuestion(name, dnstype)

	o := new(dns.OPT)
	o.Hdr.Name = "."
	o.Hdr.Rrtype = dns.TypeOPT
	e := new(dns.EDNS0_SUBNET)
	e.Code = dns.EDNS0SUBNET
	e.SourceScope = 0
	e.Address = net.ParseIP(ip)
	e.Family = 1 // IP4
	e.SourceNetmask = net.IPv4len * 8
	o.Option = append(o.Option, e)
	msg.Extra = append(msg.Extra, o)

	c.Log("msg", msg)

	return dorequest(c, msg)
}

func exchange(c *C, name string, dnstype uint16) *dns.Msg {
	msg := new(dns.Msg)

	msg.SetQuestion(name, dnstype)
	return dorequest(c, msg)
}

func dorequest(c *C, msg *dns.Msg) *dns.Msg {
	cli := new(dns.Client)
	r, _, err := cli.Exchange(msg, "127.0.0.1"+PORT)
	if err != nil {
		c.Log("err", err)
		c.Fail()
	}
	return r
}
<|endoftext|>


<commit_before>package modules

import (
	"errors"

	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/types"
)

const (
	// TransactionSizeLimit defines the size of the largest transaction that
	// will be accepted by the transaction pool according to the IsStandard
	// rules.
	TransactionSizeLimit = 16e3

	// TransactionSetSizeLimit defines the largest set of dependent unconfirmed
	// transactions that will be accepted by the transaction pool.
	TransactionSetSizeLimit = 250e3
)

var (
	// ErrDuplicateTransactionSet is the error that gets returned if a
	// duplicate transaction set is given to the transaction pool.
	ErrDuplicateTransactionSet = errors.New("transaction set contains only duplicate transactions")

	// ErrLargeTransaction is the error that gets returned if a transaction
	// provided to the transaction pool is larger than what is allowed by the
	// IsStandard rules.
	ErrLargeTransaction = errors.New("transaction is too large for this transaction pool")

	// ErrLargeTransactionSet is the error that gets returned if a transaction
	// set given to the transaction pool is larger than the limit placed by the
	// IsStandard rules of the transaction pool.
	ErrLargeTransactionSet = errors.New("transaction set is too large for this transaction pool")

	// ErrInvalidArbPrefix is the error that gets returned if a transaction is
	// submitted to the transaction pool which contains a prefix that is not
	// recognized. This helps prevent miners on old versions from mining
	// potentially illegal transactions in the event of a soft-fork.
	ErrInvalidArbPrefix = errors.New("transaction contains non-standard arbitrary data")

	// PrefixNonSia defines the prefix that should be appended to any
	// transactions that use the arbitrary data for reasons outside of the
	// standard Sia protocol. This will prevent these transactions from being
	// rejected by the IsStandard set of rules, but also means that the data
	// will never be used within the formal Sia protocol.
	PrefixNonSia = types.Specifier{'N', 'o', 'n', 'S', 'i', 'a'}

	// TransactionPoolDir is the name of the directory that is used to store
	// the transaction pool's persistent data.
	TransactionPoolDir = "transactionpool"
)

// A TransactionPoolSubscriber receives updates about the confirmed and
// unconfirmed set from the transaction pool. Generally, there is no need to
// subscribe to both the consensus set and the transaction pool.
type TransactionPoolSubscriber interface {
	// ReceiveTransactionPoolUpdate notifies subscribers of a change to the
	// consensus set and/or unconfirmed set, and includes the consensus change
	// that would result if all of the transactions made it into a block.
	ReceiveUpdatedUnconfirmedTransactions([]types.Transaction, ConsensusChange)
}

// A TransactionPool manages unconfirmed transactions.
type TransactionPool interface {
	// AcceptTransactionSet accepts a set of potentially interdependent
	// transactions.
	AcceptTransactionSet([]types.Transaction) error

	// FeeEstimation returns an estimation for how high the transaction fee
	// needs to be per byte. The minimum recommended targets getting accepted
	// in ~3 blocks, and the maximum recommended targets getting accepted
	// immediately. Taking the average has a moderate chance of being accepted
	// within one block. The minimum has a strong chance of getting accepted
	// within 10 blocks.
	FeeEstimation() (minimumRecommended, maximumRecommended types.Currency)

	// IsStandardTransaction returns `err = nil` if the transaction is
	// standard, otherwise it returns an error explaining what is not standard.
	IsStandardTransaction(types.Transaction) error

	// PurgeTransactionPool is a temporary function available to the miner. In
	// the event that a miner mines an unacceptable block, the transaction pool
	// will be purged to clear out the transaction pool and get rid of the
	// illegal transaction. This should never happen, however there are bugs
	// that make this condition necessary.
	PurgeTransactionPool()

	// TransactionList returns a list of all transactions in the transaction
	// pool. The transactions are provided in an order that can acceptably be
	// put into a block.
	TransactionList() []types.Transaction

	// TransactionPoolSubscribe adds a subscriber to the transaction pool.
	// Subscribers will receive all consensus set changes as well as
	// transaction pool changes, and should not subscribe to both.
	TransactionPoolSubscribe(TransactionPoolSubscriber)

	// Unsubscribe removes a subscriber from the transaction pool.
	// This is necessary for clean shutdown of the miner.
	Unsubscribe(TransactionPoolSubscriber)
}

// ConsensusConflict implements the error interface, and indicates that a
// transaction was rejected due to being incompatible with the current
// consensus set, meaning either a double spend or a consensus rule violation -
// it is unlikely that the transaction will ever be valid.
type ConsensusConflict string

// NewConsensusConflict returns a consensus conflict, which implements the
// error interface.
func NewConsensusConflict(s string) ConsensusConflict {
	return ConsensusConflict("consensus conflict: " + s)
}

// Error implements the error interface, turning the consensus conflict into an
// acceptable error type.
func (cc ConsensusConflict) Error() string {
	return string(cc)
}

// CalculateFee returns the fee-per-byte of a transaction set.
func CalculateFee(ts []types.Transaction) types.Currency {
	var sum types.Currency
	for _, t := range ts {
		for _, fee := range t.MinerFees {
			sum = sum.Add(fee)
		}
	}
	size := len(encoding.Marshal(ts))
	return sum.Div64(uint64(size))
}
<commit_msg>add 'Close() err' to TransactionPool interface<commit_after>package modules

import (
	"errors"
	"io"

	"github.com/NebulousLabs/Sia/encoding"
	"github.com/NebulousLabs/Sia/types"
)

const (
	// TransactionSizeLimit defines the size of the largest transaction that
	// will be accepted by the transaction pool according to the IsStandard
	// rules.
	TransactionSizeLimit = 16e3

	// TransactionSetSizeLimit defines the largest set of dependent unconfirmed
	// transactions that will be accepted by the transaction pool.
	TransactionSetSizeLimit = 250e3
)

var (
	// ErrDuplicateTransactionSet is the error that gets returned if a
	// duplicate transaction set is given to the transaction pool.
	ErrDuplicateTransactionSet = errors.New("transaction set contains only duplicate transactions")

	// ErrLargeTransaction is the error that gets returned if a transaction
	// provided to the transaction pool is larger than what is allowed by the
	// IsStandard rules.
	ErrLargeTransaction = errors.New("transaction is too large for this transaction pool")

	// ErrLargeTransactionSet is the error that gets returned if a transaction
	// set given to the transaction pool is larger than the limit placed by the
	// IsStandard rules of the transaction pool.
	ErrLargeTransactionSet = errors.New("transaction set is too large for this transaction pool")

	// ErrInvalidArbPrefix is the error that gets returned if a transaction is
	// submitted to the transaction pool which contains a prefix that is not
	// recognized. This helps prevent miners on old versions from mining
	// potentially illegal transactions in the event of a soft-fork.
	ErrInvalidArbPrefix = errors.New("transaction contains non-standard arbitrary data")

	// PrefixNonSia defines the prefix that should be appended to any
	// transactions that use the arbitrary data for reasons outside of the
	// standard Sia protocol. This will prevent these transactions from being
	// rejected by the IsStandard set of rules, but also means that the data
	// will never be used within the formal Sia protocol.
	PrefixNonSia = types.Specifier{'N', 'o', 'n', 'S', 'i', 'a'}

	// TransactionPoolDir is the name of the directory that is used to store
	// the transaction pool's persistent data.
	TransactionPoolDir = "transactionpool"
)

// A TransactionPoolSubscriber receives updates about the confirmed and
// unconfirmed set from the transaction pool. Generally, there is no need to
// subscribe to both the consensus set and the transaction pool.
type TransactionPoolSubscriber interface {
	// ReceiveTransactionPoolUpdate notifies subscribers of a change to the
	// consensus set and/or unconfirmed set, and includes the consensus change
	// that would result if all of the transactions made it into a block.
	ReceiveUpdatedUnconfirmedTransactions([]types.Transaction, ConsensusChange)
}

// A TransactionPool manages unconfirmed transactions.
type TransactionPool interface {
	// AcceptTransactionSet accepts a set of potentially interdependent
	// transactions.
	AcceptTransactionSet([]types.Transaction) error

	// Close is necessary for clean shutdown (e.g. during testing).
	Close() error

	// FeeEstimation returns an estimation for how high the transaction fee
	// needs to be per byte. The minimum recommended targets getting accepted
	// in ~3 blocks, and the maximum recommended targets getting accepted
	// immediately. Taking the average has a moderate chance of being accepted
	// within one block. The minimum has a strong chance of getting accepted
	// within 10 blocks.
	FeeEstimation() (minimumRecommended, maximumRecommended types.Currency)

	// IsStandardTransaction returns `err = nil` if the transaction is
	// standard, otherwise it returns an error explaining what is not standard.
	IsStandardTransaction(types.Transaction) error

	// PurgeTransactionPool is a temporary function available to the miner. In
	// the event that a miner mines an unacceptable block, the transaction pool
	// will be purged to clear out the transaction pool and get rid of the
	// illegal transaction. This should never happen, however there are bugs
	// that make this condition necessary.
	PurgeTransactionPool()

	// TransactionList returns a list of all transactions in the transaction
	// pool. The transactions are provided in an order that can acceptably be
	// put into a block.
	TransactionList() []types.Transaction

	// TransactionPoolSubscribe adds a subscriber to the transaction pool.
	// Subscribers will receive all consensus set changes as well as
	// transaction pool changes, and should not subscribe to both.
	TransactionPoolSubscribe(TransactionPoolSubscriber)

	// Unsubscribe removes a subscriber from the transaction pool.
	// This is necessary for clean shutdown of the miner.
	Unsubscribe(TransactionPoolSubscriber)
}

// ConsensusConflict implements the error interface, and indicates that a
// transaction was rejected due to being incompatible with the current
// consensus set, meaning either a double spend or a consensus rule violation -
// it is unlikely that the transaction will ever be valid.
type ConsensusConflict string

// NewConsensusConflict returns a consensus conflict, which implements the
// error interface.
func NewConsensusConflict(s string) ConsensusConflict {
	return ConsensusConflict("consensus conflict: " + s)
}

// Error implements the error interface, turning the consensus conflict into an
// acceptable error type.
func (cc ConsensusConflict) Error() string {
	return string(cc)
}

// CalculateFee returns the fee-per-byte of a transaction set.
func CalculateFee(ts []types.Transaction) types.Currency {
	var sum types.Currency
	for _, t := range ts {
		for _, fee := range t.MinerFees {
			sum = sum.Add(fee)
		}
	}
	size := len(encoding.Marshal(ts))
	return sum.Div64(uint64(size))
}
<|endoftext|>


<commit_before>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

// plot provides an API for building and drawing plots in Go.
package plot

import (
	"code.google.com/p/plotinum/vg"
	"code.google.com/p/plotinum/vg/veceps"
	"code.google.com/p/plotinum/vg/vecimg"
	"code.google.com/p/plotinum/vg/vecsvg"
	"fmt"
	"image/color"
	"math"
	"path/filepath"
	"strings"
)

const (
	defaultFont = "Times-Roman"
)

// Plot is the basic type representing a plot.
type Plot struct {
	Title struct {
		// Text is the text of the plot title.  If
		// Text is the empty string then the plot
		// will not have a title.
		Text string
		TextStyle
	}

	// BackgroundColor is the background color of the plot.
	// If BackgroundColor is nil, then no background color
	// is explicitly drawn.
	BackgroundColor color.Color

	// X and Y are the horizontal and vertical axes
	// of the plot respectively.
	X, Y Axis

	// Legend is the plot's legend.
	Legend Legend

	// plotters are drawn by calling their Plot method
	// after the axes are drawn.
	plotters []Plotter
}

// Plotter is an interface that wraps the Plot method.
type Plotter interface {
	// Plot draws the data to a DrawArea.
	Plot(DrawArea, *Plot)
}

// DataRanger wraps the DataRange method.
type DataRanger interface {
	// DataRange returns the range of X and Y values.
	DataRange() (xmin, xmax, ymin, ymax float64)
}

// New returns a new plot with some reasonable
// default settings.
func New() (*Plot, error) {
	titleFont, err := vg.MakeFont(defaultFont, 12)
	if err != nil {
		return nil, err
	}
	x, err := makeAxis()
	if err != nil {
		return nil, err
	}
	y, err := makeAxis()
	if err != nil {
		return nil, err
	}
	legend, err := makeLegend()
	if err != nil {
		return nil, err
	}
	p := &Plot{
		BackgroundColor: color.White,
		X:               x,
		Y:               y,
		Legend:          legend,
	}
	p.Title.TextStyle = TextStyle{
		Color: color.Black,
		Font:  titleFont,
	}
	return p, nil
}

// Add adds a Plotters to the plot.  If the plotters
// implements DataRanger then the minimum
// and maximum values of the X and Y axes are
// changed if necessary to fit the range of
// the data.
func (p *Plot) Add(ps ...Plotter) {
	for _, d := range ps {
		if x, ok := d.(DataRanger); ok {
			xmin, xmax, ymin, ymax := x.DataRange()
			p.X.Min = math.Min(p.X.Min, xmin)
			p.X.Max = math.Max(p.X.Max, xmax)
			p.Y.Min = math.Min(p.Y.Min, ymin)
			p.Y.Max = math.Max(p.Y.Max, ymax)
		}
	}

	p.plotters = append(p.plotters, ps...)
}

// Draw draws a plot to a DrawArea.
func (p *Plot) Draw(da *DrawArea) {
	if p.BackgroundColor != nil {
		da.SetColor(p.BackgroundColor)
		da.Fill(rectPath(da.Rect))
	}
	if p.Title.Text != "" {
		da.FillText(p.Title.TextStyle, da.Center().X, da.Max().Y, -0.5, -1, p.Title.Text)
		da.Size.Y -= p.Title.Height(p.Title.Text) - p.Title.Font.Extents().Descent
	}

	if p.X.Min == p.X.Max {
		p.X.Min -= 1
		p.X.Max += 1
	}
	x := horizontalAxis{p.X}

	if p.Y.Min == p.Y.Max {
		p.Y.Min -= 1
		p.Y.Max += 1
	}
	y := verticalAxis{p.Y}

	ywidth := y.size()
	x.draw(padX(p, da.crop(ywidth, 0, 0, 0)))
	xheight := x.size()
	y.draw(padY(p, da.crop(0, xheight, 0, 0)))

	da = padY(p, padX(p, da.crop(ywidth, xheight, 0, 0)))
	for _, data := range p.plotters {
		data.Plot(*da, p)
	}
	p.Legend.draw(da)
}

// DrawGlyphBoxes draws red outlines around the plot's
// GlyphBoxes.  This is intended for debugging.
func (p *Plot) DrawGlyphBoxes(da *DrawArea) {
	da.SetColor(color.RGBA{R: 255, A: 255})
	for _, b := range p.GlyphBoxes(p) {
		b.Rect.Min.X += da.X(b.X)
		b.Rect.Min.Y += da.Y(b.Y)
		da.Stroke(rectPath(b.Rect))
	}
}

// padX returns a new DrawArea that is padded horizontally
// so that glyphs will no be clipped.
func padX(p *Plot, da *DrawArea) *DrawArea {
	glyphs := p.GlyphBoxes(p)
	l := leftMost(da, glyphs)
	xAxis := horizontalAxis{p.X}
	glyphs = append(glyphs, xAxis.GlyphBoxes(p)...)
	r := rightMost(da, glyphs)

	minx := da.Min.X - l.Min.X
	maxx := da.Max().X - (r.Min.X + r.Size.X)
	lx := vg.Length(l.X)
	rx := vg.Length(r.X)
	n := (lx*maxx - rx*minx) / (lx - rx)
	m := ((lx-1)*maxx - rx*minx + minx) / (lx - rx)
	return &DrawArea{
		vg.Canvas: vg.Canvas(da),
		Rect: Rect{
			Min:  Point{X: n, Y: da.Min.Y},
			Size: Point{X: m - n, Y: da.Size.Y},
		},
	}
}

// rightMost returns the right-most GlyphBox.
func rightMost(da *DrawArea, boxes []GlyphBox) GlyphBox {
	maxx := da.Max().X
	r := GlyphBox{X: 1}
	for _, b := range boxes {
		if x := da.X(b.X) + b.Min.X + b.Size.X; x > maxx && b.X <= 1 {
			maxx = x
			r = b
		}
	}
	return r
}

// leftMost returns the left-most GlyphBox.
func leftMost(da *DrawArea, boxes []GlyphBox) GlyphBox {
	minx := da.Min.X
	l := GlyphBox{}
	for _, b := range boxes {
		if x := da.X(b.X) + b.Min.X; x < minx && b.X >= 0 {
			minx = x
			l = b
		}
	}
	return l
}

// padY returns a new DrawArea that is padded vertically
// so that glyphs will no be clipped.
func padY(p *Plot, da *DrawArea) *DrawArea {
	glyphs := p.GlyphBoxes(p)
	b := bottomMost(da, glyphs)
	yAxis := verticalAxis{p.Y}
	glyphs = append(glyphs, yAxis.GlyphBoxes(p)...)
	t := topMost(da, glyphs)

	miny := da.Min.Y - b.Min.Y
	maxy := da.Max().Y - (t.Min.Y + t.Size.Y)
	by := vg.Length(b.Y)
	ty := vg.Length(t.Y)
	n := (by*maxy - ty*miny) / (by - ty)
	m := ((by-1)*maxy - ty*miny + miny) / (by - ty)
	return &DrawArea{
		vg.Canvas: vg.Canvas(da),
		Rect: Rect{
			Min:  Point{Y: n, X: da.Min.X},
			Size: Point{Y: m - n, X: da.Size.X},
		},
	}
}

// topMost returns the top-most GlyphBox.
func topMost(da *DrawArea, boxes []GlyphBox) GlyphBox {
	maxy := da.Max().Y
	t := GlyphBox{Y: 1}
	for _, b := range boxes {
		if y := da.Y(b.Y) + b.Min.Y + b.Size.Y; y > maxy && b.Y <= 1 {
			maxy = y
			t = b
		}
	}
	return t
}

// bottomMost returns the bottom-most GlyphBox.
func bottomMost(da *DrawArea, boxes []GlyphBox) GlyphBox {
	miny := da.Min.Y
	l := GlyphBox{}
	for _, b := range boxes {
		if y := da.Y(b.Y) + b.Min.Y; y < miny && b.Y >= 0 {
			miny = y
			l = b
		}
	}
	return l
}

// Transforms returns functions to transfrom
// from the x and y data coordinate system to
// the draw coordinate system of the given
// draw area.
func (p *Plot) Transforms(da *DrawArea) (x, y func(float64) vg.Length) {
	x = func(x float64) vg.Length { return da.X(p.X.Norm(x)) }
	y = func(y float64) vg.Length { return da.Y(p.Y.Norm(y)) }
	return
}

// GlyphBoxer wraps the GlyphBoxes method.
// It should be implemented by things that meet
// the Plotter interface that draw glyphs so that
// their glyphs are not clipped if drawn near the
// edge of the DrawArea.
type GlyphBoxer interface {
	GlyphBoxes(*Plot) []GlyphBox
}

// A GlyphBox describes the location of a glyph
// and the offset/size of its bounding box.
type GlyphBox struct {
	// The glyph location in normalized coordinates.
	X, Y float64

	// Rect is the offset of the glyph's minimum drawing
	// point relative to the glyph location and its size.
	Rect
}

// GlyphBoxes returns the GlyphBoxes for all plot
// data that meet the GlyphBoxer interface.
func (p *Plot) GlyphBoxes(*Plot) (boxes []GlyphBox) {
	for _, d := range p.plotters {
		if gb, ok := d.(GlyphBoxer); ok {
			boxes = append(boxes, gb.GlyphBoxes(p)...)
		}
	}
	return
}

// NominalX configures the plot to have a nominal X
// axis—an X axis with names instead of numbers.  The
// X location corresponding to each name are the integers,
// e.g., the x value 0 is centered above the first name and
// 1 is above the second name, etc.
func (p *Plot) NominalX(names ...string) {
	p.X.Tick.Width = 0
	p.X.Tick.Length = 0
	p.X.Width = 0
	p.Y.Padding = p.X.Tick.Label.Width(names[0]) / 2
	ticks := make([]Tick, len(names))
	for i, name := range names {
		ticks[i] = Tick{float64(i), name}
	}
	p.X.Tick.Marker = ConstantTicks(ticks)
}

// NominalY configures the plot to have a nominal Y
// axis—an Y axis with names instead of numbers.  The
// Y location corresponding to each name are the integers,
// e.g., the y value 0 is centered above the first name and
// 1 is above the second name, etc.
func (p *Plot) NominalY(names ...string) {
	p.Y.Tick.Width = 0
	p.Y.Tick.Length = 0
	p.Y.Width = 0
	p.X.Padding = p.Y.Tick.Label.Height(names[0]) / 2
	ticks := make([]Tick, len(names))
	for i, name := range names {
		ticks[i] = Tick{float64(i), name}
	}
	p.Y.Tick.Marker = ConstantTicks(ticks)
}

// Save saves the plot to an image file.  Width and height
// are specified in inches, and the file format is determined
// by the extension. Currently supproted extensions are
// .png and .eps.
func (p *Plot) Save(width, height float64, file string) (err error) {
	w, h := vg.Inches(width), vg.Inches(height)
	var c vg.Canvas
	switch ext := strings.ToLower(filepath.Ext(file)); ext {
	case ".eps":
		c = veceps.New(w, h, file)
		defer c.(*veceps.Canvas).Save(file)
	case ".png":
		c, err = vecimg.New(w, h)
		if err != nil {
			return
		}
		defer func() { err = c.(*vecimg.Canvas).SavePNG(file) }()
	case ".svg":
		c = vecsvg.New(w, h)
		defer func() { err = c.(*vecsvg.Canvas).Save(file) }()
	default:
		return fmt.Errorf("Unsupported file extension: %s", ext)
	}
	p.Draw(NewDrawArea(c, w, h))
	return
}
<commit_msg>Automated merge with https://code.google.com/p/plotinum<commit_after>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

// plot provides an API for building and drawing plots in Go.
package plot

import (
	"code.google.com/p/plotinum/vg"
	"code.google.com/p/plotinum/vg/veceps"
	"code.google.com/p/plotinum/vg/vecimg"
	"code.google.com/p/plotinum/vg/vecsvg"
	"fmt"
	"image/color"
	"math"
	"path/filepath"
	"strings"
)

const (
	defaultFont = "Times-Roman"
)

// Plot is the basic type representing a plot.
type Plot struct {
	Title struct {
		// Text is the text of the plot title.  If
		// Text is the empty string then the plot
		// will not have a title.
		Text string
		TextStyle
	}

	// BackgroundColor is the background color of the plot.
	// If BackgroundColor is nil, then no background color
	// is explicitly drawn.
	BackgroundColor color.Color

	// X and Y are the horizontal and vertical axes
	// of the plot respectively.
	X, Y Axis

	// Legend is the plot's legend.
	Legend Legend

	// plotters are drawn by calling their Plot method
	// after the axes are drawn.
	plotters []Plotter
}

// Plotter is an interface that wraps the Plot method.
type Plotter interface {
	// Plot draws the data to a DrawArea.
	Plot(DrawArea, *Plot)
}

// DataRanger wraps the DataRange method.
type DataRanger interface {
	// DataRange returns the range of X and Y values.
	DataRange() (xmin, xmax, ymin, ymax float64)
}

// New returns a new plot with some reasonable
// default settings.
func New() (*Plot, error) {
	titleFont, err := vg.MakeFont(defaultFont, 12)
	if err != nil {
		return nil, err
	}
	x, err := makeAxis()
	if err != nil {
		return nil, err
	}
	y, err := makeAxis()
	if err != nil {
		return nil, err
	}
	legend, err := makeLegend()
	if err != nil {
		return nil, err
	}
	p := &Plot{
		BackgroundColor: color.White,
		X:               x,
		Y:               y,
		Legend:          legend,
	}
	p.Title.TextStyle = TextStyle{
		Color: color.Black,
		Font:  titleFont,
	}
	return p, nil
}

// Add adds a Plotters to the plot.  If the plotters
// implements DataRanger then the minimum
// and maximum values of the X and Y axes are
// changed if necessary to fit the range of
// the data.
func (p *Plot) Add(ps ...Plotter) {
	for _, d := range ps {
		if x, ok := d.(DataRanger); ok {
			xmin, xmax, ymin, ymax := x.DataRange()
			p.X.Min = math.Min(p.X.Min, xmin)
			p.X.Max = math.Max(p.X.Max, xmax)
			p.Y.Min = math.Min(p.Y.Min, ymin)
			p.Y.Max = math.Max(p.Y.Max, ymax)
		}
	}

	p.plotters = append(p.plotters, ps...)
}

// Draw draws a plot to a DrawArea.
func (p *Plot) Draw(da *DrawArea) {
	if p.BackgroundColor != nil {
		da.SetColor(p.BackgroundColor)
		da.Fill(rectPath(da.Rect))
	}
	if p.Title.Text != "" {
		da.FillText(p.Title.TextStyle, da.Center().X, da.Max().Y, -0.5, -1, p.Title.Text)
		da.Size.Y -= p.Title.Height(p.Title.Text) - p.Title.Font.Extents().Descent
	}

	if p.X.Min == p.X.Max {
		p.X.Min -= 1
		p.X.Max += 1
	}
	x := horizontalAxis{p.X}

	if p.Y.Min == p.Y.Max {
		p.Y.Min -= 1
		p.Y.Max += 1
	}
	y := verticalAxis{p.Y}

	ywidth := y.size()
	x.draw(padX(p, da.crop(ywidth, 0, 0, 0)))
	xheight := x.size()
	y.draw(padY(p, da.crop(0, xheight, 0, 0)))

	da = padY(p, padX(p, da.crop(ywidth, xheight, 0, 0)))
	for _, data := range p.plotters {
		data.Plot(*da, p)
	}
	p.Legend.draw(da)
}

// DrawGlyphBoxes draws red outlines around the plot's
// GlyphBoxes.  This is intended for debugging.
func (p *Plot) DrawGlyphBoxes(da *DrawArea) {
	da.SetColor(color.RGBA{R: 255, A: 255})
	for _, b := range p.GlyphBoxes(p) {
		b.Rect.Min.X += da.X(b.X)
		b.Rect.Min.Y += da.Y(b.Y)
		da.Stroke(rectPath(b.Rect))
	}
}

// padX returns a new DrawArea that is padded horizontally
// so that glyphs will no be clipped.
func padX(p *Plot, da *DrawArea) *DrawArea {
	glyphs := p.GlyphBoxes(p)
	l := leftMost(da, glyphs)
	xAxis := horizontalAxis{p.X}
	glyphs = append(glyphs, xAxis.GlyphBoxes(p)...)
	r := rightMost(da, glyphs)

	minx := da.Min.X - l.Min.X
	maxx := da.Max().X - (r.Min.X + r.Size.X)
	lx := vg.Length(l.X)
	rx := vg.Length(r.X)
	n := (lx*maxx - rx*minx) / (lx - rx)
	m := ((lx-1)*maxx - rx*minx + minx) / (lx - rx)
	return &DrawArea{
		vg.Canvas: vg.Canvas(da),
		Rect: Rect{
			Min:  Point{X: n, Y: da.Min.Y},
			Size: Point{X: m - n, Y: da.Size.Y},
		},
	}
}

// rightMost returns the right-most GlyphBox.
func rightMost(da *DrawArea, boxes []GlyphBox) GlyphBox {
	maxx := da.Max().X
	r := GlyphBox{X: 1}
	for _, b := range boxes {
		if x := da.X(b.X) + b.Min.X + b.Size.X; x > maxx && b.X <= 1 {
			maxx = x
			r = b
		}
	}
	return r
}

// leftMost returns the left-most GlyphBox.
func leftMost(da *DrawArea, boxes []GlyphBox) GlyphBox {
	minx := da.Min.X
	l := GlyphBox{}
	for _, b := range boxes {
		if x := da.X(b.X) + b.Min.X; x < minx && b.X >= 0 {
			minx = x
			l = b
		}
	}
	return l
}

// padY returns a new DrawArea that is padded vertically
// so that glyphs will no be clipped.
func padY(p *Plot, da *DrawArea) *DrawArea {
	glyphs := p.GlyphBoxes(p)
	b := bottomMost(da, glyphs)
	yAxis := verticalAxis{p.Y}
	glyphs = append(glyphs, yAxis.GlyphBoxes(p)...)
	t := topMost(da, glyphs)

	miny := da.Min.Y - b.Min.Y
	maxy := da.Max().Y - (t.Min.Y + t.Size.Y)
	by := vg.Length(b.Y)
	ty := vg.Length(t.Y)
	n := (by*maxy - ty*miny) / (by - ty)
	m := ((by-1)*maxy - ty*miny + miny) / (by - ty)
	return &DrawArea{
		vg.Canvas: vg.Canvas(da),
		Rect: Rect{
			Min:  Point{Y: n, X: da.Min.X},
			Size: Point{Y: m - n, X: da.Size.X},
		},
	}
}

// topMost returns the top-most GlyphBox.
func topMost(da *DrawArea, boxes []GlyphBox) GlyphBox {
	maxy := da.Max().Y
	t := GlyphBox{Y: 1}
	for _, b := range boxes {
		if y := da.Y(b.Y) + b.Min.Y + b.Size.Y; y > maxy && b.Y <= 1 {
			maxy = y
			t = b
		}
	}
	return t
}

// bottomMost returns the bottom-most GlyphBox.
func bottomMost(da *DrawArea, boxes []GlyphBox) GlyphBox {
	miny := da.Min.Y
	l := GlyphBox{}
	for _, b := range boxes {
		if y := da.Y(b.Y) + b.Min.Y; y < miny && b.Y >= 0 {
			miny = y
			l = b
		}
	}
	return l
}

// Transforms returns functions to transfrom
// from the x and y data coordinate system to
// the draw coordinate system of the given
// draw area.
func (p *Plot) Transforms(da *DrawArea) (x, y func(float64) vg.Length) {
	x = func(x float64) vg.Length { return da.X(p.X.Norm(x)) }
	y = func(y float64) vg.Length { return da.Y(p.Y.Norm(y)) }
	return
}

// GlyphBoxer wraps the GlyphBoxes method.
// It should be implemented by things that meet
// the Plotter interface that draw glyphs so that
// their glyphs are not clipped if drawn near the
// edge of the DrawArea.
type GlyphBoxer interface {
	GlyphBoxes(*Plot) []GlyphBox
}

// A GlyphBox describes the location of a glyph
// and the offset/size of its bounding box.
type GlyphBox struct {
	// The glyph location in normalized coordinates.
	X, Y float64

	// Rect is the offset of the glyph's minimum drawing
	// point relative to the glyph location and its size.
	Rect
}

// GlyphBoxes returns the GlyphBoxes for all plot
// data that meet the GlyphBoxer interface.
func (p *Plot) GlyphBoxes(*Plot) (boxes []GlyphBox) {
	for _, d := range p.plotters {
		if gb, ok := d.(GlyphBoxer); ok {
			boxes = append(boxes, gb.GlyphBoxes(p)...)
		}
	}
	return
}

// NominalX configures the plot to have a nominal X
// axis—an X axis with names instead of numbers.  The
// X location corresponding to each name are the integers,
// e.g., the x value 0 is centered above the first name and
// 1 is above the second name, etc.
func (p *Plot) NominalX(names ...string) {
	p.X.Tick.Width = 0
	p.X.Tick.Length = 0
	p.X.Width = 0
	p.Y.Padding = p.X.Tick.Label.Width(names[0]) / 2
	ticks := make([]Tick, len(names))
	for i, name := range names {
		ticks[i] = Tick{float64(i), name}
	}
	p.X.Tick.Marker = ConstantTicks(ticks)
}

// NominalY configures the plot to have a nominal Y
// axis—an Y axis with names instead of numbers.  The
// Y location corresponding to each name are the integers,
// e.g., the y value 0 is centered above the first name and
// 1 is above the second name, etc.
func (p *Plot) NominalY(names ...string) {
	p.Y.Tick.Width = 0
	p.Y.Tick.Length = 0
	p.Y.Width = 0
	p.X.Padding = p.Y.Tick.Label.Height(names[0]) / 2
	ticks := make([]Tick, len(names))
	for i, name := range names {
		ticks[i] = Tick{float64(i), name}
	}
	p.Y.Tick.Marker = ConstantTicks(ticks)
}

// Save saves the plot to an image file.  Width and height
// are specified in inches, and the file format is determined
// by the extension. Currently supproted extensions are
// .png, .eps, and .svg.
func (p *Plot) Save(width, height float64, file string) (err error) {
	w, h := vg.Inches(width), vg.Inches(height)
	var c vg.Canvas
	switch ext := strings.ToLower(filepath.Ext(file)); ext {
	case ".eps":
		c = veceps.New(w, h, file)
		defer c.(*veceps.Canvas).Save(file)
	case ".png":
		c, err = vecimg.New(w, h)
		if err != nil {
			return
		}
		defer func() { err = c.(*vecimg.Canvas).SavePNG(file) }()
	case ".svg":
		c = vecsvg.New(w, h)
		defer func() { err = c.(*vecsvg.Canvas).Save(file) }()
	default:
		return fmt.Errorf("Unsupported file extension: %s", ext)
	}
	p.Draw(NewDrawArea(c, w, h))
	return
}
<|endoftext|>