<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 obj

import "log"

func addvarint(d *Pcdata, v uint32) {
	for ; v >= 0x80; v >>= 7 {
		d.P = append(d.P, uint8(v|0x80))
	}
	d.P = append(d.P, uint8(v))
}

// funcpctab writes to dst a pc-value table mapping the code in func to the values
// returned by valfunc parameterized by arg. The invocation of valfunc to update the
// current value is, for each p,
//
//	val = valfunc(func, val, p, 0, arg);
//	record val as value at p->pc;
//	val = valfunc(func, val, p, 1, arg);
//
// where func is the function, val is the current value, p is the instruction being
// considered, and arg can be used to further parameterize valfunc.
func funcpctab(ctxt *Link, dst *Pcdata, func_ *LSym, desc string, valfunc func(*Link, *LSym, int32, *Prog, int32, interface{}) int32, arg interface{}) {
	dbg := desc == ctxt.Debugpcln

	dst.P = dst.P[:0]

	if dbg {
		ctxt.Logf("funcpctab %s [valfunc=%s]\n", func_.Name, desc)
	}

	val := int32(-1)
	oldval := val
	if func_.Text == nil {
		return
	}

	pc := func_.Text.Pc

	if dbg {
		ctxt.Logf("%6x %6d %v\n", uint64(pc), val, func_.Text)
	}

	started := int32(0)
	var delta uint32
	for p := func_.Text; p != nil; p = p.Link {
		// Update val. If it's not changing, keep going.
		val = valfunc(ctxt, func_, val, p, 0, arg)

		if val == oldval && started != 0 {
			val = valfunc(ctxt, func_, val, p, 1, arg)
			if dbg {
				ctxt.Logf("%6x %6s %v\n", uint64(p.Pc), "", p)
			}
			continue
		}

		// If the pc of the next instruction is the same as the
		// pc of this instruction, this instruction is not a real
		// instruction. Keep going, so that we only emit a delta
		// for a true instruction boundary in the program.
		if p.Link != nil && p.Link.Pc == p.Pc {
			val = valfunc(ctxt, func_, val, p, 1, arg)
			if dbg {
				ctxt.Logf("%6x %6s %v\n", uint64(p.Pc), "", p)
			}
			continue
		}

		// The table is a sequence of (value, pc) pairs, where each
		// pair states that the given value is in effect from the current position
		// up to the given pc, which becomes the new current position.
		// To generate the table as we scan over the program instructions,
		// we emit a "(value" when pc == func->value, and then
		// each time we observe a change in value we emit ", pc) (value".
		// When the scan is over, we emit the closing ", pc)".
		//
		// The table is delta-encoded. The value deltas are signed and
		// transmitted in zig-zag form, where a complement bit is placed in bit 0,
		// and the pc deltas are unsigned. Both kinds of deltas are sent
		// as variable-length little-endian base-128 integers,
		// where the 0x80 bit indicates that the integer continues.

		if dbg {
			ctxt.Logf("%6x %6d %v\n", uint64(p.Pc), val, p)
		}

		if started != 0 {
			addvarint(dst, uint32((p.Pc-pc)/int64(ctxt.Arch.MinLC)))
			pc = p.Pc
		}

		delta = uint32(val) - uint32(oldval)
		if delta>>31 != 0 {
			delta = 1 | ^(delta << 1)
		} else {
			delta <<= 1
		}
		addvarint(dst, delta)
		oldval = val
		started = 1
		val = valfunc(ctxt, func_, val, p, 1, arg)
	}

	if started != 0 {
		if dbg {
			ctxt.Logf("%6x done\n", uint64(func_.Text.Pc+func_.Size))
		}
		addvarint(dst, uint32((func_.Size-pc)/int64(ctxt.Arch.MinLC)))
		addvarint(dst, 0) // terminator
	}

	if dbg {
		ctxt.Logf("wrote %d bytes to %p\n", len(dst.P), dst)
		for i := 0; i < len(dst.P); i++ {
			ctxt.Logf(" %02x", dst.P[i])
		}
		ctxt.Logf("\n")
	}
}

// pctofileline computes either the file number (arg == 0)
// or the line number (arg == 1) to use at p.
// Because p.Pos applies to p, phase == 0 (before p)
// takes care of the update.
func pctofileline(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg interface{}) int32 {
	if p.As == ATEXT || p.As == ANOP || p.Pos.Line() == 0 || phase == 1 {
		return oldval
	}
	f, l := linkgetlineFromPos(ctxt, p.Pos)
	if f == nil {
		//	print("getline failed for %s %v\n", ctxt->cursym->name, p);
		return oldval
	}

	if arg == nil {
		return l
	}
	pcln := arg.(*Pcln)

	if f == pcln.Lastfile {
		return int32(pcln.Lastindex)
	}

	for i, file := range pcln.File {
		if file == f {
			pcln.Lastfile = f
			pcln.Lastindex = i
			return int32(i)
		}
	}
	i := len(pcln.File)
	pcln.File = append(pcln.File, f)
	pcln.Lastfile = f
	pcln.Lastindex = i
	return int32(i)
}

// pcinlineState holds the state used to create a function's inlining
// tree and the PC-value table that maps PCs to nodes in that tree.
type pcinlineState struct {
	globalToLocal map[int]int
	localTree     InlTree
}

// addBranch adds a branch from the global inlining tree in ctxt to
// the function's local inlining tree, returning the index in the local tree.
func (s *pcinlineState) addBranch(ctxt *Link, globalIndex int) int {
	if globalIndex < 0 {
		return -1
	}

	localIndex, ok := s.globalToLocal[globalIndex]
	if ok {
		return localIndex
	}

	// Since tracebacks don't include column information, we could
	// use one node for multiple calls of the same function on the
	// same line (e.g., f(x) + f(y)). For now, we use one node for
	// each inlined call.
	call := ctxt.InlTree.nodes[globalIndex]
	call.Parent = s.addBranch(ctxt, call.Parent)
	localIndex = len(s.localTree.nodes)
	s.localTree.nodes = append(s.localTree.nodes, call)
	s.globalToLocal[globalIndex] = localIndex
	return localIndex
}

// pctoinline computes the index into the local inlining tree to use at p.
// If p is not the result of inlining, pctoinline returns -1. Because p.Pos
// applies to p, phase == 0 (before p) takes care of the update.
func (s *pcinlineState) pctoinline(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg interface{}) int32 {
	if phase == 1 {
		return oldval
	}

	posBase := ctxt.PosTable.Pos(p.Pos).Base()
	if posBase == nil {
		return -1
	}

	globalIndex := posBase.InliningIndex()
	if globalIndex < 0 {
		return -1
	}

	if s.globalToLocal == nil {
		s.globalToLocal = make(map[int]int)
	}

	return int32(s.addBranch(ctxt, globalIndex))
}

// pctospadj computes the sp adjustment in effect.
// It is oldval plus any adjustment made by p itself.
// The adjustment by p takes effect only after p, so we
// apply the change during phase == 1.
func pctospadj(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg interface{}) int32 {
	if oldval == -1 { // starting
		oldval = 0
	}
	if phase == 0 {
		return oldval
	}
	if oldval+p.Spadj < -10000 || oldval+p.Spadj > 1100000000 {
		ctxt.Diag("overflow in spadj: %d + %d = %d", oldval, p.Spadj, oldval+p.Spadj)
		log.Fatalf("bad code")
	}

	return oldval + p.Spadj
}

// pctopcdata computes the pcdata value in effect at p.
// A PCDATA instruction sets the value in effect at future
// non-PCDATA instructions.
// Since PCDATA instructions have no width in the final code,
// it does not matter which phase we use for the update.
func pctopcdata(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg interface{}) int32 {
	if phase == 0 || p.As != APCDATA || p.From.Offset != int64(arg.(uint32)) {
		return oldval
	}
	if int64(int32(p.To.Offset)) != p.To.Offset {
		ctxt.Diag("overflow in PCDATA instruction: %v", p)
		log.Fatalf("bad code")
	}

	return int32(p.To.Offset)
}

func linkpcln(ctxt *Link, cursym *LSym) {
	ctxt.Cursym = cursym

	pcln := &cursym.Pcln

	npcdata := 0
	nfuncdata := 0
	for p := cursym.Text; p != nil; p = p.Link {
		// Find the highest ID of any used PCDATA table. This ignores PCDATA table
		// that consist entirely of "-1", since that's the assumed default value.
		//   From.Offset is table ID
		//   To.Offset is data
		if p.As == APCDATA && p.From.Offset >= int64(npcdata) && p.To.Offset != -1 { // ignore -1 as we start at -1, if we only see -1, nothing changed
			npcdata = int(p.From.Offset + 1)
		}
		// Find the highest ID of any FUNCDATA table.
		//   From.Offset is table ID
		if p.As == AFUNCDATA && p.From.Offset >= int64(nfuncdata) {
			nfuncdata = int(p.From.Offset + 1)
		}
	}

	pcln.Pcdata = make([]Pcdata, npcdata)
	pcln.Pcdata = pcln.Pcdata[:npcdata]
	pcln.Funcdata = make([]*LSym, nfuncdata)
	pcln.Funcdataoff = make([]int64, nfuncdata)
	pcln.Funcdataoff = pcln.Funcdataoff[:nfuncdata]

	funcpctab(ctxt, &pcln.Pcsp, cursym, "pctospadj", pctospadj, nil)
	funcpctab(ctxt, &pcln.Pcfile, cursym, "pctofile", pctofileline, pcln)
	funcpctab(ctxt, &pcln.Pcline, cursym, "pctoline", pctofileline, nil)

	pcinlineState := new(pcinlineState)
	funcpctab(ctxt, &pcln.Pcinline, cursym, "pctoinline", pcinlineState.pctoinline, nil)
	pcln.InlTree = pcinlineState.localTree
	if ctxt.Debugpcln == "pctoinline" && len(pcln.InlTree.nodes) > 0 {
		ctxt.Logf("-- inlining tree for %s:\n", cursym)
		dumpInlTree(ctxt, pcln.InlTree)
		ctxt.Logf("--\n")
	}

	// tabulate which pc and func data we have.
	havepc := make([]uint32, (npcdata+31)/32)
	havefunc := make([]uint32, (nfuncdata+31)/32)
	for p := cursym.Text; p != nil; p = p.Link {
		if p.As == AFUNCDATA {
			if (havefunc[p.From.Offset/32]>>uint64(p.From.Offset%32))&1 != 0 {
				ctxt.Diag("multiple definitions for FUNCDATA $%d", p.From.Offset)
			}
			havefunc[p.From.Offset/32] |= 1 << uint64(p.From.Offset%32)
		}

		if p.As == APCDATA && p.To.Offset != -1 {
			havepc[p.From.Offset/32] |= 1 << uint64(p.From.Offset%32)
		}
	}

	// pcdata.
	for i := 0; i < npcdata; i++ {
		if (havepc[i/32]>>uint(i%32))&1 == 0 {
			continue
		}
		funcpctab(ctxt, &pcln.Pcdata[i], cursym, "pctopcdata", pctopcdata, interface{}(uint32(i)))
	}

	// funcdata
	if nfuncdata > 0 {
		var i int
		for p := cursym.Text; p != nil; p = p.Link {
			if p.As == AFUNCDATA {
				i = int(p.From.Offset)
				pcln.Funcdataoff[i] = p.To.Offset
				if p.To.Type != TYPE_CONST {
					// TODO: Dedup.
					//funcdata_bytes += p->to.sym->size;
					pcln.Funcdata[i] = p.To.Sym
				}
			}
		}
	}
}
<commit_msg>cmd/internal/obj: change started to bool<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 obj

import "log"

func addvarint(d *Pcdata, v uint32) {
	for ; v >= 0x80; v >>= 7 {
		d.P = append(d.P, uint8(v|0x80))
	}
	d.P = append(d.P, uint8(v))
}

// funcpctab writes to dst a pc-value table mapping the code in func to the values
// returned by valfunc parameterized by arg. The invocation of valfunc to update the
// current value is, for each p,
//
//	val = valfunc(func, val, p, 0, arg);
//	record val as value at p->pc;
//	val = valfunc(func, val, p, 1, arg);
//
// where func is the function, val is the current value, p is the instruction being
// considered, and arg can be used to further parameterize valfunc.
func funcpctab(ctxt *Link, dst *Pcdata, func_ *LSym, desc string, valfunc func(*Link, *LSym, int32, *Prog, int32, interface{}) int32, arg interface{}) {
	dbg := desc == ctxt.Debugpcln

	dst.P = dst.P[:0]

	if dbg {
		ctxt.Logf("funcpctab %s [valfunc=%s]\n", func_.Name, desc)
	}

	val := int32(-1)
	oldval := val
	if func_.Text == nil {
		return
	}

	pc := func_.Text.Pc

	if dbg {
		ctxt.Logf("%6x %6d %v\n", uint64(pc), val, func_.Text)
	}

	started := false
	var delta uint32
	for p := func_.Text; p != nil; p = p.Link {
		// Update val. If it's not changing, keep going.
		val = valfunc(ctxt, func_, val, p, 0, arg)

		if val == oldval && started {
			val = valfunc(ctxt, func_, val, p, 1, arg)
			if dbg {
				ctxt.Logf("%6x %6s %v\n", uint64(p.Pc), "", p)
			}
			continue
		}

		// If the pc of the next instruction is the same as the
		// pc of this instruction, this instruction is not a real
		// instruction. Keep going, so that we only emit a delta
		// for a true instruction boundary in the program.
		if p.Link != nil && p.Link.Pc == p.Pc {
			val = valfunc(ctxt, func_, val, p, 1, arg)
			if dbg {
				ctxt.Logf("%6x %6s %v\n", uint64(p.Pc), "", p)
			}
			continue
		}

		// The table is a sequence of (value, pc) pairs, where each
		// pair states that the given value is in effect from the current position
		// up to the given pc, which becomes the new current position.
		// To generate the table as we scan over the program instructions,
		// we emit a "(value" when pc == func->value, and then
		// each time we observe a change in value we emit ", pc) (value".
		// When the scan is over, we emit the closing ", pc)".
		//
		// The table is delta-encoded. The value deltas are signed and
		// transmitted in zig-zag form, where a complement bit is placed in bit 0,
		// and the pc deltas are unsigned. Both kinds of deltas are sent
		// as variable-length little-endian base-128 integers,
		// where the 0x80 bit indicates that the integer continues.

		if dbg {
			ctxt.Logf("%6x %6d %v\n", uint64(p.Pc), val, p)
		}

		if started {
			addvarint(dst, uint32((p.Pc-pc)/int64(ctxt.Arch.MinLC)))
			pc = p.Pc
		}

		delta = uint32(val) - uint32(oldval)
		if delta>>31 != 0 {
			delta = 1 | ^(delta << 1)
		} else {
			delta <<= 1
		}
		addvarint(dst, delta)
		oldval = val
		started = true
		val = valfunc(ctxt, func_, val, p, 1, arg)
	}

	if started {
		if dbg {
			ctxt.Logf("%6x done\n", uint64(func_.Text.Pc+func_.Size))
		}
		addvarint(dst, uint32((func_.Size-pc)/int64(ctxt.Arch.MinLC)))
		addvarint(dst, 0) // terminator
	}

	if dbg {
		ctxt.Logf("wrote %d bytes to %p\n", len(dst.P), dst)
		for i := 0; i < len(dst.P); i++ {
			ctxt.Logf(" %02x", dst.P[i])
		}
		ctxt.Logf("\n")
	}
}

// pctofileline computes either the file number (arg == 0)
// or the line number (arg == 1) to use at p.
// Because p.Pos applies to p, phase == 0 (before p)
// takes care of the update.
func pctofileline(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg interface{}) int32 {
	if p.As == ATEXT || p.As == ANOP || p.Pos.Line() == 0 || phase == 1 {
		return oldval
	}
	f, l := linkgetlineFromPos(ctxt, p.Pos)
	if f == nil {
		//	print("getline failed for %s %v\n", ctxt->cursym->name, p);
		return oldval
	}

	if arg == nil {
		return l
	}
	pcln := arg.(*Pcln)

	if f == pcln.Lastfile {
		return int32(pcln.Lastindex)
	}

	for i, file := range pcln.File {
		if file == f {
			pcln.Lastfile = f
			pcln.Lastindex = i
			return int32(i)
		}
	}
	i := len(pcln.File)
	pcln.File = append(pcln.File, f)
	pcln.Lastfile = f
	pcln.Lastindex = i
	return int32(i)
}

// pcinlineState holds the state used to create a function's inlining
// tree and the PC-value table that maps PCs to nodes in that tree.
type pcinlineState struct {
	globalToLocal map[int]int
	localTree     InlTree
}

// addBranch adds a branch from the global inlining tree in ctxt to
// the function's local inlining tree, returning the index in the local tree.
func (s *pcinlineState) addBranch(ctxt *Link, globalIndex int) int {
	if globalIndex < 0 {
		return -1
	}

	localIndex, ok := s.globalToLocal[globalIndex]
	if ok {
		return localIndex
	}

	// Since tracebacks don't include column information, we could
	// use one node for multiple calls of the same function on the
	// same line (e.g., f(x) + f(y)). For now, we use one node for
	// each inlined call.
	call := ctxt.InlTree.nodes[globalIndex]
	call.Parent = s.addBranch(ctxt, call.Parent)
	localIndex = len(s.localTree.nodes)
	s.localTree.nodes = append(s.localTree.nodes, call)
	s.globalToLocal[globalIndex] = localIndex
	return localIndex
}

// pctoinline computes the index into the local inlining tree to use at p.
// If p is not the result of inlining, pctoinline returns -1. Because p.Pos
// applies to p, phase == 0 (before p) takes care of the update.
func (s *pcinlineState) pctoinline(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg interface{}) int32 {
	if phase == 1 {
		return oldval
	}

	posBase := ctxt.PosTable.Pos(p.Pos).Base()
	if posBase == nil {
		return -1
	}

	globalIndex := posBase.InliningIndex()
	if globalIndex < 0 {
		return -1
	}

	if s.globalToLocal == nil {
		s.globalToLocal = make(map[int]int)
	}

	return int32(s.addBranch(ctxt, globalIndex))
}

// pctospadj computes the sp adjustment in effect.
// It is oldval plus any adjustment made by p itself.
// The adjustment by p takes effect only after p, so we
// apply the change during phase == 1.
func pctospadj(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg interface{}) int32 {
	if oldval == -1 { // starting
		oldval = 0
	}
	if phase == 0 {
		return oldval
	}
	if oldval+p.Spadj < -10000 || oldval+p.Spadj > 1100000000 {
		ctxt.Diag("overflow in spadj: %d + %d = %d", oldval, p.Spadj, oldval+p.Spadj)
		log.Fatalf("bad code")
	}

	return oldval + p.Spadj
}

// pctopcdata computes the pcdata value in effect at p.
// A PCDATA instruction sets the value in effect at future
// non-PCDATA instructions.
// Since PCDATA instructions have no width in the final code,
// it does not matter which phase we use for the update.
func pctopcdata(ctxt *Link, sym *LSym, oldval int32, p *Prog, phase int32, arg interface{}) int32 {
	if phase == 0 || p.As != APCDATA || p.From.Offset != int64(arg.(uint32)) {
		return oldval
	}
	if int64(int32(p.To.Offset)) != p.To.Offset {
		ctxt.Diag("overflow in PCDATA instruction: %v", p)
		log.Fatalf("bad code")
	}

	return int32(p.To.Offset)
}

func linkpcln(ctxt *Link, cursym *LSym) {
	ctxt.Cursym = cursym

	pcln := &cursym.Pcln

	npcdata := 0
	nfuncdata := 0
	for p := cursym.Text; p != nil; p = p.Link {
		// Find the highest ID of any used PCDATA table. This ignores PCDATA table
		// that consist entirely of "-1", since that's the assumed default value.
		//   From.Offset is table ID
		//   To.Offset is data
		if p.As == APCDATA && p.From.Offset >= int64(npcdata) && p.To.Offset != -1 { // ignore -1 as we start at -1, if we only see -1, nothing changed
			npcdata = int(p.From.Offset + 1)
		}
		// Find the highest ID of any FUNCDATA table.
		//   From.Offset is table ID
		if p.As == AFUNCDATA && p.From.Offset >= int64(nfuncdata) {
			nfuncdata = int(p.From.Offset + 1)
		}
	}

	pcln.Pcdata = make([]Pcdata, npcdata)
	pcln.Pcdata = pcln.Pcdata[:npcdata]
	pcln.Funcdata = make([]*LSym, nfuncdata)
	pcln.Funcdataoff = make([]int64, nfuncdata)
	pcln.Funcdataoff = pcln.Funcdataoff[:nfuncdata]

	funcpctab(ctxt, &pcln.Pcsp, cursym, "pctospadj", pctospadj, nil)
	funcpctab(ctxt, &pcln.Pcfile, cursym, "pctofile", pctofileline, pcln)
	funcpctab(ctxt, &pcln.Pcline, cursym, "pctoline", pctofileline, nil)

	pcinlineState := new(pcinlineState)
	funcpctab(ctxt, &pcln.Pcinline, cursym, "pctoinline", pcinlineState.pctoinline, nil)
	pcln.InlTree = pcinlineState.localTree
	if ctxt.Debugpcln == "pctoinline" && len(pcln.InlTree.nodes) > 0 {
		ctxt.Logf("-- inlining tree for %s:\n", cursym)
		dumpInlTree(ctxt, pcln.InlTree)
		ctxt.Logf("--\n")
	}

	// tabulate which pc and func data we have.
	havepc := make([]uint32, (npcdata+31)/32)
	havefunc := make([]uint32, (nfuncdata+31)/32)
	for p := cursym.Text; p != nil; p = p.Link {
		if p.As == AFUNCDATA {
			if (havefunc[p.From.Offset/32]>>uint64(p.From.Offset%32))&1 != 0 {
				ctxt.Diag("multiple definitions for FUNCDATA $%d", p.From.Offset)
			}
			havefunc[p.From.Offset/32] |= 1 << uint64(p.From.Offset%32)
		}

		if p.As == APCDATA && p.To.Offset != -1 {
			havepc[p.From.Offset/32] |= 1 << uint64(p.From.Offset%32)
		}
	}

	// pcdata.
	for i := 0; i < npcdata; i++ {
		if (havepc[i/32]>>uint(i%32))&1 == 0 {
			continue
		}
		funcpctab(ctxt, &pcln.Pcdata[i], cursym, "pctopcdata", pctopcdata, interface{}(uint32(i)))
	}

	// funcdata
	if nfuncdata > 0 {
		var i int
		for p := cursym.Text; p != nil; p = p.Link {
			if p.As == AFUNCDATA {
				i = int(p.From.Offset)
				pcln.Funcdataoff[i] = p.To.Offset
				if p.To.Type != TYPE_CONST {
					// TODO: Dedup.
					//funcdata_bytes += p->to.sym->size;
					pcln.Funcdata[i] = p.To.Sym
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"restic"
	"restic/debug"
	"restic/errors"
	"restic/index"
	"restic/repository"
	"time"

	"github.com/spf13/cobra"

	"golang.org/x/crypto/ssh/terminal"
)

var cmdPrune = &cobra.Command{
	Use:   "prune [flags]",
	Short: "remove unneeded data from the repository",
	Long: `
The "prune" command checks the repository and removes data that is not
referenced and therefore not needed any more.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runPrune(globalOptions)
	},
}

func init() {
	cmdRoot.AddCommand(cmdPrune)
}

// newProgressMax returns a progress that counts blobs.
func newProgressMax(show bool, max uint64, description string) *restic.Progress {
	if !show {
		return nil
	}

	p := restic.NewProgress()

	p.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
		status := fmt.Sprintf("[%s] %s  %d / %d %s",
			formatDuration(d),
			formatPercent(s.Blobs, max),
			s.Blobs, max, description)

		w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
		if err == nil {
			if len(status) > w {
				max := w - len(status) - 4
				status = status[:max] + "... "
			}
		}

		PrintProgress("%s", status)
	}

	p.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
		fmt.Printf("\n")
	}

	return p
}

func runPrune(gopts GlobalOptions) error {
	repo, err := OpenRepository(gopts)
	if err != nil {
		return err
	}

	lock, err := lockRepoExclusive(repo)
	defer unlockRepo(lock)
	if err != nil {
		return err
	}

	return pruneRepository(gopts, repo)
}

func pruneRepository(gopts GlobalOptions, repo restic.Repository) error {
	err := repo.LoadIndex()
	if err != nil {
		return err
	}

	done := make(chan struct{})
	defer close(done)

	var stats struct {
		blobs     int
		packs     int
		snapshots int
		bytes     int64
	}

	Verbosef("counting files in repo\n")
	for _ = range repo.List(restic.DataFile, done) {
		stats.packs++
	}

	Verbosef("building new index for repo\n")

	bar := newProgressMax(!gopts.Quiet, uint64(stats.packs), "packs")
	idx, err := index.New(repo, bar)
	if err != nil {
		return err
	}

	blobs := 0
	for _, pack := range idx.Packs {
		stats.bytes += pack.Size
		blobs += len(pack.Entries)
	}
	Verbosef("repository contains %v packs (%v blobs) with %v bytes\n",
		len(idx.Packs), blobs, formatBytes(uint64(stats.bytes)))

	blobCount := make(map[restic.BlobHandle]int)
	duplicateBlobs := 0
	duplicateBytes := 0

	// find duplicate blobs
	for _, p := range idx.Packs {
		for _, entry := range p.Entries {
			stats.blobs++
			h := restic.BlobHandle{ID: entry.ID, Type: entry.Type}
			blobCount[h]++

			if blobCount[h] > 1 {
				duplicateBlobs++
				duplicateBytes += int(entry.Length)
			}
		}
	}

	Verbosef("processed %d blobs: %d duplicate blobs, %v duplicate\n",
		stats.blobs, duplicateBlobs, formatBytes(uint64(duplicateBytes)))
	Verbosef("load all snapshots\n")

	// find referenced blobs
	snapshots, err := restic.LoadAllSnapshots(repo)
	if err != nil {
		return err
	}

	stats.snapshots = len(snapshots)

	Verbosef("find data that is still in use for %d snapshots\n", stats.snapshots)

	usedBlobs := restic.NewBlobSet()
	seenBlobs := restic.NewBlobSet()

	bar = newProgressMax(!gopts.Quiet, uint64(len(snapshots)), "snapshots")
	bar.Start()
	for _, sn := range snapshots {
		debug.Log("process snapshot %v", sn.ID().Str())

		err = restic.FindUsedBlobs(repo, *sn.Tree, usedBlobs, seenBlobs)
		if err != nil {
			return err
		}

		debug.Log("found %v blobs for snapshot %v", sn.ID().Str())
		bar.Report(restic.Stat{Blobs: 1})
	}
	bar.Done()

	Verbosef("found %d of %d data blobs still in use, removing %d blobs\n",
		len(usedBlobs), stats.blobs, stats.blobs-len(usedBlobs))

	// find packs that need a rewrite
	rewritePacks := restic.NewIDSet()
	for _, pack := range idx.Packs {
		for _, blob := range pack.Entries {
			h := restic.BlobHandle{ID: blob.ID, Type: blob.Type}
			if !usedBlobs.Has(h) {
				rewritePacks.Insert(pack.ID)
				continue
			}

			if blobCount[h] > 1 {
				rewritePacks.Insert(pack.ID)
			}
		}
	}

	removeBytes := 0

	// find packs that are unneeded
	removePacks := restic.NewIDSet()
	for packID, p := range idx.Packs {

		hasActiveBlob := false
		for _, blob := range p.Entries {
			h := restic.BlobHandle{ID: blob.ID, Type: blob.Type}
			if usedBlobs.Has(h) {
				hasActiveBlob = true
				continue
			}

			removeBytes += int(blob.Length)
		}

		if hasActiveBlob {
			continue
		}

		removePacks.Insert(packID)

		if !rewritePacks.Has(packID) {
			return errors.Fatalf("pack %v is unneeded, but not contained in rewritePacks", packID.Str())
		}

		rewritePacks.Delete(packID)
	}

	Verbosef("will delete %d packs and rewrite %d packs, this frees %s\n",
		len(removePacks), len(rewritePacks), formatBytes(uint64(removeBytes)))

	if len(rewritePacks) != 0 {
		bar = newProgressMax(!gopts.Quiet, uint64(len(rewritePacks)), "packs rewriten")
		bar.Start()
		err = repository.Repack(repo, rewritePacks, usedBlobs, bar)
		if err != nil {
			return err
		}
		bar.Done()
	}

	if len(removePacks) != 0 {
		bar = newProgressMax(!gopts.Quiet, uint64(len(removePacks)), "packs deleted")
		bar.Start()
		for packID := range removePacks {
			h := restic.Handle{Type: restic.DataFile, Name: packID.String()}
			err = repo.Backend().Remove(h)
			if err != nil {
				Warnf("unable to remove file %v from the repository\n", packID.Str())
			}
			bar.Report(restic.Stat{Blobs: 1})
		}
		bar.Done()
	}

	Verbosef("creating new index\n")

	stats.packs = 0
	for _ = range repo.List(restic.DataFile, done) {
		stats.packs++
	}
	bar = newProgressMax(!gopts.Quiet, uint64(stats.packs), "packs")
	idx, err = index.New(repo, bar)
	if err != nil {
		return err
	}

	var supersedes restic.IDs
	for idxID := range repo.List(restic.IndexFile, done) {
		h := restic.Handle{Type: restic.IndexFile, Name: idxID.String()}
		err := repo.Backend().Remove(h)
		if err != nil {
			fmt.Fprintf(os.Stderr, "unable to remove index %v: %v\n", idxID.Str(), err)
		}

		supersedes = append(supersedes, idxID)
	}

	id, err := idx.Save(repo, supersedes)
	if err != nil {
		return err
	}
	Verbosef("saved new index as %v\n", id.Str())

	Verbosef("done\n")
	return nil
}
<commit_msg>English typo: rewriten > rewritten.<commit_after>package main

import (
	"fmt"
	"os"
	"restic"
	"restic/debug"
	"restic/errors"
	"restic/index"
	"restic/repository"
	"time"

	"github.com/spf13/cobra"

	"golang.org/x/crypto/ssh/terminal"
)

var cmdPrune = &cobra.Command{
	Use:   "prune [flags]",
	Short: "remove unneeded data from the repository",
	Long: `
The "prune" command checks the repository and removes data that is not
referenced and therefore not needed any more.
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runPrune(globalOptions)
	},
}

func init() {
	cmdRoot.AddCommand(cmdPrune)
}

// newProgressMax returns a progress that counts blobs.
func newProgressMax(show bool, max uint64, description string) *restic.Progress {
	if !show {
		return nil
	}

	p := restic.NewProgress()

	p.OnUpdate = func(s restic.Stat, d time.Duration, ticker bool) {
		status := fmt.Sprintf("[%s] %s  %d / %d %s",
			formatDuration(d),
			formatPercent(s.Blobs, max),
			s.Blobs, max, description)

		w, _, err := terminal.GetSize(int(os.Stdout.Fd()))
		if err == nil {
			if len(status) > w {
				max := w - len(status) - 4
				status = status[:max] + "... "
			}
		}

		PrintProgress("%s", status)
	}

	p.OnDone = func(s restic.Stat, d time.Duration, ticker bool) {
		fmt.Printf("\n")
	}

	return p
}

func runPrune(gopts GlobalOptions) error {
	repo, err := OpenRepository(gopts)
	if err != nil {
		return err
	}

	lock, err := lockRepoExclusive(repo)
	defer unlockRepo(lock)
	if err != nil {
		return err
	}

	return pruneRepository(gopts, repo)
}

func pruneRepository(gopts GlobalOptions, repo restic.Repository) error {
	err := repo.LoadIndex()
	if err != nil {
		return err
	}

	done := make(chan struct{})
	defer close(done)

	var stats struct {
		blobs     int
		packs     int
		snapshots int
		bytes     int64
	}

	Verbosef("counting files in repo\n")
	for _ = range repo.List(restic.DataFile, done) {
		stats.packs++
	}

	Verbosef("building new index for repo\n")

	bar := newProgressMax(!gopts.Quiet, uint64(stats.packs), "packs")
	idx, err := index.New(repo, bar)
	if err != nil {
		return err
	}

	blobs := 0
	for _, pack := range idx.Packs {
		stats.bytes += pack.Size
		blobs += len(pack.Entries)
	}
	Verbosef("repository contains %v packs (%v blobs) with %v bytes\n",
		len(idx.Packs), blobs, formatBytes(uint64(stats.bytes)))

	blobCount := make(map[restic.BlobHandle]int)
	duplicateBlobs := 0
	duplicateBytes := 0

	// find duplicate blobs
	for _, p := range idx.Packs {
		for _, entry := range p.Entries {
			stats.blobs++
			h := restic.BlobHandle{ID: entry.ID, Type: entry.Type}
			blobCount[h]++

			if blobCount[h] > 1 {
				duplicateBlobs++
				duplicateBytes += int(entry.Length)
			}
		}
	}

	Verbosef("processed %d blobs: %d duplicate blobs, %v duplicate\n",
		stats.blobs, duplicateBlobs, formatBytes(uint64(duplicateBytes)))
	Verbosef("load all snapshots\n")

	// find referenced blobs
	snapshots, err := restic.LoadAllSnapshots(repo)
	if err != nil {
		return err
	}

	stats.snapshots = len(snapshots)

	Verbosef("find data that is still in use for %d snapshots\n", stats.snapshots)

	usedBlobs := restic.NewBlobSet()
	seenBlobs := restic.NewBlobSet()

	bar = newProgressMax(!gopts.Quiet, uint64(len(snapshots)), "snapshots")
	bar.Start()
	for _, sn := range snapshots {
		debug.Log("process snapshot %v", sn.ID().Str())

		err = restic.FindUsedBlobs(repo, *sn.Tree, usedBlobs, seenBlobs)
		if err != nil {
			return err
		}

		debug.Log("found %v blobs for snapshot %v", sn.ID().Str())
		bar.Report(restic.Stat{Blobs: 1})
	}
	bar.Done()

	Verbosef("found %d of %d data blobs still in use, removing %d blobs\n",
		len(usedBlobs), stats.blobs, stats.blobs-len(usedBlobs))

	// find packs that need a rewrite
	rewritePacks := restic.NewIDSet()
	for _, pack := range idx.Packs {
		for _, blob := range pack.Entries {
			h := restic.BlobHandle{ID: blob.ID, Type: blob.Type}
			if !usedBlobs.Has(h) {
				rewritePacks.Insert(pack.ID)
				continue
			}

			if blobCount[h] > 1 {
				rewritePacks.Insert(pack.ID)
			}
		}
	}

	removeBytes := 0

	// find packs that are unneeded
	removePacks := restic.NewIDSet()
	for packID, p := range idx.Packs {

		hasActiveBlob := false
		for _, blob := range p.Entries {
			h := restic.BlobHandle{ID: blob.ID, Type: blob.Type}
			if usedBlobs.Has(h) {
				hasActiveBlob = true
				continue
			}

			removeBytes += int(blob.Length)
		}

		if hasActiveBlob {
			continue
		}

		removePacks.Insert(packID)

		if !rewritePacks.Has(packID) {
			return errors.Fatalf("pack %v is unneeded, but not contained in rewritePacks", packID.Str())
		}

		rewritePacks.Delete(packID)
	}

	Verbosef("will delete %d packs and rewrite %d packs, this frees %s\n",
		len(removePacks), len(rewritePacks), formatBytes(uint64(removeBytes)))

	if len(rewritePacks) != 0 {
		bar = newProgressMax(!gopts.Quiet, uint64(len(rewritePacks)), "packs rewritten")
		bar.Start()
		err = repository.Repack(repo, rewritePacks, usedBlobs, bar)
		if err != nil {
			return err
		}
		bar.Done()
	}

	if len(removePacks) != 0 {
		bar = newProgressMax(!gopts.Quiet, uint64(len(removePacks)), "packs deleted")
		bar.Start()
		for packID := range removePacks {
			h := restic.Handle{Type: restic.DataFile, Name: packID.String()}
			err = repo.Backend().Remove(h)
			if err != nil {
				Warnf("unable to remove file %v from the repository\n", packID.Str())
			}
			bar.Report(restic.Stat{Blobs: 1})
		}
		bar.Done()
	}

	Verbosef("creating new index\n")

	stats.packs = 0
	for _ = range repo.List(restic.DataFile, done) {
		stats.packs++
	}
	bar = newProgressMax(!gopts.Quiet, uint64(stats.packs), "packs")
	idx, err = index.New(repo, bar)
	if err != nil {
		return err
	}

	var supersedes restic.IDs
	for idxID := range repo.List(restic.IndexFile, done) {
		h := restic.Handle{Type: restic.IndexFile, Name: idxID.String()}
		err := repo.Backend().Remove(h)
		if err != nil {
			fmt.Fprintf(os.Stderr, "unable to remove index %v: %v\n", idxID.Str(), err)
		}

		supersedes = append(supersedes, idxID)
	}

	id, err := idx.Save(repo, supersedes)
	if err != nil {
		return err
	}
	Verbosef("saved new index as %v\n", id.Str())

	Verbosef("done\n")
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"net/http"
	"net/url"
	"repo"
	"resources"
	"strconv"
	"strings"
)

var port int
var todoRegex string
var excludePaths string

func init() {
	flag.IntVar(&port, "port", 8080, "Port on which to start the server.")
	flag.StringVar(
		&todoRegex,
		"todo_regex",
		"(^|[^[:alpha:]])(t|T)(o|O)(d|D)(o|O)[^[:alpha:]]",
		"Regular expression (using the re2 syntax) to use when matching TODOs.")
	flag.StringVar(
		&excludePaths,
		"exclude_paths",
		"",
		"List of file paths to exclude when matching TODOs. This is useful if your repo contains binaries")
}

func serveStaticContent(w http.ResponseWriter, resourceName string) {
	resourceContents := resources.Constants[resourceName]
	var contentType string
	if strings.HasSuffix(resourceName, ".css") {
		contentType = "text/css"
	} else if strings.HasSuffix(resourceName, ".html") {
		contentType = "text/html"
	} else if strings.HasSuffix(resourceName, ".js") {
		contentType = "text/javascript"
	} else {
		contentType = http.DetectContentType(resourceContents)
	}
	w.Header().Set("Content-Type", contentType)
	w.Write(resourceContents)
}

func readRevisionAndPathParams(r *http.Request) (repo.Revision, string, error) {
	revisionParam := r.URL.Query().Get("revision")
	if revisionParam == "" {
		return repo.Revision(""), "", errors.New("Missing the revision parameter")
	}
	fileName, err := url.QueryUnescape(r.URL.Query().Get("fileName"))
	if err != nil || fileName == "" {
		return repo.Revision(""), "", errors.New("Missing the fileName parameter")
	}
	return repo.Revision(revisionParam), fileName, nil
}

func serveRepoDetails(repository repo.Repository) {
	http.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) {
		resourceName := r.URL.Path[4:]
		serveStaticContent(w, resourceName)
	})
	http.HandleFunc("/aliases",
		func(w http.ResponseWriter, r *http.Request) {
			err := repo.WriteJson(w, repository)
			if err != nil {
				w.WriteHeader(500)
				fmt.Fprintf(w, "Server error \"%s\"", err)
			}
		})
	http.HandleFunc("/revision",
		func(w http.ResponseWriter, r *http.Request) {
			revisionParam := r.URL.Query().Get("id")
			if revisionParam == "" {
				w.WriteHeader(400)
				fmt.Fprint(w, "Missing required parameter 'id'")
				return
			}
			revision := repo.Revision(revisionParam)
			err := repo.WriteTodosJson(w, repository, revision, todoRegex, excludePaths)
			if err != nil {
				w.WriteHeader(500)
				fmt.Fprintf(w, "Server error \"%s\"", err)
			}
		})
	http.HandleFunc("/todo",
		func(w http.ResponseWriter, r *http.Request) {
			revision, fileName, err := readRevisionAndPathParams(r)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, err.Error())
				return
			}
			lineNumberParam := r.URL.Query().Get("lineNumber")
			if lineNumberParam == "" {
				w.WriteHeader(400)
				fmt.Fprintf(w, "Missing the lineNumber param")
				return
			}
			lineNumber, err := strconv.Atoi(lineNumberParam)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, "Invalid format for the lineNumber parameter: %s", err)
				return
			}
			todoId := repo.TodoId{
				Revision:   revision,
				FileName:   fileName,
				LineNumber: lineNumber,
			}
			repo.WriteTodoDetailsJson(w, repository, todoId)
		})
	http.HandleFunc("/browse",
		func(w http.ResponseWriter, r *http.Request) {
			revision, fileName, err := readRevisionAndPathParams(r)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, err.Error())
				return
			}
			lineNumberParam := r.URL.Query().Get("lineNumber")
			if lineNumberParam == "" {
				lineNumberParam = "1"
			}
			lineNumber, err := strconv.Atoi(lineNumberParam)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, "Invalid format for the lineNumber parameter: %s", err)
				return
			}
			http.Redirect(w, r, repository.GetBrowseUrl(
				revision, fileName, lineNumber), http.StatusMovedPermanently)
		})
	http.HandleFunc("/raw",
		func(w http.ResponseWriter, r *http.Request) {
			revision, fileName, err := readRevisionAndPathParams(r)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, err.Error())
				return
			}
			contents := repository.ReadFileSnippetAtRevision(revision, fileName, 1, -1)
			w.Write([]byte(contents))
		})
	http.HandleFunc("/",
		func(w http.ResponseWriter, r *http.Request) {
			http.Redirect(w, r, "/ui/list_branches.html", http.StatusMovedPermanently)
		})
	http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
}

func main() {
	flag.Parse()
	gitRepository := repo.NewGitRepository(todoRegex, excludePaths)
	serveRepoDetails(gitRepository)
}
<commit_msg>Added a log statement if the server cannot bind to the tcp port, and also added a TODO to add usage information if the server is not started correctly<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"repo"
	"resources"
	"strconv"
	"strings"
)

var port int
var todoRegex string
var excludePaths string

func init() {
	flag.IntVar(&port, "port", 8080, "Port on which to start the server.")
	flag.StringVar(
		&todoRegex,
		"todo_regex",
		"(^|[^[:alpha:]])(t|T)(o|O)(d|D)(o|O)[^[:alpha:]]",
		"Regular expression (using the re2 syntax) to use when matching TODOs.")
	flag.StringVar(
		&excludePaths,
		"exclude_paths",
		"",
		"List of file paths to exclude when matching TODOs. This is useful if your repo contains binaries")
}

func serveStaticContent(w http.ResponseWriter, resourceName string) {
	resourceContents := resources.Constants[resourceName]
	var contentType string
	if strings.HasSuffix(resourceName, ".css") {
		contentType = "text/css"
	} else if strings.HasSuffix(resourceName, ".html") {
		contentType = "text/html"
	} else if strings.HasSuffix(resourceName, ".js") {
		contentType = "text/javascript"
	} else {
		contentType = http.DetectContentType(resourceContents)
	}
	w.Header().Set("Content-Type", contentType)
	w.Write(resourceContents)
}

func readRevisionAndPathParams(r *http.Request) (repo.Revision, string, error) {
	revisionParam := r.URL.Query().Get("revision")
	if revisionParam == "" {
		return repo.Revision(""), "", errors.New("Missing the revision parameter")
	}
	fileName, err := url.QueryUnescape(r.URL.Query().Get("fileName"))
	if err != nil || fileName == "" {
		return repo.Revision(""), "", errors.New("Missing the fileName parameter")
	}
	return repo.Revision(revisionParam), fileName, nil
}

func serveRepoDetails(repository repo.Repository) {
	http.HandleFunc("/ui/", func(w http.ResponseWriter, r *http.Request) {
		resourceName := r.URL.Path[4:]
		serveStaticContent(w, resourceName)
	})
	http.HandleFunc("/aliases",
		func(w http.ResponseWriter, r *http.Request) {
			err := repo.WriteJson(w, repository)
			if err != nil {
				w.WriteHeader(500)
				fmt.Fprintf(w, "Server error \"%s\"", err)
			}
		})
	http.HandleFunc("/revision",
		func(w http.ResponseWriter, r *http.Request) {
			revisionParam := r.URL.Query().Get("id")
			if revisionParam == "" {
				w.WriteHeader(400)
				fmt.Fprint(w, "Missing required parameter 'id'")
				return
			}
			revision := repo.Revision(revisionParam)
			err := repo.WriteTodosJson(w, repository, revision, todoRegex, excludePaths)
			if err != nil {
				w.WriteHeader(500)
				fmt.Fprintf(w, "Server error \"%s\"", err)
			}
		})
	http.HandleFunc("/todo",
		func(w http.ResponseWriter, r *http.Request) {
			revision, fileName, err := readRevisionAndPathParams(r)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, err.Error())
				return
			}
			lineNumberParam := r.URL.Query().Get("lineNumber")
			if lineNumberParam == "" {
				w.WriteHeader(400)
				fmt.Fprintf(w, "Missing the lineNumber param")
				return
			}
			lineNumber, err := strconv.Atoi(lineNumberParam)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, "Invalid format for the lineNumber parameter: %s", err)
				return
			}
			todoId := repo.TodoId{
				Revision:   revision,
				FileName:   fileName,
				LineNumber: lineNumber,
			}
			repo.WriteTodoDetailsJson(w, repository, todoId)
		})
	http.HandleFunc("/browse",
		func(w http.ResponseWriter, r *http.Request) {
			revision, fileName, err := readRevisionAndPathParams(r)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, err.Error())
				return
			}
			lineNumberParam := r.URL.Query().Get("lineNumber")
			if lineNumberParam == "" {
				lineNumberParam = "1"
			}
			lineNumber, err := strconv.Atoi(lineNumberParam)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, "Invalid format for the lineNumber parameter: %s", err)
				return
			}
			http.Redirect(w, r, repository.GetBrowseUrl(
				revision, fileName, lineNumber), http.StatusMovedPermanently)
		})
	http.HandleFunc("/raw",
		func(w http.ResponseWriter, r *http.Request) {
			revision, fileName, err := readRevisionAndPathParams(r)
			if err != nil {
				w.WriteHeader(400)
				fmt.Fprintf(w, err.Error())
				return
			}
			contents := repository.ReadFileSnippetAtRevision(revision, fileName, 1, -1)
			w.Write([]byte(contents))
		})
	http.HandleFunc("/",
		func(w http.ResponseWriter, r *http.Request) {
			http.Redirect(w, r, "/ui/list_branches.html", http.StatusMovedPermanently)
		})
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
}

func main() {
	flag.Parse()
	// TODO: Add some sanity checking that the binary was started inside of a git repo directory.
	gitRepository := repo.NewGitRepository(todoRegex, excludePaths)
	serveRepoDetails(gitRepository)
}
<|endoftext|>


<commit_before>package app

import (
	"src/data"
	"src/data/types"
	"src/data/sqldb"
	"src/data/fetch"
	"src/config"
	"src/tpl"
	"src/logging"
	"src/watch"
	"appengine"
	"net/http"
	"database/sql"
	"encoding/json"
	"strings"
	"strconv"
	"fmt"
	"errors"
	_ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

var recordedLog []string
var recordedError error

var jsonFileName = config.JsonFileName()
var mapsApiKey = config.MapsApiKey()

func init() {
	log := logging.NewRecordingLogger(&logging.InitLogger{}, true)

	log.Infof("Spinning up instance with ID '%s'", appengine.InstanceID())

	err := openInit(log)
	recordInitUpdate(err, log)
	if err != nil {
		panic(err)
	}

	http.HandleFunc("/", render(front))
	http.HandleFunc("/movie", render(movies))
	http.HandleFunc("/movie/", render(movie))
	http.HandleFunc("/status", renderStatus)
	http.HandleFunc("/update", renderUpdate)
	http.HandleFunc("/ping", renderPing)
	http.HandleFunc("/data", renderDataJson)

	// TODO Make "raw data dump" page.
	// TODO Add pages for actor, ...
}

func openInit(log *logging.RecordingLogger) error {
	if err := openDb(log); err != nil {
		return err
	}
	if _, err := data.Init(db, jsonFileName, log); err != nil {
		return err
	}
	return nil
}

func recordInitUpdate(err error, log *logging.RecordingLogger) {
	recordedError = err
	entries := log.Entries
	entriesCopy := make([]string, len(entries))
	copy(entriesCopy, entries)
	recordedLog = entriesCopy
}

func openDb(logger logging.Logger) error {
	var err error
	if appengine.IsDevAppServer() {
		logger.Infof("Running in development mode")
		db, err = sql.Open("mysql", config.LocalDbSourceName())
	} else {
		logger.Infof("Running in production mode")
		db, err = sql.Open("mysql", config.CloudDbSourceName())
	}
	return err
}

func render(renderer func(w http.ResponseWriter, r *http.Request, log *logging.RecordingLogger) error) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := appengine.NewContext(r)
		log := logging.NewRecordingLogger(ctx, false)

		// Check if database is initialized and load from file if it isn't.
		initialized, err := data.Init(db, jsonFileName, log)
		if initialized {
			recordInitUpdate(err, log)
		}

		if err == nil {
			err = renderer(w, r, log)
		}

		if err != nil {
			ctx.Errorf("ERROR: %+v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

func front(w http.ResponseWriter, r *http.Request, log *logging.RecordingLogger) error {
	ctx := appengine.NewContext(r)
	templateData := tpl.NewTemplateData(ctx, log, nil)
	return tpl.Render(w, tpl.About, templateData)
}

func movie(w http.ResponseWriter, r *http.Request, log *logging.RecordingLogger) error {
	path := r.URL.Path
	idx := strings.LastIndex(path, "/")
	idStr := path[idx + 1:]

	id, err := strconv.Atoi(idStr)
	if err != nil {
		return errors.New(fmt.Sprintf("Invalid movie ID '%s'", idStr))
	}

	log.Infof("Rendering movie with ID %d", id)

	movie, err := sqldb.LoadMovie(db, int64(id), log)
	if err != nil {
		http.Error(w, fmt.Sprintf("Movie with ID %d not found", id), http.StatusNotFound)
		return nil
	}

	log.Infof("Loading coordinates")
	locNameCoordsMap, err := sqldb.LoadCoordinates(db, movie.Locations, log)

	missingCoords := make(map[string]*types.Coordinates)
	for _, loc := range movie.Locations {
		locName := loc.Name
		if _, exists := locNameCoordsMap[locName]; !exists {
			missingCoords[locName] = nil
		}
	}

	// Load missing coordinates.
	ctx := appengine.NewContext(r)
	delayFunc := func (count int) int { return 50 * count }
	fetch.FetchMissingLocationNames(missingCoords, delayFunc, mapsApiKey, ctx, log)

	// Store missing coordinates.
	if err := sqldb.StoreCoordinates(db, missingCoords, log); err != nil {
		return err
	}

	// Add missing coordinates to `coords`.
	for locName, locCoords := range missingCoords {
		if locCoords != nil {
			locNameCoordsMap[locName] = *locCoords
		}
	}

	// Set coordinates on locations.
	for i := range movie.Locations {
		loc := &movie.Locations[i]
		loc.Coordinates = locNameCoordsMap[loc.Name]
	}

	type MovieInfo struct {
		Title      string
		Year       string
		Rated      string
		Released   string
		Runtime    string
		Genre      string
		Director   string
		Writer     string
		Actors     string
		Plot       string
		Language   string
		Country    string
		Awards     string
		Poster     string
		Metascore  string
		ImdbRating string
		ImdbVotes  string
		ImdbID     string
	}

	var info MovieInfo

	info.Title = movie.Title
	info.Actors = strings.Join(movie.Actors, ", ")
	info.Writer = movie.Writer
	info.Director = movie.Director
	info.Released = strconv.Itoa(movie.ReleaseYear)

	args := &struct {
		Movie    *types.Movie
		Info     *MovieInfo
	}{&movie, &info}

	if infoJson, err := sqldb.LoadMovieInfoJson(db, movie.Title, log); infoJson != "" && err == nil {
		// Only attempt to parse JSON if it was loaded successfully
		if err := json.Unmarshal([]byte(infoJson), &info); err != nil {
			log.Errorf(err.Error())
		}
	}

	templateData := tpl.NewTemplateData(ctx, log, args)
	templateData.Subtitle = info.Title
	return tpl.Render(w, tpl.Movie, templateData)
}

func movies(w http.ResponseWriter, r *http.Request, log *logging.RecordingLogger) error {
	log.Infof("Rendering movie list page")

	movies, err := sqldb.LoadMovies(db, log)
	if err != nil {
		return err
	}

	ctx := appengine.NewContext(r)
	templateData := tpl.NewTemplateData(ctx, log, movies)
	templateData.Subtitle = "List"
	if err := tpl.Render(w, tpl.Movies, templateData); err != nil {
		return err
	}

	return nil
}

// TODO Have one optimized endpoint with only data needed for autocomplete and one with *all* data.

func renderDataJson(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)

	movies, err := sqldb.LoadMovies(db, ctx)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	if err := json.NewEncoder(w).Encode(movies); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func renderUpdate(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	log := logging.NewRecordingLogger(ctx, false)

	var err error
	defer recordInitUpdate(err, log)
	err = update(w, r, log)
	if err != nil {
		ctx.Errorf("ERROR: %+v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func update(w http.ResponseWriter, r *http.Request, log *logging.RecordingLogger) error {
	var err error

	ctx := appengine.NewContext(r)

	if r.Method != "POST" {
		errMsg := "Cannot " + r.Method + " '/update'"
		ctx.Errorf(errMsg)
		http.Error(w, errMsg, http.StatusMethodNotAllowed)
		return nil
	}

	// TODO Add timestamp(s) to DB for locking to work across instances.

	data.InitUpdateMutex.Lock()
	defer data.InitUpdateMutex.Unlock()

	movies, err := fetch.FetchFromUrl(config.ServiceUrl(), ctx, log)
	if err != nil {
		return err
	}
	if err := sqldb.InitTablesAndStoreMovies(db, movies, log); err != nil {
		return err
	}

	// Fetch movie data.
	// TODO This information should be fetched on demand (as location data is) or also fetched on initialization.
	movieTitleInfoMap, err := sqldb.LoadMovieInfoJsons(db, log)
	if err != nil {
		return err
	}

	movieTitleInfo := make(map[string]string)
	for _, movie := range movies {
		movieTitle := movie.Title
		if _, exists := movieTitleInfoMap[movieTitle]; exists {
			// Info already in DB.
			continue
		}

		infoJson, err := fetch.FetchMovieInfo(movieTitle, ctx, log)
		if err != nil {
			return err
		}

		info := &struct {
			Response string
		}{}

		json.Unmarshal([]byte(infoJson), info)
		if info.Response != "True" {
			infoJson = "";
		}

		movieTitleInfo[movieTitle] = infoJson
	}

	// Store movie data.
	if err := sqldb.StoreMovieInfo(db, movieTitleInfo, log); err != nil {
		return err
	}

	http.Redirect(w, r, "", http.StatusFound)
	return nil
}

func renderStatus(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	log := logging.NewRecordingLogger(ctx, false)
	if err := status(w, r, log); err != nil {
		ctx.Errorf(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func status(w http.ResponseWriter, r *http.Request, logger *logging.RecordingLogger) error {
	logger.Infof("Rendering status page")

	sw := watch.NewStopWatch()

	mc := 0
	ac := 0
	lc := 0
	rc := 0
	cc := 0
	ic := 0

	mt := int64(0)
	at := int64(0)
	lt := int64(0)
	rt := int64(0)
	ct := int64(0)
	it := int64(0)

	initialized, err := data.IsInitialized(db)
	if err != nil {
		return err
	}

	if initialized {
		querySingleInt := func(db *sql.DB, sql string, args ...interface{}) (int, error) {
			row := db.QueryRow(sql, args...)
			var i int
			err := row.Scan(&i)
			return i, err
		}

		mc, err = querySingleInt(db, "SELECT COUNT(*) FROM movies")
		if err != nil {
			return err
		}
		mt = sw.ElapsedTimeMillis(true)

		ac, err = querySingleInt(db, "SELECT COUNT(*) FROM actors")
		if err != nil {
			return err
		}
		at = sw.ElapsedTimeMillis(true)

		lc, err = querySingleInt(db, "SELECT COUNT(*) FROM locations")
		if err != nil {
			return err
		}
		lt = sw.ElapsedTimeMillis(true)

		rc, err = querySingleInt(db, "SELECT COUNT(*) FROM movies_actors")
		if err != nil {
			return err
		}
		rt = sw.ElapsedTimeMillis(true)

		cc, err = querySingleInt(db, "SELECT COUNT(*) FROM coordinates")
		if err != nil {
			return err
		}
		ct = sw.ElapsedTimeMillis(true)

		ic, err = querySingleInt(db, "SELECT COUNT(*) FROM movie_info")
		if err != nil {
			return err
		}
		it = sw.ElapsedTimeMillis(true)
	}

	dt := sw.TotalElapsedTimeMillis()

	args := struct {
		Clock            string
		Time             int64
		MoviesCount      int
		MoviesTime       int64
		ActorsCount      int
		ActorsTime       int64
		LocationsCount   int
		LocationsTime    int64
		MovieActorsCount int
		MovieActorsTime  int64
		CoordinatesCount int
		CoordinatesTime  int64
		InfoCount        int
		InfoTime         int64
		RecordedErr      error
		RecordedLog      []string
	}{sw.InitTime.String(), dt, mc, mt, ac, at, lc, lt, rc, rt, cc, ct, ic, it, recordedError, recordedLog}

	ctx := appengine.NewContext(r)
	templateData := tpl.NewTemplateData(ctx, logger, args)
	templateData.Subtitle = "Status"
	return tpl.Render(w, tpl.Status, templateData)
}

func renderPing(w http.ResponseWriter, r *http.Request) {
	if err := ping(w, r); err != nil {
		ctx := appengine.NewContext(r)
		ctx.Errorf(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func ping(w http.ResponseWriter, r *http.Request) error {
	sw := watch.NewStopWatch()
	//err := db.Ping()
	row := db.QueryRow("SELECT 42")

	var _42 int
	if err := row.Scan(&_42); err != nil {
		return err
	}

	if _42 != 42 {
		return errors.New("Invalid response from DB")
	}

	args := &struct {
		Clock   string
		Time    int64
	}{sw.InitTime.String(), sw.TotalElapsedTimeMillis()}

	ctx := appengine.NewContext(r)
	log := logging.NewRecordingLogger(ctx, false)
	templateData := tpl.NewTemplateData(ctx, log, args)
	templateData.Subtitle = "Ping"
	if err := tpl.Render(w, tpl.Ping, templateData); err != nil {
		return err
	}
	return nil
}
<commit_msg>Added cache prevention. It seems like IE does some speculative caching that causes the app to refer to obsolete movie IDs.<commit_after>package app

import (
	"src/data"
	"src/data/types"
	"src/data/sqldb"
	"src/data/fetch"
	"src/config"
	"src/tpl"
	"src/logging"
	"src/watch"
	"appengine"
	"net/http"
	"database/sql"
	"encoding/json"
	"strings"
	"strconv"
	"fmt"
	"errors"
	_ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

var recordedLog []string
var recordedError error

var jsonFileName = config.JsonFileName()
var mapsApiKey = config.MapsApiKey()

func init() {
	log := logging.NewRecordingLogger(&logging.InitLogger{}, true)

	log.Infof("Spinning up instance with ID '%s'", appengine.InstanceID())

	err := openInit(log)
	recordInitUpdate(err, log)
	if err != nil {
		panic(err)
	}

	http.HandleFunc("/", render(front))
	http.HandleFunc("/movie", render(movies))
	http.HandleFunc("/movie/", render(movie))
	http.HandleFunc("/status", renderStatus)
	http.HandleFunc("/update", renderUpdate)
	http.HandleFunc("/ping", renderPing)
	http.HandleFunc("/data", renderDataJson)

	// TODO Make "raw data dump" page.
	// TODO Add pages for actor, ...
}

func openInit(log *logging.RecordingLogger) error {
	if err := openDb(log); err != nil {
		return err
	}
	if _, err := data.Init(db, jsonFileName, log); err != nil {
		return err
	}
	return nil
}

func recordInitUpdate(err error, log *logging.RecordingLogger) {
	recordedError = err
	entries := log.Entries
	entriesCopy := make([]string, len(entries))
	copy(entriesCopy, entries)
	recordedLog = entriesCopy
}

func openDb(logger logging.Logger) error {
	var err error
	if appengine.IsDevAppServer() {
		logger.Infof("Running in development mode")
		db, err = sql.Open("mysql", config.LocalDbSourceName())
	} else {
		logger.Infof("Running in production mode")
		db, err = sql.Open("mysql", config.CloudDbSourceName())
	}
	return err
}

func render(renderer func(w http.ResponseWriter, r *http.Request, log *logging.RecordingLogger) error) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := appengine.NewContext(r)
		log := logging.NewRecordingLogger(ctx, false)

		// Check if database is initialized and load from file if it isn't.
		initialized, err := data.Init(db, jsonFileName, log)
		if initialized {
			recordInitUpdate(err, log)
		}

		if err == nil {
			err = renderer(w, r, log)
		}

		if err != nil {
			ctx.Errorf("ERROR: %+v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}
func preventCaching(w http.ResponseWriter) {
	// Prevent caching of pages. As App Engine auto-includes a "Date"-header, setting the "Expires"-header
	// should usually be sufficient in these modern times (according to 'http://stackoverflow.com/a/2068407/883073').
	// Also, it seems that App Engine picks it up and adds safer headers.
	w.Header().Set("Expires", "0")
}

func front(w http.ResponseWriter, r *http.Request, log *logging.RecordingLogger) error {
	ctx := appengine.NewContext(r)
	templateData := tpl.NewTemplateData(ctx, log, nil)
	return tpl.Render(w, tpl.About, templateData)
}

func movie(w http.ResponseWriter, r *http.Request, log *logging.RecordingLogger) error {
	preventCaching(w);

	path := r.URL.Path
	idx := strings.LastIndex(path, "/")
	idStr := path[idx + 1:]

	id, err := strconv.Atoi(idStr)
	if err != nil {
		return errors.New(fmt.Sprintf("Invalid movie ID '%s'", idStr))
	}

	log.Infof("Rendering movie with ID %d", id)

	movie, err := sqldb.LoadMovie(db, int64(id), log)
	if err != nil {
		http.Error(w, fmt.Sprintf("Movie with ID %d not found", id), http.StatusNotFound)
		return nil
	}

	log.Infof("Loading coordinates")
	locNameCoordsMap, err := sqldb.LoadCoordinates(db, movie.Locations, log)

	missingCoords := make(map[string]*types.Coordinates)
	for _, loc := range movie.Locations {
		locName := loc.Name
		if _, exists := locNameCoordsMap[locName]; !exists {
			missingCoords[locName] = nil
		}
	}

	// Load missing coordinates.
	ctx := appengine.NewContext(r)
	delayFunc := func (count int) int { return 50 * count }
	fetch.FetchMissingLocationNames(missingCoords, delayFunc, mapsApiKey, ctx, log)

	// Store missing coordinates.
	if err := sqldb.StoreCoordinates(db, missingCoords, log); err != nil {
		return err
	}

	// Add missing coordinates to `coords`.
	for locName, locCoords := range missingCoords {
		if locCoords != nil {
			locNameCoordsMap[locName] = *locCoords
		}
	}

	// Set coordinates on locations.
	for i := range movie.Locations {
		loc := &movie.Locations[i]
		loc.Coordinates = locNameCoordsMap[loc.Name]
	}

	type MovieInfo struct {
		Title      string
		Year       string
		Rated      string
		Released   string
		Runtime    string
		Genre      string
		Director   string
		Writer     string
		Actors     string
		Plot       string
		Language   string
		Country    string
		Awards     string
		Poster     string
		Metascore  string
		ImdbRating string
		ImdbVotes  string
		ImdbID     string
	}

	var info MovieInfo

	info.Title = movie.Title
	info.Actors = strings.Join(movie.Actors, ", ")
	info.Writer = movie.Writer
	info.Director = movie.Director
	info.Released = strconv.Itoa(movie.ReleaseYear)

	args := &struct {
		Movie    *types.Movie
		Info     *MovieInfo
	}{&movie, &info}

	if infoJson, err := sqldb.LoadMovieInfoJson(db, movie.Title, log); infoJson != "" && err == nil {
		// Only attempt to parse JSON if it was loaded successfully
		if err := json.Unmarshal([]byte(infoJson), &info); err != nil {
			log.Errorf(err.Error())
		}
	}

	templateData := tpl.NewTemplateData(ctx, log, args)
	templateData.Subtitle = info.Title
	return tpl.Render(w, tpl.Movie, templateData)
}

func movies(w http.ResponseWriter, r *http.Request, log *logging.RecordingLogger) error {
	preventCaching(w);

	log.Infof("Rendering movie list page")

	movies, err := sqldb.LoadMovies(db, log)
	if err != nil {
		return err
	}

	ctx := appengine.NewContext(r)
	templateData := tpl.NewTemplateData(ctx, log, movies)
	templateData.Subtitle = "List"
	if err := tpl.Render(w, tpl.Movies, templateData); err != nil {
		return err
	}

	return nil
}

// TODO Have one optimized endpoint with only data needed for autocomplete and one with *all* data.

func renderDataJson(w http.ResponseWriter, r *http.Request) {
	preventCaching(w);

	ctx := appengine.NewContext(r)

	movies, err := sqldb.LoadMovies(db, ctx)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}

	if err := json.NewEncoder(w).Encode(movies); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func renderUpdate(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	log := logging.NewRecordingLogger(ctx, false)

	var err error
	defer recordInitUpdate(err, log)
	err = update(w, r, log)
	if err != nil {
		ctx.Errorf("ERROR: %+v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func update(w http.ResponseWriter, r *http.Request, log *logging.RecordingLogger) error {
	var err error

	ctx := appengine.NewContext(r)

	if r.Method != "POST" {
		errMsg := "Cannot " + r.Method + " '/update'"
		ctx.Errorf(errMsg)
		http.Error(w, errMsg, http.StatusMethodNotAllowed)
		return nil
	}

	// TODO Add timestamp(s) to DB for locking to work across instances.

	data.InitUpdateMutex.Lock()
	defer data.InitUpdateMutex.Unlock()

	movies, err := fetch.FetchFromUrl(config.ServiceUrl(), ctx, log)
	if err != nil {
		return err
	}
	if err := sqldb.InitTablesAndStoreMovies(db, movies, log); err != nil {
		return err
	}

	// Fetch movie data.
	// TODO This information should be fetched on demand (as location data is) or also fetched on initialization.
	movieTitleInfoMap, err := sqldb.LoadMovieInfoJsons(db, log)
	if err != nil {
		return err
	}

	movieTitleInfo := make(map[string]string)
	for _, movie := range movies {
		movieTitle := movie.Title
		if _, exists := movieTitleInfoMap[movieTitle]; exists {
			// Info already in DB.
			continue
		}

		infoJson, err := fetch.FetchMovieInfo(movieTitle, ctx, log)
		if err != nil {
			return err
		}

		info := &struct {
			Response string
		}{}

		json.Unmarshal([]byte(infoJson), info)
		if info.Response != "True" {
			infoJson = "";
		}

		movieTitleInfo[movieTitle] = infoJson
	}

	// Store movie data.
	if err := sqldb.StoreMovieInfo(db, movieTitleInfo, log); err != nil {
		return err
	}

	http.Redirect(w, r, "", http.StatusFound)
	return nil
}

func renderStatus(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	log := logging.NewRecordingLogger(ctx, false)
	if err := status(w, r, log); err != nil {
		ctx.Errorf(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func status(w http.ResponseWriter, r *http.Request, logger *logging.RecordingLogger) error {
	preventCaching(w);

	logger.Infof("Rendering status page")

	sw := watch.NewStopWatch()

	mc := 0
	ac := 0
	lc := 0
	rc := 0
	cc := 0
	ic := 0

	mt := int64(0)
	at := int64(0)
	lt := int64(0)
	rt := int64(0)
	ct := int64(0)
	it := int64(0)

	initialized, err := data.IsInitialized(db)
	if err != nil {
		return err
	}

	if initialized {
		querySingleInt := func(db *sql.DB, sql string, args ...interface{}) (int, error) {
			row := db.QueryRow(sql, args...)
			var i int
			err := row.Scan(&i)
			return i, err
		}

		mc, err = querySingleInt(db, "SELECT COUNT(*) FROM movies")
		if err != nil {
			return err
		}
		mt = sw.ElapsedTimeMillis(true)

		ac, err = querySingleInt(db, "SELECT COUNT(*) FROM actors")
		if err != nil {
			return err
		}
		at = sw.ElapsedTimeMillis(true)

		lc, err = querySingleInt(db, "SELECT COUNT(*) FROM locations")
		if err != nil {
			return err
		}
		lt = sw.ElapsedTimeMillis(true)

		rc, err = querySingleInt(db, "SELECT COUNT(*) FROM movies_actors")
		if err != nil {
			return err
		}
		rt = sw.ElapsedTimeMillis(true)

		cc, err = querySingleInt(db, "SELECT COUNT(*) FROM coordinates")
		if err != nil {
			return err
		}
		ct = sw.ElapsedTimeMillis(true)

		ic, err = querySingleInt(db, "SELECT COUNT(*) FROM movie_info")
		if err != nil {
			return err
		}
		it = sw.ElapsedTimeMillis(true)
	}

	dt := sw.TotalElapsedTimeMillis()

	args := struct {
		Clock            string
		Time             int64
		MoviesCount      int
		MoviesTime       int64
		ActorsCount      int
		ActorsTime       int64
		LocationsCount   int
		LocationsTime    int64
		MovieActorsCount int
		MovieActorsTime  int64
		CoordinatesCount int
		CoordinatesTime  int64
		InfoCount        int
		InfoTime         int64
		RecordedErr      error
		RecordedLog      []string
	}{sw.InitTime.String(), dt, mc, mt, ac, at, lc, lt, rc, rt, cc, ct, ic, it, recordedError, recordedLog}

	ctx := appengine.NewContext(r)
	templateData := tpl.NewTemplateData(ctx, logger, args)
	templateData.Subtitle = "Status"
	return tpl.Render(w, tpl.Status, templateData)
}

func renderPing(w http.ResponseWriter, r *http.Request) {
	if err := ping(w, r); err != nil {
		ctx := appengine.NewContext(r)
		ctx.Errorf(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func ping(w http.ResponseWriter, r *http.Request) error {
	preventCaching(w);

	sw := watch.NewStopWatch()
	//err := db.Ping()
	row := db.QueryRow("SELECT 42")

	var _42 int
	if err := row.Scan(&_42); err != nil {
		return err
	}

	if _42 != 42 {
		return errors.New("Invalid response from DB")
	}

	args := &struct {
		Clock   string
		Time    int64
	}{sw.InitTime.String(), sw.TotalElapsedTimeMillis()}

	ctx := appengine.NewContext(r)
	log := logging.NewRecordingLogger(ctx, false)
	templateData := tpl.NewTemplateData(ctx, log, args)
	templateData.Subtitle = "Ping"
	if err := tpl.Render(w, tpl.Ping, templateData); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"

	"github.com/geodan/gost/src/configuration"
	"github.com/geodan/gost/src/database/postgis"
	"github.com/geodan/gost/src/http"
	"github.com/geodan/gost/src/mqtt"
	"github.com/geodan/gost/src/sensorthings/api"
	"github.com/geodan/gost/src/sensorthings/models"
)

func main() {
	log.Println("Starting GOST...")
	cfgFlag := flag.String("config", "config.yaml", "path of the config file")
	installFlag := flag.String("install", "", "path to the database creation file")
	flag.Parse()

	cfg := *cfgFlag
	conf, err := configuration.GetConfig(cfg)
	if err != nil {
		log.Fatal("config read error: ", err)
		return
	}

	configuration.SetEnvironmentVariables(&conf)

	database := postgis.NewDatabase(conf.Database.Host, conf.Database.Port, conf.Database.User, conf.Database.Password, conf.Database.Database, conf.Database.Schema, conf.Database.SSL, conf.Database.MaxIdleConns, conf.Database.MaxOpenConns)
	database.Start()

	// if install is supplied create database and close, if not start server
	sqlFile := *installFlag
	if len(sqlFile) != 0 {
		createDatabase(database, sqlFile)
	} else {
		mqttClient := mqtt.CreateMQTTClient(conf.MQTT)
		stAPI := api.NewAPI(database, conf, mqttClient)
		mqttClient.Start(&stAPI)
		createAndStartServer(&stAPI)
	}
}

func createDatabase(db models.Database, sqlFile string) {
	log.Println("--CREATING DATABASE--")

	err := db.CreateSchema(sqlFile)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Database created successfully, you can start your server now")
}

// createAndStartServer creates the GOST HTTPServer and starts it
func createAndStartServer(api *models.API) {
	a := *api
	a.Start()

	gostServer := http.CreateServer(a.GetConfig().Server.Host, a.GetConfig().Server.Port, api)
	gostServer.Start()
}
<commit_msg>testing docker automatic build<commit_after>package main

import (
	"flag"
	"log"

	"github.com/geodan/gost/src/configuration"
	"github.com/geodan/gost/src/database/postgis"
	"github.com/geodan/gost/src/http"
	"github.com/geodan/gost/src/mqtt"
	"github.com/geodan/gost/src/sensorthings/api"
	"github.com/geodan/gost/src/sensorthings/models"
)

func main() {
	log.Println("Starting GOST....")
	cfgFlag := flag.String("config", "config.yaml", "path of the config file")
	installFlag := flag.String("install", "", "path to the database creation file")
	flag.Parse()

	cfg := *cfgFlag
	conf, err := configuration.GetConfig(cfg)
	if err != nil {
		log.Fatal("config read error: ", err)
		return
	}

	configuration.SetEnvironmentVariables(&conf)

	database := postgis.NewDatabase(conf.Database.Host, conf.Database.Port, conf.Database.User, conf.Database.Password, conf.Database.Database, conf.Database.Schema, conf.Database.SSL, conf.Database.MaxIdleConns, conf.Database.MaxOpenConns)
	database.Start()

	// if install is supplied create database and close, if not start server
	sqlFile := *installFlag
	if len(sqlFile) != 0 {
		createDatabase(database, sqlFile)
	} else {
		mqttClient := mqtt.CreateMQTTClient(conf.MQTT)
		stAPI := api.NewAPI(database, conf, mqttClient)
		mqttClient.Start(&stAPI)
		createAndStartServer(&stAPI)
	}
}

func createDatabase(db models.Database, sqlFile string) {
	log.Println("--CREATING DATABASE--")

	err := db.CreateSchema(sqlFile)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Database created successfully, you can start your server now")
}

// createAndStartServer creates the GOST HTTPServer and starts it
func createAndStartServer(api *models.API) {
	a := *api
	a.Start()

	gostServer := http.CreateServer(a.GetConfig().Server.Host, a.GetConfig().Server.Port, api)
	gostServer.Start()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"time"

	twodee "../libs/twodee"
	"github.com/kurrik/tmxgo"
)

type Level struct {
	Height                      float32
	Grids                       []*twodee.Grid
	Items                       [][]*Item
	Geometry                    []*twodee.Batch
	GridRatios                  []float32
	Layers                      int32
	Player                      *Player
	Active                      int32
	Transitions                 []*LinearTween
	eventSystem                 *twodee.GameEventHandler
	onPlayerMoveEventId         int
	onPlayerPickedUpItemEventId int
	WaterAccumulation           time.Duration
}

func LoadLevel(path string, names []string, eventSystem *twodee.GameEventHandler) (l *Level, err error) {
	var player = NewPlayer(2, 2)
	l = &Level{
		Height:            0,
		Grids:             []*twodee.Grid{},
		Items:             [][]*Item{},
		Geometry:          []*twodee.Batch{},
		GridRatios:        []float32{},
		Layers:            0,
		Active:            0,
		Player:            player,
		eventSystem:       eventSystem,
		WaterAccumulation: 0,
	}
	l.onPlayerMoveEventId = eventSystem.AddObserver(PlayerMove, l.OnPlayerMoveEvent)
	l.onPlayerPickedUpItemEventId = eventSystem.AddObserver(PlayerPickedUpItem, l.OnPlayerPickedUpItemEvent)
	for _, name := range names {
		if err = l.loadLayer(path, name); err != nil {
			return
		}
	}
	return
}

const LevelWaterThreshold time.Duration = time.Duration(30) * time.Second

type LayerWaterStatus int

const (
	Dry LayerWaterStatus = iota
	Wet
	Flooded
)

const PxPerUnit float32 = 16.0

func (l *Level) loadLayer(path, name string) (err error) {
	var (
		tilemeta twodee.TileMetadata
		maptiles []*tmxgo.Tile
		textiles []twodee.TexturedTile
		maptile  *tmxgo.Tile
		m        *tmxgo.Map
		i        int
		data     []byte
		height   float32
		grid     *twodee.Grid
		items    []*Item
		batch    *twodee.Batch
		ratio    float32
	)
	path = filepath.Join(filepath.Dir(path), name)
	if data, err = ioutil.ReadFile(path); err != nil {
		return
	}
	if m, err = tmxgo.ParseMapString(string(data)); err != nil {
		return
	}
	if path, err = getTexturePath(m, path); err != nil {
		return
	}
	tilemeta = twodee.TileMetadata{
		Path:      path,
		PxPerUnit: int(PxPerUnit),
	}
	if maptiles, err = m.TilesFromLayerName("entities"); err != nil {
		return
	}
	for i, maptile = range maptiles {
		if maptile != nil {
			itemId := ItemId(maptile.Index)
			items = append(items, NewItem(
				itemId,
				ItemIdToType[itemId],
				"item",
				(maptile.TileBounds.X+maptile.TileBounds.W)/PxPerUnit,
				(maptile.TileBounds.Y+maptile.TileBounds.H)/PxPerUnit,
				maptile.TileBounds.W/PxPerUnit,
				maptile.TileBounds.H/PxPerUnit,
			))
		}
	}
	if maptiles, err = m.TilesFromLayerName("collision"); err != nil {
		return
	}
	grid = twodee.NewGrid(m.Width, m.Height)
	for i, maptile = range maptiles {
		if maptile != nil {
			grid.SetIndex(int32(i), true)
		}
	}
	if maptiles, err = m.TilesFromLayerName("tiles"); err != nil {
		return
	}
	textiles = make([]twodee.TexturedTile, len(maptiles))
	for i, maptile = range maptiles {
		if maptile != nil {
			textiles[i] = maptile
		}
	}
	if batch, err = twodee.LoadBatch(textiles, tilemeta); err != nil {
		return
	}
	//batch.SetTextureOffsetPx(0, 16)
	ratio = float32(grid.Width) * PxPerUnit / float32(m.TileWidth*m.Width)
	height = float32(grid.Height) / ratio
	if l.Height < height {
		l.Height = height
	}
	l.Grids = append(l.Grids, grid)
	l.Items = append(l.Items, items)
	l.Geometry = append(l.Geometry, batch)
	l.Layers += 1
	l.Transitions = append(l.Transitions, nil)
	l.GridRatios = append(l.GridRatios, ratio)
	return
}

func getTexturePath(m *tmxgo.Map, path string) (out string, err error) {
	var prefix = filepath.Dir(path)
	for i := 0; i < len(m.Tilesets); i++ {
		if m.Tilesets[i].Image == nil {
			continue
		}
		out = filepath.Join(prefix, m.Tilesets[i].Image.Source)
		return
	}
	err = fmt.Errorf("Could not find suitable tileset")
	return
}

func (l *Level) Delete() {
	var i int32
	for i = 0; i < l.Layers; i++ {
		l.Geometry[i].Delete()
	}
	l.eventSystem.RemoveObserver(PlayerMove, l.onPlayerMoveEventId)
	l.eventSystem.RemoveObserver(PlayerPickedUpItem, l.onPlayerPickedUpItemEventId)
}

func (l *Level) OnPlayerMoveEvent(e twodee.GETyper) {
	if move, ok := e.(*PlayerMoveEvent); ok {
		l.Player.UpdateDesiredMove(move.Dir, move.Inverse)
		//		l.Player.DesiredMove = move.Dir
	}
}

func (l *Level) OnPlayerPickedUpItemEvent(e twodee.GETyper) {
	if !l.Player.CanGetItem {
		return
	}
	if pickup, ok := e.(*PlayerPickedUpItemEvent); ok {
		l.Player.CanGetItem = false
		switch pickup.Item.Type {
		case LayerThresholdItem:
			l.Player.MoveTo(pickup.Item.Pos())
			l.Player.CanMove = false
			switch pickup.Item.Id {
			case ItemUp:
				l.LayerRewind()
			case ItemDown:
				l.LayerAdvance()
			}
		case InventoryItem:
			l.Player.AddToInventory(pickup.Item)
		}
	}
}

func (l *Level) GridAlignedX(layer int32, p twodee.Point) twodee.Point {
	var (
		ratio = l.GridRatios[layer]
		x     = int32(p.X*ratio + 0.5)
	)
	return twodee.Pt(float32(x)/ratio, p.Y)
}

func (l *Level) GridAlignedY(layer int32, p twodee.Point) twodee.Point {
	var (
		ratio = l.GridRatios[layer]
		y     = int32(p.Y*ratio + 0.5)
	)
	return twodee.Pt(p.X, float32(y)/ratio)
}

// Given points a,b defining the leading edge of a moving entity; determine
// if there is a collision with something on the grid.
func (l *Level) FrontierCollides(layer int32, a, b twodee.Point) bool {
	var (
		ratio = l.GridRatios[layer]
		xmin  = int32(a.X * ratio)
		xmax  = int32(b.X * ratio)
		ymin  = int32(a.Y * ratio)
		ymax  = int32(b.Y * ratio)
	)
	// fmt.Printf("X %v-%v, Y %v-%v\n", xmin, xmax, ymin, ymax)
	for x := xmin; x <= xmax; x++ {
		for y := ymin; y <= ymax; y++ {
			// fmt.Printf("Checking X %v Y %v\n", x, y)
			if l.Grids[layer].Get(x, y) == true {
				return true
			}
		}
	}
	playerBounds := l.Player.Bounds()
	touchedItem := false
	for _, item := range l.Items[layer] {
		if playerBounds.Overlaps(item.Bounds()) {
			l.eventSystem.Enqueue(NewPlayerPickedUpItemEvent(item))
			touchedItem = true
			break
		}
	}
	// TODO(wes): Maybe remove this once items go away after pickup.
	if !touchedItem {
		// Prevent the player from triggering another item
		// pickup until they've moved off of all items
		l.Player.CanGetItem = true
	}
	return false
}

func (l *Level) GetLayerY(index int32) float32 {
	var tween = l.Transitions[index]
	if tween != nil {
		return tween.Current()
	}
	switch {
	case index > l.Active:
		return -1
	case index < l.Active:
		return l.Height
	case index == l.Active:
		return 0
	}
	return 0
}

const TopSlideSpeed = time.Duration(320) * time.Millisecond
const BotSlideSpeed = time.Duration(320) * time.Millisecond

func (l *Level) LayerAdvance() {
	if l.Active >= l.Layers-1 {
		return
	}
	l.Transitions[l.Active] = NewLinearTween(0, l.Height, TopSlideSpeed)
	l.Player.SetState(Standing | Down)
	l.Transitions[l.Active].SetCallback(func() {
		l.Player.CanMove = true
	})
	l.Active++
	l.Transitions[l.Active] = NewLinearTween(-1, 0, BotSlideSpeed)
}

func (l *Level) LayerRewind() {
	if l.Active <= 0 {
		return
	}
	l.Transitions[l.Active-1] = NewLinearTween(l.Height, 0, TopSlideSpeed)
	l.Transitions[l.Active-1].SetCallback(func() {
		l.Active--
		l.Player.SetState(ClimbUp | Down)
		l.Player.SetCallback(func() {
			l.Player.CanMove = true
			l.Player.SetState(Standing | Down)
		})
	})
	l.Transitions[l.Active] = NewLinearTween(0, -1, BotSlideSpeed)
}

func (l *Level) Update(elapsed time.Duration) {
	var i int32
	for i = 0; i < l.Layers; i++ {
		if l.Transitions[i] != nil {
			if l.Transitions[i].Done() {
				l.Transitions[i] = nil
			} else {
				l.Transitions[i].Update(elapsed)
			}
		}
	}
	l.Player.Update(elapsed)
	l.Player.AttemptMove(l)
	l.WaterAccumulation += elapsed
}

func (l *Level) GetLayerWaterStatus(layer int32) LayerWaterStatus {
	var percentFlooded = (int32)(l.WaterAccumulation / LevelWaterThreshold)
	if percentFlooded >= 1 {
		return Flooded
	}
	var layerLevelBottom = (l.Layers - layer) / l.Layers
	var layerLevelTop = ((l.Layers - layer) + 1) / l.Layers
	if percentFlooded >= layerLevelTop {
		return Flooded
	} else if percentFlooded >= layerLevelBottom {
		return Wet
	}
	return Dry
}
<commit_msg>Items can now be removed from a layer's slice of Items; hence they disappear on pickup.<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"time"

	twodee "../libs/twodee"
	"github.com/kurrik/tmxgo"
)

type Level struct {
	Height                      float32
	Grids                       []*twodee.Grid
	Items                       [][]*Item
	Geometry                    []*twodee.Batch
	GridRatios                  []float32
	Layers                      int32
	Player                      *Player
	Active                      int32
	Transitions                 []*LinearTween
	eventSystem                 *twodee.GameEventHandler
	onPlayerMoveEventId         int
	onPlayerPickedUpItemEventId int
	WaterAccumulation           time.Duration
}

func LoadLevel(path string, names []string, eventSystem *twodee.GameEventHandler) (l *Level, err error) {
	var player = NewPlayer(2, 2)
	l = &Level{
		Height:            0,
		Grids:             []*twodee.Grid{},
		Items:             [][]*Item{},
		Geometry:          []*twodee.Batch{},
		GridRatios:        []float32{},
		Layers:            0,
		Active:            0,
		Player:            player,
		eventSystem:       eventSystem,
		WaterAccumulation: 0,
	}
	l.onPlayerMoveEventId = eventSystem.AddObserver(PlayerMove, l.OnPlayerMoveEvent)
	l.onPlayerPickedUpItemEventId = eventSystem.AddObserver(PlayerPickedUpItem, l.OnPlayerPickedUpItemEvent)
	for _, name := range names {
		if err = l.loadLayer(path, name); err != nil {
			return
		}
	}
	return
}

const LevelWaterThreshold time.Duration = time.Duration(30) * time.Second

type LayerWaterStatus int

const (
	Dry LayerWaterStatus = iota
	Wet
	Flooded
)

const PxPerUnit float32 = 16.0

func (l *Level) loadLayer(path, name string) (err error) {
	var (
		tilemeta twodee.TileMetadata
		maptiles []*tmxgo.Tile
		textiles []twodee.TexturedTile
		maptile  *tmxgo.Tile
		m        *tmxgo.Map
		i        int
		data     []byte
		height   float32
		grid     *twodee.Grid
		items    []*Item
		batch    *twodee.Batch
		ratio    float32
	)
	path = filepath.Join(filepath.Dir(path), name)
	if data, err = ioutil.ReadFile(path); err != nil {
		return
	}
	if m, err = tmxgo.ParseMapString(string(data)); err != nil {
		return
	}
	if path, err = getTexturePath(m, path); err != nil {
		return
	}
	tilemeta = twodee.TileMetadata{
		Path:      path,
		PxPerUnit: int(PxPerUnit),
	}
	if maptiles, err = m.TilesFromLayerName("entities"); err != nil {
		return
	}
	for i, maptile = range maptiles {
		if maptile != nil {
			itemId := ItemId(maptile.Index)
			items = append(items, NewItem(
				itemId,
				ItemIdToType[itemId],
				"item",
				(maptile.TileBounds.X+maptile.TileBounds.W)/PxPerUnit,
				(maptile.TileBounds.Y+maptile.TileBounds.H)/PxPerUnit,
				maptile.TileBounds.W/PxPerUnit,
				maptile.TileBounds.H/PxPerUnit,
			))
		}
	}
	if maptiles, err = m.TilesFromLayerName("collision"); err != nil {
		return
	}
	grid = twodee.NewGrid(m.Width, m.Height)
	for i, maptile = range maptiles {
		if maptile != nil {
			grid.SetIndex(int32(i), true)
		}
	}
	if maptiles, err = m.TilesFromLayerName("tiles"); err != nil {
		return
	}
	textiles = make([]twodee.TexturedTile, len(maptiles))
	for i, maptile = range maptiles {
		if maptile != nil {
			textiles[i] = maptile
		}
	}
	if batch, err = twodee.LoadBatch(textiles, tilemeta); err != nil {
		return
	}
	//batch.SetTextureOffsetPx(0, 16)
	ratio = float32(grid.Width) * PxPerUnit / float32(m.TileWidth*m.Width)
	height = float32(grid.Height) / ratio
	if l.Height < height {
		l.Height = height
	}
	l.Grids = append(l.Grids, grid)
	l.Items = append(l.Items, items)
	l.Geometry = append(l.Geometry, batch)
	l.Layers += 1
	l.Transitions = append(l.Transitions, nil)
	l.GridRatios = append(l.GridRatios, ratio)
	return
}

func getTexturePath(m *tmxgo.Map, path string) (out string, err error) {
	var prefix = filepath.Dir(path)
	for i := 0; i < len(m.Tilesets); i++ {
		if m.Tilesets[i].Image == nil {
			continue
		}
		out = filepath.Join(prefix, m.Tilesets[i].Image.Source)
		return
	}
	err = fmt.Errorf("Could not find suitable tileset")
	return
}

func (l *Level) Delete() {
	var i int32
	for i = 0; i < l.Layers; i++ {
		l.Geometry[i].Delete()
	}
	l.eventSystem.RemoveObserver(PlayerMove, l.onPlayerMoveEventId)
	l.eventSystem.RemoveObserver(PlayerPickedUpItem, l.onPlayerPickedUpItemEventId)
}

func (l *Level) OnPlayerMoveEvent(e twodee.GETyper) {
	if move, ok := e.(*PlayerMoveEvent); ok {
		l.Player.UpdateDesiredMove(move.Dir, move.Inverse)
		//		l.Player.DesiredMove = move.Dir
	}
}

func (l *Level) OnPlayerPickedUpItemEvent(e twodee.GETyper) {
	if !l.Player.CanGetItem {
		return
	}
	if pickup, ok := e.(*PlayerPickedUpItemEvent); ok {
		l.Player.CanGetItem = false
		switch pickup.Item.Type {
		case LayerThresholdItem:
			l.Player.MoveTo(pickup.Item.Pos())
			l.Player.CanMove = false
			switch pickup.Item.Id {
			case ItemUp:
				l.LayerRewind()
			case ItemDown:
				l.LayerAdvance()
			}
		case InventoryItem:
			l.RemoveItem(pickup.Item)
			l.Player.AddToInventory(pickup.Item)
		}
	}
}

// Removes the item from the current layer's Items slice.
func (l *Level) RemoveItem(item *Item) {
	layerItems := l.Items[l.Active]
	index := -1
	for i, levelItem := range layerItems {
		if levelItem == item {
			index = i
			break
		}
	}
	if index != -1 {
		layerItems = append(layerItems[:index], layerItems[index+1:]...)
	}
}

func (l *Level) GridAlignedX(layer int32, p twodee.Point) twodee.Point {
	var (
		ratio = l.GridRatios[layer]
		x     = int32(p.X*ratio + 0.5)
	)
	return twodee.Pt(float32(x)/ratio, p.Y)
}

func (l *Level) GridAlignedY(layer int32, p twodee.Point) twodee.Point {
	var (
		ratio = l.GridRatios[layer]
		y     = int32(p.Y*ratio + 0.5)
	)
	return twodee.Pt(p.X, float32(y)/ratio)
}

// Given points a,b defining the leading edge of a moving entity; determine
// if there is a collision with something on the grid.
func (l *Level) FrontierCollides(layer int32, a, b twodee.Point) bool {
	var (
		ratio = l.GridRatios[layer]
		xmin  = int32(a.X * ratio)
		xmax  = int32(b.X * ratio)
		ymin  = int32(a.Y * ratio)
		ymax  = int32(b.Y * ratio)
	)
	// fmt.Printf("X %v-%v, Y %v-%v\n", xmin, xmax, ymin, ymax)
	for x := xmin; x <= xmax; x++ {
		for y := ymin; y <= ymax; y++ {
			// fmt.Printf("Checking X %v Y %v\n", x, y)
			if l.Grids[layer].Get(x, y) == true {
				return true
			}
		}
	}
	playerBounds := l.Player.Bounds()
	touchedItem := false
	for _, item := range l.Items[layer] {
		if playerBounds.Overlaps(item.Bounds()) {
			l.eventSystem.Enqueue(NewPlayerPickedUpItemEvent(item))
			touchedItem = true
			break
		}
	}
	// TODO(wes): Maybe remove this once items go away after pickup.
	if !touchedItem {
		// Prevent the player from triggering another item
		// pickup until they've moved off of all items
		l.Player.CanGetItem = true
	}
	return false
}

func (l *Level) GetLayerY(index int32) float32 {
	var tween = l.Transitions[index]
	if tween != nil {
		return tween.Current()
	}
	switch {
	case index > l.Active:
		return -1
	case index < l.Active:
		return l.Height
	case index == l.Active:
		return 0
	}
	return 0
}

const TopSlideSpeed = time.Duration(320) * time.Millisecond
const BotSlideSpeed = time.Duration(320) * time.Millisecond

func (l *Level) LayerAdvance() {
	if l.Active >= l.Layers-1 {
		return
	}
	l.Transitions[l.Active] = NewLinearTween(0, l.Height, TopSlideSpeed)
	l.Player.SetState(Standing | Down)
	l.Transitions[l.Active].SetCallback(func() {
		l.Player.CanMove = true
	})
	l.Active++
	l.Transitions[l.Active] = NewLinearTween(-1, 0, BotSlideSpeed)
}

func (l *Level) LayerRewind() {
	if l.Active <= 0 {
		return
	}
	l.Transitions[l.Active-1] = NewLinearTween(l.Height, 0, TopSlideSpeed)
	l.Transitions[l.Active-1].SetCallback(func() {
		l.Active--
		l.Player.SetState(ClimbUp | Down)
		l.Player.SetCallback(func() {
			l.Player.CanMove = true
			l.Player.SetState(Standing | Down)
		})
	})
	l.Transitions[l.Active] = NewLinearTween(0, -1, BotSlideSpeed)
}

func (l *Level) Update(elapsed time.Duration) {
	var i int32
	for i = 0; i < l.Layers; i++ {
		if l.Transitions[i] != nil {
			if l.Transitions[i].Done() {
				l.Transitions[i] = nil
			} else {
				l.Transitions[i].Update(elapsed)
			}
		}
	}
	l.Player.Update(elapsed)
	l.Player.AttemptMove(l)
	l.WaterAccumulation += elapsed
}

func (l *Level) GetLayerWaterStatus(layer int32) LayerWaterStatus {
	var percentFlooded = (int32)(l.WaterAccumulation / LevelWaterThreshold)
	if percentFlooded >= 1 {
		return Flooded
	}
	var layerLevelBottom = (l.Layers - layer) / l.Layers
	var layerLevelTop = ((l.Layers - layer) + 1) / l.Layers
	if percentFlooded >= layerLevelTop {
		return Flooded
	} else if percentFlooded >= layerLevelBottom {
		return Wet
	}
	return Dry
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"regexp"
	"repo"
)

const (
	TodoRegex = "[^[:alpha:]](t|T)(o|O)(d|D)(o|O)[^[:alpha:]]"
)

func LoadTodos(repository repo.Repository, revision repo.Revision) []repo.Line {
	todos := make([]repo.Line, 0)
	for _, path := range repository.ReadRevisionContents(revision).Paths {
		for _, line := range repository.ReadFileAtRevision(revision, path) {
			matched, err := regexp.MatchString(TodoRegex, line.Contents)
			if err == nil && matched {
				todos = append(todos, line)
			}
		}
	}
	return todos
}

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "")
	gitRepository := repo.GitRepository{}
	for _, alias := range gitRepository.ListBranches() {
		fmt.Fprintf(w, "
Branch: \"%s\",\tRevision: \"%s\"\n",
			alias.Branch, string(alias.Revision))
		fmt.Fprintf(w, "
\n")
		for _, todoLine := range LoadTodos(gitRepository, alias.Revision) {
			fmt.Fprintf(w, "
\"%s\"
\n", todoLine.Contents)
		}
		fmt.Fprintf(w, "
\n")
	}
	fmt.Fprintf(w, "")
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}
<commit_msg>HTML escape TODO lines so that TODOs in HTML files show up properly<commit_after>package main

import (
	"fmt"
	"html"
	"net/http"
	"regexp"
	"repo"
)

const (
	TodoRegex = "[^[:alpha:]](t|T)(o|O)(d|D)(o|O)[^[:alpha:]]"
)

func LoadTodos(repository repo.Repository, revision repo.Revision) []repo.Line {
	todos := make([]repo.Line, 0)
	for _, path := range repository.ReadRevisionContents(revision).Paths {
		for _, line := range repository.ReadFileAtRevision(revision, path) {
			matched, err := regexp.MatchString(TodoRegex, line.Contents)
			if err == nil && matched {
				todos = append(todos, line)
			}
		}
	}
	return todos
}

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "")
	gitRepository := repo.GitRepository{}
	for _, alias := range gitRepository.ListBranches() {
		fmt.Fprintf(w, "
Branch: \"%s\",\tRevision: \"%s\"\n",
			alias.Branch, string(alias.Revision))
		fmt.Fprintf(w, "
\n")
		for _, todoLine := range LoadTodos(gitRepository, alias.Revision) {
			fmt.Fprintf(w, "
\"%s\"
\n", html.EscapeString(todoLine.Contents))
		}
		fmt.Fprintf(w, "
\n")
	}
	fmt.Fprintf(w, "")
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/glendc/cgreader"
	"math"
	"strings"
)

type Station struct {
	name                string
	longitude, latitude float64
}

type Destination struct {
	hash uint32
	cost uint16
}

var hashMap map[uint32]string
var identifierMap map[string]uint32

func GetInput(input <-chan string) string {
	line := <-input
	return string(line[9:])
}

func ToFloat(str string) (x float64) {
	fmt.Sscanf(str, "%f", &x)
	return
}

func GetCost(lo_a, lo_b, la_a, la_b float64) uint16 {
	x, y := (lo_b-lo_a)*math.Cos((la_a+la_b)/2), la_b-la_a
	return uint16((x*x + y*y) * 100000.0)
}

var minCost uint16 = math.MaxUint16
var routes map[uint32][]Destination
var finalHash, startHash uint32
var finalRoute []uint32
var stationsMC map[uint32]uint16

func TravelRecursive(cost uint16, route []uint32) {
	for _, destination := range routes[route[len(route)-1]] {
		if cost += destination.cost; cost < minCost {
			mcValue, mcOK := stationsMC[destination.hash]
			if (mcOK && cost < mcValue) || !mcOK {
				stationsMC[destination.hash] = cost
				if destination.hash == finalHash {
					minCost = cost
					finalRoute = append(route, destination.hash)
				} else {
					TravelRecursive(cost, append(route, destination.hash))
				}
			}
		}
	}
}

func main() {
	cgreader.RunStaticPrograms(
		cgreader.GetFileList("../../input/tan_network_%d.txt", 6),
		cgreader.GetFileList("../../output/tan_network_%d.txt", 6),
		true,
		func(input <-chan string, output chan string) {
			// this block could be ommited when solo-running
			minCost = math.MaxUint16
			routes, finalRoute = nil, nil

			start, stop := GetInput(input), GetInput(input)
			hashMap = make(map[uint32]string)
			identifierMap = make(map[string]uint32)
			stationsMC = make(map[uint32]uint16)

			var ns, nr uint32
			fmt.Sscanf(<-input, "%d", &ns)
			stations := make(map[uint32]Station)
			for i := uint32(0); i < ns; i++ {
				station := GetInput(input)
				info := strings.Split(station, ",")
				hashMap[i] = info[0]
				identifierMap[info[0]] = i
				stations[i] = Station{
					info[1][1 : len(info[1])-1],
					ToFloat(info[3]),
					ToFloat(info[4])}
			}

			startHash, finalHash = identifierMap[start], identifierMap[stop]

			if startHash == finalHash {
				output <- stations[startHash].name
				return
			}

			fmt.Sscanf(<-input, "%d", &nr)
			routes = make(map[uint32][]Destination)
			for i := uint32(0); i < nr; i++ {
				route := GetInput(input)
				ra, ro := string(route[:4]), string(route[14:])
				ha, ho := identifierMap[ra], identifierMap[ro]

				a, b := stations[ha], stations[ho]
				cost := GetCost(a.latitude, b.latitude, a.longitude, b.longitude)

				routes[ha] = append(routes[ha], Destination{ho, cost})
			}

			TravelRecursive(0, append(make([]uint32, 0), startHash))

			if finalRoute == nil {
				output <- "IMPOSSIBLE"
			} else {
				for _, hash := range finalRoute {
					output <- stations[hash].name
				}
			}
		})
}
<commit_msg>bugfix tan_network<commit_after>package main

import (
	"fmt"
	"github.com/glendc/cgreader"
	"math"
	"strings"
)

type Station struct {
	name                string
	longitude, latitude float64
}

type Destination struct {
	hash uint32
	cost uint16
}

var hashMap map[uint32]string
var identifierMap map[string]uint32

func GetInput(input <-chan string) string {
	line := <-input
	return string(line[9 : len(line)-1])
}

func ToFloat(str string) (x float64) {
	fmt.Sscanf(str, "%f", &x)
	return
}

func GetCost(lo_a, lo_b, la_a, la_b float64) uint16 {
	x, y := (lo_b-lo_a)*math.Cos((la_a+la_b)/2), la_b-la_a
	return uint16((x*x + y*y) * 100000.0)
}

var minCost uint16 = math.MaxUint16
var routes map[uint32][]Destination
var finalHash, startHash uint32
var finalRoute []uint32
var stationsMC map[uint32]uint16

func TravelRecursive(cost uint16, route []uint32) {
	for _, destination := range routes[route[len(route)-1]] {
		if cost += destination.cost; cost < minCost {
			mcValue, mcOK := stationsMC[destination.hash]
			if (mcOK && cost < mcValue) || !mcOK {
				stationsMC[destination.hash] = cost
				if destination.hash == finalHash {
					minCost = cost
					finalRoute = append(route, destination.hash)
				} else {
					TravelRecursive(cost, append(route, destination.hash))
				}
			}
		}
	}
}

func main() {
	cgreader.RunStaticPrograms(
		cgreader.GetFileList("../../input/tan_network_%d.txt", 6),
		cgreader.GetFileList("../../output/tan_network_%d.txt", 6),
		true,
		func(input <-chan string, output chan string) {
			// this block could be ommited when solo-running
			minCost = math.MaxUint16
			routes, finalRoute = nil, nil

			start, stop := GetInput(input), GetInput(input)
			hashMap = make(map[uint32]string)
			identifierMap = make(map[string]uint32)
			stationsMC = make(map[uint32]uint16)

			var ns, nr uint32
			fmt.Sscanf(<-input, "%d", &ns)
			stations := make(map[uint32]Station)
			for i := uint32(0); i < ns; i++ {
				station := GetInput(input)
				info := strings.Split(station, ",")
				hashMap[i] = info[0]
				identifierMap[info[0]] = i
				stations[i] = Station{
					info[1][1 : len(info[1])-1],
					ToFloat(info[3]),
					ToFloat(info[4])}
			}

			startHash, finalHash = identifierMap[start], identifierMap[stop]

			if startHash == finalHash {
				output <- stations[startHash].name
				return
			}

			fmt.Sscanf(<-input, "%d", &nr)
			routes = make(map[uint32][]Destination)
			for i := uint32(0); i < nr; i++ {
				route := GetInput(input)
				ra, ro := string(route[:4]), string(route[14:])
				ha, ho := identifierMap[ra], identifierMap[ro]

				a, b := stations[ha], stations[ho]
				cost := GetCost(a.latitude, b.latitude, a.longitude, b.longitude)

				routes[ha] = append(routes[ha], Destination{ho, cost})
			}

			TravelRecursive(0, append(make([]uint32, 0), startHash))

			if finalRoute == nil {
				output <- "IMPOSSIBLE\n"
			} else {
				for _, hash := range finalRoute {
					output <- fmt.Sprintln(stations[hash].name)
				}
			}
		})
}
<|endoftext|>