<commit_before>package main

import (
	"bytes"
	"image"
	"image/color"
	"image/draw"
	"image/jpeg"
	"io/ioutil"
	"log"
	"os"
	"strings"

	"gopkg.in/yaml.v2"

	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"
)

type Config struct {
	FontFile         string
	FontSize         float64
	FontDPI          float64
	FontSpacing      float64
	WordsPerLine     int
	OffsetX, OffsetY int
	TextColor        color.RGBA
	BackgroundColor  color.RGBA
}

func (c Config) Save(filename string) {
	y, err := yaml.Marshal(c)
	if err != nil {
		panic(err)
	}
	ioutil.WriteFile(filename, y, 0644)
}

func (c *Config) Load(filename string) {
	b, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(err)
	}
	err = yaml.Unmarshal(b, c)
	if err != nil {
		panic(err)
	}
}

func DefaultConfig() Config {
	return Config{
		FontFile:        "inziu-SC-regular.ttc",
		FontSize:        20,
		FontDPI:         72,
		FontSpacing:     1.5,
		WordsPerLine:    16,
		OffsetX:         1500,
		OffsetY:         50,
		TextColor:       color.RGBA{255, 255, 255, 255},
		BackgroundColor: color.RGBA{R: 0, G: 0, B: 0, A: 100},
	}
}

type WallPaper struct {
	img    image.Image
	config Config
}

func NewWallPaper(config Config) *WallPaper {
	return &WallPaper{
		config: config,
	}
}

func (wallpaper *WallPaper) AddText(text string) {
	canvas := wallpaper.img
	rgba := image.NewRGBA(canvas.Bounds())                     // image to draw, same size as input image
	draw.Draw(rgba, rgba.Bounds(), canvas, image.ZP, draw.Src) // copy input image to it
	mask := image.NewUniform(wallpaper.config.BackgroundColor) // background of text
	x0, y0 := wallpaper.getOffset(canvas)                      // x, y coordinate

	// font rendering
	c := freetype.NewContext()
	c.SetDPI(wallpaper.config.FontDPI)
	c.SetFont(wallpaper.loadFont(wallpaper.config.FontFile))
	c.SetFontSize(wallpaper.config.FontSize)
	c.SetClip(rgba.Bounds())
	c.SetDst(rgba)
	tc := image.NewUniform(wallpaper.config.TextColor)
	c.SetSrc(tc)
	c.SetHinting(font.HintingFull)

	textX, textY := x0, y0 // text x, y
	pt := freetype.Pt(textX+10, textY+10+int(c.PointToFixed(wallpaper.config.FontSize)>>6))

	lines := wallpaper.splitText(text)
	w, h := wallpaper.getWidthAndHeight(len(lines))      // calculate width of the text
	maskrect := image.Rect(x0, y0, x0+w, y0+h)           // background location and size
	draw.Draw(rgba, maskrect, mask, image.ZP, draw.Over) // draw background

	for _, s := range lines {
		_, err := c.DrawString(s, pt)
		if err != nil {
			panic(err)
		}
		pt.Y += c.PointToFixed(wallpaper.config.FontSize * wallpaper.config.FontSpacing)
	}

	wallpaper.img = rgba
}

var sl = make([]string, 0)

func (wallpaper WallPaper) splitText(s string) []string {
	wordsPerLine := wallpaper.config.WordsPerLine

	// detect line breakers
	lineBreakerIndex := strings.Index(s, "\n")
	if lineBreakerIndex != -1 {
		sl = append(sl, s[:lineBreakerIndex])
		s = s[lineBreakerIndex+1:]
	}

	r := []rune(s)
	if len(r) > wordsPerLine {
		ac := wallpaper.getASCIICharCount(r[:wordsPerLine])
		forward := 0
		i := 0

		for ; forward < ac; i++ {
			if wordsPerLine+i >= len(r) {
				// the entire paragraph is shorter than width
				break
			}

			if !wallpaper.isASCIIChar(r[wordsPerLine+i]) {
				forward += 2
			} else {
				forward++
			}
		}
		sl = append(sl, string(r[:wordsPerLine+i]))
		wallpaper.splitText(string(r[wordsPerLine+i:]))
	} else {
		sl = append(sl, s)
	}
	return sl
}

func (WallPaper) isASCIIChar(c rune) bool {
	return c >= 0x20 && c <= 0x7e || c == '\n'
}

func (wallpaper WallPaper) getASCIICharCount(r []rune) int {
	result := 0
	for _, c := range r {
		if wallpaper.isASCIIChar(c) {
			result++
		}
	}
	return result
}

func (wallpaper *WallPaper) Decode(buf []byte) {
	r := bytes.NewReader(buf)
	image, err := jpeg.Decode(r)
	if err != nil {
		panic(err)
	}
	wallpaper.img = image
}

func (wallpaper *WallPaper) Encode() []byte {
	buf := new(bytes.Buffer)
	err := jpeg.Encode(buf, wallpaper.img, &jpeg.Options{Quality: 100})
	if err != nil {
		panic(err)
	}
	return buf.Bytes()
}

func (WallPaper) loadFont(path string) *truetype.Font {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		path = "C:/Windows/Fonts/" + path
	}
	log.Println("Font:" + path)
	fontBytes, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	font, err := freetype.ParseFont(fontBytes)
	if err != nil {
		panic(err)
	}
	return font
}

func (wallpaper WallPaper) getOffset(canvas image.Image) (x, y int) {
	x = canvas.Bounds().Min.X + wallpaper.config.OffsetX
	y = canvas.Bounds().Min.Y + wallpaper.config.OffsetY
	return
}

func (wallpaper WallPaper) getWidthAndHeight(lines int) (w, h int) {
	w = wallpaper.config.WordsPerLine * (int(wallpaper.config.FontSize) + 2)
	h = int((wallpaper.config.FontSpacing + wallpaper.config.FontSize + 8) * float64(lines+1))
	return
}
<commit_msg>bug fixed<commit_after>package main

import (
	"bytes"
	"image"
	"image/color"
	"image/draw"
	"image/jpeg"
	"io/ioutil"
	"log"
	"os"
	"strings"

	"gopkg.in/yaml.v2"

	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"
)

type Config struct {
	FontFile         string
	FontSize         float64
	FontDPI          float64
	FontSpacing      float64
	WordsPerLine     int
	OffsetX, OffsetY int
	TextColor        color.RGBA
	BackgroundColor  color.RGBA
}

func (c Config) Save(filename string) {
	y, err := yaml.Marshal(c)
	if err != nil {
		panic(err)
	}
	ioutil.WriteFile(filename, y, 0644)
}

func (c *Config) Load(filename string) {
	b, err := ioutil.ReadFile(filename)
	if err != nil {
		panic(err)
	}
	err = yaml.Unmarshal(b, c)
	if err != nil {
		panic(err)
	}
}

func DefaultConfig() Config {
	return Config{
		FontFile:        "inziu-SC-regular.ttc",
		FontSize:        20,
		FontDPI:         72,
		FontSpacing:     1.5,
		WordsPerLine:    16,
		OffsetX:         1500,
		OffsetY:         50,
		TextColor:       color.RGBA{255, 255, 255, 255},
		BackgroundColor: color.RGBA{R: 0, G: 0, B: 0, A: 100},
	}
}

type WallPaper struct {
	img    image.Image
	config Config
}

func NewWallPaper(config Config) *WallPaper {
	return &WallPaper{
		config: config,
	}
}

func (wallpaper *WallPaper) AddText(text string) {
	canvas := wallpaper.img
	rgba := image.NewRGBA(canvas.Bounds())                     // image to draw, same size as input image
	draw.Draw(rgba, rgba.Bounds(), canvas, image.ZP, draw.Src) // copy input image to it
	mask := image.NewUniform(wallpaper.config.BackgroundColor) // background of text
	x0, y0 := wallpaper.getOffset(canvas)                      // x, y coordinate

	// font rendering
	c := freetype.NewContext()
	c.SetDPI(wallpaper.config.FontDPI)
	c.SetFont(wallpaper.loadFont(wallpaper.config.FontFile))
	c.SetFontSize(wallpaper.config.FontSize)
	c.SetClip(rgba.Bounds())
	c.SetDst(rgba)
	tc := image.NewUniform(wallpaper.config.TextColor)
	c.SetSrc(tc)
	c.SetHinting(font.HintingFull)

	textX, textY := x0, y0 // text x, y
	pt := freetype.Pt(textX+10, textY+10+int(c.PointToFixed(wallpaper.config.FontSize)>>6))

	lines := wallpaper.splitText(text)
	w, h := wallpaper.getWidthAndHeight(len(lines))      // calculate width of the text
	maskrect := image.Rect(x0, y0, x0+w, y0+h)           // background location and size
	draw.Draw(rgba, maskrect, mask, image.ZP, draw.Over) // draw background

	for _, s := range lines {
		_, err := c.DrawString(s, pt)
		if err != nil {
			panic(err)
		}
		pt.Y += c.PointToFixed(wallpaper.config.FontSize * wallpaper.config.FontSpacing)
	}

	wallpaper.img = rgba
}

var sl = make([]string, 0)

func (wallpaper WallPaper) splitText(s string) []string {
	wordsPerLine := wallpaper.config.WordsPerLine

	// detect line breakers
	lineBreakerIndex := strings.Index(s, "\n")
	if lineBreakerIndex != -1 && lineBreakerIndex < wallpaper.config.WordsPerLine {
		sl = append(sl, s[:lineBreakerIndex])
		s = s[lineBreakerIndex+1:]
	}

	r := []rune(s)
	if len(r) > wordsPerLine {
		ac := wallpaper.getASCIICharCount(r[:wordsPerLine])
		forward := 0
		i := 0

		for ; forward < ac; i++ {
			if wordsPerLine+i >= len(r) {
				// the entire paragraph is shorter than width
				break
			}

			if !wallpaper.isASCIIChar(r[wordsPerLine+i]) {
				forward += 2
			} else {
				forward++
			}
		}
		sl = append(sl, string(r[:wordsPerLine+i]))
		wallpaper.splitText(string(r[wordsPerLine+i:]))
	} else {
		sl = append(sl, s)
	}
	return sl
}

func (WallPaper) isASCIIChar(c rune) bool {
	return c >= 0x20 && c <= 0x7e || c == '\n'
}

func (wallpaper WallPaper) getASCIICharCount(r []rune) int {
	result := 0
	for _, c := range r {
		if wallpaper.isASCIIChar(c) {
			result++
		}
	}
	return result
}

func (wallpaper *WallPaper) Decode(buf []byte) {
	r := bytes.NewReader(buf)
	image, err := jpeg.Decode(r)
	if err != nil {
		panic(err)
	}
	wallpaper.img = image
}

func (wallpaper *WallPaper) Encode() []byte {
	buf := new(bytes.Buffer)
	err := jpeg.Encode(buf, wallpaper.img, &jpeg.Options{Quality: 100})
	if err != nil {
		panic(err)
	}
	return buf.Bytes()
}

func (WallPaper) loadFont(path string) *truetype.Font {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		path = "C:/Windows/Fonts/" + path
	}
	log.Println("Font:" + path)
	fontBytes, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	font, err := freetype.ParseFont(fontBytes)
	if err != nil {
		panic(err)
	}
	return font
}

func (wallpaper WallPaper) getOffset(canvas image.Image) (x, y int) {
	x = canvas.Bounds().Min.X + wallpaper.config.OffsetX
	y = canvas.Bounds().Min.Y + wallpaper.config.OffsetY
	return
}

func (wallpaper WallPaper) getWidthAndHeight(lines int) (w, h int) {
	w = wallpaper.config.WordsPerLine * (int(wallpaper.config.FontSize) + 2)
	h = int((wallpaper.config.FontSpacing + wallpaper.config.FontSize + 8) * float64(lines+1))
	return
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"sort"
	"strings"
	"sync"
	"time"
)

var addr = flag.String("addr", ":4089", "Server address")
var timeout = flag.Duration("timeout", 5*time.Second, "Timeout for HTTP GETs")
var conditionsInterval = flag.Duration("conditions_interval", 10*time.Minute, "Interval between updating conditions")
var siteMapUrl = flag.String("site_map_url", "http://magicseaweed.com/site-map.php", "URL of the site map")
var conditionsFilePath = flag.String("conditions_file", "conditions.json", "Path to file with conditions cache")
var conditionsPeriod = flag.Duration("conditions_period", 10*time.Second, "How often to save the conditions file")

func main() {
	loadConditionsFile()
	http.HandleFunc("/", handleRoot)
	http.HandleFunc("/errors", handleErrors)
	go keepConditionsUpdated()
	log.Printf("Listending on %s", *addr)
	log.Fatal(http.ListenAndServe(*addr, nil))
}

var head = template.HTML(`

		<title>Waveguide</title>

`)

var errsTmpl = template.Must(template.New("errs").Parse(`

{{.Head}}

		Errors:

			{{range .Errs}}

				{{.Loc.Name}}
				{{.Err}}

			{{end}}

`))

var tmpl = template.Must(template.New("main").Parse(`

{{.Head}}

		{{if .Conds}}

				Location
				Conditions
				Wave Height

				{{range .Conds}}

					{{.Loc.Name}}
					{{.Stars}}
					{{.Details}}

				{{end}}

		{{end}}

`))

type Location struct {
	Name             string
	MagicSeaweedPath string
}

type Conditions struct {
	Loc     *Location
	Rating  int
	Details string
}

type Error struct {
	Loc *Location
	Err error
}

func handleErrors(w http.ResponseWriter, r *http.Request) {
	mu.Lock()
	defer mu.Unlock()
	data := struct {
		Errs []*Error
		Head template.HTML
	}{
		Errs: errs,
		Head: head,
	}
	err := errsTmpl.Execute(w, data)
	if err != nil {
		log.Printf("Failed to execute template. %v", err)
	}
}

func handleRoot(w http.ResponseWriter, r *http.Request) {
	// Sort conditions by rating and name.
	mu.Lock()
	defer mu.Unlock()
	conds2 := make([]*Conditions, 0, len(conds))
	for _, c := range conds {
		conds2 = append(conds2, c)
	}
	sort.Sort(ByRating(conds2))

	// Render the results.
	data := struct {
		Conds []*Conditions
		Head  template.HTML
	}{Conds: conds2, Head: head}
	err := tmpl.Execute(w, data)
	if err != nil {
		log.Printf("Failed to execute template. %v", err)
	}
}

func updateConditionsAllLocations() {
	client := &http.Client{Timeout: *timeout}

	// Gather locations.
	log.Printf("Fetching %s", *siteMapUrl)
	resp, err := client.Get(*siteMapUrl)
	if err != nil {
		log.Printf("Failed to get site map. %v", err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Printf("Failed to read response body of site map. %v", err)
	}
	reportMatches := reportRx.FindAllSubmatch(body, -1)
	for _, match := range reportMatches {
		path := strings.TrimSpace(string(match[1]))
		name := strings.TrimSpace(string(match[2]))
		_, ok := locations[name]
		if !ok {
			log.Printf("Got new location: %s: %s", name, path)
		}
		loc := &Location{
			Name:             name,
			MagicSeaweedPath: path,
		}
		locations[name] = loc
	}
	log.Printf("Found %d reports", len(reportMatches))
	log.Printf("New number of locations: %d", len(locations))

	// Gather conditions and errors.
	mu.Lock()
	errs = make([]*Error, 0, len(locations))
	mu.Unlock()
	for _, loc := range locations {
		cond, err := loc.GetConditions(client)
		if err != nil {
			mu.Lock()
			e := &Error{
				Loc: loc,
				Err: err,
			}
			errs = append(errs, e)
			mu.Unlock()
			continue
		}
		mu.Lock()
		conds[loc.Name] = cond
		mu.Unlock()
		saveConditionsFile()
	}
}

func loadConditionsFile() {
	f, err := os.Open(*conditionsFilePath)
	if err != nil {
		log.Printf("Failed to open conditions file %s. %v. That's okay.", *conditionsFilePath, err)
		return
	}
	contents, err := ioutil.ReadAll(f)
	err = json.Unmarshal(contents, &conds)
	if err != nil {
		log.Printf("Failed to unmarshall the conditions map. %v", err)
		os.Exit(1)
	}
	log.Printf("Loaded conditions file %s", *conditionsFilePath)
}

func saveConditionsFile() {
	f, err := os.Create(*conditionsFilePath)
	if err != nil {
		log.Printf("Failed to open conditions file %s for writing. %v", *conditionsFilePath)
		return
	}
	mu.Lock()
	defer mu.Unlock()
	contents, err := json.Marshal(conds)
	if err != nil {
		log.Printf("Failed to marshal conditions. %v", conds)
		return
	}
	f.Write(contents)
	f.Close()
	log.Printf("Saved conditions file %s", *conditionsFilePath)
}

var locations = make(map[string]*Location)
var mu sync.Mutex // for conds and errs
var conds = make(map[string]*Conditions)
var errs []*Error

type ByRating []*Conditions

func (r ByRating) Len() int      { return len(r) }
func (r ByRating) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r ByRating) Less(i, j int) bool {
	ci := r[i]
	cj := r[j]
	if ci.Rating == cj.Rating {
		return ci.Loc.Name < cj.Loc.Name
	}
	return ci.Rating > cj.Rating
}

var starRx = regexp.MustCompile(`<li class="active"> *<i class="glyphicon glyphicon-star"></i> *</li>`)
var heightRx = regexp.MustCompile(`(\d+(?:-\d+)?)<small>ft`)
var reportRx = regexp.MustCompile(`<option value="(/[^"]+-Surf-Report/\d+/)">([^<]+)\s*</option>`)

func (loc *Location) GetConditions(client *http.Client) (*Conditions, error) {
	url := "http://magicseaweed.com" + loc.MagicSeaweedPath
	log.Printf("Fetching %s", url)
	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Failed to read body. %v", err)
	}
	foundStars := starRx.FindAll(body, -1)
	hMatch := heightRx.FindSubmatch(body)
	if len(hMatch) != 2 {
		return nil, fmt.Errorf("Wave height regex failed.")
	}
	rating := len(foundStars)
	details := fmt.Sprintf("%s ft", hMatch[1])
	cond := &Conditions{
		Loc:     loc,
		Rating:  rating,
		Details: details,
	}
	return cond, nil
}

func (c *Conditions) Stars() string {
	runes := make([]rune, 0, 5)
	for i := 0; i < c.Rating; i++ {
		runes = append(runes, '★')
	}
	for i := 0; i < 5-c.Rating; i++ {
		runes = append(runes, '☆')
	}
	return string(runes)
}

func keepConditionsUpdated() {
	updateConditionsAllLocations()
	tick := time.Tick(*conditionsInterval)
	for {
		<-tick
		updateConditionsAllLocations()
	}
}
<commit_msg>Add -h<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"sort"
	"strings"
	"sync"
	"time"
)

var addr = flag.String("addr", ":4089", "Server address")
var timeout = flag.Duration("timeout", 5*time.Second, "Timeout for HTTP GETs")
var conditionsInterval = flag.Duration("conditions_interval", 10*time.Minute, "Interval between updating conditions")
var siteMapUrl = flag.String("site_map_url", "http://magicseaweed.com/site-map.php", "URL of the site map")
var conditionsFilePath = flag.String("conditions_file", "conditions.json", "Path to file with conditions cache")
var conditionsPeriod = flag.Duration("conditions_period", 10*time.Second, "How often to save the conditions file")
var help = flag.Bool("h", false, "Show help message")

func main() {
	flag.Parse()
	if *help {
		flag.Usage()
		os.Exit(0)
	}
	loadConditionsFile()
	http.HandleFunc("/", handleRoot)
	http.HandleFunc("/errors", handleErrors)
	go keepConditionsUpdated()
	log.Printf("Listending on %s", *addr)
	log.Fatal(http.ListenAndServe(*addr, nil))
}

var head = template.HTML(`

		<title>Waveguide</title>

`)

var errsTmpl = template.Must(template.New("errs").Parse(`

{{.Head}}

		Errors:

			{{range .Errs}}

				{{.Loc.Name}}
				{{.Err}}

			{{end}}

`))

var tmpl = template.Must(template.New("main").Parse(`

{{.Head}}

		{{if .Conds}}

				Location
				Conditions
				Wave Height

				{{range .Conds}}

					{{.Loc.Name}}
					{{.Stars}}
					{{.Details}}

				{{end}}

		{{end}}

`))

type Location struct {
	Name             string
	MagicSeaweedPath string
}

type Conditions struct {
	Loc     *Location
	Rating  int
	Details string
}

type Error struct {
	Loc *Location
	Err error
}

func handleErrors(w http.ResponseWriter, r *http.Request) {
	mu.Lock()
	defer mu.Unlock()
	data := struct {
		Errs []*Error
		Head template.HTML
	}{
		Errs: errs,
		Head: head,
	}
	err := errsTmpl.Execute(w, data)
	if err != nil {
		log.Printf("Failed to execute template. %v", err)
	}
}

func handleRoot(w http.ResponseWriter, r *http.Request) {
	// Sort conditions by rating and name.
	mu.Lock()
	defer mu.Unlock()
	conds2 := make([]*Conditions, 0, len(conds))
	for _, c := range conds {
		conds2 = append(conds2, c)
	}
	sort.Sort(ByRating(conds2))

	// Render the results.
	data := struct {
		Conds []*Conditions
		Head  template.HTML
	}{Conds: conds2, Head: head}
	err := tmpl.Execute(w, data)
	if err != nil {
		log.Printf("Failed to execute template. %v", err)
	}
}

func updateConditionsAllLocations() {
	client := &http.Client{Timeout: *timeout}

	// Gather locations.
	log.Printf("Fetching %s", *siteMapUrl)
	resp, err := client.Get(*siteMapUrl)
	if err != nil {
		log.Printf("Failed to get site map. %v", err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Printf("Failed to read response body of site map. %v", err)
	}
	reportMatches := reportRx.FindAllSubmatch(body, -1)
	for _, match := range reportMatches {
		path := strings.TrimSpace(string(match[1]))
		name := strings.TrimSpace(string(match[2]))
		_, ok := locations[name]
		if !ok {
			log.Printf("Got new location: %s: %s", name, path)
		}
		loc := &Location{
			Name:             name,
			MagicSeaweedPath: path,
		}
		locations[name] = loc
	}
	log.Printf("Found %d reports", len(reportMatches))
	log.Printf("New number of locations: %d", len(locations))

	// Gather conditions and errors.
	mu.Lock()
	errs = make([]*Error, 0, len(locations))
	mu.Unlock()
	for _, loc := range locations {
		cond, err := loc.GetConditions(client)
		if err != nil {
			mu.Lock()
			e := &Error{
				Loc: loc,
				Err: err,
			}
			errs = append(errs, e)
			mu.Unlock()
			continue
		}
		mu.Lock()
		conds[loc.Name] = cond
		mu.Unlock()
		saveConditionsFile()
	}
}

func loadConditionsFile() {
	f, err := os.Open(*conditionsFilePath)
	if err != nil {
		log.Printf("Failed to open conditions file %s. %v. That's okay.", *conditionsFilePath, err)
		return
	}
	contents, err := ioutil.ReadAll(f)
	err = json.Unmarshal(contents, &conds)
	if err != nil {
		log.Printf("Failed to unmarshall the conditions map. %v", err)
		os.Exit(1)
	}
	log.Printf("Loaded conditions file %s", *conditionsFilePath)
}

func saveConditionsFile() {
	f, err := os.Create(*conditionsFilePath)
	if err != nil {
		log.Printf("Failed to open conditions file %s for writing. %v", *conditionsFilePath)
		return
	}
	mu.Lock()
	defer mu.Unlock()
	contents, err := json.Marshal(conds)
	if err != nil {
		log.Printf("Failed to marshal conditions. %v", conds)
		return
	}
	f.Write(contents)
	f.Close()
	log.Printf("Saved conditions file %s", *conditionsFilePath)
}

var locations = make(map[string]*Location)
var mu sync.Mutex // for conds and errs
var conds = make(map[string]*Conditions)
var errs []*Error

type ByRating []*Conditions

func (r ByRating) Len() int      { return len(r) }
func (r ByRating) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r ByRating) Less(i, j int) bool {
	ci := r[i]
	cj := r[j]
	if ci.Rating == cj.Rating {
		return ci.Loc.Name < cj.Loc.Name
	}
	return ci.Rating > cj.Rating
}

var starRx = regexp.MustCompile(`<li class="active"> *<i class="glyphicon glyphicon-star"></i> *</li>`)
var heightRx = regexp.MustCompile(`(\d+(?:-\d+)?)<small>ft`)
var reportRx = regexp.MustCompile(`<option value="(/[^"]+-Surf-Report/\d+/)">([^<]+)\s*</option>`)

func (loc *Location) GetConditions(client *http.Client) (*Conditions, error) {
	url := "http://magicseaweed.com" + loc.MagicSeaweedPath
	log.Printf("Fetching %s", url)
	resp, err := client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Failed to read body. %v", err)
	}
	foundStars := starRx.FindAll(body, -1)
	hMatch := heightRx.FindSubmatch(body)
	if len(hMatch) != 2 {
		return nil, fmt.Errorf("Wave height regex failed.")
	}
	rating := len(foundStars)
	details := fmt.Sprintf("%s ft", hMatch[1])
	cond := &Conditions{
		Loc:     loc,
		Rating:  rating,
		Details: details,
	}
	return cond, nil
}

func (c *Conditions) Stars() string {
	runes := make([]rune, 0, 5)
	for i := 0; i < c.Rating; i++ {
		runes = append(runes, '★')
	}
	for i := 0; i < 5-c.Rating; i++ {
		runes = append(runes, '☆')
	}
	return string(runes)
}

func keepConditionsUpdated() {
	updateConditionsAllLocations()
	tick := time.Tick(*conditionsInterval)
	for {
		<-tick
		updateConditionsAllLocations()
	}
}
<|endoftext|>


<commit_before>package lzb

import "errors"

type buffer struct {
	data       []byte
	bottom     int64 // bottom == max(top - len(data), 0)
	top        int64
	writeLimit int64
}

const maxWriteLimit = 1<<63 - 1

var (
	errOffset = errors.New("offset outside buffer range")
	errAgain  = errors.New("buffer overflow; repeat")
	errNegLen = errors.New("length is negative")
	errLimit  = errors.New("write limit reached")
)

func initBuffer(b *buffer, capacity int) {
	*b = buffer{data: make([]byte, capacity), writeLimit: maxWriteLimit}
}

func newBuffer(capacity int) *buffer {
	b := new(buffer)
	initBuffer(b, capacity)
	return b
}

func (b *buffer) capacity() int {
	return len(b.data)
}

func (b *buffer) length() int {
	return int(b.top - b.bottom)
}

func (b *buffer) setTop(off int64) {
	if off < 0 {
		panic("b.Top overflow?")
	}
	if off > b.writeLimit {
		panic("off exceeds writeLimit")
	}
	b.top = off
	b.bottom = off - int64(len(b.data))
	if b.bottom < 0 {
		b.bottom = 0
	}
}

func (b *buffer) index(off int64) int {
	if off < 0 {
		panic("negative offset?")
	}
	return int(off % int64(len(b.data)))
}

func (b *buffer) Write(p []byte) (n int, err error) {
	if len(p) == 0 {
		return
	}
	var m int
	off := b.top
	if off+int64(len(p)) > b.writeLimit {
		m = int(b.writeLimit - off)
		p = p[:m]
		err = errLimit
	}
	m = len(p) - len(b.data)
	if m > 0 {
		off += int64(m)
		p = p[m:]
	}
	for len(p) > 0 {
		m = copy(b.data[b.index(off):], p)
		off += int64(m)
		p = p[m:]
	}
	n = int(off - b.top)
	b.setTop(off)
	return n, err
}

func (b *buffer) WriteByte(c byte) error {
	if b.top >= b.writeLimit {
		return errLimit
	}
	b.data[b.index(b.top)] = c
	b.setTop(b.top + 1)
	return nil
}

func (b *buffer) writeRep(off int64, n int) (written int, err error) {
	if n < 0 {
		return 0, errNegLen
	}
	if !(b.bottom <= off && off <= b.top) {
		return 0, errOffset
	}
	start, end := off, off+int64(n)
	if !(end <= b.top) {
		return 0, errAgain
	}
	e := b.index(end)
	for off < end {
		s := b.index(off)
		var q []byte
		if s < e {
			q = b.data[s:e]
		} else {
			q = b.data[s:]
		}
		n, err := b.Write(q)
		off += int64(n)
		if err != nil {
			break
		}
		off += int64(n)
	}
	b.setTop(off)
	return int(off - start), nil
}

// equalBytes count the equal bytes at off1 and off2 until max is reached.
func (b *buffer) equalBytes(off1, off2 int64, max int) int {
	if off1 < b.top || off2 < b.top || max <= 0 {
		return 0
	}
	n := b.top - off1
	if n < int64(max) {
		if n <= 0 {
			return 0
		}
		max = int(n)
	}
	n = b.top - off2
	if n < int64(max) {
		if n <= 0 {
			return 0
		}
		max = int(n)
	}
	for k := 0; k < max; k++ {
		i, j := b.index(off1+int64(k)), b.index(off2+int64(k))
		if b.data[i] != b.data[j] {
			return k
		}
	}
	return max
}
<commit_msg>lzb: writeRep supports now n extending over the top of the buffer<commit_after>package lzb

import "errors"

type buffer struct {
	data       []byte
	bottom     int64 // bottom == max(top - len(data), 0)
	top        int64
	writeLimit int64
}

const maxWriteLimit = 1<<63 - 1

var (
	errOffset = errors.New("offset outside buffer range")
	errAgain  = errors.New("buffer overflow; repeat")
	errNegLen = errors.New("length is negative")
	errLimit  = errors.New("write limit reached")
)

func initBuffer(b *buffer, capacity int) {
	*b = buffer{data: make([]byte, capacity), writeLimit: maxWriteLimit}
}

func newBuffer(capacity int) *buffer {
	b := new(buffer)
	initBuffer(b, capacity)
	return b
}

func (b *buffer) capacity() int {
	return len(b.data)
}

func (b *buffer) length() int {
	return int(b.top - b.bottom)
}

func (b *buffer) setTop(off int64) {
	if off < 0 {
		panic("b.Top overflow?")
	}
	if off > b.writeLimit {
		panic("off exceeds writeLimit")
	}
	b.top = off
	b.bottom = off - int64(len(b.data))
	if b.bottom < 0 {
		b.bottom = 0
	}
}

func (b *buffer) index(off int64) int {
	if off < 0 {
		panic("negative offset?")
	}
	return int(off % int64(len(b.data)))
}

func (b *buffer) Write(p []byte) (n int, err error) {
	if len(p) == 0 {
		return
	}
	var m int
	off := b.top
	if off+int64(len(p)) > b.writeLimit {
		m = int(b.writeLimit - off)
		p = p[:m]
		err = errLimit
	}
	m = len(p) - len(b.data)
	if m > 0 {
		off += int64(m)
		p = p[m:]
	}
	for len(p) > 0 {
		m = copy(b.data[b.index(off):], p)
		off += int64(m)
		p = p[m:]
	}
	n = int(off - b.top)
	b.setTop(off)
	return n, err
}

func (b *buffer) WriteByte(c byte) error {
	if b.top >= b.writeLimit {
		return errLimit
	}
	b.data[b.index(b.top)] = c
	b.setTop(b.top + 1)
	return nil
}

func (b *buffer) writeRep(off int64, n int) (written int, err error) {
	if n < 0 {
		return 0, errNegLen
	}
	if !(b.bottom <= off && off < b.top) {
		return 0, errOffset
	}

	start, end := off, off+int64(n)
loop:
	for off < end {
		var next int64
		if b.top < end {
			next = b.top
		} else {
			next = end
		}
		e := b.index(next)
		for off < next {
			s := b.index(off)
			var q []byte
			if s < e {
				q = b.data[s:e]
			} else {
				q = b.data[s:]
			}
			var k int
			k, err = b.Write(q)
			off += int64(k)
			if err != nil {
				break loop
			}
		}
	}
	return int(off - start), err
}

// equalBytes count the equal bytes at off1 and off2 until max is reached.
func (b *buffer) equalBytes(off1, off2 int64, max int) int {
	if off1 < b.top || off2 < b.top || max <= 0 {
		return 0
	}
	n := b.top - off1
	if n < int64(max) {
		if n <= 0 {
			return 0
		}
		max = int(n)
	}
	n = b.top - off2
	if n < int64(max) {
		if n <= 0 {
			return 0
		}
		max = int(n)
	}
	for k := 0; k < max; k++ {
		i, j := b.index(off1+int64(k)), b.index(off2+int64(k))
		if b.data[i] != b.data[j] {
			return k
		}
	}
	return max
}
<|endoftext|>


<commit_before>package semver_test

import (
	"fmt"
	"testing"

	"github.com/hansrodtang/semver"
)

type comparison struct {
	main     *semver.Version
	other    *semver.Version
	expected int
}

var correctVersions = []string{
	"1.0.3-app-beta.1",
}

var badVersions = []string{
	"",
	".",
	"1.",
	".1",
	"a.b.c",
	"1.a.b",
	"1.1.a",
	"1.a.1",
	"a.1.1",
	"..",
	"1..",
	"1.1.",
	"1..1",
	"1.1.+123",
	"1.1.-beta",
	"-1.1.1",
	"1.-1.1",
	"1.1.-1",
	// Leading zeroes
	"01.1.1",
	"001.1.1",
	"1.01.1",
	"1.001.1",
	"1.1.01",
	"1.1.001",
	"1.1.1-01",
	"1.1.1-001",
	"1.1.1-beta.01",
	"1.1.1-beta.001",
	"0.0.0-!",
	"0.0.0+!",
	// empty prerelease
	"0.0.0-.alpha",
	// empty build metadata
	"0.0.0-alpha+",
	"0.0.0-alpha+test.",
}

var comparisons = []comparison{
	{semver.Build(1, 0, 0), semver.Build(1, 0, 0), 0},
	{semver.Build(2, 0, 0), semver.Build(1, 0, 0), 1},
	{semver.Build(0, 1, 0), semver.Build(0, 1, 0), 0},
	{semver.Build(0, 2, 0), semver.Build(0, 1, 0), 1},
	{semver.Build(0, 0, 1), semver.Build(0, 0, 1), 0},
	{semver.Build(0, 0, 2), semver.Build(0, 0, 1), 1},
	{semver.Build(1, 2, 3), semver.Build(1, 2, 3), 0},
	{semver.Build(2, 2, 4), semver.Build(1, 2, 4), 1},
	{semver.Build(1, 3, 3), semver.Build(1, 2, 3), 1},
	{semver.Build(1, 2, 4), semver.Build(1, 2, 3), 1},

	// Spec Examples #11
	{semver.Build(1, 0, 0), semver.Build(2, 0, 0), -1},
	{semver.Build(2, 0, 0), semver.Build(2, 1, 0), -1},
	{semver.Build(2, 1, 0), semver.Build(2, 1, 1), -1},

	// Spec Examples #9
	{semver.Build(1, 0, 0), semver.Build(1, 0, 0, []string{"alpha"}), 1},
	{semver.Build(1, 0, 0, []string{"alpha"}), semver.Build(1, 0, 0, []string{"alpha", "1"}), -1},
	{semver.Build(1, 0, 0, []string{"alpha", "1"}), semver.Build(1, 0, 0, []string{"alpha"}), 1},
	{semver.Build(1, 0, 0, []string{"alpha", "1"}), semver.Build(1, 0, 0, []string{"alpha", "beta"}), -1},
	{semver.Build(1, 0, 0, []string{"alpha", "beta"}), semver.Build(1, 0, 0, []string{"beta"}), -1},
	{semver.Build(1, 0, 0, []string{"beta"}), semver.Build(1, 0, 0, []string{"beta", "2"}), -1},
	{semver.Build(1, 0, 0, []string{"beta", "2"}), semver.Build(1, 0, 0, []string{"beta", "11"}), -1},
	{semver.Build(1, 0, 0, []string{"beta", "11"}), semver.Build(1, 0, 0, []string{"beta", "2"}), 1},
	{semver.Build(1, 0, 0, []string{"beta", "11"}), semver.Build(1, 0, 0, []string{"rc", "1"}), -1},
	{semver.Build(1, 0, 0, []string{"beta", "beta"}), semver.Build(1, 0, 0, []string{"beta", "alpha"}), 1},
	{semver.Build(1, 0, 0, []string{"rc", "1"}), semver.Build(1, 0, 0), -1},

	{semver.Build(1, 0, 0, []string{"beta", "alpha", "1"}), semver.Build(1, 0, 0, []string{"beta", "alpha"}), 1},

	{semver.Build(1, 0, 0, []string{"rc", "1"}), semver.Build(1, 0, 0, []string{"rc", "1"}, []string{"435345345"}), 0},
}

var badPreRelease = [][]string{
	{"alpha", "-3"},
}

func TestStringer(t *testing.T) {
	ver := semver.Build(1, 2, 3)
	ver.SetPrerelease("alpha", "1")
	ver.SetMetadata("35", "45")

	expected := "1.2.3-alpha.1+35.45"
	result := fmt.Sprint(ver)

	if result != expected {
		t.Errorf("String() => %q, wanted %q", result, expected)
	}
}

func TestGetters(t *testing.T) {
	expected_major := uint64(1)
	expected_minor := uint64(2)
	expected_patch := uint64(3)
	expected_prerelease := "alpha.1"
	expected_metadata := "35.45"

	ver := semver.Build(expected_major, expected_minor, expected_patch)
	ver.SetPrerelease("alpha", "1")
	ver.SetMetadata("35", "45")

	if result := ver.Major(); result != expected_major {
		t.Errorf("%q.Major() => %q, wanted %q", ver, result, expected_major)
	}
	if result := ver.Minor(); result != expected_minor {
		t.Errorf("%q.Minor() => %q, wanted %q", ver, result, expected_minor)
	}
	if result := ver.Patch(); result != expected_patch {
		t.Errorf("%q.Patch() => %q, wanted %q", ver, result, expected_patch)
	}
	if result := ver.Prerelease(); result != expected_prerelease {
		t.Errorf("%q.Prerelease() => %q, wanted %q", ver, result, expected_prerelease)
	}
	if result := ver.Metadata(); result != expected_metadata {
		t.Errorf("%q.Metadata() => %q, wanted %q", ver, result, expected_metadata)
	}
}

func TestSetters(t *testing.T) {
	ver := semver.Build(1, 1, 1)
	ver.SetMajor(2)
	ver.SetMinor(3)
	ver.SetPatch(4)
	if err := ver.SetPrerelease("beta", "1"); err != nil {
		t.Errorf(fmt.Sprint(err))
	}
	if err := ver.SetMetadata("22", "43"); err != nil {
		t.Errorf(fmt.Sprint(err))
	}

	expected := "2.3.4-beta.1+22.43"
	result := fmt.Sprint(ver)
	if result != expected {
		t.Errorf("Stringer() => %q, wanted %q", result, expected)
	}
}

func TestNew(t *testing.T) {
	expected := "1.0.3-alpha.1+35.45"

	ver, err := semver.New(expected)
	if err != nil {
		t.Errorf(fmt.Sprint(err))
	}

	result := fmt.Sprint(ver)
	if result != expected {
		t.Errorf("Stringer() => %q, want %q", result, expected)
	}

}

func TestBadFormat(t *testing.T) {
	for _, version := range badVersions {
		_, err := semver.New(version)
		if err == nil {
			// TODO: Set up error types
			t.Errorf("New(%q) => %v, want Error", version, err)
		}
	}
}

func TestCorrectFormat(t *testing.T) {
	for _, version := range correctVersions {
		_, err := semver.New(version)
		if err != nil {
			t.Errorf("New(%q) => %v, want <nil>", version, err)
		}
	}
}

func TestComparison(t *testing.T) {
	for _, c := range comparisons {
		result := c.main.Compare(c.other)
		if result != c.expected {
			t.Errorf("%q.Compare(%q) => %#v, want %#v", c.main, c.other, result, c.expected)
		}
	}
}

func TestSetPrerelease(t *testing.T) {
	ver := semver.Build(1, 2, 3)
	err := ver.SetPrerelease("12", "1", "0", "43")
	if err != nil {
		t.Errorf(fmt.Sprint(err))
	}
}

func BenchmarkParseSimple(b *testing.B) {
	const VERSION = "0.0.1"
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		semver.New(VERSION)
	}
}

func BenchmarkParseComplex(b *testing.B) {
	const VERSION = "0.0.1-alpha.preview+123.456"
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		semver.New(VERSION)
	}
}

func BenchmarkCompareSimple(b *testing.B) {
	const VERSION = "0.0.1"
	v, _ := semver.New(VERSION)
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		v.Compare(v)
	}
}

func BenchmarkCompareComplex(b *testing.B) {
	const VERSION = "0.0.1-alpha.preview+123.456"
	const VERSION2 = "0.0.1-alpha.preview+123.456"
	v, _ := semver.New(VERSION)
	v2, _ := semver.New(VERSION2)
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		v.Compare(v2)
	}
}
<commit_msg>Renamed some underscored variables.<commit_after>package semver_test

import (
	"fmt"
	"testing"

	"github.com/hansrodtang/semver"
)

type comparison struct {
	main     *semver.Version
	other    *semver.Version
	expected int
}

var correctVersions = []string{
	"1.0.3-app-beta.1",
}

var badVersions = []string{
	"",
	".",
	"1.",
	".1",
	"a.b.c",
	"1.a.b",
	"1.1.a",
	"1.a.1",
	"a.1.1",
	"..",
	"1..",
	"1.1.",
	"1..1",
	"1.1.+123",
	"1.1.-beta",
	"-1.1.1",
	"1.-1.1",
	"1.1.-1",
	// Leading zeroes
	"01.1.1",
	"001.1.1",
	"1.01.1",
	"1.001.1",
	"1.1.01",
	"1.1.001",
	"1.1.1-01",
	"1.1.1-001",
	"1.1.1-beta.01",
	"1.1.1-beta.001",
	"0.0.0-!",
	"0.0.0+!",
	// empty prerelease
	"0.0.0-.alpha",
	// empty build metadata
	"0.0.0-alpha+",
	"0.0.0-alpha+test.",
}

var comparisons = []comparison{
	{semver.Build(1, 0, 0), semver.Build(1, 0, 0), 0},
	{semver.Build(2, 0, 0), semver.Build(1, 0, 0), 1},
	{semver.Build(0, 1, 0), semver.Build(0, 1, 0), 0},
	{semver.Build(0, 2, 0), semver.Build(0, 1, 0), 1},
	{semver.Build(0, 0, 1), semver.Build(0, 0, 1), 0},
	{semver.Build(0, 0, 2), semver.Build(0, 0, 1), 1},
	{semver.Build(1, 2, 3), semver.Build(1, 2, 3), 0},
	{semver.Build(2, 2, 4), semver.Build(1, 2, 4), 1},
	{semver.Build(1, 3, 3), semver.Build(1, 2, 3), 1},
	{semver.Build(1, 2, 4), semver.Build(1, 2, 3), 1},

	// Spec Examples #11
	{semver.Build(1, 0, 0), semver.Build(2, 0, 0), -1},
	{semver.Build(2, 0, 0), semver.Build(2, 1, 0), -1},
	{semver.Build(2, 1, 0), semver.Build(2, 1, 1), -1},

	// Spec Examples #9
	{semver.Build(1, 0, 0), semver.Build(1, 0, 0, []string{"alpha"}), 1},
	{semver.Build(1, 0, 0, []string{"alpha"}), semver.Build(1, 0, 0, []string{"alpha", "1"}), -1},
	{semver.Build(1, 0, 0, []string{"alpha", "1"}), semver.Build(1, 0, 0, []string{"alpha"}), 1},
	{semver.Build(1, 0, 0, []string{"alpha", "1"}), semver.Build(1, 0, 0, []string{"alpha", "beta"}), -1},
	{semver.Build(1, 0, 0, []string{"alpha", "beta"}), semver.Build(1, 0, 0, []string{"beta"}), -1},
	{semver.Build(1, 0, 0, []string{"beta"}), semver.Build(1, 0, 0, []string{"beta", "2"}), -1},
	{semver.Build(1, 0, 0, []string{"beta", "2"}), semver.Build(1, 0, 0, []string{"beta", "11"}), -1},
	{semver.Build(1, 0, 0, []string{"beta", "11"}), semver.Build(1, 0, 0, []string{"beta", "2"}), 1},
	{semver.Build(1, 0, 0, []string{"beta", "11"}), semver.Build(1, 0, 0, []string{"rc", "1"}), -1},
	{semver.Build(1, 0, 0, []string{"beta", "beta"}), semver.Build(1, 0, 0, []string{"beta", "alpha"}), 1},
	{semver.Build(1, 0, 0, []string{"rc", "1"}), semver.Build(1, 0, 0), -1},

	{semver.Build(1, 0, 0, []string{"beta", "alpha", "1"}), semver.Build(1, 0, 0, []string{"beta", "alpha"}), 1},

	{semver.Build(1, 0, 0, []string{"rc", "1"}), semver.Build(1, 0, 0, []string{"rc", "1"}, []string{"435345345"}), 0},
}

var badPreRelease = [][]string{
	{"alpha", "-3"},
}

func TestStringer(t *testing.T) {
	ver := semver.Build(1, 2, 3)
	ver.SetPrerelease("alpha", "1")
	ver.SetMetadata("35", "45")

	expected := "1.2.3-alpha.1+35.45"
	result := fmt.Sprint(ver)

	if result != expected {
		t.Errorf("String() => %q, wanted %q", result, expected)
	}
}

func TestGetters(t *testing.T) {
	expectedMajor := uint64(1)
	expectedMinor := uint64(2)
	expectedPatch := uint64(3)
	expectedPrerelease := "alpha.1"
	expectedMetadata := "35.45"

	ver := semver.Build(expectedMajor, expectedMinor, expectedPatch)
	ver.SetPrerelease("alpha", "1")
	ver.SetMetadata("35", "45")

	if result := ver.Major(); result != expectedMajor {
		t.Errorf("%q.Major() => %q, wanted %q", ver, result, expectedMajor)
	}
	if result := ver.Minor(); result != expectedMinor {
		t.Errorf("%q.Minor() => %q, wanted %q", ver, result, expectedMinor)
	}
	if result := ver.Patch(); result != expectedPatch {
		t.Errorf("%q.Patch() => %q, wanted %q", ver, result, expectedPatch)
	}
	if result := ver.Prerelease(); result != expectedPrerelease {
		t.Errorf("%q.Prerelease() => %q, wanted %q", ver, result, expectedPrerelease)
	}
	if result := ver.Metadata(); result != expectedMetadata {
		t.Errorf("%q.Metadata() => %q, wanted %q", ver, result, expectedMetadata)
	}
}

func TestSetters(t *testing.T) {
	ver := semver.Build(1, 1, 1)
	ver.SetMajor(2)
	ver.SetMinor(3)
	ver.SetPatch(4)
	if err := ver.SetPrerelease("beta", "1"); err != nil {
		t.Errorf(fmt.Sprint(err))
	}
	if err := ver.SetMetadata("22", "43"); err != nil {
		t.Errorf(fmt.Sprint(err))
	}

	expected := "2.3.4-beta.1+22.43"
	result := fmt.Sprint(ver)
	if result != expected {
		t.Errorf("Stringer() => %q, wanted %q", result, expected)
	}
}

func TestNew(t *testing.T) {
	expected := "1.0.3-alpha.1+35.45"

	ver, err := semver.New(expected)
	if err != nil {
		t.Errorf(fmt.Sprint(err))
	}

	result := fmt.Sprint(ver)
	if result != expected {
		t.Errorf("Stringer() => %q, want %q", result, expected)
	}

}

func TestBadFormat(t *testing.T) {
	for _, version := range badVersions {
		_, err := semver.New(version)
		if err == nil {
			// TODO: Set up error types
			t.Errorf("New(%q) => %v, want Error", version, err)
		}

	}
}

func TestCorrectFormat(t *testing.T) {
	for _, version := range correctVersions {
		_, err := semver.New(version)
		if err != nil {
			t.Errorf("New(%q) => %v, want <nil>", version, err)
		}
	}
}

func TestComparison(t *testing.T) {
	for _, c := range comparisons {
		result := c.main.Compare(c.other)
		if result != c.expected {
			t.Errorf("%q.Compare(%q) => %#v, want %#v", c.main, c.other, result, c.expected)
		}
	}
}

func TestSetPrerelease(t *testing.T) {
	ver := semver.Build(1, 2, 3)
	err := ver.SetPrerelease("12", "1", "0", "43")
	if err != nil {
		t.Errorf(fmt.Sprint(err))
	}
}

func BenchmarkParseSimple(b *testing.B) {
	const VERSION = "0.0.1"
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		semver.New(VERSION)
	}
}

func BenchmarkParseComplex(b *testing.B) {
	const VERSION = "0.0.1-alpha.preview+123.456"
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		semver.New(VERSION)
	}
}

func BenchmarkCompareSimple(b *testing.B) {
	const VERSION = "0.0.1"
	v, _ := semver.New(VERSION)
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		v.Compare(v)
	}
}

func BenchmarkCompareComplex(b *testing.B) {
	const VERSION = "0.0.1-alpha.preview+123.456"
	const VERSION2 = "0.0.1-alpha.preview+123.456"
	v, _ := semver.New(VERSION)
	v2, _ := semver.New(VERSION2)
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		v.Compare(v2)
	}
}
<|endoftext|>


<commit_before>//
// Copyright 2014-2016 Cristian Maglie. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

// +build linux darwin freebsd

package serial // import "go.bug.st/serial.v1"

import (
	"io/ioutil"
	"regexp"
	"strings"
	"sync"
	"syscall"
	"unsafe"

	"github.com/creack/goselect"
)

type unixPort struct {
	handle int

	closeLock   sync.RWMutex
	closeSignal *pipe
}

func (port *unixPort) Close() error {
	// Close port
	port.releaseExclusiveAccess()
	if err := syscall.Close(port.handle); err != nil {
		return err
	}

	// Send close signal to all pending reads (if any)
	port.closeSignal.Write([]byte{0})

	// Wait for all readers to complete
	port.closeLock.Lock()
	defer port.closeLock.Unlock()

	// Close signaling pipe
	if err := port.closeSignal.Close(); err != nil {
		return err
	}
	return nil
}

func (port *unixPort) Read(p []byte) (n int, err error) {
	port.closeLock.RLock()
	defer port.closeLock.RUnlock()

	r := &goselect.FDSet{}
	r.Set(uintptr(port.handle))
	r.Set(uintptr(port.closeSignal.ReadFD()))
	e := &goselect.FDSet{}
	e.Set(uintptr(port.handle))
	e.Set(uintptr(port.closeSignal.ReadFD()))

	max := port.closeSignal.ReadFD()
	if port.handle > max {
		max = port.handle
	}
	if err = goselect.Select(max+1, r, nil, e, -1); err != nil {
		return 0, err
	}
	return syscall.Read(port.handle, p)
}

func (port *unixPort) Write(p []byte) (n int, err error) {
	return syscall.Write(port.handle, p)
}

func (port *unixPort) SetMode(mode *Mode) error {
	settings, err := port.getTermSettings()
	if err != nil {
		return err
	}
	if err := setTermSettingsBaudrate(mode.BaudRate, settings); err != nil {
		return err
	}
	if err := setTermSettingsParity(mode.Parity, settings); err != nil {
		return err
	}
	if err := setTermSettingsDataBits(mode.DataBits, settings); err != nil {
		return err
	}
	if err := setTermSettingsStopBits(mode.StopBits, settings); err != nil {
		return err
	}
	return port.setTermSettings(settings)
}

func nativeOpen(portName string, mode *Mode) (*unixPort, error) {
	h, err := syscall.Open(portName, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NDELAY, 0)
	if err != nil {
		switch err {
		case syscall.EBUSY:
			return nil, &PortError{code: PortBusy}
		case syscall.EACCES:
			return nil, &PortError{code: PermissionDenied}
		}
		return nil, err
	}
	port := &unixPort{
		handle: h,
	}

	// Setup serial port
	if port.SetMode(mode) != nil {
		port.Close()
		return nil, &PortError{code: InvalidSerialPort}
	}

	settings, err := port.getTermSettings()
	if err != nil {
		port.Close()
		return nil, &PortError{code: InvalidSerialPort}
	}

	// Set raw mode
	setRawMode(settings)

	// Explicitly disable RTS/CTS flow control
	setTermSettingsCtsRts(false, settings)

	if port.setTermSettings(settings) != nil {
		port.Close()
		return nil, &PortError{code: InvalidSerialPort}
	}

	syscall.SetNonblock(h, false)

	port.acquireExclusiveAccess()

	// This pipe is used as a signal to cancel blocking Read or Write
	pipe, err := newPipe()
	if err != nil {
		port.Close()
		return nil, &PortError{code: InvalidSerialPort, causedBy: err}
	}
	port.closeSignal = pipe

	return port, nil
}

func nativeGetPortsList() ([]string, error) {
	files, err := ioutil.ReadDir(devFolder)
	if err != nil {
		return nil, err
	}

	ports := make([]string, 0, len(files))
	for _, f := range files {
		// Skip folders
		if f.IsDir() {
			continue
		}

		// Keep only devices with the correct name
		match, err := regexp.MatchString(regexFilter, f.Name())
		if err != nil {
			return nil, err
		}
		if !match {
			continue
		}

		portName := devFolder + "/" + f.Name()

		// Check if serial port is real or is a placeholder serial port "ttySxx"
		if strings.HasPrefix(f.Name(), "ttyS") {
			port, err := nativeOpen(portName, &Mode{})
			if err != nil {
				serr, ok := err.(*PortError)
				if ok && serr.Code() == InvalidSerialPort {
					continue
				}
			} else {
				port.Close()
			}
		}

		// Save serial port in the resulting list
		ports = append(ports, portName)
	}

	return ports, nil
}

// termios manipulation functions

func setTermSettingsBaudrate(speed int, settings *syscall.Termios) error {
	baudrate, ok := baudrateMap[speed]
	if !ok {
		return &PortError{code: InvalidSpeed}
	}
	// revert old baudrate
	for _, rate := range baudrateMap {
		settings.Cflag &^= rate
	}
	// set new baudrate
	settings.Cflag |= baudrate
	settings.Ispeed = baudrate
	settings.Ospeed = baudrate
	return nil
}

func setTermSettingsParity(parity Parity, settings *syscall.Termios) error {
	switch parity {
	case NoParity:
		settings.Cflag &^= syscall.PARENB
		settings.Cflag &^= syscall.PARODD
		settings.Cflag &^= tcCMSPAR
		settings.Iflag &^= syscall.INPCK
	case OddParity:
		settings.Cflag |= syscall.PARENB
		settings.Cflag |= syscall.PARODD
		settings.Cflag &^= tcCMSPAR
		settings.Iflag |= syscall.INPCK
	case EvenParity:
		settings.Cflag |= syscall.PARENB
		settings.Cflag &^= syscall.PARODD
		settings.Cflag &^= tcCMSPAR
		settings.Iflag |= syscall.INPCK
	case MarkParity:
		if tcCMSPAR == 0 {
			return &PortError{code: InvalidParity}
		}
		settings.Cflag |= syscall.PARENB
		settings.Cflag |= syscall.PARODD
		settings.Cflag |= tcCMSPAR
		settings.Iflag |= syscall.INPCK
	case SpaceParity:
		if tcCMSPAR == 0 {
			return &PortError{code: InvalidParity}
		}
		settings.Cflag |= syscall.PARENB
		settings.Cflag &^= syscall.PARODD
		settings.Cflag |= tcCMSPAR
		settings.Iflag |= syscall.INPCK
	default:
		return &PortError{code: InvalidParity}
	}
	return nil
}

func setTermSettingsDataBits(bits int, settings *syscall.Termios) error {
	databits, ok := databitsMap[bits]
	if !ok {
		return &PortError{code: InvalidDataBits}
	}
	// Remove previous databits setting
	settings.Cflag &^= syscall.CSIZE
	// Set requested databits
	settings.Cflag |= databits
	return nil
}

func setTermSettingsStopBits(bits StopBits, settings *syscall.Termios) error {
	switch bits {
	case OneStopBit:
		settings.Cflag &^= syscall.CSTOPB
	case OnePointFiveStopBits:
		return &PortError{code: InvalidStopBits}
	case TwoStopBits:
		settings.Cflag |= syscall.CSTOPB
	default:
		return &PortError{code: InvalidStopBits}
	}
	return nil
}

func setTermSettingsCtsRts(enable bool, settings *syscall.Termios) {
	if enable {
		settings.Cflag |= tcCRTSCTS
	} else {
		settings.Cflag &^= tcCRTSCTS
	}
}

func setRawMode(settings *syscall.Termios) {
	// Set local mode
	settings.Cflag |= syscall.CREAD
	settings.Cflag |= syscall.CLOCAL

	// Set raw mode
	settings.Lflag &^= syscall.ICANON
	settings.Lflag &^= syscall.ECHO
	settings.Lflag &^= syscall.ECHOE
	settings.Lflag &^= syscall.ECHOK
	settings.Lflag &^= syscall.ECHONL
	settings.Lflag &^= syscall.ECHOCTL
	settings.Lflag &^= syscall.ECHOPRT
	settings.Lflag &^= syscall.ECHOKE
	settings.Lflag &^= syscall.ISIG
	settings.Lflag &^= syscall.IEXTEN

	settings.Iflag &^= syscall.IXON
	settings.Iflag &^= syscall.IXOFF
	settings.Iflag &^= syscall.IXANY
	settings.Iflag &^= syscall.INPCK
	settings.Iflag &^= syscall.IGNPAR
	settings.Iflag &^= syscall.PARMRK
	settings.Iflag &^= syscall.ISTRIP
	settings.Iflag &^= syscall.IGNBRK
	settings.Iflag &^= syscall.BRKINT
	settings.Iflag &^= syscall.INLCR
	settings.Iflag &^= syscall.IGNCR
	settings.Iflag &^= syscall.ICRNL
	settings.Iflag &^= tcIUCLC

	settings.Oflag &^= syscall.OPOST

	// Block reads until at least one char is available (no timeout)
	settings.Cc[syscall.VMIN] = 1
	settings.Cc[syscall.VTIME] = 0
}

// native syscall wrapper functions

func (port *unixPort) getTermSettings() (*syscall.Termios, error) {
	settings := &syscall.Termios{}
	err := ioctl(port.handle, ioctlTcgetattr, uintptr(unsafe.Pointer(settings)))
	return settings, err
}

func (port *unixPort) setTermSettings(settings *syscall.Termios) error {
	return ioctl(port.handle, ioctlTcsetattr, uintptr(unsafe.Pointer(settings)))
}

func (port *unixPort) acquireExclusiveAccess() error {
	return ioctl(port.handle, syscall.TIOCEXCL, 0)
}

func (port *unixPort) releaseExclusiveAccess() error {
	return ioctl(port.handle, syscall.TIOCNXCL, 0)
}
<commit_msg>posix: added check for port closing<commit_after>//
// Copyright 2014-2016 Cristian Maglie. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

// +build linux darwin freebsd

package serial // import "go.bug.st/serial.v1"

import (
	"io/ioutil"
	"regexp"
	"strings"
	"sync"
	"syscall"
	"unsafe"

	"github.com/creack/goselect"
)

type unixPort struct {
	handle int

	closeLock   sync.RWMutex
	closeSignal *pipe
}

func (port *unixPort) Close() error {
	// Close port
	port.releaseExclusiveAccess()
	if err := syscall.Close(port.handle); err != nil {
		return err
	}

	if port.closeSignal != nil {
		// Send close signal to all pending reads (if any)
		port.closeSignal.Write([]byte{0})

		// Wait for all readers to complete
		port.closeLock.Lock()
		defer port.closeLock.Unlock()

		// Close signaling pipe
		if err := port.closeSignal.Close(); err != nil {
			return err
		}
	}
	return nil
}

func (port *unixPort) Read(p []byte) (n int, err error) {
	port.closeLock.RLock()
	defer port.closeLock.RUnlock()

	r := &goselect.FDSet{}
	r.Set(uintptr(port.handle))
	r.Set(uintptr(port.closeSignal.ReadFD()))
	e := &goselect.FDSet{}
	e.Set(uintptr(port.handle))
	e.Set(uintptr(port.closeSignal.ReadFD()))

	max := port.closeSignal.ReadFD()
	if port.handle > max {
		max = port.handle
	}
	if err = goselect.Select(max+1, r, nil, e, -1); err != nil {
		return 0, err
	}
	return syscall.Read(port.handle, p)
}

func (port *unixPort) Write(p []byte) (n int, err error) {
	return syscall.Write(port.handle, p)
}

func (port *unixPort) SetMode(mode *Mode) error {
	settings, err := port.getTermSettings()
	if err != nil {
		return err
	}
	if err := setTermSettingsBaudrate(mode.BaudRate, settings); err != nil {
		return err
	}
	if err := setTermSettingsParity(mode.Parity, settings); err != nil {
		return err
	}
	if err := setTermSettingsDataBits(mode.DataBits, settings); err != nil {
		return err
	}
	if err := setTermSettingsStopBits(mode.StopBits, settings); err != nil {
		return err
	}
	return port.setTermSettings(settings)
}

func nativeOpen(portName string, mode *Mode) (*unixPort, error) {
	h, err := syscall.Open(portName, syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_NDELAY, 0)
	if err != nil {
		switch err {
		case syscall.EBUSY:
			return nil, &PortError{code: PortBusy}
		case syscall.EACCES:
			return nil, &PortError{code: PermissionDenied}
		}
		return nil, err
	}
	port := &unixPort{
		handle: h,
	}

	// Setup serial port
	if port.SetMode(mode) != nil {
		port.Close()
		return nil, &PortError{code: InvalidSerialPort}
	}

	settings, err := port.getTermSettings()
	if err != nil {
		port.Close()
		return nil, &PortError{code: InvalidSerialPort}
	}

	// Set raw mode
	setRawMode(settings)

	// Explicitly disable RTS/CTS flow control
	setTermSettingsCtsRts(false, settings)

	if port.setTermSettings(settings) != nil {
		port.Close()
		return nil, &PortError{code: InvalidSerialPort}
	}

	syscall.SetNonblock(h, false)

	port.acquireExclusiveAccess()

	// This pipe is used as a signal to cancel blocking Read or Write
	pipe, err := newPipe()
	if err != nil {
		port.Close()
		return nil, &PortError{code: InvalidSerialPort, causedBy: err}
	}
	port.closeSignal = pipe

	return port, nil
}

func nativeGetPortsList() ([]string, error) {
	files, err := ioutil.ReadDir(devFolder)
	if err != nil {
		return nil, err
	}

	ports := make([]string, 0, len(files))
	for _, f := range files {
		// Skip folders
		if f.IsDir() {
			continue
		}

		// Keep only devices with the correct name
		match, err := regexp.MatchString(regexFilter, f.Name())
		if err != nil {
			return nil, err
		}
		if !match {
			continue
		}

		portName := devFolder + "/" + f.Name()

		// Check if serial port is real or is a placeholder serial port "ttySxx"
		if strings.HasPrefix(f.Name(), "ttyS") {
			port, err := nativeOpen(portName, &Mode{})
			if err != nil {
				serr, ok := err.(*PortError)
				if ok && serr.Code() == InvalidSerialPort {
					continue
				}
			} else {
				port.Close()
			}
		}

		// Save serial port in the resulting list
		ports = append(ports, portName)
	}

	return ports, nil
}

// termios manipulation functions

func setTermSettingsBaudrate(speed int, settings *syscall.Termios) error {
	baudrate, ok := baudrateMap[speed]
	if !ok {
		return &PortError{code: InvalidSpeed}
	}
	// revert old baudrate
	for _, rate := range baudrateMap {
		settings.Cflag &^= rate
	}
	// set new baudrate
	settings.Cflag |= baudrate
	settings.Ispeed = baudrate
	settings.Ospeed = baudrate
	return nil
}

func setTermSettingsParity(parity Parity, settings *syscall.Termios) error {
	switch parity {
	case NoParity:
		settings.Cflag &^= syscall.PARENB
		settings.Cflag &^= syscall.PARODD
		settings.Cflag &^= tcCMSPAR
		settings.Iflag &^= syscall.INPCK
	case OddParity:
		settings.Cflag |= syscall.PARENB
		settings.Cflag |= syscall.PARODD
		settings.Cflag &^= tcCMSPAR
		settings.Iflag |= syscall.INPCK
	case EvenParity:
		settings.Cflag |= syscall.PARENB
		settings.Cflag &^= syscall.PARODD
		settings.Cflag &^= tcCMSPAR
		settings.Iflag |= syscall.INPCK
	case MarkParity:
		if tcCMSPAR == 0 {
			return &PortError{code: InvalidParity}
		}
		settings.Cflag |= syscall.PARENB
		settings.Cflag |= syscall.PARODD
		settings.Cflag |= tcCMSPAR
		settings.Iflag |= syscall.INPCK
	case SpaceParity:
		if tcCMSPAR == 0 {
			return &PortError{code: InvalidParity}
		}
		settings.Cflag |= syscall.PARENB
		settings.Cflag &^= syscall.PARODD
		settings.Cflag |= tcCMSPAR
		settings.Iflag |= syscall.INPCK
	default:
		return &PortError{code: InvalidParity}
	}
	return nil
}

func setTermSettingsDataBits(bits int, settings *syscall.Termios) error {
	databits, ok := databitsMap[bits]
	if !ok {
		return &PortError{code: InvalidDataBits}
	}
	// Remove previous databits setting
	settings.Cflag &^= syscall.CSIZE
	// Set requested databits
	settings.Cflag |= databits
	return nil
}

func setTermSettingsStopBits(bits StopBits, settings *syscall.Termios) error {
	switch bits {
	case OneStopBit:
		settings.Cflag &^= syscall.CSTOPB
	case OnePointFiveStopBits:
		return &PortError{code: InvalidStopBits}
	case TwoStopBits:
		settings.Cflag |= syscall.CSTOPB
	default:
		return &PortError{code: InvalidStopBits}
	}
	return nil
}

func setTermSettingsCtsRts(enable bool, settings *syscall.Termios) {
	if enable {
		settings.Cflag |= tcCRTSCTS
	} else {
		settings.Cflag &^= tcCRTSCTS
	}
}

func setRawMode(settings *syscall.Termios) {
	// Set local mode
	settings.Cflag |= syscall.CREAD
	settings.Cflag |= syscall.CLOCAL

	// Set raw mode
	settings.Lflag &^= syscall.ICANON
	settings.Lflag &^= syscall.ECHO
	settings.Lflag &^= syscall.ECHOE
	settings.Lflag &^= syscall.ECHOK
	settings.Lflag &^= syscall.ECHONL
	settings.Lflag &^= syscall.ECHOCTL
	settings.Lflag &^= syscall.ECHOPRT
	settings.Lflag &^= syscall.ECHOKE
	settings.Lflag &^= syscall.ISIG
	settings.Lflag &^= syscall.IEXTEN

	settings.Iflag &^= syscall.IXON
	settings.Iflag &^= syscall.IXOFF
	settings.Iflag &^= syscall.IXANY
	settings.Iflag &^= syscall.INPCK
	settings.Iflag &^= syscall.IGNPAR
	settings.Iflag &^= syscall.PARMRK
	settings.Iflag &^= syscall.ISTRIP
	settings.Iflag &^= syscall.IGNBRK
	settings.Iflag &^= syscall.BRKINT
	settings.Iflag &^= syscall.INLCR
	settings.Iflag &^= syscall.IGNCR
	settings.Iflag &^= syscall.ICRNL
	settings.Iflag &^= tcIUCLC

	settings.Oflag &^= syscall.OPOST

	// Block reads until at least one char is available (no timeout)
	settings.Cc[syscall.VMIN] = 1
	settings.Cc[syscall.VTIME] = 0
}

// native syscall wrapper functions

func (port *unixPort) getTermSettings() (*syscall.Termios, error) {
	settings := &syscall.Termios{}
	err := ioctl(port.handle, ioctlTcgetattr, uintptr(unsafe.Pointer(settings)))
	return settings, err
}

func (port *unixPort) setTermSettings(settings *syscall.Termios) error {
	return ioctl(port.handle, ioctlTcsetattr, uintptr(unsafe.Pointer(settings)))
}

func (port *unixPort) acquireExclusiveAccess() error {
	return ioctl(port.handle, syscall.TIOCEXCL, 0)
}

func (port *unixPort) releaseExclusiveAccess() error {
	return ioctl(port.handle, syscall.TIOCNXCL, 0)
}
<|endoftext|>


<commit_before>package imux

import (
	log "github.com/Sirupsen/logrus"
	"github.com/hkparker/TLB"
	"net"
	"reflect"
	"sync"
)

// Chan of failures to write to destinations
var FailedSocketOuts = make(map[string]chan bool)
var fsoMux sync.Mutex

// WriteQueues for each outgoing socket on the server
var server_write_queues = make(map[string]*WriteQueue)
var swqMux sync.Mutex

// DataIMUX objects to read responses from each outgoing destination socket
var responders = make(map[string]DataIMUX)
var respondersMux sync.Mutex

// Tracks if goroutines have been created for each socket to read from the
// DataIMUXer for its session and write responses down
var loopers = make(map[net.Conn]bool)
var loopersMux sync.Mutex

// Create a new TLB server to accept chunks from anywhere and order them, writing them to corresponding sockets
func ManyToOne(listener net.Listener, dial_destination Redialer) {
	tlb_server := tlb.NewServer(listener, tag_socket, type_store())
	tlb_server.Accept("all", reflect.TypeOf(Chunk{}), func(iface interface{}, context tlb.TLBContext) {
		if chunk, ok := iface.(*Chunk); ok {
			log.WithFields(log.Fields{
				"at":          "ManyToOne",
				"sequence_id": chunk.SequenceID,
				"socket_id":   chunk.SocketID,
				"session_id":  chunk.SessionID,
			}).Debug("received chunk")
			createFailReporterIfNeeded(chunk.SocketID, chunk.SessionID)
			createResponderIMUXIfNeeded(chunk.SessionID)
			writeResponseChunksIfNeeded(context.Socket, chunk.SessionID)
			queue, err := queueForDestinationDialIfNeeded(chunk.SocketID, chunk.SessionID, dial_destination)
			if err == nil {
				queue.Chunks <- chunk
				log.WithFields(log.Fields{
					"at":          "ManyToOne",
					"sequence_id": chunk.SequenceID,
					"socket_id":   chunk.SocketID,
					"session_id":  chunk.SessionID,
				}).Debug("wrote chunk")
			} else {
				log.WithFields(log.Fields{
					"at":          "ManyToOne",
					"error":       err.Error(),
					"sequence_id": chunk.SequenceID,
					"socket_id":   chunk.SocketID,
					"session_id":  chunk.SessionID,
				}).Error("dropped chunk")
				if reporter, ok := FailedSocketOuts[chunk.SocketID]; ok {
					reporter <- true
				} else {
					log.WithFields(log.Fields{
						"at":        "ManyToOne",
						"socket_id": chunk.SocketID,
					}).Error("unable to lookup fail socket out channel")
				}
			}
		}
	})

	log.WithFields(log.Fields{
		"at": "ManyToOne",
	}).Debug("created new ManyToOne")
	err := <-tlb_server.FailedServer
	log.WithFields(log.Fields{
		"error": err.Error(),
	}).Error("TLB server failed for ManyToOne")
}

// Create a chan if needed to pass events about this socket failing
func createFailReporterIfNeeded(socket_id, session_id string) {
	fsoMux.Lock()
	if _, present := FailedSocketOuts[socket_id]; !present {
		FailedSocketOuts[socket_id] = make(chan bool, 0)
		go func(socket_id, session_id string) {
			for {
				<-FailedSocketOuts[socket_id]
				responders[session_id].Chunks <- Chunk{
					SessionID:  session_id,
					SocketID:   socket_id,
					SequenceID: 0,
					Close:      true,
				}
			}
		}(socket_id, session_id)
	}
	fsoMux.Unlock()
}

// If it does not exist, create a DataIMUX to read data from
// outgoing destination sockets with a common session
func createResponderIMUXIfNeeded(session_id string) {
	respondersMux.Lock()
	if _, present := responders[session_id]; !present {
		responders[session_id] = NewDataIMUX(session_id)
		log.WithFields(log.Fields{
			"at":         "createResponderIMUXIfNeeded",
			"session_id": session_id,
		}).Debug("created new responder imux for session")
	}
	respondersMux.Unlock()
}

// If it is not already happening, ensure that response chunks for a specified
// session_id are written back down this socket.
func writeResponseChunksIfNeeded(socket net.Conn, session_id string) {
	loopersMux.Lock()
	if _, looping := loopers[socket]; !looping {
		log.WithFields(log.Fields{
			"at":         "writeResponseChunksIfNeeded",
			"session_id": session_id,
		}).Debug("creating write back routine for socket")
		go func() {
			writer, err := tlb.NewStreamWriter(socket, type_store(), reflect.TypeOf(Chunk{}))
			if err != nil {
				log.WithFields(log.Fields{
					"at":         "writeResponseChunksIfNeeded",
					"session_id": session_id,
					"error":      err.Error(),
				}).Error("error create return stream writer")
				return
			}
			respondersMux.Lock()
			chunk_stream, ok := responders[session_id]
			if !ok {
				log.WithFields(log.Fields{
					"at":         "writeResponseChunksIfNeeded",
					"session_id": session_id,
				}).Error("error looking up responder imux for session_id")
				return
			}
			respondersMux.Unlock()
			for {
				new_chunk := <-chunk_stream.Chunks
				err := writer.Write(new_chunk)
				if err != nil {
					responders[session_id].Stale <- new_chunk
					log.WithFields(log.Fields{
						"at":         "writeResponseChunksIfNeeded",
						"session_id": session_id,
						"data_len":   len(new_chunk.Data),
						"error":      err.Error(),
					}).Error("error writing a chunk down transport socket")
					break
				} else {
					log.WithFields(log.Fields{
						"at":         "writeResponseChunksIfNeeded",
						"session_id": session_id,
						"data_len":   len(new_chunk.Data),
					}).Debug("wrote a chunk down transport socket")
				}
			}
		}()
		loopers[socket] = true
	}
	loopersMux.Unlock()
}

// Get the queue a new chunk should go to, dialing the outgoing destination socket if this is the first time
// a socket ID has been observed.
func queueForDestinationDialIfNeeded(socket_id, session_id string, dial_destination func() (net.Conn, error)) (*WriteQueue, error) {
	swqMux.Lock()
	queue, present := server_write_queues[socket_id]
	if !present {
		log.WithFields(log.Fields{
			"at":         "queueForDestinationDialIfNeeded",
			"session_id": session_id,
			"socket_id":  socket_id,
		}).Debug("dialing destination")
		destination, err := dial_destination()
		if err != nil {
			log.WithFields(log.Fields{
				"at":         "queueForDestinationDialIfNeeded",
				"session_id": session_id,
				"socket_id":  socket_id,
				"error":      err.Error(),
			}).Error("error dialing destination")
			return queue, err
		}
		queue = NewWriteQueue(destination)
		server_write_queues[socket_id] = queue
		respondersMux.Lock()
		if imuxer, ok := responders[session_id]; ok {
			go imuxer.ReadFrom(socket_id, destination)
		} else {
			log.WithFields(log.Fields{
				"at":         "queueForDestinationDialIfNeeded",
				"session_id": session_id,
				"socket_id":  socket_id,
			}).Fatal("no responding reader exists, should not be possible")
		}
		respondersMux.Unlock()
	}
	swqMux.Unlock()
	return queue, nil
}
<commit_msg>defer unlocks, fixes dial after down upstream bug<commit_after>package imux

import (
	log "github.com/Sirupsen/logrus"
	"github.com/hkparker/TLB"
	"net"
	"reflect"
	"sync"
)

// Chan of failures to write to destinations
var FailedSocketOuts = make(map[string]chan bool)
var fsoMux sync.Mutex

// WriteQueues for each outgoing socket on the server
var server_write_queues = make(map[string]*WriteQueue)
var swqMux sync.Mutex

// DataIMUX objects to read responses from each outgoing destination socket
var responders = make(map[string]DataIMUX)
var respondersMux sync.Mutex

// Tracks if goroutines have been created for each socket to read from the
// DataIMUXer for its session and write responses down
var loopers = make(map[net.Conn]bool)
var loopersMux sync.Mutex

// Create a new TLB server to accept chunks from anywhere and order them, writing them to corresponding sockets
func ManyToOne(listener net.Listener, dial_destination Redialer) {
	tlb_server := tlb.NewServer(listener, tag_socket, type_store())
	tlb_server.Accept("all", reflect.TypeOf(Chunk{}), func(iface interface{}, context tlb.TLBContext) {
		if chunk, ok := iface.(*Chunk); ok {
			log.WithFields(log.Fields{
				"at":          "ManyToOne",
				"sequence_id": chunk.SequenceID,
				"socket_id":   chunk.SocketID,
				"session_id":  chunk.SessionID,
			}).Debug("received chunk")
			createFailReporterIfNeeded(chunk.SocketID, chunk.SessionID)
			createResponderIMUXIfNeeded(chunk.SessionID)
			writeResponseChunksIfNeeded(context.Socket, chunk.SessionID)
			queue, err := queueForDestinationDialIfNeeded(chunk.SocketID, chunk.SessionID, dial_destination)
			if err == nil {
				queue.Chunks <- chunk
				log.WithFields(log.Fields{
					"at":          "ManyToOne",
					"sequence_id": chunk.SequenceID,
					"socket_id":   chunk.SocketID,
					"session_id":  chunk.SessionID,
				}).Debug("wrote chunk")
			} else {
				log.WithFields(log.Fields{
					"at":          "ManyToOne",
					"error":       err.Error(),
					"sequence_id": chunk.SequenceID,
					"socket_id":   chunk.SocketID,
					"session_id":  chunk.SessionID,
				}).Error("dropped chunk")
				if reporter, ok := FailedSocketOuts[chunk.SocketID]; ok {
					reporter <- true
				} else {
					log.WithFields(log.Fields{
						"at":        "ManyToOne",
						"socket_id": chunk.SocketID,
					}).Error("unable to lookup fail socket out channel")
				}
			}
		}
	})

	log.WithFields(log.Fields{
		"at": "ManyToOne",
	}).Debug("created new ManyToOne")
	err := <-tlb_server.FailedServer
	log.WithFields(log.Fields{
		"error": err.Error(),
	}).Error("TLB server failed for ManyToOne")
}

// Create a chan if needed to pass events about this socket failing
func createFailReporterIfNeeded(socket_id, session_id string) {
	fsoMux.Lock()
	defer fsoMux.Unlock()
	if _, present := FailedSocketOuts[socket_id]; !present {
		FailedSocketOuts[socket_id] = make(chan bool, 0)
		go func(socket_id, session_id string) {
			for {
				<-FailedSocketOuts[socket_id]
				responders[session_id].Chunks <- Chunk{
					SessionID:  session_id,
					SocketID:   socket_id,
					SequenceID: 0,
					Close:      true,
				}
			}
		}(socket_id, session_id)
	}
}

// If it does not exist, create a DataIMUX to read data from
// outgoing destination sockets with a common session
func createResponderIMUXIfNeeded(session_id string) {
	respondersMux.Lock()
	defer respondersMux.Unlock()
	if _, present := responders[session_id]; !present {
		responders[session_id] = NewDataIMUX(session_id)
		log.WithFields(log.Fields{
			"at":         "createResponderIMUXIfNeeded",
			"session_id": session_id,
		}).Debug("created new responder imux for session")
	}
}

// If it is not already happening, ensure that response chunks for a specified
// session_id are written back down this socket.
func writeResponseChunksIfNeeded(socket net.Conn, session_id string) {
	loopersMux.Lock()
	defer loopersMux.Unlock()
	if _, looping := loopers[socket]; !looping {
		log.WithFields(log.Fields{
			"at":         "writeResponseChunksIfNeeded",
			"session_id": session_id,
		}).Debug("creating write back routine for socket")
		go func() {
			writer, err := tlb.NewStreamWriter(socket, type_store(), reflect.TypeOf(Chunk{}))
			if err != nil {
				log.WithFields(log.Fields{
					"at":         "writeResponseChunksIfNeeded",
					"session_id": session_id,
					"error":      err.Error(),
				}).Error("error create return stream writer")
				return
			}
			respondersMux.Lock()
			chunk_stream, ok := responders[session_id]
			if !ok {
				log.WithFields(log.Fields{
					"at":         "writeResponseChunksIfNeeded",
					"session_id": session_id,
				}).Error("error looking up responder imux for session_id")
				return
			}
			respondersMux.Unlock()
			for {
				new_chunk := <-chunk_stream.Chunks
				err := writer.Write(new_chunk)
				if err != nil {
					responders[session_id].Stale <- new_chunk
					log.WithFields(log.Fields{
						"at":         "writeResponseChunksIfNeeded",
						"session_id": session_id,
						"data_len":   len(new_chunk.Data),
						"error":      err.Error(),
					}).Error("error writing a chunk down transport socket")
					break
				} else {
					log.WithFields(log.Fields{
						"at":         "writeResponseChunksIfNeeded",
						"session_id": session_id,
						"data_len":   len(new_chunk.Data),
					}).Debug("wrote a chunk down transport socket")
				}
			}
		}()
		loopers[socket] = true
	}
}

// Get the queue a new chunk should go to, dialing the outgoing destination socket if this is the first time
// a socket ID has been observed.
func queueForDestinationDialIfNeeded(socket_id, session_id string, dial_destination func() (net.Conn, error)) (*WriteQueue, error) {
	swqMux.Lock()
	defer swqMux.Unlock()
	queue, present := server_write_queues[socket_id]
	if !present {
		log.WithFields(log.Fields{
			"at":         "queueForDestinationDialIfNeeded",
			"session_id": session_id,
			"socket_id":  socket_id,
		}).Debug("dialing destination")
		destination, err := dial_destination()
		if err != nil {
			log.WithFields(log.Fields{
				"at":         "queueForDestinationDialIfNeeded",
				"session_id": session_id,
				"socket_id":  socket_id,
				"error":      err.Error(),
			}).Error("error dialing destination")
			return queue, err
		}
		queue = NewWriteQueue(destination)
		server_write_queues[socket_id] = queue
		respondersMux.Lock()
		if imuxer, ok := responders[session_id]; ok {
			go imuxer.ReadFrom(socket_id, destination)
		} else {
			log.WithFields(log.Fields{
				"at":         "queueForDestinationDialIfNeeded",
				"session_id": session_id,
				"socket_id":  socket_id,
			}).Fatal("no responding reader exists, should not be possible")
		}
		respondersMux.Unlock()
	}
	return queue, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/gorilla/mux"
	. "github.com/mickael-kerjean/filestash/server/common"
	. "github.com/mickael-kerjean/filestash/server/ctrl"
	. "github.com/mickael-kerjean/filestash/server/middleware"
	_ "github.com/mickael-kerjean/filestash/server/plugin"
	"net/http"
    "net/http/pprof"
	"os"
	"runtime"
	"runtime/debug"
	"strconv"
)

func main() {
	app := App{}
	Init(&app)
}

func Init(a *App) {
	var middlewares []Middleware
	r := mux.NewRouter()

	// API for Session
	session := r.PathPrefix("/api/session").Subrouter()
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, SessionStart }
	session.HandleFunc("",                NewMiddlewareChain(SessionGet,          middlewares, *a)).Methods("GET")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, BodyParser }
	session.HandleFunc("",                NewMiddlewareChain(SessionAuthenticate, middlewares, *a)).Methods("POST")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, SessionTry }
	session.HandleFunc("",                NewMiddlewareChain(SessionLogout,       middlewares, *a)).Methods("DELETE")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax }
	session.HandleFunc("/auth/{service}", NewMiddlewareChain(SessionOAuthBackend, middlewares, *a)).Methods("GET")

	// API for admin
	middlewares = []Middleware{ ApiHeaders, SecureAjax }
	admin := r.PathPrefix("/admin/api").Subrouter()
	admin.HandleFunc("/session", NewMiddlewareChain(AdminSessionGet,            middlewares, *a)).Methods("GET")
	admin.HandleFunc("/session", NewMiddlewareChain(AdminSessionAuthenticate,   middlewares, *a)).Methods("POST")
	middlewares = []Middleware{ ApiHeaders, AdminOnly, SecureAjax }
	admin.HandleFunc("/config",  NewMiddlewareChain(PrivateConfigHandler,       middlewares, *a)).Methods("GET")
	admin.HandleFunc("/config",  NewMiddlewareChain(PrivateConfigUpdateHandler, middlewares, *a)).Methods("POST")
	middlewares = []Middleware{ IndexHeaders }
	admin.HandleFunc("/log",                        NewMiddlewareChain(FetchLogHandler,          middlewares, *a)).Methods("GET")

	// API for File management
	files := r.PathPrefix("/api/files").Subrouter()
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SessionStart, LoggedInOnly }
	files.HandleFunc("/cat",    NewMiddlewareChain(FileCat,    middlewares, *a)).Methods("GET", "HEAD")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, SessionStart, LoggedInOnly }
	files.HandleFunc("/cat",    NewMiddlewareChain(FileAccess, middlewares, *a)).Methods("OPTIONS")
	files.HandleFunc("/cat",    NewMiddlewareChain(FileSave,   middlewares, *a)).Methods("POST")
	files.HandleFunc("/ls",     NewMiddlewareChain(FileLs,     middlewares, *a)).Methods("GET")
	files.HandleFunc("/mv",     NewMiddlewareChain(FileMv,     middlewares, *a)).Methods("GET")
	files.HandleFunc("/rm",     NewMiddlewareChain(FileRm,     middlewares, *a)).Methods("GET")
	files.HandleFunc("/mkdir",  NewMiddlewareChain(FileMkdir,  middlewares, *a)).Methods("GET")
	files.HandleFunc("/touch",  NewMiddlewareChain(FileTouch,  middlewares, *a)).Methods("GET")
	middlewares = []Middleware{ ApiHeaders, SessionStart, LoggedInOnly }
	files.HandleFunc("/search",  NewMiddlewareChain(FileSearch,  middlewares, *a)).Methods("GET")

	// API for exporter
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, RedirectSharedLoginIfNeeded, SessionStart, LoggedInOnly }
	r.PathPrefix("/api/export/{share}/{mtype0}/{mtype1}").Handler(NewMiddlewareChain(FileExport,  middlewares, *a))

	// API for Shared link
	share := r.PathPrefix("/api/share").Subrouter()
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, SessionStart, LoggedInOnly }
	share.HandleFunc("",               NewMiddlewareChain(ShareList,        middlewares, *a)).Methods("GET")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, BodyParser }
	share.HandleFunc("/{share}/proof", NewMiddlewareChain(ShareVerifyProof, middlewares, *a)).Methods("POST")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, CanManageShare }
	share.HandleFunc("/{share}",       NewMiddlewareChain(ShareDelete,      middlewares, *a)).Methods("DELETE")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, BodyParser, CanManageShare }
	share.HandleFunc("/{share}",       NewMiddlewareChain(ShareUpsert,      middlewares, *a)).Methods("POST")

	// Webdav server / Shared Link
	middlewares = []Middleware{ IndexHeaders, SecureHeaders }
	r.HandleFunc("/s/{share}",         NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a)).Methods("GET")
	middlewares = []Middleware{ WebdavBlacklist, SessionStart }
	r.PathPrefix("/s/{share}").Handler(NewMiddlewareChain(WebdavHandler,            middlewares, *a))

	// Application Resources
	middlewares = []Middleware{ ApiHeaders }
	r.HandleFunc("/api/config",  NewMiddlewareChain(PublicConfigHandler,                             middlewares, *a)).Methods("GET")
	r.HandleFunc("/api/backend", NewMiddlewareChain(AdminBackend,                                    middlewares, *a)).Methods("GET")
	middlewares = []Middleware{ StaticHeaders }
	r.PathPrefix("/assets").Handler(http.HandlerFunc(NewMiddlewareChain(StaticHandler(FILE_ASSETS),  middlewares, *a))).Methods("GET")
	r.HandleFunc("/favicon.ico", NewMiddlewareChain(StaticHandler(FILE_ASSETS + "/assets/logo/"),    middlewares, *a)).Methods("GET")
	r.HandleFunc("/sw_cache.js", NewMiddlewareChain(StaticHandler(FILE_ASSETS + "/assets/worker/"),  middlewares, *a)).Methods("GET")

	// Other endpoints
	middlewares = []Middleware{ ApiHeaders }
	r.HandleFunc("/report",      NewMiddlewareChain(ReportHandler,                                   middlewares, *a)).Methods("POST")
	middlewares = []Middleware{ IndexHeaders }
	r.HandleFunc("/about",       NewMiddlewareChain(AboutHandler,                                    middlewares, *a)).Methods("GET")
	r.HandleFunc("/robots.txt", func(res http.ResponseWriter, req *http.Request) {
		res.Write([]byte(""))
	})

	if os.Getenv("DEBUG") == "true" {
		initDebugRoutes(r)
	}
	initPluginsRoutes(r, a)

	r.PathPrefix("/admin").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")
	r.PathPrefix("/"     ).Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")

	// Routes are served via plugins to avoid getting stuck with plain HTTP. The idea is to
	// support many more protocols in the future: HTTPS, HTTP2, TOR or whatever that sounds
	// fancy I don't know much when this got written: IPFS, solid, ...
	Log.Info("Filestash %s starting", APP_VERSION)
	for _, obj := range Hooks.Get.Starter() {
		go obj(r)
	}
	if len(Hooks.Get.Starter()) == 0 {
		Log.Warning("No starter plugin available")
		return
	}
	select { }
}

func initDebugRoutes(r *mux.Router) {
	r.HandleFunc("/debug/pprof/", pprof.Index)
	r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	r.HandleFunc("/debug/pprof/profile", pprof.Profile)
	r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	r.HandleFunc("/debug/pprof/trace", pprof.Trace)
	r.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
	r.Handle("/debug/pprof/heap", pprof.Handler("heap"))
	r.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
	r.Handle("/debug/pprof/block", pprof.Handler("block"))
	r.Handle("/debug/pprof/allocs", pprof.Handler("allocs"))
	r.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))
	r.HandleFunc("/debug/free", func(w http.ResponseWriter, r *http.Request) {
		debug.FreeOSMemory()
		w.Write([]byte("DONE"))
	})
	bToMb := func(b uint64) string {
		return strconv.Itoa(int(b / 1024 / 1024))
	}
	r.HandleFunc("/debug/memory", func(w http.ResponseWriter, r *http.Request) {
		var m runtime.MemStats
		runtime.ReadMemStats(&m)
		w.Write([]byte("
"))
		w.Write([]byte("Alloc      = " + bToMb(m.Alloc) + "MiB
"))
		w.Write([]byte("TotalAlloc = " + bToMb(m.TotalAlloc) + "MiB
"))
		w.Write([]byte("Sys        = " + bToMb(m.Sys) + "MiB
"))
		w.Write([]byte("NumGC      = " + strconv.Itoa(int(m.NumGC))))
		w.Write([]byte("
"))
	})
}

func initPluginsRoutes(r *mux.Router, a *App) {
	// Endpoints hanle by plugins
	for _, obj := range Hooks.Get.HttpEndpoint() {
		obj(r, a)
	}
	// frontoffice overrides: it is the mean by which plugin can interact with the frontoffice
	for _, obj := range Hooks.Get.FrontendOverrides() {
		r.HandleFunc(obj, func(res http.ResponseWriter, req *http.Request) {
			res.WriteHeader(http.StatusOK)
			res.Write([]byte(fmt.Sprintf("/* Default '%s' */", obj)))
		})
	}
	// map which file can be open with what application
	r.HandleFunc("/overrides/xdg-open.js", func(res http.ResponseWriter, req *http.Request) {
		res.Write([]byte(`window.overrides["xdg-open"] = function(mime){`))
		openers := Hooks.Get.XDGOpen()
		for i:=0; i<len(openers); i++ {
			res.Write([]byte(openers[i]))
		}
		res.Write([]byte(`return null;}`))
	})
}
<commit_msg>fix (mimetype): missing mimetype on the xdg-open.js<commit_after>package main

import (
	"fmt"
	"github.com/gorilla/mux"
	. "github.com/mickael-kerjean/filestash/server/common"
	. "github.com/mickael-kerjean/filestash/server/ctrl"
	. "github.com/mickael-kerjean/filestash/server/middleware"
	_ "github.com/mickael-kerjean/filestash/server/plugin"
	"net/http"
    "net/http/pprof"
	"os"
	"runtime"
	"runtime/debug"
	"strconv"
)

func main() {
	app := App{}
	Init(&app)
}

func Init(a *App) {
	var middlewares []Middleware
	r := mux.NewRouter()

	// API for Session
	session := r.PathPrefix("/api/session").Subrouter()
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, SessionStart }
	session.HandleFunc("",                NewMiddlewareChain(SessionGet,          middlewares, *a)).Methods("GET")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, BodyParser }
	session.HandleFunc("",                NewMiddlewareChain(SessionAuthenticate, middlewares, *a)).Methods("POST")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, SessionTry }
	session.HandleFunc("",                NewMiddlewareChain(SessionLogout,       middlewares, *a)).Methods("DELETE")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax }
	session.HandleFunc("/auth/{service}", NewMiddlewareChain(SessionOAuthBackend, middlewares, *a)).Methods("GET")

	// API for admin
	middlewares = []Middleware{ ApiHeaders, SecureAjax }
	admin := r.PathPrefix("/admin/api").Subrouter()
	admin.HandleFunc("/session", NewMiddlewareChain(AdminSessionGet,            middlewares, *a)).Methods("GET")
	admin.HandleFunc("/session", NewMiddlewareChain(AdminSessionAuthenticate,   middlewares, *a)).Methods("POST")
	middlewares = []Middleware{ ApiHeaders, AdminOnly, SecureAjax }
	admin.HandleFunc("/config",  NewMiddlewareChain(PrivateConfigHandler,       middlewares, *a)).Methods("GET")
	admin.HandleFunc("/config",  NewMiddlewareChain(PrivateConfigUpdateHandler, middlewares, *a)).Methods("POST")
	middlewares = []Middleware{ IndexHeaders }
	admin.HandleFunc("/log",                        NewMiddlewareChain(FetchLogHandler,          middlewares, *a)).Methods("GET")

	// API for File management
	files := r.PathPrefix("/api/files").Subrouter()
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SessionStart, LoggedInOnly }
	files.HandleFunc("/cat",    NewMiddlewareChain(FileCat,    middlewares, *a)).Methods("GET", "HEAD")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, SessionStart, LoggedInOnly }
	files.HandleFunc("/cat",    NewMiddlewareChain(FileAccess, middlewares, *a)).Methods("OPTIONS")
	files.HandleFunc("/cat",    NewMiddlewareChain(FileSave,   middlewares, *a)).Methods("POST")
	files.HandleFunc("/ls",     NewMiddlewareChain(FileLs,     middlewares, *a)).Methods("GET")
	files.HandleFunc("/mv",     NewMiddlewareChain(FileMv,     middlewares, *a)).Methods("GET")
	files.HandleFunc("/rm",     NewMiddlewareChain(FileRm,     middlewares, *a)).Methods("GET")
	files.HandleFunc("/mkdir",  NewMiddlewareChain(FileMkdir,  middlewares, *a)).Methods("GET")
	files.HandleFunc("/touch",  NewMiddlewareChain(FileTouch,  middlewares, *a)).Methods("GET")
	middlewares = []Middleware{ ApiHeaders, SessionStart, LoggedInOnly }
	files.HandleFunc("/search",  NewMiddlewareChain(FileSearch,  middlewares, *a)).Methods("GET")

	// API for exporter
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, RedirectSharedLoginIfNeeded, SessionStart, LoggedInOnly }
	r.PathPrefix("/api/export/{share}/{mtype0}/{mtype1}").Handler(NewMiddlewareChain(FileExport,  middlewares, *a))

	// API for Shared link
	share := r.PathPrefix("/api/share").Subrouter()
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, SessionStart, LoggedInOnly }
	share.HandleFunc("",               NewMiddlewareChain(ShareList,        middlewares, *a)).Methods("GET")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, BodyParser }
	share.HandleFunc("/{share}/proof", NewMiddlewareChain(ShareVerifyProof, middlewares, *a)).Methods("POST")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, CanManageShare }
	share.HandleFunc("/{share}",       NewMiddlewareChain(ShareDelete,      middlewares, *a)).Methods("DELETE")
	middlewares = []Middleware{ ApiHeaders, SecureHeaders, SecureAjax, BodyParser, CanManageShare }
	share.HandleFunc("/{share}",       NewMiddlewareChain(ShareUpsert,      middlewares, *a)).Methods("POST")

	// Webdav server / Shared Link
	middlewares = []Middleware{ IndexHeaders, SecureHeaders }
	r.HandleFunc("/s/{share}",         NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a)).Methods("GET")
	middlewares = []Middleware{ WebdavBlacklist, SessionStart }
	r.PathPrefix("/s/{share}").Handler(NewMiddlewareChain(WebdavHandler,            middlewares, *a))

	// Application Resources
	middlewares = []Middleware{ ApiHeaders }
	r.HandleFunc("/api/config",  NewMiddlewareChain(PublicConfigHandler,                             middlewares, *a)).Methods("GET")
	r.HandleFunc("/api/backend", NewMiddlewareChain(AdminBackend,                                    middlewares, *a)).Methods("GET")
	middlewares = []Middleware{ StaticHeaders }
	r.PathPrefix("/assets").Handler(http.HandlerFunc(NewMiddlewareChain(StaticHandler(FILE_ASSETS),  middlewares, *a))).Methods("GET")
	r.HandleFunc("/favicon.ico", NewMiddlewareChain(StaticHandler(FILE_ASSETS + "/assets/logo/"),    middlewares, *a)).Methods("GET")
	r.HandleFunc("/sw_cache.js", NewMiddlewareChain(StaticHandler(FILE_ASSETS + "/assets/worker/"),  middlewares, *a)).Methods("GET")

	// Other endpoints
	middlewares = []Middleware{ ApiHeaders }
	r.HandleFunc("/report",      NewMiddlewareChain(ReportHandler,                                   middlewares, *a)).Methods("POST")
	middlewares = []Middleware{ IndexHeaders }
	r.HandleFunc("/about",       NewMiddlewareChain(AboutHandler,                                    middlewares, *a)).Methods("GET")
	r.HandleFunc("/robots.txt", func(res http.ResponseWriter, req *http.Request) {
		res.Write([]byte(""))
	})

	if os.Getenv("DEBUG") == "true" {
		initDebugRoutes(r)
	}
	initPluginsRoutes(r, a)

	r.PathPrefix("/admin").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")
	r.PathPrefix("/"     ).Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")

	// Routes are served via plugins to avoid getting stuck with plain HTTP. The idea is to
	// support many more protocols in the future: HTTPS, HTTP2, TOR or whatever that sounds
	// fancy I don't know much when this got written: IPFS, solid, ...
	Log.Info("Filestash %s starting", APP_VERSION)
	for _, obj := range Hooks.Get.Starter() {
		go obj(r)
	}
	if len(Hooks.Get.Starter()) == 0 {
		Log.Warning("No starter plugin available")
		return
	}
	select { }
}

func initDebugRoutes(r *mux.Router) {
	r.HandleFunc("/debug/pprof/", pprof.Index)
	r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
	r.HandleFunc("/debug/pprof/profile", pprof.Profile)
	r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	r.HandleFunc("/debug/pprof/trace", pprof.Trace)
	r.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
	r.Handle("/debug/pprof/heap", pprof.Handler("heap"))
	r.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
	r.Handle("/debug/pprof/block", pprof.Handler("block"))
	r.Handle("/debug/pprof/allocs", pprof.Handler("allocs"))
	r.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))
	r.HandleFunc("/debug/free", func(w http.ResponseWriter, r *http.Request) {
		debug.FreeOSMemory()
		w.Write([]byte("DONE"))
	})
	bToMb := func(b uint64) string {
		return strconv.Itoa(int(b / 1024 / 1024))
	}
	r.HandleFunc("/debug/memory", func(w http.ResponseWriter, r *http.Request) {
		var m runtime.MemStats
		runtime.ReadMemStats(&m)
		w.Write([]byte("
"))
		w.Write([]byte("Alloc      = " + bToMb(m.Alloc) + "MiB
"))
		w.Write([]byte("TotalAlloc = " + bToMb(m.TotalAlloc) + "MiB
"))
		w.Write([]byte("Sys        = " + bToMb(m.Sys) + "MiB
"))
		w.Write([]byte("NumGC      = " + strconv.Itoa(int(m.NumGC))))
		w.Write([]byte("
"))
	})
}

func initPluginsRoutes(r *mux.Router, a *App) {
	// Endpoints hanle by plugins
	for _, obj := range Hooks.Get.HttpEndpoint() {
		obj(r, a)
	}
	// frontoffice overrides: it is the mean by which plugin can interact with the frontoffice
	for _, obj := range Hooks.Get.FrontendOverrides() {
		r.HandleFunc(obj, func(res http.ResponseWriter, req *http.Request) {
			res.WriteHeader(http.StatusOK)
			res.Write([]byte(fmt.Sprintf("/* Default '%s' */", obj)))
		})
	}
	// map which file can be open with what application
	r.HandleFunc("/overrides/xdg-open.js", func(res http.ResponseWriter, req *http.Request) {
		res.Header().Set("Content-Type", GetMimeType("xdg-open.js"))
		res.Write([]byte(`window.overrides["xdg-open"] = function(mime){`))
		openers := Hooks.Get.XDGOpen()
		for i:=0; i<len(openers); i++ {
			res.Write([]byte(openers[i]))
		}
		res.Write([]byte(`return null;}`))
	})
}
<|endoftext|>


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

package telemetry

import (
	"context"
	"os"
	"runtime"
	"time"

	"github.com/prometheus/procfs"
	"go.opencensus.io/stats"
	"go.opencensus.io/stats/view"
)

// ProcessMetricsViews is a struct that contains views related to process metrics (cpu, mem, etc)
type ProcessMetricsViews struct {
	ballastSizeBytes uint64
	views            []*view.View
	done             chan struct{}
}

var mRuntimeAllocMem = stats.Int64("oc.io/process/memory_alloc", "Number of bytes currently allocated in use", "By")
var viewAllocMem = &view.View{
	Name:        mRuntimeAllocMem.Name(),
	Description: mRuntimeAllocMem.Description(),
	Measure:     mRuntimeAllocMem,
	Aggregation: view.LastValue(),
	TagKeys:     nil,
}

var mRuntimeTotalAllocMem = stats.Int64("oc.io/process/total_memory_alloc", "Number of allocations in total", "By")
var viewTotalAllocMem = &view.View{
	Name:        mRuntimeTotalAllocMem.Name(),
	Description: mRuntimeTotalAllocMem.Description(),
	Measure:     mRuntimeTotalAllocMem,
	Aggregation: view.LastValue(),
	TagKeys:     nil,
}

var mRuntimeSysMem = stats.Int64("oc.io/process/sys_memory_alloc", "Number of bytes given to the process to use in total", "By")
var viewSysMem = &view.View{
	Name:        mRuntimeSysMem.Name(),
	Description: mRuntimeSysMem.Description(),
	Measure:     mRuntimeSysMem,
	Aggregation: view.LastValue(),
	TagKeys:     nil,
}

var mCPUSeconds = stats.Int64("oc.io/process/cpu_seconds", "CPU seconds for this process", "1")
var viewCPUSeconds = &view.View{
	Name:        mCPUSeconds.Name(),
	Description: mCPUSeconds.Description(),
	Measure:     mCPUSeconds,
	Aggregation: view.LastValue(),
	TagKeys:     nil,
}

// NewProcessMetricsViews creates a new set of ProcessMetrics (mem, cpu) that can be used to measure
// basic information about this process.
func NewProcessMetricsViews(ballastSizeBytes uint64) *ProcessMetricsViews {
	return &ProcessMetricsViews{
		ballastSizeBytes: ballastSizeBytes,
		views:            []*view.View{viewAllocMem, viewTotalAllocMem, viewSysMem, viewCPUSeconds},
		done:             make(chan struct{}),
	}
}

// StartCollection starts a ticker'd goroutine that will update the PMV measurements every 5 seconds
func (pmv *ProcessMetricsViews) StartCollection() {
	ticker := time.NewTicker(5 * time.Second)
	go func() {
		for {
			select {
			case <-ticker.C:
				pmv.updateViews()
			case <-pmv.done:
				return
			}
		}
	}()
}

// Views returns the views internal to the PMV
func (pmv *ProcessMetricsViews) Views() []*view.View {
	return pmv.views
}

// StopCollection stops the collection of the process metric information
func (pmv *ProcessMetricsViews) StopCollection() {
	close(pmv.done)
}

func (pmv *ProcessMetricsViews) updateViews() {
	ms := &runtime.MemStats{}
	pmv.readMemStats(ms)
	stats.Record(context.Background(), mRuntimeAllocMem.M(int64(ms.Alloc)))
	stats.Record(context.Background(), mRuntimeTotalAllocMem.M(int64(ms.TotalAlloc)))
	stats.Record(context.Background(), mRuntimeSysMem.M(int64(ms.Sys)))

	pid := os.Getpid()
	proc, err := procfs.NewProc(pid)
	if err == nil {
		if procStat, err := proc.Stat(); err == nil {
			stats.Record(context.Background(), mCPUSeconds.M(int64(procStat.CPUTime())))
		}
	}
}

func (pmv *ProcessMetricsViews) readMemStats(ms *runtime.MemStats) {
	runtime.ReadMemStats(ms)
	ms.Alloc -= pmv.ballastSizeBytes
	ms.HeapAlloc -= pmv.ballastSizeBytes
	ms.HeapSys -= pmv.ballastSizeBytes
	ms.HeapInuse -= pmv.ballastSizeBytes
}
<commit_msg>Rename Processor Metrics (#551)<commit_after>// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package telemetry

import (
	"context"
	"os"
	"runtime"
	"time"

	"github.com/prometheus/procfs"
	"go.opencensus.io/stats"
	"go.opencensus.io/stats/view"
)

// ProcessMetricsViews is a struct that contains views related to process metrics (cpu, mem, etc)
type ProcessMetricsViews struct {
	ballastSizeBytes uint64
	views            []*view.View
	done             chan struct{}
}

var mRuntimeAllocMem = stats.Int64(
	"process/runtime/heap_alloc_bytes",
	"Bytes of allocated heap objects (see 'go doc runtime.MemStats.HeapAlloc')",
	stats.UnitBytes)
var viewAllocMem = &view.View{
	Name:        mRuntimeAllocMem.Name(),
	Description: mRuntimeAllocMem.Description(),
	Measure:     mRuntimeAllocMem,
	Aggregation: view.LastValue(),
	TagKeys:     nil,
}

var mRuntimeTotalAllocMem = stats.Int64(
	"process/runtime/total_alloc_bytes",
	"Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc')",
	stats.UnitBytes)
var viewTotalAllocMem = &view.View{
	Name:        mRuntimeTotalAllocMem.Name(),
	Description: mRuntimeTotalAllocMem.Description(),
	Measure:     mRuntimeTotalAllocMem,
	Aggregation: view.LastValue(),
	TagKeys:     nil,
}

var mRuntimeSysMem = stats.Int64(
	"process/runtime/total_sys_memory_bytes",
	"Total bytes of memory obtained from the OS (see 'go doc runtime.MemStats.Sys')",
	stats.UnitBytes)
var viewSysMem = &view.View{
	Name:        mRuntimeSysMem.Name(),
	Description: mRuntimeSysMem.Description(),
	Measure:     mRuntimeSysMem,
	Aggregation: view.LastValue(),
	TagKeys:     nil,
}

var mCPUSeconds = stats.Int64(
	"process/cpu_seconds",
	"Total CPU user and system time in seconds",
	stats.UnitDimensionless)
var viewCPUSeconds = &view.View{
	Name:        mCPUSeconds.Name(),
	Description: mCPUSeconds.Description(),
	Measure:     mCPUSeconds,
	Aggregation: view.LastValue(),
	TagKeys:     nil,
}

// NewProcessMetricsViews creates a new set of ProcessMetrics (mem, cpu) that can be used to measure
// basic information about this process.
func NewProcessMetricsViews(ballastSizeBytes uint64) *ProcessMetricsViews {
	return &ProcessMetricsViews{
		ballastSizeBytes: ballastSizeBytes,
		views:            []*view.View{viewAllocMem, viewTotalAllocMem, viewSysMem, viewCPUSeconds},
		done:             make(chan struct{}),
	}
}

// StartCollection starts a ticker'd goroutine that will update the PMV measurements every 5 seconds
func (pmv *ProcessMetricsViews) StartCollection() {
	ticker := time.NewTicker(5 * time.Second)
	go func() {
		for {
			select {
			case <-ticker.C:
				pmv.updateViews()
			case <-pmv.done:
				return
			}
		}
	}()
}

// Views returns the views internal to the PMV
func (pmv *ProcessMetricsViews) Views() []*view.View {
	return pmv.views
}

// StopCollection stops the collection of the process metric information
func (pmv *ProcessMetricsViews) StopCollection() {
	close(pmv.done)
}

func (pmv *ProcessMetricsViews) updateViews() {
	ms := &runtime.MemStats{}
	pmv.readMemStats(ms)
	stats.Record(context.Background(), mRuntimeAllocMem.M(int64(ms.Alloc)))
	stats.Record(context.Background(), mRuntimeTotalAllocMem.M(int64(ms.TotalAlloc)))
	stats.Record(context.Background(), mRuntimeSysMem.M(int64(ms.Sys)))

	pid := os.Getpid()
	proc, err := procfs.NewProc(pid)
	if err == nil {
		if procStat, err := proc.Stat(); err == nil {
			stats.Record(context.Background(), mCPUSeconds.M(int64(procStat.CPUTime())))
		}
	}
}

func (pmv *ProcessMetricsViews) readMemStats(ms *runtime.MemStats) {
	runtime.ReadMemStats(ms)
	ms.Alloc -= pmv.ballastSizeBytes
	ms.HeapAlloc -= pmv.ballastSizeBytes
	ms.HeapSys -= pmv.ballastSizeBytes
	ms.HeapInuse -= pmv.ballastSizeBytes
}
<|endoftext|>