<commit_before>package main

import (
	"errors"
	"html/template"
	"io/ioutil"
	"net/http"
	"regexp"
)

type Page struct {
	Title string
	Body  []byte
}

var templates = template.Must(template.ParseFiles("edit.tpl.html", "view.tpl.html"))

var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")

func (p *Page) save() error {
	filename := p.Title + ".txt"
	return ioutil.WriteFile(filename, p.Body, 0600)
}

func loadPage(title string) (*Page, error) {
	filename := title + ".txt"
	body, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return &Page{Title: title, Body: body}, nil
}

func main() {
	http.HandleFunc("/view/", viewHandler)
	http.HandleFunc("/edit/", editHandler)
	http.HandleFunc("/save/", saveHandler)
	http.ListenAndServe(":8080", nil)
}

func viewHandler(w http.ResponseWriter, r *http.Request) {
	title, err := getTitle(w, r)
	if err != nil {
		return
	}
	p, err := loadPage(title)
	if err != nil {
		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
		return
	}
	renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, r *http.Request) {
	title, err := getTitle(w, r)
	if err != nil {
		return
	}
	p, err := loadPage(title)
	if err != nil {
		p = &Page{Title: title}
	}
	renderTemplate(w, "edit", p)
}

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	err := templates.ExecuteTemplate(w, tmpl+".tpl.html", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func saveHandler(w http.ResponseWriter, r *http.Request) {
	title, err := getTitle(w, r)
	if err != nil {
		return
	}
	body := r.FormValue("body")
	p := &Page{Title: title, Body: []byte(body)}
	err = p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

func getTitle(w http.ResponseWriter, r *http.Request) (string, error) {
	m := validPath.FindStringSubmatch(r.URL.Path)
	if m == nil {
		http.NotFound(w, r)
		return "", errors.New("Invalid Page Title")
	}
	return m[2], nil // The title is the second subexpression.
}
<commit_msg>Use closures<commit_after>package main

import (
	"errors"
	"html/template"
	"io/ioutil"
	"net/http"
	"regexp"
)

type Page struct {
	Title string
	Body  []byte
}

var templates = template.Must(template.ParseFiles("edit.tpl.html", "view.tpl.html"))

var validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")

func (p *Page) save() error {
	filename := p.Title + ".txt"
	return ioutil.WriteFile(filename, p.Body, 0600)
}

func loadPage(title string) (*Page, error) {
	filename := title + ".txt"
	body, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return &Page{Title: title, Body: body}, nil
}

func main() {
	http.HandleFunc("/view/", makeHandler(viewHandler))
	http.HandleFunc("/edit/", makeHandler(editHandler))
	http.HandleFunc("/save/", makeHandler(saveHandler))

	http.ListenAndServe(":8080", nil)
}

func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
	p, err := loadPage(title)
	if err != nil {
		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
		return
	}
	renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, r *http.Request, title string) {
	p, err := loadPage(title)
	if err != nil {
		p = &Page{Title: title}
	}
	renderTemplate(w, "edit", p)
}

func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
	body := r.FormValue("body")
	p := &Page{Title: title, Body: []byte(body)}
	err := p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	err := templates.ExecuteTemplate(w, tmpl+".tpl.html", p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func getTitle(w http.ResponseWriter, r *http.Request) (string, error) {
	m := validPath.FindStringSubmatch(r.URL.Path)
	if m == nil {
		http.NotFound(w, r)
		return "", errors.New("Invalid Page Title")
	}
	return m[2], nil // The title is the second subexpression.
}

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		m := validPath.FindStringSubmatch(r.URL.Path)
		if m == nil {
			http.NotFound(w, r)
			return
		}
		fn(w, r, m[2])
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"html/template"
	"net/http"
	"regexp"
)

const (
	rootPath    = "/"
	viewPath    = "/view/"
	editPath    = "/edit/"
	savePath    = "/save/"
	defaultPage = "Home"
)

var (
	viewTemplate   = template.Must(template.ParseFiles("tmpl/base.html", "tmpl/view.html"))
	editTemplate   = template.Must(template.ParseFiles("tmpl/base.html", "tmpl/edit.html"))
	titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
)

func renderTemplate(w http.ResponseWriter, p *Page, tmpl *template.Template) {
	err := tmpl.Execute(w, p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func getTitle(w http.ResponseWriter, path string, r *http.Request) (title string, err error) {
	title = r.URL.Path[len(path):]
	if !titleValidator.MatchString(title) {
		http.NotFound(w, r)
		err = errors.New("Invalid page title.")
	}
	return
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, viewPath+defaultPage, http.StatusFound)
}

func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
	page, err := loadPage(title)
	if err != nil {
		http.Redirect(w, r, editPath+title, http.StatusFound)
		return
	}

	renderTemplate(w, viewTemplate, page)
}

func editHandler(w http.ResponseWriter, r *http.Request, title string) {
	page, err := loadPage(title)
	if err != nil {
		page = &Page{title, nil}
	}

	renderTemplate(w, editTemplate, page)
}

func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
	body := r.FormValue("body")

	p := &Page{title, []byte(body)}
	err := p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		title, err := getTitle(w, savePath, r)
		if err != nil {
			return
		}
		fn(w, r, title)
	}
}

func main() {
	page := &Page{"TestPage", []byte("This is a sample page.")}
	page.save()

	http.HandleFunc(rootPath, rootHandler)
	http.HandleFunc(viewPath, makeHandler(viewHandler))
	http.HandleFunc(editPath, makeHandler(editHandler))
	http.HandleFunc(savePath, makeHandler(saveHandler))
	http.ListenAndServe("localhost:8080", nil)
}
<commit_msg>Fix template renderer<commit_after>package main

import (
	"errors"
	"html/template"
	"net/http"
	"regexp"
)

const (
	rootPath    = "/"
	viewPath    = "/view/"
	editPath    = "/edit/"
	savePath    = "/save/"
	defaultPage = "Home"
)

var (
	viewTemplate   = template.Must(template.ParseFiles("tmpl/base.html", "tmpl/view.html"))
	editTemplate   = template.Must(template.ParseFiles("tmpl/base.html", "tmpl/edit.html"))
	titleValidator = regexp.MustCompile("^[a-zA-Z0-9]+$")
)

func renderTemplate(w http.ResponseWriter, tmpl *template.Template, p *Page) {
	err := tmpl.Execute(w, p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func getTitle(w http.ResponseWriter, path string, r *http.Request) (title string, err error) {
	title = r.URL.Path[len(path):]
	if !titleValidator.MatchString(title) {
		http.NotFound(w, r)
		err = errors.New("Invalid page title.")
	}
	return
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, viewPath+defaultPage, http.StatusFound)
}

func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
	page, err := loadPage(title)
	if err != nil {
		http.Redirect(w, r, editPath+title, http.StatusFound)
		return
	}

	renderTemplate(w, viewTemplate, page)
}

func editHandler(w http.ResponseWriter, r *http.Request, title string) {
	page, err := loadPage(title)
	if err != nil {
		page = &Page{title, nil}
	}

	renderTemplate(w, editTemplate, page)
}

func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
	body := r.FormValue("body")

	p := &Page{title, []byte(body)}
	err := p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		title, err := getTitle(w, savePath, r)
		if err != nil {
			return
		}
		fn(w, r, title)
	}
}

func main() {
	page := &Page{"TestPage", []byte("This is a sample page.")}
	page.save()

	http.HandleFunc(rootPath, rootHandler)
	http.HandleFunc(viewPath, makeHandler(viewHandler))
	http.HandleFunc(editPath, makeHandler(editHandler))
	http.HandleFunc(savePath, makeHandler(saveHandler))
	http.ListenAndServe("localhost:8080", nil)
}
<|endoftext|>


<commit_before>/* Copyright (c) 2014-2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"
	"strconv"
	"sync"

	"github.com/gorilla/mux"
)

var (
	listen = flag.String("l", ":8080", "Host and port to listen to")
)

func logr(w http.ResponseWriter, r *http.Request) {
	log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL.Path)
}

type handler struct {
	tmpl   *template.Template
	places []Place
	users  []User
	activs []Activity
	m      sync.RWMutex
}

func newHttpHandler() *handler {
	h := &handler{
		users:  getUsers(),
		activs: getActivities(),
	}
	h.addPlaces(getClubs())
	h.addPlaces(getSchools())
	return h
}

func (h *handler) addPlaces(places []Place) {
	for _, p := range places {
		p.Id = len(h.places) + 1
		h.places = append(h.places, p)
	}
}

func (h *handler) index(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	http.ServeFile(w, r, "README.md")
}

func filterPlaces(places []Place, search string) []Place {
	if search == "" {
		return places
	}
	found := []Place{}
	for _, p := range places {
		if !p.Matches(search) {
			continue
		}
		found = append(found, p)
	}
	return found
}

func (h *handler) searchplaces(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	search := mux.Vars(r)["search"]
	places := filterPlaces(h.places, search)
	doMarshal(w, r, &places)
}

func filterActivs(activs []Activity, search string) []Activity {
	if search == "" {
		return activs
	}
	found := []Activity{}
	for _, p := range activs {
		if !p.Matches(search) {
			continue
		}
		found = append(found, p)
	}
	return found
}

func (h *handler) searchactivs(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	h.m.RLock()
	defer h.m.RUnlock()
	search := mux.Vars(r)["search"]
	activs := filterActivs(h.activs, search)
	doMarshal(w, r, &activs)
}

func (h *handler) getplacebyid(id string) (*Place, error) {
	n, err := strconv.Atoi(id)
	if err != nil {
		return nil, err
	}
	if n < 1 || n > len(h.places) {
		return nil, errors.New("place not found")
	}
	return &h.places[n-1], nil
}

func (h *handler) getplace(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	id := mux.Vars(r)["id"]
	p, err := h.getplacebyid(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	doMarshal(w, r, p)
}

func (h *handler) getuserbyid(id string) (*User, error) {
	n, err := strconv.Atoi(id)
	if err != nil {
		return nil, err
	}
	if n < 1 || n > len(h.users) {
		return nil, errors.New("user not found")
	}
	return &h.users[n-1], nil
}

func (h *handler) getuser(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	id := mux.Vars(r)["id"]
	u, err := h.getuserbyid(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	doMarshal(w, r, u)
}

func (h *handler) postactivity(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	h.m.Lock()
	defer h.m.Unlock()
	decoder := json.NewDecoder(r.Body)
	var a Activity
	if err := decoder.Decode(&a); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		log.Println(err)
		return
	}
	a.Id = len(h.activs) + 1
	if a.Cap < 1 {
		a.Cap = 1
	}
	if a.Cap > 50 {
		a.Cap = 50
	}
	if a.Owner < 1 {
		a.Owner = 1
	}
	a.Parts = []int{a.Owner}
	if a.Owner > len(h.users) {
		a.Owner = 1
	}
	h.activs = append(h.activs, a)
	doOK(w, r)
}

func (h *handler) getactivitybyid(id string) (*Activity, error) {
	n, err := strconv.Atoi(id)
	if err != nil {
		return nil, err
	}
	if n < 1 || n > len(h.activs) {
		return nil, errors.New("activity not found")
	}
	return &h.activs[n-1], nil
}

func (h *handler) getactivity(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	h.m.RLock()
	defer h.m.RUnlock()
	id := mux.Vars(r)["id"]
	a, err := h.getactivitybyid(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	doMarshal(w, r, a)
}

func (h *handler) joinactivity(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	h.m.Lock()
	defer h.m.Unlock()
	uid := mux.Vars(r)["uid"]
	u, err := h.getuserbyid(uid)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	aid := mux.Vars(r)["aid"]
	a, err := h.getactivitybyid(aid)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if len(a.Parts) >= a.Cap {
		http.Error(w, "activity already full", http.StatusBadRequest)
		return
	}
	for _, id := range a.Parts {
		if id == u.Id {
			http.Error(w, "user already part of activity", http.StatusBadRequest)
			return
		}
	}
	a.Parts = append(a.Parts, u.Id)
	doOK(w, r)
}

func (h *handler) leaveactivity(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	h.m.Lock()
	defer h.m.Unlock()
	uid := mux.Vars(r)["uid"]
	u, err := h.getuserbyid(uid)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	aid := mux.Vars(r)["aid"]
	a, err := h.getactivitybyid(aid)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if u.Id == a.Owner {
		http.Error(w, "owner cannot leave activity", http.StatusBadRequest)
		return
	}
	found := false
	newparts := []int{}
	for _, id := range a.Parts {
		if id == u.Id {
			found = true
			continue
		}
		newparts = append(newparts, id)
	}
	if !found {
		http.Error(w, "user not part of activity", http.StatusBadRequest)
		return
	}
	a.Parts = newparts
	doOK(w, r)
}

func (h *handler) getusers(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	doMarshal(w, r, &h.users)
}

func (h *handler) getidimg(kind, name string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		logr(w, r)
		id := mux.Vars(r)["id"]
		doFile(w, r, filepath.Join(kind, "img", name, id+".jpg"))
	}
}

func addHeaders(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
}

func doFile(w http.ResponseWriter, r *http.Request, path string) {
	addHeaders(w, r)
	http.ServeFile(w, r, path)
}

func doMarshal(w http.ResponseWriter, r *http.Request, v interface{}) {
	addHeaders(w, r)
	b, err := json.MarshalIndent(v, "", "\t")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Write(b)
}

func doOK(w http.ResponseWriter, r *http.Request) {
	addHeaders(w, r)
	fmt.Fprintln(w, "OK")
}

func main() {
	flag.Parse()
	h := newHttpHandler()
	r := mux.NewRouter()
	r.HandleFunc("/", h.index).Methods("GET")
	r.HandleFunc("/place/{id}", h.getplace).Methods("GET")
	r.HandleFunc("/places", h.searchplaces).Methods("GET")
	r.HandleFunc("/places/", h.searchplaces).Methods("GET")
	r.HandleFunc("/places/{search}", h.searchplaces).Methods("GET")
	r.HandleFunc("/user/{id}", h.getuser).Methods("GET")
	r.HandleFunc("/user/{uid}/join/{aid}", h.joinactivity).Methods("GET")
	r.HandleFunc("/user/{uid}/leave/{aid}", h.leaveactivity).Methods("GET")
	r.HandleFunc("/users", h.getusers).Methods("GET")
	r.HandleFunc("/users/", h.getusers).Methods("GET")
	r.HandleFunc("/activity", h.postactivity).Methods("POST")
	r.HandleFunc("/activity/{id}", h.getactivity).Methods("GET")
	r.HandleFunc("/activities", h.searchactivs).Methods("GET")
	r.HandleFunc("/activities/", h.searchactivs).Methods("GET")
	r.HandleFunc("/img/place/{id}", h.getidimg("const", "place")).Methods("GET")
	r.HandleFunc("/img/user/{id}", h.getidimg("const", "user")).Methods("GET")
	r.HandleFunc("/img/activity/{id}", h.getidimg("var", "activity")).Methods("GET")
	log.Printf("listen = %s", *listen)
	http.Handle("/", r)
	log.Println("Up and running!")
	log.Fatal(http.ListenAndServe(*listen, nil))
}
<commit_msg>fix parts<commit_after>/* Copyright (c) 2014-2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"
	"strconv"
	"sync"

	"github.com/gorilla/mux"
)

var (
	listen = flag.String("l", ":8080", "Host and port to listen to")
)

func logr(w http.ResponseWriter, r *http.Request) {
	log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL.Path)
}

type handler struct {
	tmpl   *template.Template
	places []Place
	users  []User
	activs []Activity
	m      sync.RWMutex
}

func newHttpHandler() *handler {
	h := &handler{
		users:  getUsers(),
		activs: getActivities(),
	}
	h.addPlaces(getClubs())
	h.addPlaces(getSchools())
	return h
}

func (h *handler) addPlaces(places []Place) {
	for _, p := range places {
		p.Id = len(h.places) + 1
		h.places = append(h.places, p)
	}
}

func (h *handler) index(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	http.ServeFile(w, r, "README.md")
}

func filterPlaces(places []Place, search string) []Place {
	if search == "" {
		return places
	}
	found := []Place{}
	for _, p := range places {
		if !p.Matches(search) {
			continue
		}
		found = append(found, p)
	}
	return found
}

func (h *handler) searchplaces(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	search := mux.Vars(r)["search"]
	places := filterPlaces(h.places, search)
	doMarshal(w, r, &places)
}

func filterActivs(activs []Activity, search string) []Activity {
	if search == "" {
		return activs
	}
	found := []Activity{}
	for _, p := range activs {
		if !p.Matches(search) {
			continue
		}
		found = append(found, p)
	}
	return found
}

func (h *handler) searchactivs(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	h.m.RLock()
	defer h.m.RUnlock()
	search := mux.Vars(r)["search"]
	activs := filterActivs(h.activs, search)
	doMarshal(w, r, &activs)
}

func (h *handler) getplacebyid(id string) (*Place, error) {
	n, err := strconv.Atoi(id)
	if err != nil {
		return nil, err
	}
	if n < 1 || n > len(h.places) {
		return nil, errors.New("place not found")
	}
	return &h.places[n-1], nil
}

func (h *handler) getplace(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	id := mux.Vars(r)["id"]
	p, err := h.getplacebyid(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	doMarshal(w, r, p)
}

func (h *handler) getuserbyid(id string) (*User, error) {
	n, err := strconv.Atoi(id)
	if err != nil {
		return nil, err
	}
	if n < 1 || n > len(h.users) {
		return nil, errors.New("user not found")
	}
	return &h.users[n-1], nil
}

func (h *handler) getuser(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	id := mux.Vars(r)["id"]
	u, err := h.getuserbyid(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	doMarshal(w, r, u)
}

func (h *handler) postactivity(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	h.m.Lock()
	defer h.m.Unlock()
	decoder := json.NewDecoder(r.Body)
	var a Activity
	if err := decoder.Decode(&a); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		log.Println(err)
		return
	}
	a.Id = len(h.activs) + 1
	if a.Cap < 1 {
		a.Cap = 1
	}
	if a.Cap > 50 {
		a.Cap = 50
	}
	if a.Owner < 1 {
		a.Owner = 1
	}
	if a.Owner > len(h.users) {
		a.Owner = 1
	}
	a.Parts = []int{a.Owner}
	h.activs = append(h.activs, a)
	doOK(w, r)
}

func (h *handler) getactivitybyid(id string) (*Activity, error) {
	n, err := strconv.Atoi(id)
	if err != nil {
		return nil, err
	}
	if n < 1 || n > len(h.activs) {
		return nil, errors.New("activity not found")
	}
	return &h.activs[n-1], nil
}

func (h *handler) getactivity(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	h.m.RLock()
	defer h.m.RUnlock()
	id := mux.Vars(r)["id"]
	a, err := h.getactivitybyid(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	doMarshal(w, r, a)
}

func (h *handler) joinactivity(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	h.m.Lock()
	defer h.m.Unlock()
	uid := mux.Vars(r)["uid"]
	u, err := h.getuserbyid(uid)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	aid := mux.Vars(r)["aid"]
	a, err := h.getactivitybyid(aid)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if len(a.Parts) >= a.Cap {
		http.Error(w, "activity already full", http.StatusBadRequest)
		return
	}
	for _, id := range a.Parts {
		if id == u.Id {
			http.Error(w, "user already part of activity", http.StatusBadRequest)
			return
		}
	}
	a.Parts = append(a.Parts, u.Id)
	doOK(w, r)
}

func (h *handler) leaveactivity(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	h.m.Lock()
	defer h.m.Unlock()
	uid := mux.Vars(r)["uid"]
	u, err := h.getuserbyid(uid)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	aid := mux.Vars(r)["aid"]
	a, err := h.getactivitybyid(aid)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if u.Id == a.Owner {
		http.Error(w, "owner cannot leave activity", http.StatusBadRequest)
		return
	}
	found := false
	newparts := []int{}
	for _, id := range a.Parts {
		if id == u.Id {
			found = true
			continue
		}
		newparts = append(newparts, id)
	}
	if !found {
		http.Error(w, "user not part of activity", http.StatusBadRequest)
		return
	}
	a.Parts = newparts
	doOK(w, r)
}

func (h *handler) getusers(w http.ResponseWriter, r *http.Request) {
	logr(w, r)
	doMarshal(w, r, &h.users)
}

func (h *handler) getidimg(kind, name string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		logr(w, r)
		id := mux.Vars(r)["id"]
		doFile(w, r, filepath.Join(kind, "img", name, id+".jpg"))
	}
}

func addHeaders(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
}

func doFile(w http.ResponseWriter, r *http.Request, path string) {
	addHeaders(w, r)
	http.ServeFile(w, r, path)
}

func doMarshal(w http.ResponseWriter, r *http.Request, v interface{}) {
	addHeaders(w, r)
	b, err := json.MarshalIndent(v, "", "\t")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Write(b)
}

func doOK(w http.ResponseWriter, r *http.Request) {
	addHeaders(w, r)
	fmt.Fprintln(w, "OK")
}

func main() {
	flag.Parse()
	h := newHttpHandler()
	r := mux.NewRouter()
	r.HandleFunc("/", h.index).Methods("GET")
	r.HandleFunc("/place/{id}", h.getplace).Methods("GET")
	r.HandleFunc("/places", h.searchplaces).Methods("GET")
	r.HandleFunc("/places/", h.searchplaces).Methods("GET")
	r.HandleFunc("/places/{search}", h.searchplaces).Methods("GET")
	r.HandleFunc("/user/{id}", h.getuser).Methods("GET")
	r.HandleFunc("/user/{uid}/join/{aid}", h.joinactivity).Methods("GET")
	r.HandleFunc("/user/{uid}/leave/{aid}", h.leaveactivity).Methods("GET")
	r.HandleFunc("/users", h.getusers).Methods("GET")
	r.HandleFunc("/users/", h.getusers).Methods("GET")
	r.HandleFunc("/activity", h.postactivity).Methods("POST")
	r.HandleFunc("/activity/{id}", h.getactivity).Methods("GET")
	r.HandleFunc("/activities", h.searchactivs).Methods("GET")
	r.HandleFunc("/activities/", h.searchactivs).Methods("GET")
	r.HandleFunc("/img/place/{id}", h.getidimg("const", "place")).Methods("GET")
	r.HandleFunc("/img/user/{id}", h.getidimg("const", "user")).Methods("GET")
	r.HandleFunc("/img/activity/{id}", h.getidimg("var", "activity")).Methods("GET")
	log.Printf("listen = %s", *listen)
	http.Handle("/", r)
	log.Println("Up and running!")
	log.Fatal(http.ListenAndServe(*listen, nil))
}
<|endoftext|>


<commit_before>package nameserver

import (
	"net"
	"time"

	"github.com/miekg/dns"
	. "github.com/weaveworks/weave/common"
)

type uniqZoneRecordKey struct {
	name string
	ipv4 IPv4
}

// A group of ZoneRecords where there are no duplicates (according to the name & IPv4)
type uniqZoneRecords map[uniqZoneRecordKey]ZoneRecord

func newUniqZoneRecords() uniqZoneRecords {
	return make(uniqZoneRecords, 0)
}

// Add a new ZoneRecord to the group
func (uzr *uniqZoneRecords) add(zr ZoneRecord) {
	key := uniqZoneRecordKey{zr.Name(), ipToIPv4(zr.IP())}
	(*uzr)[key] = zr
}

// Return the group as an slice
func (uzr *uniqZoneRecords) toSlice() []ZoneRecord {
	res := make([]ZoneRecord, len(*uzr))
	i := 0
	for _, r := range *uzr {
		res[i] = r
		i++
	}
	return res
}

//////////////////////////////////////////////////////////////////////////////

// Lookup in the database for locally-introduced information
func (zone *ZoneDb) lookup(target string, lfun func(ns *nameSet) []*recordEntry) (res []ZoneRecord, err error) {
	uniq := newUniqZoneRecords()
	for identName, nameset := range zone.idents {
		if identName != defaultRemoteIdent {
			for _, ze := range lfun(nameset) {
				uniq.add(ze)
			}
		}
	}
	if len(uniq) == 0 {
		return nil, LookupError(target)
	}
	return uniq.toSlice(), nil
}

// Perform a lookup for a name in the zone
// The name can be resolved locally with the local database
func (zone *ZoneDb) LookupName(name string) (res []ZoneRecord, err error) {
	zone.mx.RLock()
	defer zone.mx.RUnlock()

	// note: LookupName() is usually called from the mDNS server, so we do not touch the name
	name = dns.Fqdn(name)
	Debug.Printf("[zonedb] Looking for name '%s' in local database", name)
	return zone.lookup(name, func(ns *nameSet) []*recordEntry { return ns.getEntriesForName(name) })
}

// Perform a lookup for a IP address in the zone
// The address can be resolved locally with the local database
func (zone *ZoneDb) LookupInaddr(inaddr string) (res []ZoneRecord, err error) {
	zone.mx.RLock()
	defer zone.mx.RUnlock()

	// note: LookupInaddr() is usually called from the mDNS server, so we do not touch the name
	revIPv4, err := raddrToIPv4(inaddr)
	if err != nil {
		return nil, newParseError("lookup address", inaddr)
	}
	Debug.Printf("[zonedb] Looking for address in local database: '%s' (%s)", revIPv4, inaddr)
	return zone.lookup(inaddr, func(ns *nameSet) []*recordEntry { return ns.getEntriesForIP(revIPv4) })
}

// Perform a domain lookup with mDNS
func (zone *ZoneDb) domainLookup(target string, lfun ZoneLookupFunc) (res []ZoneRecord, err error) {
	// no local results have been obtained in the local database: try with a mDNS query
	Debug.Printf("[zonedb] '%s' not in local database... trying with mDNS", target)
	lanswers, err := lfun(target)
	if err != nil {
		Debug.Printf("[zonedb] mDNS lookup error for '%s': %s", target, err)
		return nil, err
	}

	// if the request has been successful, save the IP in the local database and return the corresponding ZoneRecord
	// (we do not get the remote ident in the mDNS reply, so we save it in a "remote" ident)
	Debug.Printf("[zonedb] adding '%s' (obtained with mDNS) to '%s'", lanswers, target)
	res = make([]ZoneRecord, len(lanswers))
	zone.mx.Lock()
	now := zone.clock.Now()
	uniq := newUniqZoneRecords()
	remoteIdent := zone.getNameSet(defaultRemoteIdent)
	for _, answer := range lanswers {
		r, err := remoteIdent.addIPToName(answer, now)
		if err != nil {
			zone.mx.Unlock()
			Warning.Printf("[zonedb] '%s' insertion for %s failed: %s", answer, target, err)
			return nil, err
		}
		uniq.add(r)
	}
	zone.mx.Unlock()

	return uniq.toSlice(), nil
}

// Perform a lookup for a name in the zone
// The name can be resolved locally with the local database or with some other resolution method (eg, a mDNS query)
func (zone *ZoneDb) DomainLookupName(name string) (res []ZoneRecord, err error) {
	name = dns.Fqdn(name)
	Debug.Printf("[zonedb] Looking for name '%s' in local(&remote) database", name)

	zone.mx.RLock()
	now := zone.clock.Now()
	uniq := newUniqZoneRecords()
	for identName, nameset := range zone.idents {
		for _, ze := range nameset.getEntriesForName(name) {
			// filter the entries with expired TTL
			// locally introduced entries are never expired: they always have TTL=0
			if ze.hasExpired(now) {
				Debug.Printf("[zonedb] '%s': expired entry '%s' ignored: removing", name, ze)
				nameset.deleteNameIP(name, net.IP{})
			} else {
				uniq.add(ze)
			}
		}
		if identName != defaultRemoteIdent {
			nameset.touchName(name, now)
		}
	}
	zone.mx.RUnlock()

	if len(uniq) > 0 {
		Debug.Printf("[zonedb] '%s' resolved in local database", name)
		res = uniq.toSlice()
	} else {
		res, err = zone.domainLookup(name, zone.mdnsCli.LookupName)
	}

	if len(res) > 0 {
		zone.startUpdatingName(name)
		return res, nil
	}
	return nil, LookupError(name)
}

// Perform a lookup for a IP address in the zone
// The address can be resolved either with the local database or
// with some other resolution method (eg, a mDNS query)
func (zone *ZoneDb) DomainLookupInaddr(inaddr string) (res []ZoneRecord, err error) {
	revIPv4, err := raddrToIPv4(inaddr)
	if err != nil {
		return nil, newParseError("lookup address", inaddr)
	}

	Debug.Printf("[zonedb] Looking for address in local(&remote) database: '%s' (%s)", revIPv4, inaddr)

	zone.mx.RLock()
	now := zone.clock.Now()
	uniq := newUniqZoneRecords()
	for identName, nameset := range zone.idents {
		for _, ze := range nameset.getEntriesForIP(revIPv4) {
			// filter the entries with expired TTL
			// locally introduced entries are never expired: they always have TTL=0
			if ze.hasExpired(now) {
				Debug.Printf("[zonedb] '%s': expired entry '%s' ignored: removing", revIPv4, ze)
				nameset.deleteNameIP("", revIPv4.toNetIP())
			} else {
				uniq.add(ze)
				if identName != defaultRemoteIdent {
					nameset.touchName(ze.Name(), now)
				}
			}
		}
	}
	zone.mx.RUnlock()

	if len(uniq) > 0 {
		Debug.Printf("[zonedb] '%s' resolved in local database", inaddr)
		res = uniq.toSlice()
	} else {
		res, err = zone.domainLookup(inaddr, zone.mdnsCli.LookupInaddr)
	}

	if len(res) > 0 {
		// note: even for reverse addresses, we perform the background updates in the name, not in the IP
		//       this simplifies the process and produces basically the same results...
		// note: we do not spend time trying to update names that did not return an initial response...
		for _, r := range res {
			zone.startUpdatingName(r.Name())
		}

		return res, nil
	}
	return nil, LookupError(inaddr)
}

//////////////////////////////////////////////////////////////////////////////

// Names updates try to find all the IPs for a given name with a mDNS query
//
// There are two types of names updates:
//
// - immediate updates.
//   After a `DomainLookup*()` for a name not in the database we will return the
//   first IP we can get with mDNS from other peers. Waiting for more responses would
//   mean more latency in the response to the client, so we send only one answer BUT
//   we also trigger an immediate update request for that name in order to get all
//   the other IPs we didn't wait for...
//
// - periodic updates
//   once we have obtained the first group of IPs for a name, we schedule a periodic
//   refresh for that name, so we keep the list of IPs for that name up to date.
//
// These names updates are repeated until either
//
//  a) there is no interest in the name, determined by a global 'relevant time'
//     and the last time some local client asked about the name,
//     or
//  b) no peers answer one of our refresh requests (because the name has probably
//     disappeared from the network)
//

// Check if we must start updating a name and, in that case, trigger a immediate update
func (zone *ZoneDb) startUpdatingName(name string) {
	if zone.refreshInterval > 0 {
		zone.mx.Lock()
		// check if we should enqueue a refresh request for this name
		n := zone.getNameSet(defaultRemoteIdent).getName(name, true)
		now := zone.clock.Now()
		zone.mx.Unlock() // Don't hold the lock while talking to the SchedQueue
		if n.lastRefreshTime.IsZero() {
			n.lastRefreshTime = now

			Debug.Printf("[zonedb] Creating new immediate refresh request for '%s'", name)
			zone.refreshScheds.Add(func() time.Time { return zone.updater(name) }, now)
		}
	}
}

// Update the IPs we have for a name
func (zone *ZoneDb) updater(name string) (nextTime time.Time) {
	deleteRemoteInfo := func() {
		zone.mx.Lock()
		zone.getNameSet(defaultRemoteIdent).deleteNameIP(name, net.IP{})
		zone.mx.Unlock()
	}

	// if nobody has asked for this name for long time, just forget about it...
	if !zone.IsNameRelevant(name) || zone.IsNameExpired(name) {
		Debug.Printf("[zonedb] '%s' seem to be irrelevant now: removing any remote information", name)
		deleteRemoteInfo()
		return
	}

	// perform the refresh for this name
	fullName := dns.Fqdn(name)
	startTime := zone.clock.Now()
	Debug.Printf("[zonedb] Refreshing name '%s' with mDNS...", fullName)
	res, _ := zone.mdnsCli.InsistentLookupName(fullName)
	if res != nil && len(res) > 0 {
		numIps := len(res)
		zone.mx.Lock()
		now := zone.clock.Now()
		added, removed := zone.getNameSet(defaultRemoteIdent).getName(name, true).updateIPs(res, now)
		zone.mx.Unlock()
		Debug.Printf("[zonedb] Obtained %d IPs for name '%s' with mDNS: %d added, %d removed",
			numIps, name, added, removed)

		// once the name has been updated, we re-schedule the update
		nextTime = startTime.Add(zone.refreshInterval)
		Debug.Printf("[zonedb] Rescheduling update for '%s' in %s", name, nextTime.Sub(zone.clock.Now()))
	} else {
		Debug.Printf("[zonedb] nobody knows about '%s'... removing", name)
		deleteRemoteInfo()
	}
	return
}
<commit_msg>Hold lock while accessing mutable variable, but release before sending on chan<commit_after>package nameserver

import (
	"net"
	"time"

	"github.com/miekg/dns"
	. "github.com/weaveworks/weave/common"
)

type uniqZoneRecordKey struct {
	name string
	ipv4 IPv4
}

// A group of ZoneRecords where there are no duplicates (according to the name & IPv4)
type uniqZoneRecords map[uniqZoneRecordKey]ZoneRecord

func newUniqZoneRecords() uniqZoneRecords {
	return make(uniqZoneRecords, 0)
}

// Add a new ZoneRecord to the group
func (uzr *uniqZoneRecords) add(zr ZoneRecord) {
	key := uniqZoneRecordKey{zr.Name(), ipToIPv4(zr.IP())}
	(*uzr)[key] = zr
}

// Return the group as an slice
func (uzr *uniqZoneRecords) toSlice() []ZoneRecord {
	res := make([]ZoneRecord, len(*uzr))
	i := 0
	for _, r := range *uzr {
		res[i] = r
		i++
	}
	return res
}

//////////////////////////////////////////////////////////////////////////////

// Lookup in the database for locally-introduced information
func (zone *ZoneDb) lookup(target string, lfun func(ns *nameSet) []*recordEntry) (res []ZoneRecord, err error) {
	uniq := newUniqZoneRecords()
	for identName, nameset := range zone.idents {
		if identName != defaultRemoteIdent {
			for _, ze := range lfun(nameset) {
				uniq.add(ze)
			}
		}
	}
	if len(uniq) == 0 {
		return nil, LookupError(target)
	}
	return uniq.toSlice(), nil
}

// Perform a lookup for a name in the zone
// The name can be resolved locally with the local database
func (zone *ZoneDb) LookupName(name string) (res []ZoneRecord, err error) {
	zone.mx.RLock()
	defer zone.mx.RUnlock()

	// note: LookupName() is usually called from the mDNS server, so we do not touch the name
	name = dns.Fqdn(name)
	Debug.Printf("[zonedb] Looking for name '%s' in local database", name)
	return zone.lookup(name, func(ns *nameSet) []*recordEntry { return ns.getEntriesForName(name) })
}

// Perform a lookup for a IP address in the zone
// The address can be resolved locally with the local database
func (zone *ZoneDb) LookupInaddr(inaddr string) (res []ZoneRecord, err error) {
	zone.mx.RLock()
	defer zone.mx.RUnlock()

	// note: LookupInaddr() is usually called from the mDNS server, so we do not touch the name
	revIPv4, err := raddrToIPv4(inaddr)
	if err != nil {
		return nil, newParseError("lookup address", inaddr)
	}
	Debug.Printf("[zonedb] Looking for address in local database: '%s' (%s)", revIPv4, inaddr)
	return zone.lookup(inaddr, func(ns *nameSet) []*recordEntry { return ns.getEntriesForIP(revIPv4) })
}

// Perform a domain lookup with mDNS
func (zone *ZoneDb) domainLookup(target string, lfun ZoneLookupFunc) (res []ZoneRecord, err error) {
	// no local results have been obtained in the local database: try with a mDNS query
	Debug.Printf("[zonedb] '%s' not in local database... trying with mDNS", target)
	lanswers, err := lfun(target)
	if err != nil {
		Debug.Printf("[zonedb] mDNS lookup error for '%s': %s", target, err)
		return nil, err
	}

	// if the request has been successful, save the IP in the local database and return the corresponding ZoneRecord
	// (we do not get the remote ident in the mDNS reply, so we save it in a "remote" ident)
	Debug.Printf("[zonedb] adding '%s' (obtained with mDNS) to '%s'", lanswers, target)
	res = make([]ZoneRecord, len(lanswers))
	zone.mx.Lock()
	now := zone.clock.Now()
	uniq := newUniqZoneRecords()
	remoteIdent := zone.getNameSet(defaultRemoteIdent)
	for _, answer := range lanswers {
		r, err := remoteIdent.addIPToName(answer, now)
		if err != nil {
			zone.mx.Unlock()
			Warning.Printf("[zonedb] '%s' insertion for %s failed: %s", answer, target, err)
			return nil, err
		}
		uniq.add(r)
	}
	zone.mx.Unlock()

	return uniq.toSlice(), nil
}

// Perform a lookup for a name in the zone
// The name can be resolved locally with the local database or with some other resolution method (eg, a mDNS query)
func (zone *ZoneDb) DomainLookupName(name string) (res []ZoneRecord, err error) {
	name = dns.Fqdn(name)
	Debug.Printf("[zonedb] Looking for name '%s' in local(&remote) database", name)

	zone.mx.RLock()
	now := zone.clock.Now()
	uniq := newUniqZoneRecords()
	for identName, nameset := range zone.idents {
		for _, ze := range nameset.getEntriesForName(name) {
			// filter the entries with expired TTL
			// locally introduced entries are never expired: they always have TTL=0
			if ze.hasExpired(now) {
				Debug.Printf("[zonedb] '%s': expired entry '%s' ignored: removing", name, ze)
				nameset.deleteNameIP(name, net.IP{})
			} else {
				uniq.add(ze)
			}
		}
		if identName != defaultRemoteIdent {
			nameset.touchName(name, now)
		}
	}
	zone.mx.RUnlock()

	if len(uniq) > 0 {
		Debug.Printf("[zonedb] '%s' resolved in local database", name)
		res = uniq.toSlice()
	} else {
		res, err = zone.domainLookup(name, zone.mdnsCli.LookupName)
	}

	if len(res) > 0 {
		zone.startUpdatingName(name)
		return res, nil
	}
	return nil, LookupError(name)
}

// Perform a lookup for a IP address in the zone
// The address can be resolved either with the local database or
// with some other resolution method (eg, a mDNS query)
func (zone *ZoneDb) DomainLookupInaddr(inaddr string) (res []ZoneRecord, err error) {
	revIPv4, err := raddrToIPv4(inaddr)
	if err != nil {
		return nil, newParseError("lookup address", inaddr)
	}

	Debug.Printf("[zonedb] Looking for address in local(&remote) database: '%s' (%s)", revIPv4, inaddr)

	zone.mx.RLock()
	now := zone.clock.Now()
	uniq := newUniqZoneRecords()
	for identName, nameset := range zone.idents {
		for _, ze := range nameset.getEntriesForIP(revIPv4) {
			// filter the entries with expired TTL
			// locally introduced entries are never expired: they always have TTL=0
			if ze.hasExpired(now) {
				Debug.Printf("[zonedb] '%s': expired entry '%s' ignored: removing", revIPv4, ze)
				nameset.deleteNameIP("", revIPv4.toNetIP())
			} else {
				uniq.add(ze)
				if identName != defaultRemoteIdent {
					nameset.touchName(ze.Name(), now)
				}
			}
		}
	}
	zone.mx.RUnlock()

	if len(uniq) > 0 {
		Debug.Printf("[zonedb] '%s' resolved in local database", inaddr)
		res = uniq.toSlice()
	} else {
		res, err = zone.domainLookup(inaddr, zone.mdnsCli.LookupInaddr)
	}

	if len(res) > 0 {
		// note: even for reverse addresses, we perform the background updates in the name, not in the IP
		//       this simplifies the process and produces basically the same results...
		// note: we do not spend time trying to update names that did not return an initial response...
		for _, r := range res {
			zone.startUpdatingName(r.Name())
		}

		return res, nil
	}
	return nil, LookupError(inaddr)
}

//////////////////////////////////////////////////////////////////////////////

// Names updates try to find all the IPs for a given name with a mDNS query
//
// There are two types of names updates:
//
// - immediate updates.
//   After a `DomainLookup*()` for a name not in the database we will return the
//   first IP we can get with mDNS from other peers. Waiting for more responses would
//   mean more latency in the response to the client, so we send only one answer BUT
//   we also trigger an immediate update request for that name in order to get all
//   the other IPs we didn't wait for...
//
// - periodic updates
//   once we have obtained the first group of IPs for a name, we schedule a periodic
//   refresh for that name, so we keep the list of IPs for that name up to date.
//
// These names updates are repeated until either
//
//  a) there is no interest in the name, determined by a global 'relevant time'
//     and the last time some local client asked about the name,
//     or
//  b) no peers answer one of our refresh requests (because the name has probably
//     disappeared from the network)
//

// Check if we must start updating a name and, in that case, trigger a immediate update
func (zone *ZoneDb) startUpdatingName(name string) {
	if zone.refreshInterval > 0 {
		zone.mx.Lock()

		// check if we should enqueue a refresh request for this name
		n := zone.getNameSet(defaultRemoteIdent).getName(name, true)
		if n.lastRefreshTime.IsZero() {
			now := zone.clock.Now()
			n.lastRefreshTime = now
			zone.mx.Unlock()

			Debug.Printf("[zonedb] Creating new immediate refresh request for '%s'", name)
			zone.refreshScheds.Add(func() time.Time { return zone.updater(name) }, now)
		} else {
			zone.mx.Unlock()
		}
	}
}

// Update the IPs we have for a name
func (zone *ZoneDb) updater(name string) (nextTime time.Time) {
	deleteRemoteInfo := func() {
		zone.mx.Lock()
		zone.getNameSet(defaultRemoteIdent).deleteNameIP(name, net.IP{})
		zone.mx.Unlock()
	}

	// if nobody has asked for this name for long time, just forget about it...
	if !zone.IsNameRelevant(name) || zone.IsNameExpired(name) {
		Debug.Printf("[zonedb] '%s' seem to be irrelevant now: removing any remote information", name)
		deleteRemoteInfo()
		return
	}

	// perform the refresh for this name
	fullName := dns.Fqdn(name)
	startTime := zone.clock.Now()
	Debug.Printf("[zonedb] Refreshing name '%s' with mDNS...", fullName)
	res, _ := zone.mdnsCli.InsistentLookupName(fullName)
	if res != nil && len(res) > 0 {
		numIps := len(res)
		zone.mx.Lock()
		now := zone.clock.Now()
		added, removed := zone.getNameSet(defaultRemoteIdent).getName(name, true).updateIPs(res, now)
		zone.mx.Unlock()
		Debug.Printf("[zonedb] Obtained %d IPs for name '%s' with mDNS: %d added, %d removed",
			numIps, name, added, removed)

		// once the name has been updated, we re-schedule the update
		nextTime = startTime.Add(zone.refreshInterval)
		Debug.Printf("[zonedb] Rescheduling update for '%s' in %s", name, nextTime.Sub(zone.clock.Now()))
	} else {
		Debug.Printf("[zonedb] nobody knows about '%s'... removing", name)
		deleteRemoteInfo()
	}
	return
}
<|endoftext|>


<commit_before>// This is free and unencumbered software released into the public
// domain.  For more information, see <http://unlicense.org> or the
// accompanying UNLICENSE file.

package navigator

import (
	"fmt"
	"go/token"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/fsnotify/fsnotify"
	"github.com/nelsam/gxui"
	"github.com/nelsam/gxui/math"
	"github.com/nelsam/gxui/mixins"
	"github.com/nelsam/gxui/themes/basic"
	"github.com/nelsam/vidar/controller"
	"github.com/nelsam/vidar/editor"
	"github.com/nelsam/vidar/settings"
)

const maxWatchDirs = 4096

var (
	dirColor = gxui.Color{
		R: 0.8,
		G: 1,
		B: 0.7,
		A: 1,
	}
	dropColor = gxui.Color{
		R: 0.7,
		G: 0.7,
		B: 0.1,
		A: 1,
	}
	fileColor = gxui.Gray80

	splitterBarBackgroundColor = gxui.Color{
		R: 0.6,
		G: 0.3,
		B: 0.3,
		A: 1,
	}
	splitterBarForegroundColor = gxui.Color{
		R: 0.8,
		G: 0.4,
		B: 0.4,
		A: 1,
	}
)

type Locationer interface {
	File() string
	Position() token.Position
}

type ProjectTree struct {
	button gxui.Button

	driver gxui.Driver
	theme  *basic.Theme

	callback func(controller.Executor)
	dirs     *directory
	tocCtl   gxui.Control
	toc      *TOC
	tocLock  sync.RWMutex

	watcher    *fsnotify.Watcher
	reloadLock chan struct{}

	layout *splitterLayout
}

func NewProjectTree(driver gxui.Driver, window gxui.Window, theme *basic.Theme) *ProjectTree {
	tree := &ProjectTree{
		driver:     driver,
		theme:      theme,
		reloadLock: make(chan struct{}, 1),
		button:     createIconButton(driver, theme, "folder.png"),
		layout:     newSplitterLayout(window, theme),
	}
	tree.layout.SetOrientation(gxui.Vertical)
	tree.SetProject(settings.DefaultProject)

	return tree
}

func (p *ProjectTree) SetTOC(toc *TOC) {
	p.tocLock.Lock()
	defer p.tocLock.Unlock()
	p.toc = toc
}

func (p *ProjectTree) TOC() *TOC {
	p.tocLock.RLock()
	defer p.tocLock.RUnlock()
	return p.toc
}

func (p *ProjectTree) Button() gxui.Button {
	return p.button
}

func (p *ProjectTree) SetRoot(path string) {
	p.layout.RemoveAll()
	p.SetTOC(nil)
	p.tocCtl = nil

	if p.watcher != nil {
		p.watcher.Close()
	}
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Printf("Error creating project tree watcher: %s", err)
	}
	p.watcher = watcher
	p.startWatch(path)

	p.dirs = newDirectory(p, path)
	scrollable := p.theme.CreateScrollLayout()
	// Disable horiz scrolling until we can figure out an accurate
	// way to calculate our width.
	scrollable.SetScrollAxis(false, true)
	scrollable.SetChild(p.dirs)
	p.layout.AddChild(scrollable)
	p.layout.SetChildWeight(p.dirs, 1)

	// Expand the top level
	p.dirs.button.Click(gxui.MouseEvent{})
}

func (p *ProjectTree) startWatch(root string) {
	if p.watcher == nil {
		return
	}

	count := 0
	err := filepath.Walk(root, func(path string, finfo os.FileInfo, err error) error {
		if err != nil {
			return fmt.Errorf("Error walking directory %s: %s", root, err)
		}
		if count > maxWatchDirs {
			p.watcher.Close()
			return fmt.Errorf("Could not watch project: exceeded directory watch limit of %d", maxWatchDirs)
		}
		if finfo.IsDir() {
			count++
			p.watcher.Add(path)
		}
		return nil
	})
	if err != nil {
		log.Printf("Warning: %s", err)
		return
	}
	go p.watch()
	go p.watchErrs()
}

func (p *ProjectTree) watchErrs() {
	for err := range p.watcher.Errors {
		log.Printf("Watcher received error %s", err)
	}
}

// watch waits for events from p.watcher.  For each event, the tree will
// spin off a goroutine to update the its children.
//
// Events are processed in separate goroutines to help us keep up with
// rapidly occurring events, e.g. in the case of a `git checkout` that
// touches many, many files and directories.  It doesn't completely
// prevent UI lock up, but it mitigates it some.
func (p *ProjectTree) watch() {
	for e := range p.watcher.Events {
		switch e.Op {
		case fsnotify.Write, fsnotify.Create, fsnotify.Remove, fsnotify.Rename:
			go p.update(e.Name)
		}
	}
}

// update will update any parts of p that changes to path would affect.
//
// If other concurrent calls to update are running, update may bail out
// in order to prevent locking up the UI.
//
// KNOWN ISSUE: This logic could cause the UI to get out of sync with
// the filesystem.  Either an event for /bar could be ignored while
// we process an event for /foo, or an event for /foo could be ignored
// when the current state of the filesystem has already been processed,
// but before the lock has been released.
//
// So far, though, I haven't seen a situation where fsnotify overwhelms
// this logic to the point that the UI displays incorrect data, so maybe
// it's good enough?  I'll be keeping my eye out for the UI getting in
// to a bad state, but I'm not going to solve the issue until I know
// it really is an issue.
func (p *ProjectTree) update(path string) {
	select {
	case p.reloadLock <- struct{}{}:
	default:
		return
	}
	defer func() {
		<-p.reloadLock
	}()

	p.driver.CallSync(func() {
		p.dirs.update(path)
	})
	toc := p.TOC()
	if toc != nil && strings.HasPrefix(path, toc.dir) {
		p.driver.CallSync(toc.Reload)
	}
}

func (p *ProjectTree) SetProject(project settings.Project) {
	p.SetRoot(project.Path)
}

func (p *ProjectTree) Open(path string, pos token.Position) {
	dir, _ := filepath.Split(path)
	p.dirs.ExpandTo(dir)
}

func (p *ProjectTree) Frame() gxui.Control {
	return p.layout
}

func (p *ProjectTree) OnComplete(callback func(controller.Executor)) {
	p.callback = callback
	go attachCallback(p.TOC(), callback)
}

type splitterLayout struct {
	mixins.SplitterLayout

	window gxui.Window
	theme  gxui.Theme
}

func newSplitterLayout(window gxui.Window, theme gxui.Theme) *splitterLayout {
	l := &splitterLayout{
		window: window,
		theme:  theme,
	}
	l.Init(l, theme)
	return l
}

func (l *splitterLayout) DesiredSize(min, max math.Size) math.Size {
	s := l.SplitterLayout.DesiredSize(min, max)
	width := 20 * l.theme.DefaultMonospaceFont().GlyphMaxSize().W
	if min.W > width {
		width = min.W
	}
	if max.W < width {
		width = max.W
	}
	s.W = width
	return s
}

func (l *splitterLayout) CreateSplitterBar() gxui.Control {
	bar := editor.NewSplitterBar(l.window.Viewport(), l.theme)
	bar.OnSplitterDragged(func(wndPnt math.Point) { l.SplitterDragged(bar, wndPnt) })
	return bar
}

type parent interface {
	Children() gxui.Children
}

type irrespParent interface {
	MissingChild() gxui.Control
}

type selectionButton interface {
	OnSelected(func(controller.Executor))
}

func attachCallback(control gxui.Control, callback func(controller.Executor)) {
	if b, ok := control.(selectionButton); ok {
		b.OnSelected(callback)
	}
	if p, ok := control.(parent); ok {
		for _, c := range p.Children() {
			attachCallback(c.Control, callback)
		}
	}
	if p, ok := control.(irrespParent); ok {
		attachCallback(p.MissingChild(), callback)
	}
}
<commit_msg>Don't watch hidden directories<commit_after>// This is free and unencumbered software released into the public
// domain.  For more information, see <http://unlicense.org> or the
// accompanying UNLICENSE file.

package navigator

import (
	"fmt"
	"go/token"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/fsnotify/fsnotify"
	"github.com/nelsam/gxui"
	"github.com/nelsam/gxui/math"
	"github.com/nelsam/gxui/mixins"
	"github.com/nelsam/gxui/themes/basic"
	"github.com/nelsam/vidar/controller"
	"github.com/nelsam/vidar/editor"
	"github.com/nelsam/vidar/settings"
)

// maxWatchDirs is here just to ensure that we don't take too long to
// open up a project.  If the project has an excessive number of files,
// watching will fail when it hits this number.
//
// TODO: think about alternate limits, and possibly rely on polling
// when watching fails.
const maxWatchDirs = 4096

var (
	dirColor = gxui.Color{
		R: 0.8,
		G: 1,
		B: 0.7,
		A: 1,
	}
	dropColor = gxui.Color{
		R: 0.7,
		G: 0.7,
		B: 0.1,
		A: 1,
	}
	fileColor = gxui.Gray80

	splitterBarBackgroundColor = gxui.Color{
		R: 0.6,
		G: 0.3,
		B: 0.3,
		A: 1,
	}
	splitterBarForegroundColor = gxui.Color{
		R: 0.8,
		G: 0.4,
		B: 0.4,
		A: 1,
	}
)

type Locationer interface {
	File() string
	Position() token.Position
}

type ProjectTree struct {
	button gxui.Button

	driver gxui.Driver
	theme  *basic.Theme

	callback func(controller.Executor)
	dirs     *directory
	tocCtl   gxui.Control
	toc      *TOC
	tocLock  sync.RWMutex

	watcher    *fsnotify.Watcher
	reloadLock chan struct{}

	layout *splitterLayout
}

func NewProjectTree(driver gxui.Driver, window gxui.Window, theme *basic.Theme) *ProjectTree {
	tree := &ProjectTree{
		driver:     driver,
		theme:      theme,
		reloadLock: make(chan struct{}, 1),
		button:     createIconButton(driver, theme, "folder.png"),
		layout:     newSplitterLayout(window, theme),
	}
	tree.layout.SetOrientation(gxui.Vertical)
	tree.SetProject(settings.DefaultProject)

	return tree
}

func (p *ProjectTree) SetTOC(toc *TOC) {
	p.tocLock.Lock()
	defer p.tocLock.Unlock()
	p.toc = toc
}

func (p *ProjectTree) TOC() *TOC {
	p.tocLock.RLock()
	defer p.tocLock.RUnlock()
	return p.toc
}

func (p *ProjectTree) Button() gxui.Button {
	return p.button
}

func (p *ProjectTree) SetRoot(path string) {
	p.layout.RemoveAll()
	p.SetTOC(nil)
	p.tocCtl = nil

	if p.watcher != nil {
		p.watcher.Close()
	}
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Printf("Error creating project tree watcher: %s", err)
	}
	p.watcher = watcher
	p.startWatch(path)

	p.dirs = newDirectory(p, path)
	scrollable := p.theme.CreateScrollLayout()
	// Disable horiz scrolling until we can figure out an accurate
	// way to calculate our width.
	scrollable.SetScrollAxis(false, true)
	scrollable.SetChild(p.dirs)
	p.layout.AddChild(scrollable)
	p.layout.SetChildWeight(p.dirs, 1)

	// Expand the top level
	p.dirs.button.Click(gxui.MouseEvent{})
}

func (p *ProjectTree) startWatch(root string) {
	if p.watcher == nil {
		return
	}

	count := 0
	err := filepath.Walk(root, func(path string, finfo os.FileInfo, err error) error {
		if err != nil {
			return fmt.Errorf("Error walking directory %s: %s", root, err)
		}
		if finfo.IsDir() {
			if filepath.Base(path)[0] == '.' {
				// This is mostly to skip .git directories, which contain
				// a pretty deep structure and can eat up a lot of our
				// watches.  Especially important on OS X, where the default
				// max number of watches is 256.
				return filepath.SkipDir
			}
			count++
			p.watcher.Add(path)
		}
		if count > maxWatchDirs {
			p.watcher.Close()
			return fmt.Errorf("Could not watch project: exceeded directory watch limit of %d", maxWatchDirs)
		}
		return nil
	})
	if err != nil {
		log.Printf("Warning: %s", err)
		return
	}
	go p.watch()
	go p.watchErrs()
}

func (p *ProjectTree) watchErrs() {
	for err := range p.watcher.Errors {
		log.Printf("Watcher received error %s", err)
	}
}

// watch waits for events from p.watcher.  For each event, the tree will
// spin off a goroutine to update the its children.
//
// Events are processed in separate goroutines to help us keep up with
// rapidly occurring events, e.g. in the case of a `git checkout` that
// touches many, many files and directories.  It doesn't completely
// prevent UI lock up, but it mitigates it some.
func (p *ProjectTree) watch() {
	for e := range p.watcher.Events {
		switch e.Op {
		case fsnotify.Write, fsnotify.Create, fsnotify.Remove, fsnotify.Rename:
			go p.update(e.Name)
		}
	}
}

// update will update any parts of p that changes to path would affect.
//
// If other concurrent calls to update are running, update may bail out
// in order to prevent locking up the UI.
//
// KNOWN ISSUE: This logic could cause the UI to get out of sync with
// the filesystem.  Either an event for /bar could be ignored while
// we process an event for /foo, or an event for /foo could be ignored
// when the current state of the filesystem has already been processed,
// but before the lock has been released.
//
// So far, though, I haven't seen a situation where fsnotify overwhelms
// this logic to the point that the UI displays incorrect data, so maybe
// it's good enough?  I'll be keeping my eye out for the UI getting in
// to a bad state, but I'm not going to solve the issue until I know
// it really is an issue.
func (p *ProjectTree) update(path string) {
	select {
	case p.reloadLock <- struct{}{}:
	default:
		return
	}
	defer func() {
		<-p.reloadLock
	}()

	p.driver.CallSync(func() {
		p.dirs.update(path)
	})
	toc := p.TOC()
	if toc != nil && strings.HasPrefix(path, toc.dir) {
		p.driver.CallSync(toc.Reload)
	}
}

func (p *ProjectTree) SetProject(project settings.Project) {
	p.SetRoot(project.Path)
}

func (p *ProjectTree) Open(path string, pos token.Position) {
	dir, _ := filepath.Split(path)
	p.dirs.ExpandTo(dir)
}

func (p *ProjectTree) Frame() gxui.Control {
	return p.layout
}

func (p *ProjectTree) OnComplete(callback func(controller.Executor)) {
	p.callback = callback
	go attachCallback(p.TOC(), callback)
}

type splitterLayout struct {
	mixins.SplitterLayout

	window gxui.Window
	theme  gxui.Theme
}

func newSplitterLayout(window gxui.Window, theme gxui.Theme) *splitterLayout {
	l := &splitterLayout{
		window: window,
		theme:  theme,
	}
	l.Init(l, theme)
	return l
}

func (l *splitterLayout) DesiredSize(min, max math.Size) math.Size {
	s := l.SplitterLayout.DesiredSize(min, max)
	width := 20 * l.theme.DefaultMonospaceFont().GlyphMaxSize().W
	if min.W > width {
		width = min.W
	}
	if max.W < width {
		width = max.W
	}
	s.W = width
	return s
}

func (l *splitterLayout) CreateSplitterBar() gxui.Control {
	bar := editor.NewSplitterBar(l.window.Viewport(), l.theme)
	bar.OnSplitterDragged(func(wndPnt math.Point) { l.SplitterDragged(bar, wndPnt) })
	return bar
}

type parent interface {
	Children() gxui.Children
}

type irrespParent interface {
	MissingChild() gxui.Control
}

type selectionButton interface {
	OnSelected(func(controller.Executor))
}

func attachCallback(control gxui.Control, callback func(controller.Executor)) {
	if b, ok := control.(selectionButton); ok {
		b.OnSelected(callback)
	}
	if p, ok := control.(parent); ok {
		for _, c := range p.Children() {
			attachCallback(c.Control, callback)
		}
	}
	if p, ok := control.(irrespParent); ok {
		attachCallback(p.MissingChild(), callback)
	}
}
<|endoftext|>


<commit_before>package genetics

import "github.com/yaricom/goNEAT/neat/network"

// Organisms are Genomes and Networks with fitness information,
// i.e. the genotype and phenotype together.
type Organism struct {
	// A measure of fitness for the Organism
	Fitness float64
	// A fitness measure that won't change during adjustments
	OriginalFitness float64

	// Win marker (if needed for a particular task)
	IsWinner bool

	// The Organism's phenotype
	Net *network.Network
	// The Organism's genotype
	GNome *Genome
	// The Species of the Organism
	SpeciesOf *Species

	// Number of children this Organism may have
	ExpectedOffspring int64
	// Tells which generation this Organism is from
	Generation int32

	// Marker for destruction of inferior Organisms
	ToEliminate bool
	// Marks the species champion
	IsChampion bool

	// Number of reserved offspring for a population leader
	SuperChampOffspring int
	// Marks the best in population
	IsPopulationChampion bool
	// Marks the duplicate child of a champion (for tracking purposes)
	IsPopulationChampionChild bool

	// DEBUG variable - highest fitness of champ
	HighestFitness float64

	// Track its origin - for debugging or analysis - we can tell how the organism was born
	MutationStructBaby bool
	MateBaby bool
}

// Creates new organism with specified genome, fitness and given generation number
func NewOrganism(fit float64, g *Genome, gen int32) *Organism {
	return &Organism{
		Fitness:fit,
		GNome:g,
		Net:g.Genesis(g.GenomeId),
		Generation:gen,
	}
}

// Regenerate the network based on a change in the genotype
func (o *Organism) UpdatePhenotype() {
	// First, delete the old phenotype (net)
	o.Net = nil

	// Now, recreate the phenotype off the new genotype
	o.Net = o.GNome.Genesis(o.GNome.GenomeId)
}
<commit_msg>Added sort by fitness and to string<commit_after>package genetics

import (
	"github.com/yaricom/goNEAT/neat/network"
	"fmt"
)

// Organisms are Genomes and Networks with fitness information,
// i.e. the genotype and phenotype together.
type Organism struct {
	// A measure of fitness for the Organism
	Fitness                   float64
	// A fitness measure that won't change during adjustments
	OriginalFitness           float64

	// Win marker (if needed for a particular task)
	IsWinner                  bool

	// The Organism's phenotype
	Net                       *network.Network
	// The Organism's genotype
	GNome                     *Genome
	// The Species of the Organism
	SpeciesOf                 *Species

	// Number of children this Organism may have
	ExpectedOffspring         int64
	// Tells which generation this Organism is from
	Generation                int32

	// Marker for destruction of inferior Organisms
	ToEliminate               bool
	// Marks the species champion
	IsChampion                bool

	// Number of reserved offspring for a population leader
	SuperChampOffspring       int
	// Marks the best in population
	IsPopulationChampion      bool
	// Marks the duplicate child of a champion (for tracking purposes)
	IsPopulationChampionChild bool

	// DEBUG variable - highest fitness of champ
	HighestFitness            float64

	// Track its origin - for debugging or analysis - we can tell how the organism was born
	MutationStructBaby        bool
	MateBaby                  bool
}

// Creates new organism with specified genome, fitness and given generation number
func NewOrganism(fit float64, g *Genome, generation int32) *Organism {
	return &Organism{
		Fitness:fit,
		GNome:g,
		Net:g.Genesis(g.GenomeId),
		Generation:generation,
	}
}

// Regenerate the network based on a change in the genotype
func (o *Organism) UpdatePhenotype() {
	// First, delete the old phenotype (net)
	o.Net = nil

	// Now, recreate the phenotype off the new genotype
	o.Net = o.GNome.Genesis(o.GNome.GenomeId)
}

func (o *Organism) String() string {
	return fmt.Sprintf("[Organism, fitness: %f]", o.Fitness)
}

// ByFitness implements sort.Interface for []Organism based on the Fitness field.
type ByFitness []*Organism
func (f ByFitness) Len() int {
	return len(f)
}
func (f ByFitness) Swap(i, j int) {
	f[i], f[j] = f[j], f[i]
}
func (f ByFitness) Less(i, j int) bool {
	return f[i].Fitness < f[j].Fitness
}
<|endoftext|>


<commit_before>// Copyright 2016 Markus Lindenberg
//
// 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 main

import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"

	"gopkg.in/mcuadros/go-syslog.v2"
)

const (
	namespace = "nginx_request"
)

func main() {
	var (
		listenAddress = flag.String("web.listen-address", ":9147", "Address to listen on for web interface and telemetry.")
		metricsPath   = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
		syslogAddress = flag.String("nginx.syslog-address", "127.0.0.1:9514", "Syslog listen address/socket for Nginx.")
	)
	flag.Parse()

	// Listen to signals
	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGINT)

	// Set up syslog server
	channel := make(syslog.LogPartsChannel, 20000)
	handler := syslog.NewChannelHandler(channel)
	server := syslog.NewServer()
	server.SetFormat(syslog.RFC3164)
	server.SetHandler(handler)

	var err error
	if strings.HasPrefix(*syslogAddress, "unix:") {
		err = server.ListenUnixgram(strings.TrimPrefix(*syslogAddress, "unix:"))
	} else {
		err = server.ListenUDP(*syslogAddress)
	}
	if err != nil {
		log.Fatal(err)
	}
	err = server.Boot()
	if err != nil {
		log.Fatal(err)
	}

	// Setup metrics
	syslogMessages := prometheus.NewCounter(prometheus.CounterOpts{
		Namespace: namespace,
		Name:      "exporter_syslog_messages",
		Help:      "Current total syslog messages received.",
	})
	err = prometheus.Register(syslogMessages)
	if err != nil {
		log.Fatal(err)
	}
	syslogParseFailures := prometheus.NewCounter(prometheus.CounterOpts{
		Namespace: namespace,
		Name:      "exporter_syslog_parse_failure",
		Help:      "Number of errors while parsing syslog messages.",
	})
	err = prometheus.Register(syslogParseFailures)
	if err != nil {
		log.Fatal(err)
	}
	var msgs int64
	go func() {
		for part := range channel {
			syslogMessages.Inc()
			msgs++
			tag, _ := part["tag"].(string)
			if tag != "nginx" {
				log.Warn("Ignoring syslog message with wrong tag")
				syslogParseFailures.Inc()
				continue
			}
			server, _ := part["hostname"].(string)
			if server == "" {
				log.Warn("Hostname missing in syslog message")
				syslogParseFailures.Inc()
				continue
			}

			content, _ := part["content"].(string)
			if content == "" {
				log.Warn("Ignoring empty syslog message")
				syslogParseFailures.Inc()
				continue
			}

			metrics, labels, err := parseMessage(content)
			if err != nil {
				log.Error(err)
				continue
			}
			for _, metric := range metrics {
				var collector prometheus.Collector
				collector = prometheus.NewHistogramVec(prometheus.HistogramOpts{
					Namespace: namespace,
					Name:      metric.Name,
					Help:      fmt.Sprintf("Nginx request log value for %s", metric.Name),
				}, labels.Names)
				err = prometheus.Register(collector)
				if err != nil {
					log.Error(err)
					continue
				}
				collector.(*prometheus.HistogramVec).WithLabelValues(labels.Values...).Observe(metric.Value)
			}
		}
	}()

	// Setup HTTP server
	http.Handle(*metricsPath, prometheus.Handler())
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
             <title>Nginx Request Exporter</title>

Nginx Request Exporter

<a href='` + *metricsPath + `'>Metrics

             `))
	})

	go func() {
		log.Infof("Starting Server: %s", *listenAddress)
		log.Fatal(http.ListenAndServe(*listenAddress, nil))
	}()

	s := <-sigchan
	log.Infof("Received %v, terminating", s)
	log.Infof("Messages received: %d", msgs)
	err = server.Kill()
	if err != nil {
		log.Error(err)
	}
	os.Exit(0)
}
<commit_msg>Accounted for AlreadyRegisteredError<commit_after>// Copyright 2016 Markus Lindenberg
//
// 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 main

import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"syscall"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"

	"gopkg.in/mcuadros/go-syslog.v2"
)

const (
	namespace = "nginx_request"
)

func main() {
	var (
		listenAddress = flag.String("web.listen-address", ":9147", "Address to listen on for web interface and telemetry.")
		metricsPath   = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.")
		syslogAddress = flag.String("nginx.syslog-address", "127.0.0.1:9514", "Syslog listen address/socket for Nginx.")
	)
	flag.Parse()

	// Listen to signals
	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGINT)

	// Set up syslog server
	channel := make(syslog.LogPartsChannel, 20000)
	handler := syslog.NewChannelHandler(channel)
	server := syslog.NewServer()
	server.SetFormat(syslog.RFC3164)
	server.SetHandler(handler)

	var err error
	if strings.HasPrefix(*syslogAddress, "unix:") {
		err = server.ListenUnixgram(strings.TrimPrefix(*syslogAddress, "unix:"))
	} else {
		err = server.ListenUDP(*syslogAddress)
	}
	if err != nil {
		log.Fatal(err)
	}
	err = server.Boot()
	if err != nil {
		log.Fatal(err)
	}

	// Setup metrics
	syslogMessages := prometheus.NewCounter(prometheus.CounterOpts{
		Namespace: namespace,
		Name:      "exporter_syslog_messages",
		Help:      "Current total syslog messages received.",
	})
	err = prometheus.Register(syslogMessages)
	if err != nil {
		log.Fatal(err)
	}
	syslogParseFailures := prometheus.NewCounter(prometheus.CounterOpts{
		Namespace: namespace,
		Name:      "exporter_syslog_parse_failure",
		Help:      "Number of errors while parsing syslog messages.",
	})
	err = prometheus.Register(syslogParseFailures)
	if err != nil {
		log.Fatal(err)
	}
	var msgs int64
	go func() {
		for part := range channel {
			syslogMessages.Inc()
			msgs++
			tag, _ := part["tag"].(string)
			if tag != "nginx" {
				log.Warn("Ignoring syslog message with wrong tag")
				syslogParseFailures.Inc()
				continue
			}
			server, _ := part["hostname"].(string)
			if server == "" {
				log.Warn("Hostname missing in syslog message")
				syslogParseFailures.Inc()
				continue
			}

			content, _ := part["content"].(string)
			if content == "" {
				log.Warn("Ignoring empty syslog message")
				syslogParseFailures.Inc()
				continue
			}

			metrics, labels, err := parseMessage(content)
			if err != nil {
				log.Error(err)
				continue
			}
			for _, metric := range metrics {
				var collector prometheus.Collector
				collector = prometheus.NewHistogramVec(prometheus.HistogramOpts{
					Namespace: namespace,
					Name:      metric.Name,
					Help:      fmt.Sprintf("Nginx request log value for %s", metric.Name),
				}, labels.Names)
				if err := prometheus.Register(collector); err != nil {
					if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
						collector = are.ExistingCollector.(*prometheus.HistogramVec)
					} else {
						log.Error(err)
						continue
					}
				}
				collector.(*prometheus.HistogramVec).WithLabelValues(labels.Values...).Observe(metric.Value)
			}
		}
	}()

	// Setup HTTP server
	http.Handle(*metricsPath, prometheus.Handler())
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
             <title>Nginx Request Exporter</title>

Nginx Request Exporter

<a href='` + *metricsPath + `'>Metrics

             `))
	})

	go func() {
		log.Infof("Starting Server: %s", *listenAddress)
		log.Fatal(http.ListenAndServe(*listenAddress, nil))
	}()

	s := <-sigchan
	log.Infof("Received %v, terminating", s)
	log.Infof("Messages received: %d", msgs)
	err = server.Kill()
	if err != nil {
		log.Error(err)
	}
	os.Exit(0)
}
<|endoftext|>


<commit_before>package eval

import (
	"errors"
	"github.com/suzuken/gs/types"
)

// Eval is body of evaluator
func Eval(exp types.Expression, env *types.Env) (types.Expression, error) {
	switch t := exp.(type) {
	case types.Boolean:
		return exp, nil
	case types.Number:
		return exp, nil
	case types.Symbol:
		// it's variable. get value from environment
		e, err := env.Get(t)
		if err != nil {
			return nil, err
		}
		return e, nil
	case []types.Expression:
		// this is multiple expressions pattern
		// at first, get car. car of expression is symbol for each expression
		car, ok := t[0].(types.Symbol)
		if !ok {
			return nil, errors.New("cannot conversion car of expressions. it should be types.Symbol but not.")
		}
		switch car {
		case "define":
			if len(t) < 2 {
				return nil, errors.New("define clause must have symbol and body.")
			}
			// TODO consider if simply Eval(t[2], env) and put it into env.
			// because []types.Expression should be evaluated by Eval() anyway.
			switch tt := t[1].(type) {
			// put symbol and variables
			// (define x 1) style definition
			case types.Symbol:
				value, err := Eval(t[2], env)
				if err != nil {
					return nil, err
				}
				env.Put(tt, value)
				return tt, nil
			// (define (hoge args) (..)) style definition
			case []types.Expression:
				if len(tt) < 2 {
					return nil, errors.New("define statament must have more than 2 words.")
				}
				caddr, ok := tt[0].(types.Symbol)
				if !ok {
					return nil, errors.New("(define x) of x should be symbol..")
				}
				// create lambda and put it into environment
				env.Put(caddr, types.Lambda{tt[0], t[2]})
				return tt[0], nil
			}
		case "if":
			// like, (if (ok? yeah) (go) (not go))
			b, err := Eval(t[1], env)
			if err != nil {
				return nil, err
			}
			if b {
				return Eval(t[2], env)
			} else {
				return Eval(t[3], env)
			}
		case "cond":
			// TODO transform and use if evaluation here, too.
		case "lambda":
			if len(t) < 3 {
				return nil, errors.New("lambda must have more than 3 words.")
			}
			return types.Lambda{t[1], t[2]}, nil
		case "begin":
		default:
		}
	default:
		// not found any known operands. failed.
		return nil, errors.New("unkonwn expression type")
	}
	return nil, nil
}

// Apply receives procedure and arguments. if procedure is compounded, evaluate on extended environment.
func Apply(p *types.Expression, args []types.Expression) (*types.Expression, error) {
	if primitiveProcedure(p) {
		ApplyPrimitiveProcedure(p, args)
	} else if compoundProcedure(p) {
		EvalSequence(ProcedureBody(p), ExtendEnvironment(ProcedureParameters(p), args, ProcedureEnvironment(p)))
	} else {
		return nil, errors.New("Unknown procedure type")
	}
	return nil, nil
}

func ProcedureBody(p types.Expression) (exps []types.Expression) {
	return exps
}

func ProcedureParameters(p types.Expression) (exps []types.Expression) {
	return exps
}

func ProcedureEnvironment(p types.Expression) *types.Env {
	return nil
}

func ExtendEnvironment(exps, args []types.Expression, env *types.Env) *types.Env {
	return nil
}

// EvalSeauencd evaluate sequence of expressions in certain environment.
// Return is last expression.
func EvalSequence(exps []types.Expression, env *types.Env) (types.Expression, error) {
	if len(exps) == 1 {
		return Eval(exps[0], env)
	}
	// making environment (Yes, it's pointer)
	if _, err := Eval(exps[0], env); err != nil {
		return nil, err
	}
	return EvalSequence(exps[1:], env)
}

// listOfValues returns arguments for evaluator.
func listOfValues(exps []types.Expression, env *types.Env) (types.Expression, error) {
	if len(exps) <= 0 {
		return nil, nil
	}
	// evaluate exps one by one on each environment
	first, err := Eval(exps[0], env)
	if err != nil {
		return nil, err
	}
	// TODO: should use for in Go way?
	rest, err := listOfValues(exps[1:], env)
	if err != nil {
		return nil, err
	}
	return types.Pair{first, rest}, nil
}

func primitiveProcedure(s *types.Expression) bool {
	return false
}

func compoundProcedure(s *types.Expression) bool {
	return false
}

func ApplyPrimitiveProcedure(p *types.Expression, args []types.Expression) (*types.Expression, error) {
	return nil, nil
}
<commit_msg>begin<commit_after>package eval

import (
	"errors"
	"github.com/suzuken/gs/types"
)

// Eval is body of evaluator
func Eval(exp types.Expression, env *types.Env) (types.Expression, error) {
	switch t := exp.(type) {
	case types.Boolean:
		return exp, nil
	case types.Number:
		return exp, nil
	case types.Symbol:
		// it's variable. get value from environment
		e, err := env.Get(t)
		if err != nil {
			return nil, err
		}
		return e, nil
	case []types.Expression:
		// this is multiple expressions pattern
		// at first, get car. car of expression is symbol for each expression
		car, ok := t[0].(types.Symbol)
		if !ok {
			return nil, errors.New("cannot conversion car of expressions. it should be types.Symbol but not.")
		}
		switch car {
		case "define":
			if len(t) < 2 {
				return nil, errors.New("define clause must have symbol and body.")
			}
			// TODO consider if simply Eval(t[2], env) and put it into env.
			// because []types.Expression should be evaluated by Eval() anyway.
			switch tt := t[1].(type) {
			// put symbol and variables
			// (define x 1) style definition
			case types.Symbol:
				value, err := Eval(t[2], env)
				if err != nil {
					return nil, err
				}
				env.Put(tt, value)
				return tt, nil
			// (define (hoge args) (..)) style definition
			case []types.Expression:
				if len(tt) < 2 {
					return nil, errors.New("define statament must have more than 2 words.")
				}
				caddr, ok := tt[0].(types.Symbol)
				if !ok {
					return nil, errors.New("(define x) of x should be symbol..")
				}
				// create lambda and put it into environment
				env.Put(caddr, types.Lambda{tt[0], t[2]})
				return tt[0], nil
			}
		case "if":
			// like, (if (ok? yeah) (go) (not go))
			b, err := Eval(t[1], env)
			if err != nil {
				return nil, err
			}
			bb, ok := b.(types.Boolean)
			if !ok {
				return nil, errors.New("if-predicate should return types.Boolean")
			}
			if bb {
				return Eval(t[2], env)
			} else {
				return Eval(t[3], env)
			}
		case "cond":
			// TODO transform and use if evaluation here, too.
		case "lambda":
			if len(t) < 3 {
				return nil, errors.New("lambda must have more than 3 words.")
			}
			return types.Lambda{t[1], t[2]}, nil
		case "begin":
			// (begin s1 s2 ... last)
			var lastExp types.Expression
			for _, beginExp := range t[1:] {
				l, err := Eval(beginExp, env)
				if err != nil {
					return nil, err
				}
				lastExp = l
			}
			return lastExp, nil
		default:
		}
	default:
		// not found any known operands. failed.
		return nil, errors.New("unkonwn expression type")
	}
	return nil, nil
}

// Apply receives procedure and arguments. if procedure is compounded, evaluate on extended environment.
func Apply(p *types.Expression, args []types.Expression) (*types.Expression, error) {
	if primitiveProcedure(p) {
		ApplyPrimitiveProcedure(p, args)
	} else if compoundProcedure(p) {
		EvalSequence(ProcedureBody(p), ExtendEnvironment(ProcedureParameters(p), args, ProcedureEnvironment(p)))
	} else {
		return nil, errors.New("Unknown procedure type")
	}
	return nil, nil
}

func ProcedureBody(p types.Expression) (exps []types.Expression) {
	return exps
}

func ProcedureParameters(p types.Expression) (exps []types.Expression) {
	return exps
}

func ProcedureEnvironment(p types.Expression) *types.Env {
	return nil
}

func ExtendEnvironment(exps, args []types.Expression, env *types.Env) *types.Env {
	return nil
}

// EvalSeauencd evaluate sequence of expressions in certain environment.
// Return is last expression.
func EvalSequence(exps []types.Expression, env *types.Env) (types.Expression, error) {
	if len(exps) == 1 {
		return Eval(exps[0], env)
	}
	// making environment (Yes, it's pointer)
	if _, err := Eval(exps[0], env); err != nil {
		return nil, err
	}
	return EvalSequence(exps[1:], env)
}

// listOfValues returns arguments for evaluator.
func listOfValues(exps []types.Expression, env *types.Env) (types.Expression, error) {
	if len(exps) <= 0 {
		return nil, nil
	}
	// evaluate exps one by one on each environment
	first, err := Eval(exps[0], env)
	if err != nil {
		return nil, err
	}
	// TODO: should use for in Go way?
	rest, err := listOfValues(exps[1:], env)
	if err != nil {
		return nil, err
	}
	return types.Pair{first, rest}, nil
}

func primitiveProcedure(s *types.Expression) bool {
	return false
}

func compoundProcedure(s *types.Expression) bool {
	return false
}

func ApplyPrimitiveProcedure(p *types.Expression, args []types.Expression) (*types.Expression, error) {
	return nil, nil
}
<|endoftext|>


<commit_before>package logging

import (
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"testing"
	"time"
)

var (
	logger       = log.New(os.Stdout, "", log.LstdFlags)
	pathPrefix   = "logrotator"
	baseFileName = "redis.stdout"
)

func TestFileRotator_IncorrectPath(t *testing.T) {
	if _, err := NewFileRotator("/foo", baseFileName, 10, 10, logger); err == nil {
		t.Fatalf("expected error")
	}
}

func TestFileRotator_CreateNewFile(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	_, err = NewFileRotator(path, baseFileName, 10, 10, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	if _, err := os.Stat(filepath.Join(path, "redis.stdout.0")); err != nil {
		t.Fatalf("expected file")
	}
}

func TestFileRotator_OpenLastFile(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	fname1 := filepath.Join(path, "redis.stdout.0")
	fname2 := filepath.Join(path, "redis.stdout.2")
	if _, err := os.Create(fname1); err != nil {
		t.Fatalf("test setup failure: %v", err)
	}
	if _, err := os.Create(fname2); err != nil {
		t.Fatalf("test setup failure: %v", err)
	}

	fr, err := NewFileRotator(path, baseFileName, 10, 10, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	if fr.currentFile.Name() != fname2 {
		t.Fatalf("expected current file: %v, got: %v", fname2, fr.currentFile.Name())
	}
}

func TestFileRotator_WriteToCurrentFile(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	fname1 := filepath.Join(path, "redis.stdout.0")
	if _, err := os.Create(fname1); err != nil {
		t.Fatalf("test setup failure: %v", err)
	}

	fr, err := NewFileRotator(path, baseFileName, 10, 5, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	fr.Write([]byte("abcde"))
	time.Sleep(200 * time.Millisecond)
	fi, err := os.Stat(fname1)
	if err != nil {
		t.Fatalf("error getting the file info: %v", err)
	}
	if fi.Size() != 5 {
		t.Fatalf("expected size: %v, actual: %v", 5, fi.Size())
	}
}

func TestFileRotator_RotateFiles(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	fr, err := NewFileRotator(path, baseFileName, 10, 5, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	str := "abcdefgh"
	nw, err := fr.Write([]byte(str))
	time.Sleep(200 * time.Millisecond)
	if err != nil {
		t.Fatalf("got error while writing: %v", err)
	}
	if nw != len(str) {
		t.Fatalf("expected %v, got %v", len(str), nw)
	}
	fname1 := filepath.Join(path, "redis.stdout.0")
	fi, err := os.Stat(fname1)
	if err != nil {
		t.Fatalf("error getting the file info: %v", err)
	}
	if fi.Size() != 5 {
		t.Fatalf("expected size: %v, actual: %v", 5, fi.Size())
	}

	fname2 := filepath.Join(path, "redis.stdout.1")
	if _, err := os.Stat(fname2); err != nil {
		t.Fatalf("expected file %v to exist", fname2)
	}

	if fi2, err := os.Stat(fname2); err == nil {
		if fi2.Size() != 3 {
			t.Fatalf("expected size: %v, actual: %v", 3, fi2.Size())
		}
	} else {
		t.Fatalf("error getting the file info: %v", err)
	}
}

func TestFileRotator_WriteRemaining(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	fname1 := filepath.Join(path, "redis.stdout.0")
	if f, err := os.Create(fname1); err == nil {
		f.Write([]byte("abcd"))
	} else {
		t.Fatalf("test setup failure: %v", err)
	}

	fr, err := NewFileRotator(path, baseFileName, 10, 5, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	str := "efghijkl"
	nw, err := fr.Write([]byte(str))
	if err != nil {
		t.Fatalf("got error while writing: %v", err)
	}
	time.Sleep(200 * time.Millisecond)
	if nw != len(str) {
		t.Fatalf("expected %v, got %v", len(str), nw)
	}
	fi, err := os.Stat(fname1)
	if err != nil {
		t.Fatalf("error getting the file info: %v", err)
	}
	if fi.Size() != 5 {
		t.Fatalf("expected size: %v, actual: %v", 5, fi.Size())
	}

	fname2 := filepath.Join(path, "redis.stdout.1")
	if _, err := os.Stat(fname2); err != nil {
		t.Fatalf("expected file %v to exist", fname2)
	}

	if fi2, err := os.Stat(fname2); err == nil {
		if fi2.Size() != 5 {
			t.Fatalf("expected size: %v, actual: %v", 5, fi2.Size())
		}
	} else {
		t.Fatalf("error getting the file info: %v", err)
	}

	fname3 := filepath.Join(path, "redis.stdout.2")
	if _, err := os.Stat(fname3); err != nil {
		t.Fatalf("expected file %v to exist", fname3)
	}

	if fi3, err := os.Stat(fname3); err == nil {
		if fi3.Size() != 2 {
			t.Fatalf("expected size: %v, actual: %v", 2, fi3.Size())
		}
	} else {
		t.Fatalf("error getting the file info: %v", err)
	}

}

func TestFileRotator_PurgeOldFiles(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	fr, err := NewFileRotator(path, baseFileName, 2, 2, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	str := "abcdeghijklmn"
	nw, err := fr.Write([]byte(str))
	if err != nil {
		t.Fatalf("got error while writing: %v", err)
	}
	if nw != len(str) {
		t.Fatalf("expected %v, got %v", len(str), nw)
	}

	time.Sleep(1 * time.Second)
	f, err := ioutil.ReadDir(path)
	if err != nil {
		t.Fatalf("test error: %v", err)
	}

	if len(f) != 2 {
		t.Fatalf("expected number of files: %v, got: %v", 2, len(f))
	}
}
<commit_msg>Wrap file rotator tests in wait for<commit_after>package logging

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"testing"

	"github.com/hashicorp/nomad/testutil"
)

var (
	logger       = log.New(os.Stdout, "", log.LstdFlags)
	pathPrefix   = "logrotator"
	baseFileName = "redis.stdout"
)

func TestFileRotator_IncorrectPath(t *testing.T) {
	if _, err := NewFileRotator("/foo", baseFileName, 10, 10, logger); err == nil {
		t.Fatalf("expected error")
	}
}

func TestFileRotator_CreateNewFile(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	_, err = NewFileRotator(path, baseFileName, 10, 10, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	if _, err := os.Stat(filepath.Join(path, "redis.stdout.0")); err != nil {
		t.Fatalf("expected file")
	}
}

func TestFileRotator_OpenLastFile(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	fname1 := filepath.Join(path, "redis.stdout.0")
	fname2 := filepath.Join(path, "redis.stdout.2")
	if _, err := os.Create(fname1); err != nil {
		t.Fatalf("test setup failure: %v", err)
	}
	if _, err := os.Create(fname2); err != nil {
		t.Fatalf("test setup failure: %v", err)
	}

	fr, err := NewFileRotator(path, baseFileName, 10, 10, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	if fr.currentFile.Name() != fname2 {
		t.Fatalf("expected current file: %v, got: %v", fname2, fr.currentFile.Name())
	}
}

func TestFileRotator_WriteToCurrentFile(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	fname1 := filepath.Join(path, "redis.stdout.0")
	if _, err := os.Create(fname1); err != nil {
		t.Fatalf("test setup failure: %v", err)
	}

	fr, err := NewFileRotator(path, baseFileName, 10, 5, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	fr.Write([]byte("abcde"))

	var actual int64
	testutil.WaitForResult(func() (bool, error) {
		fi, err := os.Stat(fname1)
		if err != nil {
			return false, err
		}
		actual = fi.Size()
		if actual != 5 {
			return false, nil
		}

		return true, nil
	}, func(err error) {
		t.Fatalf("expected size: %v, actual: %v", 5, actual)
	})
}

func TestFileRotator_RotateFiles(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	fr, err := NewFileRotator(path, baseFileName, 10, 5, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	str := "abcdefgh"
	nw, err := fr.Write([]byte(str))
	if err != nil {
		t.Fatalf("got error while writing: %v", err)
	}

	if nw != len(str) {
		t.Fatalf("expected %v, got %v", len(str), nw)
	}

	var lastErr error
	testutil.WaitForResult(func() (bool, error) {
		fname1 := filepath.Join(path, "redis.stdout.0")
		fi, err := os.Stat(fname1)
		if err != nil {
			lastErr = err
			return false, nil
		}
		if fi.Size() != 5 {
			lastErr = fmt.Errorf("expected size: %v, actual: %v", 5, fi.Size())
			return false, nil
		}

		fname2 := filepath.Join(path, "redis.stdout.1")
		if _, err := os.Stat(fname2); err != nil {
			lastErr = fmt.Errorf("expected file %v to exist", fname2)
			return false, nil
		}

		if fi2, err := os.Stat(fname2); err == nil {
			if fi2.Size() != 3 {
				lastErr = fmt.Errorf("expected size: %v, actual: %v", 3, fi2.Size())
				return false, nil
			}
		} else {
			lastErr = fmt.Errorf("error getting the file info: %v", err)
			return false, nil
		}

		return true, nil
	}, func(err error) {
		t.Fatalf("%v", lastErr)
	})
}

func TestFileRotator_WriteRemaining(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	fname1 := filepath.Join(path, "redis.stdout.0")
	if f, err := os.Create(fname1); err == nil {
		f.Write([]byte("abcd"))
	} else {
		t.Fatalf("test setup failure: %v", err)
	}

	fr, err := NewFileRotator(path, baseFileName, 10, 5, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	str := "efghijkl"
	nw, err := fr.Write([]byte(str))
	if err != nil {
		t.Fatalf("got error while writing: %v", err)
	}
	if nw != len(str) {
		t.Fatalf("expected %v, got %v", len(str), nw)
	}
	var lastErr error
	testutil.WaitForResult(func() (bool, error) {
		fi, err := os.Stat(fname1)
		if err != nil {
			lastErr = fmt.Errorf("error getting the file info: %v", err)
			return false, nil
		}
		if fi.Size() != 5 {
			lastErr = fmt.Errorf("expected size: %v, actual: %v", 5, fi.Size())
			return false, nil
		}

		fname2 := filepath.Join(path, "redis.stdout.1")
		if _, err := os.Stat(fname2); err != nil {
			lastErr = fmt.Errorf("expected file %v to exist", fname2)
			return false, nil
		}

		if fi2, err := os.Stat(fname2); err == nil {
			if fi2.Size() != 5 {
				lastErr = fmt.Errorf("expected size: %v, actual: %v", 5, fi2.Size())
				return false, nil
			}
		} else {
			lastErr = fmt.Errorf("error getting the file info: %v", err)
			return false, nil
		}

		fname3 := filepath.Join(path, "redis.stdout.2")
		if _, err := os.Stat(fname3); err != nil {
			lastErr = fmt.Errorf("expected file %v to exist", fname3)
			return false, nil
		}

		if fi3, err := os.Stat(fname3); err == nil {
			if fi3.Size() != 2 {
				lastErr = fmt.Errorf("expected size: %v, actual: %v", 2, fi3.Size())
				return false, nil
			}
		} else {
			lastErr = fmt.Errorf("error getting the file info: %v", err)
			return false, nil
		}

		return true, nil
	}, func(err error) {
		t.Fatalf("%v", lastErr)
	})

}

func TestFileRotator_PurgeOldFiles(t *testing.T) {
	var path string
	var err error
	if path, err = ioutil.TempDir("", pathPrefix); err != nil {
		t.Fatalf("test setup err: %v", err)
	}
	defer os.RemoveAll(path)

	fr, err := NewFileRotator(path, baseFileName, 2, 2, logger)
	if err != nil {
		t.Fatalf("test setup err: %v", err)
	}

	str := "abcdeghijklmn"
	nw, err := fr.Write([]byte(str))
	if err != nil {
		t.Fatalf("got error while writing: %v", err)
	}
	if nw != len(str) {
		t.Fatalf("expected %v, got %v", len(str), nw)
	}

	var lastErr error
	testutil.WaitForResult(func() (bool, error) {
		f, err := ioutil.ReadDir(path)
		if err != nil {
			lastErr = fmt.Errorf("test error: %v", err)
			return false, nil
		}

		if len(f) != 2 {
			lastErr = fmt.Errorf("expected number of files: %v, got: %v", 2, len(f))
			return false, nil
		}

		return true, nil
	}, func(err error) {
		t.Fatalf("%v", lastErr)
	})
}
<|endoftext|>