<commit_before>package mch

import (
	"net/http"
	"crypto/md5"
	"crypto/hmac"
	"fmt"
	"crypto/sha256"
	"go.uber.org/zap"
	"github.com/jiudaoyun/wechat"
	"io"
	"encoding/base64"
	"github.com/nanjishidu/gomini/gocrypto"
	"bytes"
	"strconv"
	"time"
)

type NotifyMsg struct {
	AppID    string
	MchID    string
	SubAppID string
	SubMchID string

	OrderInfo
}

type RefundNotifyMsg struct {
	TransactionID string
	OutTradeNO string
	RefundID string
	OutRefundNO string
	TotalFee int
	RefundFee int
	RefundStatus RefundStatus
	SuccessTime time.Time
}

type NotifyHandler struct {
	payNotifyHandler func(*NotifyMsg) error
	refundNotifyHandler func(*RefundNotifyMsg) error

	appID  string
	mchID  string
	apiKey string

	subAppID string
	subMchID string

	*zap.SugaredLogger
}

func NewNotifyHandler(appID, mchID, apiKey string, payNofityHandler func(*NotifyMsg) error, refundNotifyHandler func(*RefundNotifyMsg) error) *NotifyHandler {
	apiKeyMD5 := fmt.Sprintf("%x", md5.Sum([]byte(apiKey)))
	gocrypto.SetAesKey(apiKeyMD5)

	return &NotifyHandler{
		payNotifyHandler: payNofityHandler,
		refundNotifyHandler: refundNotifyHandler,

		appID:  appID,
		mchID:  mchID,
		apiKey: apiKey,

		SugaredLogger: wechat.Sugar,
	}
}

func (nm *NotifyHandler) reply(w io.Writer, code, reason string) {
	err := EncodeXML(w, map[string]string{
		"return_code": code,
		"return_msg":  reason,
	})
	if err != nil {
		nm.Errorw("NotifyHandler write XML failed", "error", err)
	}
}

func (nm *NotifyHandler) replyError(w io.Writer, reason string) {
	nm.Errorw("NotifyHandler reply error", "error", reason)
	nm.reply(w, ReturnCodeFail, reason)
}

func (nm *NotifyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	nm.Serve(w, r, nm.payNotifyHandler, nm.refundNotifyHandler)
}

func (nm *NotifyHandler) Serve(w io.Writer, r *http.Request, payNotifyHandler func(*NotifyMsg) error, refundNotifyHandler func(*RefundNotifyMsg) error) {
	if r.Method != "POST" {
		nm.replyError(w, "unexpected HTTP Method: "+r.Method)
		return
	}

	req, err := DecodeXML(r.Body)
	if err != nil {
		nm.replyError(w, err.Error())
		return
	}

	nm.Infof("wechat callback: %s", req)

	returnCode := req["return_code"]
	if returnCode != ReturnCodeSuccess {
		err = &Error{
			ReturnCode: returnCode,
			ReturnMsg:  req["return_msg"],
		}
		nm.replyError(w, err.Error())
		return
	}

	if nm.appID != "" {
		wantAppId := nm.appID
		haveAppId := req["appid"]
		if !wechat.Compare(haveAppId, wantAppId) {
			nm.replyError(w, fmt.Sprintf("appid mismatch, have: %s, want: %s", haveAppId, wantAppId))
			return
		}
	}
	if nm.mchID != "" {
		wantMchId := nm.mchID
		haveMchId := req["mch_id"]
		if !wechat.Compare(haveMchId, wantMchId) {
			nm.replyError(w, fmt.Sprintf("mch_id mismatch, have: %s, want: %s", haveMchId, wantMchId))
			return
		}
	}

	if nm.subAppID != "" {
		wantSubAppId := nm.subAppID
		haveSubAppId := req["sub_appid"]
		if haveSubAppId != "" && !wechat.Compare(haveSubAppId, wantSubAppId) {
			nm.replyError(w, fmt.Sprintf("sub_appid mismatch, have: %s, want: %s", haveSubAppId, wantSubAppId))
			return
		}
	}
	if nm.subMchID != "" {
		wantSubMchId := nm.subMchID
		haveSubMchId := req["sub_mch_id"]
		if !wechat.Compare(haveSubMchId, wantSubMchId) {
			nm.replyError(w, fmt.Sprintf("sub_mch_id mismatch, have: %s, want: %s", haveSubMchId, wantSubMchId))
			return
		}
	}

	// 1. refund order notify
	if reqInfo, ok := req["req_info"]; ok {
		encrypted, err := base64.StdEncoding.DecodeString(reqInfo)
		if err != nil {
			nm.replyError(w, err.Error())
			return
		}
		decrypted, err := gocrypto.AesECBDecrypt(encrypted)
		if err != nil {
			nm.replyError(w, err.Error())
			return
		}
		nm.Infof("wechat callback: %s", decrypted)
		info, err := DecodeXML(bytes.NewBuffer(decrypted))
		if err != nil {
			nm.replyError(w, err.Error())
			return
		}

		msg, err := getRefundNotifyMsg(info)
		if err != nil {
			nm.replyError(w, err.Error())
			return
		}

		err = refundNotifyHandler(msg)
		if err != nil {
			nm.replyError(w, err.Error())
			return
		}
		return
	}

	// 2. pay order notify

	haveSign := req["sign"]
	var wantSign string
	switch signType := req["sign_type"]; signType {
	case "", SignTypeMD5:
		wantSign = Sign(req, nm.apiKey, md5.New())
	case SignTypeHMAC_SHA256:
		wantSign = Sign(req, nm.apiKey, hmac.New(sha256.New, []byte(nm.apiKey)))
	default:
		nm.replyError(w, fmt.Sprintf("unsupported notification sign_type: %s", signType))
		return
	}
	if !wechat.Compare(haveSign, wantSign) {
		nm.replyError(w, fmt.Sprintf("sign mismatch,\nhave: %s,\nwant: %s", haveSign, wantSign))
		return
	}

	resultCode := req["result_code"]
	if resultCode != ResultCodeSuccess {
		err = &BizError{
			ResultCode:  resultCode,
			ErrCode:     req["err_code"],
			ErrCodeDesc: req["err_code_des"],
		}
		nm.replyError(w, err.Error())
		return
	}

	orderInfo, err := getOrderInfo(req)
	if err != nil {
		nm.replyError(w, err.Error())
	}

	msg := NotifyMsg{
		AppID: req["appid"],
		MchID: req["mch_id"],
		SubAppID: req["sub_appid"],
		SubMchID: req["sub_mch_id"],
		OrderInfo: *orderInfo,
	}
	err = payNotifyHandler(&msg)
	if err != nil {
		nm.replyError(w, err.Error())
	}

	nm.reply(w, ReturnCodeSuccess, "OK")
}

func getRefundNotifyMsg(req map[string]string) (*RefundNotifyMsg, error) {
	totalFee, err := strconv.Atoi(req["total_fee"])
	if err != nil {
		return nil, err
	}
	refundFee, err := strconv.Atoi(req["refund_fee"])
	if err != nil {
		return nil, err
	}
	var successTime time.Time
	if str, ok := req["success_time"]; ok {
		successTime, err = time.Parse("2006-01-02 15:04:05", str)
		if err != nil {
			return nil, err
		}
	}

	return &RefundNotifyMsg{
		TransactionID: req["transaction_id"],
		OutTradeNO: req["out_trade_no"],
		RefundID: req["refund_id"],
		OutRefundNO: req["out_refund_no"],
		TotalFee: totalFee,
		RefundFee: refundFee,
		RefundStatus: RefundStatus(req["refund_status"]),
		SuccessTime: successTime,
	}, nil
}
<commit_msg>Update files<commit_after>package mch

import (
	"net/http"
	"crypto/md5"
	"crypto/hmac"
	"fmt"
	"crypto/sha256"
	"go.uber.org/zap"
	"github.com/jiudaoyun/wechat"
	"io"
	"encoding/base64"
	"github.com/nanjishidu/gomini/gocrypto"
	"bytes"
	"strconv"
	"time"
)

type NotifyMsg struct {
	AppID    string
	MchID    string
	SubAppID string
	SubMchID string

	OrderInfo
}

type RefundNotifyMsg struct {
	TransactionID string
	OutTradeNO string
	RefundID string
	OutRefundNO string
	TotalFee int
	RefundFee int
	RefundStatus RefundStatus
	SuccessTime time.Time
}

type NotifyHandler struct {
	payNotifyHandler func(*NotifyMsg) error
	refundNotifyHandler func(*RefundNotifyMsg) error

	appID  string
	mchID  string
	apiKey string

	subAppID string
	subMchID string

	*zap.SugaredLogger
}

func NewNotifyHandler(appID, mchID, apiKey string, payNofityHandler func(*NotifyMsg) error, refundNotifyHandler func(*RefundNotifyMsg) error) *NotifyHandler {
	apiKeyMD5 := fmt.Sprintf("%x", md5.Sum([]byte(apiKey)))
	gocrypto.SetAesKey(apiKeyMD5)

	return &NotifyHandler{
		payNotifyHandler: payNofityHandler,
		refundNotifyHandler: refundNotifyHandler,

		appID:  appID,
		mchID:  mchID,
		apiKey: apiKey,

		SugaredLogger: wechat.Sugar,
	}
}

func (nm *NotifyHandler) reply(w io.Writer, code, reason string) {
	err := EncodeXML(w, map[string]string{
		"return_code": code,
		"return_msg":  reason,
	})
	if err != nil {
		nm.Errorw("NotifyHandler write XML failed", "error", err)
	}
}

func (nm *NotifyHandler) replyError(w io.Writer, reason string) {
	nm.Errorw("NotifyHandler reply error", "error", reason)
	nm.reply(w, ReturnCodeFail, reason)
}

func (nm *NotifyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	nm.Serve(w, r, nm.payNotifyHandler, nm.refundNotifyHandler)
}

func (nm *NotifyHandler) Serve(w io.Writer, r *http.Request, payNotifyHandler func(*NotifyMsg) error, refundNotifyHandler func(*RefundNotifyMsg) error) {
	if r.Method != "POST" {
		nm.replyError(w, "unexpected HTTP Method: "+r.Method)
		return
	}

	req, err := DecodeXML(r.Body)
	if err != nil {
		nm.replyError(w, err.Error())
		return
	}

	nm.Infof("wechat callback: %s", req)

	returnCode := req["return_code"]
	if returnCode != ReturnCodeSuccess {
		err = &Error{
			ReturnCode: returnCode,
			ReturnMsg:  req["return_msg"],
		}
		nm.replyError(w, err.Error())
		return
	}

	if nm.appID != "" {
		wantAppId := nm.appID
		haveAppId := req["appid"]
		if !wechat.Compare(haveAppId, wantAppId) {
			nm.replyError(w, fmt.Sprintf("appid mismatch, have: %s, want: %s", haveAppId, wantAppId))
			return
		}
	}
	if nm.mchID != "" {
		wantMchId := nm.mchID
		haveMchId := req["mch_id"]
		if !wechat.Compare(haveMchId, wantMchId) {
			nm.replyError(w, fmt.Sprintf("mch_id mismatch, have: %s, want: %s", haveMchId, wantMchId))
			return
		}
	}

	if nm.subAppID != "" {
		wantSubAppId := nm.subAppID
		haveSubAppId := req["sub_appid"]
		if haveSubAppId != "" && !wechat.Compare(haveSubAppId, wantSubAppId) {
			nm.replyError(w, fmt.Sprintf("sub_appid mismatch, have: %s, want: %s", haveSubAppId, wantSubAppId))
			return
		}
	}
	if nm.subMchID != "" {
		wantSubMchId := nm.subMchID
		haveSubMchId := req["sub_mch_id"]
		if !wechat.Compare(haveSubMchId, wantSubMchId) {
			nm.replyError(w, fmt.Sprintf("sub_mch_id mismatch, have: %s, want: %s", haveSubMchId, wantSubMchId))
			return
		}
	}

	// 1. refund order notify
	if reqInfo, ok := req["req_info"]; ok {
		encrypted, err := base64.StdEncoding.DecodeString(reqInfo)
		if err != nil {
			nm.replyError(w, err.Error())
			return
		}
		decrypted, err := gocrypto.AesECBDecrypt(encrypted)
		if err != nil {
			nm.replyError(w, err.Error())
			return
		}
		nm.Infof("wechat callback: %s", decrypted)
		info, err := DecodeXML(bytes.NewBuffer(decrypted))
		if err != nil {
			nm.replyError(w, err.Error())
			return
		}

		msg, err := getRefundNotifyMsg(info)
		if err != nil {
			nm.replyError(w, err.Error())
			return
		}

		err = refundNotifyHandler(msg)
		if err != nil {
			nm.replyError(w, err.Error())
			return
		}

		nm.reply(w, ReturnCodeSuccess, "OK")
		return
	}

	// 2. pay order notify

	haveSign := req["sign"]
	var wantSign string
	switch signType := req["sign_type"]; signType {
	case "", SignTypeMD5:
		wantSign = Sign(req, nm.apiKey, md5.New())
	case SignTypeHMAC_SHA256:
		wantSign = Sign(req, nm.apiKey, hmac.New(sha256.New, []byte(nm.apiKey)))
	default:
		nm.replyError(w, fmt.Sprintf("unsupported notification sign_type: %s", signType))
		return
	}
	if !wechat.Compare(haveSign, wantSign) {
		nm.replyError(w, fmt.Sprintf("sign mismatch,\nhave: %s,\nwant: %s", haveSign, wantSign))
		return
	}

	resultCode := req["result_code"]
	if resultCode != ResultCodeSuccess {
		err = &BizError{
			ResultCode:  resultCode,
			ErrCode:     req["err_code"],
			ErrCodeDesc: req["err_code_des"],
		}
		nm.replyError(w, err.Error())
		return
	}

	orderInfo, err := getOrderInfo(req)
	if err != nil {
		nm.replyError(w, err.Error())
		return
	}

	msg := NotifyMsg{
		AppID: req["appid"],
		MchID: req["mch_id"],
		SubAppID: req["sub_appid"],
		SubMchID: req["sub_mch_id"],
		OrderInfo: *orderInfo,
	}
	err = payNotifyHandler(&msg)
	if err != nil {
		nm.replyError(w, err.Error())
		return
	}

	nm.reply(w, ReturnCodeSuccess, "OK")
}

func getRefundNotifyMsg(req map[string]string) (*RefundNotifyMsg, error) {
	totalFee, err := strconv.Atoi(req["total_fee"])
	if err != nil {
		return nil, err
	}
	refundFee, err := strconv.Atoi(req["refund_fee"])
	if err != nil {
		return nil, err
	}
	var successTime time.Time
	if str, ok := req["success_time"]; ok {
		successTime, err = time.Parse("2006-01-02 15:04:05", str)
		if err != nil {
			return nil, err
		}
	}

	return &RefundNotifyMsg{
		TransactionID: req["transaction_id"],
		OutTradeNO: req["out_trade_no"],
		RefundID: req["refund_id"],
		OutRefundNO: req["out_refund_no"],
		TotalFee: totalFee,
		RefundFee: refundFee,
		RefundStatus: RefundStatus(req["refund_status"]),
		SuccessTime: successTime,
	}, nil
}
<|endoftext|>


<commit_before>package lclip

import (
	"encoding/json"
	"os"
	"path/filepath"

	"github.com/mitchellh/go-homedir"
)

func DefaultPath() (string, error) {
	h, err := homedir.Dir()
	if err != nil {
		return "", err
	}
	return filepath.Join(h, ".lclip.json"), nil
}

type Clipboard struct {
	path    string
	storage map[string]string
}

func NewClipboard(path string) (*Clipboard, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	c := &Clipboard{path: path}
	if err = json.NewDecoder(f).Decode(&c.storage); err != nil {
		return nil, err
	}
	return c, nil
}

func (c *Clipboard) Get(label string) string {
	return c.storage[label]
}

func (c *Clipboard) Set(label, data string) {
	c.storage[label] = data
}

func (c *Clipboard) Labels() []string {
	return []string{}
}

func (c *Clipboard) Close() error {
	f, err := os.Create(c.path)
	if err != nil {
		return err
	}
	return json.NewEncoder(f).Encode(&c.storage)
}
<commit_msg>Implement Labels as fake<commit_after>package lclip

import (
	"encoding/json"
	"os"
	"path/filepath"

	"github.com/mitchellh/go-homedir"
)

func DefaultPath() (string, error) {
	h, err := homedir.Dir()
	if err != nil {
		return "", err
	}
	return filepath.Join(h, ".lclip.json"), nil
}

type Clipboard struct {
	path    string
	storage map[string]string
}

func NewClipboard(path string) (*Clipboard, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	c := &Clipboard{path: path}
	if err = json.NewDecoder(f).Decode(&c.storage); err != nil {
		return nil, err
	}
	return c, nil
}

func (c *Clipboard) Get(label string) string {
	return c.storage[label]
}

func (c *Clipboard) Set(label, data string) {
	c.storage[label] = data
}

func (c *Clipboard) Labels() []string {
	return []string{"foo", "bar", "baz"}
}

func (c *Clipboard) Close() error {
	f, err := os.Create(c.path)
	if err != nil {
		return err
	}
	return json.NewEncoder(f).Encode(&c.storage)
}
<|endoftext|>


<commit_before>// Copyright 2012 The go-gl 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 glh

import (
	"github.com/go-gl/gl"
)

// Mesh describes data for a single mesh object.
type Mesh struct {
	Colors   [][4]float32
	Vertices [][3]float32
	Textures [][3]float32
	Normals  [][3]float32
	Indices  []uint
}

// MeshDescriptor describes the data offsets for a single
// mesh inside a mesh buffer.
type MeshDescriptor struct {
	VertexStart  int // Start index for vertex data.
	VertexCount  int // Number of vertices.
	TextureStart int // Start index for texture coordinate data.
	TextureCount int // Number of texture coordinates.
	NormalStart  int // Start index for normal data.
	NormalCount  int // Number of normals.
	ColorStart   int // Start index for color data.
	ColorCount   int // Number of colors.
	IndexStart   int // Start index for index data.
	IndexCount   int // Number of indices.
}

// Known mesh buffer states.
const (
	mbClean = iota
	mbDirty
	mbFrozen
)

// These define the size of each component, in bytes.
var (
	mbVertexStride  = int(Sizeof(gl.FLOAT)) * 3
	mbColorStride   = int(Sizeof(gl.FLOAT)) * 4
	mbNormalStride  = int(Sizeof(gl.FLOAT)) * 3
	mbTextureStride = int(Sizeof(gl.FLOAT)) * 3
	mbIndexStride   = int(Sizeof(gl.UNSIGNED_INT))
)

// MeshBuffer represents a mesh buffer.
// It can cache and render vertices, colors, texture coords and normals
// for an arbitrary amount of independent meshes.
type MeshBuffer struct {
	meshes   []*MeshDescriptor // Mesh descriptor.
	vertices [][3]float32      // List of vertices.
	colors   [][4]float32      // List of colors.
	normals  [][3]float32      // List of normals.
	textures [][3]float32      // List of texture coords.
	indices  []uint            // List of indices.

	vertexId  gl.Buffer // Vertex buffer identity.
	colorId   gl.Buffer // Color buffer identity.
	textureId gl.Buffer // Texture buffer identity.
	normalId  gl.Buffer // Normal buffer identity.
	indexId   gl.Buffer // Index buffer identity.

	gpuVertexSize  int // Current size of the vertex buffer in GPU.
	gpuColorSize   int // Current size of the color buffer in GPU.
	gpuTextureSize int // Current size of the texture coord buffer in GPU.
	gpuNormalSize  int // Current size of the normal buffer in GPU.
	gpuIndexSize   int // Current size of the indices buffer in GPU.

	state int       // Mesh buffer state.
	usage gl.GLenum // Usage flag. GL_DYNAMIC, STATIC_DRAW. GL_STREAM_DRAW, etc/
}

// NewMeshBuffer returns a new mesh buffer object.
func NewMeshBuffer() *MeshBuffer {
	mb := new(MeshBuffer)
	mb.state = mbDirty
	mb.usage = gl.STATIC_DRAW
	mb.vertexId = gl.GenBuffer()
	mb.colorId = gl.GenBuffer()
	mb.textureId = gl.GenBuffer()
	mb.normalId = gl.GenBuffer()
	mb.indexId = gl.GenBuffer()
	return mb
}

// Release releases all resources for this buffer.
func (mb *MeshBuffer) Release() {
	mb.vertexId.Delete()
	mb.indexId.Delete()
	mb.vertexId.Delete()
	mb.colorId.Delete()
	mb.textureId.Delete()
	mb.normalId.Delete()
	mb.indexId.Delete()
	mb.Clear()
}

// Clear clears the mesh buffer.
func (mb *MeshBuffer) Clear() {
	mb.state = mbFrozen
	mb.vertices = nil
	mb.colors = nil
	mb.textures = nil
	mb.normals = nil
	mb.indices = nil
	mb.meshes = nil
	mb.gpuVertexSize = 0
	mb.gpuColorSize = 0
	mb.gpuTextureSize = 0
	mb.gpuNormalSize = 0
	mb.gpuIndexSize = 0
	mb.state = mbDirty
}

// Render renders the entire mesh buffer.
// The mode defines one of the symbolic constants like GL_TRIANGLES,
// GL_QUADS, GL_POLYGON, GL_TRIANGLE_STRIP, etc.
func (mb *MeshBuffer) Render(mode gl.GLenum) {
	if len(mb.meshes) == 0 {
		return
	}

	mb.render(
		mode,
		0, len(mb.vertices),
		0, len(mb.colors),
		0, len(mb.textures),
		0, len(mb.normals),
		0, len(mb.indices),
	)
}

// RenderMesh renders a single mesh, idenfified by its index.
// The mode defines one of the symbolic constants like GL_TRIANGLES,
// GL_QUADS, GL_POLYGON, GL_TRIANGLE_STRIP, etc.
func (mb *MeshBuffer) RenderMesh(index int, mode gl.GLenum) {
	if index < 0 || index >= len(mb.meshes) {
		return
	}

	if mb.state != mbClean {
		mb.Commit()
	}

	md := mb.meshes[index]

	mb.render(
		mode,
		md.VertexStart, md.VertexCount,
		md.ColorStart, md.ColorCount,
		md.TextureStart, md.TextureCount,
		md.NormalStart, md.NormalCount,
		md.IndexStart, md.IndexCount,
	)
}

// render draws the elements defined by the given start and count values for
// [v]ertices, [c]olors, [t]exture coords, [n]ormals and [i]ndices.
func (mb *MeshBuffer) render(mode gl.GLenum, vs, vc, cs, cc, ts, tc, ns, nc, is, ic int) {
	if mb.state != mbClean {
		mb.Commit()
	}

	gl.PushClientAttrib(gl.CLIENT_VERTEX_ARRAY_BIT)
	defer gl.PopClientAttrib()

	gl.EnableClientState(gl.VERTEX_ARRAY)
	defer gl.DisableClientState(gl.VERTEX_ARRAY)

	if cc > 0 {
		gl.EnableClientState(gl.COLOR_ARRAY)
		defer gl.DisableClientState(gl.COLOR_ARRAY)
	}

	if tc > 0 {
		gl.EnableClientState(gl.TEXTURE_COORD_ARRAY)
		defer gl.DisableClientState(gl.TEXTURE_COORD_ARRAY)
	}

	if nc > 0 {
		gl.EnableClientState(gl.NORMAL_ARRAY)
		defer gl.DisableClientState(gl.NORMAL_ARRAY)
	}

	if ic > 0 {
		start := is * mbIndexStride

		mb.indexId.Bind(gl.ELEMENT_ARRAY_BUFFER)
		gl.DrawElements(mode, ic, gl.UNSIGNED_INT, uintptr(start))
		mb.indexId.Unbind(gl.ELEMENT_ARRAY_BUFFER)
	} else {
		start := vs * mbVertexStride

		mb.vertexId.Bind(gl.ARRAY_BUFFER)
		gl.DrawArrays(mode, start, vc)
		mb.vertexId.Unbind(gl.ARRAY_BUFFER)
	}
}

// Commit pushes the buffer data to the GPU.
//
// This is normally called implicitely by MeshBuffer.Render and only
// when necessary. However, it may need to be called manually, when the
// buffer data is changed.
func (mb *MeshBuffer) Commit() {
	if mb.state == mbFrozen {
		return
	}

	// Upload vertices.
	size := len(mb.vertices) * mbVertexStride
	if size > 0 {
		mb.vertexId.Bind(gl.ARRAY_BUFFER)

		if size != mb.gpuVertexSize {
			gl.BufferData(gl.ARRAY_BUFFER, size, mb.vertices, mb.usage)
			mb.gpuVertexSize = size
		} else {
			gl.BufferSubData(gl.ARRAY_BUFFER, 0, size, mb.vertices)
		}

		gl.VertexPointer(3, gl.FLOAT, 0, uintptr(0))
		mb.vertexId.Unbind(gl.ARRAY_BUFFER)
	}

	// Upload colors.
	size = len(mb.colors) * mbColorStride
	if size > 0 {
		mb.colorId.Bind(gl.ARRAY_BUFFER)

		if size != mb.gpuColorSize {
			gl.BufferData(gl.ARRAY_BUFFER, size, mb.colors, mb.usage)
			mb.gpuColorSize = size
		} else {
			gl.BufferSubData(gl.ARRAY_BUFFER, 0, size, mb.colors)
		}

		gl.ColorPointer(4, gl.FLOAT, 0, uintptr(0))
		mb.colorId.Unbind(gl.ARRAY_BUFFER)
	}

	// Upload normals.
	size = len(mb.normals) * mbNormalStride
	if size > 0 {
		mb.normalId.Bind(gl.ARRAY_BUFFER)

		if size != mb.gpuNormalSize {
			gl.BufferData(gl.ARRAY_BUFFER, size, mb.normals, mb.usage)
			mb.gpuNormalSize = size
		} else {
			gl.BufferSubData(gl.ARRAY_BUFFER, 0, size, mb.normals)
		}

		gl.NormalPointer(gl.FLOAT, 0, uintptr(0))
		mb.normalId.Unbind(gl.ARRAY_BUFFER)
	}

	// Upload texture coords.
	size = len(mb.textures) * mbTextureStride
	if size > 0 {
		mb.textureId.Bind(gl.ARRAY_BUFFER)

		if size != mb.gpuTextureSize {
			gl.BufferData(gl.ARRAY_BUFFER, size, mb.textures, mb.usage)
			mb.gpuTextureSize = size
		} else {
			gl.BufferSubData(gl.ARRAY_BUFFER, 0, size, mb.textures)
		}

		gl.TexCoordPointer(3, gl.FLOAT, 0, uintptr(0))
		mb.textureId.Unbind(gl.ARRAY_BUFFER)
	}

	// Upload indices.
	size = len(mb.indices) * mbIndexStride
	if size > 0 {
		mb.indexId.Bind(gl.ELEMENT_ARRAY_BUFFER)

		if size != mb.gpuIndexSize {
			gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, size, mb.indices, mb.usage)
			mb.gpuIndexSize = size
		} else {
			gl.BufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, size, mb.indices)
		}

		gl.IndexPointer(gl.UNSIGNED_INT, 0, uintptr(0))
		mb.indexId.Unbind(gl.ELEMENT_ARRAY_BUFFER)
	}

	gl.Finish()
	mb.state = mbClean
}

// Add appends new mesh to the buffer.
// Returns an index for a MeshDescriptor object. This can be used to index
// the list returned by MeshBuffer.Meshes()
func (mb *MeshBuffer) Add(m *Mesh) int {
	mb.state = mbFrozen

	md := new(MeshDescriptor)
	md.VertexStart = len(mb.vertices)
	md.VertexCount = len(m.Vertices)
	md.TextureStart = len(mb.textures)
	md.TextureCount = len(m.Textures)
	md.NormalStart = len(mb.normals)
	md.NormalCount = len(m.Normals)
	md.ColorStart = len(mb.colors)
	md.ColorCount = len(m.Colors)
	md.IndexStart = len(mb.indices)
	md.IndexCount = len(m.Indices)

	mb.meshes = append(mb.meshes, md)
	mb.vertices = append(mb.vertices, m.Vertices...)
	mb.textures = append(mb.textures, m.Textures...)
	mb.normals = append(mb.normals, m.Normals...)
	mb.colors = append(mb.colors, m.Colors...)
	mb.indices = append(mb.indices, m.Indices...)

	// Update indices into the mesh buffers.
	for i := md.IndexStart; i < len(mb.indices); i++ {
		mb.indices[i] += uint(md.VertexStart)
	}

	mb.state = mbDirty
	return len(mb.meshes) - 1
}

// Remove removes the mesh with the given index from the buffer.
func (mb *MeshBuffer) Remove(index int) {
	if index < 0 || index >= len(mb.meshes) {
		return
	}

	md := mb.meshes[index]

	// Remove vertices.
	s, c := md.VertexStart, md.VertexCount
	copy(mb.vertices[s:], mb.vertices[s+c:])
	mb.vertices = mb.vertices[:len(mb.vertices)-c]

	// Remove texture coords.
	s, c = md.TextureStart, md.TextureCount
	copy(mb.textures[s:], mb.textures[s+c:])
	mb.textures = mb.textures[:len(mb.textures)-c]

	// Remove normals.
	s, c = md.NormalStart, md.NormalCount
	copy(mb.normals[s:], mb.normals[s+c:])
	mb.normals = mb.normals[:len(mb.normals)-c]

	// Remove colors.
	s, c = md.ColorStart, md.ColorCount
	copy(mb.colors[s:], mb.colors[s+c:])
	mb.colors = mb.colors[:len(mb.colors)-c]

	// Remove indices.
	s, c = md.IndexStart, md.IndexCount
	copy(mb.indices[s:], mb.indices[s+c:])
	mb.indices = mb.indices[:len(mb.indices)-c]

	// Remove mesh descriptor.
	copy(mb.meshes[index:], mb.meshes[index+1:])
	mb.meshes = mb.meshes[:len(mb.meshes)-1]

	mb.state = mbDirty
}

// Usage returns the usage type associated with this buffer.
func (mb *MeshBuffer) Usage() gl.GLenum { return mb.usage }

// SetUsage sets the usage type for this buffer. Picking the right value for
// the purpose of this buffer can greatly affect performance.
// It defaults to GL_DYNAMIC_DRAW.
//
// The expected values are:
//
//    GL_STATIC_DRAW
//    GL_DYNAMIC_DRAW
//    GL_STREAM_DRAW
//
// "STATIC" means the data in mbO will not be changed (specified once and used
// many times).
//
// "DYNAMIC" means the data will be changed frequently (specified and used
// repeatedly).
//
// "STREAM" means the data will be changed every frame (specified once and
// used once).
//
// The mbO memory manager will choose the best memory places for the buffer
// object based on this usage flag. For example: GL_STATIC_DRAW and
// GL_STREAM_DRAW may use video memory, while GL_DYNAMIC_DRAW may use AGP memory.
//
// Note: This issues a re-commit of the buffer data to take effect.
func (mb *MeshBuffer) SetUsage(usage gl.GLenum) {
	mb.usage = usage
	mb.state = mbDirty
}

// Len returns the number of mesh objects in the buffer.
func (mb *MeshBuffer) Len() int { return len(mb.meshes) }

// Meshes returns a list of mesh descriptors.
func (mb *MeshBuffer) Meshes() []*MeshDescriptor { return mb.meshes }

// Vertices returns a list of all vertices in the buffer.
func (mb *MeshBuffer) Vertices() [][3]float32 { return mb.vertices }

// Colors returns a list of all colors in the buffer.
func (mb *MeshBuffer) Colors() [][4]float32 { return mb.colors }

// Textures returns a list of all texture coords in the buffer.
func (mb *MeshBuffer) Textures() [][3]float32 { return mb.textures }

// Normals returns a list of all normals in the buffer.
func (mb *MeshBuffer) Normals() [][3]float32 { return mb.normals }

// Indices returns a list of all indices in the buffer.
func (mb *MeshBuffer) Indices() []uint { return mb.indices }
<commit_msg>glh.MeshBuffer: Removes duplicate Commit call.<commit_after>// Copyright 2012 The go-gl 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 glh

import (
	"github.com/go-gl/gl"
)

// Mesh describes data for a single mesh object.
type Mesh struct {
	Colors   [][4]float32
	Vertices [][3]float32
	Textures [][3]float32
	Normals  [][3]float32
	Indices  []uint
}

// MeshDescriptor describes the data offsets for a single
// mesh inside a mesh buffer.
type MeshDescriptor struct {
	VertexStart  int // Start index for vertex data.
	VertexCount  int // Number of vertices.
	TextureStart int // Start index for texture coordinate data.
	TextureCount int // Number of texture coordinates.
	NormalStart  int // Start index for normal data.
	NormalCount  int // Number of normals.
	ColorStart   int // Start index for color data.
	ColorCount   int // Number of colors.
	IndexStart   int // Start index for index data.
	IndexCount   int // Number of indices.
}

// Known mesh buffer states.
const (
	mbClean = iota
	mbDirty
	mbFrozen
)

// These define the size of each component, in bytes.
var (
	mbVertexStride  = int(Sizeof(gl.FLOAT)) * 3
	mbColorStride   = int(Sizeof(gl.FLOAT)) * 4
	mbNormalStride  = int(Sizeof(gl.FLOAT)) * 3
	mbTextureStride = int(Sizeof(gl.FLOAT)) * 3
	mbIndexStride   = int(Sizeof(gl.UNSIGNED_INT))
)

// MeshBuffer represents a mesh buffer.
// It can cache and render vertices, colors, texture coords and normals
// for an arbitrary amount of independent meshes.
type MeshBuffer struct {
	meshes   []*MeshDescriptor // Mesh descriptor.
	vertices [][3]float32      // List of vertices.
	colors   [][4]float32      // List of colors.
	normals  [][3]float32      // List of normals.
	textures [][3]float32      // List of texture coords.
	indices  []uint            // List of indices.

	vertexId  gl.Buffer // Vertex buffer identity.
	colorId   gl.Buffer // Color buffer identity.
	textureId gl.Buffer // Texture buffer identity.
	normalId  gl.Buffer // Normal buffer identity.
	indexId   gl.Buffer // Index buffer identity.

	gpuVertexSize  int // Current size of the vertex buffer in GPU.
	gpuColorSize   int // Current size of the color buffer in GPU.
	gpuTextureSize int // Current size of the texture coord buffer in GPU.
	gpuNormalSize  int // Current size of the normal buffer in GPU.
	gpuIndexSize   int // Current size of the indices buffer in GPU.

	state int       // Mesh buffer state.
	usage gl.GLenum // Usage flag. GL_DYNAMIC, STATIC_DRAW. GL_STREAM_DRAW, etc/
}

// NewMeshBuffer returns a new mesh buffer object.
func NewMeshBuffer() *MeshBuffer {
	mb := new(MeshBuffer)
	mb.state = mbDirty
	mb.usage = gl.STATIC_DRAW
	mb.vertexId = gl.GenBuffer()
	mb.colorId = gl.GenBuffer()
	mb.textureId = gl.GenBuffer()
	mb.normalId = gl.GenBuffer()
	mb.indexId = gl.GenBuffer()
	return mb
}

// Release releases all resources for this buffer.
func (mb *MeshBuffer) Release() {
	mb.vertexId.Delete()
	mb.indexId.Delete()
	mb.vertexId.Delete()
	mb.colorId.Delete()
	mb.textureId.Delete()
	mb.normalId.Delete()
	mb.indexId.Delete()
	mb.Clear()
}

// Clear clears the mesh buffer.
func (mb *MeshBuffer) Clear() {
	mb.state = mbFrozen
	mb.vertices = nil
	mb.colors = nil
	mb.textures = nil
	mb.normals = nil
	mb.indices = nil
	mb.meshes = nil
	mb.gpuVertexSize = 0
	mb.gpuColorSize = 0
	mb.gpuTextureSize = 0
	mb.gpuNormalSize = 0
	mb.gpuIndexSize = 0
	mb.state = mbDirty
}

// Render renders the entire mesh buffer.
// The mode defines one of the symbolic constants like GL_TRIANGLES,
// GL_QUADS, GL_POLYGON, GL_TRIANGLE_STRIP, etc.
func (mb *MeshBuffer) Render(mode gl.GLenum) {
	if len(mb.meshes) == 0 {
		return
	}

	mb.render(
		mode,
		0, len(mb.vertices),
		0, len(mb.colors),
		0, len(mb.textures),
		0, len(mb.normals),
		0, len(mb.indices),
	)
}

// RenderMesh renders a single mesh, idenfified by its index.
// The mode defines one of the symbolic constants like GL_TRIANGLES,
// GL_QUADS, GL_POLYGON, GL_TRIANGLE_STRIP, etc.
func (mb *MeshBuffer) RenderMesh(index int, mode gl.GLenum) {
	if index < 0 || index >= len(mb.meshes) {
		return
	}

	md := mb.meshes[index]

	mb.render(
		mode,
		md.VertexStart, md.VertexCount,
		md.ColorStart, md.ColorCount,
		md.TextureStart, md.TextureCount,
		md.NormalStart, md.NormalCount,
		md.IndexStart, md.IndexCount,
	)
}

// render draws the elements defined by the given start and count values for
// [v]ertices, [c]olors, [t]exture coords, [n]ormals and [i]ndices.
func (mb *MeshBuffer) render(mode gl.GLenum, vs, vc, cs, cc, ts, tc, ns, nc, is, ic int) {
	if mb.state != mbClean {
		mb.Commit()
	}

	gl.PushClientAttrib(gl.CLIENT_VERTEX_ARRAY_BIT)
	defer gl.PopClientAttrib()

	gl.EnableClientState(gl.VERTEX_ARRAY)
	defer gl.DisableClientState(gl.VERTEX_ARRAY)

	if cc > 0 {
		gl.EnableClientState(gl.COLOR_ARRAY)
		defer gl.DisableClientState(gl.COLOR_ARRAY)
	}

	if tc > 0 {
		gl.EnableClientState(gl.TEXTURE_COORD_ARRAY)
		defer gl.DisableClientState(gl.TEXTURE_COORD_ARRAY)
	}

	if nc > 0 {
		gl.EnableClientState(gl.NORMAL_ARRAY)
		defer gl.DisableClientState(gl.NORMAL_ARRAY)
	}

	if ic > 0 {
		start := is * mbIndexStride

		mb.indexId.Bind(gl.ELEMENT_ARRAY_BUFFER)
		gl.DrawElements(mode, ic, gl.UNSIGNED_INT, uintptr(start))
		mb.indexId.Unbind(gl.ELEMENT_ARRAY_BUFFER)
	} else {
		start := vs * mbVertexStride

		mb.vertexId.Bind(gl.ARRAY_BUFFER)
		gl.DrawArrays(mode, start, vc)
		mb.vertexId.Unbind(gl.ARRAY_BUFFER)
	}
}

// Commit pushes the buffer data to the GPU.
//
// This is normally called implicitely by MeshBuffer.Render and only
// when necessary. However, it may need to be called manually, when the
// buffer data is changed.
func (mb *MeshBuffer) Commit() {
	if mb.state == mbFrozen {
		return
	}

	// Upload vertices.
	size := len(mb.vertices) * mbVertexStride
	if size > 0 {
		mb.vertexId.Bind(gl.ARRAY_BUFFER)

		if size != mb.gpuVertexSize {
			gl.BufferData(gl.ARRAY_BUFFER, size, mb.vertices, mb.usage)
			mb.gpuVertexSize = size
		} else {
			gl.BufferSubData(gl.ARRAY_BUFFER, 0, size, mb.vertices)
		}

		gl.VertexPointer(3, gl.FLOAT, 0, uintptr(0))
		mb.vertexId.Unbind(gl.ARRAY_BUFFER)
	}

	// Upload colors.
	size = len(mb.colors) * mbColorStride
	if size > 0 {
		mb.colorId.Bind(gl.ARRAY_BUFFER)

		if size != mb.gpuColorSize {
			gl.BufferData(gl.ARRAY_BUFFER, size, mb.colors, mb.usage)
			mb.gpuColorSize = size
		} else {
			gl.BufferSubData(gl.ARRAY_BUFFER, 0, size, mb.colors)
		}

		gl.ColorPointer(4, gl.FLOAT, 0, uintptr(0))
		mb.colorId.Unbind(gl.ARRAY_BUFFER)
	}

	// Upload normals.
	size = len(mb.normals) * mbNormalStride
	if size > 0 {
		mb.normalId.Bind(gl.ARRAY_BUFFER)

		if size != mb.gpuNormalSize {
			gl.BufferData(gl.ARRAY_BUFFER, size, mb.normals, mb.usage)
			mb.gpuNormalSize = size
		} else {
			gl.BufferSubData(gl.ARRAY_BUFFER, 0, size, mb.normals)
		}

		gl.NormalPointer(gl.FLOAT, 0, uintptr(0))
		mb.normalId.Unbind(gl.ARRAY_BUFFER)
	}

	// Upload texture coords.
	size = len(mb.textures) * mbTextureStride
	if size > 0 {
		mb.textureId.Bind(gl.ARRAY_BUFFER)

		if size != mb.gpuTextureSize {
			gl.BufferData(gl.ARRAY_BUFFER, size, mb.textures, mb.usage)
			mb.gpuTextureSize = size
		} else {
			gl.BufferSubData(gl.ARRAY_BUFFER, 0, size, mb.textures)
		}

		gl.TexCoordPointer(3, gl.FLOAT, 0, uintptr(0))
		mb.textureId.Unbind(gl.ARRAY_BUFFER)
	}

	// Upload indices.
	size = len(mb.indices) * mbIndexStride
	if size > 0 {
		mb.indexId.Bind(gl.ELEMENT_ARRAY_BUFFER)

		if size != mb.gpuIndexSize {
			gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, size, mb.indices, mb.usage)
			mb.gpuIndexSize = size
		} else {
			gl.BufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, size, mb.indices)
		}

		gl.IndexPointer(gl.UNSIGNED_INT, 0, uintptr(0))
		mb.indexId.Unbind(gl.ELEMENT_ARRAY_BUFFER)
	}

	gl.Finish()
	mb.state = mbClean
}

// Add appends new mesh to the buffer.
// Returns an index for a MeshDescriptor object. This can be used to index
// the list returned by MeshBuffer.Meshes()
func (mb *MeshBuffer) Add(m *Mesh) int {
	mb.state = mbFrozen

	md := new(MeshDescriptor)
	md.VertexStart = len(mb.vertices)
	md.VertexCount = len(m.Vertices)
	md.TextureStart = len(mb.textures)
	md.TextureCount = len(m.Textures)
	md.NormalStart = len(mb.normals)
	md.NormalCount = len(m.Normals)
	md.ColorStart = len(mb.colors)
	md.ColorCount = len(m.Colors)
	md.IndexStart = len(mb.indices)
	md.IndexCount = len(m.Indices)

	mb.meshes = append(mb.meshes, md)
	mb.vertices = append(mb.vertices, m.Vertices...)
	mb.textures = append(mb.textures, m.Textures...)
	mb.normals = append(mb.normals, m.Normals...)
	mb.colors = append(mb.colors, m.Colors...)
	mb.indices = append(mb.indices, m.Indices...)

	// Update indices into the mesh buffers.
	for i := md.IndexStart; i < len(mb.indices); i++ {
		mb.indices[i] += uint(md.VertexStart)
	}

	mb.state = mbDirty
	return len(mb.meshes) - 1
}

// Remove removes the mesh with the given index from the buffer.
func (mb *MeshBuffer) Remove(index int) {
	if index < 0 || index >= len(mb.meshes) {
		return
	}

	md := mb.meshes[index]

	// Remove vertices.
	s, c := md.VertexStart, md.VertexCount
	copy(mb.vertices[s:], mb.vertices[s+c:])
	mb.vertices = mb.vertices[:len(mb.vertices)-c]

	// Remove texture coords.
	s, c = md.TextureStart, md.TextureCount
	copy(mb.textures[s:], mb.textures[s+c:])
	mb.textures = mb.textures[:len(mb.textures)-c]

	// Remove normals.
	s, c = md.NormalStart, md.NormalCount
	copy(mb.normals[s:], mb.normals[s+c:])
	mb.normals = mb.normals[:len(mb.normals)-c]

	// Remove colors.
	s, c = md.ColorStart, md.ColorCount
	copy(mb.colors[s:], mb.colors[s+c:])
	mb.colors = mb.colors[:len(mb.colors)-c]

	// Remove indices.
	s, c = md.IndexStart, md.IndexCount
	copy(mb.indices[s:], mb.indices[s+c:])
	mb.indices = mb.indices[:len(mb.indices)-c]

	// Remove mesh descriptor.
	copy(mb.meshes[index:], mb.meshes[index+1:])
	mb.meshes = mb.meshes[:len(mb.meshes)-1]

	mb.state = mbDirty
}

// Usage returns the usage type associated with this buffer.
func (mb *MeshBuffer) Usage() gl.GLenum { return mb.usage }

// SetUsage sets the usage type for this buffer. Picking the right value for
// the purpose of this buffer can greatly affect performance.
// It defaults to GL_DYNAMIC_DRAW.
//
// The expected values are:
//
//    GL_STATIC_DRAW
//    GL_DYNAMIC_DRAW
//    GL_STREAM_DRAW
//
// "STATIC" means the data in mbO will not be changed (specified once and used
// many times).
//
// "DYNAMIC" means the data will be changed frequently (specified and used
// repeatedly).
//
// "STREAM" means the data will be changed every frame (specified once and
// used once).
//
// The mbO memory manager will choose the best memory places for the buffer
// object based on this usage flag. For example: GL_STATIC_DRAW and
// GL_STREAM_DRAW may use video memory, while GL_DYNAMIC_DRAW may use AGP memory.
//
// Note: This issues a re-commit of the buffer data to take effect.
func (mb *MeshBuffer) SetUsage(usage gl.GLenum) {
	mb.usage = usage
	mb.state = mbDirty
}

// Len returns the number of mesh objects in the buffer.
func (mb *MeshBuffer) Len() int { return len(mb.meshes) }

// Meshes returns a list of mesh descriptors.
func (mb *MeshBuffer) Meshes() []*MeshDescriptor { return mb.meshes }

// Vertices returns a list of all vertices in the buffer.
func (mb *MeshBuffer) Vertices() [][3]float32 { return mb.vertices }

// Colors returns a list of all colors in the buffer.
func (mb *MeshBuffer) Colors() [][4]float32 { return mb.colors }

// Textures returns a list of all texture coords in the buffer.
func (mb *MeshBuffer) Textures() [][3]float32 { return mb.textures }

// Normals returns a list of all normals in the buffer.
func (mb *MeshBuffer) Normals() [][3]float32 { return mb.normals }

// Indices returns a list of all indices in the buffer.
func (mb *MeshBuffer) Indices() []uint { return mb.indices }
<|endoftext|>


<commit_before>package model

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/oinume/goenum"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"

	"github.com/oinume/lekcije/backend/domain/config"
	"github.com/oinume/lekcije/backend/errors"
	"github.com/oinume/lekcije/backend/util"
)

const (
	lessonTimeFormat = "2006-01-02 15:04"
)

type Lesson struct {
	ID        uint64 `gorm:"primary_key"`
	TeacherID uint32
	Datetime  time.Time
	Status    string // TODO: enum
	CreatedAt time.Time
	UpdatedAt time.Time
}

func (*Lesson) TableName() string {
	return "lesson"
}

func (l *Lesson) String() string {
	return fmt.Sprintf(
		"TeacherID=%v, Datetime=%v, Status=%v",
		l.TeacherID, l.Datetime.Format(lessonTimeFormat), l.Status,
	)
}

type LessonStatus struct {
	Finished  int `goenum:"終了"`
	Reserved  int `goenum:"予約済"`
	Available int `goenum:"予約可"`
	Cancelled int `goenum:"休講"`
}

var LessonStatuses = goenum.EnumerateStruct(&LessonStatus{
	Finished:  1,
	Reserved:  2,
	Available: 3,
	Cancelled: 4,
})

type LessonService struct {
	db *gorm.DB
}

func NewLessonService(db *gorm.DB) *LessonService {
	return &LessonService{db: db}
}

func (s *LessonService) TableName() string {
	return (&Lesson{}).TableName()
}

func (s *LessonService) UpdateLessons(lessons []*Lesson) (int64, error) {
	if len(lessons) == 0 {
		return 0, nil
	}

	existingLessons, err := s.FindLessonsByTeacherIDAndDatetimeAsMap(lessons[0].TeacherID, lessons)
	if err != nil {
		return 0, err
	}

	rowsAffected := 0
	now := time.Now().UTC()
	for _, lesson := range lessons {
		lesson.Status = strings.ToLower(lesson.Status)
		if l, ok := existingLessons[lesson.Datetime.Format(lessonTimeFormat)]; ok {
			if lesson.Status == l.Status {
				continue
			}
			// UPDATE
			values := &Lesson{Status: lesson.Status, UpdatedAt: now}
			if err := s.db.Model(lesson).Where("id = ?", l.ID).Updates(values).Error; err != nil {
				return 0, errors.NewInternalError(
					errors.WithError(err),
				)
			}
			rowsAffected++

			log := &LessonStatusLog{
				LessonID:  l.ID,
				Status:    lesson.Status,
				CreatedAt: now,
			}
			if err := NewLessonStatusLogService(s.db).Create(log); err != nil {
				return 0, err
			}
		} else {
			// INSERT
			dt := lesson.Datetime
			lesson.Datetime = time.Date(dt.Year(), dt.Month(), dt.Day(), dt.Hour(), dt.Minute(), dt.Second(), 0, time.UTC)
			where := Lesson{TeacherID: lesson.TeacherID, Datetime: lesson.Datetime}
			if err := s.db.Where(where).FirstOrCreate(lesson).Error; err != nil {
				return 0, errors.NewInternalError(
					errors.WithError(err),
					errors.WithMessage("FirstOrCreate failed"),
					errors.WithResource(errors.NewResourceWithEntries(
						s.TableName(),
						[]errors.ResourceEntry{
							{Key: "teacherID", Value: lesson.TeacherID},
							{Key: "datetime", Value: lesson.Datetime},
						},
					)),
				)
			}
			rowsAffected++

			log := &LessonStatusLog{
				LessonID:  lesson.ID,
				Status:    lesson.Status,
				CreatedAt: now,
			}
			if err := NewLessonStatusLogService(s.db).Create(log); err != nil {
				return 0, err
			}
		}
	}

	return int64(rowsAffected), nil
}

func (s *LessonService) FindLessonsByTeacherIDAndDatetimeAsMap(
	teacherID uint32,
	lessonsArgs []*Lesson,
) (map[string]*Lesson, error) {
	if len(lessonsArgs) == 0 {
		return nil, nil
	}

	datetimes := make([]string, len(lessonsArgs))
	for i, l := range lessonsArgs {
		datetimes[i] = l.Datetime.Format(dbDatetimeFormat)
	}

	placeholder := Placeholders(util.StringToInterfaceSlice(datetimes...))
	lessons := make([]*Lesson, 0, len(lessonsArgs))
	sql := strings.TrimSpace(fmt.Sprintf(`
SELECT * FROM %s
WHERE
  teacher_id = ?
  AND datetime IN (%s)
`, s.TableName(), placeholder))

	values := []interface{}{teacherID}
	values = append(values, util.StringToInterfaceSlice(datetimes...)...)
	result := s.db.Raw(sql, values...).Scan(&lessons)
	if result.Error != nil {
		if result.RecordNotFound() {
			return nil, nil
		}
		return nil, errors.NewInternalError(
			errors.WithError(result.Error),
			errors.WithMessage("Failed to find lessonsArgs"),
			errors.WithResource(errors.NewResource(s.TableName(), "teacherID", teacherID)),
		)
	}

	ret := make(map[string]*Lesson, len(lessons))
	for _, l := range lessons {
		ret[l.Datetime.Format(lessonTimeFormat)] = l
	}
	return ret, nil
}

func (s *LessonService) FindLessons(
	ctx context.Context,
	teacherID uint32,
	fromDate,
	toDate time.Time,
) ([]*Lesson, error) {
	_, span := otel.Tracer(config.DefaultTracerName).Start(ctx, "LessonService.FindLessons")
	span.SetAttributes(attribute.KeyValue{
		Key:   "teacherID",
		Value: attribute.Int64Value(int64(teacherID)),
	})
	defer span.End()

	lessons := make([]*Lesson, 0, 1000)
	sql := strings.TrimSpace(fmt.Sprintf(`
SELECT * FROM %s
WHERE
  teacher_id = ?
  AND DATE(datetime) BETWEEN ? AND ?
ORDER BY datetime ASC
LIMIT 1000
	`, s.TableName()))

	toDateAdded := toDate.Add(24 * 2 * time.Hour)
	result := s.db.Raw(sql, teacherID, fromDate.Format("2006-01-02"), toDateAdded.Format("2006-01-02")).Scan(&lessons)
	if result.Error != nil {
		if result.RecordNotFound() {
			return lessons, nil
		}
		return nil, errors.NewInternalError(
			errors.WithError(result.Error),
			errors.WithMessage("Failed to find lessons"),
			errors.WithResource(errors.NewResource(s.TableName(), "teacherID", teacherID)),
		)
	}

	return lessons, nil
}

func (s *LessonService) GetNewAvailableLessons(ctx context.Context, oldLessons, newLessons []*Lesson) []*Lesson {
	_, span := otel.Tracer(config.DefaultTracerName).Start(ctx, "LessonService.GetNewAvailableLessons")
	defer span.End()

	// Pattern
	// 2016-01-01 00:00@Any -> Available
	oldLessonsMap := make(map[string]*Lesson, len(oldLessons))
	newLessonsMap := make(map[string]*Lesson, len(newLessons))
	availableLessons := make([]*Lesson, 0, len(oldLessons)+len(newLessons))
	availableLessonsMap := make(map[string]*Lesson, len(oldLessons)+len(newLessons))
	for _, l := range oldLessons {
		oldLessonsMap[l.Datetime.Format(lessonTimeFormat)] = l
	}
	for _, l := range newLessons {
		newLessonsMap[l.Datetime.Format(lessonTimeFormat)] = l
	}
	for datetime, oldLesson := range oldLessonsMap {
		if newLesson, ok := newLessonsMap[datetime]; ok && strings.ToLower(oldLesson.Status) != "available" && strings.ToLower(newLesson.Status) == "available" {
			// exists in oldLessons and newLessons
			availableLessons = append(availableLessons, newLesson)
			availableLessonsMap[datetime] = newLesson
		}
	}

	for _, l := range newLessons {
		datetime := l.Datetime.Format(lessonTimeFormat)
		if _, ok := oldLessonsMap[datetime]; !ok && strings.ToLower(l.Status) == "available" {
			// not exists in oldLessons
			availableLessons = append(availableLessons, l)
			availableLessonsMap[datetime] = l
		}
	}

	// TODO: sort availableLessonsMap by datetime
	return availableLessons
}
<commit_msg>more readable code<commit_after>package model

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/oinume/goenum"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"

	"github.com/oinume/lekcije/backend/domain/config"
	"github.com/oinume/lekcije/backend/errors"
	"github.com/oinume/lekcije/backend/util"
)

const (
	lessonTimeFormat = "2006-01-02 15:04"
)

type Lesson struct {
	ID        uint64 `gorm:"primary_key"`
	TeacherID uint32
	Datetime  time.Time
	Status    string // TODO: enum
	CreatedAt time.Time
	UpdatedAt time.Time
}

func (*Lesson) TableName() string {
	return "lesson"
}

func (l *Lesson) String() string {
	return fmt.Sprintf(
		"TeacherID=%v, Datetime=%v, Status=%v",
		l.TeacherID, l.Datetime.Format(lessonTimeFormat), l.Status,
	)
}

type LessonStatus struct {
	Finished  int `goenum:"終了"`
	Reserved  int `goenum:"予約済"`
	Available int `goenum:"予約可"`
	Cancelled int `goenum:"休講"`
}

var LessonStatuses = goenum.EnumerateStruct(&LessonStatus{
	Finished:  1,
	Reserved:  2,
	Available: 3,
	Cancelled: 4,
})

type LessonService struct {
	db *gorm.DB
}

func NewLessonService(db *gorm.DB) *LessonService {
	return &LessonService{db: db}
}

func (s *LessonService) TableName() string {
	return (&Lesson{}).TableName()
}

func (s *LessonService) UpdateLessons(lessons []*Lesson) (int64, error) {
	if len(lessons) == 0 {
		return 0, nil
	}

	existingLessons, err := s.FindLessonsByTeacherIDAndDatetimeAsMap(lessons[0].TeacherID, lessons)
	if err != nil {
		return 0, err
	}

	rowsAffected := 0
	now := time.Now().UTC()
	for _, lesson := range lessons {
		lesson.Status = strings.ToLower(lesson.Status)
		if l, ok := existingLessons[lesson.Datetime.Format(lessonTimeFormat)]; ok {
			if lesson.Status == l.Status {
				continue
			}
			// UPDATE
			values := &Lesson{Status: lesson.Status, UpdatedAt: now}
			if err := s.db.Model(lesson).Where("id = ?", l.ID).Updates(values).Error; err != nil {
				return 0, errors.NewInternalError(
					errors.WithError(err),
				)
			}
			rowsAffected++

			log := &LessonStatusLog{
				LessonID:  l.ID,
				Status:    lesson.Status,
				CreatedAt: now,
			}
			if err := NewLessonStatusLogService(s.db).Create(log); err != nil {
				return 0, err
			}
		} else {
			// INSERT
			dt := lesson.Datetime
			lesson.Datetime = time.Date(dt.Year(), dt.Month(), dt.Day(), dt.Hour(), dt.Minute(), dt.Second(), 0, time.UTC)
			where := Lesson{TeacherID: lesson.TeacherID, Datetime: lesson.Datetime}
			if err := s.db.Where(where).FirstOrCreate(lesson).Error; err != nil {
				return 0, errors.NewInternalError(
					errors.WithError(err),
					errors.WithMessage("FirstOrCreate failed"),
					errors.WithResource(errors.NewResourceWithEntries(
						s.TableName(),
						[]errors.ResourceEntry{
							{Key: "teacherID", Value: lesson.TeacherID},
							{Key: "datetime", Value: lesson.Datetime},
						},
					)),
				)
			}
			rowsAffected++

			log := &LessonStatusLog{
				LessonID:  lesson.ID,
				Status:    lesson.Status,
				CreatedAt: now,
			}
			if err := NewLessonStatusLogService(s.db).Create(log); err != nil {
				return 0, err
			}
		}
	}

	return int64(rowsAffected), nil
}

func (s *LessonService) FindLessonsByTeacherIDAndDatetimeAsMap(
	teacherID uint32,
	lessonsArgs []*Lesson,
) (map[string]*Lesson, error) {
	if len(lessonsArgs) == 0 {
		return nil, nil
	}

	datetimes := make([]string, len(lessonsArgs))
	for i, l := range lessonsArgs {
		datetimes[i] = l.Datetime.Format(dbDatetimeFormat)
	}

	placeholder := Placeholders(util.StringToInterfaceSlice(datetimes...))
	lessons := make([]*Lesson, 0, len(lessonsArgs))
	sql := strings.TrimSpace(fmt.Sprintf(`
SELECT * FROM %s
WHERE
  teacher_id = ?
  AND datetime IN (%s)
`, s.TableName(), placeholder))

	values := []interface{}{teacherID}
	values = append(values, util.StringToInterfaceSlice(datetimes...)...)
	result := s.db.Raw(sql, values...).Scan(&lessons)
	if result.Error != nil {
		if result.RecordNotFound() {
			return nil, nil
		}
		return nil, errors.NewInternalError(
			errors.WithError(result.Error),
			errors.WithMessage("Failed to find lessonsArgs"),
			errors.WithResource(errors.NewResource(s.TableName(), "teacherID", teacherID)),
		)
	}

	ret := make(map[string]*Lesson, len(lessons))
	for _, l := range lessons {
		ret[l.Datetime.Format(lessonTimeFormat)] = l
	}
	return ret, nil
}

func (s *LessonService) FindLessons(
	ctx context.Context,
	teacherID uint32,
	fromDate,
	toDate time.Time,
) ([]*Lesson, error) {
	_, span := otel.Tracer(config.DefaultTracerName).Start(ctx, "LessonService.FindLessons")
	span.SetAttributes(attribute.KeyValue{
		Key:   "teacherID",
		Value: attribute.Int64Value(int64(teacherID)),
	})
	defer span.End()

	lessons := make([]*Lesson, 0, 1000)
	sql := strings.TrimSpace(fmt.Sprintf(`
SELECT * FROM %s
WHERE
  teacher_id = ?
  AND DATE(datetime) BETWEEN ? AND ?
ORDER BY datetime ASC
LIMIT 1000
	`, s.TableName()))

	toDateAdded := toDate.Add(24 * 2 * time.Hour)
	result := s.db.Raw(sql, teacherID, fromDate.Format("2006-01-02"), toDateAdded.Format("2006-01-02")).Scan(&lessons)
	if result.Error != nil {
		if result.RecordNotFound() {
			return lessons, nil
		}
		return nil, errors.NewInternalError(
			errors.WithError(result.Error),
			errors.WithMessage("Failed to find lessons"),
			errors.WithResource(errors.NewResource(s.TableName(), "teacherID", teacherID)),
		)
	}

	return lessons, nil
}

func (s *LessonService) GetNewAvailableLessons(ctx context.Context, oldLessons, newLessons []*Lesson) []*Lesson {
	_, span := otel.Tracer(config.DefaultTracerName).Start(ctx, "LessonService.GetNewAvailableLessons")
	defer span.End()

	// Pattern
	// 2016-01-01 00:00@Any -> Available
	oldLessonsMap := make(map[string]*Lesson, len(oldLessons))
	newLessonsMap := make(map[string]*Lesson, len(newLessons))
	availableLessons := make([]*Lesson, 0, len(oldLessons)+len(newLessons))
	availableLessonsMap := make(map[string]*Lesson, len(oldLessons)+len(newLessons))
	for _, l := range oldLessons {
		oldLessonsMap[l.Datetime.Format(lessonTimeFormat)] = l
	}
	for _, l := range newLessons {
		newLessonsMap[l.Datetime.Format(lessonTimeFormat)] = l
	}
	for datetime, oldLesson := range oldLessonsMap {
		newLesson, newLessonExists := newLessonsMap[datetime]
		oldStatus := strings.ToLower(oldLesson.Status)
		newStatus := strings.ToLower(newLesson.Status)
		if newLessonExists && oldStatus != "available" && newStatus == "available" {
			// exists in oldLessons and newLessons and "any status" -> "available"
			availableLessons = append(availableLessons, newLesson)
			availableLessonsMap[datetime] = newLesson
		}
	}
	for _, l := range newLessons {
		datetime := l.Datetime.Format(lessonTimeFormat)
		if _, ok := oldLessonsMap[datetime]; !ok && strings.ToLower(l.Status) == "available" {
			// not exists in oldLessons
			availableLessons = append(availableLessons, l)
			availableLessonsMap[datetime] = l
		}
	}

	// TODO: sort availableLessonsMap by datetime
	return availableLessons
}
<|endoftext|>


<commit_before>package GoMetricMiddleware

import (
	"net/http"
	"time"

	"strconv"

	"github.com/prometheus/client_golang/prometheus"
)

type loggingResponseWriter struct {
	http.ResponseWriter
	statusCode int
}

var requestsDuration prometheus.Histogram
var requestsCurrent prometheus.Gauge
var requestsStatus prometheus.CounterVec
var clientErrors prometheus.Counter

func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
	return &loggingResponseWriter{w, http.StatusOK}
}

func (lrw *loggingResponseWriter) WriteHeader(code int) {
	lrw.statusCode = code
	lrw.ResponseWriter.WriteHeader(code)
}

func Handler(namespace string, next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requestsDuration := prometheus.NewHistogram(
			prometheus.HistogramOpts{
				Namespace: namespace,
				Name:      "request_duration_seconds",
				Help:      "The duration of the requests to the Statistics service.",
			},
		)

		requestsCurrent := prometheus.NewGauge(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "requests_current",
				Help:      "The current number of requests to " + namespace,
			},
		)

		requestsStatus := prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: namespace,
				Name:      "requests_total",
				Help:      "The total number of requests to the " + namespace + " by status, method and path.",
			},
			[]string{"code", "method", "path"},
		)

		clientErrors := prometheus.NewCounter(
			prometheus.CounterOpts{
				Namespace: namespace,
				Name:      "errors",
				Help:      "The total number of " + namespace + " client errors",
			})

		start := time.Now()
		requestsCurrent.Inc()

		lrw := newLoggingResponseWriter(w)
		next.ServeHTTP(lrw, r)

		statusCode := lrw.statusCode

		requestsStatus.WithLabelValues(strconv.Itoa(statusCode), r.Method, r.URL.Path).Inc()

		if statusCode != http.StatusOK && statusCode != http.StatusNoContent {
			clientErrors.Inc()
		}

		requestsCurrent.Dec()
		requestsDuration.Observe(float64(time.Since(start).Seconds()))
	})
}

func init() {
	prometheus.MustRegister(requestsDuration)
	prometheus.MustRegister(requestsCurrent)
	prometheus.MustRegister(requestsStatus)
	prometheus.MustRegister(clientErrors)
}
<commit_msg>Changing package name<commit_after>package metrics

import (
	"net/http"
	"time"

	"strconv"

	"github.com/prometheus/client_golang/prometheus"
)

type loggingResponseWriter struct {
	http.ResponseWriter
	statusCode int
}

var (
	requestsDuration prometheus.Histogram
	requestsCurrent  prometheus.Gauge
	requestsStatus   prometheus.CounterVec
	clientErrors     prometheus.Counter
)

func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
	return &loggingResponseWriter{w, http.StatusOK}
}

func (lrw *loggingResponseWriter) WriteHeader(code int) {
	lrw.statusCode = code
	lrw.ResponseWriter.WriteHeader(code)
}

func Handler(namespace string, next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requestsDuration := prometheus.NewHistogram(
			prometheus.HistogramOpts{
				Namespace: namespace,
				Name:      "request_duration_seconds",
				Help:      "The duration of the requests to the Statistics service.",
			},
		)

		requestsCurrent := prometheus.NewGauge(
			prometheus.GaugeOpts{
				Namespace: namespace,
				Name:      "requests_current",
				Help:      "The current number of requests to " + namespace,
			},
		)

		requestsStatus := prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Namespace: namespace,
				Name:      "requests_total",
				Help:      "The total number of requests to the " + namespace + " by status, method and path.",
			},
			[]string{"code", "method", "path"},
		)

		clientErrors := prometheus.NewCounter(
			prometheus.CounterOpts{
				Namespace: namespace,
				Name:      "errors",
				Help:      "The total number of " + namespace + " client errors",
			})

		start := time.Now()
		requestsCurrent.Inc()

		lrw := newLoggingResponseWriter(w)
		next.ServeHTTP(lrw, r)

		statusCode := lrw.statusCode

		requestsStatus.WithLabelValues(strconv.Itoa(statusCode), r.Method, r.URL.Path).Inc()

		if statusCode != http.StatusOK && statusCode != http.StatusNoContent {
			clientErrors.Inc()
		}

		requestsCurrent.Dec()
		requestsDuration.Observe(float64(time.Since(start).Seconds()))
	})
}

func init() {
	prometheus.MustRegister(requestsDuration)
	prometheus.MustRegister(requestsCurrent)
	prometheus.MustRegister(requestsStatus)
	prometheus.MustRegister(clientErrors)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/animezb/newsrover"
	"github.com/animezb/newsroverd/sinks"
	_ "github.com/animezb/newsroverd/sinks/elasticsink"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"
)

var configFile string

func init() {
	flag.StringVar(&configFile, "config", "./roverdconf.json", "Configuration file path.")
}

type SinkConf struct {
	Name    string          `json:"name"`
	Options json.RawMessage `json:"options"`
}

type RoverDConf struct {
	LogFile string                  `json:"log"`
	Rovers  []newsrover.RoverConfig `json:"newsgroups"`
	Sinks   []SinkConf              `json:"sinks"`
}

func ctrlc(stop chan<- bool) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, syscall.SIGTERM)
	go func() {
		forceExit := false
		for sig := range c {
			if sig == os.Interrupt || sig == syscall.SIGTERM {
				if forceExit {
					os.Exit(2)
				} else {
					go func() {
						stop <- true
					}()
					forceExit = true
				}
			}
		}
	}()
}

func main() {
	flag.Parse()
	runtime.GOMAXPROCS(runtime.NumCPU())
	go func() {
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()

	var conf RoverDConf
	var logStream io.Writer

	if config, err := ioutil.ReadFile(configFile); err != nil {
		fmt.Printf("Error: Failed to open configuration file %s. (%s)\n", configFile, err.Error())
		os.Exit(1)
	} else {
		if err := json.Unmarshal(config, &conf); err != nil {
			fmt.Printf("Error: Failed to parse configuration file %s. (%s)\n", configFile, err.Error())
			os.Exit(1)
		}
	}

	if conf.LogFile != "" {
		logfile, err := os.OpenFile(conf.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err == nil {
			defer logfile.Close()
			logStream = logfile
		} else {
			fmt.Printf("Failed to open log file. Writing log to stdout.\n")
		}
	}
	if logStream == nil {
		logStream = os.Stdout
	}

	generalLog := log.New(logStream, "[NewsRoverD]", log.LstdFlags)
	if len(conf.Rovers) == 0 {
		generalLog.Println("No newgroups configured, no work to do. Quitting...")
		return
	}

	sinks.Register("standard", func(config json.RawMessage) (newsrover.Sink, error) {
		return &newsrover.StdSink{}, nil
	})

	newsSinks := make([]newsrover.Sink, 0, 4)
	for _, c := range conf.Sinks {
		if s, err := sinks.CreateSink(c.Name, c.Options); err == nil {
			s.SetLogger(log.New(logStream, "", log.LstdFlags))
			newsSinks = append(newsSinks, s)
		} else {
			generalLog.Printf("Error with sink %s: %s", c.Name, err.Error())
		}
	}

	if len(newsSinks) == 0 {
		generalLog.Println("No sinks configured, no where to send work to. Quitting...")
		return
	}

	generalLog.Printf("Loaded %d sinks.", len(newsSinks))

	rovers := make([]*newsrover.Rover, 0, 4)
	for _, c := range conf.Rovers {
		if c.SSL {
			if r, err := newsrover.NewRoverSsl(c.Host, c); err == nil {
				rovers = append(rovers, r)
				r.SetLogger(log.New(logStream, "", log.LstdFlags))
			} else {
				generalLog.Printf("Failed to start rover on %s. (%s)", c.Group, err.Error())
				os.Exit(1)
			}
		} else {
			if r, err := newsrover.NewRover(c.Host, c); err == nil {
				rovers = append(rovers, r)
				r.SetLogger(log.New(logStream, "", log.LstdFlags))
			} else {
				generalLog.Printf("Failed to start rover on %s. (%s)", c.Group, err.Error())
				os.Exit(1)
			}
		}
	}

	generalLog.Printf("Loaded %d groups.", len(rovers))

	for _, rov := range rovers {
		for _, sink := range newsSinks {
			rov.AddSink(sink)
		}
	}

	var runningWg sync.WaitGroup
	for _, sink := range newsSinks {
		runningWg.Add(1)
		go func(sink newsrover.Sink) {
			defer runningWg.Done()
			sink.Serve()
			for _, rov := range rovers {
				rov.RemoveSink(sink)
			}
			generalLog.Printf("Closed sink %s.", sink.Name())
		}(sink)
	}

	for _, rov := range rovers {
		runningWg.Add(1)
		go func(rov *newsrover.Rover) {
			defer runningWg.Done()
			for i := 0; i < 4; i++ {
				if i > 0 {
					generalLog.Printf("Restarting rover on newsgroup %s.", rov.Group())
				}
				if err := rov.Serve(); err != nil {
					generalLog.Printf("Rover on newsgroup %s crashed. %s", rov.Group(), err.Error())
					time.Sleep(10 * time.Second)
				} else {
					break
				}
			}
			generalLog.Printf("Closed rover on newsgroup %s.", rov.Group())
		}(rov)
	}
	quitChan := make(chan bool)
	ctrlc(quitChan)
	runningWg.Add(1)
	go func() {
		<-quitChan
		generalLog.Printf("Stopping newsroverd (rovers)...")
		for _, rov := range rovers {
			rov.Stop()
		}
		time.Sleep(1 * time.Microsecond) // Yield
		generalLog.Printf("Stopping newsroverd (sinks)...")
		for _, sink := range newsSinks {
			sink.Stop()
		}
		runningWg.Done()
	}()

	runningWg.Wait()
	generalLog.Printf("Bye.")
}
<commit_msg>Log header<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/animezb/newsrover"
	"github.com/animezb/newsroverd/sinks"
	_ "github.com/animezb/newsroverd/sinks/elasticsink"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"
)

var configFile string

func init() {
	flag.StringVar(&configFile, "config", "./roverdconf.json", "Configuration file path.")
}

type SinkConf struct {
	Name    string          `json:"name"`
	Options json.RawMessage `json:"options"`
}

type RoverDConf struct {
	LogFile string                  `json:"log"`
	Rovers  []newsrover.RoverConfig `json:"newsgroups"`
	Sinks   []SinkConf              `json:"sinks"`
}

func ctrlc(stop chan<- bool) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, syscall.SIGTERM)
	go func() {
		forceExit := false
		for sig := range c {
			if sig == os.Interrupt || sig == syscall.SIGTERM {
				if forceExit {
					os.Exit(2)
				} else {
					go func() {
						stop <- true
					}()
					forceExit = true
				}
			}
		}
	}()
}

func main() {
	flag.Parse()
	runtime.GOMAXPROCS(runtime.NumCPU())

	go func() {
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()

	var conf RoverDConf
	var logStream io.Writer

	if config, err := ioutil.ReadFile(configFile); err != nil {
		fmt.Printf("Error: Failed to open configuration file %s. (%s)\n", configFile, err.Error())
		os.Exit(1)
	} else {
		if err := json.Unmarshal(config, &conf); err != nil {
			fmt.Printf("Error: Failed to parse configuration file %s. (%s)\n", configFile, err.Error())
			os.Exit(1)
		}
	}

	if conf.LogFile != "" {
		logfile, err := os.OpenFile(conf.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err == nil {
			defer logfile.Close()
			logStream = logfile
		} else {
			fmt.Printf("Failed to open log file. Writing log to stdout.\n")
		}
	}
	if logStream == nil {
		logStream = os.Stdout
	}

	generalLog := log.New(logStream, "[NewsRoverD]", log.LstdFlags)
	if len(conf.Rovers) == 0 {
		generalLog.Println("No newgroups configured, no work to do. Quitting...")
		return
	}

	sinks.Register("standard", func(config json.RawMessage) (newsrover.Sink, error) {
		return &newsrover.StdSink{}, nil
	})

	newsSinks := make([]newsrover.Sink, 0, 4)
	for _, c := range conf.Sinks {
		if s, err := sinks.CreateSink(c.Name, c.Options); err == nil {
			s.SetLogger(log.New(logStream, "", log.LstdFlags))
			newsSinks = append(newsSinks, s)
		} else {
			generalLog.Printf("Error with sink %s: %s", c.Name, err.Error())
		}
	}

	generalLog.Printf("-------------")
	generalLog.Printf("Starting NewsRoverd")
	generalLog.Printf("-------------")

	if len(newsSinks) == 0 {
		generalLog.Println("No sinks configured, no where to send work to. Quitting...")
		return
	}

	generalLog.Printf("Loaded %d sinks.", len(newsSinks))

	rovers := make([]*newsrover.Rover, 0, 4)
	for _, c := range conf.Rovers {
		if c.SSL {
			if r, err := newsrover.NewRoverSsl(c.Host, c); err == nil {
				rovers = append(rovers, r)
				r.SetLogger(log.New(logStream, "", log.LstdFlags))
			} else {
				generalLog.Printf("Failed to start rover on %s. (%s)", c.Group, err.Error())
				os.Exit(1)
			}
		} else {
			if r, err := newsrover.NewRover(c.Host, c); err == nil {
				rovers = append(rovers, r)
				r.SetLogger(log.New(logStream, "", log.LstdFlags))
			} else {
				generalLog.Printf("Failed to start rover on %s. (%s)", c.Group, err.Error())
				os.Exit(1)
			}
		}
	}

	generalLog.Printf("Loaded %d groups.", len(rovers))

	for _, rov := range rovers {
		for _, sink := range newsSinks {
			rov.AddSink(sink)
		}
	}

	var runningWg sync.WaitGroup
	for _, sink := range newsSinks {
		runningWg.Add(1)
		go func(sink newsrover.Sink) {
			defer runningWg.Done()
			sink.Serve()
			for _, rov := range rovers {
				rov.RemoveSink(sink)
			}
			generalLog.Printf("Closed sink %s.", sink.Name())
		}(sink)
	}

	for _, rov := range rovers {
		runningWg.Add(1)
		go func(rov *newsrover.Rover) {
			defer runningWg.Done()
			for i := 0; i < 4; i++ {
				if i > 0 {
					generalLog.Printf("Restarting rover on newsgroup %s.", rov.Group())
				}
				if err := rov.Serve(); err != nil {
					generalLog.Printf("Rover on newsgroup %s crashed. %s", rov.Group(), err.Error())
					time.Sleep(10 * time.Second)
				} else {
					break
				}
			}
			generalLog.Printf("Closed rover on newsgroup %s.", rov.Group())
		}(rov)
	}
	quitChan := make(chan bool)
	ctrlc(quitChan)
	runningWg.Add(1)
	go func() {
		<-quitChan
		generalLog.Printf("Stopping newsroverd (rovers)...")
		for _, rov := range rovers {
			rov.Stop()
		}
		time.Sleep(1 * time.Microsecond) // Yield
		generalLog.Printf("Stopping newsroverd (sinks)...")
		for _, sink := range newsSinks {
			sink.Stop()
		}
		runningWg.Done()
	}()

	runningWg.Wait()
	generalLog.Printf("Bye.")
}
<|endoftext|>


<commit_before>package dexcom

import (
	"fmt"
	"log"
	"time"

	"github.com/ecc1/nightscout"
)

// NightscoutEntries converts records (in reverse-chronological order)
// into a Nightscout entries.  Neighboring Sensor and EGV records are merged.
func NightscoutEntries(records Records) nightscout.Entries {
	entries := make(nightscout.Entries, len(records))
	for i, r := range records {
		entries[i] = r.nightscoutEntry()
	}
	return mergeGlucoseEntries(entries)
}

func (r Record) nightscoutEntry() nightscout.Entry {
	t := r.Time()
	e := nightscout.Entry{
		Date:       nightscout.Date(t),
		DateString: t.Format(nightscout.DateStringLayout),
		Device:     nightscout.Device(),
	}
	switch info := r.Info.(type) {
	case SensorInfo:
		e.Type = nightscout.SGVType
		e.Unfiltered = int(info.Unfiltered)
		e.Filtered = int(info.Filtered)
		e.RSSI = int(info.RSSI)
	case EGVInfo:
		e.Type = nightscout.SGVType
		e.SGV = int(info.Glucose)
		e.Direction = nightscoutTrend(info.Trend)
		e.Noise = int(info.Noise)
	case MeterInfo:
		e.Type = nightscout.MBGType
		e.MBG = int(info.Glucose)
	case CalibrationInfo:
		e.Type = nightscout.CalType
		e.Slope = info.Slope
		e.Intercept = info.Intercept
		e.Scale = info.Scale
	default:
		panic(fmt.Sprintf("nightscoutEntry %+v", r))
	}
	return e
}

func nightscoutTrend(t Trend) string {
	switch t {
	case UpUp:
		return "DoubleUp"
	case Up:
		return "SingeUp"
	case Up45:
		return "FortyFiveUp"
	case Flat:
		return "Flat"
	case Down45:
		return "FortyFiveDown"
	case Down:
		return "SingleDown"
	case DownDown:
		return "DoubleDown"
	default:
		return ""
	}
}

const (
	// Time window within which sensor and EGV readings will be merged.
	glucoseReadingWindow = 10 * time.Second
)

func mergeGlucoseEntries(entries nightscout.Entries) nightscout.Entries {
	merged := make(nightscout.Entries, 0, len(entries))
	i := 0
	for i < len(entries) {
		e := entries[i]
		if e.Type == nightscout.SGVType && i+1 < len(entries) {
			f := entries[i+1]
			if f.Type == nightscout.SGVType {
				delta := e.Time().Sub(f.Time())
				if delta < 0 {
					log.Panicf("out-of-order glucose entries (delta = %v)", delta)
				}
				if delta < glucoseReadingWindow {
					e = combineEntries(e, f)
					i++
				}
			}
		}
		merged = append(merged, e)
		i++
	}
	return merged
}

func combineEntries(a, b nightscout.Entry) nightscout.Entry {
	if a.Type != nightscout.SGVType || b.Type != nightscout.SGVType {
		log.Panicf("combining %s and %s", a.Type, b.Type)
	}
	if b.Time().Before(a.Time()) {
		// Use b's earlier time.
		a.Date = b.Date
		a.DateString = b.DateString
	}
	// Update a with non-zero sgv values from b.
	if b.SGV != 0 {
		a.SGV = b.SGV
	}
	if b.Direction != "" {
		a.Direction = b.Direction
	}
	if b.Filtered != 0 {
		a.Filtered = b.Filtered
	}
	if b.Unfiltered != 0 {
		a.Unfiltered = b.Unfiltered
	}
	if b.RSSI != 0 {
		a.RSSI = b.RSSI
	}
	if b.Noise != 0 {
		a.Noise = b.Noise
	}
	return a
}
<commit_msg>Fix typo in trend name<commit_after>package dexcom

import (
	"fmt"
	"log"
	"time"

	"github.com/ecc1/nightscout"
)

// NightscoutEntries converts records (in reverse-chronological order)
// into a Nightscout entries.  Neighboring Sensor and EGV records are merged.
func NightscoutEntries(records Records) nightscout.Entries {
	entries := make(nightscout.Entries, len(records))
	for i, r := range records {
		entries[i] = r.nightscoutEntry()
	}
	return mergeGlucoseEntries(entries)
}

func (r Record) nightscoutEntry() nightscout.Entry {
	t := r.Time()
	e := nightscout.Entry{
		Date:       nightscout.Date(t),
		DateString: t.Format(nightscout.DateStringLayout),
		Device:     nightscout.Device(),
	}
	switch info := r.Info.(type) {
	case SensorInfo:
		e.Type = nightscout.SGVType
		e.Unfiltered = int(info.Unfiltered)
		e.Filtered = int(info.Filtered)
		e.RSSI = int(info.RSSI)
	case EGVInfo:
		e.Type = nightscout.SGVType
		e.SGV = int(info.Glucose)
		e.Direction = nightscoutTrend(info.Trend)
		e.Noise = int(info.Noise)
	case MeterInfo:
		e.Type = nightscout.MBGType
		e.MBG = int(info.Glucose)
	case CalibrationInfo:
		e.Type = nightscout.CalType
		e.Slope = info.Slope
		e.Intercept = info.Intercept
		e.Scale = info.Scale
	default:
		panic(fmt.Sprintf("nightscoutEntry %+v", r))
	}
	return e
}

func nightscoutTrend(t Trend) string {
	switch t {
	case UpUp:
		return "DoubleUp"
	case Up:
		return "SingleUp"
	case Up45:
		return "FortyFiveUp"
	case Flat:
		return "Flat"
	case Down45:
		return "FortyFiveDown"
	case Down:
		return "SingleDown"
	case DownDown:
		return "DoubleDown"
	default:
		return ""
	}
}

const (
	// Time window within which sensor and EGV readings will be merged.
	glucoseReadingWindow = 10 * time.Second
)

func mergeGlucoseEntries(entries nightscout.Entries) nightscout.Entries {
	merged := make(nightscout.Entries, 0, len(entries))
	i := 0
	for i < len(entries) {
		e := entries[i]
		if e.Type == nightscout.SGVType && i+1 < len(entries) {
			f := entries[i+1]
			if f.Type == nightscout.SGVType {
				delta := e.Time().Sub(f.Time())
				if delta < 0 {
					log.Panicf("out-of-order glucose entries (delta = %v)", delta)
				}
				if delta < glucoseReadingWindow {
					e = combineEntries(e, f)
					i++
				}
			}
		}
		merged = append(merged, e)
		i++
	}
	return merged
}

func combineEntries(a, b nightscout.Entry) nightscout.Entry {
	if a.Type != nightscout.SGVType || b.Type != nightscout.SGVType {
		log.Panicf("combining %s and %s", a.Type, b.Type)
	}
	if b.Time().Before(a.Time()) {
		// Use b's earlier time.
		a.Date = b.Date
		a.DateString = b.DateString
	}
	// Update a with non-zero sgv values from b.
	if b.SGV != 0 {
		a.SGV = b.SGV
	}
	if b.Direction != "" {
		a.Direction = b.Direction
	}
	if b.Filtered != 0 {
		a.Filtered = b.Filtered
	}
	if b.Unfiltered != 0 {
		a.Unfiltered = b.Unfiltered
	}
	if b.RSSI != 0 {
		a.RSSI = b.RSSI
	}
	if b.Noise != 0 {
		a.Noise = b.Noise
	}
	return a
}
<|endoftext|>


<commit_before>package openstack

import (
	"fmt"
	"net/url"

	"github.com/rackspace/gophercloud"
	tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
	tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
	"github.com/rackspace/gophercloud/openstack/utils"
)

const (
	v20 = "v2.0"
	v30 = "v3.0"
)

// NewClient prepares an unauthenticated ProviderClient instance.
// Most users will probably prefer using the AuthenticatedClient function instead.
// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
// for example.
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
	u, err := url.Parse(endpoint)
	if err != nil {
		return nil, err
	}
	hadPath := u.Path != ""
	u.Path, u.RawQuery, u.Fragment = "", "", ""
	base := u.String()

	endpoint = gophercloud.NormalizeURL(endpoint)
	base = gophercloud.NormalizeURL(base)

	if hadPath {
		return &gophercloud.ProviderClient{
			IdentityBase:     base,
			IdentityEndpoint: endpoint,
		}, nil
	}

	return &gophercloud.ProviderClient{
		IdentityBase:     base,
		IdentityEndpoint: "",
	}, nil
}

// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and
// returns a Client instance that's ready to operate.
// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
// the most recent identity service available to proceed.
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
	client, err := NewClient(options.IdentityEndpoint)
	if err != nil {
		return nil, err
	}

	err = Authenticate(client, options)
	if err != nil {
		return nil, err
	}
	return client, nil
}

// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
	versions := []*utils.Version{
		&utils.Version{ID: v20, Priority: 20, Suffix: "/v2.0/"},
		&utils.Version{ID: v30, Priority: 30, Suffix: "/v3/"},
	}

	chosen, endpoint, err := utils.ChooseVersion(client, versions)
	if err != nil {
		return err
	}

	switch chosen.ID {
	case v20:
		return v2auth(client, endpoint, options)
	case v30:
		return v3auth(client, endpoint, options)
	default:
		// The switch statement must be out of date from the versions list.
		return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
	}
}

// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
	return v2auth(client, "", options)
}

func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
	v2Client := NewIdentityV2(client)
	if endpoint != "" {
		v2Client.Endpoint = endpoint
	}

	result := tokens2.Create(v2Client, tokens2.AuthOptions{AuthOptions: options})

	token, err := result.ExtractToken()
	if err != nil {
		return err
	}

	catalog, err := result.ExtractServiceCatalog()
	if err != nil {
		return err
	}

	if options.AllowReauth {
		client.ReauthFunc = func() error {
			return AuthenticateV2(client, options)
		}
	}
	client.TokenID = token.ID
	client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
		return V2EndpointURL(catalog, opts)
	}

	return nil
}

// AuthenticateV3 explicitly authenticates against the identity v3 service.
func AuthenticateV3(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
	return v3auth(client, "", options)
}

func v3auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
	// Override the generated service endpoint with the one returned by the version endpoint.
	v3Client := NewIdentityV3(client)
	if endpoint != "" {
		v3Client.Endpoint = endpoint
	}

	token, err := tokens3.Create(v3Client, options, nil).Extract()
	if err != nil {
		return err
	}
	client.TokenID = token.ID

	if options.AllowReauth {
		client.ReauthFunc = func() error {
			return AuthenticateV3(client, options)
		}
	}
	client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
		return V3EndpointURL(v3Client, opts)
	}

	return nil
}

// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
	v2Endpoint := client.IdentityBase + "v2.0/"

	return &gophercloud.ServiceClient{
		ProviderClient: client,
		Endpoint:       v2Endpoint,
	}
}

// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
	v3Endpoint := client.IdentityBase + "v3/"

	return &gophercloud.ServiceClient{
		ProviderClient: client,
		Endpoint:       v3Endpoint,
	}
}

// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("object-store")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}

// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package.
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("compute")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}

// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package.
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("network")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{
		ProviderClient: client,
		Endpoint:       url,
		ResourceBase:   url + "v2.0/",
	}, nil
}

// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service.
func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("volume")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}

// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
// CDN service.
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("cdn")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}

// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("orchestration")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
<commit_msg>fix recursive reauth failure<commit_after>package openstack

import (
	"fmt"
	"net/url"

	"github.com/rackspace/gophercloud"
	tokens2 "github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
	tokens3 "github.com/rackspace/gophercloud/openstack/identity/v3/tokens"
	"github.com/rackspace/gophercloud/openstack/utils"
)

const (
	v20 = "v2.0"
	v30 = "v3.0"
)

// NewClient prepares an unauthenticated ProviderClient instance.
// Most users will probably prefer using the AuthenticatedClient function instead.
// This is useful if you wish to explicitly control the version of the identity service that's used for authentication explicitly,
// for example.
func NewClient(endpoint string) (*gophercloud.ProviderClient, error) {
	u, err := url.Parse(endpoint)
	if err != nil {
		return nil, err
	}
	hadPath := u.Path != ""
	u.Path, u.RawQuery, u.Fragment = "", "", ""
	base := u.String()

	endpoint = gophercloud.NormalizeURL(endpoint)
	base = gophercloud.NormalizeURL(base)

	if hadPath {
		return &gophercloud.ProviderClient{
			IdentityBase:     base,
			IdentityEndpoint: endpoint,
		}, nil
	}

	return &gophercloud.ProviderClient{
		IdentityBase:     base,
		IdentityEndpoint: "",
	}, nil
}

// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a token, and
// returns a Client instance that's ready to operate.
// It first queries the root identity endpoint to determine which versions of the identity service are supported, then chooses
// the most recent identity service available to proceed.
func AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
	client, err := NewClient(options.IdentityEndpoint)
	if err != nil {
		return nil, err
	}

	err = Authenticate(client, options)
	if err != nil {
		return nil, err
	}
	return client, nil
}

// Authenticate or re-authenticate against the most recent identity service supported at the provided endpoint.
func Authenticate(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
	versions := []*utils.Version{
		&utils.Version{ID: v20, Priority: 20, Suffix: "/v2.0/"},
		&utils.Version{ID: v30, Priority: 30, Suffix: "/v3/"},
	}

	chosen, endpoint, err := utils.ChooseVersion(client, versions)
	if err != nil {
		return err
	}

	switch chosen.ID {
	case v20:
		return v2auth(client, endpoint, options)
	case v30:
		return v3auth(client, endpoint, options)
	default:
		// The switch statement must be out of date from the versions list.
		return fmt.Errorf("Unrecognized identity version: %s", chosen.ID)
	}
}

// AuthenticateV2 explicitly authenticates against the identity v2 endpoint.
func AuthenticateV2(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
	return v2auth(client, "", options)
}

func v2auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
	v2Client := NewIdentityV2(client)
	if endpoint != "" {
		v2Client.Endpoint = endpoint
	}

	result := tokens2.Create(v2Client, tokens2.AuthOptions{AuthOptions: options})

	token, err := result.ExtractToken()
	if err != nil {
		return err
	}

	catalog, err := result.ExtractServiceCatalog()
	if err != nil {
		return err
	}

	if options.AllowReauth {
		client.ReauthFunc = func() error {
			client.TokenID = ""
			return AuthenticateV2(client, options)
		}
	}
	client.TokenID = token.ID
	client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
		return V2EndpointURL(catalog, opts)
	}

	return nil
}

// AuthenticateV3 explicitly authenticates against the identity v3 service.
func AuthenticateV3(client *gophercloud.ProviderClient, options gophercloud.AuthOptions) error {
	return v3auth(client, "", options)
}

func v3auth(client *gophercloud.ProviderClient, endpoint string, options gophercloud.AuthOptions) error {
	// Override the generated service endpoint with the one returned by the version endpoint.
	v3Client := NewIdentityV3(client)
	if endpoint != "" {
		v3Client.Endpoint = endpoint
	}

	token, err := tokens3.Create(v3Client, options, nil).Extract()
	if err != nil {
		return err
	}
	client.TokenID = token.ID

	if options.AllowReauth {
		client.ReauthFunc = func() error {
			return AuthenticateV3(client, options)
		}
	}
	client.EndpointLocator = func(opts gophercloud.EndpointOpts) (string, error) {
		return V3EndpointURL(v3Client, opts)
	}

	return nil
}

// NewIdentityV2 creates a ServiceClient that may be used to interact with the v2 identity service.
func NewIdentityV2(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
	v2Endpoint := client.IdentityBase + "v2.0/"

	return &gophercloud.ServiceClient{
		ProviderClient: client,
		Endpoint:       v2Endpoint,
	}
}

// NewIdentityV3 creates a ServiceClient that may be used to access the v3 identity service.
func NewIdentityV3(client *gophercloud.ProviderClient) *gophercloud.ServiceClient {
	v3Endpoint := client.IdentityBase + "v3/"

	return &gophercloud.ServiceClient{
		ProviderClient: client,
		Endpoint:       v3Endpoint,
	}
}

// NewObjectStorageV1 creates a ServiceClient that may be used with the v1 object storage package.
func NewObjectStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("object-store")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}

// NewComputeV2 creates a ServiceClient that may be used with the v2 compute package.
func NewComputeV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("compute")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}

// NewNetworkV2 creates a ServiceClient that may be used with the v2 network package.
func NewNetworkV2(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("network")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{
		ProviderClient: client,
		Endpoint:       url,
		ResourceBase:   url + "v2.0/",
	}, nil
}

// NewBlockStorageV1 creates a ServiceClient that may be used to access the v1 block storage service.
func NewBlockStorageV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("volume")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}

// NewCDNV1 creates a ServiceClient that may be used to access the OpenStack v1
// CDN service.
func NewCDNV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("cdn")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}

// NewOrchestrationV1 creates a ServiceClient that may be used to access the v1 orchestration service.
func NewOrchestrationV1(client *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (*gophercloud.ServiceClient, error) {
	eo.ApplyDefaults("orchestration")
	url, err := client.EndpointLocator(eo)
	if err != nil {
		return nil, err
	}
	return &gophercloud.ServiceClient{ProviderClient: client, Endpoint: url}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
	"time"
)

func (p *Process) Start() {
	command := []string{"/bin/bash", p.bashArgument(), fmt.Sprintf("source \"%s\" 2>/dev/null; %s", filepath.Join(p.Root, ".profile"), p.Command)}
	p.cmd = exec.Command(command[0], command[1:]...)
	p.cmd.Dir = p.Root
	p.cmd.Env = p.envAsArray()
	p.cmd.Stdin = p.Stdin
	p.cmd.Stdout = p.Stdout
	p.cmd.Stderr = p.Stderr
	if !p.Interactive {
		p.cmd.SysProcAttr = &syscall.SysProcAttr{}
		p.cmd.SysProcAttr.Setsid = true
	}
	p.cmd.Start()
}

func (p *Process) Signal(signal syscall.Signal) {
	if p.Running() {
		group, _ := os.FindProcess(-1 * p.Pid())
		group.Signal(signal)
	}
}

func ShutdownProcesses(of *OutletFactory) {
	shutdown_mutex.Lock()
	of.SystemOutput("shutting down")
	for name, ps := range processes {
		of.SystemOutput(fmt.Sprintf("sending SIGTERM to %s", name))
		ps.Signal(syscall.SIGTERM)
	}
	go func() {
		time.Sleep(shutdownGraceTime)
		for name, ps := range processes {
			of.SystemOutput(fmt.Sprintf("sending SIGKILL to %s", name))
			ps.Signal(syscall.SIGKILL)
		}
	}()
}
<commit_msg>fix error in linux build<commit_after>package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"syscall"
	"time"
)

func (p *Process) Start() {
	command := []string{"/bin/bash", p.shellArgument(), fmt.Sprintf("source \"%s\" 2>/dev/null; %s", filepath.Join(p.Root, ".profile"), p.Command)}
	p.cmd = exec.Command(command[0], command[1:]...)
	p.cmd.Dir = p.Root
	p.cmd.Env = p.envAsArray()
	p.cmd.Stdin = p.Stdin
	p.cmd.Stdout = p.Stdout
	p.cmd.Stderr = p.Stderr
	if !p.Interactive {
		p.cmd.SysProcAttr = &syscall.SysProcAttr{}
		p.cmd.SysProcAttr.Setsid = true
	}
	p.cmd.Start()
}

func (p *Process) Signal(signal syscall.Signal) {
	if p.Running() {
		group, _ := os.FindProcess(-1 * p.Pid())
		group.Signal(signal)
	}
}

func ShutdownProcesses(of *OutletFactory) {
	shutdown_mutex.Lock()
	of.SystemOutput("shutting down")
	for name, ps := range processes {
		of.SystemOutput(fmt.Sprintf("sending SIGTERM to %s", name))
		ps.Signal(syscall.SIGTERM)
	}
	go func() {
		time.Sleep(shutdownGraceTime)
		for name, ps := range processes {
			of.SystemOutput(fmt.Sprintf("sending SIGKILL to %s", name))
			ps.Signal(syscall.SIGKILL)
		}
	}()
}
<|endoftext|>


<commit_before>package orderedtaskpool

/*
orderedtask.Pool

The primary use-case for this package would be to insert tassks in order, have them
processed in parallel and have the results consumed in the original order.

Example:
	You need to read billions of JSON messages out of a Kafka partition, unmarshall the JSON
	into a struct and maintain the order of messages.

	JSON unmarshalling is very slow and could add days to your processing time, so you want to
	unmarshall the data in parallel.  But after you unmarshall the messages, you have to
	put them back into the original order they were read from kafka.

	Solution use OrderedTaskPool, the Task's index will be the kafka-offset and the Task's input
	will be the message bytes.   The Task's result will be the unmarshalled struct.  For you
	processor func(),  write code to do the JSON unmarshalling

Events are emitted ascending index order.   i.e. 1, 2, 3,...,N

	        |                            |
	        |                            |
	        |     ---->  worker() --\    |
	        |    /                   \   |
Enqueue()--in----------->  worker() ------out----> Results()
	        |    \                   /   |
	        |     ----> worker() ---/    |
	        |                            |

*/
<commit_msg>fixing package name in docs.go<commit_after>package orderedtask

/*
orderedtask.Pool

The primary use-case for this package would be to insert tasks in order, have them
processed in parallel and have the results consumed in the original order.

If a tasks is going to take 10ms and you have 10,000 tasks to perform, then the expected runtime
to perform those tasks one at a time is: `10000 * 10ms == 100 seconds`.  But if you can do them
parallel with 12 workers, the expect runtime becomes `(10000 * 10ms) / 12 == 8.3 seconds`.

Notes:

All Tasks are emitted in ascending index order.   i.e. 1, 2, 3,...,N

Internally were using two MinHeaps, one for the lowest task inserted into the pool and
one for the lowest index of finished tasks.  While the finish tasks index matches the
lowest inserted task, we'll emit the results from the finished tasks.  If the lowest
index for finished tasks is not equal to the lowest inserted task's index, then we wait for
the workers who are processing the lowest indexes to finish before draining.

	        |                            |
	        |                            |
	        |     ---->  worker() --\    |
	        |    /                   \   |
Enqueue()--in----------->  worker() ------out----> Results()
	        |    \                   /   |
	        |     ----> worker() ---/    |
	        |                            |

*/
<|endoftext|>