<commit_before>package gogadgets

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/cswank/rex"
)

type Server struct {
	host        string
	master      string
	isMaster    bool
	port        int
	prefix      string
	lg          Logger
	external    chan Message
	internal    chan Message
	id          string
	updates     map[string]Message
	seen        map[string]time.Time
	statusLock  sync.Mutex
	seenLock    sync.Mutex
	clientsLock sync.Mutex
	clients     map[string]string
}

func NewServer(host, master string, port int, lg Logger) *Server {
	var isMaster bool
	clients := map[string]string{}
	if master == "" {
		isMaster = true
	} else {
		clients = map[string]string{
			master: "",
		}
	}
	return &Server{
		master:   master,
		host:     host,
		isMaster: isMaster,
		port:     port,
		lg:       lg,
		updates:  map[string]Message{},
		id:       "server",
		external: make(chan Message),
		seen:     map[string]time.Time{},
		clients:  clients,
	}
}

func (s *Server) Start(i <-chan Message, o chan<- Message) {
	if !s.isMaster {
		go s.register()
	}
	go s.startServer()
	go s.cleanup()

	for {
		select {
		case msg := <-i:
			if (msg.Type == UPDATE || msg.Type == METHODUPDATE) && s.isMaster {
				s.statusLock.Lock()
				s.updates[msg.Sender] = msg
				s.statusLock.Unlock()
			}
			if !s.isSeen(msg) {
				s.send(msg)
			}
		case msg := <-s.external:
			s.setSeen(msg)
			o <- msg
			if s.isMaster && msg.Sender == "client" {
				s.send(msg)
			}
		}
	}
}

func (s *Server) send(msg Message) {
	s.clientsLock.Lock()
	for host, token := range s.clients {
		go s.doSend(host, msg, token)
	}
	s.clientsLock.Unlock()
}

func (s *Server) doSend(host string, msg Message, token string) {
	msg.Host = s.host
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.Encode(msg)
	req, err := http.NewRequest("POST", host, &buf)
	if err != nil {
		log.Printf("error posting to quimby host: %s, err: %v\n", host, err)
		s.clientsLock.Lock()
		delete(s.clients, host)
		s.clientsLock.Unlock()
		return
	}
	if len(token) > 0 {
		req.Header.Add("Authorization", "Bearer "+token)
	}
	r, err := http.DefaultClient.Do(req)
	if r != nil {
		r.Body.Close()
	}

	if err != nil || r.StatusCode != http.StatusOK {
		s.clientsLock.Lock()
		delete(s.clients, host)
		s.clientsLock.Unlock()
	}
}

func (s *Server) setSeen(msg Message) {
	s.seenLock.Lock()
	s.seen[msg.UUID] = time.Now()
	s.seenLock.Unlock()
}

func (s *Server) isSeen(msg Message) bool {
	s.seenLock.Lock()
	_, ok := s.seen[msg.UUID]
	s.seenLock.Unlock()
	return ok
}

func (s *Server) cleanup() {
	for {
		time.Sleep(60 * time.Second)
		now := time.Now()
		s.seenLock.Lock()
		for k, v := range s.seen {
			if v.Sub(now) > 10*time.Second {
				delete(s.seen, k)
			}
		}
		s.seenLock.Unlock()
	}
}

func (s *Server) GetUID() string {
	return s.id
}

func (s *Server) GetDirection() string {
	return "na"
}

func (s *Server) startServer() {
	r := rex.New("main")
	r.Get("/gadgets", http.HandlerFunc(s.status))
	r.Get("/gadgets/values", http.HandlerFunc(s.values))
	r.Get("/gadgets/locations/{location}/devices/{device}/status", http.HandlerFunc(s.deviceValue))
	r.Put("/gadgets", http.HandlerFunc(s.update))
	r.Post("/gadgets", http.HandlerFunc(s.update))
	if s.isMaster {
		r.Post("/clients", http.HandlerFunc(s.setClient))
		r.Get("/clients", http.HandlerFunc(s.getClients))
		r.Delete("/clients", http.HandlerFunc(s.removeClient))
	}

	s.lg.Printf("listening on 0.0.0.0:%d\n", s.port)
	err := http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
	if err != nil {
		s.lg.Fatal(err)
	}
}

func (s *Server) getClients(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.clientsLock.Lock()
	if err := enc.Encode(s.clients); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
	}
	s.clientsLock.Unlock()
}

func (s *Server) setClient(w http.ResponseWriter, r *http.Request) {
	var a map[string]string
	dec := json.NewDecoder(r.Body)
	err := dec.Decode(&a)
	if err != nil || len(a["address"]) == 0 || len(a["token"]) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	s.clientsLock.Lock()
	s.clients[a["address"]] = a["token"]
	s.clientsLock.Unlock()
}

func (s *Server) removeClient(w http.ResponseWriter, r *http.Request) {
	s.clientsLock.Lock()
	delete(s.clients, r.URL.Host)
	s.clientsLock.Unlock()
}

func (s *Server) status(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.statusLock.Lock()
	if err := enc.Encode(s.updates); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		lg.Println(err)
	}
	s.statusLock.Unlock()
}

func (s *Server) deviceValue(w http.ResponseWriter, r *http.Request) {
	vars := rex.Vars(r, "main")
	k := fmt.Sprintf("%s %s", vars["location"], vars["device"])
	s.statusLock.Lock()
	m, ok := s.updates[k]
	s.statusLock.Unlock()
	if !ok {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	enc := json.NewEncoder(w)
	if err := enc.Encode(m.Value); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
	}
}

func (s *Server) values(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.statusLock.Lock()
	v := map[string]map[string]Value{}

	for _, msg := range s.updates {
		l, ok := v[msg.Location]
		if !ok {
			l = map[string]Value{}
		}
		l[msg.Name] = msg.Value
		v[msg.Location] = l
	}
	s.statusLock.Unlock()
	if err := enc.Encode(v); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		lg.Println(err)
	}
}

func (s *Server) update(w http.ResponseWriter, r *http.Request) {
	var msg Message
	dec := json.NewDecoder(r.Body)
	if err := dec.Decode(&msg); err != nil {
		lg.Println(err)
		return
	}
	if msg.UUID == "" {
		msg.UUID = GetUUID()
	}
	s.external <- msg
}

//
func (s *Server) register() {
	var tries int
	addr := fmt.Sprintf("%s/clients", s.master)
	a := map[string]string{"address": fmt.Sprintf("%s/gadgets", s.host), "token": "n/a"}
	for {
		buf := &bytes.Buffer{}
		enc := json.NewEncoder(buf)
		enc.Encode(&a)
		r, err := http.Post(addr, "application/json", buf)
		if err == nil && r.StatusCode == http.StatusOK {
			return
		}
		tries = increment(tries)
		time.Sleep(time.Duration(tries) * 100 * time.Millisecond)
	}
}

func increment(i int) int {
	if i == 100 {
		return i
	}
	return i + 1
}
<commit_msg>already locked<commit_after>package gogadgets

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/cswank/rex"
)

type Server struct {
	host        string
	master      string
	isMaster    bool
	port        int
	prefix      string
	lg          Logger
	external    chan Message
	internal    chan Message
	id          string
	updates     map[string]Message
	seen        map[string]time.Time
	statusLock  sync.Mutex
	seenLock    sync.Mutex
	clientsLock sync.Mutex
	clients     map[string]string
}

func NewServer(host, master string, port int, lg Logger) *Server {
	var isMaster bool
	clients := map[string]string{}
	if master == "" {
		isMaster = true
	} else {
		clients = map[string]string{
			master: "",
		}
	}
	return &Server{
		master:   master,
		host:     host,
		isMaster: isMaster,
		port:     port,
		lg:       lg,
		updates:  map[string]Message{},
		id:       "server",
		external: make(chan Message),
		seen:     map[string]time.Time{},
		clients:  clients,
	}
}

func (s *Server) Start(i <-chan Message, o chan<- Message) {
	if !s.isMaster {
		go s.register()
	}
	go s.startServer()
	go s.cleanup()

	for {
		select {
		case msg := <-i:
			if (msg.Type == UPDATE || msg.Type == METHODUPDATE) && s.isMaster {
				s.statusLock.Lock()
				s.updates[msg.Sender] = msg
				s.statusLock.Unlock()
			}
			if !s.isSeen(msg) {
				s.send(msg)
			}
		case msg := <-s.external:
			s.setSeen(msg)
			o <- msg
			if s.isMaster && msg.Sender == "client" {
				s.send(msg)
			}
		}
	}
}

func (s *Server) send(msg Message) {
	s.clientsLock.Lock()
	for host, token := range s.clients {
		go s.doSend(host, msg, token)
	}
	s.clientsLock.Unlock()
}

func (s *Server) doSend(host string, msg Message, token string) {
	msg.Host = s.host
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.Encode(msg)
	req, err := http.NewRequest("POST", host, &buf)
	if err != nil {
		delete(s.clients, host)
		log.Printf("error posting to quimby host: %s, err: %v\n", host, err)
		return
	}
	if len(token) > 0 {
		req.Header.Add("Authorization", "Bearer "+token)
	}
	r, err := http.DefaultClient.Do(req)
	if r != nil {
		r.Body.Close()
	}

	if err != nil || r.StatusCode != http.StatusOK {
		delete(s.clients, host)
	}
}

func (s *Server) setSeen(msg Message) {
	s.seenLock.Lock()
	s.seen[msg.UUID] = time.Now()
	s.seenLock.Unlock()
}

func (s *Server) isSeen(msg Message) bool {
	s.seenLock.Lock()
	_, ok := s.seen[msg.UUID]
	s.seenLock.Unlock()
	return ok
}

func (s *Server) cleanup() {
	for {
		time.Sleep(60 * time.Second)
		now := time.Now()
		s.seenLock.Lock()
		for k, v := range s.seen {
			if v.Sub(now) > 10*time.Second {
				delete(s.seen, k)
			}
		}
		s.seenLock.Unlock()
	}
}

func (s *Server) GetUID() string {
	return s.id
}

func (s *Server) GetDirection() string {
	return "na"
}

func (s *Server) startServer() {
	r := rex.New("main")
	r.Get("/gadgets", http.HandlerFunc(s.status))
	r.Get("/gadgets/values", http.HandlerFunc(s.values))
	r.Get("/gadgets/locations/{location}/devices/{device}/status", http.HandlerFunc(s.deviceValue))
	r.Put("/gadgets", http.HandlerFunc(s.update))
	r.Post("/gadgets", http.HandlerFunc(s.update))
	if s.isMaster {
		r.Post("/clients", http.HandlerFunc(s.setClient))
		r.Get("/clients", http.HandlerFunc(s.getClients))
		r.Delete("/clients", http.HandlerFunc(s.removeClient))
	}

	s.lg.Printf("listening on 0.0.0.0:%d\n", s.port)
	err := http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
	if err != nil {
		s.lg.Fatal(err)
	}
}

func (s *Server) getClients(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.clientsLock.Lock()
	if err := enc.Encode(s.clients); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
	}
	s.clientsLock.Unlock()
}

func (s *Server) setClient(w http.ResponseWriter, r *http.Request) {
	var a map[string]string
	dec := json.NewDecoder(r.Body)
	err := dec.Decode(&a)
	if err != nil || len(a["address"]) == 0 || len(a["token"]) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	s.clientsLock.Lock()
	s.clients[a["address"]] = a["token"]
	s.clientsLock.Unlock()
}

func (s *Server) removeClient(w http.ResponseWriter, r *http.Request) {
	s.clientsLock.Lock()
	delete(s.clients, r.URL.Host)
	s.clientsLock.Unlock()
}

func (s *Server) status(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.statusLock.Lock()
	if err := enc.Encode(s.updates); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		lg.Println(err)
	}
	s.statusLock.Unlock()
}

func (s *Server) deviceValue(w http.ResponseWriter, r *http.Request) {
	vars := rex.Vars(r, "main")
	k := fmt.Sprintf("%s %s", vars["location"], vars["device"])
	s.statusLock.Lock()
	m, ok := s.updates[k]
	s.statusLock.Unlock()
	if !ok {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	enc := json.NewEncoder(w)
	if err := enc.Encode(m.Value); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
	}
}

func (s *Server) values(w http.ResponseWriter, r *http.Request) {
	enc := json.NewEncoder(w)
	s.statusLock.Lock()
	v := map[string]map[string]Value{}

	for _, msg := range s.updates {
		l, ok := v[msg.Location]
		if !ok {
			l = map[string]Value{}
		}
		l[msg.Name] = msg.Value
		v[msg.Location] = l
	}
	s.statusLock.Unlock()
	if err := enc.Encode(v); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		lg.Println(err)
	}
}

func (s *Server) update(w http.ResponseWriter, r *http.Request) {
	var msg Message
	dec := json.NewDecoder(r.Body)
	if err := dec.Decode(&msg); err != nil {
		lg.Println(err)
		return
	}
	if msg.UUID == "" {
		msg.UUID = GetUUID()
	}
	s.external <- msg
}

//
func (s *Server) register() {
	var tries int
	addr := fmt.Sprintf("%s/clients", s.master)
	a := map[string]string{"address": fmt.Sprintf("%s/gadgets", s.host), "token": "n/a"}
	for {
		buf := &bytes.Buffer{}
		enc := json.NewEncoder(buf)
		enc.Encode(&a)
		r, err := http.Post(addr, "application/json", buf)
		if err == nil && r.StatusCode == http.StatusOK {
			return
		}
		tries = increment(tries)
		time.Sleep(time.Duration(tries) * 100 * time.Millisecond)
	}
}

func increment(i int) int {
	if i == 100 {
		return i
	}
	return i + 1
}
<|endoftext|>


<commit_before>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// 一个模块化的微形web框架。
//  m, err := web.NewModule("m1")
//  m.Get("/", ...).
//    Post("/", ...)
//  // 其它模块的初始化工作...
//  web.Run(&Config{}) // 开始监听端口
package web

import (
	"net/http"
	"net/http/pprof"
	"path/filepath"
	"strings"

	"github.com/issue9/context"
	"github.com/issue9/handlers"
	"github.com/issue9/logs"
)

// web包的相关配置内容。
type Config struct {
	HTTPS      bool                 `json:"https"`                // 是否启用https
	CertFile   string               `json:"certFile,omitempty"`   // 当https为true时，此值为必须
	KeyFile    string               `json:"keyFile,omitempty"`    // 当https为true时，此值为必须
	Port       string               `json:"port,omitempty"`       // 端口，不指定，默认为80或是443
	ServerName string               `json:"serverName,omitempty"` // 响应头的server变量，为空时，不输出该内容
	Static     map[string]string    `json:"static,omitempty"`     // 静态路由映身，键名表示路由路径，键值表示文件目录
	Pprof      string               `json:"pprof,omitempty"`      // 指定pprof地址
	ErrHandler handlers.RecoverFunc `json:"-"`                    // 错误处理
}

// 检测cfg的各项字段是否合法，
func (cfg *Config) init() {
	// Port检测
	if len(cfg.Port) == 0 {
		if cfg.HTTPS {
			cfg.Port = ":443"
		} else {
			cfg.Port = ":80"
		}
	} else if cfg.Port[0] != ':' {
		cfg.Port = ":" + cfg.Port
	}

	// 确保每个目录都以/结尾
	for k, v := range cfg.Static {
		last := v[len(v)-1]
		if last != filepath.Separator && last != '/' {
			v += string(filepath.Separator)
		}
		cfg.Static[k] = v
	}
}

// 修改服务器名称
func (cfg *Config) buildServeName(h http.Handler) http.Handler {
	if len(cfg.ServerName) > 0 {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Add("Server", cfg.ServerName)
			h.ServeHTTP(w, r)
		})
	}

	return h
}

// 根据config.Pprof决定是否包装调试地址
func (cfg *Config) buildPprof(h http.Handler) http.Handler {
	if len(cfg.Pprof) > 0 {
		logs.Info("web:", "开启了调试功能，地址为：", cfg.Pprof)

		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if !strings.HasPrefix(r.URL.Path, cfg.Pprof) {
				h.ServeHTTP(w, r)
				return
			}

			path := r.URL.Path[len(cfg.Pprof):]
			switch path {
			case "cmdline":
				pprof.Cmdline(w, r)
			case "profile":
				pprof.Profile(w, r)
			case "symbol":
				pprof.Symbol(w, r)
			case "trace":
				pprof.Trace(w, r)
			default:
				pprof.Index(w, r)
			}
		})
	}

	return h
}

// 初始化web包的内容。
func Run(cfg *Config) {
	cfg.init()

	if len(cfg.Static) > 0 {
		m, err := NewModule("static")
		if err != nil {
			panic(err)
		}

		for url, dir := range cfg.Static {
			m.Get(url, http.StripPrefix(url, http.FileServer(http.Dir(dir))))
		}
	}

	listen(cfg)
}

// 开始监听。
func listen(cfg *Config) {
	h := cfg.buildServeName(serveMux)

	// 作一些清理和错误处理
	h = handlers.Recovery(context.FreeHandler(h), cfg.ErrHandler)

	// 在最外层添加调试地址，保证调试内容不会被其它handler干扰。
	h = cfg.buildPprof(h)

	if cfg.HTTPS {
		http.ListenAndServeTLS(cfg.Port, cfg.CertFile, cfg.KeyFile, h)
	} else {
		http.ListenAndServe(cfg.Port, h)
	}
}
<commit_msg>调试开启提示信息，现在输出到debug通道；去掉static的配置变量，没有通用性<commit_after>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// 一个模块化的微形web框架。
//  m, err := web.NewModule("m1")
//  m.Get("/", ...).
//    Post("/", ...)
//  // 其它模块的初始化工作...
//  web.Run(&Config{}) // 开始监听端口
//
// web依赖logs包作日志输出，请确保已经正确初始化该包。
package web

import (
	"net/http"
	"net/http/pprof"
	"strings"

	"github.com/issue9/context"
	"github.com/issue9/handlers"
	"github.com/issue9/logs"
)

// web包的相关配置内容。
type Config struct {
	HTTPS      bool                 `json:"https"`                // 是否启用https
	CertFile   string               `json:"certFile,omitempty"`   // 当https为true时，此值为必须
	KeyFile    string               `json:"keyFile,omitempty"`    // 当https为true时，此值为必须
	Port       string               `json:"port,omitempty"`       // 端口，不指定，默认为80或是443
	ServerName string               `json:"serverName,omitempty"` // 响应头的server变量，为空时，不输出该内容
	Pprof      string               `json:"pprof,omitempty"`      // 指定pprof地址
	ErrHandler handlers.RecoverFunc `json:"-"`                    // 错误处理
}

// 检测cfg的各项字段是否合法，
func (cfg *Config) init() {
	// Port检测
	if len(cfg.Port) == 0 {
		if cfg.HTTPS {
			cfg.Port = ":443"
		} else {
			cfg.Port = ":80"
		}
	} else if cfg.Port[0] != ':' {
		cfg.Port = ":" + cfg.Port
	}
}

// 修改服务器名称
func (cfg *Config) buildServeName(h http.Handler) http.Handler {
	if len(cfg.ServerName) > 0 {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Add("Server", cfg.ServerName)
			h.ServeHTTP(w, r)
		})
	}

	return h
}

// 根据config.Pprof决定是否包装调试地址
func (cfg *Config) buildPprof(h http.Handler) http.Handler {
	if len(cfg.Pprof) > 0 {
		logs.Debug("web:", "开启了调试功能，地址为：", cfg.Pprof)

		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if !strings.HasPrefix(r.URL.Path, cfg.Pprof) {
				h.ServeHTTP(w, r)
				return
			}

			path := r.URL.Path[len(cfg.Pprof):]
			switch path {
			case "cmdline":
				pprof.Cmdline(w, r)
			case "profile":
				pprof.Profile(w, r)
			case "symbol":
				pprof.Symbol(w, r)
			case "trace":
				pprof.Trace(w, r)
			default:
				pprof.Index(w, r)
			}
		})
	}

	return h
}

// 初始化web包的内容。
func Run(cfg *Config) {
	cfg.init()

	listen(cfg)
}

// 开始监听。
func listen(cfg *Config) {
	h := cfg.buildServeName(serveMux)

	// 作一些清理和错误处理
	h = handlers.Recovery(context.FreeHandler(h), cfg.ErrHandler)

	// 在最外层添加调试地址，保证调试内容不会被其它handler干扰。
	h = cfg.buildPprof(h)

	if cfg.HTTPS {
		http.ListenAndServeTLS(cfg.Port, cfg.CertFile, cfg.KeyFile, h)
	} else {
		http.ListenAndServe(cfg.Port, h)
	}
}
<|endoftext|>


<commit_before>package web

import (
	"net/http"

	"github.com/omeid/gonzo"
	"github.com/omeid/gonzo/context"
)

// Gets  the list of urls and passes the results to output channel.
// It reports the progress to the Context using a ReadProgress proxy.
func Get(ctx context.Context, urls ...string) gonzo.Pipe {

	out := make(chan gonzo.File)
	client := &http.Client{}
	go func() {
		defer close(out)

		for _, url := range urls {

			select {
			case <-ctx.Done():
				ctx.Warn(context.Canceled)
				return
			default:
				ctx.Infof("Downloading %s", url)

				file, err := get(ctx, client, url)
				if err != nil {
					ctx.Error(err)
					break
				}

				//TODO: Add progress meter.
				//s, _ := file.Stat()
				//file.Reader = c.ReadProgress(file.Reader, "Downloading "+file.Path, s.Size())
				out <- file
			}
		}
	}()

	return gonzo.NewPipe(ctx, out)
}
<commit_msg>improve error handling.<commit_after>package web

import (
	"net/http"

	"github.com/omeid/gonzo"
	"github.com/omeid/gonzo/context"
)

// Gets  the list of urls and passes the results to output channel.
// It reports the progress to the Context using a ReadProgress proxy.
func Get(ctx context.Context, urls ...string) gonzo.Pipe {

	ctx, cancel := context.WithCancel(ctx)
	out := make(chan gonzo.File)
	client := &http.Client{}
	go func() {
		defer close(out)

		for _, url := range urls {

			if url == "" {
				ctx.Error("Empty URL.")
				cancel()
				return
			}
			select {
			case <-ctx.Done():
				ctx.Warn(context.Canceled)
				return
			default:
				ctx.Infof("Downloading %s", url)

				file, err := get(ctx, client, url)
				if err != nil {
					ctx.Error(err)
					cancel()
					break
				}

				//TODO: Add progress meter.
				//s, _ := file.Stat()
				//file.Reader = c.ReadProgress(file.Reader, "Downloading "+file.Path, s.Size())
				out <- file
			}
		}
	}()

	return gonzo.NewPipe(ctx, out)
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/hoisie/mustache"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

func authedForHeader(authHeader string) (bool, error) {
	if !strings.HasPrefix(authHeader, "Basic ") {
		return false, nil
	}
	logr.Debugln("Yay, client gave a Basic auth header")

	userpass, err := base64.StdEncoding.DecodeString(authHeader[6:])
	if err != nil {
		logr.Debugln("Oops, error decoding the client's Basic auth header:", err.Error())
		// but report it as Unauthorized, not an error
		return false, nil
	}
	userpassParts := strings.SplitN(string(userpass), ":", 2)
	username, pass := userpassParts[0], userpassParts[1]

	account, err := AccountByName(username)
	if err == sql.ErrNoRows {
		logr.Debugln("No such account %s", username)
		return false, nil
	} else if err != nil {
		return false, err
	}

	return account.HasPassword(pass), nil
}

func IsAuthed(w http.ResponseWriter, r *http.Request) (authed bool) {
	authHeader := r.Header.Get("Authorization")
	authed, err := authedForHeader(authHeader)
	if err != nil {
		logr.Errln("Error checking auth information:", err.Error())
		http.Error(w, "error loading auth information", http.StatusInternalServerError)
	} else if !authed {
		w.Header().Set("WWW-Authenticate", "Basic realm=\"cares\"")
		http.Error(w, "authorization required", http.StatusUnauthorized)
	}
	return
}

func WriteRssForPosts(w http.ResponseWriter, r *http.Request, posts []*Post, titleFormat string) (err error) {
	var host string
	var port string
	if strings.Contains(r.Host, ":") {
		host, port, err = net.SplitHostPort(r.Host)
		if err != nil {
			return
		}
	} else {
		host = r.Host
		// TODO: set port appropriately if we're on HTTPS
		port = "80"
	}

	owner := AccountForOwner()
	firstPost, err := FirstPost()
	if err != nil {
		return
	}

	baseurl, err := url.Parse("/")
	if err != nil {
		return
	}
	baseurl.Host = r.Host // including port if any
	// TODO: somehow determine if we're on HTTPS or no?
	baseurl.Scheme = "http"

	data := map[string]interface{}{
		"posts": posts,
		"OwnerName": owner.DisplayName,
		"Title": fmt.Sprintf(titleFormat, owner.DisplayName),
		"baseurl": strings.TrimRight(baseurl.String(), "/"),
		"host": host,
		"port": port,
		"FirstPost": firstPost,
	}
	logr.Debugln("Rendering RSS with baseurl of", data["baseurl"])
	xml := mustache.RenderFile("html/rss.xml", data)
	w.Header().Set("Content-Type", "application/rss+xml")
	w.Write([]byte(xml))
	return
}

func rss(w http.ResponseWriter, r *http.Request) {
	posts, err := RecentPosts(10)
	if err != nil {
		logr.Errln("Error loading posts for RSS feed:", err.Error())
		http.Error(w, "error finding recent posts", http.StatusInternalServerError)
		return
	}

	err = WriteRssForPosts(w, r, posts, "%s")
	if err != nil {
		logr.Errln("Error building RSS for recent posts:", err.Error())
		http.Error(w, "error generating rss for recent posts", http.StatusInternalServerError)
	}
}

func archive(w http.ResponseWriter, r *http.Request) {
	//  /archive/2012/09/06/rss.xml
	// 0 1       2    3  4  5
	pathParts := strings.SplitN(r.URL.Path, "/", 6)
	if len(pathParts) != 6 {
		http.NotFound(w, r)
		return
	}

	var year, month, day int
	var err error
	year, err = strconv.Atoi(pathParts[2])
	if err == nil {
		month, err = strconv.Atoi(pathParts[3])
	}
	if err == nil {
		day, err = strconv.Atoi(pathParts[4])
	}
	if err != nil {
		http.NotFound(w, r)
		return
	}

	// TODO: support HTML archives too
	if pathParts[5] != "rss.xml" {
		http.NotFound(w, r)
		return
	}

	archiveDate := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
	posts, err := PostsOnDay(archiveDate)
	if err != nil {
		logr.Errln("Error getting posts for day", archiveDate, "from database:", err.Error())
		http.Error(w, "error finding posts for day", http.StatusInternalServerError)
		return
	}

	err = WriteRssForPosts(w, r, posts, archiveDate.Format("%s for _2 Jan 2006"))
	if err != nil {
		logr.Errln("Error generating RSS for date", archiveDate, ":", err.Error())
		http.Error(w, "error generating rss for date", http.StatusInternalServerError)
	}
}

func index(w http.ResponseWriter, r *http.Request) {
	if len(r.URL.Path) > 1 {
		// Actually some other unhandled URL, so 404.
		http.NotFound(w, r)
		return
	}

	posts, err := RecentPosts(10)
	if err != nil {
		logr.Errln("Error loading recent posts for home page:", err.Error())
	}

	owner := AccountForOwner()
	data := map[string]interface{}{
		"posts": posts,
		"Title": owner.DisplayName,
		"OwnerName": owner.DisplayName,
	}
	html := mustache.RenderFileInLayout("html/index.html", "html/base.html", data)
	w.Write([]byte(html))
}

func permalink(w http.ResponseWriter, r *http.Request) {
	idstr := r.URL.Path[len("/post/"):]
	post, err := PostBySlug(idstr)
	if err != nil {
		http.Error(w, fmt.Sprintf("invalid post %s: %s", idstr, err.Error()), http.StatusBadRequest)
		return
	}

	if r.Method == "DELETE" {
		if !IsAuthed(w, r) {
			return
		}

		post.MarkDeleted()

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusNoContent)
		w.Write([]byte("{\"ok\":true}"))
		return
	}

	owner := AccountForOwner()
	data := map[string]interface{}{
		"post": post,
		"Title": "a post • " + owner.DisplayName,
		"OwnerName": owner.DisplayName,
	}
	html := mustache.RenderFileInLayout("html/permalink.html", "html/base.html", data)
	w.Write([]byte(html))
}

func post(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		w.Header().Set("Allow", "POST")
		http.Error(w, "POST is required", http.StatusMethodNotAllowed)
		return
	}
	if !IsAuthed(w, r) {
		return
	}

	post := NewPost()
	html := r.FormValue("html")
	if html == "" {
		http.Error(w, "html value is required", http.StatusBadRequest)
	}

	post.Html = html
	err := post.Save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	go NotifyRssCloud(fmt.Sprintf("http://%s/rss", r.Host))

	ret, err := json.Marshal(post)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(ret)
}

func static(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path[1:]
	logr.Debugln("Serving static file", path)
	http.ServeFile(w, r, path)
}
<commit_msg>Make an URL naturally<commit_after>package main

import (
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/hoisie/mustache"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

func authedForHeader(authHeader string) (bool, error) {
	if !strings.HasPrefix(authHeader, "Basic ") {
		return false, nil
	}
	logr.Debugln("Yay, client gave a Basic auth header")

	userpass, err := base64.StdEncoding.DecodeString(authHeader[6:])
	if err != nil {
		logr.Debugln("Oops, error decoding the client's Basic auth header:", err.Error())
		// but report it as Unauthorized, not an error
		return false, nil
	}
	userpassParts := strings.SplitN(string(userpass), ":", 2)
	username, pass := userpassParts[0], userpassParts[1]

	account, err := AccountByName(username)
	if err == sql.ErrNoRows {
		logr.Debugln("No such account %s", username)
		return false, nil
	} else if err != nil {
		return false, err
	}

	return account.HasPassword(pass), nil
}

func IsAuthed(w http.ResponseWriter, r *http.Request) (authed bool) {
	authHeader := r.Header.Get("Authorization")
	authed, err := authedForHeader(authHeader)
	if err != nil {
		logr.Errln("Error checking auth information:", err.Error())
		http.Error(w, "error loading auth information", http.StatusInternalServerError)
	} else if !authed {
		w.Header().Set("WWW-Authenticate", "Basic realm=\"cares\"")
		http.Error(w, "authorization required", http.StatusUnauthorized)
	}
	return
}

func WriteRssForPosts(w http.ResponseWriter, r *http.Request, posts []*Post, titleFormat string) (err error) {
	var host string
	var port string
	if strings.Contains(r.Host, ":") {
		host, port, err = net.SplitHostPort(r.Host)
		if err != nil {
			return
		}
	} else {
		host = r.Host
		// TODO: set port appropriately if we're on HTTPS
		port = "80"
	}

	owner := AccountForOwner()
	firstPost, err := FirstPost()
	if err != nil {
		return
	}

	// TODO: somehow determine if we're on HTTPS or no?
	baseurlUrl := url.URL{"http", "", nil, r.Host, "/", "", ""}
	baseurl := strings.TrimRight(baseurlUrl.String(), "/")

	data := map[string]interface{}{
		"posts": posts,
		"OwnerName": owner.DisplayName,
		"Title": fmt.Sprintf(titleFormat, owner.DisplayName),
		"baseurl": baseurl,
		"host": host,
		"port": port,
		"FirstPost": firstPost,
	}
	logr.Debugln("Rendering RSS with baseurl of", baseurl)
	xml := mustache.RenderFile("html/rss.xml", data)
	w.Header().Set("Content-Type", "application/rss+xml")
	w.Write([]byte(xml))
	return
}

func rss(w http.ResponseWriter, r *http.Request) {
	posts, err := RecentPosts(10)
	if err != nil {
		logr.Errln("Error loading posts for RSS feed:", err.Error())
		http.Error(w, "error finding recent posts", http.StatusInternalServerError)
		return
	}

	err = WriteRssForPosts(w, r, posts, "%s")
	if err != nil {
		logr.Errln("Error building RSS for recent posts:", err.Error())
		http.Error(w, "error generating rss for recent posts", http.StatusInternalServerError)
	}
}

func archive(w http.ResponseWriter, r *http.Request) {
	//  /archive/2012/09/06/rss.xml
	// 0 1       2    3  4  5
	pathParts := strings.SplitN(r.URL.Path, "/", 6)
	if len(pathParts) != 6 {
		http.NotFound(w, r)
		return
	}

	var year, month, day int
	var err error
	year, err = strconv.Atoi(pathParts[2])
	if err == nil {
		month, err = strconv.Atoi(pathParts[3])
	}
	if err == nil {
		day, err = strconv.Atoi(pathParts[4])
	}
	if err != nil {
		http.NotFound(w, r)
		return
	}

	// TODO: support HTML archives too
	if pathParts[5] != "rss.xml" {
		http.NotFound(w, r)
		return
	}

	archiveDate := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
	posts, err := PostsOnDay(archiveDate)
	if err != nil {
		logr.Errln("Error getting posts for day", archiveDate, "from database:", err.Error())
		http.Error(w, "error finding posts for day", http.StatusInternalServerError)
		return
	}

	err = WriteRssForPosts(w, r, posts, archiveDate.Format("%s for _2 Jan 2006"))
	if err != nil {
		logr.Errln("Error generating RSS for date", archiveDate, ":", err.Error())
		http.Error(w, "error generating rss for date", http.StatusInternalServerError)
	}
}

func index(w http.ResponseWriter, r *http.Request) {
	if len(r.URL.Path) > 1 {
		// Actually some other unhandled URL, so 404.
		http.NotFound(w, r)
		return
	}

	posts, err := RecentPosts(10)
	if err != nil {
		logr.Errln("Error loading recent posts for home page:", err.Error())
	}

	owner := AccountForOwner()
	data := map[string]interface{}{
		"posts": posts,
		"Title": owner.DisplayName,
		"OwnerName": owner.DisplayName,
	}
	html := mustache.RenderFileInLayout("html/index.html", "html/base.html", data)
	w.Write([]byte(html))
}

func permalink(w http.ResponseWriter, r *http.Request) {
	idstr := r.URL.Path[len("/post/"):]
	post, err := PostBySlug(idstr)
	if err != nil {
		http.Error(w, fmt.Sprintf("invalid post %s: %s", idstr, err.Error()), http.StatusBadRequest)
		return
	}

	if r.Method == "DELETE" {
		if !IsAuthed(w, r) {
			return
		}

		post.MarkDeleted()

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusNoContent)
		w.Write([]byte("{\"ok\":true}"))
		return
	}

	owner := AccountForOwner()
	data := map[string]interface{}{
		"post": post,
		"Title": "a post • " + owner.DisplayName,
		"OwnerName": owner.DisplayName,
	}
	html := mustache.RenderFileInLayout("html/permalink.html", "html/base.html", data)
	w.Write([]byte(html))
}

func post(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		w.Header().Set("Allow", "POST")
		http.Error(w, "POST is required", http.StatusMethodNotAllowed)
		return
	}
	if !IsAuthed(w, r) {
		return
	}

	post := NewPost()
	html := r.FormValue("html")
	if html == "" {
		http.Error(w, "html value is required", http.StatusBadRequest)
	}

	post.Html = html
	err := post.Save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	go NotifyRssCloud(fmt.Sprintf("http://%s/rss", r.Host))

	ret, err := json.Marshal(post)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(ret)
}

func static(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path[1:]
	logr.Debugln("Serving static file", path)
	http.ServeFile(w, r, path)
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/go-uuid/uuid"
	"fmt"
	"github.com/GeertJohan/go.rice"
	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"regexp"
	"text/template"
)

var TMPL = regexp.MustCompile(".tmpl$")

var resourceBox = rice.MustFindBox("resources")

type IndexPageState struct {
	Succeeded int
	Retryable int
	Failed    int
	Programs  []*Program
}

type InfoPageState struct {
	Program *Program
}

type ExecutionPageState struct {
	Execution    *Execution
	ExecutionUrl string
}

func handleIndex(dagr Dagr) func(http.ResponseWriter, *http.Request) {
	indexTemplate, err := loadTemplate("index.html.tmpl")

	if err != nil {
		log.Fatal(err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		if err := indexTemplate.Execute(w, IndexPageState{77, 13, 12, dagr.AllPrograms()}); err != nil {
			log.Println("error when executing index template:", err)
			http.Error(w, err.Error(), 500)
		}
	}
}

func handleInfo(dagr Dagr) func(http.ResponseWriter, *http.Request) {
	infoTemplate, err := loadTemplate("info.html.tmpl")

	if err != nil {
		log.Fatal(err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		programName := vars["program"]
		program := dagr.FindProgram(programName)
		if program == nil {
			log.Println("no such program:", programName)
			http.NotFound(w, req)
		} else if err := infoTemplate.Execute(w, InfoPageState{program}); err != nil {
			log.Println("error when executing info template:", err)
			http.Error(w, err.Error(), 500)
		}
	}
}

type ExecutionState struct {
	Execution *Execution
	ExecutionSubscribers *ExecutionSubscribers
}

func handleExecution(dagr Dagr) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		programName := vars["program"]
		program := dagr.FindProgram(programName)
		if program == nil {
			log.Println("no such program:", programName)
			http.NotFound(w, req)
		} else {
			exec := NewExecution(program)
			guid := uuid.New()
			dagr.AddExecution(guid, &ExecutionState{exec, NewExecutionSubscribers()})
			exec.Execute() // FIXME -- this may return an error
			http.Redirect(w, req, "/executions/"+guid, 302)
		}
	}
}

func showExecution(dagr Dagr) func(http.ResponseWriter, *http.Request) {
	showTemplate, err := loadTemplate("show.html.tmpl")

	if err != nil {
		log.Fatal(err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		executionId := vars["executionId"]
		execution := dagr.FindExecution(executionId)
		if execution == nil {
			log.Println("no such execution:", executionId)
			http.NotFound(w, req)
		} else {
			executionUrl := "ws://localhost:8080/executions/" + executionId + "/messages"

			if err := showTemplate.Execute(w, ExecutionPageState{execution.Execution, executionUrl}); err != nil {
				log.Println("error when executing execution template:", err)
				http.Error(w, err.Error(), 500)
			}
		}
	}
}

func loadTemplate(path string) (*template.Template, error) {
	templateString, err := resourceBox.String(path)
	if err != nil {
		return nil, err
	}
	var t = template.New(path)
	return t.Parse(templateString)
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

type ExecutionSubscribers struct {
	subscribers []*websocket.Conn
}
func NewExecutionSubscribers() *ExecutionSubscribers {
	return &ExecutionSubscribers{}
}
func (e *ExecutionSubscribers) Unsubscribe(c *websocket.Conn) {
	for i, conn := range e.subscribers {
		if conn == c {
			e.subscribers = append(e.subscribers[:i], e.subscribers[i+1:]...)
		}
	}
}
func (e *ExecutionSubscribers) Subscribe(c *websocket.Conn) {
	e.subscribers = append(e.subscribers, c)
}
func (e *ExecutionSubscribers) SendMessage(msg string) {
	for _, conn := range e.subscribers {
		conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintln(msg)))
	}
}

func handleExecutionMessages(dagr Dagr) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		conn, err := upgrader.Upgrade(w, req, nil)
		if err != nil {
			log.Println("cannot upgrade to websocket")
			return
		}
		vars := mux.Vars(req)
		executionId := vars["executionId"]
		log.Println("broadcasting messages for execution id:", executionId)
		execution := dagr.FindExecution(executionId)

		go func() {
			for msg := range execution.Execution.Writer.Message {
				conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintln(msg)))
			}
		}()
	}
}

func Serve(httpAddr string, dagr Dagr) error {

	r := mux.NewRouter()
	r.HandleFunc("/", handleIndex(dagr)).Methods("GET")
	r.HandleFunc("/program/{program}", handleInfo(dagr)).Methods("GET")
	r.HandleFunc("/program/{program}/execute", handleExecution(dagr)).Methods("POST")
	r.HandleFunc("/executions/{executionId}", showExecution(dagr)).Methods("GET")
	r.HandleFunc("/executions/{executionId}/messages", handleExecutionMessages(dagr))
	http.Handle("/", r)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(resourceBox.HTTPBox())))

	server := &http.Server{
		Addr: httpAddr,
	}

	log.Println("dagr listening on", httpAddr)

	return server.ListenAndServe()
}
<commit_msg>add subscriber when connecting web socket; broadcast messages from the message channel to all connected subscriber channels<commit_after>package main

import (
	"code.google.com/p/go-uuid/uuid"
	"fmt"
	"github.com/GeertJohan/go.rice"
	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"regexp"
	"text/template"
)

var TMPL = regexp.MustCompile(".tmpl$")

var resourceBox = rice.MustFindBox("resources")

type IndexPageState struct {
	Succeeded int
	Retryable int
	Failed    int
	Programs  []*Program
}

type InfoPageState struct {
	Program *Program
}

type ExecutionPageState struct {
	Execution    *Execution
	ExecutionUrl string
}

func handleIndex(dagr Dagr) func(http.ResponseWriter, *http.Request) {
	indexTemplate, err := loadTemplate("index.html.tmpl")

	if err != nil {
		log.Fatal(err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		if err := indexTemplate.Execute(w, IndexPageState{77, 13, 12, dagr.AllPrograms()}); err != nil {
			log.Println("error when executing index template:", err)
			http.Error(w, err.Error(), 500)
		}
	}
}

func handleInfo(dagr Dagr) func(http.ResponseWriter, *http.Request) {
	infoTemplate, err := loadTemplate("info.html.tmpl")

	if err != nil {
		log.Fatal(err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		programName := vars["program"]
		program := dagr.FindProgram(programName)
		if program == nil {
			log.Println("no such program:", programName)
			http.NotFound(w, req)
		} else if err := infoTemplate.Execute(w, InfoPageState{program}); err != nil {
			log.Println("error when executing info template:", err)
			http.Error(w, err.Error(), 500)
		}
	}
}

type ExecutionState struct {
	Execution *Execution
	ExecutionSubscribers *ExecutionSubscribers
}
func (e *ExecutionState) Subscribe(c *websocket.Conn) {
	log.Println("adding subscriber")
	e.ExecutionSubscribers.Subscribe(c)
}
func (e *ExecutionState) Unsubscribe(c *websocket.Conn) {
	log.Println("removing subscriber")
	e.ExecutionSubscribers.Unsubscribe(c)
}
func (e *ExecutionState) StartRelay() {
	go func() {
		for msg := range e.Execution.Writer.Message {
			e.ExecutionSubscribers.BroadcastMessage(msg)
		}
	}()
}

func handleExecution(dagr Dagr) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		programName := vars["program"]
		program := dagr.FindProgram(programName)
		if program == nil {
			log.Println("no such program:", programName)
			http.NotFound(w, req)
		} else {
			exec := NewExecution(program)
			guid := uuid.New()
			executionState := &ExecutionState{exec, NewExecutionSubscribers()}
			dagr.AddExecution(guid, executionState)

			exec.Execute() // FIXME -- this may return an error
			executionState.StartRelay() // FIXME -- has to be run after Execute()

			http.Redirect(w, req, "/executions/"+guid, 302)
		}
	}
}

func showExecution(dagr Dagr) func(http.ResponseWriter, *http.Request) {
	showTemplate, err := loadTemplate("show.html.tmpl")

	if err != nil {
		log.Fatal(err)
	}

	return func(w http.ResponseWriter, req *http.Request) {
		vars := mux.Vars(req)
		executionId := vars["executionId"]
		execution := dagr.FindExecution(executionId)
		if execution == nil {
			log.Println("no such execution:", executionId)
			http.NotFound(w, req)
		} else {
			executionUrl := "ws://localhost:8080/executions/" + executionId + "/messages"

			if err := showTemplate.Execute(w, ExecutionPageState{execution.Execution, executionUrl}); err != nil {
				log.Println("error when executing execution template:", err)
				http.Error(w, err.Error(), 500)
			}
		}
	}
}

func loadTemplate(path string) (*template.Template, error) {
	templateString, err := resourceBox.String(path)
	if err != nil {
		return nil, err
	}
	var t = template.New(path)
	return t.Parse(templateString)
}

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

type ExecutionSubscribers struct {
	subscribers []*websocket.Conn
}
func NewExecutionSubscribers() *ExecutionSubscribers {
	return &ExecutionSubscribers{}
}
func (e *ExecutionSubscribers) Unsubscribe(c *websocket.Conn) {
	for i, conn := range e.subscribers {
		if conn == c {
			e.subscribers = append(e.subscribers[:i], e.subscribers[i+1:]...)
		}
	}
}
func (e *ExecutionSubscribers) Subscribe(c *websocket.Conn) {
	e.subscribers = append(e.subscribers, c)
}
func (e *ExecutionSubscribers) BroadcastMessage(msg string) {
	for _, conn := range e.subscribers {
		conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintln(msg)))
	}
}

func handleExecutionMessages(dagr Dagr) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, req *http.Request) {
		conn, err := upgrader.Upgrade(w, req, nil)
		if err != nil {
			log.Println("cannot upgrade to websocket")
			return
		}
		vars := mux.Vars(req)
		executionId := vars["executionId"]
		log.Println("broadcasting messages for execution id:", executionId)
		executionState := dagr.FindExecution(executionId)

		executionState.Subscribe(conn)
	}
}

func Serve(httpAddr string, dagr Dagr) error {

	r := mux.NewRouter()
	r.HandleFunc("/", handleIndex(dagr)).Methods("GET")
	r.HandleFunc("/program/{program}", handleInfo(dagr)).Methods("GET")
	r.HandleFunc("/program/{program}/execute", handleExecution(dagr)).Methods("POST")
	r.HandleFunc("/executions/{executionId}", showExecution(dagr)).Methods("GET")
	r.HandleFunc("/executions/{executionId}/messages", handleExecutionMessages(dagr))
	http.Handle("/", r)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(resourceBox.HTTPBox())))

	server := &http.Server{
		Addr: httpAddr,
	}

	log.Println("dagr listening on", httpAddr)

	return server.ListenAndServe()
}
<|endoftext|>


<commit_before>// Copyright 2018 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package web

import (
	"net/http"
	"time"

	"golang.org/x/text/encoding"

	"github.com/issue9/web/app"
	"github.com/issue9/web/context"
	"github.com/issue9/web/result"
)

var defaultApp *app.App

// Options 传递给 web.Init() 的参数
type Options struct {
	// 配置文件所在的目录。
	ConfigDir string

	// 所有需要支持的字符集
	//
	// 内置 utf-8 字符集支持，不需要再指定
	Charset map[string]encoding.Encoding

	// 指定从客户端数据转换到结构体的函数。
	//
	// 已内置对 text/plain 的支持。
	Marshals map[string]context.Marshal

	// 指定将结构体转换成字符串的函数。
	//
	// 已内置对 text/plain 的支持。
	Unmarshals map[string]context.Unmarshal

	// 所有的错误代码与错误信息的对照表。
	Messages map[int]string
}

// Init 初始化框架的基本内容
func Init(opt *Options) error {
	for name, c := range opt.Charset {
		if err := context.AddCharset(name, c); err != nil {
			return err
		}
	}

	for name, m := range opt.Marshals {
		if err := context.AddMarshal(name, m); err != nil {
			return err
		}
	}

	for name, m := range opt.Unmarshals {
		if err := context.AddUnmarshal(name, m); err != nil {
			return err
		}
	}

	if err := result.NewMessages(opt.Messages); err != nil {
		return err
	}

	app, err := app.New(opt.ConfigDir)
	if err != nil {
		return err
	}

	defaultApp = app
	return nil
}

// IsDebug 是否处在调试模式
func IsDebug() bool {
	return defaultApp.IsDebug()
}

// Run 运行路由，执行监听程序，具体说明可参考 App.Run()。
func Run(build app.BuildHandler) error {
	return defaultApp.Run(build)
}

// Shutdown 关闭所有服务，具体说明可参考 App.Shutdown()
func Shutdown(timeout time.Duration) error {
	return defaultApp.Shutdown(timeout)
}

// File 获取配置目录下的文件，具体说明可参考 App.File()。
func File(path string) string {
	return defaultApp.File(path)
}

// URL 构建一条完整 URL
func URL(path string) string {
	return defaultApp.URL(path)
}

// AddModule 注册一个模块
func AddModule(m *app.Module) *app.App {
	return defaultApp.AddModule(m)
}

// NewContext 根据当前配置，生成 context.Context 对象，若是出错则返回 nil
func NewContext(w http.ResponseWriter, r *http.Request) *context.Context {
	return defaultApp.NewContext(w, r)
}

// NewModule 声明一个模块内容。
func NewModule(name, desc string, deps ...string) *app.Module {
	return app.NewModule(name, desc, deps...)
}

// NewPlugin 声明一个插件下的模块信息。
func NewPlugin(name, desc string, deps ...string) *app.Module {
	return app.NewPlugin(name, desc, deps...)
}

// NewResult 生成一个 *result.Result 对象
func NewResult(code int) *result.Result {
	return result.New(code, nil)
}

// NewResultWithDetail 声明一个带详细内容的 result.Result 对象
func NewResultWithDetail(code int, fields map[string]string) *result.Result {
	return result.New(code, fields)
}
<commit_msg>添加 Restart 函数<commit_after>// Copyright 2018 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package web

import (
	"net/http"
	"time"

	"golang.org/x/text/encoding"

	"github.com/issue9/web/app"
	"github.com/issue9/web/context"
	"github.com/issue9/web/result"
)

var defaultApp *app.App

// Options 传递给 web.Init() 的参数
type Options struct {
	// 配置文件所在的目录。
	ConfigDir string

	// 所有需要支持的字符集
	//
	// 内置 utf-8 字符集支持，不需要再指定
	Charset map[string]encoding.Encoding

	// 指定从客户端数据转换到结构体的函数。
	//
	// 已内置对 text/plain 的支持。
	Marshals map[string]context.Marshal

	// 指定将结构体转换成字符串的函数。
	//
	// 已内置对 text/plain 的支持。
	Unmarshals map[string]context.Unmarshal

	// 所有的错误代码与错误信息的对照表。
	Messages map[int]string

	Build app.BuildHandler
}

// Init 初始化框架的基本内容
func Init(opt *Options) error {
	for name, c := range opt.Charset {
		if err := context.AddCharset(name, c); err != nil {
			return err
		}
	}

	for name, m := range opt.Marshals {
		if err := context.AddMarshal(name, m); err != nil {
			return err
		}
	}

	for name, m := range opt.Unmarshals {
		if err := context.AddUnmarshal(name, m); err != nil {
			return err
		}
	}

	if err := result.NewMessages(opt.Messages); err != nil {
		return err
	}

	app, err := app.New(opt.ConfigDir, opt.Build)
	if err != nil {
		return err
	}

	defaultApp = app
	return nil
}

// IsDebug 是否处在调试模式
func IsDebug() bool {
	return defaultApp.IsDebug()
}

// Run 运行路由，执行监听程序，具体说明可参考 App.Run()。
func Run() error {
	return defaultApp.Run()
}

// Shutdown 关闭所有服务，具体说明可参考 App.Shutdown()
func Shutdown(timeout time.Duration) error {
	return defaultApp.Shutdown(timeout)
}

// File 获取配置目录下的文件，具体说明可参考 App.File()。
func File(path string) string {
	return defaultApp.File(path)
}

// URL 构建一条完整 URL
func URL(path string) string {
	return defaultApp.URL(path)
}

// AddModule 注册一个模块
func AddModule(m *app.Module) *app.App {
	return defaultApp.AddModule(m)
}

// NewContext 根据当前配置，生成 context.Context 对象，若是出错则返回 nil
func NewContext(w http.ResponseWriter, r *http.Request) *context.Context {
	return defaultApp.NewContext(w, r)
}

// NewModule 声明一个模块内容。
func NewModule(name, desc string, deps ...string) *app.Module {
	return app.NewModule(name, desc, deps...)
}

// NewPlugin 声明一个插件下的模块信息。
func NewPlugin(name, desc string, deps ...string) *app.Module {
	return app.NewPlugin(name, desc, deps...)
}

// NewResult 生成一个 *result.Result 对象
func NewResult(code int) *result.Result {
	return result.New(code, nil)
}

// NewResultWithDetail 声明一个带详细内容的 result.Result 对象
func NewResultWithDetail(code int, fields map[string]string) *result.Result {
	return result.New(code, fields)
}

// Restart 重启服务
//
// 其实是终止旧有的服务，然后再开一个新的服务，
// 包括初始化等操作，都会重新走一遍。
func Restart(opt *Options, timeout time.Duration) error {
	if err := Shutdown(timeout); err != nil {
		return err
	}

	if err := Init(opt); err != nil {
		return err
	}

	return Run()
}
<|endoftext|>


<commit_before>package arn

import (
	"errors"

	"github.com/aerogo/aero"
	"github.com/aerogo/api"
)

// Force interface implementations
var (
	_ Publishable   = (*Quote)(nil)
	_ api.Newable   = (*Quote)(nil)
	_ api.Editable  = (*Quote)(nil)
	_ api.Deletable = (*Quote)(nil)
)

// Actions
func init() {
	API.RegisterActions("Quote", []*api.Action{
		// Publish
		PublishAction(),

		// Unpublish
		UnpublishAction(),

		// Like
		LikeAction(),

		// Unlike
		UnlikeAction(),
	})
}

// Create sets the data for a new quote with data we received from the API request.
func (quote *Quote) Create(ctx *aero.Context) error {
	user := GetUserFromContext(ctx)

	if user == nil {
		return errors.New("Not logged in")
	}

	quote.ID = GenerateID("Quote")
	quote.Created = DateTimeUTC()
	quote.CreatedBy = user.ID
	quote.Likes = []string{}

	return quote.Unpublish()
}

// AfterEdit updates the metadata.
func (quote *Quote) AfterEdit(ctx *aero.Context) error {
	quote.Edited = DateTimeUTC()
	quote.EditedBy = GetUserFromContext(ctx).ID
	return nil
}

// Save saves the quote in the database.
func (quote *Quote) Save() {
	DB.Set("Quote", quote.ID, quote)
}

// Delete deletes the object from the database.
func (quote *Quote) Delete() error {
	character, err := GetCharacter(quote.CharacterId)

	if err != nil {
		return err
	}

	// Remove the reference of the post in the thread that contains it
	if !character.Remove(quote.ID) {
		return errors.New("This quote does not exist in the thread")
	}

	character.Save()

	if quote.IsDraft {
		draftIndex := quote.Creator().DraftIndex()
		draftIndex.QuoteID = ""
		draftIndex.Save()
	}

	DB.Delete("Quote", quote.ID)
	return nil
}

// Authorize returns an error if the given API request is not authorized.
func (quote *Quote) Authorize(ctx *aero.Context, action string) error {
	user := GetUserFromContext(ctx)

	if user == nil {
		return errors.New("Not logged in")
	}

	if action == "delete" {
		if user.Role != "editor" && user.Role != "admin" {
			return errors.New("Insufficient permissions")
		}
	}

	return nil
}
<commit_msg>🐛 Remove the quote from it's previous referenced character on edit<commit_after>package arn

import (
	"errors"

	"github.com/aerogo/aero"
	"github.com/aerogo/api"
	"reflect"
)

// Force interface implementations
var (
	_ Likeable      = (*Quote)(nil)
	_ Publishable   = (*Quote)(nil)
	_ api.Newable   = (*Quote)(nil)
	_ api.Editable  = (*Quote)(nil)
	_ api.Deletable = (*Quote)(nil)
)

// Actions
func init() {
	API.RegisterActions("Quote", []*api.Action{
		// Publish
		PublishAction(),

		// Unpublish
		UnpublishAction(),

		// Like
		LikeAction(),

		// Unlike
		UnlikeAction(),
	})
}

// Create sets the data for a new quote with data we received from the API request.
func (quote *Quote) Create(ctx *aero.Context) error {
	user := GetUserFromContext(ctx)

	if user == nil {
		return errors.New("Not logged in")
	}

	quote.ID = GenerateID("Quote")
	quote.Created = DateTimeUTC()
	quote.CreatedBy = user.ID
	quote.Likes = []string{}

	return quote.Unpublish()
}

// Edit ...
func (quote *Quote) Edit(ctx *aero.Context, key string, value reflect.Value, newValue reflect.Value) (bool, error) {
	if key == "CharacterId" {
		newCharacterId := newValue.String()
		previousCharacterId := newValue.String()
		if previousCharacterId != newCharacterId {
			newCharacter, err := GetCharacter(newCharacterId)
			previousCharacter, err := GetCharacter(previousCharacterId)

			if err != nil {
				return false, err
			}

			// Remove the reference of the quote in the previous character that contained it
			if !previousCharacter.Remove(quote.ID) {
				return false, errors.New("This quote does not exist")
			}

			previousCharacter.Save()
			value.SetString(newCharacterId)

			// Append to quotes Ids to the new character
			newCharacter.QuotesIds = append(newCharacter.QuotesIds, quote.ID)

			// Save the character
			newCharacter.Save()
			return true, nil
		}

	}
	return false, nil
}

// AfterEdit updates the metadata.
func (quote *Quote) AfterEdit(ctx *aero.Context) error {
	quote.Edited = DateTimeUTC()
	quote.EditedBy = GetUserFromContext(ctx).ID
	return nil
}

// Save saves the quote in the database.
func (quote *Quote) Save() {
	DB.Set("Quote", quote.ID, quote)
}

// Delete deletes the object from the database.
func (quote *Quote) Delete() error {
	character, err := GetCharacter(quote.CharacterId)

	if err != nil {
		return err
	}

	// Remove the reference of the quote in the character that contains it
	if !character.Remove(quote.ID) {
		return errors.New("This quote does not exist")
	}

	character.Save()

	if quote.IsDraft {
		draftIndex := quote.Creator().DraftIndex()
		draftIndex.QuoteID = ""
		draftIndex.Save()
	}

	DB.Delete("Quote", quote.ID)
	return nil
}

// Authorize returns an error if the given API request is not authorized.
func (quote *Quote) Authorize(ctx *aero.Context, action string) error {
	user := GetUserFromContext(ctx)

	if user == nil {
		return errors.New("Not logged in")
	}

	if action == "delete" {
		if user.Role != "editor" && user.Role != "admin" {
			return errors.New("Insufficient permissions")
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Package xcf implements a simple reader for XCF files. This is the native
// format of the GIMP program.
//
// The current implementation only supports loading RGB and RGBA layers.
//
// The implementation uses information from
//    http://henning.makholm.net/xcftools/xcfspec-saved
// as a reference.
package xcf

import (
	"encoding/binary"
	"errors"
	"fmt"
	"image"
	"io"
	"io/ioutil"
	"os"
)

type Canvas struct {
	Width, Height uint32
	Layers        []Layer
}

type Layer struct {
	image.Image
	Name    string
	Visible bool
	Opacity uint8
}

func LoadFromFile(path string) (Canvas, error) {
	file, err := os.Open(path)
	if err != nil {
		return Canvas{}, err
	}
	defer file.Close()

	return Decode(file)
}

func Decode(r io.ReadSeeker) (canvas Canvas, finalErr error) {
	// read header
	var header fileHeader

	if err := binary.Read(r, endianness, &header); err != nil {
		finalErr = errors.New("read XCF: error reading header: " + err.Error())
		return
	}

	magic := string(header.MagicID[:])
	if magic != "gimp xcf " {
		finalErr = errors.New("read XCF: wrong magic ID: " + magic)
		return
	}

	version := string(header.Version[:])
	if version != "file" {
		finalErr = errors.New("read XCF: unsupported file version: " + version)
		return
	}

	if header.ColorFormat != rgb {
		finalErr = fmt.Errorf("read XCF: unsupported color format: %v", header.ColorFormat)
		return
	}

	// read properties
	for {
		var propHeader propertyHeader
		if err := binary.Read(r, endianness, &propHeader); err != nil {
			finalErr = errors.New("read XCF: error reading property header: " + err.Error())
			return
		}

		if propHeader.MagicID == propEnd {
			break
		} else if propHeader.MagicID == propColormap {
			// NOTE skipping the color map cannot rely on the property header
			// length since there are broken GIMP versions that write it wrong
			var colorCount uint32
			if err := binary.Read(r, endianness, &colorCount); err != nil {
				finalErr = errors.New("read XCF: unable to read number of colors in color map: " + err.Error())
				return
			}
			if _, err := io.CopyN(ioutil.Discard, r, int64(3*colorCount)); err != nil {
				finalErr = errors.New("read XCF: error skipping color map: " + err.Error())
				return
			}
		} else if propHeader.MagicID == propCompression {
			var compression [1]byte
			if _, err := r.Read(compression[:]); err != nil {
				finalErr = errors.New("read XCF: error reading compression: " + err.Error())
				return
			}
			if compression[0] != rleCompression {
				finalErr = fmt.Errorf("read XCF: unsupported compression: %v", compression[0])
				return
			}
		} else {
			// skip unknown properties
			if err := skipProperty(r, propHeader); err != nil {
				finalErr = errors.New("unable to skip property: " + err.Error())
			}
		}
	}

	// read layers
	var layerPointers []uint32 // top-most layer comes first
	var ptr uint32
	for {
		binary.Read(r, endianness, &ptr)
		if ptr == 0 {
			break
		}
		layerPointers = append(layerPointers, ptr)
	}

	var layers []Layer
	for _, ptr := range layerPointers {
		layer, err := loadLayer(r, ptr)
		if err != nil {
			finalErr = errors.New("read XCF: error reading layers: " + err.Error())
			return
		}
		layers = append(layers, layer)
	}

	return Canvas{
		Width:  header.Width,
		Height: header.Height,
		Layers: layers,
	}, nil
}

var endianness = binary.BigEndian

func skipProperty(r io.Reader, header propertyHeader) error {
	_, err := io.CopyN(ioutil.Discard, r, int64(header.Length))
	if err != nil {
		return errors.New("read XCF: unable to skip property: " + err.Error())
	}
	return nil
}

type fileHeader struct {
	MagicID           [9]byte
	Version           [4]byte
	VersionTerminator byte
	Width, Height     uint32
	ColorFormat       uint32
}

// color formats
const (
	// main image color formats
	rgb       = 0
	grayscale = 1
	indexed   = 2
	// layer color formats
	rgbNoAlpha       = 0
	rgbAlpha         = 1
	grayscaleNoAlpha = 2
	grayscaleAlpha   = 3
	indexedNoAlpha   = 4
	indexedAlpha     = 5
)

type propertyHeader struct {
	MagicID uint32
	Length  uint32
}

// property types
const (
	propEnd               = 0
	propColormap          = 1
	propActiveLayer       = 2
	propActiveChannel     = 3
	propSelection         = 4
	propFloatingSelection = 5
	propOpacity           = 6
	propMode              = 7
	propVisible           = 8
	propLinked            = 9
	propLockAlpha         = 10
	propApplyMask         = 11
	propEditMask          = 12
	propShowMask          = 13
	propShowMasked        = 14
	propOffsets           = 15
	propColor             = 16
	propCompression       = 17
	propGuides            = 18
	propResolution        = 19
	propTattoo            = 20
	propParasites         = 21
	propUnit              = 22
	propPaths             = 23
	propUserUnit          = 24
	propVectors           = 25
	propTextLayerFlags    = 26
	propSamplePoints      = 27
	propLockContent       = 28
	propGroupItem         = 29
	propItemPath          = 30
	propGroupItemFlags    = 31
)

// compression types
const (
	noCompression      = 0
	rleCompression     = 1
	zlibCompression    = 2
	fractalCompression = 3
)

type layerHeader struct {
	Width, Height uint32
	ColorFormat   uint32
}

func readString(r io.Reader) (string, error) {
	var length uint32
	if err := binary.Read(r, endianness, &length); err != nil {
		return "", errors.New("reading string length: " + err.Error())
	}
	// the empty string is stored simply as the number 0
	if length == 0 {
		return "", nil
	}

	// the length includes a 0 terminator that has to be read as well
	data := make([]byte, length)
	_, err := r.Read(data)
	if err != nil {
		return "", errors.New("reading string: " + err.Error())
	}

	// there is a 0 terminator at the end of the string, ignore that
	return string(data[:length-1]), nil
}

func loadLayer(r io.ReadSeeker, offset uint32) (layer Layer, err error) {
	var header layerHeader
	_, err = r.Seek(int64(offset), 0)
	if err != nil {
		return
	}

	if err = binary.Read(r, endianness, &header); err != nil {
		return
	}
	if !(header.ColorFormat == rgbAlpha || header.ColorFormat == rgbNoAlpha) {
		err = fmt.Errorf("unsupported layer color format, must be RGB: %v", header.ColorFormat)
		return
	}

	if layer.Name, err = readString(r); err != nil {
		return
	}

	// read properties
	layer.Visible = true
	//layer.Width, layer.Height = int(header.Width), int(header.Height)
	var x, y int32
	for {
		var propHeader propertyHeader
		if err = binary.Read(r, endianness, &propHeader); err != nil {
			return
		}

		if propHeader.MagicID == propEnd {
			break
		} else if propHeader.MagicID == propOffsets {
			if err = binary.Read(r, endianness, x); err != nil {
				return
			}
			if err = binary.Read(r, endianness, y); err != nil {
				return
			}
		} else if propHeader.MagicID == propVisible {
			var visible uint32
			if err = binary.Read(r, endianness, &visible); err != nil {
				return
			}
			layer.Visible = visible != 0
		} else if propHeader.MagicID == propOpacity {
			var opacity uint32
			if err = binary.Read(r, endianness, &opacity); err != nil {
				return
			}
			layer.Opacity = uint8(opacity)
		} else {
			if err = skipProperty(r, propHeader); err != nil {
				return
			}
		}
	}

	var pixelPointer uint32
	if err = binary.Read(r, endianness, &pixelPointer); err != nil {
		return
	}

	var maskPointer uint32
	if err = binary.Read(r, endianness, &maskPointer); err != nil {
		return
	}

	layer.Image, err = readImageData(r, header, int(x), int(y), pixelPointer)
	if err != nil {
		return
	}

	return
}

type hierarchyHeader struct {
	Width, Height     uint32
	BytesPerPixel     uint32
	FirstLevelPointer uint32
}

func readImageData(r io.ReadSeeker, layerHeader layerHeader, layerX, layerY int, offset uint32) (img image.Image, err error) {
	var header hierarchyHeader
	r.Seek(int64(offset), 0)
	if err = binary.Read(r, endianness, &header); err != nil {
		return
	}

	// read unused level pointers
	var ptr uint32
	for {
		if err = binary.Read(r, endianness, &ptr); err != nil {
			return
		}
		if ptr == 0 {
			break
		}
	}

	// read first level data
	r.Seek(int64(header.FirstLevelPointer), 0)
	var levelWidth, levelHeight uint32 // redundant, same as in layerHeader
	if err = binary.Read(r, endianness, &levelWidth); err != nil {
		return
	}
	if err = binary.Read(r, endianness, &levelHeight); err != nil {
		return
	}

	tileCountX := int(layerHeader.Width+63) / 64
	tileCountY := int(layerHeader.Height+63) / 64
	rightMostTileWidth := int(layerHeader.Width) % 64
	if rightMostTileWidth == 0 {
		rightMostTileWidth = 64
	}
	bottomMostTileHeight := int(layerHeader.Height) % 64
	if bottomMostTileHeight == 0 {
		bottomMostTileHeight = 64
	}
	tileCount := tileCountX * tileCountY

	tilePointers := make([]uint32, tileCount+1) // + 1 for 0 terminator
	if err = binary.Read(r, endianness, tilePointers); err != nil {
		return
	}
	tilePointers = tilePointers[:tileCount] // remove 0 terminator

	rgba := image.NewRGBA(image.Rect(
		layerX,
		layerY,
		layerX+int(layerHeader.Width),
		layerY+int(layerHeader.Height),
	))

	buffer := make([]byte, 64*64*header.BytesPerPixel)
	for tileY := 0; tileY < tileCountY; tileY++ {
		for tileX := 0; tileX < tileCountX; tileX++ {
			left, top := tileX*64, tileY*64
			w, h := 64, 64
			if tileX == tileCountX-1 {
				w = rightMostTileWidth
			}
			if tileY == tileCountY-1 {
				h = bottomMostTileHeight
			}
			destByteCount := w * h * int(header.BytesPerPixel)
			data := buffer[:destByteCount]
			if err = decodeRLE(r, data); err != nil {
				return
			}

			dataIndex := 0
			for y := top; y < top+h; y++ {
				line := rgba.Pix[rgba.Stride*y:]
				if layerHeader.ColorFormat == rgbAlpha {

					ofs := len(data) / 4
					for x := left; x < left+w; x++ {
						line[x*4] = uint8(data[dataIndex])
						line[x*4+1] = uint8(data[dataIndex+ofs])
						line[x*4+2] = uint8(data[dataIndex+2*ofs])
						line[x*4+3] = uint8(data[dataIndex+3*ofs])
						dataIndex++
					}

				} else if layerHeader.ColorFormat == rgbNoAlpha {

					ofs := len(data) / 3
					for x := left; x < left+w; x++ {
						line[x*4] = uint8(data[dataIndex])
						line[x*4+1] = uint8(data[dataIndex+ofs])
						line[x*4+2] = uint8(data[dataIndex+2*ofs])
						line[x*4+3] = 255
						dataIndex++
					}

				}
			}
		}
	}
	img = rgba

	return
}

func decodeRLE(encoded io.Reader, dest []byte) (err error) {
	var buffer [4]byte
	next := 0

	for next < len(dest) {
		if _, err = encoded.Read(buffer[:1]); err != nil {
			return
		}
		op := buffer[0]
		if op <= 126 {
			// short run of identical bytes
			if _, err = encoded.Read(buffer[1:2]); err != nil {
				return
			}
			count := int(op) + 1
			value := buffer[1]
			for i := 0; i < count; i++ {
				dest[next] = value
				next++
			}
		} else if op == 127 {
			// long run of identical bytes
			if _, err = encoded.Read(buffer[1:4]); err != nil {
				return
			}
			count := int(buffer[1])*256 + int(buffer[2])
			value := buffer[3]
			for i := 0; i < count; i++ {
				dest[next] = value
				next++
			}
		} else if op == 128 {
			// long run of different bytes, copied verbatim from the stream
			if _, err = encoded.Read(buffer[1:3]); err != nil {
				return
			}
			count := int(buffer[1])*256 + int(buffer[2])
			if _, err = encoded.Read(dest[next : next+count]); err != nil {
				return
			}
			next += count
		} else {
			// short run of different bytes, copied verbatim from the stream
			count := 256 - int(op)
			if _, err = encoded.Read(dest[next : next+count]); err != nil {
				return
			}
			next += count
		}
	}
	return nil
}
<commit_msg>Bugfix: layer offset is read correctly now.<commit_after>// Package xcf implements a simple reader for XCF files. This is the native
// format of the GIMP program.
//
// The current implementation only supports loading RGB and RGBA layers.
//
// The implementation uses information from
//    http://henning.makholm.net/xcftools/xcfspec-saved
// as a reference.
package xcf

import (
	"encoding/binary"
	"errors"
	"fmt"
	"image"
	"io"
	"io/ioutil"
	"os"
)

type Canvas struct {
	Width, Height uint32
	Layers        []Layer
}

type Layer struct {
	image.Image
	Name    string
	Visible bool
	Opacity uint8
}

func LoadFromFile(path string) (Canvas, error) {
	file, err := os.Open(path)
	if err != nil {
		return Canvas{}, err
	}
	defer file.Close()

	return Decode(file)
}

func Decode(r io.ReadSeeker) (canvas Canvas, finalErr error) {
	// read header
	var header fileHeader

	if err := binary.Read(r, endianness, &header); err != nil {
		finalErr = errors.New("read XCF: error reading header: " + err.Error())
		return
	}

	magic := string(header.MagicID[:])
	if magic != "gimp xcf " {
		finalErr = errors.New("read XCF: wrong magic ID: " + magic)
		return
	}

	version := string(header.Version[:])
	if version != "file" {
		finalErr = errors.New("read XCF: unsupported file version: " + version)
		return
	}

	if header.ColorFormat != rgb {
		finalErr = fmt.Errorf("read XCF: unsupported color format: %v", header.ColorFormat)
		return
	}

	// read properties
	for {
		var propHeader propertyHeader
		if err := binary.Read(r, endianness, &propHeader); err != nil {
			finalErr = errors.New("read XCF: error reading property header: " + err.Error())
			return
		}

		if propHeader.MagicID == propEnd {
			break
		} else if propHeader.MagicID == propColormap {
			// NOTE skipping the color map cannot rely on the property header
			// length since there are broken GIMP versions that write it wrong
			var colorCount uint32
			if err := binary.Read(r, endianness, &colorCount); err != nil {
				finalErr = errors.New("read XCF: unable to read number of colors in color map: " + err.Error())
				return
			}
			if _, err := io.CopyN(ioutil.Discard, r, int64(3*colorCount)); err != nil {
				finalErr = errors.New("read XCF: error skipping color map: " + err.Error())
				return
			}
		} else if propHeader.MagicID == propCompression {
			var compression [1]byte
			if _, err := r.Read(compression[:]); err != nil {
				finalErr = errors.New("read XCF: error reading compression: " + err.Error())
				return
			}
			if compression[0] != rleCompression {
				finalErr = fmt.Errorf("read XCF: unsupported compression: %v", compression[0])
				return
			}
		} else {
			// skip unknown properties
			if err := skipProperty(r, propHeader); err != nil {
				finalErr = errors.New("unable to skip property: " + err.Error())
			}
		}
	}

	// read layers
	var layerPointers []uint32 // top-most layer comes first
	var ptr uint32
	for {
		binary.Read(r, endianness, &ptr)
		if ptr == 0 {
			break
		}
		layerPointers = append(layerPointers, ptr)
	}

	var layers []Layer
	for _, ptr := range layerPointers {
		layer, err := loadLayer(r, ptr)
		if err != nil {
			finalErr = errors.New("read XCF: error reading layers: " + err.Error())
			return
		}
		layers = append(layers, layer)
	}

	return Canvas{
		Width:  header.Width,
		Height: header.Height,
		Layers: layers,
	}, nil
}

var endianness = binary.BigEndian

func skipProperty(r io.Reader, header propertyHeader) error {
	_, err := io.CopyN(ioutil.Discard, r, int64(header.Length))
	if err != nil {
		return errors.New("read XCF: unable to skip property: " + err.Error())
	}
	return nil
}

type fileHeader struct {
	MagicID           [9]byte
	Version           [4]byte
	VersionTerminator byte
	Width, Height     uint32
	ColorFormat       uint32
}

// color formats
const (
	// main image color formats
	rgb       = 0
	grayscale = 1
	indexed   = 2
	// layer color formats
	rgbNoAlpha       = 0
	rgbAlpha         = 1
	grayscaleNoAlpha = 2
	grayscaleAlpha   = 3
	indexedNoAlpha   = 4
	indexedAlpha     = 5
)

type propertyHeader struct {
	MagicID uint32
	Length  uint32
}

// property types
const (
	propEnd               = 0
	propColormap          = 1
	propActiveLayer       = 2
	propActiveChannel     = 3
	propSelection         = 4
	propFloatingSelection = 5
	propOpacity           = 6
	propMode              = 7
	propVisible           = 8
	propLinked            = 9
	propLockAlpha         = 10
	propApplyMask         = 11
	propEditMask          = 12
	propShowMask          = 13
	propShowMasked        = 14
	propOffsets           = 15
	propColor             = 16
	propCompression       = 17
	propGuides            = 18
	propResolution        = 19
	propTattoo            = 20
	propParasites         = 21
	propUnit              = 22
	propPaths             = 23
	propUserUnit          = 24
	propVectors           = 25
	propTextLayerFlags    = 26
	propSamplePoints      = 27
	propLockContent       = 28
	propGroupItem         = 29
	propItemPath          = 30
	propGroupItemFlags    = 31
)

// compression types
const (
	noCompression      = 0
	rleCompression     = 1
	zlibCompression    = 2
	fractalCompression = 3
)

type layerHeader struct {
	Width, Height uint32
	ColorFormat   uint32
}

func readString(r io.Reader) (string, error) {
	var length uint32
	if err := binary.Read(r, endianness, &length); err != nil {
		return "", errors.New("reading string length: " + err.Error())
	}
	// the empty string is stored simply as the number 0
	if length == 0 {
		return "", nil
	}

	// the length includes a 0 terminator that has to be read as well
	data := make([]byte, length)
	_, err := r.Read(data)
	if err != nil {
		return "", errors.New("reading string: " + err.Error())
	}

	// there is a 0 terminator at the end of the string, ignore that
	return string(data[:length-1]), nil
}

func loadLayer(r io.ReadSeeker, offset uint32) (layer Layer, err error) {
	var header layerHeader
	_, err = r.Seek(int64(offset), 0)
	if err != nil {
		return
	}

	if err = binary.Read(r, endianness, &header); err != nil {
		return
	}
	if !(header.ColorFormat == rgbAlpha || header.ColorFormat == rgbNoAlpha) {
		err = fmt.Errorf("unsupported layer color format, must be RGB: %v", header.ColorFormat)
		return
	}

	if layer.Name, err = readString(r); err != nil {
		return
	}

	// read properties
	layer.Visible = true
	var x, y int32
	for {
		var propHeader propertyHeader
		if err = binary.Read(r, endianness, &propHeader); err != nil {
			return
		}

		if propHeader.MagicID == propEnd {
			break
		} else if propHeader.MagicID == propOffsets {
			if err = binary.Read(r, endianness, &x); err != nil {
				return
			}
			if err = binary.Read(r, endianness, &y); err != nil {
				return
			}
		} else if propHeader.MagicID == propVisible {
			var visible uint32
			if err = binary.Read(r, endianness, &visible); err != nil {
				return
			}
			layer.Visible = visible != 0
		} else if propHeader.MagicID == propOpacity {
			var opacity uint32
			if err = binary.Read(r, endianness, &opacity); err != nil {
				return
			}
			layer.Opacity = uint8(opacity)
		} else {
			if err = skipProperty(r, propHeader); err != nil {
				return
			}
		}
	}

	var pixelPointer uint32
	if err = binary.Read(r, endianness, &pixelPointer); err != nil {
		return
	}

	var maskPointer uint32
	if err = binary.Read(r, endianness, &maskPointer); err != nil {
		return
	}

	layer.Image, err = readImageData(r, header, int(x), int(y), pixelPointer)
	if err != nil {
		return
	}

	return
}

type hierarchyHeader struct {
	Width, Height     uint32
	BytesPerPixel     uint32
	FirstLevelPointer uint32
}

func readImageData(r io.ReadSeeker, layerHeader layerHeader, layerX, layerY int, offset uint32) (img image.Image, err error) {
	var header hierarchyHeader
	r.Seek(int64(offset), 0)
	if err = binary.Read(r, endianness, &header); err != nil {
		return
	}

	// read unused level pointers
	var ptr uint32
	for {
		if err = binary.Read(r, endianness, &ptr); err != nil {
			return
		}
		if ptr == 0 {
			break
		}
	}

	// read first level data
	r.Seek(int64(header.FirstLevelPointer), 0)
	var levelWidth, levelHeight uint32 // redundant, same as in layerHeader
	if err = binary.Read(r, endianness, &levelWidth); err != nil {
		return
	}
	if err = binary.Read(r, endianness, &levelHeight); err != nil {
		return
	}

	tileCountX := int(layerHeader.Width+63) / 64
	tileCountY := int(layerHeader.Height+63) / 64
	rightMostTileWidth := int(layerHeader.Width) % 64
	if rightMostTileWidth == 0 {
		rightMostTileWidth = 64
	}
	bottomMostTileHeight := int(layerHeader.Height) % 64
	if bottomMostTileHeight == 0 {
		bottomMostTileHeight = 64
	}
	tileCount := tileCountX * tileCountY

	tilePointers := make([]uint32, tileCount+1) // + 1 for 0 terminator
	if err = binary.Read(r, endianness, tilePointers); err != nil {
		return
	}
	tilePointers = tilePointers[:tileCount] // remove 0 terminator

	rgba := image.NewRGBA(image.Rect(
		layerX,
		layerY,
		layerX+int(layerHeader.Width),
		layerY+int(layerHeader.Height),
	))

	buffer := make([]byte, 64*64*header.BytesPerPixel)
	for tileY := 0; tileY < tileCountY; tileY++ {
		for tileX := 0; tileX < tileCountX; tileX++ {
			left, top := tileX*64, tileY*64
			w, h := 64, 64
			if tileX == tileCountX-1 {
				w = rightMostTileWidth
			}
			if tileY == tileCountY-1 {
				h = bottomMostTileHeight
			}
			destByteCount := w * h * int(header.BytesPerPixel)
			data := buffer[:destByteCount]
			if err = decodeRLE(r, data); err != nil {
				return
			}

			dataIndex := 0
			for y := top; y < top+h; y++ {
				line := rgba.Pix[rgba.Stride*y:]
				if layerHeader.ColorFormat == rgbAlpha {

					ofs := len(data) / 4
					for x := left; x < left+w; x++ {
						line[x*4] = uint8(data[dataIndex])
						line[x*4+1] = uint8(data[dataIndex+ofs])
						line[x*4+2] = uint8(data[dataIndex+2*ofs])
						line[x*4+3] = uint8(data[dataIndex+3*ofs])
						dataIndex++
					}

				} else if layerHeader.ColorFormat == rgbNoAlpha {

					ofs := len(data) / 3
					for x := left; x < left+w; x++ {
						line[x*4] = uint8(data[dataIndex])
						line[x*4+1] = uint8(data[dataIndex+ofs])
						line[x*4+2] = uint8(data[dataIndex+2*ofs])
						line[x*4+3] = 255
						dataIndex++
					}

				}
			}
		}
	}
	img = rgba

	return
}

func decodeRLE(encoded io.Reader, dest []byte) (err error) {
	var buffer [4]byte
	next := 0

	for next < len(dest) {
		if _, err = encoded.Read(buffer[:1]); err != nil {
			return
		}
		op := buffer[0]
		if op <= 126 {
			// short run of identical bytes
			if _, err = encoded.Read(buffer[1:2]); err != nil {
				return
			}
			count := int(op) + 1
			value := buffer[1]
			for i := 0; i < count; i++ {
				dest[next] = value
				next++
			}
		} else if op == 127 {
			// long run of identical bytes
			if _, err = encoded.Read(buffer[1:4]); err != nil {
				return
			}
			count := int(buffer[1])*256 + int(buffer[2])
			value := buffer[3]
			for i := 0; i < count; i++ {
				dest[next] = value
				next++
			}
		} else if op == 128 {
			// long run of different bytes, copied verbatim from the stream
			if _, err = encoded.Read(buffer[1:3]); err != nil {
				return
			}
			count := int(buffer[1])*256 + int(buffer[2])
			if _, err = encoded.Read(dest[next : next+count]); err != nil {
				return
			}
			next += count
		} else {
			// short run of different bytes, copied verbatim from the stream
			count := 256 - int(op)
			if _, err = encoded.Read(dest[next : next+count]); err != nil {
				return
			}
			next += count
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package dns

import (
	"fmt"
	"time"
)

// Envelope is used when doing a zone transfer with a remote server.
type Envelope struct {
	RR    []RR  // The set of RRs in the answer section of the xfr reply message.
	Error error // If something went wrong, this contains the error.
}

// A Transfer defines parameters that are used during a zone transfer.
type Transfer struct {
	*Conn
	DialTimeout    time.Duration     // net.DialTimeout, defaults to 2 seconds
	ReadTimeout    time.Duration     // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds
	WriteTimeout   time.Duration     // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds
	TsigSecret     map[string]string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be fully qualified
	tsigTimersOnly bool
}

// Think we need to away to stop the transfer

// In performs an incoming transfer with the server in a.
// If you would like to set the source IP, or some other attribute
// of a Dialer for a Transfer, you can do so by specifying the attributes
// in the Transfer.Conn:
//
//	d := net.Dialer{LocalAddr: transfer_source}
//	con, err := d.Dial("tcp", master)
//	dnscon := &dns.Conn{Conn:con}
//	transfer = &dns.Transfer{Conn: dnscon}
//	channel, err := transfer.In(message, master)
//
func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
	timeout := dnsTimeout
	if t.DialTimeout != 0 {
		timeout = t.DialTimeout
	}
	if t.Conn == nil {
		t.Conn, err = DialTimeout("tcp", a, timeout)
		if err != nil {
			return nil, err
		}
	}
	if err := t.WriteMsg(q); err != nil {
		return nil, err
	}
	env = make(chan *Envelope)
	go func() {
		if q.Question[0].Qtype == TypeAXFR {
			go t.inAxfr(q.Id, env)
			return
		}
		if q.Question[0].Qtype == TypeIXFR {
			go t.inIxfr(q.Id, env)
			return
		}
	}()
	return env, nil
}

func (t *Transfer) inAxfr(id uint16, c chan *Envelope) {
	first := true
	defer t.Close()
	defer close(c)
	timeout := dnsTimeout
	if t.ReadTimeout != 0 {
		timeout = t.ReadTimeout
	}
	for {
		t.Conn.SetReadDeadline(time.Now().Add(timeout))
		in, err := t.ReadMsg()
		if err != nil {
			c <- &Envelope{nil, err}
			return
		}
		if id != in.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			if in.Rcode != RcodeSuccess {
				c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}}
				return
			}
			if !isSOAFirst(in) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			first = !first
			// only one answer that is SOA, receive more
			if len(in.Answer) == 1 {
				t.tsigTimersOnly = true
				c <- &Envelope{in.Answer, nil}
				continue
			}
		}

		if !first {
			t.tsigTimersOnly = true // Subsequent envelopes use this.
			if isSOALast(in) {
				c <- &Envelope{in.Answer, nil}
				return
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
}

func (t *Transfer) inIxfr(id uint16, c chan *Envelope) {
	serial := uint32(0) // The first serial seen is the current server serial
	first := true
	defer t.Close()
	defer close(c)
	timeout := dnsTimeout
	if t.ReadTimeout != 0 {
		timeout = t.ReadTimeout
	}
	for {
		t.SetReadDeadline(time.Now().Add(timeout))
		in, err := t.ReadMsg()
		if err != nil {
			c <- &Envelope{nil, err}
			return
		}
		if id != in.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			if in.Rcode != RcodeSuccess {
				c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}}
				return
			}
			// A single SOA RR signals "no changes"
			if len(in.Answer) == 1 && isSOAFirst(in) {
				c <- &Envelope{in.Answer, nil}
				return
			}

			// Check if the returned answer is ok
			if !isSOAFirst(in) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			// This serial is important
			serial = in.Answer[0].(*SOA).Serial
			first = !first
		}

		// Now we need to check each message for SOA records, to see what we need to do
		if !first {
			t.tsigTimersOnly = true
			// If the last record in the IXFR contains the servers' SOA,  we should quit
			if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok {
				if v.Serial == serial {
					c <- &Envelope{in.Answer, nil}
					return
				}
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
}

// Out performs an outgoing transfer with the client connecting in w.
// Basic use pattern:
//
//	ch := make(chan *dns.Envelope)
//	tr := new(dns.Transfer)
//	go tr.Out(w, r, ch)
//	ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}}
//	close(ch)
//	w.Hijack()
//	// w.Close() // Client closes connection
//
// The server is responsible for sending the correct sequence of RRs through the
// channel ch.
func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error {
	for x := range ch {
		r := new(Msg)
		// Compress?
		r.SetReply(q)
		r.Authoritative = true
		// assume it fits TODO(miek): fix
		r.Answer = append(r.Answer, x.RR...)
		if err := w.WriteMsg(r); err != nil {
			return err
		}
	}
	w.TsigTimersOnly(true)
	return nil
}

// ReadMsg reads a message from the transfer connection t.
func (t *Transfer) ReadMsg() (*Msg, error) {
	m := new(Msg)
	p := make([]byte, MaxMsgSize)
	n, err := t.Read(p)
	if err != nil && n == 0 {
		return nil, err
	}
	p = p[:n]
	if err := m.Unpack(p); err != nil {
		return nil, err
	}
	if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
		if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
			return m, ErrSecret
		}
		// Need to work on the original message p, as that was used to calculate the tsig.
		err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
		t.tsigRequestMAC = ts.MAC
	}
	return m, err
}

// WriteMsg writes a message through the transfer connection t.
func (t *Transfer) WriteMsg(m *Msg) (err error) {
	var out []byte
	if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
		if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
			return ErrSecret
		}
		out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
	} else {
		out, err = m.Pack()
	}
	if err != nil {
		return err
	}
	if _, err = t.Write(out); err != nil {
		return err
	}
	return nil
}

func isSOAFirst(in *Msg) bool {
	if len(in.Answer) > 0 {
		return in.Answer[0].Header().Rrtype == TypeSOA
	}
	return false
}

func isSOALast(in *Msg) bool {
	if len(in.Answer) > 0 {
		return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA
	}
	return false
}

const errXFR = "bad xfr rcode: %d"
<commit_msg>Fix IXFR may end prematurely (#512) (#507)<commit_after>package dns

import (
	"fmt"
	"time"
)

// Envelope is used when doing a zone transfer with a remote server.
type Envelope struct {
	RR    []RR  // The set of RRs in the answer section of the xfr reply message.
	Error error // If something went wrong, this contains the error.
}

// A Transfer defines parameters that are used during a zone transfer.
type Transfer struct {
	*Conn
	DialTimeout    time.Duration     // net.DialTimeout, defaults to 2 seconds
	ReadTimeout    time.Duration     // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds
	WriteTimeout   time.Duration     // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds
	TsigSecret     map[string]string // Secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be fully qualified
	tsigTimersOnly bool
}

// Think we need to away to stop the transfer

// In performs an incoming transfer with the server in a.
// If you would like to set the source IP, or some other attribute
// of a Dialer for a Transfer, you can do so by specifying the attributes
// in the Transfer.Conn:
//
//	d := net.Dialer{LocalAddr: transfer_source}
//	con, err := d.Dial("tcp", master)
//	dnscon := &dns.Conn{Conn:con}
//	transfer = &dns.Transfer{Conn: dnscon}
//	channel, err := transfer.In(message, master)
//
func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
	timeout := dnsTimeout
	if t.DialTimeout != 0 {
		timeout = t.DialTimeout
	}
	if t.Conn == nil {
		t.Conn, err = DialTimeout("tcp", a, timeout)
		if err != nil {
			return nil, err
		}
	}
	if err := t.WriteMsg(q); err != nil {
		return nil, err
	}
	env = make(chan *Envelope)
	go func() {
		if q.Question[0].Qtype == TypeAXFR {
			go t.inAxfr(q, env)
			return
		}
		if q.Question[0].Qtype == TypeIXFR {
			go t.inIxfr(q, env)
			return
		}
	}()
	return env, nil
}

func (t *Transfer) inAxfr(q *Msg, c chan *Envelope) {
	first := true
	defer t.Close()
	defer close(c)
	timeout := dnsTimeout
	if t.ReadTimeout != 0 {
		timeout = t.ReadTimeout
	}
	for {
		t.Conn.SetReadDeadline(time.Now().Add(timeout))
		in, err := t.ReadMsg()
		if err != nil {
			c <- &Envelope{nil, err}
			return
		}
		if q.Id != in.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if first {
			if in.Rcode != RcodeSuccess {
				c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}}
				return
			}
			if !isSOAFirst(in) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			first = !first
			// only one answer that is SOA, receive more
			if len(in.Answer) == 1 {
				t.tsigTimersOnly = true
				c <- &Envelope{in.Answer, nil}
				continue
			}
		}

		if !first {
			t.tsigTimersOnly = true // Subsequent envelopes use this.
			if isSOALast(in) {
				c <- &Envelope{in.Answer, nil}
				return
			}
			c <- &Envelope{in.Answer, nil}
		}
	}
}

func (t *Transfer) inIxfr(q *Msg, c chan *Envelope) {
	serial := uint32(0) // The first serial seen is the current server serial
	axfr := true
	n := 0
	qser := q.Ns[0].(*SOA).Serial
	defer t.Close()
	defer close(c)
	timeout := dnsTimeout
	if t.ReadTimeout != 0 {
		timeout = t.ReadTimeout
	}
	for {
		t.SetReadDeadline(time.Now().Add(timeout))
		in, err := t.ReadMsg()
		if err != nil {
			c <- &Envelope{nil, err}
			return
		}
		if q.Id != in.Id {
			c <- &Envelope{in.Answer, ErrId}
			return
		}
		if in.Rcode != RcodeSuccess {
			c <- &Envelope{in.Answer, &Error{err: fmt.Sprintf(errXFR, in.Rcode)}}
			return
		}
		if n == 0 {
			// Check if the returned answer is ok
			if !isSOAFirst(in) {
				c <- &Envelope{in.Answer, ErrSoa}
				return
			}
			// This serial is important
			serial = in.Answer[0].(*SOA).Serial
			// Check if there are no changes in zone
			if qser >= serial {
				c <- &Envelope{in.Answer, nil}
				return
			}
		}
		// Now we need to check each message for SOA records, to see what we need to do
		t.tsigTimersOnly = true
		for _, rr := range in.Answer {
			if v, ok := rr.(*SOA); ok {
				if v.Serial == serial {
					n++
					// quit if it's a full axfr or the the servers' SOA is repeated the third time
					if axfr && n == 2 || n == 3 {
						c <- &Envelope{in.Answer, nil}
						return
					}
				} else if axfr {
					// it's an ixfr
					axfr = false
				}
			}
		}
		c <- &Envelope{in.Answer, nil}
	}
}

// Out performs an outgoing transfer with the client connecting in w.
// Basic use pattern:
//
//	ch := make(chan *dns.Envelope)
//	tr := new(dns.Transfer)
//	go tr.Out(w, r, ch)
//	ch <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}}
//	close(ch)
//	w.Hijack()
//	// w.Close() // Client closes connection
//
// The server is responsible for sending the correct sequence of RRs through the
// channel ch.
func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error {
	for x := range ch {
		r := new(Msg)
		// Compress?
		r.SetReply(q)
		r.Authoritative = true
		// assume it fits TODO(miek): fix
		r.Answer = append(r.Answer, x.RR...)
		if err := w.WriteMsg(r); err != nil {
			return err
		}
	}
	w.TsigTimersOnly(true)
	return nil
}

// ReadMsg reads a message from the transfer connection t.
func (t *Transfer) ReadMsg() (*Msg, error) {
	m := new(Msg)
	p := make([]byte, MaxMsgSize)
	n, err := t.Read(p)
	if err != nil && n == 0 {
		return nil, err
	}
	p = p[:n]
	if err := m.Unpack(p); err != nil {
		return nil, err
	}
	if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
		if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
			return m, ErrSecret
		}
		// Need to work on the original message p, as that was used to calculate the tsig.
		err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
		t.tsigRequestMAC = ts.MAC
	}
	return m, err
}

// WriteMsg writes a message through the transfer connection t.
func (t *Transfer) WriteMsg(m *Msg) (err error) {
	var out []byte
	if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil {
		if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok {
			return ErrSecret
		}
		out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly)
	} else {
		out, err = m.Pack()
	}
	if err != nil {
		return err
	}
	if _, err = t.Write(out); err != nil {
		return err
	}
	return nil
}

func isSOAFirst(in *Msg) bool {
	if len(in.Answer) > 0 {
		return in.Answer[0].Header().Rrtype == TypeSOA
	}
	return false
}

func isSOALast(in *Msg) bool {
	if len(in.Answer) > 0 {
		return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA
	}
	return false
}

const errXFR = "bad xfr rcode: %d"
<|endoftext|>