<commit_before>package gmgmap

import (
	"math/rand"
)

type street struct {
	r          rect
	horizontal bool
	level      int
}

func (s street) dAlong() vec2 {
	if s.horizontal {
		return vec2{1, 0}
	}
	return vec2{0, 1}
}

func (s street) dAcross() vec2 {
	if s.horizontal {
		return vec2{0, 1}
	}
	return vec2{1, 0}
}

// NewBSPInterior - Create new BSP interior map
// Implementation of https://gamedev.stackexchange.com/questions/47917/procedural-house-with-rooms-generator/48216#48216
func NewBSPInterior(width, height, splits, minRoomSize, corridorWidth int) *Map {
	corridorLevelDiffBlock := 1
	m := NewMap(width, height)

	// Split the map for a number of iterations, choosing alternating axis and random location
	var areas []bspRoom
	var streets []street
	hcount := rand.Intn(2)
	areas = append(areas, bspRoomRoot(width, height))
	for i := 0; i < len(areas); i++ {
		if areas[i].level == splits {
			break
		}
		var r1, r2 bspRoom
		var err error = nil
		// Alternate splitting direction per level
		horizontal := ((hcount + areas[i].level) % 2) == 1
		if horizontal {
			r1, r2, err = bspSplitHorizontal(&areas[i], i, minRoomSize+corridorWidth/2)
		} else {
			r1, r2, err = bspSplitVertical(&areas[i], i, minRoomSize+corridorWidth/2)
		}
		if err == nil {
			// Resize rooms to allow space for street
			for j := 0; j < corridorWidth; j++ {
				if horizontal {
					if j%2 == 0 {
						r1.r.w--
					} else {
						r2.r.x++
						r2.r.w--
					}
				} else {
					if j%2 == 0 {
						r1.r.h--
					} else {
						r2.r.y++
						r2.r.h--
					}
				}
			}
			areas[i].child1 = len(areas)
			areas = append(areas, r1)
			areas[i].child2 = len(areas)
			areas = append(areas, r2)
			var s street
			if horizontal {
				s.r = rect{r1.r.x + r1.r.w, r1.r.y, corridorWidth, r1.r.h}
			} else {
				s.r = rect{r1.r.x, r1.r.y + r1.r.h, r1.r.w, corridorWidth}
			}
			s.level = r1.level
			s.horizontal = !horizontal
			streets = append(streets, s)
		}
	}

	g := m.Layer("Ground")
	s := m.Layer("Structures")
	// Turn the leaves into rooms
	for i := 0; i < len(areas); i++ {
		// Only place rooms in leaf nodes
		if areas[i].child1 >= 0 || areas[i].child2 >= 0 {
			continue
		}

		// Try to split into more rooms, length-wise
		var r1, r2 bspRoom
		var err error = nil
		if !areas[i].horizontal {
			r1, r2, err = bspSplitHorizontal(&areas[i], i, minRoomSize)
		} else {
			r1, r2, err = bspSplitVertical(&areas[i], i, minRoomSize)
		}
		if err == nil {
			// Resize rooms so they share a splitting wall
			if r1.horizontal {
				r1.r.w++
			} else {
				r1.r.h++
			}
			areas[i].child1 = len(areas)
			areas = append(areas, r1)
			areas[i].child2 = len(areas)
			areas = append(areas, r2)
			continue
		}

		var r rect
		r.w = areas[i].r.w
		r.x = areas[i].r.x
		r.h = areas[i].r.h
		r.y = areas[i].r.y
		g.rectangleFilled(rect{r.x + 1, r.y + 1, r.w - 2, r.h - 2}, room)
		s.rectangleUnfilled(r, wall2)
		// Add doors leading to hallways
		for j := 0; j < 4; j++ {
			doorPos := vec2{areas[i].r.x + areas[i].r.w/2, areas[i].r.y + areas[i].r.h/2}
			var outsideDoor vec2
			if j == 0 {
				// top
				doorPos.y = areas[i].r.y
				outsideDoor = vec2{doorPos.x, doorPos.y - 1}
			} else if j == 1 {
				// right
				doorPos.x = areas[i].r.x + areas[i].r.w - 1
				outsideDoor = vec2{doorPos.x + 1, doorPos.y}
			} else if j == 2 {
				// bottom
				doorPos.y = areas[i].r.y + areas[i].r.h - 1
				outsideDoor = vec2{doorPos.x, doorPos.y + 1}
			} else {
				// left
				doorPos.x = areas[i].r.x
				outsideDoor = vec2{doorPos.x - 1, doorPos.y}
			}
			for i := range streets {
				if streets[i].r.isIn(outsideDoor.x, outsideDoor.y) {
					g.setTile(doorPos.x, doorPos.y, room)
					s.setTile(doorPos.x, doorPos.y, door)
					break
				}
			}
		}
	}

	// Fill streets
	for i := range streets {
		g.rectangleFilled(streets[i].r, room2)
		// Check ends of street - if next to much older street, block off with wall
		end1 := vec2{streets[i].r.x, streets[i].r.y}
		end2 := vec2{streets[i].r.x + streets[i].r.w - 1, streets[i].r.y + streets[i].r.h - 1}
		capStreet(g, s, streets, streets[i], end1, streets[i].dAcross(), streets[i].dAlong(), corridorWidth, corridorLevelDiffBlock)
		capStreet(g, s, streets, streets[i], end2, vec2{-streets[i].dAcross().x, -streets[i].dAcross().y}, vec2{-streets[i].dAlong().x, -streets[i].dAlong().y}, corridorWidth, corridorLevelDiffBlock)
	}

	// Place stairs going up at end of first (main) street
	s.setTile(streets[0].r.x+streets[0].dAlong().x, streets[0].r.y+streets[0].dAlong().y, stairsUp)
	// Place stairs going down in last room
	lastRoomRect := areas[len(areas)-1].r
	s.setTile(lastRoomRect.x+lastRoomRect.w/2, lastRoomRect.y+lastRoomRect.h/2, stairsDown)

	return m
}

func capStreet(g, s *Layer, streets []street, st street, end, dAcross, dAlong vec2, corridorWidth, corridorLevelDiffBlock int) {
	// Check ends of street - if outside map, or next to much older street, block off with wall
	outside := vec2{end.x - dAlong.x, end.y - dAlong.y}
	doCap := false
	if !g.isIn(outside.x, outside.y) {
		doCap = true
	} else {
		for i := range streets {
			if streets[i].r.isIn(outside.x, outside.y) && st.level-streets[i].level > corridorLevelDiffBlock {
				doCap = true
				break
			}
		}
	}
	if doCap {
		for i := 0; i < corridorWidth; i++ {
			g.setTile(end.x+dAcross.x*i, end.y+dAcross.y*i, nothing)
			s.setTile(end.x+dAcross.x*i, end.y+dAcross.y*i, wall2)
		}
	}
}
<commit_msg>Connect interior rooms<commit_after>package gmgmap

import (
	"math/rand"
)

type street struct {
	r          rect
	horizontal bool
	level      int
}

func (s street) dAlong() vec2 {
	if s.horizontal {
		return vec2{1, 0}
	}
	return vec2{0, 1}
}

func (s street) dAcross() vec2 {
	if s.horizontal {
		return vec2{0, 1}
	}
	return vec2{1, 0}
}

// NewBSPInterior - Create new BSP interior map
// Implementation of https://gamedev.stackexchange.com/questions/47917/procedural-house-with-rooms-generator/48216#48216
func NewBSPInterior(width, height, splits, minRoomSize, corridorWidth int) *Map {
	corridorLevelDiffBlock := 1
	m := NewMap(width, height)

	// Split the map for a number of iterations, choosing alternating axis and random location
	var areas []bspRoom
	var streets []street
	hcount := rand.Intn(2)
	areas = append(areas, bspRoomRoot(width, height))
	for i := 0; i < len(areas); i++ {
		if areas[i].level == splits {
			break
		}
		var r1, r2 bspRoom
		var err error = nil
		// Alternate splitting direction per level
		horizontal := ((hcount + areas[i].level) % 2) == 1
		if horizontal {
			r1, r2, err = bspSplitHorizontal(&areas[i], i, minRoomSize+corridorWidth/2)
		} else {
			r1, r2, err = bspSplitVertical(&areas[i], i, minRoomSize+corridorWidth/2)
		}
		if err == nil {
			// Resize rooms to allow space for street
			for j := 0; j < corridorWidth; j++ {
				if horizontal {
					if j%2 == 0 {
						r1.r.w--
					} else {
						r2.r.x++
						r2.r.w--
					}
				} else {
					if j%2 == 0 {
						r1.r.h--
					} else {
						r2.r.y++
						r2.r.h--
					}
				}
			}
			areas[i].child1 = len(areas)
			areas = append(areas, r1)
			areas[i].child2 = len(areas)
			areas = append(areas, r2)
			var s street
			if horizontal {
				s.r = rect{r1.r.x + r1.r.w, r1.r.y, corridorWidth, r1.r.h}
			} else {
				s.r = rect{r1.r.x, r1.r.y + r1.r.h, r1.r.w, corridorWidth}
			}
			s.level = r1.level
			s.horizontal = !horizontal
			streets = append(streets, s)
		}
	}

	g := m.Layer("Ground")
	s := m.Layer("Structures")
	for i := 0; i < len(areas); i++ {
		// Try to split leaf rooms into more rooms, length-wise
		if areas[i].child1 >= 0 || areas[i].child2 >= 0 {
			continue
		}

		var r1, r2 bspRoom
		var err error = nil
		if !areas[i].horizontal {
			r1, r2, err = bspSplitHorizontal(&areas[i], i, minRoomSize)
		} else {
			r1, r2, err = bspSplitVertical(&areas[i], i, minRoomSize)
		}
		if err == nil {
			// Resize rooms so they share a splitting wall
			if r1.horizontal {
				r1.r.w++
			} else {
				r1.r.h++
			}
			areas[i].child1 = len(areas)
			areas = append(areas, r1)
			areas[i].child2 = len(areas)
			areas = append(areas, r2)
		}
	}
	// Discard non-leaf rooms
	for i := 0; i < len(areas); i++ {
		if areas[i].child1 >= 0 || areas[i].child2 >= 0 {
			areas[i] = areas[len(areas)-1]
			areas = areas[0 : len(areas)-1]
			i--
		}
	}
	for i := range areas {
		// Fill rooms
		var r rect
		r.w = areas[i].r.w
		r.x = areas[i].r.x
		r.h = areas[i].r.h
		r.y = areas[i].r.y
		g.rectangleFilled(rect{r.x + 1, r.y + 1, r.w - 2, r.h - 2}, room)
		s.rectangleUnfilled(r, wall2)

		// Add doors leading to hallways
		// Reuse level attribute for distance from hallway
		areas[i].level = -1
		for j := 0; j < 4; j++ {
			doorPos := vec2{areas[i].r.x + areas[i].r.w/2, areas[i].r.y + areas[i].r.h/2}
			var outsideDoor vec2
			if j == 0 {
				// top
				doorPos.y = areas[i].r.y
				outsideDoor = vec2{doorPos.x, doorPos.y - 1}
			} else if j == 1 {
				// right
				doorPos.x = areas[i].r.x + areas[i].r.w - 1
				outsideDoor = vec2{doorPos.x + 1, doorPos.y}
			} else if j == 2 {
				// bottom
				doorPos.y = areas[i].r.y + areas[i].r.h - 1
				outsideDoor = vec2{doorPos.x, doorPos.y + 1}
			} else {
				// left
				doorPos.x = areas[i].r.x
				outsideDoor = vec2{doorPos.x - 1, doorPos.y}
			}
			for k := range streets {
				if streets[k].r.isIn(outsideDoor.x, outsideDoor.y) {
					g.setTile(doorPos.x, doorPos.y, room)
					s.setTile(doorPos.x, doorPos.y, door)
					areas[i].level = 0
					break
				}
			}
		}
	}
	// For every room, connect it to a random room with lower depth
	// Keep going until all rooms are connected
	for {
		numUnconnected := 0
		for i := range areas {
			if areas[i].level >= 0 {
				continue
			}
			numUnconnected++
			r := areas[i].r
			// Shrink rectangles by 1 to determine overlap
			r.w--
			r.h--
			overlapSize := 1
			for j := range areas {
				if i == j {
					continue
				}
				roomOther := areas[j]
				// Only connect to a room that is also connected
				if roomOther.level < 0 {
					continue
				}
				rOther := roomOther.r
				// Shrink rectangles by 1 to determine overlap
				rOther.w--
				rOther.h--
				if !rectIsAdjacent(r, rOther, overlapSize) {
					continue
				}
				// Rooms are adjacent; pick the cell that's in the middle of the
				// adjacent area and turn into a door
				minOverlapX := imin(
					areas[i].r.x+areas[i].r.w, roomOther.r.x+roomOther.r.w)
				maxOverlapX := imax(areas[i].r.x, roomOther.r.x)
				minOverlapY := imin(
					areas[i].r.y+areas[i].r.h, roomOther.r.y+roomOther.r.h)
				maxOverlapY := imax(areas[i].r.y, roomOther.r.y)
				overlapX := (minOverlapX + maxOverlapX) / 2
				overlapY := (minOverlapY + maxOverlapY) / 2
				g.setTile(overlapX, overlapY, room2)
				s.setTile(overlapX, overlapY, door)
				areas[i].level = roomOther.level + 1
				numUnconnected--
				break
			}
		}
		if numUnconnected == 0 {
			break
		}
	}

	// Fill streets
	for i := range streets {
		g.rectangleFilled(streets[i].r, room2)
		// Check ends of street - if next to much older street, block off with wall
		end1 := vec2{streets[i].r.x, streets[i].r.y}
		end2 := vec2{streets[i].r.x + streets[i].r.w - 1, streets[i].r.y + streets[i].r.h - 1}
		capStreet(g, s, streets, streets[i], end1, streets[i].dAcross(), streets[i].dAlong(), corridorWidth, corridorLevelDiffBlock)
		capStreet(g, s, streets, streets[i], end2, vec2{-streets[i].dAcross().x, -streets[i].dAcross().y}, vec2{-streets[i].dAlong().x, -streets[i].dAlong().y}, corridorWidth, corridorLevelDiffBlock)
	}

	// Place stairs going up at end of first (main) street
	s.setTile(streets[0].r.x+streets[0].dAlong().x, streets[0].r.y+streets[0].dAlong().y, stairsUp)
	// Place stairs going down in last room
	lastRoomRect := areas[len(areas)-1].r
	s.setTile(lastRoomRect.x+lastRoomRect.w/2, lastRoomRect.y+lastRoomRect.h/2, stairsDown)

	return m
}

func capStreet(g, s *Layer, streets []street, st street, end, dAcross, dAlong vec2, corridorWidth, corridorLevelDiffBlock int) {
	// Check ends of street - if outside map, or next to much older street, block off with wall
	outside := vec2{end.x - dAlong.x, end.y - dAlong.y}
	doCap := false
	if !g.isIn(outside.x, outside.y) {
		doCap = true
	} else {
		for i := range streets {
			if streets[i].r.isIn(outside.x, outside.y) && st.level-streets[i].level > corridorLevelDiffBlock {
				doCap = true
				break
			}
		}
	}
	if doCap {
		for i := 0; i < corridorWidth; i++ {
			g.setTile(end.x+dAcross.x*i, end.y+dAcross.y*i, nothing)
			s.setTile(end.x+dAcross.x*i, end.y+dAcross.y*i, wall2)
		}
	}
}
<|endoftext|>


<commit_before>// work.go implements all controller actions and their helper functions
// for the parent work.html template. This also comprises the
// search functionality. All user work is done on tabs that are dynamically
// loaded into the work template. These have their own controller files.
package controller

import (
	"fmt"
	"github.com/geobe/gostip/go/model"
	"github.com/geobe/gostip/go/view"
	"github.com/pkg/errors"
	"html"
	"net/http"
	"time"
	"github.com/justinas/nosurf"
	"github.com/geobe/gostip/go/transcription"
)

// load the "work" template. Tabs are included depending on role of current user.
func HandleWork(w http.ResponseWriter, r *http.Request) {
	roles := viewmodel{}
	roles["csrftoken"] = nosurf.Token(r)
	roles["csrfid"] = "csrf_id_find"
	addRoles(r, roles)
	view.Views().ExecuteTemplate(w, "work", roles)
}

// handler function that executes a database search for applicants and
// returns an html fragment for a select box.
func FindApplicant(w http.ResponseWriter, r *http.Request) {
	l := r.Header["Accept-Language"]
	getKyr := transcription.UsesKyrillic(l)
	r.ParseForm()
	lastName := html.EscapeString(r.PostFormValue("lastname"))
	firstName := html.EscapeString(r.PostFormValue("firstname"))
	action := html.EscapeString(r.PostFormValue("action"))
	flag := html.EscapeString(r.PostFormValue("flag"))
	enrol := action == "enrol"
	active := flag == ""
	applicants := findApplicants(lastName, firstName, enrol, active)
	view.Views().ExecuteTemplate(w, "qresult", applicantResultList(applicants, getKyr))
}

func applicantResultList(appls []model.Applicant, getKyr bool) (res []map[string]string) {
	res = make([]map[string]string, len(appls))
	for i, app := range appls {
		isKyr := transcription.IsKyrgyz(app.Data.LastName)
		if isKyr == getKyr {
			res[i] = map[string]string{
				"id":        fmt.Sprintf("%d", app.ID),
				"lastname":  app.Data.LastName,
				"firstname": app.Data.FirstName,
			}
		} else {
			res[i] = map[string]string{
				"id":        fmt.Sprintf("%d", app.ID),
				"lastname":  app.Data.LastNameTx,
				"firstname": app.Data.FirstNameTx,
			}
		}
	}
	return
}

// find applicants based on lastname and/or firstname. Per default, a wildcard search
// character (%) is appended to the search strings and query uses LIKE condition.
// Search is performed also in trnscription fields
// ln, fn:      lastname, firstname search strings
// enrol:       true -> searching from the enrol use case for new applicants
// active:      true -> active applicant, not cancelled
func findApplicants(ln, fn string, enrol bool, active bool) (apps []model.Applicant) {
	var qs string
	if enrol {
		// query for newly registered applicants
		qs = "applicant_data.enrolled_at <'1900-01-01'"
	} else {
		// query for enrolled applicants
		qs = "applicant_data.enrolled_at > '" +
			time.Now().Format("2006") + "-01-01'"
	}
	db := model.Db()
	if active {
		// query for active applicants
		db.Preload("Data").
			Joins("INNER JOIN applicant_data ON applicants.id = applicant_data.applicant_id").
			Where("applicant_data.deleted_at IS NULL").
			Where(qs).
		//Where("applicant_data.last_name like ?", ln + "%").
		//Where("applicant_data.first_name like ?", fn + "%").
			Where("applicant_data.last_name like ? OR applicant_data.last_name_tx like ?",
			ln + "%", ln + "%").
			Where("applicant_data.first_name like ? OR applicant_data.first_name_tx like ?",
			fn + "%", fn + "%").
			Find(&apps)
	} else {
		// query for deleted applicants
		db.Unscoped().Preload("Data").
			Where("applicants.deleted_at IS NOT NULL").
			Joins("INNER JOIN applicant_data ON applicants.id = applicant_data.applicant_id").
			Where("applicant_data.deleted_at IS NULL").
			Where(qs).
			Where("applicant_data.last_name like ?", ln + "%").
			Where("applicant_data.first_name like ?", fn + "%").
			Find(&apps)
	}
	return
}

// add user role fields to the viewmodel map according to the role privileges of current user
func addRoles(r *http.Request, data viewmodel) (err error) {
	session, err := SessionStore().Get(r, S_DKFAI)
	if err != nil {
		return
	}
	role, ok := session.Values["role"].(int)
	if !ok {
		err = errors.New("no role defined")
		return
	}
	if role & model.U_ANY != 0 {
		data["authany"] = true
	}
	if role & model.U_ENROL != 0 {
		data["authenrol"] = true
	}
	if role & model.U_POFF != 0 {
		data["authpoff"] = true
	}
	if role & model.U_UADMIN != 0 {
		data["authuadmin"] = true
	}
	if role & model.U_FULLADMIN != 0 {
		data["authfulladmin"] = true
	}
	if role & model.U_ALL != 0 {
		data["authall"] = true
	}
	return
}
<commit_msg>removed commented lines<commit_after>// work.go implements all controller actions and their helper functions
// for the parent work.html template. This also comprises the
// search functionality. All user work is done on tabs that are dynamically
// loaded into the work template. These have their own controller files.
package controller

import (
	"fmt"
	"github.com/geobe/gostip/go/model"
	"github.com/geobe/gostip/go/view"
	"github.com/pkg/errors"
	"html"
	"net/http"
	"time"
	"github.com/justinas/nosurf"
	"github.com/geobe/gostip/go/transcription"
)

// load the "work" template. Tabs are included depending on role of current user.
func HandleWork(w http.ResponseWriter, r *http.Request) {
	roles := viewmodel{}
	roles["csrftoken"] = nosurf.Token(r)
	roles["csrfid"] = "csrf_id_find"
	addRoles(r, roles)
	view.Views().ExecuteTemplate(w, "work", roles)
}

// handler function that executes a database search for applicants and
// returns an html fragment for a select box.
func FindApplicant(w http.ResponseWriter, r *http.Request) {
	l := r.Header["Accept-Language"]
	getKyr := transcription.UsesKyrillic(l)
	r.ParseForm()
	lastName := html.EscapeString(r.PostFormValue("lastname"))
	firstName := html.EscapeString(r.PostFormValue("firstname"))
	action := html.EscapeString(r.PostFormValue("action"))
	flag := html.EscapeString(r.PostFormValue("flag"))
	enrol := action == "enrol"
	active := flag == ""
	applicants := findApplicants(lastName, firstName, enrol, active)
	view.Views().ExecuteTemplate(w, "qresult", applicantResultList(applicants, getKyr))
}

func applicantResultList(appls []model.Applicant, getKyr bool) (res []map[string]string) {
	res = make([]map[string]string, len(appls))
	for i, app := range appls {
		isKyr := transcription.IsKyrgyz(app.Data.LastName)
		if isKyr == getKyr {
			res[i] = map[string]string{
				"id":        fmt.Sprintf("%d", app.ID),
				"lastname":  app.Data.LastName,
				"firstname": app.Data.FirstName,
			}
		} else {
			res[i] = map[string]string{
				"id":        fmt.Sprintf("%d", app.ID),
				"lastname":  app.Data.LastNameTx,
				"firstname": app.Data.FirstNameTx,
			}
		}
	}
	return
}

// find applicants based on lastname and/or firstname. Per default, a wildcard search
// character (%) is appended to the search strings and query uses LIKE condition.
// Search is performed also in transcription fields
// ln, fn:      lastname, firstname search strings
// enrol:       true -> searching from the enrol use case for new applicants
// active:      true -> active applicant, not cancelled
func findApplicants(ln, fn string, enrol bool, active bool) (apps []model.Applicant) {
	var qs string
	if enrol {
		// query for newly registered applicants
		qs = "applicant_data.enrolled_at <'1900-01-01'"
	} else {
		// query for enrolled applicants
		qs = "applicant_data.enrolled_at > '" +
			time.Now().Format("2006") + "-01-01'"
	}
	db := model.Db()
	if active {
		// query for active applicants
		db.Preload("Data").
			Joins("INNER JOIN applicant_data ON applicants.id = applicant_data.applicant_id").
			Where("applicant_data.deleted_at IS NULL").
			Where(qs).
			Where("applicant_data.last_name like ? OR applicant_data.last_name_tx like ?",
			ln + "%", ln + "%").
			Where("applicant_data.first_name like ? OR applicant_data.first_name_tx like ?",
			fn + "%", fn + "%").
			Find(&apps)
	} else {
		// query for deleted applicants
		db.Unscoped().Preload("Data").
			Where("applicants.deleted_at IS NOT NULL").
			Joins("INNER JOIN applicant_data ON applicants.id = applicant_data.applicant_id").
			Where("applicant_data.deleted_at IS NULL").
			Where(qs).
			Where("applicant_data.last_name like ?", ln + "%").
			Where("applicant_data.first_name like ?", fn + "%").
			Find(&apps)
	}
	return
}

// add user role fields to the viewmodel map according to the role privileges of current user
func addRoles(r *http.Request, data viewmodel) (err error) {
	session, err := SessionStore().Get(r, S_DKFAI)
	if err != nil {
		return
	}
	role, ok := session.Values["role"].(int)
	if !ok {
		err = errors.New("no role defined")
		return
	}
	if role & model.U_ANY != 0 {
		data["authany"] = true
	}
	if role & model.U_ENROL != 0 {
		data["authenrol"] = true
	}
	if role & model.U_POFF != 0 {
		data["authpoff"] = true
	}
	if role & model.U_UADMIN != 0 {
		data["authuadmin"] = true
	}
	if role & model.U_FULLADMIN != 0 {
		data["authfulladmin"] = true
	}
	if role & model.U_ALL != 0 {
		data["authall"] = true
	}
	return
}
<|endoftext|>


<commit_before>package core

import (
	"crypto/sha256"
	"errors"
	"fmt"
	"io/ioutil"
	"math/big"
	"os"
	"path"
	"path/filepath"

	routing "gx/ipfs/QmSY3nkMNLzh9GdbFKK5tT7YMfLpf52iUZ8ZRkr29MJaa5/go-libp2p-kad-dht"
	ma "gx/ipfs/QmTZBfrPJmjWsCvHEtX5FE6KimVJhsJg5sBbqEFYf4UZtL/go-multiaddr"
	"gx/ipfs/QmerPMzPk1mJVowm8KgmoknWa4yCYvvugMPsgWmDNUvDLW/go-multihash"

	"github.com/OpenBazaar/jsonpb"
	"github.com/OpenBazaar/openbazaar-go/ipfs"
	"github.com/OpenBazaar/openbazaar-go/pb"
	"github.com/OpenBazaar/openbazaar-go/repo"
	"golang.org/x/net/context"
)

// ModeratorPointerID  moderator ipfs multihash
var ModeratorPointerID multihash.Multihash

// ErrNoListings - no listing error
// FIXME : This is not used anywhere
var ErrNoListings = errors.New("no listings to set moderators on")

func init() {
	modHash := sha256.Sum256([]byte("moderators"))
	encoded, err := multihash.Encode(modHash[:], multihash.SHA2_256)
	if err != nil {
		log.Fatal("Error creating moderator pointer ID (multihash encode)")
	}
	mh, err := multihash.Cast(encoded)
	if err != nil {
		log.Fatal("Error creating moderator pointer ID (multihash cast)")
	}
	ModeratorPointerID = mh
}

// IsModerator - Am I a moderator?
func (n *OpenBazaarNode) IsModerator() bool {
	profile, err := n.GetProfile()
	if err != nil {
		return false
	}
	return profile.Moderator
}

// SetSelfAsModerator - set self as a moderator
func (n *OpenBazaarNode) SetSelfAsModerator(moderator *pb.Moderator) error {
	if moderator != nil {
		if moderator.Fee == nil {
			return errors.New("moderator must have a fee set")
		}
		if (int(moderator.Fee.FeeType) == 0 || int(moderator.Fee.FeeType) == 2) && moderator.Fee.FixedFee.BigAmount == "" && moderator.Fee.FixedFee.Amount == 0 {
			return errors.New("fixed fee must be set when using a fixed fee type")
		}

		// Update profile
		profile, err := n.GetProfile()
		if err != nil {
			return err
		}

		var currencies []string
		settingsData, _ := n.Datastore.Settings().Get()
		if settingsData.PreferredCurrencies != nil {
			currencies = append(currencies, *settingsData.PreferredCurrencies...)
		} else {
			for ct := range n.Multiwallet {
				currencies = append(currencies, ct.CurrencyCode())
			}
		}
		for _, cc := range currencies {
			currency, err := n.LookupCurrency(cc)
			if err != nil {
				return fmt.Errorf("moderator fee currency (%s) unknown: %s", cc, err)
			}
			moderator.AcceptedCurrencies = append(moderator.AcceptedCurrencies, currency.CurrencyCode().String())
		}

		profile.Moderator = true
		profile.ModeratorInfo = moderator
		err = n.UpdateProfile(&profile)
		if err != nil {
			return err
		}
	}

	// Publish pointer
	pointers, err := n.Datastore.Pointers().GetByPurpose(ipfs.MODERATOR)
	ctx := context.Background()
	if err != nil || len(pointers) == 0 {
		addr, err := ma.NewMultiaddr("/ipfs/" + n.IpfsNode.Identity.Pretty())
		if err != nil {
			return err
		}
		pointer, err := ipfs.NewPointer(ModeratorPointerID, 64, addr, []byte(n.IpfsNode.Identity.Pretty()))
		if err != nil {
			return err
		}
		go func(dht *routing.IpfsDHT, ctx context.Context, pointer ipfs.Pointer) {
			err := ipfs.PublishPointer(dht, ctx, pointer)
			if err != nil {
				log.Error(err)
			}
		}(n.DHT, ctx, pointer)
		pointer.Purpose = ipfs.MODERATOR
		err = n.Datastore.Pointers().Put(pointer)
		if err != nil {
			return err
		}
	} else {
		go func(dht *routing.IpfsDHT, ctx context.Context, pointer ipfs.Pointer) {
			err := ipfs.PublishPointer(dht, ctx, pointer)
			if err != nil {
				log.Error(err)
			}
		}(n.DHT, ctx, pointers[0])
	}
	return nil
}

// RemoveSelfAsModerator - relinquish moderatorship
func (n *OpenBazaarNode) RemoveSelfAsModerator() error {
	// Update profile
	profile, err := n.GetProfile()
	if err != nil {
		return err
	}
	profile.Moderator = false
	err = n.UpdateProfile(&profile)
	if err != nil {
		return err
	}

	// Delete pointer from database
	err = n.Datastore.Pointers().DeleteAll(ipfs.MODERATOR)
	if err != nil {
		return err
	}
	return nil
}

// GetModeratorFee is called by the Moderator when determining their take of the dispute
func (n *OpenBazaarNode) GetModeratorFee(transactionTotal *big.Int, txCurrencyCode string) (*big.Int, error) {
	file, err := ioutil.ReadFile(path.Join(n.RepoPath, "root", "profile.json"))
	if err != nil {
		return big.NewInt(0), err
	}
	profile := new(pb.Profile)
	err = jsonpb.UnmarshalString(string(file), profile)
	if err != nil {
		return big.NewInt(0), err
	}
	txCurrency, err := n.LookupCurrency(txCurrencyCode)
	if err != nil {
		return big.NewInt(0), fmt.Errorf("lookup dispute transaction currency (%s): %s", txCurrencyCode, err)
	}
	t := new(big.Float).SetInt(transactionTotal)
	switch profile.ModeratorInfo.Fee.FeeType {
	case pb.Moderator_Fee_PERCENTAGE:
		f := big.NewFloat(float64(profile.ModeratorInfo.Fee.Percentage))
		f.Mul(f, big.NewFloat(0.01))
		t.Mul(t, f)
		total, _ := t.Int(nil)
		return total, nil
	case pb.Moderator_Fee_FIXED:
		modFeeCurrency, err := n.LookupCurrency(profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code)
		if err != nil {
			return big.NewInt(0), fmt.Errorf("lookup moderator fee currency (%s): %s", profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code, err)
		}
		fixedFee, ok := new(big.Int).SetString(profile.ModeratorInfo.Fee.FixedFee.BigAmount, 10)
		if !ok {
			return big.NewInt(0), errors.New("invalid fixed fee amount")
		}
		if modFeeCurrency.Equal(txCurrency) {
			if fixedFee.Cmp(transactionTotal) > 0 {
				return big.NewInt(0), errors.New("fixed moderator fee exceeds transaction amount")
			}
			return fixedFee, nil
		}
		amt, ok := new(big.Int).SetString(profile.ModeratorInfo.Fee.FixedFee.BigAmount, 10)
		if !ok {
			return big.NewInt(0), errors.New("invalid fixed fee amount")
		}
		fee, err := n.getPriceInSatoshi(txCurrency.CurrencyCode().String(), profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code, amt)
		if err != nil {
			return big.NewInt(0), err
		} else if fee.Cmp(transactionTotal) > 0 {
			return big.NewInt(0), errors.New("Fixed moderator fee exceeds transaction amount")
		}
		return fee, err

	case pb.Moderator_Fee_FIXED_PLUS_PERCENTAGE:
		var fixed *big.Int
		var ok bool
		modFeeCurrency, err := n.LookupCurrency(profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code)
		if err != nil {
			return big.NewInt(0), fmt.Errorf("lookup moderator fee currency (%s): %s", profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code, err)
		}
		if modFeeCurrency.Equal(txCurrency) {
			fixed, ok = new(big.Int).SetString(profile.ModeratorInfo.Fee.FixedFee.BigAmount, 10)
			if !ok {
				return big.NewInt(0), errors.New("invalid fixed fee amount")
			}
		} else {
			f, ok := new(big.Int).SetString(profile.ModeratorInfo.Fee.FixedFee.BigAmount, 10)
			if !ok {
				return big.NewInt(0), errors.New("invalid fixed fee amount")
			}
			f0, err := n.getPriceInSatoshi(txCurrency.CurrencyCode().String(), profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code, f)
			if err != nil {
				return big.NewInt(0), err
			}
			fixed = f0
		}
		f := big.NewFloat(float64(profile.ModeratorInfo.Fee.Percentage))
		f.Mul(f, big.NewFloat(0.01))
		t.Mul(t, f)
		total, _ := t.Int(transactionTotal)
		if fixed.Add(fixed, total).Cmp(transactionTotal) > 0 {
			return big.NewInt(0), errors.New("Fixed moderator fee exceeds transaction amount")
		}
		return fixed.Add(fixed, total), nil
	default:
		return big.NewInt(0), errors.New("Unrecognized fee type")
	}
}

// SetModeratorsOnListings - set moderators for a listing
func (n *OpenBazaarNode) SetModeratorsOnListings(moderators []string) error {
	absPath, err := filepath.Abs(path.Join(n.RepoPath, "root", "listings"))
	if err != nil {
		return err
	}
	hashes := make(map[string]string)
	walkpath := func(p string, f os.FileInfo, err error) error {
		if !f.IsDir() {
			file, err := ioutil.ReadFile(p)
			if err != nil {
				return err
			}
			sl := new(pb.SignedListing)
			err = jsonpb.UnmarshalString(string(file), sl)
			if err != nil {
				return err
			}
			coupons, err := n.Datastore.Coupons().Get(sl.Listing.Slug)
			if err != nil {
				return err
			}
			couponMap := make(map[string]string)
			for _, c := range coupons {
				couponMap[c.Hash] = c.Code
			}
			for _, coupon := range sl.Listing.Coupons {
				code, ok := couponMap[coupon.GetHash()]
				if ok {
					coupon.Code = &pb.Listing_Coupon_DiscountCode{DiscountCode: code}
				}
			}

			sl.Listing.Moderators = moderators

			rsl, err := repo.NewListingFromProtobuf(sl.Listing)
			if err != nil {
				return fmt.Errorf("create repo signed listing: %s", err.Error())
			}
			sl0, err := n.SignListing(*rsl)
			if err != nil {
				return err
			}
			sl = sl0.ProtoSignedListing
			m := jsonpb.Marshaler{
				EnumsAsInts:  false,
				EmitDefaults: false,
				Indent:       "    ",
				OrigName:     false,
			}
			fi, err := os.Create(p)
			if err != nil {
				return err
			}
			out, err := m.MarshalToString(sl)
			if err != nil {
				return err
			}
			if _, err := fi.WriteString(out); err != nil {
				return err
			}
			hash, err := ipfs.GetHashOfFile(n.IpfsNode, p)
			if err != nil {
				return err
			}
			hashes[sl.Listing.Slug] = hash

			return nil
		}
		return nil
	}

	err = filepath.Walk(absPath, walkpath)
	if err != nil {
		return err
	}

	// Update moderators and hashes on index
	updater := func(listing *repo.ListingIndexData) error {
		listing.ModeratorIDs = moderators
		if hash, ok := hashes[listing.Slug]; ok {
			listing.Hash = hash
		}
		return nil
	}
	return n.UpdateEachListingOnIndex(updater)
}

// NotifyModerators - notify moderators(peers)
func (n *OpenBazaarNode) NotifyModerators(addedMods, removedMods []string) error {
	n.Service.WaitForReady()
	for _, mod := range addedMods {
		go func(mod string) {
			err := n.SendModeratorAdd(mod)
			if err != nil {
				log.Error(err)
			}
		}(mod)
	}
	for _, mod := range removedMods {
		go func(mod string) {
			err := n.SendModeratorRemove(mod)
			if err != nil {
				log.Error(err)
			}
		}(mod)
	}
	return nil
}
<commit_msg>[#1839] Fix math logic for Moderator Fixed Fee plus Percentage<commit_after>package core

import (
	"crypto/sha256"
	"errors"
	"fmt"
	"io/ioutil"
	"math/big"
	"os"
	"path"
	"path/filepath"

	routing "gx/ipfs/QmSY3nkMNLzh9GdbFKK5tT7YMfLpf52iUZ8ZRkr29MJaa5/go-libp2p-kad-dht"
	ma "gx/ipfs/QmTZBfrPJmjWsCvHEtX5FE6KimVJhsJg5sBbqEFYf4UZtL/go-multiaddr"
	"gx/ipfs/QmerPMzPk1mJVowm8KgmoknWa4yCYvvugMPsgWmDNUvDLW/go-multihash"

	"github.com/OpenBazaar/jsonpb"
	"github.com/OpenBazaar/openbazaar-go/ipfs"
	"github.com/OpenBazaar/openbazaar-go/pb"
	"github.com/OpenBazaar/openbazaar-go/repo"
	"golang.org/x/net/context"
)

// ModeratorPointerID  moderator ipfs multihash
var ModeratorPointerID multihash.Multihash

// ErrNoListings - no listing error
// FIXME : This is not used anywhere
var ErrNoListings = errors.New("no listings to set moderators on")

func init() {
	modHash := sha256.Sum256([]byte("moderators"))
	encoded, err := multihash.Encode(modHash[:], multihash.SHA2_256)
	if err != nil {
		log.Fatal("Error creating moderator pointer ID (multihash encode)")
	}
	mh, err := multihash.Cast(encoded)
	if err != nil {
		log.Fatal("Error creating moderator pointer ID (multihash cast)")
	}
	ModeratorPointerID = mh
}

// IsModerator - Am I a moderator?
func (n *OpenBazaarNode) IsModerator() bool {
	profile, err := n.GetProfile()
	if err != nil {
		return false
	}
	return profile.Moderator
}

// SetSelfAsModerator - set self as a moderator
func (n *OpenBazaarNode) SetSelfAsModerator(moderator *pb.Moderator) error {
	if moderator != nil {
		if moderator.Fee == nil {
			return errors.New("moderator must have a fee set")
		}
		if (int(moderator.Fee.FeeType) == 0 || int(moderator.Fee.FeeType) == 2) && moderator.Fee.FixedFee.BigAmount == "" && moderator.Fee.FixedFee.Amount == 0 {
			return errors.New("fixed fee must be set when using a fixed fee type")
		}

		// Update profile
		profile, err := n.GetProfile()
		if err != nil {
			return err
		}

		var currencies []string
		settingsData, _ := n.Datastore.Settings().Get()
		if settingsData.PreferredCurrencies != nil {
			currencies = append(currencies, *settingsData.PreferredCurrencies...)
		} else {
			for ct := range n.Multiwallet {
				currencies = append(currencies, ct.CurrencyCode())
			}
		}
		for _, cc := range currencies {
			currency, err := n.LookupCurrency(cc)
			if err != nil {
				return fmt.Errorf("moderator fee currency (%s) unknown: %s", cc, err)
			}
			moderator.AcceptedCurrencies = append(moderator.AcceptedCurrencies, currency.CurrencyCode().String())
		}

		profile.Moderator = true
		profile.ModeratorInfo = moderator
		err = n.UpdateProfile(&profile)
		if err != nil {
			return err
		}
	}

	// Publish pointer
	pointers, err := n.Datastore.Pointers().GetByPurpose(ipfs.MODERATOR)
	ctx := context.Background()
	if err != nil || len(pointers) == 0 {
		addr, err := ma.NewMultiaddr("/ipfs/" + n.IpfsNode.Identity.Pretty())
		if err != nil {
			return err
		}
		pointer, err := ipfs.NewPointer(ModeratorPointerID, 64, addr, []byte(n.IpfsNode.Identity.Pretty()))
		if err != nil {
			return err
		}
		go func(dht *routing.IpfsDHT, ctx context.Context, pointer ipfs.Pointer) {
			err := ipfs.PublishPointer(dht, ctx, pointer)
			if err != nil {
				log.Error(err)
			}
		}(n.DHT, ctx, pointer)
		pointer.Purpose = ipfs.MODERATOR
		err = n.Datastore.Pointers().Put(pointer)
		if err != nil {
			return err
		}
	} else {
		go func(dht *routing.IpfsDHT, ctx context.Context, pointer ipfs.Pointer) {
			err := ipfs.PublishPointer(dht, ctx, pointer)
			if err != nil {
				log.Error(err)
			}
		}(n.DHT, ctx, pointers[0])
	}
	return nil
}

// RemoveSelfAsModerator - relinquish moderatorship
func (n *OpenBazaarNode) RemoveSelfAsModerator() error {
	// Update profile
	profile, err := n.GetProfile()
	if err != nil {
		return err
	}
	profile.Moderator = false
	err = n.UpdateProfile(&profile)
	if err != nil {
		return err
	}

	// Delete pointer from database
	err = n.Datastore.Pointers().DeleteAll(ipfs.MODERATOR)
	if err != nil {
		return err
	}
	return nil
}

// GetModeratorFee is called by the Moderator when determining their take of the dispute
func (n *OpenBazaarNode) GetModeratorFee(transactionTotal *big.Int, txCurrencyCode string) (*big.Int, error) {
	file, err := ioutil.ReadFile(path.Join(n.RepoPath, "root", "profile.json"))
	if err != nil {
		return big.NewInt(0), err
	}
	profile := new(pb.Profile)
	err = jsonpb.UnmarshalString(string(file), profile)
	if err != nil {
		return big.NewInt(0), err
	}
	txCurrency, err := n.LookupCurrency(txCurrencyCode)
	if err != nil {
		return big.NewInt(0), fmt.Errorf("lookup dispute transaction currency (%s): %s", txCurrencyCode, err)
	}
	t := new(big.Float).SetInt(transactionTotal)
	switch profile.ModeratorInfo.Fee.FeeType {
	case pb.Moderator_Fee_PERCENTAGE:
		f := big.NewFloat(float64(profile.ModeratorInfo.Fee.Percentage))
		f.Mul(f, big.NewFloat(0.01))
		t.Mul(t, f)
		total, _ := t.Int(nil)
		return total, nil
	case pb.Moderator_Fee_FIXED:
		modFeeCurrency, err := n.LookupCurrency(profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code)
		if err != nil {
			return big.NewInt(0), fmt.Errorf("lookup moderator fee currency (%s): %s", profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code, err)
		}
		fixedFee, ok := new(big.Int).SetString(profile.ModeratorInfo.Fee.FixedFee.BigAmount, 10)
		if !ok {
			return big.NewInt(0), errors.New("invalid fixed fee amount")
		}
		if modFeeCurrency.Equal(txCurrency) {
			if fixedFee.Cmp(transactionTotal) > 0 {
				return big.NewInt(0), errors.New("fixed moderator fee exceeds transaction amount")
			}
			return fixedFee, nil
		}
		amt, ok := new(big.Int).SetString(profile.ModeratorInfo.Fee.FixedFee.BigAmount, 10)
		if !ok {
			return big.NewInt(0), errors.New("invalid fixed fee amount")
		}
		fee, err := n.getPriceInSatoshi(txCurrency.CurrencyCode().String(), profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code, amt)
		if err != nil {
			return big.NewInt(0), err
		} else if fee.Cmp(transactionTotal) > 0 {
			return big.NewInt(0), errors.New("Fixed moderator fee exceeds transaction amount")
		}
		return fee, err

	case pb.Moderator_Fee_FIXED_PLUS_PERCENTAGE:
		var fixed *big.Int
		var ok bool
		modFeeCurrency, err := n.LookupCurrency(profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code)
		if err != nil {
			return big.NewInt(0), fmt.Errorf("lookup moderator fee currency (%s): %s", profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code, err)
		}
		if modFeeCurrency.Equal(txCurrency) {
			fixed, ok = new(big.Int).SetString(profile.ModeratorInfo.Fee.FixedFee.BigAmount, 10)
			if !ok {
				return big.NewInt(0), errors.New("invalid fixed fee amount")
			}
		} else {
			f, ok := new(big.Int).SetString(profile.ModeratorInfo.Fee.FixedFee.BigAmount, 10)
			if !ok {
				return big.NewInt(0), errors.New("invalid fixed fee amount")
			}
			f0, err := n.getPriceInSatoshi(txCurrency.CurrencyCode().String(), profile.ModeratorInfo.Fee.FixedFee.AmountCurrency.Code, f)
			if err != nil {
				return big.NewInt(0), err
			}
			fixed = f0
		}
		f := big.NewFloat(float64(profile.ModeratorInfo.Fee.Percentage))
		f.Mul(f, big.NewFloat(0.01))
		percentAmt, _ := new(big.Float).Mul(t, f).Int(nil)
		feeTotal := new(big.Int).Add(fixed, percentAmt)
		if feeTotal.Cmp(transactionTotal) > 0 {
			return big.NewInt(0), errors.New("Fixed moderator fee exceeds transaction amount")
		}
		return feeTotal, nil
	default:
		return big.NewInt(0), errors.New("Unrecognized fee type")
	}
}

// SetModeratorsOnListings - set moderators for a listing
func (n *OpenBazaarNode) SetModeratorsOnListings(moderators []string) error {
	absPath, err := filepath.Abs(path.Join(n.RepoPath, "root", "listings"))
	if err != nil {
		return err
	}
	hashes := make(map[string]string)
	walkpath := func(p string, f os.FileInfo, err error) error {
		if !f.IsDir() {
			file, err := ioutil.ReadFile(p)
			if err != nil {
				return err
			}
			sl := new(pb.SignedListing)
			err = jsonpb.UnmarshalString(string(file), sl)
			if err != nil {
				return err
			}
			coupons, err := n.Datastore.Coupons().Get(sl.Listing.Slug)
			if err != nil {
				return err
			}
			couponMap := make(map[string]string)
			for _, c := range coupons {
				couponMap[c.Hash] = c.Code
			}
			for _, coupon := range sl.Listing.Coupons {
				code, ok := couponMap[coupon.GetHash()]
				if ok {
					coupon.Code = &pb.Listing_Coupon_DiscountCode{DiscountCode: code}
				}
			}

			sl.Listing.Moderators = moderators

			rsl, err := repo.NewListingFromProtobuf(sl.Listing)
			if err != nil {
				return fmt.Errorf("create repo signed listing: %s", err.Error())
			}
			sl0, err := n.SignListing(*rsl)
			if err != nil {
				return err
			}
			sl = sl0.ProtoSignedListing
			m := jsonpb.Marshaler{
				EnumsAsInts:  false,
				EmitDefaults: false,
				Indent:       "    ",
				OrigName:     false,
			}
			fi, err := os.Create(p)
			if err != nil {
				return err
			}
			out, err := m.MarshalToString(sl)
			if err != nil {
				return err
			}
			if _, err := fi.WriteString(out); err != nil {
				return err
			}
			hash, err := ipfs.GetHashOfFile(n.IpfsNode, p)
			if err != nil {
				return err
			}
			hashes[sl.Listing.Slug] = hash

			return nil
		}
		return nil
	}

	err = filepath.Walk(absPath, walkpath)
	if err != nil {
		return err
	}

	// Update moderators and hashes on index
	updater := func(listing *repo.ListingIndexData) error {
		listing.ModeratorIDs = moderators
		if hash, ok := hashes[listing.Slug]; ok {
			listing.Hash = hash
		}
		return nil
	}
	return n.UpdateEachListingOnIndex(updater)
}

// NotifyModerators - notify moderators(peers)
func (n *OpenBazaarNode) NotifyModerators(addedMods, removedMods []string) error {
	n.Service.WaitForReady()
	for _, mod := range addedMods {
		go func(mod string) {
			err := n.SendModeratorAdd(mod)
			if err != nil {
				log.Error(err)
			}
		}(mod)
	}
	for _, mod := range removedMods {
		go func(mod string) {
			err := n.SendModeratorRemove(mod)
			if err != nil {
				log.Error(err)
			}
		}(mod)
	}
	return nil
}
<|endoftext|>


<commit_before>package core

import (
	"bytes"
	"compress/gzip"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/livepeer/go-livepeer/common"
	"github.com/livepeer/go-livepeer/monitor"
	"github.com/livepeer/lpms/ffmpeg"

	"github.com/golang/glog"
)

type Transcoder interface {
	Transcode(md *SegTranscodingMetadata) (*TranscodeData, error)
}

type LocalTranscoder struct {
	workDir string
}

var WorkDir string

func (lt *LocalTranscoder) Transcode(md *SegTranscodingMetadata) (*TranscodeData, error) {
	// Set up in / out config
	in := &ffmpeg.TranscodeOptionsIn{
		Fname: md.Fname,
		Accel: ffmpeg.Software,
	}
	profiles := md.Profiles
	opts := profilesToTranscodeOptions(lt.workDir, ffmpeg.Software, profiles)

	_, seqNo, parseErr := parseURI(md.Fname)
	start := time.Now()

	res, err := ffmpeg.Transcode3(in, opts)
	if err != nil {
		return nil, err
	}

	if monitor.Enabled && parseErr == nil {
		// This will run only when fname is actual URL and contains seqNo in it.
		// When orchestrator works as transcoder, `fname` will be relative path to file in local
		// filesystem and will not contain seqNo in it. For that case `SegmentTranscoded` will
		// be called in orchestrator.go
		monitor.SegmentTranscoded(0, seqNo, md.Duration, time.Since(start), common.ProfilesNames(profiles))
	}

	return resToTranscodeData(res, opts)
}

func NewLocalTranscoder(workDir string) Transcoder {
	return &LocalTranscoder{workDir: workDir}
}

type NvidiaTranscoder struct {
	device  string
	session *ffmpeg.Transcoder
}

func (nv *NvidiaTranscoder) Transcode(md *SegTranscodingMetadata) (*TranscodeData, error) {

	in := &ffmpeg.TranscodeOptionsIn{
		Fname:  md.Fname,
		Accel:  ffmpeg.Nvidia,
		Device: nv.device,
	}
	profiles := md.Profiles
	out := profilesToTranscodeOptions(WorkDir, ffmpeg.Nvidia, profiles)

	_, seqNo, parseErr := parseURI(md.Fname)
	start := time.Now()

	res, err := nv.session.Transcode(in, out)
	if err != nil {
		return nil, err
	}

	if monitor.Enabled && parseErr == nil {
		// This will run only when fname is actual URL and contains seqNo in it.
		// When orchestrator works as transcoder, `fname` will be relative path to file in local
		// filesystem and will not contain seqNo in it. For that case `SegmentTranscoded` will
		// be called in orchestrator.go
		monitor.SegmentTranscoded(0, seqNo, md.Duration, time.Since(start), common.ProfilesNames(profiles))
	}

	return resToTranscodeData(res, out)
}

// TestNvidiaTranscoder tries to transcode test segment on all the devices
func TestNvidiaTranscoder(devices []string) error {
	b := bytes.NewReader(testSegment)
	z, err := gzip.NewReader(b)
	if err != nil {
		return err
	}
	mp4testSeg, err := ioutil.ReadAll(z)
	z.Close()
	if err != nil {
		return err
	}
	fname := filepath.Join(WorkDir, "testseg.tempfile")
	err = ioutil.WriteFile(fname, mp4testSeg, 0644)
	if err != nil {
		return err
	}
	defer os.Remove(fname)
	for _, device := range devices {
		t1 := NewNvidiaTranscoder(device)
		// "145x1" is the minimal resolution that succeeds on Windows, so use "145x145"
		p := ffmpeg.VideoProfile{Resolution: "145x145", Bitrate: "1k", Format: ffmpeg.FormatMP4}
		md := &SegTranscodingMetadata{Fname: fname, Profiles: []ffmpeg.VideoProfile{p, p, p, p}}
		td, err := t1.Transcode(md)

		t1.Stop()
		if err != nil {
			return err
		}
		if len(td.Segments) == 0 || td.Pixels == 0 {
			return errors.New("Empty transcoded segment")
		}
	}

	return nil
}

func NewNvidiaTranscoder(gpu string) TranscoderSession {
	return &NvidiaTranscoder{
		device:  gpu,
		session: ffmpeg.NewTranscoder(),
	}
}

func (nv *NvidiaTranscoder) Stop() {
	nv.session.StopTranscoder()
}

func parseURI(uri string) (string, uint64, error) {
	var mid string
	var seqNo uint64
	parts := strings.Split(uri, "/")
	if len(parts) < 3 {
		return mid, seqNo, fmt.Errorf("BadURI")
	}
	mid = parts[len(parts)-2]
	parts = strings.Split(parts[len(parts)-1], ".")
	seqNo, err := strconv.ParseUint(parts[0], 10, 64)
	return mid, seqNo, err
}

func resToTranscodeData(res *ffmpeg.TranscodeResults, opts []ffmpeg.TranscodeOptions) (*TranscodeData, error) {
	if len(res.Encoded) != len(opts) {
		return nil, errors.New("lengths of results and options different")
	}

	// Convert results into in-memory bytes following the expected API
	segments := make([]*TranscodedSegmentData, len(opts), len(opts))
	for i := range opts {
		oname := opts[i].Oname
		o, err := ioutil.ReadFile(oname)
		if err != nil {
			glog.Error("Cannot read transcoded output for ", oname)
			return nil, err
		}
		segments[i] = &TranscodedSegmentData{Data: o, Pixels: res.Encoded[i].Pixels}
		os.Remove(oname)
	}

	return &TranscodeData{
		Segments: segments,
		Pixels:   res.Decoded.Pixels,
	}, nil
}

func profilesToTranscodeOptions(workDir string, accel ffmpeg.Acceleration, profiles []ffmpeg.VideoProfile) []ffmpeg.TranscodeOptions {
	opts := make([]ffmpeg.TranscodeOptions, len(profiles), len(profiles))
	for i := range profiles {
		o := ffmpeg.TranscodeOptions{
			Oname:        fmt.Sprintf("%s/out_%s.tempfile", workDir, common.RandName()),
			Profile:      profiles[i],
			Accel:        accel,
			AudioEncoder: ffmpeg.ComponentOptions{Name: "copy"},
		}
		opts[i] = o
	}
	return opts
}
<commit_msg>core/transcoder: Configure detection, extract results<commit_after>package core

import (
	"bytes"
	"compress/gzip"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/livepeer/go-livepeer/common"
	"github.com/livepeer/go-livepeer/monitor"
	"github.com/livepeer/lpms/ffmpeg"

	"github.com/golang/glog"
)

type Transcoder interface {
	Transcode(md *SegTranscodingMetadata) (*TranscodeData, error)
}

type LocalTranscoder struct {
	workDir string
}

var WorkDir string

func (lt *LocalTranscoder) Transcode(md *SegTranscodingMetadata) (*TranscodeData, error) {
	// Set up in / out config
	in := &ffmpeg.TranscodeOptionsIn{
		Fname: md.Fname,
		Accel: ffmpeg.Software,
	}
	profiles := md.Profiles
	opts := profilesToTranscodeOptions(lt.workDir, ffmpeg.Software, profiles)
	opts = append(opts, detectorsToTranscodeOptions(lt.workDir, ffmpeg.Software, md.DetectorProfiles)...)

	_, seqNo, parseErr := parseURI(md.Fname)
	start := time.Now()

	res, err := ffmpeg.Transcode3(in, opts)
	if err != nil {
		return nil, err
	}

	if monitor.Enabled && parseErr == nil {
		// This will run only when fname is actual URL and contains seqNo in it.
		// When orchestrator works as transcoder, `fname` will be relative path to file in local
		// filesystem and will not contain seqNo in it. For that case `SegmentTranscoded` will
		// be called in orchestrator.go
		monitor.SegmentTranscoded(0, seqNo, md.Duration, time.Since(start), common.ProfilesNames(profiles))
	}

	return resToTranscodeData(res, opts)
}

func NewLocalTranscoder(workDir string) Transcoder {
	return &LocalTranscoder{workDir: workDir}
}

type NvidiaTranscoder struct {
	device  string
	session *ffmpeg.Transcoder
}

func (nv *NvidiaTranscoder) Transcode(md *SegTranscodingMetadata) (*TranscodeData, error) {

	in := &ffmpeg.TranscodeOptionsIn{
		Fname:  md.Fname,
		Accel:  ffmpeg.Nvidia,
		Device: nv.device,
	}
	profiles := md.Profiles
	out := profilesToTranscodeOptions(WorkDir, ffmpeg.Nvidia, profiles)
	out = append(out, detectorsToTranscodeOptions(WorkDir, ffmpeg.Nvidia, md.DetectorProfiles)...)

	_, seqNo, parseErr := parseURI(md.Fname)
	start := time.Now()

	res, err := nv.session.Transcode(in, out)
	if err != nil {
		return nil, err
	}

	if monitor.Enabled && parseErr == nil {
		// This will run only when fname is actual URL and contains seqNo in it.
		// When orchestrator works as transcoder, `fname` will be relative path to file in local
		// filesystem and will not contain seqNo in it. For that case `SegmentTranscoded` will
		// be called in orchestrator.go
		monitor.SegmentTranscoded(0, seqNo, md.Duration, time.Since(start), common.ProfilesNames(profiles))
	}

	return resToTranscodeData(res, out)
}

// TestNvidiaTranscoder tries to transcode test segment on all the devices
func TestNvidiaTranscoder(devices []string) error {
	b := bytes.NewReader(testSegment)
	z, err := gzip.NewReader(b)
	if err != nil {
		return err
	}
	mp4testSeg, err := ioutil.ReadAll(z)
	z.Close()
	if err != nil {
		return err
	}
	fname := filepath.Join(WorkDir, "testseg.tempfile")
	err = ioutil.WriteFile(fname, mp4testSeg, 0644)
	if err != nil {
		return err
	}
	defer os.Remove(fname)
	for _, device := range devices {
		t1 := NewNvidiaTranscoder(device)
		// "145x1" is the minimal resolution that succeeds on Windows, so use "145x145"
		p := ffmpeg.VideoProfile{Resolution: "145x145", Bitrate: "1k", Format: ffmpeg.FormatMP4}
		md := &SegTranscodingMetadata{Fname: fname, Profiles: []ffmpeg.VideoProfile{p, p, p, p}}
		td, err := t1.Transcode(md)

		t1.Stop()
		if err != nil {
			return err
		}
		if len(td.Segments) == 0 || td.Pixels == 0 {
			return errors.New("Empty transcoded segment")
		}
	}

	return nil
}

func NewNvidiaTranscoder(gpu string) TranscoderSession {
	return &NvidiaTranscoder{
		device:  gpu,
		session: ffmpeg.NewTranscoder(),
	}
}

func (nv *NvidiaTranscoder) Stop() {
	nv.session.StopTranscoder()
}

func parseURI(uri string) (string, uint64, error) {
	var mid string
	var seqNo uint64
	parts := strings.Split(uri, "/")
	if len(parts) < 3 {
		return mid, seqNo, fmt.Errorf("BadURI")
	}
	mid = parts[len(parts)-2]
	parts = strings.Split(parts[len(parts)-1], ".")
	seqNo, err := strconv.ParseUint(parts[0], 10, 64)
	return mid, seqNo, err
}

func resToTranscodeData(res *ffmpeg.TranscodeResults, opts []ffmpeg.TranscodeOptions) (*TranscodeData, error) {
	if len(res.Encoded) != len(opts) {
		return nil, errors.New("lengths of results and options different")
	}

	// Convert results into in-memory bytes following the expected API
	segments := []*TranscodedSegmentData{}
	// Extract detection data from detector outputs
	detections := []ffmpeg.DetectData{}
	for i := range opts {
		if opts[i].Detector == nil {
			oname := opts[i].Oname
			o, err := ioutil.ReadFile(oname)
			if err != nil {
				glog.Error("Cannot read transcoded output for ", oname)
				return nil, err
			}
			segments = append(segments, &TranscodedSegmentData{Data: o, Pixels: res.Encoded[i].Pixels})
			os.Remove(oname)
		} else {
			detections = append(detections, res.Encoded[i].DetectData)
		}
	}

	return &TranscodeData{
		Segments:   segments,
		Pixels:     res.Decoded.Pixels,
		Detections: detections,
	}, nil
}

func profilesToTranscodeOptions(workDir string, accel ffmpeg.Acceleration, profiles []ffmpeg.VideoProfile) []ffmpeg.TranscodeOptions {
	opts := make([]ffmpeg.TranscodeOptions, len(profiles), len(profiles))
	for i := range profiles {
		o := ffmpeg.TranscodeOptions{
			Oname:        fmt.Sprintf("%s/out_%s.tempfile", workDir, common.RandName()),
			Profile:      profiles[i],
			Accel:        accel,
			AudioEncoder: ffmpeg.ComponentOptions{Name: "copy"},
		}
		opts[i] = o
	}
	return opts
}

func detectorsToTranscodeOptions(workDir string, accel ffmpeg.Acceleration, profiles []ffmpeg.DetectorProfile) []ffmpeg.TranscodeOptions {
	opts := make([]ffmpeg.TranscodeOptions, len(profiles), len(profiles))
	for i := range profiles {
		var o ffmpeg.TranscodeOptions
		switch profiles[i].Type() {
		case ffmpeg.SceneClassification:
			classifier := profiles[i].(*ffmpeg.SceneClassificationProfile)
			classifier.ModelPath = fmt.Sprintf("%s/%s", workDir, ffmpeg.DSceneAdultSoccer.ModelPath)
			classifier.Input = ffmpeg.DSceneAdultSoccer.Input
			classifier.Output = ffmpeg.DSceneAdultSoccer.Output
			o = ffmpeg.TranscodeOptions{
				Detector: classifier,
				Accel:    accel,
			}
		}
		opts[i] = o
	}
	return opts
}
<|endoftext|>


<commit_before>package couchdb

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

type updateResponse struct {
	ID  string `json:"id"`
	Rev string `json:"rev"`
	Ok  bool   `json:"ok"`
}

// Doc is the interface that encapsulate a couchdb document, of any
// serializable type. This interface defines method to set and get the
// ID of the document.
type Doc interface {
	ID() string
	Rev() string
	DocType() string

	SetID(id string)
	SetRev(rev string)
}

// JSONDoc is a map representing a simple json object that implements
// the Doc interface.
type JSONDoc struct {
	M    map[string]interface{}
	Type string
}

// ID returns the identifier field of the document
//   "io.cozy.event/123abc123" == doc.ID()
func (j JSONDoc) ID() string {
	id, ok := j.M["_id"].(string)
	if ok {
		return id
	}
	return ""
}

// Rev returns the revision field of the document
//   "3-1234def1234" == doc.Rev()
func (j JSONDoc) Rev() string {
	rev, ok := j.M["_rev"].(string)
	if ok {
		return rev
	}
	return ""
}

// DocType returns the document type of the document
//   "io.cozy.event" == doc.Doctype()
func (j JSONDoc) DocType() string {
	return j.Type
}

// SetID is used to set the identifier of the document
func (j JSONDoc) SetID(id string) {
	j.M["_id"] = id
}

// SetRev is used to set the revision of the document
func (j JSONDoc) SetRev(rev string) {
	j.M["_rev"] = rev
}

// MarshalJSON implements json.Marshaller by proxying to internal map
func (j JSONDoc) MarshalJSON() ([]byte, error) {
	return json.Marshal(j.M)
}

// UnmarshalJSON implements json.Unmarshaller by proxying to internal map
func (j *JSONDoc) UnmarshalJSON(bytes []byte) error {
	return json.Unmarshal(bytes, &j.M)
}

// ToMapWithType returns the JSONDoc internal map including its DocType
// its used in request response.
func (j *JSONDoc) ToMapWithType() map[string]interface{} {
	j.M["_type"] = j.DocType()
	return j.M
}

// CouchURL is the URL where to check if CouchDB is up
func CouchURL() string {
	return "http://localhost:5984/"
}

var couchdbClient = &http.Client{}

func makeDBName(dbprefix, doctype string) string {
	// @TODO This should be better analysed
	dbname := dbprefix + doctype
	dbname = strings.Replace(dbname, ".", "-", -1)
	dbname = strings.ToLower(dbname)
	return url.QueryEscape(dbname)
}

func docURL(dbprefix, doctype, id string) string {
	return makeDBName(dbprefix, doctype) + "/" + url.QueryEscape(id)
}

func makeRequest(method, path string, reqbody interface{}, resbody interface{}) error {
	var reqjson []byte
	var err error

	if reqbody != nil {
		reqjson, err = json.Marshal(reqbody)
		if err != nil {
			return err
		}
	}

	fmt.Printf("[couchdb request] %v %v %v\n", method, path, string(reqjson))

	req, err := http.NewRequest(method, CouchURL()+path, bytes.NewReader(reqjson))
	// Possible err = wrong method, unparsable url
	if err != nil {
		return newRequestError(err)
	}
	if reqbody != nil {
		req.Header.Add("Content-Type", "application/json")
	}
	req.Header.Add("Accept", "application/json")
	resp, err := couchdbClient.Do(req)
	// Possible err = mostly connection failure
	if err != nil {
		return newConnectionError(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	// Possible err = mostly connection failure (hangup)
	if err != nil {
		return newIOReadError(err)
	}

	fmt.Printf("[couchdb response] %v\n", string(body))

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		// Couchdb as returned an error HTTP status code
		return newCouchdbError(resp.StatusCode, body)
	}

	if resbody == nil {
		// dont care about the return value
		return nil
	}
	err = json.Unmarshal(body, &resbody)
	return err
}

// GetDoc fetch a document by its docType and ID, out is filled with
// the document by json.Unmarshal-ing
func GetDoc(dbprefix, doctype, id string, out Doc) error {
	err := makeRequest("GET", docURL(dbprefix, doctype, id), nil, out)
	if IsNoDatabaseError(err) {
		err.(*Error).Reason = "wrong_doctype"
	}
	return err
}

// CreateDB creates the necessary database for a doctype
func CreateDB(dbprefix, doctype string) error {
	return makeRequest("PUT", makeDBName(dbprefix, doctype), nil, nil)
}

// DeleteDB destroy the database for a doctype
func DeleteDB(dbprefix, doctype string) error {
	return makeRequest("DELETE", makeDBName(dbprefix, doctype), nil, nil)
}

// ResetDB destroy and recreate the database for a doctype
func ResetDB(dbprefix, doctype string) (err error) {
	err = DeleteDB(dbprefix, doctype)
	if err != nil {
		return err
	}
	return CreateDB(dbprefix, doctype)
}

// Delete destroy a document by its doctype and ID .
// If the document's current rev does not match the one passed,
// a CouchdbError(409 conflict) will be returned.
// This functions returns the tombstone revision as string
func Delete(dbprefix, doctype, id, rev string) (tombrev string, err error) {
	var res updateResponse
	qs := url.Values{"rev": []string{rev}}
	url := docURL(dbprefix, doctype, id) + "?" + qs.Encode()
	err = makeRequest("DELETE", url, nil, &res)
	if err == nil {
		tombrev = res.Rev
	}
	return
}

// DeleteDoc deletes a struct implementing the couchb.Doc interface
// The document's SetRev will be called with tombstone revision
func DeleteDoc(dbprefix string, doc Doc) (err error) {
	doctype := doc.DocType()
	id := doc.ID()
	rev := doc.Rev()
	tombrev, err := Delete(dbprefix, doctype, id, rev)
	if err == nil {
		doc.SetRev(tombrev)
	}
	return
}

// UpdateDoc update a document. The document ID and Rev should be fillled.
// The doc SetRev function will be called with the new rev.
func UpdateDoc(dbprefix string, doc Doc) (err error) {
	doctype := doc.DocType()
	id := doc.ID()
	rev := doc.Rev()
	if id == "" || rev == "" || doctype == "" {
		return fmt.Errorf("UpdateDoc argument should have doctype, id and rev ")
	}

	url := docURL(dbprefix, doctype, id)
	var res updateResponse
	err = makeRequest("PUT", url, doc, &res)
	if err == nil {
		doc.SetRev(res.Rev)
	}
	return err
}

func createDocOrDb(dbprefix string, doc Doc, response interface{}) (err error) {
	doctype := doc.DocType()
	db := makeDBName(dbprefix, doctype)
	err = makeRequest("POST", db, doc, response)
	if err == nil || !IsNoDatabaseError(err) {
		return
	}

	err = CreateDB(dbprefix, doctype)
	if err == nil {
		err = makeRequest("POST", db, doc, response)
	}
	return
}

// CreateDoc is used to persist the given document in the couchdb
// database. The document's SetRev and SetID function will be called
// with the document's new ID and Rev.
// This function creates a database if this is the first document of its type
func CreateDoc(dbprefix string, doc Doc) (err error) {
	var res *updateResponse

	if doc.ID() != "" {
		err = fmt.Errorf("Can not create document with a defined ID")
		return
	}

	err = createDocOrDb(dbprefix, doc, &res)
	if err != nil {
		return err
	} else if !res.Ok {
		return fmt.Errorf("CouchDB replied with 200 ok=false")
	}

	doc.SetID(res.ID)
	doc.SetRev(res.Rev)
	return nil
}
<commit_msg>make JSONDoc unmashal its type<commit_after>package couchdb

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

type updateResponse struct {
	ID  string `json:"id"`
	Rev string `json:"rev"`
	Ok  bool   `json:"ok"`
}

// Doc is the interface that encapsulate a couchdb document, of any
// serializable type. This interface defines method to set and get the
// ID of the document.
type Doc interface {
	ID() string
	Rev() string
	DocType() string

	SetID(id string)
	SetRev(rev string)
}

// JSONDoc is a map representing a simple json object that implements
// the Doc interface.
type JSONDoc struct {
	M    map[string]interface{}
	Type string
}

// ID returns the identifier field of the document
//   "io.cozy.event/123abc123" == doc.ID()
func (j JSONDoc) ID() string {
	id, ok := j.M["_id"].(string)
	if ok {
		return id
	}
	return ""
}

// Rev returns the revision field of the document
//   "3-1234def1234" == doc.Rev()
func (j JSONDoc) Rev() string {
	rev, ok := j.M["_rev"].(string)
	if ok {
		return rev
	}
	return ""
}

// DocType returns the document type of the document
//   "io.cozy.event" == doc.Doctype()
func (j JSONDoc) DocType() string {
	return j.Type
}

// SetID is used to set the identifier of the document
func (j JSONDoc) SetID(id string) {
	j.M["_id"] = id
}

// SetRev is used to set the revision of the document
func (j JSONDoc) SetRev(rev string) {
	j.M["_rev"] = rev
}

// MarshalJSON implements json.Marshaller by proxying to internal map
func (j JSONDoc) MarshalJSON() ([]byte, error) {
	return json.Marshal(j.M)
}

// UnmarshalJSON implements json.Unmarshaller by proxying to internal map
func (j *JSONDoc) UnmarshalJSON(bytes []byte) error {
	err := json.Unmarshal(bytes, &j.M)
	if err != nil {
		return err
	}
	doctype, ok := j.M["_type"].(string)
	if ok {
		j.Type = doctype
	}
	delete(j.M, "_type")
	return nil
}

// ToMapWithType returns the JSONDoc internal map including its DocType
// its used in request response.
func (j *JSONDoc) ToMapWithType() map[string]interface{} {
	j.M["_type"] = j.DocType()
	return j.M
}

// CouchURL is the URL where to check if CouchDB is up
func CouchURL() string {
	return "http://localhost:5984/"
}

var couchdbClient = &http.Client{}

func makeDBName(dbprefix, doctype string) string {
	// @TODO This should be better analysed
	dbname := dbprefix + doctype
	dbname = strings.Replace(dbname, ".", "-", -1)
	dbname = strings.ToLower(dbname)
	return url.QueryEscape(dbname)
}

func docURL(dbprefix, doctype, id string) string {
	return makeDBName(dbprefix, doctype) + "/" + url.QueryEscape(id)
}

func makeRequest(method, path string, reqbody interface{}, resbody interface{}) error {
	var reqjson []byte
	var err error

	if reqbody != nil {
		reqjson, err = json.Marshal(reqbody)
		if err != nil {
			return err
		}
	}

	fmt.Printf("[couchdb request] %v %v %v\n", method, path, string(reqjson))

	req, err := http.NewRequest(method, CouchURL()+path, bytes.NewReader(reqjson))
	// Possible err = wrong method, unparsable url
	if err != nil {
		return newRequestError(err)
	}
	if reqbody != nil {
		req.Header.Add("Content-Type", "application/json")
	}
	req.Header.Add("Accept", "application/json")
	resp, err := couchdbClient.Do(req)
	// Possible err = mostly connection failure
	if err != nil {
		return newConnectionError(err)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	// Possible err = mostly connection failure (hangup)
	if err != nil {
		return newIOReadError(err)
	}

	fmt.Printf("[couchdb response] %v\n", string(body))

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		// Couchdb as returned an error HTTP status code
		return newCouchdbError(resp.StatusCode, body)
	}

	if resbody == nil {
		// dont care about the return value
		return nil
	}
	err = json.Unmarshal(body, &resbody)
	return err
}

// GetDoc fetch a document by its docType and ID, out is filled with
// the document by json.Unmarshal-ing
func GetDoc(dbprefix, doctype, id string, out Doc) error {
	err := makeRequest("GET", docURL(dbprefix, doctype, id), nil, out)
	if IsNoDatabaseError(err) {
		err.(*Error).Reason = "wrong_doctype"
	}
	return err
}

// CreateDB creates the necessary database for a doctype
func CreateDB(dbprefix, doctype string) error {
	return makeRequest("PUT", makeDBName(dbprefix, doctype), nil, nil)
}

// DeleteDB destroy the database for a doctype
func DeleteDB(dbprefix, doctype string) error {
	return makeRequest("DELETE", makeDBName(dbprefix, doctype), nil, nil)
}

// ResetDB destroy and recreate the database for a doctype
func ResetDB(dbprefix, doctype string) (err error) {
	err = DeleteDB(dbprefix, doctype)
	if err != nil {
		return err
	}
	return CreateDB(dbprefix, doctype)
}

// Delete destroy a document by its doctype and ID .
// If the document's current rev does not match the one passed,
// a CouchdbError(409 conflict) will be returned.
// This functions returns the tombstone revision as string
func Delete(dbprefix, doctype, id, rev string) (tombrev string, err error) {
	var res updateResponse
	qs := url.Values{"rev": []string{rev}}
	url := docURL(dbprefix, doctype, id) + "?" + qs.Encode()
	err = makeRequest("DELETE", url, nil, &res)
	if err == nil {
		tombrev = res.Rev
	}
	return
}

// DeleteDoc deletes a struct implementing the couchb.Doc interface
// The document's SetRev will be called with tombstone revision
func DeleteDoc(dbprefix string, doc Doc) (err error) {
	doctype := doc.DocType()
	id := doc.ID()
	rev := doc.Rev()
	tombrev, err := Delete(dbprefix, doctype, id, rev)
	if err == nil {
		doc.SetRev(tombrev)
	}
	return
}

// UpdateDoc update a document. The document ID and Rev should be fillled.
// The doc SetRev function will be called with the new rev.
func UpdateDoc(dbprefix string, doc Doc) (err error) {
	doctype := doc.DocType()
	id := doc.ID()
	rev := doc.Rev()
	if id == "" || rev == "" || doctype == "" {
		return fmt.Errorf("UpdateDoc argument should have doctype, id and rev ")
	}

	url := docURL(dbprefix, doctype, id)
	var res updateResponse
	err = makeRequest("PUT", url, doc, &res)
	if err == nil {
		doc.SetRev(res.Rev)
	}
	return err
}

func createDocOrDb(dbprefix string, doc Doc, response interface{}) (err error) {
	doctype := doc.DocType()
	db := makeDBName(dbprefix, doctype)
	err = makeRequest("POST", db, doc, response)
	if err == nil || !IsNoDatabaseError(err) {
		return
	}

	err = CreateDB(dbprefix, doctype)
	if err == nil {
		err = makeRequest("POST", db, doc, response)
	}
	return
}

// CreateDoc is used to persist the given document in the couchdb
// database. The document's SetRev and SetID function will be called
// with the document's new ID and Rev.
// This function creates a database if this is the first document of its type
func CreateDoc(dbprefix string, doc Doc) (err error) {
	var res *updateResponse

	if doc.ID() != "" {
		err = fmt.Errorf("Can not create document with a defined ID")
		return
	}

	err = createDocOrDb(dbprefix, doc, &res)
	if err != nil {
		return err
	} else if !res.Ok {
		return fmt.Errorf("CouchDB replied with 200 ok=false")
	}

	doc.SetID(res.ID)
	doc.SetRev(res.Rev)
	return nil
}
<|endoftext|>


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

package ssa_test

// This file runs the SSA builder in sanity-checking mode on all
// packages beneath $GOROOT and prints some summary information.
//
// Run test with GOMAXPROCS=8.

import (
	"go/token"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
	"time"

	"code.google.com/p/go.tools/go/loader"
	"code.google.com/p/go.tools/go/ssa"
	"code.google.com/p/go.tools/go/ssa/ssautil"
)

func allPackages() []string {
	var pkgs []string
	root := filepath.Join(runtime.GOROOT(), "src/pkg") + string(os.PathSeparator)
	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		// Prune the search if we encounter any of these names:
		switch filepath.Base(path) {
		case "testdata", ".hg":
			return filepath.SkipDir
		}
		if info.IsDir() {
			pkg := strings.TrimPrefix(path, root)
			switch pkg {
			case "builtin", "pkg", "code.google.com":
				return filepath.SkipDir // skip these subtrees
			case "":
				return nil // ignore root of tree
			}
			pkgs = append(pkgs, pkg)
		}

		return nil
	})
	return pkgs
}

func TestStdlib(t *testing.T) {
	// TODO(brainman): delete when issue 7189 is fixed.
	if runtime.GOOS == "windows" {
		t.Skip("skipping broken test on windows; see golang.org/issue/7189")
	}

	// Load, parse and type-check the program.
	t0 := time.Now()

	var conf loader.Config
	if _, err := conf.FromArgs(allPackages()); err != nil {
		t.Errorf("FromArgs failed: %v", err)
		return
	}

	iprog, err := conf.Load()
	if err != nil {
		t.Fatalf("Load failed: %v", err)
	}

	t1 := time.Now()

	runtime.GC()
	var memstats runtime.MemStats
	runtime.ReadMemStats(&memstats)
	alloc := memstats.Alloc

	// Create SSA packages.
	var mode ssa.BuilderMode
	// Comment out these lines during benchmarking.  Approx SSA build costs are noted.
	mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time
	mode |= ssa.GlobalDebug          // +30% space, +18% time
	prog := ssa.Create(iprog, mode)

	t2 := time.Now()

	// Build SSA IR... if it's safe.
	prog.BuildAll()

	t3 := time.Now()

	runtime.GC()
	runtime.ReadMemStats(&memstats)

	numPkgs := len(prog.AllPackages())
	if want := 140; numPkgs < want {
		t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
	}

	// Dump some statistics.
	allFuncs := ssautil.AllFunctions(prog)
	var numInstrs int
	for fn := range allFuncs {
		for _, b := range fn.Blocks {
			numInstrs += len(b.Instrs)
		}
	}

	// determine line count
	var lineCount int
	prog.Fset.Iterate(func(f *token.File) bool {
		lineCount += f.LineCount()
		return true
	})

	// NB: when benchmarking, don't forget to clear the debug +
	// sanity builder flags for better performance.

	t.Log("GOMAXPROCS:           ", runtime.GOMAXPROCS(0))
	t.Log("#Source lines:        ", lineCount)
	t.Log("Load/parse/typecheck: ", t1.Sub(t0))
	t.Log("SSA create:           ", t2.Sub(t1))
	t.Log("SSA build:            ", t3.Sub(t2))

	// SSA stats:
	t.Log("#Packages:            ", numPkgs)
	t.Log("#Functions:           ", len(allFuncs))
	t.Log("#Instructions:        ", numInstrs)
	t.Log("#MB:                  ", (memstats.Alloc-alloc)/1000000)
}
<commit_msg>go.tools/go/ssa: remove windows test exclusion<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ssa_test

// This file runs the SSA builder in sanity-checking mode on all
// packages beneath $GOROOT and prints some summary information.
//
// Run test with GOMAXPROCS=8.

import (
	"go/token"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
	"time"

	"code.google.com/p/go.tools/go/loader"
	"code.google.com/p/go.tools/go/ssa"
	"code.google.com/p/go.tools/go/ssa/ssautil"
)

func allPackages() []string {
	var pkgs []string
	root := filepath.Join(runtime.GOROOT(), "src/pkg") + string(os.PathSeparator)
	filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		// Prune the search if we encounter any of these names:
		switch filepath.Base(path) {
		case "testdata", ".hg":
			return filepath.SkipDir
		}
		if info.IsDir() {
			pkg := strings.TrimPrefix(path, root)
			switch pkg {
			case "builtin", "pkg", "code.google.com":
				return filepath.SkipDir // skip these subtrees
			case "":
				return nil // ignore root of tree
			}
			pkgs = append(pkgs, pkg)
		}

		return nil
	})
	return pkgs
}

func TestStdlib(t *testing.T) {
	// Load, parse and type-check the program.
	t0 := time.Now()

	var conf loader.Config
	if _, err := conf.FromArgs(allPackages()); err != nil {
		t.Errorf("FromArgs failed: %v", err)
		return
	}

	iprog, err := conf.Load()
	if err != nil {
		t.Fatalf("Load failed: %v", err)
	}

	t1 := time.Now()

	runtime.GC()
	var memstats runtime.MemStats
	runtime.ReadMemStats(&memstats)
	alloc := memstats.Alloc

	// Create SSA packages.
	var mode ssa.BuilderMode
	// Comment out these lines during benchmarking.  Approx SSA build costs are noted.
	mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time
	mode |= ssa.GlobalDebug          // +30% space, +18% time
	prog := ssa.Create(iprog, mode)

	t2 := time.Now()

	// Build SSA IR... if it's safe.
	prog.BuildAll()

	t3 := time.Now()

	runtime.GC()
	runtime.ReadMemStats(&memstats)

	numPkgs := len(prog.AllPackages())
	if want := 140; numPkgs < want {
		t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
	}

	// Dump some statistics.
	allFuncs := ssautil.AllFunctions(prog)
	var numInstrs int
	for fn := range allFuncs {
		for _, b := range fn.Blocks {
			numInstrs += len(b.Instrs)
		}
	}

	// determine line count
	var lineCount int
	prog.Fset.Iterate(func(f *token.File) bool {
		lineCount += f.LineCount()
		return true
	})

	// NB: when benchmarking, don't forget to clear the debug +
	// sanity builder flags for better performance.

	t.Log("GOMAXPROCS:           ", runtime.GOMAXPROCS(0))
	t.Log("#Source lines:        ", lineCount)
	t.Log("Load/parse/typecheck: ", t1.Sub(t0))
	t.Log("SSA create:           ", t2.Sub(t1))
	t.Log("SSA build:            ", t3.Sub(t2))

	// SSA stats:
	t.Log("#Packages:            ", numPkgs)
	t.Log("#Functions:           ", len(allFuncs))
	t.Log("#Instructions:        ", numInstrs)
	t.Log("#MB:                  ", (memstats.Alloc-alloc)/1000000)
}
<|endoftext|>


<commit_before>// Copyright © 2017 Johnny Morrice <john@functorama.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"fmt"
	"os"
	"os/signal"
	"runtime"
	"time"

	lib "github.com/johnny-morrice/godless"
	"github.com/johnny-morrice/godless/api"
	"github.com/johnny-morrice/godless/cache"
	"github.com/johnny-morrice/godless/internal/http"
	"github.com/johnny-morrice/godless/log"
	"github.com/spf13/cobra"
)

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
	Use:   "server",
	Short: "Run a Godless server",
	Long:  `A godless server listens to queries over HTTP.`,
	Run: func(cmd *cobra.Command, args []string) {
		readKeysFromViper()
		serve(cmd)
	},
}

func serve(cmd *cobra.Command) {
	client := http.DefaultBackendClient()
	client.Timeout = serverTimeout

	queue := makePriorityQueue(cmd)
	memimg, err := makeMemoryImage()
	cache, err := makeCache(cmd)

	if err != nil {
		die(err)
	}

	options := lib.Options{
		IpfsServiceUrl:    ipfsService,
		WebServiceAddr:    addr,
		IndexHash:         hash,
		FailEarly:         earlyConnect,
		ReplicateInterval: interval,
		Topics:            topics,
		APIQueryLimit:     apiQueryLimit,
		KeyStore:          keyStore,
		PublicServer:      publicServer,
		IpfsClient:        client,
		Pulse:             pulse,
		PriorityQueue:     queue,
		Cache:             cache,
		MemoryImage:       memimg,
	}

	godless, err := lib.New(options)

	if err != nil {
		die(err)
	}

	shutdownOnTrap(godless)

	for runError := range godless.Errors() {
		log.Error("%s", runError.Error())
	}

	defer shutdown(godless)
}

var addr string
var interval time.Duration
var pulse time.Duration
var earlyConnect bool
var apiQueryLimit int
var apiQueueLength int
var memoryBufferLength int
var publicServer bool
var serverTimeout time.Duration
var cacheType string
var databaseFilePath string
var boltFactory *cache.BoltFactory

func makeCache(cmd *cobra.Command) (api.Cache, error) {
	switch cacheType {
	case __MEMORY_CACHE_TYPE:
		return makeMemoryCache()
	case __BOLT_CACHE_TYPE:
		return makeBoltCache()
	default:
		err := fmt.Errorf("Unknown cache: '%s'", cacheType)
		cmd.Help()
		die(err)
	}

	return nil, fmt.Errorf("Bug in makeCache")
}

func makeBoltCache() (api.Cache, error) {
	factory := getBoltFactoryInstance()
	return factory.MakeCache()
}

func makeMemoryCache() (api.Cache, error) {
	memCache := cache.MakeResidentMemoryCache(memoryBufferLength, memoryBufferLength)
	return memCache, nil
}

func makeMemoryImage() (api.MemoryImage, error) {
	factory := getBoltFactoryInstance()
	return factory.MakeMemoryImage()
}

func getBoltFactoryInstance() *cache.BoltFactory {
	if boltFactory == nil {
		options := cache.BoltOptions{
			FilePath: databaseFilePath,
			Mode:     0600,
		}
		factory, err := cache.MakeBoltCacheFactory(options)

		if err != nil {
			die(err)
		}

		boltFactory = &factory
	}

	return boltFactory
}

func shutdownOnTrap(godless *lib.Godless) {
	onTrap(func(signal os.Signal) {
		log.Warn("Caught signal: %s", signal.String())
		shutdown(godless)
	})
}

func onTrap(handler func(signal os.Signal)) {
	sigch := make(chan os.Signal, 1)
	signal.Notify(sigch, os.Interrupt, os.Kill)
	sig := <-sigch
	handler(sig)
}

func makePriorityQueue(cmd *cobra.Command) api.RequestPriorityQueue {
	return cache.MakeResidentBufferQueue(apiQueueLength)
}

func shutdown(godless *lib.Godless) {
	godless.Shutdown()
	os.Exit(0)
}

func init() {
	storeCmd.AddCommand(serveCmd)

	defaultLimit := runtime.NumCPU()
	serveCmd.PersistentFlags().StringVar(&addr, "address", __DEFAULT_LISTEN_ADDR, "Listen address for server")
	serveCmd.PersistentFlags().DurationVar(&interval, "synctime", __DEFAULT_REPLICATION_INTERVAL, "Interval between peer replications")
	serveCmd.PersistentFlags().DurationVar(&pulse, "pulse", __DEFAULT_PULSE, "Interval between writes to IPFS")
	serveCmd.PersistentFlags().BoolVar(&earlyConnect, "early", __DEFAULT_EARLY_CONNECTION, "Early check on IPFS API access")
	serveCmd.PersistentFlags().IntVar(&apiQueryLimit, "limit", defaultLimit, "Number of simulataneous queries run by the API. limit < 0 for no restrictions.")
	serveCmd.PersistentFlags().BoolVar(&publicServer, "public", __DEFAULT_SERVER_PUBLIC_STATUS, "Don't limit pubsub updates to the public key list")
	serveCmd.PersistentFlags().DurationVar(&serverTimeout, "timeout", __DEFAULT_SERVER_TIMEOUT, "Timeout for serverside HTTP queries")
	serveCmd.PersistentFlags().IntVar(&apiQueueLength, "qlength", __DEFAULT_QUEUE_LENGTH, "API Priority queue length")
	serveCmd.PersistentFlags().StringVar(&cacheType, "cache", __DEFAULT_CACHE_TYPE, "Cache type (disk|memory)")
	serveCmd.PersistentFlags().IntVar(&memoryBufferLength, "buffer", __DEFAULT_MEMORY_BUFFER_LENGTH, "Buffer length if using memory cache")
	serveCmd.PersistentFlags().StringVar(&databaseFilePath, "dbpath", __DEFAULT_BOLT_DB_PATH, "Embedded database file path")
}

const __MEMORY_CACHE_TYPE = "memory"
const __BOLT_CACHE_TYPE = "disk"

const __DEFAULT_BOLT_DB_PATH = "godless.bolt"
const __DEFAULT_EARLY_CONNECTION = false
const __DEFAULT_SERVER_PUBLIC_STATUS = false
const __DEFAULT_CACHE_TYPE = __MEMORY_CACHE_TYPE
const __DEFAULT_LISTEN_ADDR = "localhost:8085"
const __DEFAULT_SERVER_TIMEOUT = time.Minute * 10
const __DEFAULT_QUEUE_LENGTH = 4096
const __DEFAULT_PULSE = time.Second * 10
const __DEFAULT_REPLICATION_INTERVAL = time.Minute
const __DEFAULT_MEMORY_BUFFER_LENGTH = -1
<commit_msg>Disk cache is default<commit_after>// Copyright © 2017 Johnny Morrice <john@functorama.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package cmd

import (
	"fmt"
	"os"
	"os/signal"
	"runtime"
	"time"

	lib "github.com/johnny-morrice/godless"
	"github.com/johnny-morrice/godless/api"
	"github.com/johnny-morrice/godless/cache"
	"github.com/johnny-morrice/godless/internal/http"
	"github.com/johnny-morrice/godless/log"
	"github.com/spf13/cobra"
)

// serveCmd represents the serve command
var serveCmd = &cobra.Command{
	Use:   "server",
	Short: "Run a Godless server",
	Long:  `A godless server listens to queries over HTTP.`,
	Run: func(cmd *cobra.Command, args []string) {
		readKeysFromViper()
		serve(cmd)
	},
}

func serve(cmd *cobra.Command) {
	client := http.DefaultBackendClient()
	client.Timeout = serverTimeout

	queue := makePriorityQueue(cmd)
	memimg, err := makeMemoryImage()
	cache, err := makeCache(cmd)

	if err != nil {
		die(err)
	}

	options := lib.Options{
		IpfsServiceUrl:    ipfsService,
		WebServiceAddr:    addr,
		IndexHash:         hash,
		FailEarly:         earlyConnect,
		ReplicateInterval: interval,
		Topics:            topics,
		APIQueryLimit:     apiQueryLimit,
		KeyStore:          keyStore,
		PublicServer:      publicServer,
		IpfsClient:        client,
		Pulse:             pulse,
		PriorityQueue:     queue,
		Cache:             cache,
		MemoryImage:       memimg,
	}

	godless, err := lib.New(options)

	if err != nil {
		die(err)
	}

	shutdownOnTrap(godless)

	for runError := range godless.Errors() {
		log.Error("%s", runError.Error())
	}

	defer shutdown(godless)
}

var addr string
var interval time.Duration
var pulse time.Duration
var earlyConnect bool
var apiQueryLimit int
var apiQueueLength int
var memoryBufferLength int
var publicServer bool
var serverTimeout time.Duration
var cacheType string
var databaseFilePath string
var boltFactory *cache.BoltFactory

func makeCache(cmd *cobra.Command) (api.Cache, error) {
	switch cacheType {
	case __MEMORY_CACHE_TYPE:
		return makeMemoryCache()
	case __BOLT_CACHE_TYPE:
		return makeBoltCache()
	default:
		err := fmt.Errorf("Unknown cache: '%s'", cacheType)
		cmd.Help()
		die(err)
	}

	return nil, fmt.Errorf("Bug in makeCache")
}

func makeBoltCache() (api.Cache, error) {
	factory := getBoltFactoryInstance()
	return factory.MakeCache()
}

func makeMemoryCache() (api.Cache, error) {
	memCache := cache.MakeResidentMemoryCache(memoryBufferLength, memoryBufferLength)
	return memCache, nil
}

func makeMemoryImage() (api.MemoryImage, error) {
	factory := getBoltFactoryInstance()
	return factory.MakeMemoryImage()
}

func getBoltFactoryInstance() *cache.BoltFactory {
	if boltFactory == nil {
		options := cache.BoltOptions{
			FilePath: databaseFilePath,
			Mode:     0600,
		}
		factory, err := cache.MakeBoltCacheFactory(options)

		if err != nil {
			die(err)
		}

		boltFactory = &factory
	}

	return boltFactory
}

func shutdownOnTrap(godless *lib.Godless) {
	onTrap(func(signal os.Signal) {
		log.Warn("Caught signal: %s", signal.String())
		shutdown(godless)
	})
}

func onTrap(handler func(signal os.Signal)) {
	sigch := make(chan os.Signal, 1)
	signal.Notify(sigch, os.Interrupt, os.Kill)
	sig := <-sigch
	handler(sig)
}

func makePriorityQueue(cmd *cobra.Command) api.RequestPriorityQueue {
	return cache.MakeResidentBufferQueue(apiQueueLength)
}

func shutdown(godless *lib.Godless) {
	godless.Shutdown()
	os.Exit(0)
}

func init() {
	storeCmd.AddCommand(serveCmd)

	defaultLimit := runtime.NumCPU()
	serveCmd.PersistentFlags().StringVar(&addr, "address", __DEFAULT_LISTEN_ADDR, "Listen address for server")
	serveCmd.PersistentFlags().DurationVar(&interval, "synctime", __DEFAULT_REPLICATION_INTERVAL, "Interval between peer replications")
	serveCmd.PersistentFlags().DurationVar(&pulse, "pulse", __DEFAULT_PULSE, "Interval between writes to IPFS")
	serveCmd.PersistentFlags().BoolVar(&earlyConnect, "early", __DEFAULT_EARLY_CONNECTION, "Early check on IPFS API access")
	serveCmd.PersistentFlags().IntVar(&apiQueryLimit, "limit", defaultLimit, "Number of simulataneous queries run by the API. limit < 0 for no restrictions.")
	serveCmd.PersistentFlags().BoolVar(&publicServer, "public", __DEFAULT_SERVER_PUBLIC_STATUS, "Don't limit pubsub updates to the public key list")
	serveCmd.PersistentFlags().DurationVar(&serverTimeout, "timeout", __DEFAULT_SERVER_TIMEOUT, "Timeout for serverside HTTP queries")
	serveCmd.PersistentFlags().IntVar(&apiQueueLength, "qlength", __DEFAULT_QUEUE_LENGTH, "API Priority queue length")
	serveCmd.PersistentFlags().StringVar(&cacheType, "cache", __DEFAULT_CACHE_TYPE, "Cache type (disk|memory)")
	serveCmd.PersistentFlags().IntVar(&memoryBufferLength, "buffer", __DEFAULT_MEMORY_BUFFER_LENGTH, "Buffer length if using memory cache")
	serveCmd.PersistentFlags().StringVar(&databaseFilePath, "dbpath", __DEFAULT_BOLT_DB_PATH, "Embedded database file path")
}

const __MEMORY_CACHE_TYPE = "memory"
const __BOLT_CACHE_TYPE = "disk"

const __DEFAULT_BOLT_DB_PATH = "godless.bolt"
const __DEFAULT_EARLY_CONNECTION = false
const __DEFAULT_SERVER_PUBLIC_STATUS = false
const __DEFAULT_CACHE_TYPE = __BOLT_CACHE_TYPE
const __DEFAULT_LISTEN_ADDR = "localhost:8085"
const __DEFAULT_SERVER_TIMEOUT = time.Minute * 10
const __DEFAULT_QUEUE_LENGTH = 4096
const __DEFAULT_PULSE = time.Second * 10
const __DEFAULT_REPLICATION_INTERVAL = time.Minute
const __DEFAULT_MEMORY_BUFFER_LENGTH = -1
<|endoftext|>


<commit_before>package gopherSh

import (
	"bytes"
	"context"
	"errors"
	"io/ioutil"
	"os"
	"strings"

	"github.com/yuin/gopher-lua"
	"github.com/zetamatta/nyagos/alias"
	"github.com/zetamatta/nyagos/shell"
)

type LuaBinaryChank struct {
	Chank *lua.LFunction
}

func (this LuaBinaryChank) String() string {
	return this.Chank.String()
}

func (this *LuaBinaryChank) Call(ctx context.Context, cmd *shell.Cmd) (int, error) {
	luawrapper, ok := cmd.Tag().(*luaWrapper)
	if !ok {
		return 255, errors.New("LuaBinaryChank.Call: Lua instance not found")
	}
	L := luawrapper.Lua
	ctx = context.WithValue(ctx, luaKey, L)
	L.Push(this.Chank)

	table := L.NewTable()
	for i, arg1 := range cmd.Args() {
		L.SetTable(table, lua.LNumber(i), lua.LString(arg1))
	}
	rawargs := L.NewTable()
	for i, arg1 := range cmd.RawArgs() {
		L.SetTable(rawargs, lua.LNumber(i), lua.LString(arg1))
	}
	L.SetField(table, "rawargs", rawargs)
	L.Push(table)

	return 1, callLua(ctx, &cmd.Shell, 1, 0)
}

func cmdSetAlias(L Lua) int {
	key := strings.ToLower(L.ToString(-2))
	switch L.Get(-1).Type() {
	case lua.LTString:
		alias.Table[key] = alias.New(L.ToString(-1))
	case lua.LTFunction:
		alias.Table[key] = &LuaBinaryChank{Chank: L.ToFunction(-1)}
	case lua.LTNil:
		delete(alias.Table, key)
	}
	L.Push(lua.LTrue)
	return 1
}

func cmdGetAlias(L Lua) int {
	value, ok := alias.Table[strings.ToLower(L.ToString(-1))]
	if !ok {
		L.Push(lua.LNil)
		return 1
	}
	switch v := value.(type) {
	case *LuaBinaryChank:
		L.Push(v.Chank)
	default:
		L.Push(lua.LString(v.String()))
	}
	return 1
}

func cmdExec(L Lua) int {
	errorlevel := 0
	var err error
	table, ok := L.Get(1).(*lua.LTable)
	if ok {
		n := table.Len()
		args := make([]string, 0, n)
		for i := 1; i <= n; i++ {
			arg1 := L.GetTable(table, lua.LNumber(i)).String()
			args = append(args, arg1)
		}
		ctx, sh := getRegInt(L)
		if sh == nil {
			println("main/lua_cmd.go: cmdExec: not found interpreter object")
			sh = shell.New()
			newL, err := Clone(L)
			if err == nil && newL != nil {
				sh.SetTag(&luaWrapper{Lua: newL})
			}
			defer sh.Close()
		}
		cmd := sh.Command()
		defer cmd.Close()
		cmd.SetArgs(args)
		errorlevel, err = cmd.Spawnvp(ctx)
	} else {
		statement, ok := L.Get(1).(lua.LString)
		if !ok {
			return lerror(L, "nyagos.exec: the 1st argument is not a string")
		}
		ctx, sh := getRegInt(L)
		if ctx == nil {
			return lerror(L, "nyagos.exec: context not found")
		}
		if sh == nil {
			println("nyagos.exec: warning shell is not found.")
			sh = shell.New()
			sh.SetTag(&luaWrapper{L})
			defer sh.Close()
		}
		errorlevel, err = sh.Interpret(ctx, string(statement))
	}
	L.Push(lua.LNumber(errorlevel))
	if err != nil {
		L.Push(lua.LString(err.Error()))
	} else {
		L.Push(lua.LNil)
	}
	return 2
}

func cmdEval(L Lua) int {
	statement, ok := L.Get(1).(lua.LString)
	if !ok {
		L.Push(lua.LNil)
		L.Push(lua.LString("nyagos.eval: an argument is not string"))
		return 2
	}
	r, w, err := os.Pipe()
	if err != nil {
		L.Push(lua.LNil)
		L.Push(lua.LString(err.Error()))
		return 2
	}
	go func(statement string, w *os.File) {
		ctx, sh := getRegInt(L)
		if ctx == nil {
			ctx = context.Background()
			println("cmdEval: context not found.")
		}
		if sh == nil {
			sh = shell.New()
			println("cmdEval: shell not found.")
			defer sh.Close()
		}
		sh.SetTag(&luaWrapper{L})
		saveOut := sh.Stdout
		sh.Stdout = w
		sh.Interpret(ctx, statement)
		sh.Stdout = saveOut
		w.Close()
	}(string(statement), w)

	result, err := ioutil.ReadAll(r)
	r.Close()
	if err == nil {
		L.Push(lua.LString(string(bytes.Trim(result, "\r\n\t "))))
	} else {
		L.Push(lua.LNil)
	}
	return 1
}
<commit_msg>Fix: alias function's return value was not evaluted.<commit_after>package gopherSh

import (
	"bytes"
	"context"
	"errors"
	"io/ioutil"
	"os"
	"strings"

	"github.com/yuin/gopher-lua"
	"github.com/zetamatta/nyagos/alias"
	"github.com/zetamatta/nyagos/shell"
)

type LuaBinaryChank struct {
	Chank *lua.LFunction
}

func (this LuaBinaryChank) String() string {
	return this.Chank.String()
}

func (this *LuaBinaryChank) Call(ctx context.Context, cmd *shell.Cmd) (int, error) {
	luawrapper, ok := cmd.Tag().(*luaWrapper)
	if !ok {
		return 255, errors.New("LuaBinaryChank.Call: Lua instance not found")
	}
	L := luawrapper.Lua
	ctx = context.WithValue(ctx, luaKey, L)
	L.Push(this.Chank)

	table := L.NewTable()
	for i, arg1 := range cmd.Args() {
		L.SetTable(table, lua.LNumber(i), lua.LString(arg1))
	}
	rawargs := L.NewTable()
	for i, arg1 := range cmd.RawArgs() {
		L.SetTable(rawargs, lua.LNumber(i), lua.LString(arg1))
	}
	L.SetField(table, "rawargs", rawargs)
	L.Push(table)

	errorlevel := 0
	err := callLua(ctx, &cmd.Shell, 1, 1)
	if err == nil {
		switch val := L.Get(-1).(type) {
		case *lua.LTable:
			size := val.Len()
			newargs := make([]string, size)
			for i := 0; i < size; i++ {
				newargs[i] = L.GetTable(val, lua.LNumber(i+1)).String()
			}
			sh := cmd.Command()
			sh.SetArgs(newargs)
			errorlevel, err = sh.Spawnvp(ctx)
			sh.Close()
		case lua.LNumber:
			errorlevel = int(val)
		case lua.LString:
			errorlevel, err = cmd.Interpret(ctx, string(val))
		}
	}
	L.Pop(1)
	return errorlevel, err
}

func cmdSetAlias(L Lua) int {
	key := strings.ToLower(L.ToString(-2))
	switch L.Get(-1).Type() {
	case lua.LTString:
		alias.Table[key] = alias.New(L.ToString(-1))
	case lua.LTFunction:
		alias.Table[key] = &LuaBinaryChank{Chank: L.ToFunction(-1)}
	case lua.LTNil:
		delete(alias.Table, key)
	}
	L.Push(lua.LTrue)
	return 1
}

func cmdGetAlias(L Lua) int {
	value, ok := alias.Table[strings.ToLower(L.ToString(-1))]
	if !ok {
		L.Push(lua.LNil)
		return 1
	}
	switch v := value.(type) {
	case *LuaBinaryChank:
		L.Push(v.Chank)
	default:
		L.Push(lua.LString(v.String()))
	}
	return 1
}

func cmdExec(L Lua) int {
	errorlevel := 0
	var err error
	table, ok := L.Get(1).(*lua.LTable)
	if ok {
		n := table.Len()
		args := make([]string, 0, n)
		for i := 1; i <= n; i++ {
			arg1 := L.GetTable(table, lua.LNumber(i)).String()
			args = append(args, arg1)
		}
		ctx, sh := getRegInt(L)
		if sh == nil {
			println("main/lua_cmd.go: cmdExec: not found interpreter object")
			sh = shell.New()
			newL, err := Clone(L)
			if err == nil && newL != nil {
				sh.SetTag(&luaWrapper{Lua: newL})
			}
			defer sh.Close()
		}
		cmd := sh.Command()
		defer cmd.Close()
		cmd.SetArgs(args)
		errorlevel, err = cmd.Spawnvp(ctx)
	} else {
		statement, ok := L.Get(1).(lua.LString)
		if !ok {
			return lerror(L, "nyagos.exec: the 1st argument is not a string")
		}
		ctx, sh := getRegInt(L)
		if ctx == nil {
			return lerror(L, "nyagos.exec: context not found")
		}
		if sh == nil {
			println("nyagos.exec: warning shell is not found.")
			sh = shell.New()
			sh.SetTag(&luaWrapper{L})
			defer sh.Close()
		}
		errorlevel, err = sh.Interpret(ctx, string(statement))
	}
	L.Push(lua.LNumber(errorlevel))
	if err != nil {
		L.Push(lua.LString(err.Error()))
	} else {
		L.Push(lua.LNil)
	}
	return 2
}

func cmdEval(L Lua) int {
	statement, ok := L.Get(1).(lua.LString)
	if !ok {
		L.Push(lua.LNil)
		L.Push(lua.LString("nyagos.eval: an argument is not string"))
		return 2
	}
	r, w, err := os.Pipe()
	if err != nil {
		L.Push(lua.LNil)
		L.Push(lua.LString(err.Error()))
		return 2
	}
	go func(statement string, w *os.File) {
		ctx, sh := getRegInt(L)
		if ctx == nil {
			ctx = context.Background()
			println("cmdEval: context not found.")
		}
		if sh == nil {
			sh = shell.New()
			println("cmdEval: shell not found.")
			defer sh.Close()
		}
		sh.SetTag(&luaWrapper{L})
		saveOut := sh.Stdout
		sh.Stdout = w
		sh.Interpret(ctx, statement)
		sh.Stdout = saveOut
		w.Close()
	}(string(statement), w)

	result, err := ioutil.ReadAll(r)
	r.Close()
	if err == nil {
		L.Push(lua.LString(string(bytes.Trim(result, "\r\n\t "))))
	} else {
		L.Push(lua.LNil)
	}
	return 1
}
<|endoftext|>