<commit_before>package mathgl

import "math"

// Rotate2D returns a rotation Matrix of type FLOAT64 about a (radian) angle in 2-D space. Specifically about the origin.
// It is a 2x2 matrix, if you need a 3x3 for Homogeneous math (e.g. composition with a Translation matrix)
// see HomogRotate2D
func Rotate2D(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{cos, -sin,
		sin, cos}, FLOAT64), FLOAT64, 2, 2)
}

// Rotate3DX returns a 3x3 (non-homogeneous) Matrix of type FLOAT64 that rotates by (radian) angle about the X-axis
//
// Where c is cos(angle) and s is sin(angle)
//    [1 0 0]
//    [0 c -s]
//    [0 s c ]
func Rotate3DX(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		1, 0, 0,
		0, cos, -sin,
		0, sin, cos}, FLOAT64), FLOAT64, 3, 3)
}

// Rotate3DY returns a 3x3 (non-homogeneous) Matrix of type FLOAT64 that rotates by (radian) angle about the Y-axis
//
// Where c is cos(angle) and s is sin(angle)
//    [c 0 s]
//    [0 1 0]
//    [s 0 c ]
func Rotate3DY(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		cos, 0, sin,
		0, 1, 0,
		-sin, 0, cos}, FLOAT64), FLOAT64, 3, 3)
}

// Rotate3DZ returns a 3x3 (non-homogeneous) Matrix of type FLOAT64 that rotates by (radian) angle about the Z-axis
//
// Where c is cos(angle) and s is sin(angle)
//    [c -s 0]
//    [s c 0]
//    [0 0 1 ]
func Rotate3DZ(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		cos, -sin, 0,
		-sin, cos, 0,
		0, 0, 1}, FLOAT64), FLOAT64, 3, 3)
}

// Translate2D returns a homogeneous (3x3 for 2D-space) Translation matrix of type FLOAT64 that moves a point by Tx units in the x-direction and Ty units in the y-direction
//
//    [[1, 0, Tx]]
//    [[0, 1, Ty]]
//    [[0, 0, 1 ]]
func Translate2D(Tx, Ty float64) Matrix {
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		1, 0, Tx,
		0, 1, Ty,
		0, 0, 1}, FLOAT64), FLOAT64, 3, 3)
}

// Translate3D returns a homogeneous (4x4 for 3D-space) Translation matrix of type FLOAT64 that moves a point by Tx units in the x-direction, Ty units in the y-direction,
// and Tz units in the z-direction
//
//    [[1, 0, 0, Tx]]
//    [[0, 1, 0, Ty]]
//    [[0, 0, 1, Tz]]
//    [[0, 0, 0, 1 ]]
func Translate3D(Tx, Ty, Tz float64) Matrix {
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		1, 0, 0, Tx,
		0, 1, 0, Ty,
		0, 0, 1, Tz,
		0, 0, 0, 1}, FLOAT64), FLOAT64, 4, 4)
}

// Same as Rotate2D, except homogeneous (3x3 with the extra row/col being all zeroes with a one in the bottom right)
func HomogRotate2D(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		cos, -sin, 0,
		sin, cos, 0,
		0, 0, 1}, FLOAT64), FLOAT64, 3, 3)
}

// Same as Rotate3DX, except homogeneous (4x4 with the extra row/col being all zeroes with a one in the bottom right)
func HomogRotate3DX(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		1, 0, 0, 0,
		0, cos, -sin, 0,
		0, sin, cos, 0,
		0, 0, 0, 1}, FLOAT64), FLOAT64, 4, 4)
}

// Same as Rotate3DY, except homogeneous (4x4 with the extra row/col being all zeroes with a one in the bottom right)
func HomogRotate3DY(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		cos, 0, sin, 0,
		0, 1, 0, 0,
		-sin, 0, cos, 0,
		0, 0, 0, 1}, FLOAT64), FLOAT64, 4, 4)
}

// Same as Rotate3DZ, except homogeneous (4x4 with the extra row/col being all zeroes with a one in the bottom right)
func HomogRotate3DZ(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		cos, -sin, 0, 0,
		-sin, cos, 0, 0,
		0, 0, 1, 0,
		0, 0, 0, 1}, FLOAT64), FLOAT64, 4, 4)
}

// HomogRotate3D creates a 3D rotation Matrix of type FLOAT64 that rotates by (radian) angle about some arbitrary axis given by a Vector.
// It produces a homogeneous matrix (4x4)
//
// Where c is cos(angle) and s is sin(angle), and x, y, and z are the first, second, and third elements of the axis vector (respectively):
//
//    [[ x^2(c-1)+c, xy(c-1)-zs, xz(c-1)+ys, 0 ]]
//    [[ xy(c-1)+zs, y^2(c-1)+c, yz(c-1)-xs, 0 ]]
//    [[ xz(c-1)-ys, yz(c-1)+xs, z^2(c-1)+c, 0 ]]
//    [[ 0         , 0         , 0         , 1 ]]
//
// The axis vector's type must be FLOAT64 or you'll get the zero-type Matrix
func HomogRotate3D(angle float64, axis Vector) Matrix {
	if (axis.Size() != 4 && axis.Size() != 3) || axis.typ != FLOAT64 {
		return Matrix{}
	}
	x, y, z := axis.dat[0].Fl64(), axis.dat[1].Fl64(), axis.dat[2].Fl64()
	s, c := math.Sin(angle), math.Cos(angle)
	k := 1 - c
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		x*x*k + c, x*y*k - z*s, x*z*k + y*s, 0,
		x*y*k + z*s, y*y*k + c, y*z*k - x*s, 0,
		x*z*k - y*s, y*z*k + x*s, z*z*k + c, 0,
		0, 0, 0, 1}, FLOAT64), FLOAT64, 4, 4)
}

// QuaternionRotation creates a Quaternion of type FLOAT64 that represents a rotation about the axis given by a Vector by (radian) angle
//
// Where s is sin(angle) and c is cos(angle), and x,y,z and the first, second, and third elements of the axis vector (respectively), the Quaternion is represented by:
//
// (c/2 * 1) + (s/2 * x * i) + (s/2 * y * j) + (s/2 * z * k)
func QuaternionRotation(angle float64, axis Vector) Quaternion {
	if axis.typ != FLOAT64 {
		return Quaternion{}
	}

	sin, cos := math.Sin(angle), math.Cos(angle)
	return Quaternion{MakeScalar(cos/float64(2), FLOAT64), axis.ScalarMul(MakeScalar(sin/float64(2), FLOAT64)), FLOAT64}
}
<commit_msg>Added shear and scale<commit_after>package mathgl

import "math"

// Rotate2D returns a rotation Matrix of type FLOAT64 about a (radian) angle in 2-D space. Specifically about the origin.
// It is a 2x2 matrix, if you need a 3x3 for Homogeneous math (e.g. composition with a Translation matrix)
// see HomogRotate2D
func Rotate2D(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{cos, -sin,
		sin, cos}, FLOAT64), FLOAT64, 2, 2)
}

// Rotate3DX returns a 3x3 (non-homogeneous) Matrix of type FLOAT64 that rotates by (radian) angle about the X-axis
//
// Where c is cos(angle) and s is sin(angle)
//    [1 0 0]
//    [0 c -s]
//    [0 s c ]
func Rotate3DX(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		1, 0, 0,
		0, cos, -sin,
		0, sin, cos}, FLOAT64), FLOAT64, 3, 3)
}

// Rotate3DY returns a 3x3 (non-homogeneous) Matrix of type FLOAT64 that rotates by (radian) angle about the Y-axis
//
// Where c is cos(angle) and s is sin(angle)
//    [c 0 s]
//    [0 1 0]
//    [s 0 c ]
func Rotate3DY(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		cos, 0, sin,
		0, 1, 0,
		-sin, 0, cos}, FLOAT64), FLOAT64, 3, 3)
}

// Rotate3DZ returns a 3x3 (non-homogeneous) Matrix of type FLOAT64 that rotates by (radian) angle about the Z-axis
//
// Where c is cos(angle) and s is sin(angle)
//    [c -s 0]
//    [s c 0]
//    [0 0 1 ]
func Rotate3DZ(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		cos, -sin, 0,
		-sin, cos, 0,
		0, 0, 1}, FLOAT64), FLOAT64, 3, 3)
}

// Translate2D returns a homogeneous (3x3 for 2D-space) Translation matrix of type FLOAT64 that moves a point by Tx units in the x-direction and Ty units in the y-direction
//
//    [[1, 0, Tx]]
//    [[0, 1, Ty]]
//    [[0, 0, 1 ]]
func Translate2D(Tx, Ty float64) Matrix {
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		1, 0, Tx,
		0, 1, Ty,
		0, 0, 1}, FLOAT64), FLOAT64, 3, 3)
}

// Translate3D returns a homogeneous (4x4 for 3D-space) Translation matrix of type FLOAT64 that moves a point by Tx units in the x-direction, Ty units in the y-direction,
// and Tz units in the z-direction
//
//    [[1, 0, 0, Tx]]
//    [[0, 1, 0, Ty]]
//    [[0, 0, 1, Tz]]
//    [[0, 0, 0, 1 ]]
func Translate3D(Tx, Ty, Tz float64) Matrix {
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		1, 0, 0, Tx,
		0, 1, 0, Ty,
		0, 0, 1, Tz,
		0, 0, 0, 1}, FLOAT64), FLOAT64, 4, 4)
}

// Same as Rotate2D, except homogeneous (3x3 with the extra row/col being all zeroes with a one in the bottom right)
func HomogRotate2D(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		cos, -sin, 0,
		sin, cos, 0,
		0, 0, 1}, FLOAT64), FLOAT64, 3, 3)
}

// Same as Rotate3DX, except homogeneous (4x4 with the extra row/col being all zeroes with a one in the bottom right)
func HomogRotate3DX(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		1, 0, 0, 0,
		0, cos, -sin, 0,
		0, sin, cos, 0,
		0, 0, 0, 1}, FLOAT64), FLOAT64, 4, 4)
}

// Same as Rotate3DY, except homogeneous (4x4 with the extra row/col being all zeroes with a one in the bottom right)
func HomogRotate3DY(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		cos, 0, sin, 0,
		0, 1, 0, 0,
		-sin, 0, cos, 0,
		0, 0, 0, 1}, FLOAT64), FLOAT64, 4, 4)
}

// Same as Rotate3DZ, except homogeneous (4x4 with the extra row/col being all zeroes with a one in the bottom right)
func HomogRotate3DZ(angle float64) Matrix {
	sin, cos := math.Sin(angle), math.Cos(angle)
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		cos, -sin, 0, 0,
		-sin, cos, 0, 0,
		0, 0, 1, 0,
		0, 0, 0, 1}, FLOAT64), FLOAT64, 4, 4)
}

func Scale3D(scaleX, scaleY, scaleZ float64) Matrix {
	mp,_ := InferMatrixFromSlice([]interface{}{
		scaleX, 0., 0., 0.,
		0., scaleY, 0., 0.,
		0., 0., scaleZ, 0.,
		0., 0., 0., 1.}, 4, 4)
	return *mp
}

func Scale2D(scaleX, scaleY float64) Matrix {
	mp,_ := InferMatrixFromSlice([]interface{}{
		scaleX, 0., 0.,
		0., scaleY, 0.,
		0., 0., 1,}, 3, 3)
	return *mp
}

func ShearX2D(shear float64) Matrix {
	mp,_ := InferMatrixFromSlice([]interface{}{
		1., shear, 0.,
		0., 1., 0.,
		0., 0., 1,}, 3, 3)
	return *mp
}

func ShearY2D(shear float64) Matrix {
	mp,_ := InferMatrixFromSlice([]interface{}{
		1., 0., 0.,
		shear, 1., 0.,
		0., 0., 1,}, 3, 3)
	return *mp
}

func ScaleX3D(shear float64) Matrix {
	mp,_ := InferMatrixFromSlice([]interface{}{
		1., shear, shear, 0.,
		0., 1., 0., 0.,
		0., 0., 1., 0.,
		0., 0., 0., 1.}, 4, 4)
	return *mp
}

func ScaleY3D(shear float64) Matrix {
	mp,_ := InferMatrixFromSlice([]interface{}{
		1., 0., 0., 0.,
		shear, 1., shear, 0.,
		0., 0., 1., 0.,
		0., 0., 0., 1.}, 4, 4)
	return *mp
}

func ScaleZ3D(shear float64) Matrix {
	mp,_ := InferMatrixFromSlice([]interface{}{
		1., 0., 0., 0.,
		0., 1., 0., 0.,
		shear, shear, 1., 0.,
		0., 0., 0., 1.}, 4, 4)
	return *mp
}

// HomogRotate3D creates a 3D rotation Matrix of type FLOAT64 that rotates by (radian) angle about some arbitrary axis given by a Vector.
// It produces a homogeneous matrix (4x4)
//
// Where c is cos(angle) and s is sin(angle), and x, y, and z are the first, second, and third elements of the axis vector (respectively):
//
//    [[ x^2(c-1)+c, xy(c-1)-zs, xz(c-1)+ys, 0 ]]
//    [[ xy(c-1)+zs, y^2(c-1)+c, yz(c-1)-xs, 0 ]]
//    [[ xz(c-1)-ys, yz(c-1)+xs, z^2(c-1)+c, 0 ]]
//    [[ 0         , 0         , 0         , 1 ]]
//
// The axis vector's type must be FLOAT64 or you'll get the zero-type Matrix
func HomogRotate3D(angle float64, axis Vector) Matrix {
	if (axis.Size() != 4 && axis.Size() != 3) || axis.typ != FLOAT64 {
		return Matrix{}
	}
	x, y, z := axis.dat[0].Fl64(), axis.dat[1].Fl64(), axis.dat[2].Fl64()
	s, c := math.Sin(angle), math.Cos(angle)
	k := 1 - c
	return *unsafeMatrixFromSlice(ScalarSlice([]interface{}{
		x*x*k + c, x*y*k - z*s, x*z*k + y*s, 0,
		x*y*k + z*s, y*y*k + c, y*z*k - x*s, 0,
		x*z*k - y*s, y*z*k + x*s, z*z*k + c, 0,
		0, 0, 0, 1}, FLOAT64), FLOAT64, 4, 4)
}

// QuaternionRotation creates a Quaternion of type FLOAT64 that represents a rotation about the axis given by a Vector by (radian) angle
//
// Where s is sin(angle) and c is cos(angle), and x,y,z and the first, second, and third elements of the axis vector (respectively), the Quaternion is represented by:
//
// (c/2 * 1) + (s/2 * x * i) + (s/2 * y * j) + (s/2 * z * k)
func QuaternionRotation(angle float64, axis Vector) Quaternion {
	if axis.typ != FLOAT64 {
		return Quaternion{}
	}

	sin, cos := math.Sin(angle), math.Cos(angle)
	return Quaternion{MakeScalar(cos/float64(2), FLOAT64), axis.ScalarMul(MakeScalar(sin/float64(2), FLOAT64)), FLOAT64}
}
<|endoftext|>


<commit_before>package matrix

import (
	"fmt"
	zmq "github.com/pebbe/zmq4"
	"time"
)

type matrix struct {
	socket        *zmq.Socket
	context       *zmq.Context
	bitmap        [][][3]byte
	flame_buffer  [][]byte
	flame_palette [][3]byte
	rows          int
	columns       int
}

func Init(host string, rows int, columns int) *matrix {
	var matrix = matrix{
		rows:    rows,
		columns: columns,
	}
	context, err := zmq.NewContext()
	if err != nil {
		panic(err)
	}
	matrix.context = context
	socket, err := matrix.context.NewSocket(zmq.PUSH)
	if err != nil {
		panic(err)
	}
	matrix.socket = socket
	matrix.socket.SetImmediate(true)
	matrix.socket.SetSndhwm(1)
	matrix.socket.SetLinger(1 * time.Second)
	if err := matrix.socket.Connect(host); err != nil {
		panic(err)
	}
	matrix.bitmap = make([][][3]byte, rows)
	for r, _ := range matrix.bitmap {
		matrix.bitmap[r] = make([][3]byte, columns)
	}

	return &matrix
}

func (matrix *matrix) Send() {
	var data []byte
	for _, row := range matrix.bitmap {
		for _, b := range row {
			data = append(data, b[0], b[1], b[2])
		}
	}
	fmt.Printf("Sending %d bytes\n", len(data))
	if _, err := matrix.socket.SendMessage(data, nil); err != nil {
		panic(err)
	}
}

func (matrix *matrix) Fill(color [3]byte) {
	for r, row := range matrix.bitmap {
		for c, _ := range row {
			matrix.bitmap[r][c] = color
		}
	}
}

func (matrix *matrix) Close() {
	matrix.Fill(ColorBlack())
	matrix.Send()
	matrix.socket.Close()
	matrix.context.Term()
}

func ColorBlack() [3]byte {
	return [3]byte{0, 0, 0}
}

func ColorWhite() [3]byte {
	return [3]byte{255, 255, 255}
}
<commit_msg>Remove debug print<commit_after>package matrix

import (
	zmq "github.com/pebbe/zmq4"
	"time"
)

type matrix struct {
	socket        *zmq.Socket
	context       *zmq.Context
	bitmap        [][][3]byte
	flame_buffer  [][]byte
	flame_palette [][3]byte
	rows          int
	columns       int
}

func Init(host string, rows int, columns int) *matrix {
	var matrix = matrix{
		rows:    rows,
		columns: columns,
	}
	context, err := zmq.NewContext()
	if err != nil {
		panic(err)
	}
	matrix.context = context
	socket, err := matrix.context.NewSocket(zmq.PUSH)
	if err != nil {
		panic(err)
	}
	matrix.socket = socket
	matrix.socket.SetImmediate(true)
	matrix.socket.SetSndhwm(1)
	matrix.socket.SetLinger(1 * time.Second)
	if err := matrix.socket.Connect(host); err != nil {
		panic(err)
	}
	matrix.bitmap = make([][][3]byte, rows)
	for r, _ := range matrix.bitmap {
		matrix.bitmap[r] = make([][3]byte, columns)
	}

	return &matrix
}

func (matrix *matrix) Send() {
	var data []byte
	for _, row := range matrix.bitmap {
		for _, b := range row {
			data = append(data, b[0], b[1], b[2])
		}
	}
	if _, err := matrix.socket.SendMessage(data, nil); err != nil {
		panic(err)
	}
}

func (matrix *matrix) Fill(color [3]byte) {
	for r, row := range matrix.bitmap {
		for c, _ := range row {
			matrix.bitmap[r][c] = color
		}
	}
}

func (matrix *matrix) Close() {
	matrix.Fill(ColorBlack())
	matrix.Send()
	matrix.socket.Close()
	matrix.context.Term()
}

func ColorBlack() [3]byte {
	return [3]byte{0, 0, 0}
}

func ColorWhite() [3]byte {
	return [3]byte{255, 255, 255}
}
<|endoftext|>


<commit_before>package gocoins

import (
	"net"
	"net/http"
	"time"
)

func TimeoutDialer(connectTimeout time.Duration, readWriteTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
	return func(netw, addr string) (net.Conn, error) {
		conn, err := net.DialTimeout(netw, addr, connectTimeout)
		if err != nil {
			return nil, err
		}
		conn.SetDeadline(time.Now().Add(readWriteTimeout))
		return conn, nil
	}
}

func TimeoutTransport(connectTimeout time.Duration, readWriteTimeout time.Duration) *http.Transport {
	return &http.Transport{
		Dial: TimeoutDialer(connectTimeout, readWriteTimeout),
	}
}
<commit_msg>transport: change to total timeout<commit_after>package gocoins

import (
	"net"
	"net/http"
	"time"
)

func TimeoutDialer(connectTimeout time.Duration, totalTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
	return func(netw, addr string) (net.Conn, error) {
		start := time.Now()
		conn, err := net.DialTimeout(netw, addr, connectTimeout)
		if err != nil {
			return nil, err
		}
		conn.SetDeadline(start.Add(totalTimeout))
		return conn, nil
	}
}

func TimeoutTransport(connectTimeout time.Duration, totalTimeout time.Duration) *http.Transport {
	return &http.Transport{
		Dial: TimeoutDialer(connectTimeout, totalTimeout),
	}
}
<|endoftext|>


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

package tls

import (
	"crypto/ecdsa"
	"crypto/rsa"
	"testing"
)

func TestKeyLength(t *testing.T) {
	cert, err := generateSelfSignedCert()

	if err != nil {
		t.Fatalf("Unexpected error: %v\n", err)
	}

	var keyLength int
	switch key := cert.PrivateKey.(type) {
	case *rsa.PrivateKey:
		keyLength = key.N.BitLen()
	case *ecdsa.PrivateKey:
		keyLength = key.Curve.Params().BitSize
	default:
		t.Fatal("unsupported private key")
	}

	if keyLength < 2048 {
		t.Errorf("Private key length is too small:  %d\n", keyLength)
	}
}
<commit_msg>Added more TLS tests<commit_after>// Copyright © 2017 Igor Bondarenko <ibondare@protonmail.com>
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tls

import (
	"crypto/ecdsa"
	"crypto/rsa"
	"testing"
	"crypto/x509"
	"time"
)

func TestKeyLength(t *testing.T) {
	cert, err := generateSelfSignedCert()

	if err != nil {
		t.Fatalf("Unexpected error: %v\n", err)
	}

	var keyLength int
	switch key := cert.PrivateKey.(type) {
	case *rsa.PrivateKey:
		keyLength = key.N.BitLen()
	case *ecdsa.PrivateKey:
		keyLength = key.Curve.Params().BitSize
	default:
		t.Fatal("unsupported private key")
	}

	if keyLength < 2048 {
		t.Errorf("Private key length is too small:  %d\n", keyLength)
	}
}

func TestCertExpiration(t *testing.T) {
	cert, err := generateSelfSignedCert()

	if err != nil {
		t.Fatalf("Unexpected error: %v\n", err)
	}

	x509Cert, err := x509.ParseCertificate(cert.Certificate[0])

	if err != nil {
		t.Fatalf("Unexpected error: %v\n", err)
	}

	// The cert is expected to expire in a year
	expiresAfter := time.Now().Add(time.Hour * 24 * 364)
	expiresBefore := time.Now().Add(time.Hour * 24 * 366)

	if x509Cert.NotAfter.Before(expiresAfter) {
		t.Errorf("Certificate expiration date is %v, expected after %v", x509Cert.NotAfter, expiresAfter)
	}

	if x509Cert.NotAfter.After(expiresBefore) {
		t.Errorf("Certificate expiration date is %v, expected before %v", x509Cert.NotAfter, expiresBefore)
	}
}<|endoftext|>


<commit_before>package main

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

	"github.com/Unknwon/macaron"
	"github.com/grafana/grafana/pkg/log"
	"github.com/raintank/met/helper"
	"github.com/raintank/raintank-apps/tsdb/api"
	"github.com/raintank/raintank-apps/tsdb/elasticsearch"
	"github.com/raintank/raintank-apps/tsdb/event_publish"
	"github.com/raintank/raintank-apps/tsdb/graphite"
	"github.com/raintank/raintank-apps/tsdb/metric_publish"
	"github.com/rakyll/globalconf"
)

var (
	GitHash     = "(none)"
	showVersion = flag.Bool("version", false, "print version string")
	logLevel    = flag.Int("log-level", 2, "log level. 0=TRACE|1=DEBUG|2=INFO|3=WARN|4=ERROR|5=CRITICAL|6=FATAL")
	confFile    = flag.String("config", "/etc/raintank/tsdb.ini", "configuration file path")

	nsqdAddr       = flag.String("nsqd-addr", "localhost:4150", "nsqd service address")
	metricTopic    = flag.String("metric-topic", "metrics", "NSQ topic for metrics")
	publishMetrics = flag.Bool("publish-metrics", false, "enable metric publishing")
	eventTopic     = flag.String("event-topic", "metrics", "NSQ topic for events")
	publishEvents  = flag.Bool("publish-events", false, "enable event publishing")

	addr = flag.String("addr", "localhost:80", "http service address")

	statsEnabled = flag.Bool("stats-enabled", false, "enable statsd metrics")
	statsdAddr   = flag.String("statsd-addr", "localhost:8125", "statsd address")
	statsdType   = flag.String("statsd-type", "standard", "statsd type: standard or datadog")

	graphiteUrl      = flag.String("graphite-url", "http://localhost:8080", "graphite-api address")
	worldpingUrl     = flag.String("worldping-url", "http://localhost/", "worldping-api address")
	elasticsearchUrl = flag.String("elasticsearch-url", "http://localhost:9200", "elasticsearch server address")
	esIndex          = flag.String("es-index", "events", "elasticsearch index name")

	adminKey = flag.String("admin-key", "not_very_secret_key", "Admin Secret Key")
)

func main() {
	flag.Parse()

	// Only try and parse the conf file if it exists
	if _, err := os.Stat(*confFile); err == nil {
		conf, err := globalconf.NewWithOptions(&globalconf.Options{Filename: *confFile})
		if err != nil {
			panic(fmt.Sprintf("error with configuration file: %s", err))
		}
		conf.ParseAll()
	}

	log.NewLogger(0, "console", fmt.Sprintf(`{"level": %d, "formatting":true}`, *logLevel))
	// workaround for https://github.com/grafana/grafana/issues/4055
	switch *logLevel {
	case 0:
		log.Level(log.TRACE)
	case 1:
		log.Level(log.DEBUG)
	case 2:
		log.Level(log.INFO)
	case 3:
		log.Level(log.WARN)
	case 4:
		log.Level(log.ERROR)
	case 5:
		log.Level(log.CRITICAL)
	case 6:
		log.Level(log.FATAL)
	}

	if *showVersion {
		fmt.Printf("tsdb (built with %s, git hash %s)\n", runtime.Version(), GitHash)
		return
	}

	hostname, _ := os.Hostname()

	stats, err := helper.New(*statsEnabled, *statsdAddr, *statsdType, "raintank_tsdb", strings.Replace(hostname, ".", "_", -1))
	if err != nil {
		log.Fatal(4, "failed to initialize statsd. %s", err)
	}

	metric_publish.Init(stats, *metricTopic, *nsqdAddr, *publishMetrics)
	event_publish.Init(stats, *eventTopic, *nsqdAddr, *publishEvents)

	m := macaron.Classic()
	m.Use(macaron.Logger())
	m.Use(macaron.Renderer())

	api.InitRoutes(m, *adminKey)

	if err := graphite.Init(*graphiteUrl, *worldpingUrl); err != nil {
		log.Fatal(4, err.Error())
	}
	if err := elasticsearch.Init(*elasticsearchUrl, *esIndex); err != nil {
		log.Fatal(4, err.Error())
	}

	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt)

	log.Info("starting up")
	// define our own listner so we can call Close on it
	l, err := net.Listen("tcp", *addr)
	if err != nil {
		log.Fatal(4, err.Error())
	}
	done := make(chan struct{})
	go handleShutdown(done, interrupt, l)
	err = http.Serve(l, m)
	if err != nil {
		log.Info(err.Error())
	}
	<-done
}

func handleShutdown(done chan struct{}, interrupt chan os.Signal, l net.Listener) {
	<-interrupt
	log.Info("shutdown started.")
	l.Close()
	close(done)
}
<commit_msg>remove redundant macaron.Logger() handler.<commit_after>package main

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

	"github.com/Unknwon/macaron"
	"github.com/grafana/grafana/pkg/log"
	"github.com/raintank/met/helper"
	"github.com/raintank/raintank-apps/tsdb/api"
	"github.com/raintank/raintank-apps/tsdb/elasticsearch"
	"github.com/raintank/raintank-apps/tsdb/event_publish"
	"github.com/raintank/raintank-apps/tsdb/graphite"
	"github.com/raintank/raintank-apps/tsdb/metric_publish"
	"github.com/rakyll/globalconf"
)

var (
	GitHash     = "(none)"
	showVersion = flag.Bool("version", false, "print version string")
	logLevel    = flag.Int("log-level", 2, "log level. 0=TRACE|1=DEBUG|2=INFO|3=WARN|4=ERROR|5=CRITICAL|6=FATAL")
	confFile    = flag.String("config", "/etc/raintank/tsdb.ini", "configuration file path")

	nsqdAddr       = flag.String("nsqd-addr", "localhost:4150", "nsqd service address")
	metricTopic    = flag.String("metric-topic", "metrics", "NSQ topic for metrics")
	publishMetrics = flag.Bool("publish-metrics", false, "enable metric publishing")
	eventTopic     = flag.String("event-topic", "metrics", "NSQ topic for events")
	publishEvents  = flag.Bool("publish-events", false, "enable event publishing")

	addr = flag.String("addr", "localhost:80", "http service address")

	statsEnabled = flag.Bool("stats-enabled", false, "enable statsd metrics")
	statsdAddr   = flag.String("statsd-addr", "localhost:8125", "statsd address")
	statsdType   = flag.String("statsd-type", "standard", "statsd type: standard or datadog")

	graphiteUrl      = flag.String("graphite-url", "http://localhost:8080", "graphite-api address")
	worldpingUrl     = flag.String("worldping-url", "http://localhost/", "worldping-api address")
	elasticsearchUrl = flag.String("elasticsearch-url", "http://localhost:9200", "elasticsearch server address")
	esIndex          = flag.String("es-index", "events", "elasticsearch index name")

	adminKey = flag.String("admin-key", "not_very_secret_key", "Admin Secret Key")
)

func main() {
	flag.Parse()

	// Only try and parse the conf file if it exists
	if _, err := os.Stat(*confFile); err == nil {
		conf, err := globalconf.NewWithOptions(&globalconf.Options{Filename: *confFile})
		if err != nil {
			panic(fmt.Sprintf("error with configuration file: %s", err))
		}
		conf.ParseAll()
	}

	log.NewLogger(0, "console", fmt.Sprintf(`{"level": %d, "formatting":true}`, *logLevel))
	// workaround for https://github.com/grafana/grafana/issues/4055
	switch *logLevel {
	case 0:
		log.Level(log.TRACE)
	case 1:
		log.Level(log.DEBUG)
	case 2:
		log.Level(log.INFO)
	case 3:
		log.Level(log.WARN)
	case 4:
		log.Level(log.ERROR)
	case 5:
		log.Level(log.CRITICAL)
	case 6:
		log.Level(log.FATAL)
	}

	if *showVersion {
		fmt.Printf("tsdb (built with %s, git hash %s)\n", runtime.Version(), GitHash)
		return
	}

	hostname, _ := os.Hostname()

	stats, err := helper.New(*statsEnabled, *statsdAddr, *statsdType, "raintank_tsdb", strings.Replace(hostname, ".", "_", -1))
	if err != nil {
		log.Fatal(4, "failed to initialize statsd. %s", err)
	}

	metric_publish.Init(stats, *metricTopic, *nsqdAddr, *publishMetrics)
	event_publish.Init(stats, *eventTopic, *nsqdAddr, *publishEvents)

	m := macaron.Classic()
	m.Use(macaron.Renderer())

	api.InitRoutes(m, *adminKey)

	if err := graphite.Init(*graphiteUrl, *worldpingUrl); err != nil {
		log.Fatal(4, err.Error())
	}
	if err := elasticsearch.Init(*elasticsearchUrl, *esIndex); err != nil {
		log.Fatal(4, err.Error())
	}

	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, os.Interrupt)

	log.Info("starting up")
	// define our own listner so we can call Close on it
	l, err := net.Listen("tcp", *addr)
	if err != nil {
		log.Fatal(4, err.Error())
	}
	done := make(chan struct{})
	go handleShutdown(done, interrupt, l)
	err = http.Serve(l, m)
	if err != nil {
		log.Info(err.Error())
	}
	<-done
}

func handleShutdown(done chan struct{}, interrupt chan os.Signal, l net.Listener) {
	<-interrupt
	log.Info("shutdown started.")
	l.Close()
	close(done)
}
<|endoftext|>


<commit_before>package inflator

import (
	"bufio"
	"bytes"
	"cred-alert/mimetype"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"

	"code.cloudfoundry.org/lager"
)

func RecursivelyExtractArchive(logger lager.Logger, path, destination string, cleanup bool) error {
	fh, err := os.Open(path)
	if err != nil {
		return err
	}

	br := bufio.NewReader(fh)
	if mime, isArchive := mimetype.IsArchive(logger, br); isArchive {
		basename := filepath.Base(fh.Name())
		nextLevelDestination := filepath.Join(destination, basename+"-contents")
		extractFile(mime, fh.Name(), nextLevelDestination)

		err = fh.Close()
		if err != nil {
			return err
		}

		if cleanup {
			err = os.RemoveAll(fh.Name())
			if err != nil {
				return err
			}
		}

		return recursivelyExtractArchivesInDir(logger, nextLevelDestination, nextLevelDestination)
	}

	err = fh.Close()
	if err != nil {
		return err
	}

	return nil
}

func extractFile(mime, path, destination string) {
	err := os.MkdirAll(destination, 0755)
	if err != nil {
		panic(err.Error())
	}

	var cmd *exec.Cmd
	switch mime {
	case "application/zip":
		cmd = exec.Command("unzip", path, "-d", destination)
	case "application/x-tar":
		cmd = exec.Command("tar", "xf", path, "-C", destination)
	case "application/gzip", "application/x-gzip":
		fileName := filepath.Base(path)
		fileNameWithoutExt := fileName[:len(fileName)-len(filepath.Ext(fileName))]
		output, err := os.Create(filepath.Join(destination, fileNameWithoutExt))
		if err != nil {
			panic(err.Error())
		}
		defer output.Close()

		cmd = exec.Command("gunzip", "-c", path)
		cmd.Stdout = output
	default:
		panic(fmt.Sprintf("don't know how to extract %s", mime))
	}

	buf := &bytes.Buffer{}
	cmd.Stderr = buf
	err = cmd.Run()
	if err != nil {
		fmt.Printf("failed-to-run-cmd: %s\nStderr:\n%s\n", err.Error(), buf.String())
	}
}

func recursivelyExtractArchivesInDir(logger lager.Logger, path, destination string) error {
	children, err := ioutil.ReadDir(path)
	if err != nil {
		return err
	}

	for i := range children {
		basename := children[i].Name()

		if children[i].IsDir() {
			err := recursivelyExtractArchivesInDir(logger, filepath.Join(path, basename), filepath.Join(path, basename))
			if err != nil {
				return err
			}
			continue
		}

		if !children[i].Mode().IsRegular() {
			continue
		}

		_, found := nonArchiveExtensions[filepath.Ext(basename)]
		if !found {
			RecursivelyExtractArchive(logger, filepath.Join(path, basename), destination, true)
		}
	}

	return nil
}

var nonArchiveExtensions = map[string]struct{}{
	".MF":           struct{}{},
	".S":            struct{}{},
	".a":            struct{}{},
	".am":           struct{}{},
	".article":      struct{}{},
	".au":           struct{}{},
	".autotest":     struct{}{},
	".bash":         struct{}{},
	".bat":          struct{}{},
	".builder":      struct{}{},
	".c":            struct{}{},
	".ca":           struct{}{},
	".cc":           struct{}{},
	".cert":         struct{}{},
	".cfg":          struct{}{},
	".class":        struct{}{},
	".classpath":    struct{}{},
	".cmake":        struct{}{},
	".cnf":          struct{}{},
	".column":       struct{}{},
	".conf":         struct{}{},
	".cpp":          struct{}{},
	".crt":          struct{}{},
	".css":          struct{}{},
	".csv":          struct{}{},
	".dat":          struct{}{},
	".data":         struct{}{},
	".decTest":      struct{}{},
	".def":          struct{}{},
	".devtools":     struct{}{},
	".dir":          struct{}{},
	".document":     struct{}{},
	".dtd":          struct{}{},
	".dumped":       struct{}{},
	".ec":           struct{}{},
	".ecpp":         struct{}{},
	".editorconfig": struct{}{},
	".ejava":        struct{}{},
	".ejs":          struct{}{},
	".eot":          struct{}{},
	".eperl":        struct{}{},
	".ephp":         struct{}{},
	".erb":          struct{}{},
	".erubis":       struct{}{},
	".eruby":        struct{}{},
	".escheme":      struct{}{},
	".example":      struct{}{},
	".exe":          struct{}{},
	".exp":          struct{}{},
	".fcgi":         struct{}{},
	".feature":      struct{}{},
	".gemfile":      struct{}{},
	".gemspec":      struct{}{},
	".gemtest":      struct{}{},
	".gif":          struct{}{},
	".gitignore":    struct{}{},
	".gitkeep":      struct{}{},
	".gitmodules":   struct{}{},
	".go":           struct{}{},
	".golden":       struct{}{},
	".gyp":          struct{}{},
	".h":            struct{}{},
	".haml":         struct{}{},
	".hoerc":        struct{}{},
	".hp":           struct{}{},
	".hpp":          struct{}{},
	".html":         struct{}{},
	".ico":          struct{}{},
	".iml":          struct{}{},
	".in":           struct{}{},
	".input":        struct{}{},
	".irbrc":        struct{}{},
	".iso":          struct{}{}, // to be removed when we support .iso
	".java":         struct{}{},
	".jpeg":         struct{}{},
	".jpg":          struct{}{},
	".jrubydir":     struct{}{},
	".js":           struct{}{},
	".json":         struct{}{},
	".jsp":          struct{}{},
	".keep":         struct{}{},
	".key":          struct{}{},
	".kpeg":         struct{}{},
	".liquid":       struct{}{},
	".list":         struct{}{},
	".lock":         struct{}{},
	".log":          struct{}{},
	".m4":           struct{}{},
	".mab":          struct{}{},
	".markdown":     struct{}{},
	".md":           struct{}{},
	".md5sums":      struct{}{},
	".mf":           struct{}{},
	".mk":           struct{}{},
	".mo":           struct{}{},
	".monitrc":      struct{}{},
	".msg":          struct{}{},
	".mspec":        struct{}{},
	".nokogiri":     struct{}{},
	".npmignore":    struct{}{},
	".obj":          struct{}{},
	".opts":         struct{}{},
	".out":          struct{}{},
	".ovf":          struct{}{},
	".patch":        struct{}{},
	".pdf":          struct{}{},
	".pem":          struct{}{},
	".php":          struct{}{},
	".phpt":         struct{}{},
	".pl":           struct{}{},
	".pm":           struct{}{},
	".png":          struct{}{},
	".po":           struct{}{},
	".postinst":     struct{}{},
	".postrm":       struct{}{},
	".project":      struct{}{},
	".properties":   struct{}{},
	".proto":        struct{}{},
	".psf":          struct{}{},
	".py":           struct{}{},
	".pyc":          struct{}{},
	".pyo":          struct{}{},
	".radius":       struct{}{},
	".rake":         struct{}{},
	".rake_example": struct{}{},
	".rb":           struct{}{},
	".rdoc":         struct{}{},
	".reek":         struct{}{},
	".reg":          struct{}{},
	".result":       struct{}{},
	".rhtml":        struct{}{},
	".rid":          struct{}{},
	".rl":           struct{}{},
	".rsc":          struct{}{},
	".rspec":        struct{}{},
	".rst":          struct{}{},
	".ru":           struct{}{},
	".ruby-gemset":  struct{}{},
	".ruby-version": struct{}{},
	".ry":           struct{}{},
	".s":            struct{}{},
	".sample":       struct{}{},
	".sass":         struct{}{},
	".sgml":         struct{}{},
	".sh":           struct{}{},
	".slim":         struct{}{},
	".sng":          struct{}{},
	".so":           struct{}{},
	".sql":          struct{}{},
	".src":          struct{}{},
	".str":          struct{}{},
	".supp":         struct{}{},
	".svg":          struct{}{},
	".t":            struct{}{},
	".test":         struct{}{},
	".text":         struct{}{},
	".thor":         struct{}{},
	".tmpl":         struct{}{},
	".tsv":          struct{}{},
	".tt":           struct{}{},
	".ttf":          struct{}{},
	".txt":          struct{}{},
	".utf8":         struct{}{},
	".vcproj":       struct{}{},
	".vmdk":         struct{}{},
	".x":            struct{}{},
	".xhtml":        struct{}{},
	".xml":          struct{}{},
	".xsd":          struct{}{},
	".xyz":          struct{}{},
	".y":            struct{}{},
	".yaml":         struct{}{},
	".yardopts":     struct{}{},
	".yml":          struct{}{},
}
<commit_msg>Remove duplicate filepath.Join calls<commit_after>package inflator

import (
	"bufio"
	"bytes"
	"cred-alert/mimetype"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"

	"code.cloudfoundry.org/lager"
)

func RecursivelyExtractArchive(logger lager.Logger, path, destination string, cleanup bool) error {
	fh, err := os.Open(path)
	if err != nil {
		return err
	}

	br := bufio.NewReader(fh)
	if mime, isArchive := mimetype.IsArchive(logger, br); isArchive {
		basename := filepath.Base(fh.Name())
		nextLevelDestination := filepath.Join(destination, basename+"-contents")
		extractFile(mime, fh.Name(), nextLevelDestination)

		err = fh.Close()
		if err != nil {
			return err
		}

		if cleanup {
			err = os.RemoveAll(fh.Name())
			if err != nil {
				return err
			}
		}

		return recursivelyExtractArchivesInDir(logger, nextLevelDestination, nextLevelDestination)
	}

	err = fh.Close()
	if err != nil {
		return err
	}

	return nil
}

func extractFile(mime, path, destination string) {
	err := os.MkdirAll(destination, 0755)
	if err != nil {
		panic(err.Error())
	}

	var cmd *exec.Cmd
	switch mime {
	case "application/zip":
		cmd = exec.Command("unzip", path, "-d", destination)
	case "application/x-tar":
		cmd = exec.Command("tar", "xf", path, "-C", destination)
	case "application/gzip", "application/x-gzip":
		fileName := filepath.Base(path)
		fileNameWithoutExt := fileName[:len(fileName)-len(filepath.Ext(fileName))]
		output, err := os.Create(filepath.Join(destination, fileNameWithoutExt))
		if err != nil {
			panic(err.Error())
		}
		defer output.Close()

		cmd = exec.Command("gunzip", "-c", path)
		cmd.Stdout = output
	default:
		panic(fmt.Sprintf("don't know how to extract %s", mime))
	}

	buf := &bytes.Buffer{}
	cmd.Stderr = buf
	err = cmd.Run()
	if err != nil {
		fmt.Printf("failed-to-run-cmd: %s\nStderr:\n%s\n", err.Error(), buf.String())
	}
}

func recursivelyExtractArchivesInDir(logger lager.Logger, path, destination string) error {
	children, err := ioutil.ReadDir(path)
	if err != nil {
		return err
	}

	for i := range children {
		basename := children[i].Name()
		wholeName := filepath.Join(path, basename)

		if children[i].IsDir() {
			err := recursivelyExtractArchivesInDir(logger, wholeName, wholeName)
			if err != nil {
				return err
			}
			continue
		}

		if !children[i].Mode().IsRegular() {
			continue
		}

		_, found := nonArchiveExtensions[filepath.Ext(basename)]
		if !found {
			RecursivelyExtractArchive(logger, wholeName, destination, true)
		}
	}

	return nil
}

var nonArchiveExtensions = map[string]struct{}{
	".MF":           struct{}{},
	".S":            struct{}{},
	".a":            struct{}{},
	".am":           struct{}{},
	".article":      struct{}{},
	".au":           struct{}{},
	".autotest":     struct{}{},
	".bash":         struct{}{},
	".bat":          struct{}{},
	".builder":      struct{}{},
	".c":            struct{}{},
	".ca":           struct{}{},
	".cc":           struct{}{},
	".cert":         struct{}{},
	".cfg":          struct{}{},
	".class":        struct{}{},
	".classpath":    struct{}{},
	".cmake":        struct{}{},
	".cnf":          struct{}{},
	".column":       struct{}{},
	".conf":         struct{}{},
	".cpp":          struct{}{},
	".crt":          struct{}{},
	".css":          struct{}{},
	".csv":          struct{}{},
	".dat":          struct{}{},
	".data":         struct{}{},
	".decTest":      struct{}{},
	".def":          struct{}{},
	".devtools":     struct{}{},
	".dir":          struct{}{},
	".document":     struct{}{},
	".dtd":          struct{}{},
	".dumped":       struct{}{},
	".ec":           struct{}{},
	".ecpp":         struct{}{},
	".editorconfig": struct{}{},
	".ejava":        struct{}{},
	".ejs":          struct{}{},
	".eot":          struct{}{},
	".eperl":        struct{}{},
	".ephp":         struct{}{},
	".erb":          struct{}{},
	".erubis":       struct{}{},
	".eruby":        struct{}{},
	".escheme":      struct{}{},
	".example":      struct{}{},
	".exe":          struct{}{},
	".exp":          struct{}{},
	".fcgi":         struct{}{},
	".feature":      struct{}{},
	".gemfile":      struct{}{},
	".gemspec":      struct{}{},
	".gemtest":      struct{}{},
	".gif":          struct{}{},
	".gitignore":    struct{}{},
	".gitkeep":      struct{}{},
	".gitmodules":   struct{}{},
	".go":           struct{}{},
	".golden":       struct{}{},
	".gyp":          struct{}{},
	".h":            struct{}{},
	".haml":         struct{}{},
	".hoerc":        struct{}{},
	".hp":           struct{}{},
	".hpp":          struct{}{},
	".html":         struct{}{},
	".ico":          struct{}{},
	".iml":          struct{}{},
	".in":           struct{}{},
	".input":        struct{}{},
	".irbrc":        struct{}{},
	".iso":          struct{}{}, // to be removed when we support .iso
	".java":         struct{}{},
	".jpeg":         struct{}{},
	".jpg":          struct{}{},
	".jrubydir":     struct{}{},
	".js":           struct{}{},
	".json":         struct{}{},
	".jsp":          struct{}{},
	".keep":         struct{}{},
	".key":          struct{}{},
	".kpeg":         struct{}{},
	".liquid":       struct{}{},
	".list":         struct{}{},
	".lock":         struct{}{},
	".log":          struct{}{},
	".m4":           struct{}{},
	".mab":          struct{}{},
	".markdown":     struct{}{},
	".md":           struct{}{},
	".md5sums":      struct{}{},
	".mf":           struct{}{},
	".mk":           struct{}{},
	".mo":           struct{}{},
	".monitrc":      struct{}{},
	".msg":          struct{}{},
	".mspec":        struct{}{},
	".nokogiri":     struct{}{},
	".npmignore":    struct{}{},
	".obj":          struct{}{},
	".opts":         struct{}{},
	".out":          struct{}{},
	".ovf":          struct{}{},
	".patch":        struct{}{},
	".pdf":          struct{}{},
	".pem":          struct{}{},
	".php":          struct{}{},
	".phpt":         struct{}{},
	".pl":           struct{}{},
	".pm":           struct{}{},
	".png":          struct{}{},
	".po":           struct{}{},
	".postinst":     struct{}{},
	".postrm":       struct{}{},
	".project":      struct{}{},
	".properties":   struct{}{},
	".proto":        struct{}{},
	".psf":          struct{}{},
	".py":           struct{}{},
	".pyc":          struct{}{},
	".pyo":          struct{}{},
	".radius":       struct{}{},
	".rake":         struct{}{},
	".rake_example": struct{}{},
	".rb":           struct{}{},
	".rdoc":         struct{}{},
	".reek":         struct{}{},
	".reg":          struct{}{},
	".result":       struct{}{},
	".rhtml":        struct{}{},
	".rid":          struct{}{},
	".rl":           struct{}{},
	".rsc":          struct{}{},
	".rspec":        struct{}{},
	".rst":          struct{}{},
	".ru":           struct{}{},
	".ruby-gemset":  struct{}{},
	".ruby-version": struct{}{},
	".ry":           struct{}{},
	".s":            struct{}{},
	".sample":       struct{}{},
	".sass":         struct{}{},
	".sgml":         struct{}{},
	".sh":           struct{}{},
	".slim":         struct{}{},
	".sng":          struct{}{},
	".so":           struct{}{},
	".sql":          struct{}{},
	".src":          struct{}{},
	".str":          struct{}{},
	".supp":         struct{}{},
	".svg":          struct{}{},
	".t":            struct{}{},
	".test":         struct{}{},
	".text":         struct{}{},
	".thor":         struct{}{},
	".tmpl":         struct{}{},
	".tsv":          struct{}{},
	".tt":           struct{}{},
	".ttf":          struct{}{},
	".txt":          struct{}{},
	".utf8":         struct{}{},
	".vcproj":       struct{}{},
	".vmdk":         struct{}{},
	".x":            struct{}{},
	".xhtml":        struct{}{},
	".xml":          struct{}{},
	".xsd":          struct{}{},
	".xyz":          struct{}{},
	".y":            struct{}{},
	".yaml":         struct{}{},
	".yardopts":     struct{}{},
	".yml":          struct{}{},
}
<|endoftext|>


<commit_before>package state

import (
	"errors"
	"fmt"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/txn"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/trivial"
	"strconv"
)

// Service represents the state of a service.
type Service struct {
	st  *State
	doc serviceDoc
}

// serviceDoc represents the internal state of a service in MongoDB.
type serviceDoc struct {
	Name       string `bson:"_id"`
	CharmURL   *charm.URL
	ForceCharm bool
	Life       Life
	UnitSeq    int
	UnitCount  int
	Exposed    bool
	TxnRevno   int64 `bson:"txn-revno"`
}

func newService(st *State, doc *serviceDoc) *Service {
	return &Service{st: st, doc: *doc}
}

// Name returns the service name.
func (s *Service) Name() string {
	return s.doc.Name
}

// Life returns whether the service is Alive, Dying or Dead.
func (s *Service) Life() Life {
	return s.doc.Life
}

// EnsureDying sets the service lifecycle to Dying if it is Alive.
// It does nothing otherwise.
func (s *Service) EnsureDying() error {
	err := ensureDying(s.st, s.st.services, s.doc.Name, "service")
	if err != nil {
		return err
	}
	s.doc.Life = Dying
	return nil
}

// EnsureDead sets the service lifecycle to Dead if it is Alive or Dying.
// It does nothing otherwise. It will return an error if the service still
// has units.
func (s *Service) EnsureDead() error {
	assertOps := []txn.Op{{
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: D{{"unitcount", 0}},
	}}
	err := ensureDead(s.st, s.st.services, s.doc.Name, "service", assertOps, "service still has units")
	if err != nil {
		return err
	}
	s.doc.Life = Dead
	return nil
}

// IsExposed returns whether this service is exposed. The explicitly open
// ports (with open-port) for exposed services may be accessed from machines
// outside of the local deployment network. See SetExposed and ClearExposed.
func (s *Service) IsExposed() bool {
	return s.doc.Exposed
}

// SetExposed marks the service as exposed.
// See ClearExposed and IsExposed.
func (s *Service) SetExposed() error {
	return s.setExposed(true)
}

// ClearExposed removes the exposed flag from the service.
// See SetExposed and IsExposed.
func (s *Service) ClearExposed() error {
	return s.setExposed(false)
}

func (s *Service) setExposed(exposed bool) (err error) {
	ops := []txn.Op{{
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: isAlive,
		Update: D{{"$set", D{{"exposed", exposed}}}},
	}}
	if err := s.st.runner.Run(ops, "", nil); err != nil {
		return fmt.Errorf("cannot set exposed flag for service %q to %v: %v", s, exposed, onAbort(err, errNotAlive))
	}
	s.doc.Exposed = exposed
	return nil
}

// Charm returns the service's charm and whether units should upgrade to that
// charm even if they are in an error state.
func (s *Service) Charm() (ch *Charm, force bool, err error) {
	ch, err = s.st.Charm(s.doc.CharmURL)
	if err != nil {
		return nil, false, err
	}
	return ch, s.doc.ForceCharm, nil
}

// SetCharm changes the charm for the service. New units will be started with
// this charm, and existing units will be upgraded to use it. If force is true,
// units will be upgraded even if they are in an error state.
func (s *Service) SetCharm(ch *Charm, force bool) (err error) {
	ops := []txn.Op{{
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: isAlive,
		Update: D{{"$set", D{{"charmurl", ch.URL()}, {"forcecharm", force}}}},
	}}
	if err := s.st.runner.Run(ops, "", nil); err != nil {
		return fmt.Errorf("cannot set charm for service %q: %v", s, onAbort(err, errNotAlive))
	}
	s.doc.CharmURL = ch.URL()
	s.doc.ForceCharm = force
	return nil
}

// String returns the service name.
func (s *Service) String() string {
	return s.doc.Name
}

// Refresh refreshes the contents of the Service from the underlying
// state. It returns a NotFoundError if the service has been removed.
func (s *Service) Refresh() error {
	err := s.st.services.FindId(s.doc.Name).One(&s.doc)
	if err == mgo.ErrNotFound {
		return notFound("service %v", s)
	}
	if err != nil {
		return fmt.Errorf("cannot refresh service %q: %v", s, err)
	}
	return nil
}

// newUnitName returns the next unit name.
func (s *Service) newUnitName() (string, error) {
	change := mgo.Change{Update: D{{"$inc", D{{"unitseq", 1}}}}}
	result := serviceDoc{}
	_, err := s.st.services.Find(D{{"_id", s.doc.Name}}).Apply(change, &result)
	if err != nil {
		return "", fmt.Errorf("cannot increment unit sequence: %v", err)
	}
	name := s.doc.Name + "/" + strconv.Itoa(result.UnitSeq)
	return name, nil
}

// addUnit adds the named unit.
func (s *Service) addUnit(name string, principal *Unit) (*Unit, error) {
	udoc := &unitDoc{
		Name:    name,
		Service: s.doc.Name,
		Life:    Alive,
	}
	ops := []txn.Op{{
		C:      s.st.units.Name,
		Id:     name,
		Assert: txn.DocMissing,
		Insert: udoc,
	}, {
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: isAlive,
		Update: D{{"$inc", D{{"unitcount", 1}}}},
	}}
	if principal != nil {
		udoc.Principal = principal.Name()
		ops = append(ops, txn.Op{
			C:      s.st.units.Name,
			Id:     principal.Name(),
			Assert: isAlive,
			Update: D{{"$addToSet", D{{"subordinates", name}}}},
		})
	}
	err := s.st.runner.Run(ops, "", nil)
	if err != nil {
		if err == txn.ErrAborted {
			if principal == nil {
				err = fmt.Errorf("service is not alive")
			} else {
				err = fmt.Errorf("service or principal unit are not alive")
			}
		}
		return nil, err
	}
	// Refresh to pick the txn-revno.
	u := newUnit(s.st, udoc)
	err = u.Refresh()
	if err != nil {
		return nil, err
	}
	return u, nil
}

// AddUnit adds a new principal unit to the service.
func (s *Service) AddUnit() (unit *Unit, err error) {
	defer trivial.ErrorContextf(&err, "cannot add unit to service %q", s)
	ch, _, err := s.Charm()
	if err != nil {
		return nil, err
	}
	if ch.Meta().Subordinate {
		return nil, fmt.Errorf("unit is a subordinate")
	}
	name, err := s.newUnitName()
	if err != nil {
		return nil, err
	}
	return s.addUnit(name, nil)
}

// AddUnitSubordinateTo adds a new subordinate unit to the service,
// subordinate to principal.
func (s *Service) AddUnitSubordinateTo(principal *Unit) (unit *Unit, err error) {
	defer trivial.ErrorContextf(&err, "cannot add unit to service %q as a subordinate of %q", s, principal)
	ch, _, err := s.Charm()
	if err != nil {
		return nil, err
	}
	if !ch.Meta().Subordinate {
		return nil, fmt.Errorf("service is not a subordinate")
	}
	if !principal.IsPrincipal() {
		return nil, fmt.Errorf("unit is not a principal")
	}
	name, err := s.newUnitName()
	if err != nil {
		return nil, err
	}
	return s.addUnit(name, principal)
}

// RemoveUnit removes the given unit from s.
func (s *Service) RemoveUnit(u *Unit) (err error) {
	defer trivial.ErrorContextf(&err, "cannot remove unit %q", u)
	if u.doc.Life != Dead {
		return errors.New("unit is not dead")
	}
	if u.doc.Service != s.doc.Name {
		return fmt.Errorf("unit is not assigned to service %q", s)
	}
	if u.doc.MachineId != nil {
		err = u.UnassignFromMachine()
		if err != nil {
			return err
		}
	}
	ops := []txn.Op{{
		C:      s.st.units.Name,
		Id:     u.doc.Name,
		Assert: D{{"life", Dead}},
		Remove: true,
	}, {
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: D{{"unitcount", D{{"$gt", 0}}}},
		Update: D{{"$inc", D{{"unitcount", -1}}}},
	}}
	if u.doc.Principal != "" {
		ops = append(ops, txn.Op{
			C:      s.st.units.Name,
			Id:     u.doc.Principal,
			Assert: txn.DocExists,
			Update: D{{"$pull", D{{"subordinates", u.doc.Name}}}},
		})
	}
	// TODO assert that subordinates are empty before deleting
	// a principal
	if err = s.st.runner.Run(ops, "", nil); err != nil {
		// TODO Remove this once we know the logic is right:
		if c, err := s.st.units.FindId(u.doc.Name).Count(); err != nil {
			return err
		} else if c > 0 {
			return fmt.Errorf("cannot remove unit; something smells bad")
		}
		// If aborted, the unit is either dead or recreated.
		return onAbort(err, nil)
	}
	return nil
}

func (s *Service) unitDoc(name string) (*unitDoc, error) {
	udoc := &unitDoc{}
	sel := D{
		{"_id", name},
		{"service", s.doc.Name},
	}
	err := s.st.units.Find(sel).One(udoc)
	if err != nil {
		return nil, err
	}
	return udoc, nil
}

// Unit returns the service's unit with name.
func (s *Service) Unit(name string) (*Unit, error) {
	if !IsUnitName(name) {
		return nil, fmt.Errorf("%q is not a valid unit name", name)
	}
	udoc, err := s.unitDoc(name)
	if err != nil {
		return nil, fmt.Errorf("cannot get unit %q from service %q: %v", name, s.doc.Name, err)
	}
	return newUnit(s.st, udoc), nil
}

// AllUnits returns all units of the service.
func (s *Service) AllUnits() (units []*Unit, err error) {
	docs := []unitDoc{}
	err = s.st.units.Find(D{{"service", s.doc.Name}}).All(&docs)
	if err != nil {
		return nil, fmt.Errorf("cannot get all units from service %q: %v", s, err)
	}
	for i := range docs {
		units = append(units, newUnit(s.st, &docs[i]))
	}
	return units, nil
}

// Relations returns a Relation for every relation the service is in.
func (s *Service) Relations() (relations []*Relation, err error) {
	defer trivial.ErrorContextf(&err, "can't get relations for service %q", s)
	docs := []relationDoc{}
	err = s.st.relations.Find(D{{"endpoints.servicename", s.doc.Name}}).All(&docs)
	if err != nil {
		return nil, err
	}
	for _, v := range docs {
		relations = append(relations, newRelation(s.st, &v))
	}
	return relations, nil
}

// Config returns the configuration node for the service.
func (s *Service) Config() (config *ConfigNode, err error) {
	config, err = readConfigNode(s.st, "s#"+s.Name())
	if err != nil {
		return nil, fmt.Errorf("cannot get configuration of service %q: %v", s, err)
	}
	return config, nil
}
<commit_msg>state: %v->%q<commit_after>package state

import (
	"errors"
	"fmt"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/txn"
	"launchpad.net/juju-core/charm"
	"launchpad.net/juju-core/trivial"
	"strconv"
)

// Service represents the state of a service.
type Service struct {
	st  *State
	doc serviceDoc
}

// serviceDoc represents the internal state of a service in MongoDB.
type serviceDoc struct {
	Name       string `bson:"_id"`
	CharmURL   *charm.URL
	ForceCharm bool
	Life       Life
	UnitSeq    int
	UnitCount  int
	Exposed    bool
	TxnRevno   int64 `bson:"txn-revno"`
}

func newService(st *State, doc *serviceDoc) *Service {
	return &Service{st: st, doc: *doc}
}

// Name returns the service name.
func (s *Service) Name() string {
	return s.doc.Name
}

// Life returns whether the service is Alive, Dying or Dead.
func (s *Service) Life() Life {
	return s.doc.Life
}

// EnsureDying sets the service lifecycle to Dying if it is Alive.
// It does nothing otherwise.
func (s *Service) EnsureDying() error {
	err := ensureDying(s.st, s.st.services, s.doc.Name, "service")
	if err != nil {
		return err
	}
	s.doc.Life = Dying
	return nil
}

// EnsureDead sets the service lifecycle to Dead if it is Alive or Dying.
// It does nothing otherwise. It will return an error if the service still
// has units.
func (s *Service) EnsureDead() error {
	assertOps := []txn.Op{{
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: D{{"unitcount", 0}},
	}}
	err := ensureDead(s.st, s.st.services, s.doc.Name, "service", assertOps, "service still has units")
	if err != nil {
		return err
	}
	s.doc.Life = Dead
	return nil
}

// IsExposed returns whether this service is exposed. The explicitly open
// ports (with open-port) for exposed services may be accessed from machines
// outside of the local deployment network. See SetExposed and ClearExposed.
func (s *Service) IsExposed() bool {
	return s.doc.Exposed
}

// SetExposed marks the service as exposed.
// See ClearExposed and IsExposed.
func (s *Service) SetExposed() error {
	return s.setExposed(true)
}

// ClearExposed removes the exposed flag from the service.
// See SetExposed and IsExposed.
func (s *Service) ClearExposed() error {
	return s.setExposed(false)
}

func (s *Service) setExposed(exposed bool) (err error) {
	ops := []txn.Op{{
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: isAlive,
		Update: D{{"$set", D{{"exposed", exposed}}}},
	}}
	if err := s.st.runner.Run(ops, "", nil); err != nil {
		return fmt.Errorf("cannot set exposed flag for service %q to %v: %v", s, exposed, onAbort(err, errNotAlive))
	}
	s.doc.Exposed = exposed
	return nil
}

// Charm returns the service's charm and whether units should upgrade to that
// charm even if they are in an error state.
func (s *Service) Charm() (ch *Charm, force bool, err error) {
	ch, err = s.st.Charm(s.doc.CharmURL)
	if err != nil {
		return nil, false, err
	}
	return ch, s.doc.ForceCharm, nil
}

// SetCharm changes the charm for the service. New units will be started with
// this charm, and existing units will be upgraded to use it. If force is true,
// units will be upgraded even if they are in an error state.
func (s *Service) SetCharm(ch *Charm, force bool) (err error) {
	ops := []txn.Op{{
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: isAlive,
		Update: D{{"$set", D{{"charmurl", ch.URL()}, {"forcecharm", force}}}},
	}}
	if err := s.st.runner.Run(ops, "", nil); err != nil {
		return fmt.Errorf("cannot set charm for service %q: %v", s, onAbort(err, errNotAlive))
	}
	s.doc.CharmURL = ch.URL()
	s.doc.ForceCharm = force
	return nil
}

// String returns the service name.
func (s *Service) String() string {
	return s.doc.Name
}

// Refresh refreshes the contents of the Service from the underlying
// state. It returns a NotFoundError if the service has been removed.
func (s *Service) Refresh() error {
	err := s.st.services.FindId(s.doc.Name).One(&s.doc)
	if err == mgo.ErrNotFound {
		return notFound("service %q", s)
	}
	if err != nil {
		return fmt.Errorf("cannot refresh service %q: %v", s, err)
	}
	return nil
}

// newUnitName returns the next unit name.
func (s *Service) newUnitName() (string, error) {
	change := mgo.Change{Update: D{{"$inc", D{{"unitseq", 1}}}}}
	result := serviceDoc{}
	_, err := s.st.services.Find(D{{"_id", s.doc.Name}}).Apply(change, &result)
	if err != nil {
		return "", fmt.Errorf("cannot increment unit sequence: %v", err)
	}
	name := s.doc.Name + "/" + strconv.Itoa(result.UnitSeq)
	return name, nil
}

// addUnit adds the named unit.
func (s *Service) addUnit(name string, principal *Unit) (*Unit, error) {
	udoc := &unitDoc{
		Name:    name,
		Service: s.doc.Name,
		Life:    Alive,
	}
	ops := []txn.Op{{
		C:      s.st.units.Name,
		Id:     name,
		Assert: txn.DocMissing,
		Insert: udoc,
	}, {
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: isAlive,
		Update: D{{"$inc", D{{"unitcount", 1}}}},
	}}
	if principal != nil {
		udoc.Principal = principal.Name()
		ops = append(ops, txn.Op{
			C:      s.st.units.Name,
			Id:     principal.Name(),
			Assert: isAlive,
			Update: D{{"$addToSet", D{{"subordinates", name}}}},
		})
	}
	err := s.st.runner.Run(ops, "", nil)
	if err != nil {
		if err == txn.ErrAborted {
			if principal == nil {
				err = fmt.Errorf("service is not alive")
			} else {
				err = fmt.Errorf("service or principal unit are not alive")
			}
		}
		return nil, err
	}
	// Refresh to pick the txn-revno.
	u := newUnit(s.st, udoc)
	err = u.Refresh()
	if err != nil {
		return nil, err
	}
	return u, nil
}

// AddUnit adds a new principal unit to the service.
func (s *Service) AddUnit() (unit *Unit, err error) {
	defer trivial.ErrorContextf(&err, "cannot add unit to service %q", s)
	ch, _, err := s.Charm()
	if err != nil {
		return nil, err
	}
	if ch.Meta().Subordinate {
		return nil, fmt.Errorf("unit is a subordinate")
	}
	name, err := s.newUnitName()
	if err != nil {
		return nil, err
	}
	return s.addUnit(name, nil)
}

// AddUnitSubordinateTo adds a new subordinate unit to the service,
// subordinate to principal.
func (s *Service) AddUnitSubordinateTo(principal *Unit) (unit *Unit, err error) {
	defer trivial.ErrorContextf(&err, "cannot add unit to service %q as a subordinate of %q", s, principal)
	ch, _, err := s.Charm()
	if err != nil {
		return nil, err
	}
	if !ch.Meta().Subordinate {
		return nil, fmt.Errorf("service is not a subordinate")
	}
	if !principal.IsPrincipal() {
		return nil, fmt.Errorf("unit is not a principal")
	}
	name, err := s.newUnitName()
	if err != nil {
		return nil, err
	}
	return s.addUnit(name, principal)
}

// RemoveUnit removes the given unit from s.
func (s *Service) RemoveUnit(u *Unit) (err error) {
	defer trivial.ErrorContextf(&err, "cannot remove unit %q", u)
	if u.doc.Life != Dead {
		return errors.New("unit is not dead")
	}
	if u.doc.Service != s.doc.Name {
		return fmt.Errorf("unit is not assigned to service %q", s)
	}
	if u.doc.MachineId != nil {
		err = u.UnassignFromMachine()
		if err != nil {
			return err
		}
	}
	ops := []txn.Op{{
		C:      s.st.units.Name,
		Id:     u.doc.Name,
		Assert: D{{"life", Dead}},
		Remove: true,
	}, {
		C:      s.st.services.Name,
		Id:     s.doc.Name,
		Assert: D{{"unitcount", D{{"$gt", 0}}}},
		Update: D{{"$inc", D{{"unitcount", -1}}}},
	}}
	if u.doc.Principal != "" {
		ops = append(ops, txn.Op{
			C:      s.st.units.Name,
			Id:     u.doc.Principal,
			Assert: txn.DocExists,
			Update: D{{"$pull", D{{"subordinates", u.doc.Name}}}},
		})
	}
	// TODO assert that subordinates are empty before deleting
	// a principal
	if err = s.st.runner.Run(ops, "", nil); err != nil {
		// TODO Remove this once we know the logic is right:
		if c, err := s.st.units.FindId(u.doc.Name).Count(); err != nil {
			return err
		} else if c > 0 {
			return fmt.Errorf("cannot remove unit; something smells bad")
		}
		// If aborted, the unit is either dead or recreated.
		return onAbort(err, nil)
	}
	return nil
}

func (s *Service) unitDoc(name string) (*unitDoc, error) {
	udoc := &unitDoc{}
	sel := D{
		{"_id", name},
		{"service", s.doc.Name},
	}
	err := s.st.units.Find(sel).One(udoc)
	if err != nil {
		return nil, err
	}
	return udoc, nil
}

// Unit returns the service's unit with name.
func (s *Service) Unit(name string) (*Unit, error) {
	if !IsUnitName(name) {
		return nil, fmt.Errorf("%q is not a valid unit name", name)
	}
	udoc, err := s.unitDoc(name)
	if err != nil {
		return nil, fmt.Errorf("cannot get unit %q from service %q: %v", name, s.doc.Name, err)
	}
	return newUnit(s.st, udoc), nil
}

// AllUnits returns all units of the service.
func (s *Service) AllUnits() (units []*Unit, err error) {
	docs := []unitDoc{}
	err = s.st.units.Find(D{{"service", s.doc.Name}}).All(&docs)
	if err != nil {
		return nil, fmt.Errorf("cannot get all units from service %q: %v", s, err)
	}
	for i := range docs {
		units = append(units, newUnit(s.st, &docs[i]))
	}
	return units, nil
}

// Relations returns a Relation for every relation the service is in.
func (s *Service) Relations() (relations []*Relation, err error) {
	defer trivial.ErrorContextf(&err, "can't get relations for service %q", s)
	docs := []relationDoc{}
	err = s.st.relations.Find(D{{"endpoints.servicename", s.doc.Name}}).All(&docs)
	if err != nil {
		return nil, err
	}
	for _, v := range docs {
		relations = append(relations, newRelation(s.st, &v))
	}
	return relations, nil
}

// Config returns the configuration node for the service.
func (s *Service) Config() (config *ConfigNode, err error) {
	config, err = readConfigNode(s.st, "s#"+s.Name())
	if err != nil {
		return nil, fmt.Errorf("cannot get configuration of service %q: %v", s, err)
	}
	return config, nil
}
<|endoftext|>


<commit_before>//
// model.go
// template model implementation
//
package srnd

import (
  "fmt"
  "io"
  "path/filepath"
  "strings"
  "time"
)

// base model type
type BaseModel interface {

  // site url prefix
  Prefix() string

  // render to a writer
  RenderTo(wr io.Writer) error

}

// for attachments
type AttachmentModel interface {

  BaseModel

  Thumbnail() string
  Source() string
  Filename() string

}

// for individual posts
type PostModel interface {

  BaseModel

  CSSClass() string

  MessageID() string
  PostHash() string
  ShortHash() string
  PostURL() string
  Frontend() string
  Subject() string
  Name() string
  Date() string
  OP() bool
  Attachments() []AttachmentModel
  Board() string
  Sage() bool
  Pubkey() string
  Reference() string

  RenderBody() string
  RenderPost() string

  IsI2P() bool
  IsTor() bool
  IsClearnet() bool

  // truncate body to a certain size
  // return copy
  Truncate() PostModel

}

// interface for models that have a navbar
type NavbarModel interface {

  Navbar() string

}

// for threads
type ThreadModel interface {

  BaseModel
  NavbarModel

  OP() PostModel
  Replies() []PostModel
  Board() string
  BoardURL() string
  // return a short version of the thread
  // does not include all replies
  Truncate() ThreadModel
  // update the thread's replies
  // return the updated model
  Update(db Database) ThreadModel
}

// board interface
// for 1 page on a board
type BoardModel interface {

  BaseModel
  NavbarModel

  Frontend() string
  Name() string
  Threads() []ThreadModel

  // JUST update this thread
  // if we don't have it already loaded do nothing
  UpdateThread(message_id string, db Database) BoardModel

  // get a thread model with this id
  // returns nil if we don't have it
  GetThread(message_id string) ThreadModel

  // deprecated, use GetThread
  HasThread(message_id string) bool

  // update the board's contents
  // return the updated model
  Update(db Database) BoardModel
}

type LinkModel interface {

  Text() string
  LinkURL() string
}

// newsgroup model
// every page on a newsgroup
type GroupModel []BoardModel

// TODO: optimize using 1 query?
// update every page
// return updated model
func (self GroupModel) UpdateAll(db Database) GroupModel {
  for idx, page := range self {
    self[idx] = page.Update(db)
  }
  return self
}

// update a certain page
// does nothing if out of bounds
func (self GroupModel) Update(page int, db Database) GroupModel {
  if len(self) > page {
    self[page] = self[page].Update(db)
  }
  return self
}

type boardModel struct {
  frontend string
  prefix string
  board string
  page int
  pages int
  threads []ThreadModel
}

func (self boardModel) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("page %d for %s", self.page, self.board)
  param["frontend"] = self.frontend
  var links []LinkModel
  for i := 0 ; i < self.pages ; i ++ {
    links = append(links, linkModel{
      link: fmt.Sprintf("%s%s-%d.html", self.prefix, self.board, i),
      text: fmt.Sprintf("[ %d ]", i),
    })
  }
  param["prefix"] = self.prefix
  param["links"] = links
  return template.renderTemplate("navbar.mustache", param)
}

func (self boardModel) UpdateThread(messageID string, db Database) BoardModel {

  for idx, th := range self.threads {
    if th.OP().MessageID() == messageID {
      // found it
      self.threads[idx] = th.Update(db)
      break
    }
  }
  return self
}

func (self boardModel) GetThread(messageID string) ThreadModel {
  for _, th := range self.threads {
    if th.OP().MessageID() == messageID {
      return th
    }
  }
  return nil
}

func (self boardModel) HasThread(messageID string) bool {
  return self.GetThread(messageID) != nil
}

func (self boardModel) Frontend() string {
  return self.frontend
}

func (self boardModel) Prefix() string {
  return self.prefix
}

func (self boardModel) Name() string {
  return self.board
}

func (self boardModel) Threads() []ThreadModel {
  return self.threads
}

func (self boardModel) RenderTo(wr io.Writer) error {
  param := make(map[string]interface{})
  param["board"] = self
  param["form"] = renderPostForm(self.Prefix(), self.board, "")
  _, err := io.WriteString(wr, template.renderTemplate("board.mustache", param))
  return err
}

// refetch all threads on this page
func (self boardModel) Update(db Database) BoardModel {
  // ignore error
  perpage, _ := db.GetThreadsPerPage(self.board)
  // refetch all on this page
  model := db.GetGroupForPage(self.prefix, self.frontend, self.board, self.page, int(perpage))
  var threads []ThreadModel
  for _, th := range model.Threads() {
    threads = append(threads, th.Update(db))
  }
  return boardModel{
    frontend: self.frontend,
    prefix: self.prefix,
    board: self.board,
    page: self.page,
    pages: self.pages,
    threads: threads,
  }
}

type post struct {
  prefix string
  board string
  name string
  subject string
  message string
  message_rendered string
  message_id string
  path string
  addr string
  op bool
  posted int64
  parent string
  sage bool
  pubkey string
  reference string
  attachments []AttachmentModel
}

type attachment struct {
  prefix string
  filepath string
  filename string
}

func (self attachment) Prefix() string {
  return self.prefix
}

func (self attachment) RenderTo(wr io.Writer) error {
  // does nothing
  return nil
}

func (self attachment) Thumbnail() string {
  return self.prefix + "thm/" + self.filepath + ".jpg"
}

func (self attachment) Source() string {
  return self.prefix + "img/" + self.filepath
}

func (self attachment) Filename() string {
  return self.filename
}

func PostModelFromMessage(parent, prefix string, nntp NNTPMessage) PostModel {
  p := post{}
  p.name = nntp.Name()
  p.subject = nntp.Subject()
  p.message = nntp.Message()
  p.path = nntp.Path()
  p.message_id = nntp.MessageID()
  p.board = nntp.Newsgroup()
  p.posted = nntp.Posted()
  p.op = nntp.OP()
  p.prefix = prefix
  p.parent = parent
  p.addr = nntp.Addr()
  p.sage = nntp.Sage()
  p.pubkey = nntp.Pubkey()
  for _, att := range nntp.Attachments() {
    p.attachments = append(p.attachments, att.ToModel(prefix))
  }
  return p
}

func (self post) Reference() string {
  return self.parent
}

func (self post) ShortHash() string {
  return ShortHashMessageID(self.message_id)
}

func (self post) Pubkey() string {
  if len(self.pubkey) > 0 {
    return fmt.Sprintf("%s", self.pubkey, makeTripcode(self.pubkey))
  }
  return ""
}

func (self post) Sage() bool {
  return self.sage
}

func (self post) CSSClass() string {
  if self.OP() {
    return "post op"
  } else {
    return "post reply"
  }
}

func (self post) OP() bool {
  return self.parent == self.message_id || len(self.parent) == 0
}

func (self post) Date() string {
  return time.Unix(self.posted, 0).Format(time.ANSIC)
}

func (self post) TemplateDir() string {
  return filepath.Join("contrib", "templates", "default")
}

func (self post) MessageID() string  {
  return self.message_id
}

func (self post) Frontend() string {
  idx := strings.LastIndex(self.path, "!")
  if idx == -1 {
    return self.path
  }
  return self.path[idx+1:]
}

func (self post) Board() string {
  return self.board
}

func (self post) PostHash() string {
  return HashMessageID(self.message_id)
}

func (self post) Name() string {
  return self.name
}

func (self post) Subject() string {
  return self.subject
}

func (self post) Attachments() []AttachmentModel {
  return self.attachments
}

func (self post) PostURL() string {
  return fmt.Sprintf("%sthread-%s.html#%s", self.Prefix(), ShortHashMessageID(self.parent), self.PostHash())
}

func (self post) Prefix() string {
  return self.prefix
}

func (self post) IsClearnet() bool {
  return len(self.addr) == encAddrLen()
}

func (self post) IsI2P() bool {
  return len(self.addr) == i2pDestHashLen()
}

func (self post) IsTor() bool {
  return len(self.addr) == 0
}

func (self post) RenderTo(wr io.Writer) error {
  _, err := io.WriteString(wr, self.RenderPost())
  return err
}

func (self post) RenderPost() string {
  return template.renderTemplate("post.mustache", self)
}

func (self post) Truncate() PostModel {
  if len(self.message) > 500 {
    message := self.message[:500] + "\n...\n[Post Truncated]\n"
    subject := self.subject
    if len(subject) > 100 {
      subject = subject[:100] + "..."
    }
    return post{
      message: message,
      prefix: self.prefix,
      board: self.board,
      name: self.name,
      subject: subject,
      message_id: self.message_id,
      path: self.path,
      op: self.op,
      posted: self.posted,
      parent: self.parent,
      sage: self.sage,
      pubkey: self.pubkey,
      reference: self.reference,
      attachments: self.attachments,
      addr: self.addr,
    }
  }
  return self
}

func (self post) RenderShortBody() string {
  // TODO: hardcoded limit
  return memeposting(self.message)
}

func (self post) RenderBody() string {
  // :^)
  if len(self.message_rendered) == 0 {
    self.message_rendered = memeposting(self.message)
  }
  return self.message_rendered
}

type thread struct {
  prefix string
  links []LinkModel
  posts []PostModel
}

func (self thread) Prefix() string {
  return self.prefix
}

func (self thread) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("Thread %s", self.posts[0].ShortHash())
  param["frontend"] = self.Board()
  param["links"] = self.links
  param["prefix"] = self.prefix
  return template.renderTemplate("navbar.mustache", param)
}

func (self thread) Board() string {
  return self.posts[0].Board()
}

func (self thread) BoardURL() string {
  return fmt.Sprintf("%s%s-0.html", self.Prefix(), self.Board())
}

// get our default template dir
func defaultTemplateDir() string {
  return  filepath.Join("contrib", "templates", "default")
}

func (self thread) RenderTo(wr io.Writer) error {
  postform := renderPostForm(self.prefix, self.Board(), self.posts[0].MessageID())
  data := template.renderTemplate("thread.mustache", map[string]interface{} { "thread": self, "form" : postform})
  io.WriteString(wr, data)
  return nil
}

func (self thread) OP() PostModel {
  return self.posts[0]
}

func (self thread) Replies() []PostModel {
  if len(self.posts) > 1 {
    return self.posts[1:]
  }
  return []PostModel{}
}

func (self thread) Truncate() ThreadModel {
  trunc := 5
  if len(self.posts) > trunc {
    return thread{
      links: self.links,
      posts: append([]PostModel{self.posts[0]}, self.posts[len(self.posts)-trunc:]...),
      prefix: self.prefix,
    }
  }
  return self
}

// refetch all replies if anything differs
func (self thread) Update(db Database) ThreadModel {
  root := self.posts[0].MessageID()
  reply_count := db.CountThreadReplies(root)

  if int(reply_count) + 1 != len(self.posts) {

    return thread{
      posts: append([]PostModel{self.posts[0]}, db.GetThreadReplyPostModels(self.prefix, root, 0)...),
      links: self.links,
      prefix: self.prefix,
    }
  }
  return self
}

type linkModel struct {
  text string
  link string
}

func (self linkModel) LinkURL() string {
  return self.link
}

func (self linkModel) Text() string {
  return self.text
}

type boardPageRow struct {
  Board string
  Hour int64
  Day int64
  All int64
}

type boardPageRows []boardPageRow

func (self boardPageRows) Len() int {
  return len(self)
}

func (self boardPageRows) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return (i_val.Day + i_val.Hour * 24 ) > ( j_val.Day + j_val.Hour * 24)
}

func (self boardPageRows) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}

type postsGraphRow struct {
  day time.Time
  Num int64
  mag int64
}

func (p *postsGraphRow) GraphRune(r string) (s string) {
  var num int64
  if p.mag > 0 {
    num = p.Num / p.mag
  } else {
    num = p.Num
  }
  for num > 0 {
    s += r
    num --
  }
  return
}

func (p postsGraphRow) Day() (s string) {
  return p.day.String()[5:11]
}

func (p postsGraphRow) RegularGraph() (s string) {
  return p.GraphRune("=")
}

// :0========3 overcock :3 graph of data
func (p postsGraphRow) OvercockGraph() (s string) {
  var num int64
  if p.mag > 0 {
    num = p.Num / p.mag
  } else {
    num = p.Num
  }
  if num > 0 {
    s = ":0"
    num -= 1
    for num > 0 {
      s += "="
      num--
    }
    s += "3"
  } else {
    s = ":3"
  }
  return
}

type postsGraph []postsGraphRow

func (graph postsGraph) Render() string {
  return template.renderTemplate("posts_graph.mustache", map[string]interface{} { "graph": graph})
}

func (self postsGraph) Len() int {
  return len(self)
}

func (self postsGraph) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return i_val.day.Day() > j_val.day.Day()
}

func (self postsGraph) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}

func (self postsGraph) Scale() (graph postsGraph) {
  // find max
  max := int64(0)
  for _, p := range self {
    if p.Num > max {
      max = p.Num
    }
  }
  mag := max / 25
  for _, p := range self {
    p.mag = mag
    graph = append(graph, p)
  }
  return
}

type overviewModel []PostModel

func (o overviewModel) Render() string {
  return template.renderTemplate("overview.mustache", map[string]interface{} { "overview" : o })
}
<commit_msg>fix comparison<commit_after>//
// model.go
// template model implementation
//
package srnd

import (
  "fmt"
  "io"
  "path/filepath"
  "strings"
  "time"
)

// base model type
type BaseModel interface {

  // site url prefix
  Prefix() string

  // render to a writer
  RenderTo(wr io.Writer) error

}

// for attachments
type AttachmentModel interface {

  BaseModel

  Thumbnail() string
  Source() string
  Filename() string

}

// for individual posts
type PostModel interface {

  BaseModel

  CSSClass() string

  MessageID() string
  PostHash() string
  ShortHash() string
  PostURL() string
  Frontend() string
  Subject() string
  Name() string
  Date() string
  OP() bool
  Attachments() []AttachmentModel
  Board() string
  Sage() bool
  Pubkey() string
  Reference() string

  RenderBody() string
  RenderPost() string

  IsI2P() bool
  IsTor() bool
  IsClearnet() bool

  // truncate body to a certain size
  // return copy
  Truncate() PostModel

}

// interface for models that have a navbar
type NavbarModel interface {

  Navbar() string

}

// for threads
type ThreadModel interface {

  BaseModel
  NavbarModel

  OP() PostModel
  Replies() []PostModel
  Board() string
  BoardURL() string
  // return a short version of the thread
  // does not include all replies
  Truncate() ThreadModel
  // update the thread's replies
  // return the updated model
  Update(db Database) ThreadModel
}

// board interface
// for 1 page on a board
type BoardModel interface {

  BaseModel
  NavbarModel

  Frontend() string
  Name() string
  Threads() []ThreadModel

  // JUST update this thread
  // if we don't have it already loaded do nothing
  UpdateThread(message_id string, db Database) BoardModel

  // get a thread model with this id
  // returns nil if we don't have it
  GetThread(message_id string) ThreadModel

  // deprecated, use GetThread
  HasThread(message_id string) bool

  // update the board's contents
  // return the updated model
  Update(db Database) BoardModel
}

type LinkModel interface {

  Text() string
  LinkURL() string
}

// newsgroup model
// every page on a newsgroup
type GroupModel []BoardModel

// TODO: optimize using 1 query?
// update every page
// return updated model
func (self GroupModel) UpdateAll(db Database) GroupModel {
  for idx, page := range self {
    self[idx] = page.Update(db)
  }
  return self
}

// update a certain page
// does nothing if out of bounds
func (self GroupModel) Update(page int, db Database) GroupModel {
  if len(self) > page {
    self[page] = self[page].Update(db)
  }
  return self
}

type boardModel struct {
  frontend string
  prefix string
  board string
  page int
  pages int
  threads []ThreadModel
}

func (self boardModel) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("page %d for %s", self.page, self.board)
  param["frontend"] = self.frontend
  var links []LinkModel
  for i := 0 ; i < self.pages ; i ++ {
    links = append(links, linkModel{
      link: fmt.Sprintf("%s%s-%d.html", self.prefix, self.board, i),
      text: fmt.Sprintf("[ %d ]", i),
    })
  }
  param["prefix"] = self.prefix
  param["links"] = links
  return template.renderTemplate("navbar.mustache", param)
}

func (self boardModel) UpdateThread(messageID string, db Database) BoardModel {

  for idx, th := range self.threads {
    if th.OP().MessageID() == messageID {
      // found it
      self.threads[idx] = th.Update(db)
      break
    }
  }
  return self
}

func (self boardModel) GetThread(messageID string) ThreadModel {
  for _, th := range self.threads {
    if th.OP().MessageID() == messageID {
      return th
    }
  }
  return nil
}

func (self boardModel) HasThread(messageID string) bool {
  return self.GetThread(messageID) != nil
}

func (self boardModel) Frontend() string {
  return self.frontend
}

func (self boardModel) Prefix() string {
  return self.prefix
}

func (self boardModel) Name() string {
  return self.board
}

func (self boardModel) Threads() []ThreadModel {
  return self.threads
}

func (self boardModel) RenderTo(wr io.Writer) error {
  param := make(map[string]interface{})
  param["board"] = self
  param["form"] = renderPostForm(self.Prefix(), self.board, "")
  _, err := io.WriteString(wr, template.renderTemplate("board.mustache", param))
  return err
}

// refetch all threads on this page
func (self boardModel) Update(db Database) BoardModel {
  // ignore error
  perpage, _ := db.GetThreadsPerPage(self.board)
  // refetch all on this page
  model := db.GetGroupForPage(self.prefix, self.frontend, self.board, self.page, int(perpage))
  var threads []ThreadModel
  for _, th := range model.Threads() {
    threads = append(threads, th.Update(db))
  }
  return boardModel{
    frontend: self.frontend,
    prefix: self.prefix,
    board: self.board,
    page: self.page,
    pages: self.pages,
    threads: threads,
  }
}

type post struct {
  prefix string
  board string
  name string
  subject string
  message string
  message_rendered string
  message_id string
  path string
  addr string
  op bool
  posted int64
  parent string
  sage bool
  pubkey string
  reference string
  attachments []AttachmentModel
}

type attachment struct {
  prefix string
  filepath string
  filename string
}

func (self attachment) Prefix() string {
  return self.prefix
}

func (self attachment) RenderTo(wr io.Writer) error {
  // does nothing
  return nil
}

func (self attachment) Thumbnail() string {
  return self.prefix + "thm/" + self.filepath + ".jpg"
}

func (self attachment) Source() string {
  return self.prefix + "img/" + self.filepath
}

func (self attachment) Filename() string {
  return self.filename
}

func PostModelFromMessage(parent, prefix string, nntp NNTPMessage) PostModel {
  p := post{}
  p.name = nntp.Name()
  p.subject = nntp.Subject()
  p.message = nntp.Message()
  p.path = nntp.Path()
  p.message_id = nntp.MessageID()
  p.board = nntp.Newsgroup()
  p.posted = nntp.Posted()
  p.op = nntp.OP()
  p.prefix = prefix
  p.parent = parent
  p.addr = nntp.Addr()
  p.sage = nntp.Sage()
  p.pubkey = nntp.Pubkey()
  for _, att := range nntp.Attachments() {
    p.attachments = append(p.attachments, att.ToModel(prefix))
  }
  return p
}

func (self post) Reference() string {
  return self.parent
}

func (self post) ShortHash() string {
  return ShortHashMessageID(self.message_id)
}

func (self post) Pubkey() string {
  if len(self.pubkey) > 0 {
    return fmt.Sprintf("%s", self.pubkey, makeTripcode(self.pubkey))
  }
  return ""
}

func (self post) Sage() bool {
  return self.sage
}

func (self post) CSSClass() string {
  if self.OP() {
    return "post op"
  } else {
    return "post reply"
  }
}

func (self post) OP() bool {
  return self.parent == self.message_id || len(self.parent) == 0
}

func (self post) Date() string {
  return time.Unix(self.posted, 0).Format(time.ANSIC)
}

func (self post) TemplateDir() string {
  return filepath.Join("contrib", "templates", "default")
}

func (self post) MessageID() string  {
  return self.message_id
}

func (self post) Frontend() string {
  idx := strings.LastIndex(self.path, "!")
  if idx == -1 {
    return self.path
  }
  return self.path[idx+1:]
}

func (self post) Board() string {
  return self.board
}

func (self post) PostHash() string {
  return HashMessageID(self.message_id)
}

func (self post) Name() string {
  return self.name
}

func (self post) Subject() string {
  return self.subject
}

func (self post) Attachments() []AttachmentModel {
  return self.attachments
}

func (self post) PostURL() string {
  return fmt.Sprintf("%sthread-%s.html#%s", self.Prefix(), ShortHashMessageID(self.parent), self.PostHash())
}

func (self post) Prefix() string {
  return self.prefix
}

func (self post) IsClearnet() bool {
  return len(self.addr) == encAddrLen()
}

func (self post) IsI2P() bool {
  return len(self.addr) == i2pDestHashLen()
}

func (self post) IsTor() bool {
  return len(self.addr) == 0
}

func (self post) RenderTo(wr io.Writer) error {
  _, err := io.WriteString(wr, self.RenderPost())
  return err
}

func (self post) RenderPost() string {
  return template.renderTemplate("post.mustache", self)
}

func (self post) Truncate() PostModel {
  if len(self.message) > 500 {
    message := self.message[:500] + "\n...\n[Post Truncated]\n"
    subject := self.subject
    if len(subject) > 100 {
      subject = subject[:100] + "..."
    }
    return post{
      message: message,
      prefix: self.prefix,
      board: self.board,
      name: self.name,
      subject: subject,
      message_id: self.message_id,
      path: self.path,
      op: self.op,
      posted: self.posted,
      parent: self.parent,
      sage: self.sage,
      pubkey: self.pubkey,
      reference: self.reference,
      attachments: self.attachments,
      addr: self.addr,
    }
  }
  return self
}

func (self post) RenderShortBody() string {
  // TODO: hardcoded limit
  return memeposting(self.message)
}

func (self post) RenderBody() string {
  // :^)
  if len(self.message_rendered) == 0 {
    self.message_rendered = memeposting(self.message)
  }
  return self.message_rendered
}

type thread struct {
  prefix string
  links []LinkModel
  posts []PostModel
}

func (self thread) Prefix() string {
  return self.prefix
}

func (self thread) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("Thread %s", self.posts[0].ShortHash())
  param["frontend"] = self.Board()
  param["links"] = self.links
  param["prefix"] = self.prefix
  return template.renderTemplate("navbar.mustache", param)
}

func (self thread) Board() string {
  return self.posts[0].Board()
}

func (self thread) BoardURL() string {
  return fmt.Sprintf("%s%s-0.html", self.Prefix(), self.Board())
}

// get our default template dir
func defaultTemplateDir() string {
  return  filepath.Join("contrib", "templates", "default")
}

func (self thread) RenderTo(wr io.Writer) error {
  postform := renderPostForm(self.prefix, self.Board(), self.posts[0].MessageID())
  data := template.renderTemplate("thread.mustache", map[string]interface{} { "thread": self, "form" : postform})
  io.WriteString(wr, data)
  return nil
}

func (self thread) OP() PostModel {
  return self.posts[0]
}

func (self thread) Replies() []PostModel {
  if len(self.posts) > 1 {
    return self.posts[1:]
  }
  return []PostModel{}
}

func (self thread) Truncate() ThreadModel {
  trunc := 5
  if len(self.posts) > trunc {
    return thread{
      links: self.links,
      posts: append([]PostModel{self.posts[0]}, self.posts[len(self.posts)-trunc:]...),
      prefix: self.prefix,
    }
  }
  return self
}

// refetch all replies if anything differs
func (self thread) Update(db Database) ThreadModel {
  root := self.posts[0].MessageID()
  reply_count := db.CountThreadReplies(root)

  if int(reply_count) + 1 != len(self.posts) {

    return thread{
      posts: append([]PostModel{self.posts[0]}, db.GetThreadReplyPostModels(self.prefix, root, 0)...),
      links: self.links,
      prefix: self.prefix,
    }
  }
  return self
}

type linkModel struct {
  text string
  link string
}

func (self linkModel) LinkURL() string {
  return self.link
}

func (self linkModel) Text() string {
  return self.text
}

type boardPageRow struct {
  Board string
  Hour int64
  Day int64
  All int64
}

type boardPageRows []boardPageRow

func (self boardPageRows) Len() int {
  return len(self)
}

func (self boardPageRows) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return (i_val.Day + i_val.Hour * 24 ) > ( j_val.Day + j_val.Hour * 24)
}

func (self boardPageRows) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}

type postsGraphRow struct {
  day time.Time
  Num int64
  mag int64
}

func (p *postsGraphRow) GraphRune(r string) (s string) {
  var num int64
  if p.mag > 0 {
    num = p.Num / p.mag
  } else {
    num = p.Num
  }
  for num > 0 {
    s += r
    num --
  }
  return
}

func (p postsGraphRow) Day() (s string) {
  return p.day.String()[5:11]
}

func (p postsGraphRow) RegularGraph() (s string) {
  return p.GraphRune("=")
}

// :0========3 overcock :3 graph of data
func (p postsGraphRow) OvercockGraph() (s string) {
  var num int64
  if p.mag > 0 {
    num = p.Num / p.mag
  } else {
    num = p.Num
  }
  if num > 0 {
    s = ":0"
    num -= 1
    for num > 0 {
      s += "="
      num--
    }
    s += "3"
  } else {
    s = ":3"
  }
  return
}

type postsGraph []postsGraphRow

func (graph postsGraph) Render() string {
  return template.renderTemplate("posts_graph.mustache", map[string]interface{} { "graph": graph})
}

func (self postsGraph) Len() int {
  return len(self)
}

func (self postsGraph) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return i_val.day.Unix() > j_val.day.Unix()
}

func (self postsGraph) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}

func (self postsGraph) Scale() (graph postsGraph) {
  // find max
  max := int64(0)
  for _, p := range self {
    if p.Num > max {
      max = p.Num
    }
  }
  mag := max / 25
  for _, p := range self {
    p.mag = mag
    graph = append(graph, p)
  }
  return
}

type overviewModel []PostModel

func (o overviewModel) Render() string {
  return template.renderTemplate("overview.mustache", map[string]interface{} { "overview" : o })
}
<|endoftext|>