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

package pbf

import (
	"bytes"
	"compress/zlib"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"reflect"
	"runtime"
	"sync"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/maguro/pbf/protobuf"
)

const (
	initialBufferSize = 1024 * 1024
)

type encoded struct {
	header *protobuf.BlobHeader
	blob   *protobuf.Blob
	err    error
}

type decoded struct {
	elements []interface{}
	err      error
}

type pair struct {
	element interface{}
	err     error
}

// Decoder reads and decodes OpenStreetMap PBF data from an input stream.
type Decoder struct {
	protoBufferSize      int
	inputChannelLength   int
	outputChannelLength  int
	decodedChannelLength int
	ncpu                 uint16
	decoded              chan pair
	done                 chan struct{}
	begin                sync.Once
	end                  sync.Once

	reader io.Reader

	Header *Header
}

// DecoderConfig provides optional configuration parameters for Decoder construction.
type DecoderConfig struct {
	ProtoBufferSize      int    // buffer size for protobuf un-marshaling
	InputChannelLength   int    // channel length of raw blobs
	OutputChannelLength  int    // channel length of decoded arrays of element
	DecodedChannelLength int    // channel length of decoded elements coalesced from output channels
	NCpu                 uint16 // the number of CPUs to use for background processing
}

// DefaultConfig provides a default configuration.
var DefaultConfig = DecoderConfig{}

// NewDecoder returns a new decoder that reads from r.
func NewDecoder(r io.Reader, cfg DecoderConfig) (*Decoder, error) {
	d := &Decoder{
		protoBufferSize:      initialBufferSize,
		inputChannelLength:   16,
		outputChannelLength:  8,
		decodedChannelLength: 8000,
		ncpu:                 uint16(runtime.GOMAXPROCS(-1)),
		reader:               r,
	}

	if cfg.ProtoBufferSize > 0 {
		d.protoBufferSize = cfg.ProtoBufferSize
	}
	if cfg.InputChannelLength > 0 {
		d.inputChannelLength = cfg.InputChannelLength
	}
	if cfg.OutputChannelLength > 0 {
		d.outputChannelLength = cfg.OutputChannelLength
	}
	if cfg.DecodedChannelLength > 0 {
		d.decodedChannelLength = cfg.DecodedChannelLength
	}
	if cfg.NCpu > 0 {
		d.ncpu = cfg.NCpu
	}

	buf := bytes.NewBuffer(make([]byte, 0, d.protoBufferSize))

	h, err := d.readBlobHeader(buf)
	if err != nil {
		return nil, err
	}
	b, err := d.readBlob(buf, h)
	if err != nil {
		return nil, err
	}
	elements, err := decode(h, b, bytes.NewBuffer(make([]byte, 0, 1024)))
	if err != nil {
		return nil, err
	}
	d.Header = elements[0].(*Header)
	if d.Header == nil {
		err = fmt.Errorf("expected header data but got %v", reflect.TypeOf(elements[0]))
		return nil, err
	}

	return d, nil
}

// start begins parsing in the background using n goroutines.  The background
// processing can be canceled by calling Stop.
func (d *Decoder) start() {
	n := int(d.ncpu)

	d.begin.Do(func() {
		inputs := make([]chan<- encoded, n)
		outputs := make([]<-chan decoded, n)
		d.decoded = make(chan pair, d.decodedChannelLength)
		d.done = make(chan struct{})

		// start data decoders
		for i := 0; i < n; i++ {
			input := make(chan encoded, d.inputChannelLength)
			output := make(chan decoded, d.outputChannelLength)

			go d.decode(input, output)

			inputs[i] = input
			outputs[i] = output
		}

		// read raw blobs and distribute amongst inputs
		go d.read(inputs)

		// coalesce decoded elements
		go d.coalesce(outputs)
	})
}

// Stop will cancel the background decoding pipeline.
func (d *Decoder) Stop() {

	d.begin.Do(func() {
		// close decoded so calls to Decode return EOF
		close(d.decoded)
	})

	d.end.Do(func() {
		if d.done != nil {
			// closing done notifies pipeline to cancel
			close(d.done)
		}
	})
}

// Decode reads the next OSM object and returns either a pointer to Node, Way
// or Relation struct representing the underlying OpenStreetMap PBF data, or
// error encountered. The end of the input stream is reported by an io.EOF
// error.
//
// If background parsing was not begun by calling Start, Start is called with
// the maximum number of CPUs returned by GOMAXPROCS.
func (d *Decoder) Decode() (interface{}, error) {

	d.start()

	decoded, more := <-d.decoded
	if !more {
		return nil, io.EOF
	}
	return decoded.element, decoded.err
}

// read reads blobs and their headers and place them onto input channels to be
// decoded.  The blob/header pairs are placed, round-robin, onto an array of
// input channels to be concurrently decoded.
func (d Decoder) read(inputs []chan<- encoded) {
	defer func() {
		for _, input := range inputs {
			close(input)
		}
	}()

	n := len(inputs)
	buffer := bytes.NewBuffer(make([]byte, 0, d.protoBufferSize))
	var i int
	for {
		input := inputs[i]
		i = (i + 1) % n

		h, err := d.readBlobHeader(buffer)
		if err == io.EOF {
			return
		} else if err != nil {
			input <- encoded{err: err}
			return
		}

		b, err := d.readBlob(buffer, h)
		if err != nil {
			input <- encoded{err: err}
			return
		}

		select {
		case <-d.done:
			return
		case input <- encoded{header: h, blob: b}:
		}
	}
}

// decode decodes blob/header pairs into an array of OSM elements.  These
// arrays are placed onto an output channel where they will be coalesced into
// their correct order.
func (d *Decoder) decode(input chan encoded, output chan decoded) {
	defer close(output)

	buf := bytes.NewBuffer(make([]byte, 0, d.protoBufferSize))

	for {
		raw, more := <-input
		if !more {
			return
		}

		if raw.err != nil {
			output <- decoded{nil, raw.err}
			return
		}

		elements, err := decode(raw.header, raw.blob, buf)

		output <- decoded{elements, err}
	}
}

// coalesce coalesces decoded elements from an array of channels, in a
// round-robin manner, into the decoded channel to be consumed by the Decode
// method.
func (d *Decoder) coalesce(outputs []<-chan decoded) {
	defer close(d.decoded)

	n := len(outputs)
	var i int
	for {
		output := outputs[i]
		i = (i + 1) % n

		decoded, more := <-output
		if !more {
			return
		}

		if decoded.err != nil {
			d.decoded <- pair{nil, decoded.err}
			return
		}

		for _, e := range decoded.elements {
			d.decoded <- pair{e, nil}
		}
	}
}

// readBlobHeader unmarshals a header from an array of protobuf encoded bytes.
// The header is used when decoding blobs into OSM elements.
func (d *Decoder) readBlobHeader(buffer *bytes.Buffer) (header *protobuf.BlobHeader, err error) {
	var size uint32
	err = binary.Read(d.reader, binary.BigEndian, &size)
	if err != nil {
		return nil, err
	}

	buffer.Reset()
	if _, err := io.CopyN(buffer, d.reader, int64(size)); err != nil {
		return nil, err
	}

	header = &protobuf.BlobHeader{}
	if err := proto.Unmarshal(buffer.Bytes(), header); err != nil {
		return nil, err
	}

	return header, nil
}

// readBlob unmarshals a blob from an array of protobuf encoded bytes.  The
// blob still needs to be decoded into OSM elements using decode().
func (d *Decoder) readBlob(buffer *bytes.Buffer, header *protobuf.BlobHeader) (*protobuf.Blob, error) {
	size := header.GetDatasize()

	buffer.Reset()
	if _, err := io.CopyN(buffer, d.reader, int64(size)); err != nil {
		return nil, err
	}

	blob := &protobuf.Blob{}
	if err := proto.Unmarshal(buffer.Bytes(), blob); err != nil {
		return nil, err
	}

	return blob, nil
}

// decode unmarshals an array of OSM elements from an array of protobuf encoded
// bytes.  The bytes could possibly be compressed; zlibBuf is used to facilitate
// decompression.
func decode(header *protobuf.BlobHeader, blob *protobuf.Blob, zlibBuf *bytes.Buffer) ([]interface{}, error) {
	var buf []byte
	switch {
	case blob.Raw != nil:
		buf = blob.GetRaw()

	case blob.ZlibData != nil:
		r, err := zlib.NewReader(bytes.NewReader(blob.GetZlibData()))
		if err != nil {
			return nil, err
		}
		zlibBuf.Reset()
		rawBufferSize := int(blob.GetRawSize() + bytes.MinRead)
		if rawBufferSize > zlibBuf.Cap() {
			zlibBuf.Grow(rawBufferSize)
		}
		_, err = zlibBuf.ReadFrom(r)
		if err != nil {
			return nil, err
		}
		if zlibBuf.Len() != int(blob.GetRawSize()) {
			err = fmt.Errorf("raw blob data size %d but expected %d", zlibBuf.Len(), blob.GetRawSize())
			return nil, err
		}
		buf = zlibBuf.Bytes()

	default:
		return nil, errors.New("unknown blob data type")
	}

	ht := *header.Type
	if ht == "OSMHeader" {
		h, err := parseOSMHeader(buf)
		if err != nil {
			return nil, err
		}
		return []interface{}{h}, nil
	} else if ht == "OSMData" {
		return parsePrimitiveBlock(buf)
	} else {
		return nil, fmt.Errorf("unknown header type %s", ht)
	}
}

// parseOSMHeader unmarshals the OSM header from an array of protobuf encoded bytes.
func parseOSMHeader(buffer []byte) (*Header, error) {
	hb := &protobuf.HeaderBlock{}
	if err := proto.Unmarshal(buffer, hb); err != nil {
		return nil, err
	}

	header := &Header{
		RequiredFeatures: hb.GetRequiredFeatures(),
		OptionalFeatures: hb.GetOptionalFeatures(),
		WritingProgram:   hb.GetWritingprogram(),
		Source:           hb.GetSource(),
		OsmosisReplicationBaseURL:        hb.GetOsmosisReplicationBaseUrl(),
		OsmosisReplicationSequenceNumber: hb.GetOsmosisReplicationSequenceNumber(),
	}

	if hb.Bbox != nil {
		header.BoundingBox = &BoundingBox{
			Left:   toDegrees(0, 1, hb.Bbox.GetLeft()),
			Right:  toDegrees(0, 1, hb.Bbox.GetRight()),
			Top:    toDegrees(0, 1, hb.Bbox.GetTop()),
			Bottom: toDegrees(0, 1, hb.Bbox.GetBottom()),
		}
	}

	if hb.OsmosisReplicationTimestamp != nil {
		header.OsmosisReplicationTimestamp = time.Unix(*hb.OsmosisReplicationTimestamp, 0)
	}

	return header, nil
}

// toDegrees converts a coordinate into Degrees, given the offset and
// granularity of the coordinate.
func toDegrees(offset int64, granularity int32, coordinate int64) Degrees {
	return 1e-9 * Degrees(offset+(int64(granularity)*coordinate))
}
<commit_msg>Fix Decoder receiver for read()<commit_after>// Copyright 2017 the original author or authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pbf

import (
	"bytes"
	"compress/zlib"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"reflect"
	"runtime"
	"sync"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/maguro/pbf/protobuf"
)

const (
	initialBufferSize = 1024 * 1024
)

type encoded struct {
	header *protobuf.BlobHeader
	blob   *protobuf.Blob
	err    error
}

type decoded struct {
	elements []interface{}
	err      error
}

type pair struct {
	element interface{}
	err     error
}

// Decoder reads and decodes OpenStreetMap PBF data from an input stream.
type Decoder struct {
	protoBufferSize      int
	inputChannelLength   int
	outputChannelLength  int
	decodedChannelLength int
	ncpu                 uint16
	decoded              chan pair
	done                 chan struct{}
	begin                sync.Once
	end                  sync.Once

	reader io.Reader

	Header *Header
}

// DecoderConfig provides optional configuration parameters for Decoder construction.
type DecoderConfig struct {
	ProtoBufferSize      int    // buffer size for protobuf un-marshaling
	InputChannelLength   int    // channel length of raw blobs
	OutputChannelLength  int    // channel length of decoded arrays of element
	DecodedChannelLength int    // channel length of decoded elements coalesced from output channels
	NCpu                 uint16 // the number of CPUs to use for background processing
}

// DefaultConfig provides a default configuration.
var DefaultConfig = DecoderConfig{}

// NewDecoder returns a new decoder that reads from r.
func NewDecoder(r io.Reader, cfg DecoderConfig) (*Decoder, error) {
	d := &Decoder{
		protoBufferSize:      initialBufferSize,
		inputChannelLength:   16,
		outputChannelLength:  8,
		decodedChannelLength: 8000,
		ncpu:                 uint16(runtime.GOMAXPROCS(-1)),
		reader:               r,
	}

	if cfg.ProtoBufferSize > 0 {
		d.protoBufferSize = cfg.ProtoBufferSize
	}
	if cfg.InputChannelLength > 0 {
		d.inputChannelLength = cfg.InputChannelLength
	}
	if cfg.OutputChannelLength > 0 {
		d.outputChannelLength = cfg.OutputChannelLength
	}
	if cfg.DecodedChannelLength > 0 {
		d.decodedChannelLength = cfg.DecodedChannelLength
	}
	if cfg.NCpu > 0 {
		d.ncpu = cfg.NCpu
	}

	buf := bytes.NewBuffer(make([]byte, 0, d.protoBufferSize))

	h, err := d.readBlobHeader(buf)
	if err != nil {
		return nil, err
	}
	b, err := d.readBlob(buf, h)
	if err != nil {
		return nil, err
	}
	elements, err := decode(h, b, bytes.NewBuffer(make([]byte, 0, 1024)))
	if err != nil {
		return nil, err
	}
	d.Header = elements[0].(*Header)
	if d.Header == nil {
		err = fmt.Errorf("expected header data but got %v", reflect.TypeOf(elements[0]))
		return nil, err
	}

	return d, nil
}

// start begins parsing in the background using n goroutines.  The background
// processing can be canceled by calling Stop.
func (d *Decoder) start() {
	n := int(d.ncpu)

	d.begin.Do(func() {
		inputs := make([]chan<- encoded, n)
		outputs := make([]<-chan decoded, n)
		d.decoded = make(chan pair, d.decodedChannelLength)
		d.done = make(chan struct{})

		// start data decoders
		for i := 0; i < n; i++ {
			input := make(chan encoded, d.inputChannelLength)
			output := make(chan decoded, d.outputChannelLength)

			go d.decode(input, output)

			inputs[i] = input
			outputs[i] = output
		}

		// read raw blobs and distribute amongst inputs
		go d.read(inputs)

		// coalesce decoded elements
		go d.coalesce(outputs)
	})
}

// Stop will cancel the background decoding pipeline.
func (d *Decoder) Stop() {

	d.begin.Do(func() {
		// close decoded so calls to Decode return EOF
		close(d.decoded)
	})

	d.end.Do(func() {
		if d.done != nil {
			// closing done notifies pipeline to cancel
			close(d.done)
		}
	})
}

// Decode reads the next OSM object and returns either a pointer to Node, Way
// or Relation struct representing the underlying OpenStreetMap PBF data, or
// error encountered. The end of the input stream is reported by an io.EOF
// error.
//
// If background parsing was not begun by calling Start, Start is called with
// the maximum number of CPUs returned by GOMAXPROCS.
func (d *Decoder) Decode() (interface{}, error) {

	d.start()

	decoded, more := <-d.decoded
	if !more {
		return nil, io.EOF
	}
	return decoded.element, decoded.err
}

// read reads blobs and their headers and place them onto input channels to be
// decoded.  The blob/header pairs are placed, round-robin, onto an array of
// input channels to be concurrently decoded.
func (d *Decoder) read(inputs []chan<- encoded) {
	defer func() {
		for _, input := range inputs {
			close(input)
		}
	}()

	n := len(inputs)
	buffer := bytes.NewBuffer(make([]byte, 0, d.protoBufferSize))
	var i int
	for {
		input := inputs[i]
		i = (i + 1) % n

		h, err := d.readBlobHeader(buffer)
		if err == io.EOF {
			return
		} else if err != nil {
			input <- encoded{err: err}
			return
		}

		b, err := d.readBlob(buffer, h)
		if err != nil {
			input <- encoded{err: err}
			return
		}

		select {
		case <-d.done:
			return
		case input <- encoded{header: h, blob: b}:
		}
	}
}

// decode decodes blob/header pairs into an array of OSM elements.  These
// arrays are placed onto an output channel where they will be coalesced into
// their correct order.
func (d *Decoder) decode(input chan encoded, output chan decoded) {
	defer close(output)

	buf := bytes.NewBuffer(make([]byte, 0, d.protoBufferSize))

	for {
		raw, more := <-input
		if !more {
			return
		}

		if raw.err != nil {
			output <- decoded{nil, raw.err}
			return
		}

		elements, err := decode(raw.header, raw.blob, buf)

		output <- decoded{elements, err}
	}
}

// coalesce coalesces decoded elements from an array of channels, in a
// round-robin manner, into the decoded channel to be consumed by the Decode
// method.
func (d *Decoder) coalesce(outputs []<-chan decoded) {
	defer close(d.decoded)

	n := len(outputs)
	var i int
	for {
		output := outputs[i]
		i = (i + 1) % n

		decoded, more := <-output
		if !more {
			return
		}

		if decoded.err != nil {
			d.decoded <- pair{nil, decoded.err}
			return
		}

		for _, e := range decoded.elements {
			d.decoded <- pair{e, nil}
		}
	}
}

// readBlobHeader unmarshals a header from an array of protobuf encoded bytes.
// The header is used when decoding blobs into OSM elements.
func (d *Decoder) readBlobHeader(buffer *bytes.Buffer) (header *protobuf.BlobHeader, err error) {
	var size uint32
	err = binary.Read(d.reader, binary.BigEndian, &size)
	if err != nil {
		return nil, err
	}

	buffer.Reset()
	if _, err := io.CopyN(buffer, d.reader, int64(size)); err != nil {
		return nil, err
	}

	header = &protobuf.BlobHeader{}
	if err := proto.Unmarshal(buffer.Bytes(), header); err != nil {
		return nil, err
	}

	return header, nil
}

// readBlob unmarshals a blob from an array of protobuf encoded bytes.  The
// blob still needs to be decoded into OSM elements using decode().
func (d *Decoder) readBlob(buffer *bytes.Buffer, header *protobuf.BlobHeader) (*protobuf.Blob, error) {
	size := header.GetDatasize()

	buffer.Reset()
	if _, err := io.CopyN(buffer, d.reader, int64(size)); err != nil {
		return nil, err
	}

	blob := &protobuf.Blob{}
	if err := proto.Unmarshal(buffer.Bytes(), blob); err != nil {
		return nil, err
	}

	return blob, nil
}

// decode unmarshals an array of OSM elements from an array of protobuf encoded
// bytes.  The bytes could possibly be compressed; zlibBuf is used to facilitate
// decompression.
func decode(header *protobuf.BlobHeader, blob *protobuf.Blob, zlibBuf *bytes.Buffer) ([]interface{}, error) {
	var buf []byte
	switch {
	case blob.Raw != nil:
		buf = blob.GetRaw()

	case blob.ZlibData != nil:
		r, err := zlib.NewReader(bytes.NewReader(blob.GetZlibData()))
		if err != nil {
			return nil, err
		}
		zlibBuf.Reset()
		rawBufferSize := int(blob.GetRawSize() + bytes.MinRead)
		if rawBufferSize > zlibBuf.Cap() {
			zlibBuf.Grow(rawBufferSize)
		}
		_, err = zlibBuf.ReadFrom(r)
		if err != nil {
			return nil, err
		}
		if zlibBuf.Len() != int(blob.GetRawSize()) {
			err = fmt.Errorf("raw blob data size %d but expected %d", zlibBuf.Len(), blob.GetRawSize())
			return nil, err
		}
		buf = zlibBuf.Bytes()

	default:
		return nil, errors.New("unknown blob data type")
	}

	ht := *header.Type
	if ht == "OSMHeader" {
		h, err := parseOSMHeader(buf)
		if err != nil {
			return nil, err
		}
		return []interface{}{h}, nil
	} else if ht == "OSMData" {
		return parsePrimitiveBlock(buf)
	} else {
		return nil, fmt.Errorf("unknown header type %s", ht)
	}
}

// parseOSMHeader unmarshals the OSM header from an array of protobuf encoded bytes.
func parseOSMHeader(buffer []byte) (*Header, error) {
	hb := &protobuf.HeaderBlock{}
	if err := proto.Unmarshal(buffer, hb); err != nil {
		return nil, err
	}

	header := &Header{
		RequiredFeatures: hb.GetRequiredFeatures(),
		OptionalFeatures: hb.GetOptionalFeatures(),
		WritingProgram:   hb.GetWritingprogram(),
		Source:           hb.GetSource(),
		OsmosisReplicationBaseURL:        hb.GetOsmosisReplicationBaseUrl(),
		OsmosisReplicationSequenceNumber: hb.GetOsmosisReplicationSequenceNumber(),
	}

	if hb.Bbox != nil {
		header.BoundingBox = &BoundingBox{
			Left:   toDegrees(0, 1, hb.Bbox.GetLeft()),
			Right:  toDegrees(0, 1, hb.Bbox.GetRight()),
			Top:    toDegrees(0, 1, hb.Bbox.GetTop()),
			Bottom: toDegrees(0, 1, hb.Bbox.GetBottom()),
		}
	}

	if hb.OsmosisReplicationTimestamp != nil {
		header.OsmosisReplicationTimestamp = time.Unix(*hb.OsmosisReplicationTimestamp, 0)
	}

	return header, nil
}

// toDegrees converts a coordinate into Degrees, given the offset and
// granularity of the coordinate.
func toDegrees(offset int64, granularity int32, coordinate int64) Degrees {
	return 1e-9 * Degrees(offset+(int64(granularity)*coordinate))
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/base64"
	"io/ioutil"
	"os"
	"strings"

	"golang.org/x/crypto/openpgp"
)

// XXX: For actual encryption in app do not use armor for decoding

var privateKeyEntityList openpgp.EntityList
var passphrase string

func decrypt(base64msg string) (string, error) {

	var err error

	if passphrase == "" {
		var pass []byte
		pass, err = ioutil.ReadFile(config.Passphrase)
		if err != nil {
			panic(err)
		}
		passphrase = strings.TrimSpace(string(pass))
	}

	message, err := base64.StdEncoding.DecodeString(base64msg)
	if err != nil {
		return "", err
	}

	buf := bytes.NewBuffer([]byte(message))

	/*result, err := armor.Decode(buf)
	if err != nil {
		return "", err
	}*/
	if privateKeyEntityList == nil {
		var keyringFileBuffer *os.File
		keyringFileBuffer, err = os.Open(config.PrivateKey)
		if err != nil {
			return "", err
		}
		defer keyringFileBuffer.Close()

		privateKeyEntityList, err = openpgp.ReadKeyRing(keyringFileBuffer)
		if err != nil {
			return "", err
		}
	}

	entity := privateKeyEntityList[0]
	passphraseByte := []byte(passphrase)
	if entity.PrivateKey.Encrypted {
		if err = entity.PrivateKey.Decrypt(passphraseByte); err != nil {
			return "", err
		}
	}
	for _, subkey := range entity.Subkeys {
		if subkey.PrivateKey.Encrypted {
			if err = subkey.PrivateKey.Decrypt(passphraseByte); err != nil {
				return "", err
			}
		}
	}

	md, err := openpgp.ReadMessage(buf, privateKeyEntityList, nil, nil)
	if err != nil {
		return "", err
	}

	bytes, err := ioutil.ReadAll(md.UnverifiedBody)
	if err != nil {
		return "", err
	}

	return string(bytes), nil
}

/*func main() {
	msg, err := ioutil.ReadFile("./msg.txt")
	if err != nil {
		panic(err)
	}

	pass, err := ioutil.ReadFile("./pass.key")
	if err != nil {
		panic(err)
	}

	str, err := decrypt(string(msg), strings.TrimSpace(string(pass)))
	if err != nil {
		panic(err)
	}

	fmt.Println(str)
}*/
<commit_msg>fixed decrypt<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"io/ioutil"
	"os"
	"strings"

	"golang.org/x/crypto/openpgp"
)

// XXX: For actual encryption in app do not use armor for decoding

var privateKeyEntityList openpgp.EntityList
var passphrase string

func decrypt(base64msg string) (string, error) {

	var err error

	if passphrase == "" {
		var pass []byte
		pass, err = ioutil.ReadFile(config.Passphrase)
		if err != nil {
			panic(err)
		}
		passphrase = strings.TrimSpace(string(pass))
	}

	messageByteArr, err := base64.StdEncoding.DecodeString(base64msg)
	if err != nil {
		return "", err
	}

	buf := bytes.NewBuffer(messageByteArr)

	/*result, err := armor.Decode(buf)
	if err != nil {
		return "", err
	}*/
	if privateKeyEntityList == nil {
		var keyringFileBuffer *os.File
		keyringFileBuffer, err = os.Open(config.PrivateKey)
		if err != nil {
			return "", err
		}
		defer keyringFileBuffer.Close()

		privateKeyEntityList, err = openpgp.ReadArmoredKeyRing(keyringFileBuffer)
		if err != nil {
			return "", err
		}
	}

	entity := privateKeyEntityList[0]
	passphraseByte := []byte(passphrase)
	if entity.PrivateKey.Encrypted {
		if err = entity.PrivateKey.Decrypt(passphraseByte); err != nil {
			return "", err
		}
	}
	for _, subkey := range entity.Subkeys {
		if subkey.PrivateKey.Encrypted {
			if err = subkey.PrivateKey.Decrypt(passphraseByte); err != nil {
				return "", err
			}
		}
	}

	md, err := openpgp.ReadMessage(buf, privateKeyEntityList, nil, nil)
	if err != nil {
		return "", err
	}

	bytes, err := ioutil.ReadAll(md.UnverifiedBody)
	if err != nil {
		return "", err
	}

	return string(bytes), nil
}

/*func main() {
	msg, err := ioutil.ReadFile("./msg.txt")
	if err != nil {
		panic(err)
	}

	pass, err := ioutil.ReadFile("./pass.key")
	if err != nil {
		panic(err)
	}

	str, err := decrypt(string(msg), strings.TrimSpace(string(pass)))
	if err != nil {
		panic(err)
	}

	fmt.Println(str)
}*/
<|endoftext|>


<commit_before>//   Copyright (c) 2016 Ivan A Kostko (github.com/ivan-kostko)

//   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 Interfaces

import (
	. "CustomErrors"
)

// Interface represents Initialize methood
type Initializer interface {
	// The method sets up instance and returns error if instance won't be initialized
	Initialize() *Error
}
<commit_msg>Update CustomErrors reference<commit_after>//   Copyright (c) 2016 Ivan A Kostko (github.com/ivan-kostko)

//   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 Interfaces

import (
	. "github.com/ivan-kostko/GoLibs/CustomErrors"
)

// Interface represents Initialize methood
type Initializer interface {
	// The method sets up instance and returns error if instance won't be initialized
	Initialize() *Error
}
<|endoftext|>


<commit_before>package wantlist

import (
	u "github.com/jbenet/go-ipfs/util"
	"sort"
	"sync"
)

type ThreadSafe struct {
	lk sync.RWMutex
	Wantlist
}

// not threadsafe
type Wantlist struct {
	set map[u.Key]*Entry
}

type Entry struct {
	Key      u.Key
	Priority int
}

type entrySlice []*Entry

func (es entrySlice) Len() int           { return len(es) }
func (es entrySlice) Swap(i, j int)      { es[i], es[j] = es[j], es[i] }
func (es entrySlice) Less(i, j int) bool { return es[i].Priority > es[j].Priority }

func NewThreadSafe() *ThreadSafe {
	return &ThreadSafe{
		Wantlist: *New(),
	}
}

func New() *Wantlist {
	return &Wantlist{
		set: make(map[u.Key]*Entry),
	}
}

func (w *ThreadSafe) Add(k u.Key, priority int) {
	// TODO rm defer for perf
	w.lk.Lock()
	defer w.lk.Unlock()
	w.Wantlist.Add(k, priority)
}

func (w *ThreadSafe) Remove(k u.Key) {
	// TODO rm defer for perf
	w.lk.Lock()
	defer w.lk.Unlock()
	w.Wantlist.Remove(k)
}

func (w *ThreadSafe) Contains(k u.Key) bool {
	// TODO rm defer for perf
	w.lk.RLock()
	defer w.lk.RUnlock()
	return w.Wantlist.Contains(k)
}

func (w *ThreadSafe) Entries() []*Entry {
	w.lk.RLock()
	defer w.lk.RUnlock()
	var es entrySlice
	for _, e := range w.set {
		es = append(es, e)
	}
	// TODO rename SortedEntries (state that they're sorted so callers know
	// they're paying an expense)
	sort.Sort(es)
	return es
}

func (w *ThreadSafe) SortedEntries() []*Entry {
	w.lk.RLock()
	defer w.lk.RUnlock()
	var es entrySlice

	for _, e := range w.set {
		es = append(es, e)
	}
	sort.Sort(es)
	return es
}

func (w *Wantlist) Add(k u.Key, priority int) {
	if _, ok := w.set[k]; ok {
		return
	}
	w.set[k] = &Entry{
		Key:      k,
		Priority: priority,
	}
}

func (w *Wantlist) Remove(k u.Key) {
	delete(w.set, k)
}

func (w *Wantlist) Contains(k u.Key) bool {
	_, ok := w.set[k]
	return ok
}

func (w *Wantlist) Entries() []*Entry {
	var es entrySlice

	for _, e := range w.set {
		es = append(es, e)
	}
	sort.Sort(es)
	return es
}

func (w *Wantlist) SortedEntries() []*Entry {
	var es entrySlice

	for _, e := range w.set {
		es = append(es, e)
	}
	sort.Sort(es)
	return es
}
<commit_msg>fix: don't sort the output of Entries()<commit_after>package wantlist

import (
	u "github.com/jbenet/go-ipfs/util"
	"sort"
	"sync"
)

type ThreadSafe struct {
	lk sync.RWMutex
	Wantlist
}

// not threadsafe
type Wantlist struct {
	set map[u.Key]*Entry
}

type Entry struct {
	Key      u.Key
	Priority int
}

type entrySlice []*Entry

func (es entrySlice) Len() int           { return len(es) }
func (es entrySlice) Swap(i, j int)      { es[i], es[j] = es[j], es[i] }
func (es entrySlice) Less(i, j int) bool { return es[i].Priority > es[j].Priority }

func NewThreadSafe() *ThreadSafe {
	return &ThreadSafe{
		Wantlist: *New(),
	}
}

func New() *Wantlist {
	return &Wantlist{
		set: make(map[u.Key]*Entry),
	}
}

func (w *ThreadSafe) Add(k u.Key, priority int) {
	// TODO rm defer for perf
	w.lk.Lock()
	defer w.lk.Unlock()
	w.Wantlist.Add(k, priority)
}

func (w *ThreadSafe) Remove(k u.Key) {
	// TODO rm defer for perf
	w.lk.Lock()
	defer w.lk.Unlock()
	w.Wantlist.Remove(k)
}

func (w *ThreadSafe) Contains(k u.Key) bool {
	// TODO rm defer for perf
	w.lk.RLock()
	defer w.lk.RUnlock()
	return w.Wantlist.Contains(k)
}

func (w *ThreadSafe) Entries() []*Entry {
	w.lk.RLock()
	defer w.lk.RUnlock()
	return w.Wantlist.Entries()
}

func (w *ThreadSafe) SortedEntries() []*Entry {
	w.lk.RLock()
	defer w.lk.RUnlock()
	return w.Wantlist.SortedEntries()
}

func (w *Wantlist) Add(k u.Key, priority int) {
	if _, ok := w.set[k]; ok {
		return
	}
	w.set[k] = &Entry{
		Key:      k,
		Priority: priority,
	}
}

func (w *Wantlist) Remove(k u.Key) {
	delete(w.set, k)
}

func (w *Wantlist) Contains(k u.Key) bool {
	_, ok := w.set[k]
	return ok
}

func (w *Wantlist) Entries() []*Entry {
	var es entrySlice
	for _, e := range w.set {
		es = append(es, e)
	}
	return es
}

func (w *Wantlist) SortedEntries() []*Entry {
	var es entrySlice
	for _, e := range w.set {
		es = append(es, e)
	}
	sort.Sort(es)
	return es
}
<|endoftext|>


<commit_before>package jiracmd

import (
	"fmt"

	"github.com/coryb/figtree"
	"github.com/coryb/oreo"

	"gopkg.in/Netflix-Skunkworks/go-jira.v1"
	"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiracli"
	"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiradata"
	kingpin "gopkg.in/alecthomas/kingpin.v2"
)

type EditOptions struct {
	jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
	jiradata.IssueUpdate  `yaml:",inline" json:",inline" figtree:",inline"`
	jira.SearchOptions    `yaml:",inline" json:",inline" figtree:",inline"`
	Overrides             map[string]string `yaml:"overrides,omitempty" json:"overrides,omitempty"`
	Issue                 string            `yaml:"issue,omitempty" json:"issue,omitempty"`
	Queries               map[string]string `yaml:"queries,omitempty" json:"queries,omitempty"`
}

func CmdEditRegistry() *jiracli.CommandRegistryEntry {
	opts := EditOptions{
		CommonOptions: jiracli.CommonOptions{
			Template: figtree.NewStringOption("edit"),
		},
		Overrides: map[string]string{},
	}

	return &jiracli.CommandRegistryEntry{
		"Edit issue details",
		func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
			jiracli.LoadConfigs(cmd, fig, &opts)
			return CmdEditUsage(cmd, &opts, fig)
		},
		func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
			return CmdEdit(o, globals, &opts)
		},
	}
}

func CmdEditUsage(cmd *kingpin.CmdClause, opts *EditOptions, fig *figtree.FigTree) error {
	jiracli.BrowseUsage(cmd, &opts.CommonOptions)
	jiracli.EditorUsage(cmd, &opts.CommonOptions)
	jiracli.TemplateUsage(cmd, &opts.CommonOptions)
	cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing)
	cmd.Flag("named-query", "The name of a query in the `queries` configuration").Short('n').PreAction(func(ctx *kingpin.ParseContext) error {
		name := jiracli.FlagValue(ctx, "named-query")
		if query, ok := opts.Queries[name]; ok && query != "" {
			var err error
			opts.Query, err = jiracli.ConfigTemplate(fig, query, cmd.FullCommand(), opts)
			return err
		}
		return fmt.Errorf("A valid named-query %q not found in `queries` configuration", name)
	}).String()
	cmd.Flag("query", "Jira Query Language (JQL) expression for the search to edit multiple issues").Short('q').StringVar(&opts.Query)
	cmd.Flag("comment", "Comment message for issue").Short('m').PreAction(func(ctx *kingpin.ParseContext) error {
		opts.Overrides["comment"] = jiracli.FlagValue(ctx, "comment")
		return nil
	}).String()
	cmd.Flag("override", "Set issue property").Short('o').StringMapVar(&opts.Overrides)
	cmd.Arg("ISSUE", "issue id to edit").StringVar(&opts.Issue)
	return nil
}

// Edit will get issue data and send to "edit" template
func CmdEdit(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EditOptions) error {
	type templateInput struct {
		*jiradata.Issue `yaml:",inline"`
		Meta            *jiradata.EditMeta `yaml:"meta" json:"meta"`
		Overrides       map[string]string  `yaml:"overrides" json:"overrides"`
	}
	if opts.Issue != "" {
		issueData, err := jira.GetIssue(o, globals.Endpoint.Value, opts.Issue, nil)
		if err != nil {
			return err
		}
		editMeta, err := jira.GetIssueEditMeta(o, globals.Endpoint.Value, opts.Issue)
		if err != nil {
			return err
		}

		issueUpdate := jiradata.IssueUpdate{}
		input := templateInput{
			Issue:     issueData,
			Meta:      editMeta,
			Overrides: opts.Overrides,
		}
		err = jiracli.EditLoop(&opts.CommonOptions, &input, &issueUpdate, func() error {
			return jira.EditIssue(o, globals.Endpoint.Value, opts.Issue, &issueUpdate)
		})
		if err != nil {
			return err
		}
		if !globals.Quiet.Value {
			fmt.Printf("OK %s %s/browse/%s\n", opts.Issue, globals.Endpoint.Value, opts.Issue)
		}
		if opts.Browse.Value {
			return CmdBrowse(globals, opts.Issue)
		}
	}
	results, err := jira.Search(o, globals.Endpoint.Value, opts)
	if err != nil {
		return err
	}
	for _, issueData := range results.Issues {
		editMeta, err := jira.GetIssueEditMeta(o, globals.Endpoint.Value, issueData.Key)
		if err != nil {
			return err
		}

		issueUpdate := jiradata.IssueUpdate{}
		input := templateInput{
			Issue: issueData,
			Meta:  editMeta,
		}
		err = jiracli.EditLoop(&opts.CommonOptions, &input, &issueUpdate, func() error {
			return jira.EditIssue(o, globals.Endpoint.Value, issueData.Key, &issueUpdate)
		})
		if err != nil {
			return err
		}
		if !globals.Quiet.Value {
			fmt.Printf("OK %s %s/browse/%s\n", issueData.Key, globals.Endpoint.Value, issueData.Key)
		}
		if opts.Browse.Value {
			return CmdBrowse(globals, issueData.Key)
		}
	}
	return nil
}
<commit_msg>fix edit<commit_after>package jiracmd

import (
	"fmt"

	"github.com/coryb/figtree"
	"github.com/coryb/oreo"

	"gopkg.in/Netflix-Skunkworks/go-jira.v1"
	"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiracli"
	"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiradata"
	kingpin "gopkg.in/alecthomas/kingpin.v2"
)

type EditOptions struct {
	jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
	jiradata.IssueUpdate  `yaml:",inline" json:",inline" figtree:",inline"`
	jira.SearchOptions    `yaml:",inline" json:",inline" figtree:",inline"`
	Overrides             map[string]string `yaml:"overrides,omitempty" json:"overrides,omitempty"`
	Issue                 string            `yaml:"issue,omitempty" json:"issue,omitempty"`
	Queries               map[string]string `yaml:"queries,omitempty" json:"queries,omitempty"`
}

func CmdEditRegistry() *jiracli.CommandRegistryEntry {
	opts := EditOptions{
		CommonOptions: jiracli.CommonOptions{
			Template: figtree.NewStringOption("edit"),
		},
		Overrides: map[string]string{},
	}

	return &jiracli.CommandRegistryEntry{
		"Edit issue details",
		func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
			jiracli.LoadConfigs(cmd, fig, &opts)
			return CmdEditUsage(cmd, &opts, fig)
		},
		func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
			return CmdEdit(o, globals, &opts)
		},
	}
}

func CmdEditUsage(cmd *kingpin.CmdClause, opts *EditOptions, fig *figtree.FigTree) error {
	jiracli.BrowseUsage(cmd, &opts.CommonOptions)
	jiracli.EditorUsage(cmd, &opts.CommonOptions)
	jiracli.TemplateUsage(cmd, &opts.CommonOptions)
	cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing)
	cmd.Flag("named-query", "The name of a query in the `queries` configuration").Short('n').PreAction(func(ctx *kingpin.ParseContext) error {
		name := jiracli.FlagValue(ctx, "named-query")
		if query, ok := opts.Queries[name]; ok && query != "" {
			var err error
			opts.Query, err = jiracli.ConfigTemplate(fig, query, cmd.FullCommand(), opts)
			return err
		}
		return fmt.Errorf("A valid named-query %q not found in `queries` configuration", name)
	}).String()
	cmd.Flag("query", "Jira Query Language (JQL) expression for the search to edit multiple issues").Short('q').StringVar(&opts.Query)
	cmd.Flag("comment", "Comment message for issue").Short('m').PreAction(func(ctx *kingpin.ParseContext) error {
		opts.Overrides["comment"] = jiracli.FlagValue(ctx, "comment")
		return nil
	}).String()
	cmd.Flag("override", "Set issue property").Short('o').StringMapVar(&opts.Overrides)
	cmd.Arg("ISSUE", "issue id to edit").StringVar(&opts.Issue)
	return nil
}

// Edit will get issue data and send to "edit" template
func CmdEdit(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EditOptions) error {
	type templateInput struct {
		*jiradata.Issue `yaml:",inline"`
		Meta            *jiradata.EditMeta `yaml:"meta" json:"meta"`
		Overrides       map[string]string  `yaml:"overrides" json:"overrides"`
	}
	if opts.Issue != "" {
		issueData, err := jira.GetIssue(o, globals.Endpoint.Value, opts.Issue, nil)
		if err != nil {
			return err
		}
		editMeta, err := jira.GetIssueEditMeta(o, globals.Endpoint.Value, opts.Issue)
		if err != nil {
			return err
		}

		issueUpdate := jiradata.IssueUpdate{}
		input := templateInput{
			Issue:     issueData,
			Meta:      editMeta,
			Overrides: opts.Overrides,
		}
		err = jiracli.EditLoop(&opts.CommonOptions, &input, &issueUpdate, func() error {
			return jira.EditIssue(o, globals.Endpoint.Value, opts.Issue, &issueUpdate)
		})
		if err != nil {
			return err
		}
		if !globals.Quiet.Value {
			fmt.Printf("OK %s %s/browse/%s\n", opts.Issue, globals.Endpoint.Value, opts.Issue)
		}
		if opts.Browse.Value {
			return CmdBrowse(globals, opts.Issue)
		}
		return nil
	}
	results, err := jira.Search(o, globals.Endpoint.Value, opts)
	if err != nil {
		return err
	}
	for _, issueData := range results.Issues {
		editMeta, err := jira.GetIssueEditMeta(o, globals.Endpoint.Value, issueData.Key)
		if err != nil {
			return err
		}

		issueUpdate := jiradata.IssueUpdate{}
		input := templateInput{
			Issue:     issueData,
			Meta:      editMeta,
			Overrides: opts.Overrides,
		}
		err = jiracli.EditLoop(&opts.CommonOptions, &input, &issueUpdate, func() error {
			return jira.EditIssue(o, globals.Endpoint.Value, issueData.Key, &issueUpdate)
		})
		if err != nil {
			return err
		}
		if !globals.Quiet.Value {
			fmt.Printf("OK %s %s/browse/%s\n", issueData.Key, globals.Endpoint.Value, issueData.Key)
		}
		if opts.Browse.Value {
			return CmdBrowse(globals, issueData.Key)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package jobs

import (
	"fmt"
	"strings"
	"time"

	log "github.com/meifamily/logrus"

	"github.com/meifamily/ptt-alertor/models/ptt/article"
	board "github.com/meifamily/ptt-alertor/models/ptt/board/redis"
	user "github.com/meifamily/ptt-alertor/models/user/redis"
	"github.com/meifamily/ptt-alertor/myutil"
)

const checkBoardDuration = 200 * time.Millisecond
const checkHighBoardDuration = 1 * time.Second

var boardCh = make(chan *board.Board)
var ckerCh = make(chan Checker)

type Checker struct {
	email      string
	line       string
	lineNotify string
	messenger  string
	board      string
	keyword    string
	author     string
	articles   article.Articles
	subType    string
	word       string
}

func (cker Checker) String() string {
	subType := "關鍵字"
	if cker.author != "" {
		subType = "作者"
	}
	return fmt.Sprintf("%s@%s\r\n看板：%s；%s：%s%s", cker.word, cker.board, cker.board, subType, cker.word, cker.articles.String())
}

// Self return Checker itself
func (cker Checker) Self() Checker {
	return cker
}

// Run is main in Job
func (cker Checker) Run() {
	highBoards := highBoards()
	go func(highBoards []*board.Board) {
		for {
			checkBoards(highBoards, checkHighBoardDuration)
		}
	}(highBoards)
	go func() {
		for {
			checkBoards(new(board.Board).All(), checkBoardDuration)
		}
	}()

	for {
		select {
		case bd := <-boardCh:
			checkSubscriber(bd, cker)
		case cker := <-ckerCh:
			ckCh <- cker
		}
	}
}

func highBoards() (highBoards []*board.Board) {
	boardcfg := myutil.Config("board")
	highBoardNames := strings.Split(boardcfg["high"], ",")
	for _, name := range highBoardNames {
		bd := new(board.Board)
		bd.Name = name
		highBoards = append(highBoards, bd)
	}
	return highBoards
}

func checkBoards(bds []*board.Board, duration time.Duration) {
	for _, bd := range bds {
		time.Sleep(duration)
		go checkNewArticle(bd, boardCh)
	}
}

func checkNewArticle(bd *board.Board, boardCh chan *board.Board) {
	bd.WithNewArticles()
	if bd.NewArticles == nil {
		bd.Articles = bd.OnlineArticles
		log.WithField("board", bd.Name).Info("Created Articles")
		bd.Save()
	}
	if len(bd.NewArticles) != 0 {
		bd.Articles = bd.OnlineArticles
		log.WithField("board", bd.Name).Info("Updated Articles")
		err := bd.Save()
		if err == nil {
			boardCh <- bd
		}
	}
}

func checkSubscriber(bd *board.Board, cker Checker) {
	users := new(user.User).All()
	for _, user := range users {
		if user.Enable {
			cker.email = user.Profile.Email
			cker.line = user.Profile.Line
			cker.lineNotify = user.Profile.LineAccessToken
			cker.messenger = user.Profile.Messenger
			go subscribeChecker(user, bd, cker)
		}
	}
}

func subscribeChecker(user *user.User, bd *board.Board, cker Checker) {
	for _, sub := range user.Subscribes {
		if bd.Name == sub.Board {
			cker.board = sub.Board
			for _, keyword := range sub.Keywords {
				go keywordChecker(keyword, bd, cker)
			}
			for _, author := range sub.Authors {
				go authorChecker(author, bd, cker)
			}
		}
	}
}

func keywordChecker(keyword string, bd *board.Board, cker Checker) {
	keywordArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if newAtcl.MatchKeyword(keyword) {
			newAtcl.Author = ""
			keywordArticles = append(keywordArticles, newAtcl)
		}
	}
	if len(keywordArticles) != 0 {
		cker.keyword = keyword
		cker.articles = keywordArticles
		cker.subType = "keyword"
		cker.word = keyword
		ckerCh <- cker
	}
}

func authorChecker(author string, bd *board.Board, cker Checker) {
	authorArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if strings.EqualFold(newAtcl.Author, author) {
			authorArticles = append(authorArticles, newAtcl)
		}
	}
	if len(authorArticles) != 0 {
		cker.author = author
		cker.articles = authorArticles
		cker.subType = "author"
		cker.word = author
		ckerCh <- cker
	}
}
<commit_msg>waiting for all articles updated before entry next round<commit_after>package jobs

import (
	"fmt"
	"strings"
	"sync"
	"time"

	log "github.com/meifamily/logrus"

	"github.com/meifamily/ptt-alertor/models/ptt/article"
	board "github.com/meifamily/ptt-alertor/models/ptt/board/redis"
	user "github.com/meifamily/ptt-alertor/models/user/redis"
	"github.com/meifamily/ptt-alertor/myutil"
)

const checkBoardDuration = 200 * time.Millisecond
const checkHighBoardDuration = 1 * time.Second

var boardCh = make(chan *board.Board)
var ckerCh = make(chan Checker)

type Checker struct {
	email      string
	line       string
	lineNotify string
	messenger  string
	board      string
	keyword    string
	author     string
	articles   article.Articles
	subType    string
	word       string
}

func (cker Checker) String() string {
	subType := "關鍵字"
	if cker.author != "" {
		subType = "作者"
	}
	return fmt.Sprintf("%s@%s\r\n看板：%s；%s：%s%s", cker.word, cker.board, cker.board, subType, cker.word, cker.articles.String())
}

// Self return Checker itself
func (cker Checker) Self() Checker {
	return cker
}

// Run is main in Job
func (cker Checker) Run() {
	highBoards := highBoards()
	var wgHigh sync.WaitGroup
	var wg sync.WaitGroup
	go func(highBoards []*board.Board) {
		for {
			checkBoards(&wgHigh, highBoards, checkHighBoardDuration)
			wgHigh.Wait()
		}
	}(highBoards)
	go func() {
		for {
			checkBoards(&wg, new(board.Board).All(), checkBoardDuration)
			wg.Wait()
		}
	}()

	for {
		select {
		case bd := <-boardCh:
			checkSubscriber(bd, cker)
		case cker := <-ckerCh:
			ckCh <- cker
		}
	}
}

func highBoards() (highBoards []*board.Board) {
	boardcfg := myutil.Config("board")
	highBoardNames := strings.Split(boardcfg["high"], ",")
	for _, name := range highBoardNames {
		bd := new(board.Board)
		bd.Name = name
		highBoards = append(highBoards, bd)
	}
	return highBoards
}

func checkBoards(wg *sync.WaitGroup, bds []*board.Board, duration time.Duration) {
	wg.Add(len(bds))
	for _, bd := range bds {
		time.Sleep(duration)
		go checkNewArticle(wg, bd, boardCh)
	}
}

func checkNewArticle(wg *sync.WaitGroup, bd *board.Board, boardCh chan *board.Board) {
	defer wg.Done()
	bd.WithNewArticles()
	if bd.NewArticles == nil {
		bd.Articles = bd.OnlineArticles
		log.WithField("board", bd.Name).Info("Created Articles")
		bd.Save()
	}
	if len(bd.NewArticles) != 0 {
		bd.Articles = bd.OnlineArticles
		log.WithField("board", bd.Name).Info("Updated Articles")
		err := bd.Save()
		if err == nil {
			boardCh <- bd
		}
	}
}

func checkSubscriber(bd *board.Board, cker Checker) {
	users := new(user.User).All()
	for _, user := range users {
		if user.Enable {
			cker.email = user.Profile.Email
			cker.line = user.Profile.Line
			cker.lineNotify = user.Profile.LineAccessToken
			cker.messenger = user.Profile.Messenger
			go subscribeChecker(user, bd, cker)
		}
	}
}

func subscribeChecker(user *user.User, bd *board.Board, cker Checker) {
	for _, sub := range user.Subscribes {
		if bd.Name == sub.Board {
			cker.board = sub.Board
			for _, keyword := range sub.Keywords {
				go keywordChecker(keyword, bd, cker)
			}
			for _, author := range sub.Authors {
				go authorChecker(author, bd, cker)
			}
		}
	}
}

func keywordChecker(keyword string, bd *board.Board, cker Checker) {
	keywordArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if newAtcl.MatchKeyword(keyword) {
			newAtcl.Author = ""
			keywordArticles = append(keywordArticles, newAtcl)
		}
	}
	if len(keywordArticles) != 0 {
		cker.keyword = keyword
		cker.articles = keywordArticles
		cker.subType = "keyword"
		cker.word = keyword
		ckerCh <- cker
	}
}

func authorChecker(author string, bd *board.Board, cker Checker) {
	authorArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if strings.EqualFold(newAtcl.Author, author) {
			authorArticles = append(authorArticles, newAtcl)
		}
	}
	if len(authorArticles) != 0 {
		cker.author = author
		cker.articles = authorArticles
		cker.subType = "author"
		cker.word = author
		ckerCh <- cker
	}
}
<|endoftext|>


<commit_before>package jobs

import (
	"fmt"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/liam-lai/ptt-alertor/line"
	"github.com/liam-lai/ptt-alertor/mail"
	"github.com/liam-lai/ptt-alertor/messenger"
	"github.com/liam-lai/ptt-alertor/models/ptt/article"
	board "github.com/liam-lai/ptt-alertor/models/ptt/board/redis"
	user "github.com/liam-lai/ptt-alertor/models/user/redis"
)

const checkBoardDuration = 10

type Checker struct {
	email      string
	line       string
	lineNotify string
	messenger  string
	board      string
	keyword    string
	author     string
	articles   article.Articles
}

func (cker Checker) String() string {
	subType := "關鍵字"
	subText := cker.keyword
	if cker.author != "" {
		subType = "作者"
		subText = cker.author
	}
	return fmt.Sprintf("\r\n看版：%s；%s：%s%s", cker.board, subType, subText, cker.articles.String())
}

func (cker Checker) Run() {
	boardCh := make(chan *board.Board)
	go func() {
		for {
			bds := new(board.Board).All()
			for _, bd := range bds {
				go checkNewArticle(bd, boardCh)
			}
			time.Sleep(checkBoardDuration * time.Second)
		}
	}()
	ckerCh := make(chan Checker)

	for {
		select {
		case bd := <-boardCh:
			checkSubscriber(bd, cker, ckerCh)
		case m := <-ckerCh:
			sendMessage(m)
		}
	}
}

func checkNewArticle(bd *board.Board, boardCh chan *board.Board) {
	bd.WithNewArticles()
	if len(bd.NewArticles) != 0 {
		bd.Articles = bd.OnlineArticles()
		log.WithField("board", bd.Name).Info("Updated Articles")
		bd.Save()
		boardCh <- bd
	}
}

func checkSubscriber(bd *board.Board, cker Checker, ckerCh chan Checker) {
	users := new(user.User).All()
	for _, user := range users {
		if user.Enable {
			cker.email = user.Profile.Email
			cker.line = user.Profile.Line
			cker.lineNotify = user.Profile.LineAccessToken
			cker.messenger = user.Profile.Messenger
			log.WithField("user", user.Profile.Account).Info("Checking Subscriber")
			go subscribeChecker(user, bd, cker, ckerCh)
		}
	}
}

func subscribeChecker(user *user.User, bd *board.Board, cker Checker, ckerCh chan Checker) {
	for _, sub := range user.Subscribes {
		if bd.Name == sub.Board {
			cker.board = sub.Board
			for _, keyword := range sub.Keywords {
				go keywordChecker(keyword, bd, cker, ckerCh)
			}
			for _, author := range sub.Authors {
				go authorChecker(author, bd, cker, ckerCh)
			}
		}
	}
}

func keywordChecker(keyword string, bd *board.Board, cker Checker, ckerCh chan Checker) {
	keywordArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if newAtcl.MatchKeyword(keyword) {
			newAtcl.Author = ""
			keywordArticles = append(keywordArticles, newAtcl)
		}
	}
	if len(keywordArticles) != 0 {
		cker.keyword = keyword
		cker.articles = keywordArticles
		ckerCh <- cker
	}
}

func authorChecker(author string, bd *board.Board, cker Checker, ckerCh chan Checker) {
	authorArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if strings.EqualFold(newAtcl.Author, author) {
			authorArticles = append(authorArticles, newAtcl)
		}
	}
	if len(authorArticles) != 0 {
		cker.author = author
		cker.articles = authorArticles
		ckerCh <- cker
	}

}

func sendMessage(cker Checker) {
	var account string
	if cker.email != "" {
		account = cker.email
		sendMail(cker)
	}
	if cker.lineNotify != "" {
		account = cker.line
		sendLineNotify(cker)
	}
	if cker.messenger != "" {
		account = cker.messenger
		sendMessenger(cker)
	}
	log.WithFields(log.Fields{
		"account": account,
		"board":   cker.board,
		"keyword": cker.keyword,
		"author":  cker.author,
	}).Info("Message Sent")
}

func sendMail(cker Checker) {
	m := new(mail.Mail)
	m.Title.BoardName = cker.board
	m.Title.Keyword = cker.keyword
	m.Body.Articles = cker.articles
	m.Receiver = cker.email

	m.Send()
}

func sendLine(cker Checker) {
	line.PushTextMessage(cker.line, cker.String())
}

func sendLineNotify(cker Checker) {
	line.Notify(cker.lineNotify, cker.String())
}

func sendMessenger(cker Checker) {
	m := messenger.New()
	m.SendTextMessage(cker.messenger, cker.String())
}
<commit_msg>update checker log<commit_after>package jobs

import (
	"fmt"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/liam-lai/ptt-alertor/line"
	"github.com/liam-lai/ptt-alertor/mail"
	"github.com/liam-lai/ptt-alertor/messenger"
	"github.com/liam-lai/ptt-alertor/models/ptt/article"
	board "github.com/liam-lai/ptt-alertor/models/ptt/board/redis"
	user "github.com/liam-lai/ptt-alertor/models/user/redis"
)

const checkBoardDuration = 10

type Checker struct {
	email      string
	line       string
	lineNotify string
	messenger  string
	board      string
	keyword    string
	author     string
	articles   article.Articles
}

func (cker Checker) String() string {
	subType := "關鍵字"
	subText := cker.keyword
	if cker.author != "" {
		subType = "作者"
		subText = cker.author
	}
	return fmt.Sprintf("\r\n看版：%s；%s：%s%s", cker.board, subType, subText, cker.articles.String())
}

func (cker Checker) Run() {
	boardCh := make(chan *board.Board)
	go func() {
		for {
			bds := new(board.Board).All()
			for _, bd := range bds {
				go checkNewArticle(bd, boardCh)
			}
			time.Sleep(checkBoardDuration * time.Second)
		}
	}()
	ckerCh := make(chan Checker)

	for {
		select {
		case bd := <-boardCh:
			checkSubscriber(bd, cker, ckerCh)
		case m := <-ckerCh:
			sendMessage(m)
		}
	}
}

func checkNewArticle(bd *board.Board, boardCh chan *board.Board) {
	bd.WithNewArticles()
	if len(bd.NewArticles) != 0 {
		bd.Articles = bd.OnlineArticles()
		log.WithField("board", bd.Name).Info("Updated Articles")
		bd.Save()
		boardCh <- bd
	}
}

func checkSubscriber(bd *board.Board, cker Checker, ckerCh chan Checker) {
	users := new(user.User).All()
	for _, user := range users {
		if user.Enable {
			cker.email = user.Profile.Email
			cker.line = user.Profile.Line
			cker.lineNotify = user.Profile.LineAccessToken
			cker.messenger = user.Profile.Messenger
			go subscribeChecker(user, bd, cker, ckerCh)
		}
	}
}

func subscribeChecker(user *user.User, bd *board.Board, cker Checker, ckerCh chan Checker) {
	for _, sub := range user.Subscribes {
		if bd.Name == sub.Board {
			cker.board = sub.Board
			for _, keyword := range sub.Keywords {
				go keywordChecker(keyword, bd, cker, ckerCh)
			}
			for _, author := range sub.Authors {
				go authorChecker(author, bd, cker, ckerCh)
			}
		}
	}
}

func keywordChecker(keyword string, bd *board.Board, cker Checker, ckerCh chan Checker) {
	keywordArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if newAtcl.MatchKeyword(keyword) {
			newAtcl.Author = ""
			keywordArticles = append(keywordArticles, newAtcl)
		}
	}
	if len(keywordArticles) != 0 {
		cker.keyword = keyword
		cker.articles = keywordArticles
		ckerCh <- cker
	}
}

func authorChecker(author string, bd *board.Board, cker Checker, ckerCh chan Checker) {
	authorArticles := make(article.Articles, 0)
	for _, newAtcl := range bd.NewArticles {
		if strings.EqualFold(newAtcl.Author, author) {
			authorArticles = append(authorArticles, newAtcl)
		}
	}
	if len(authorArticles) != 0 {
		cker.author = author
		cker.articles = authorArticles
		ckerCh <- cker
	}

}

func sendMessage(cker Checker) {
	var account string
	var platform string
	subType := "keyword"
	word := cker.keyword

	if cker.author != "" {
		subType = "author"
		word = cker.author
	}

	if cker.email != "" {
		account = cker.email
		platform = "mail"
		sendMail(cker)
	}
	if cker.lineNotify != "" {
		account = cker.line
		platform = "line"
		sendLineNotify(cker)
	}
	if cker.messenger != "" {
		account = cker.messenger
		platform = "messenger"
		sendMessenger(cker)
	}
	log.WithFields(log.Fields{
		"account":  account,
		"platform": platform,
		"board":    cker.board,
		"type":     subType,
		"word":     word,
	}).Info("Message Sent")
}

func sendMail(cker Checker) {
	m := new(mail.Mail)
	m.Title.BoardName = cker.board
	m.Title.Keyword = cker.keyword
	m.Body.Articles = cker.articles
	m.Receiver = cker.email

	m.Send()
}

func sendLine(cker Checker) {
	line.PushTextMessage(cker.line, cker.String())
}

func sendLineNotify(cker Checker) {
	line.Notify(cker.lineNotify, cker.String())
}

func sendMessenger(cker Checker) {
	m := messenger.New()
	m.SendTextMessage(cker.messenger, cker.String())
}
<|endoftext|>


<commit_before>package jobs

import (
	"fmt"

	"time"

	"github.com/liam-lai/ptt-alertor/mail"
	"github.com/liam-lai/ptt-alertor/ptt/article"
	"github.com/liam-lai/ptt-alertor/ptt/board"
	"github.com/liam-lai/ptt-alertor/user"
)

type Message struct {
	email    string
	board    string
	keyword  string
	articles []article.Article
}

func (msg Message) Run() {
	bs := new(board.Boards).All().WithNewArticles(true)
	users := new(user.Users).All()
	msgCh := make(chan Message)
	for _, user := range users {
		msg.email = user.Profile.Email
		go userChecker(user, bs, msg, msgCh)
	}

	for {
		select {
		case m := <-msgCh:
			sendMail(m)
		case <-time.After(time.Second * 3):
			fmt.Println("message done")
			return
		}
	}
}

func userChecker(user *user.User, bds board.Boards, msg Message, msgCh chan Message) {
	for _, bd := range bds {
		go subscribeChecker(user, bd, msg, msgCh)
	}
}

func subscribeChecker(user *user.User, bd *board.Board, msg Message, msgCh chan Message) {
	for _, sub := range user.Subscribes {
		if bd.Name == sub.Board {
			msg.board = sub.Board
			for _, keyword := range sub.Keywords {
				go keywordChecker(keyword, bd, msg, msgCh)
			}
		}
	}
}

func keywordChecker(keyword string, bd *board.Board, msg Message, msgCh chan Message) {
	keywordArticles := bd.NewArticles.ContainKeyword(keyword)
	if len(keywordArticles) != 0 {
		msg.keyword = keyword
		msg.articles = keywordArticles
		msgCh <- msg
	}
}

func sendMail(msg Message) {
	m := new(mail.Mail)
	m.Title.BoardName = msg.board
	m.Title.Keyword = msg.keyword
	m.Body.Articles = msg.articles
	m.Receiver = msg.email

	m.Send()
}
<commit_msg>Rename abbr. bs to bds<commit_after>package jobs

import (
	"fmt"

	"time"

	"github.com/liam-lai/ptt-alertor/mail"
	"github.com/liam-lai/ptt-alertor/ptt/article"
	"github.com/liam-lai/ptt-alertor/ptt/board"
	"github.com/liam-lai/ptt-alertor/user"
)

type Message struct {
	email    string
	board    string
	keyword  string
	articles []article.Article
}

func (msg Message) Run() {
	bds := new(board.Boards).All().WithNewArticles(true)
	users := new(user.Users).All()
	msgCh := make(chan Message)
	for _, user := range users {
		msg.email = user.Profile.Email
		go userChecker(user, bds, msg, msgCh)
	}

	for {
		select {
		case m := <-msgCh:
			sendMail(m)
		case <-time.After(time.Second * 3):
			fmt.Println("message done")
			return
		}
	}
}

func userChecker(user *user.User, bds board.Boards, msg Message, msgCh chan Message) {
	for _, bd := range bds {
		go subscribeChecker(user, bd, msg, msgCh)
	}
}

func subscribeChecker(user *user.User, bd *board.Board, msg Message, msgCh chan Message) {
	for _, sub := range user.Subscribes {
		if bd.Name == sub.Board {
			msg.board = sub.Board
			for _, keyword := range sub.Keywords {
				go keywordChecker(keyword, bd, msg, msgCh)
			}
		}
	}
}

func keywordChecker(keyword string, bd *board.Board, msg Message, msgCh chan Message) {
	keywordArticles := bd.NewArticles.ContainKeyword(keyword)
	if len(keywordArticles) != 0 {
		msg.keyword = keyword
		msg.articles = keywordArticles
		msgCh <- msg
	}
}

func sendMail(msg Message) {
	m := new(mail.Mail)
	m.Title.BoardName = msg.board
	m.Title.Keyword = msg.keyword
	m.Body.Articles = msg.articles
	m.Receiver = msg.email

	m.Send()
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	"io"
	"os"
	"time"

	"strings"

	"github.com/ncodes/cocoon/core/orderer"
	proto "github.com/ncodes/cocoon/core/runtime/golang/proto"
	"github.com/ncodes/cocoon/core/types"
	logging "github.com/op/go-logging"
	cmap "github.com/orcaman/concurrent-map"
	context "golang.org/x/net/context"
	"google.golang.org/grpc"
)

var log = logging.MustGetLogger("connector.client")

// txChannels holds the channels to send transaction responses to
var txRespChannels = cmap.New()

// Client represents a cocoon code GRPC client
// that interacts with a cocoon code.
type Client struct {
	ccodeAddr        string
	stub             proto.StubClient
	conCtx           context.Context
	conCancel        context.CancelFunc
	orderDiscoTicker *time.Ticker
	ordererAddrs     []string
	stream           proto.Stub_TransactClient
	cocoonID         string
}

// NewClient creates a new cocoon code client
func NewClient() *Client {
	return &Client{}
}

// SetCocoonID sets the cocoon id
func (c *Client) SetCocoonID(id string) {
	c.cocoonID = id
}

// SetCocoonCodeAddr sets the cocoon code bind address
func (c *Client) SetCocoonCodeAddr(ccAddr string) {
	c.ccodeAddr = ccAddr
}

// getCCAddr returns the cocoon code bind address.
// For development, if DEV_COCOON_ADDR is set, it connects to it.
func (c *Client) getCCAddr() string {
	if devCCodeAddr := os.Getenv("DEV_COCOON_ADDR"); len(devCCodeAddr) > 0 {
		return devCCodeAddr
	}
	return c.ccodeAddr
}

// Close the stream and cancel connections
func (c *Client) Close() {
	if c.stream != nil {
		c.stream.CloseSend()
	}
	if c.conCancel != nil {
		c.conCancel()
	}
}

// GetStream returns the grpc stream connected to the grpc cocoon code server
func (c *Client) GetStream() proto.Stub_TransactClient {
	return c.stream
}

// Connect connects to a cocoon code server
// running on a known port
func (c *Client) Connect() error {

	log.Info("Starting cocoon code client")

	// start a ticker to continously discover orderer addreses
	go func() {
		c.orderDiscoTicker = time.NewTicker(60 * time.Second)
		for _ = range c.orderDiscoTicker.C {
			var ordererAddrs []string
			ordererAddrs, err := orderer.DiscoverOrderers()
			if err != nil {
				log.Error(err.Error())
				continue
			}
			c.ordererAddrs = ordererAddrs
		}
	}()

	var ordererAddrs []string
	ordererAddrs, err := orderer.DiscoverOrderers()
	if err != nil {
		return err
	}
	c.ordererAddrs = ordererAddrs

	if len(c.ordererAddrs) > 0 {
		log.Infof("Orderer address list updated. Contains %d orderer address(es)", len(c.ordererAddrs))
	} else {
		log.Warning("No orderer address was found. We won't be able to reach the orderer. ")
	}

	log.Info(">>>> ", c.getCCAddr())

	conn, err := grpc.Dial(c.getCCAddr(), grpc.WithInsecure())
	if err != nil {
		return fmt.Errorf("failed to connect to cocoon code server. %s", err)
	}
	defer conn.Close()

	log.Debugf("Now connected to cocoon code at port=%s", strings.Split(c.getCCAddr(), ":")[1])

	c.stub = proto.NewStubClient(conn)

	if err = c.Do(conn); err != nil {
		log.Info("Connect() ends with error", err)
		return err
	}
	log.Info("Connect() ends")

	return nil
}

// Do starts a request loop that continously asks the
// server for transactions. When it receives a transaction,
// it processes it and returns the result.
func (c *Client) Do(conn *grpc.ClientConn) error {

	var err error

	// create a context so we have complete controll of the connection
	c.conCtx, c.conCancel = context.WithCancel(context.Background())

	// connect to the cocoon code
	c.stream, err = c.stub.Transact(c.conCtx)
	if err != nil {
		return fmt.Errorf("failed to start transaction stream with cocoon code. %s", err)
	}

	for {

		in, err := c.stream.Recv()
		if err == io.EOF {
			return fmt.Errorf("connection with cocoon code has ended")
		}
		if err != nil {
			if strings.Contains(err.Error(), "context canceled") {
				log.Info("Connection to cocoon code closed")
				return nil
			}
			return fmt.Errorf("failed to read message from cocoon code. %s", err)
		}

		switch in.Invoke {
		case true:
			go func() {
				log.Debugf("New invoke transaction (%s) from cocoon code", in.GetId())
				if err = c.handleInvokeTransaction(in); err != nil {
					log.Error(err.Error())
					c.stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		case false:
			log.Debugf("New response transaction (%s) from cocoon code", in.GetId())
			go func() {
				if err = c.handleRespTransaction(in); err != nil {
					log.Error(err.Error())
					c.stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		}
	}
}

// handleInvokeTransaction processes invoke transaction requests
func (c *Client) handleInvokeTransaction(tx *proto.Tx) error {
	switch tx.GetName() {
	case types.TxCreateLedger:
		return c.createLedger(tx)
	case types.TxPut:
		return c.put(tx)
	case types.TxGetLedger:
		return c.getLedger(tx)
	case types.TxGet:
		return c.get(tx, false)
	case types.TxGetByID:
		return c.get(tx, true)
	case types.TxGetBlockByID:
		return c.getBlock(tx)
	case types.TxRangeGet:
		return c.getRange(tx)
	default:
		return c.stream.Send(&proto.Tx{
			Id:       tx.GetId(),
			Response: true,
			Status:   500,
			Body:     []byte(fmt.Sprintf("unsupported transaction name (%s)", tx.GetName())),
		})
	}
}

// handleRespTransaction passes the transaction to a response
// channel with a matching transaction id and deletes the channel afterwards.
func (c *Client) handleRespTransaction(tx *proto.Tx) error {
	if !txRespChannels.Has(tx.GetId()) {
		return fmt.Errorf("response transaction (%s) does not have a corresponding response channel", tx.GetId())
	}

	txRespCh, _ := txRespChannels.Get(tx.GetId())
	txRespCh.(chan *proto.Tx) <- tx
	txRespChannels.Remove(tx.GetId())
	return nil
}

// SendTx sends a transaction to the cocoon code
// and saves the response channel. The response channel will
// be passed a response when it is available in the Transact loop.
func (c *Client) SendTx(tx *proto.Tx, respCh chan *proto.Tx) error {
	txRespChannels.Set(tx.GetId(), respCh)
	if err := c.stream.Send(tx); err != nil {
		txRespChannels.Remove(tx.GetId())
		return err
	}
	log.Debugf("Successfully sent transaction (%s) to cocoon code", tx.GetId())
	return nil
}
<commit_msg>debug<commit_after>package client

import (
	"fmt"
	"io"
	"os"
	"time"

	"strings"

	"github.com/ncodes/cocoon/core/orderer"
	proto "github.com/ncodes/cocoon/core/runtime/golang/proto"
	"github.com/ncodes/cocoon/core/types"
	logging "github.com/op/go-logging"
	cmap "github.com/orcaman/concurrent-map"
	context "golang.org/x/net/context"
	"google.golang.org/grpc"
)

var log = logging.MustGetLogger("connector.client")

// txChannels holds the channels to send transaction responses to
var txRespChannels = cmap.New()

// Client represents a cocoon code GRPC client
// that interacts with a cocoon code.
type Client struct {
	ccodeAddr        string
	stub             proto.StubClient
	conCtx           context.Context
	conCancel        context.CancelFunc
	orderDiscoTicker *time.Ticker
	ordererAddrs     []string
	stream           proto.Stub_TransactClient
	cocoonID         string
}

// NewClient creates a new cocoon code client
func NewClient() *Client {
	return &Client{}
}

// SetCocoonID sets the cocoon id
func (c *Client) SetCocoonID(id string) {
	c.cocoonID = id
}

// SetCocoonCodeAddr sets the cocoon code bind address
func (c *Client) SetCocoonCodeAddr(ccAddr string) {
	c.ccodeAddr = ccAddr
}

// getCCAddr returns the cocoon code bind address.
// For development, if DEV_COCOON_ADDR is set, it connects to it.
func (c *Client) getCCAddr() string {
	if devCCodeAddr := os.Getenv("DEV_COCOON_ADDR"); len(devCCodeAddr) > 0 {
		return devCCodeAddr
	}
	return c.ccodeAddr
}

// Close the stream and cancel connections
func (c *Client) Close() {
	if c.stream != nil {
		c.stream.CloseSend()
	}
	if c.conCancel != nil {
		c.conCancel()
	}
}

// GetStream returns the grpc stream connected to the grpc cocoon code server
func (c *Client) GetStream() proto.Stub_TransactClient {
	return c.stream
}

// Connect connects to a cocoon code server
// running on a known port
func (c *Client) Connect() error {

	log.Info("Starting cocoon code client")

	// start a ticker to continously discover orderer addreses
	go func() {
		c.orderDiscoTicker = time.NewTicker(60 * time.Second)
		for _ = range c.orderDiscoTicker.C {
			var ordererAddrs []string
			ordererAddrs, err := orderer.DiscoverOrderers()
			if err != nil {
				log.Error(err.Error())
				continue
			}
			c.ordererAddrs = ordererAddrs
		}
	}()

	var ordererAddrs []string
	ordererAddrs, err := orderer.DiscoverOrderers()
	if err != nil {
		return err
	}
	c.ordererAddrs = ordererAddrs

	if len(c.ordererAddrs) > 0 {
		log.Infof("Orderer address list updated. Contains %d orderer address(es)", len(c.ordererAddrs))
	} else {
		log.Warning("No orderer address was found. We won't be able to reach the orderer. ")
	}

	log.Info(">>>> ", c.getCCAddr())

	conn, err := grpc.Dial(c.getCCAddr(), grpc.WithInsecure())
	if err != nil {
		return fmt.Errorf("failed to connect to cocoon code server. %s", err)
	}
	defer conn.Close()

	log.Debugf("Now connected to cocoon code at port=%s", strings.Split(c.getCCAddr(), ":")[1])

	c.stub = proto.NewStubClient(conn)

	time.Sleep(5 * time.Second)
	if err = c.Do(conn); err != nil {
		return err
	}

	return nil
}

// Do starts a request loop that continously asks the
// server for transactions. When it receives a transaction,
// it processes it and returns the result.
func (c *Client) Do(conn *grpc.ClientConn) error {

	var err error

	// create a context so we have complete controll of the connection
	c.conCtx, c.conCancel = context.WithCancel(context.Background())

	// connect to the cocoon code
	c.stream, err = c.stub.Transact(c.conCtx)
	if err != nil {
		return fmt.Errorf("failed to start transaction stream with cocoon code. %s", err)
	}

	for {

		in, err := c.stream.Recv()
		if err == io.EOF {
			return fmt.Errorf("connection with cocoon code has ended")
		}
		if err != nil {
			if strings.Contains(err.Error(), "context canceled") {
				log.Info("Connection to cocoon code closed")
				return nil
			}
			return fmt.Errorf("failed to read message from cocoon code. %s", err)
		}

		switch in.Invoke {
		case true:
			go func() {
				log.Debugf("New invoke transaction (%s) from cocoon code", in.GetId())
				if err = c.handleInvokeTransaction(in); err != nil {
					log.Error(err.Error())
					c.stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		case false:
			log.Debugf("New response transaction (%s) from cocoon code", in.GetId())
			go func() {
				if err = c.handleRespTransaction(in); err != nil {
					log.Error(err.Error())
					c.stream.Send(&proto.Tx{
						Response: true,
						Id:       in.GetId(),
						Status:   500,
						Body:     []byte(err.Error()),
					})
				}
			}()
		}
	}
}

// handleInvokeTransaction processes invoke transaction requests
func (c *Client) handleInvokeTransaction(tx *proto.Tx) error {
	switch tx.GetName() {
	case types.TxCreateLedger:
		return c.createLedger(tx)
	case types.TxPut:
		return c.put(tx)
	case types.TxGetLedger:
		return c.getLedger(tx)
	case types.TxGet:
		return c.get(tx, false)
	case types.TxGetByID:
		return c.get(tx, true)
	case types.TxGetBlockByID:
		return c.getBlock(tx)
	case types.TxRangeGet:
		return c.getRange(tx)
	default:
		return c.stream.Send(&proto.Tx{
			Id:       tx.GetId(),
			Response: true,
			Status:   500,
			Body:     []byte(fmt.Sprintf("unsupported transaction name (%s)", tx.GetName())),
		})
	}
}

// handleRespTransaction passes the transaction to a response
// channel with a matching transaction id and deletes the channel afterwards.
func (c *Client) handleRespTransaction(tx *proto.Tx) error {
	if !txRespChannels.Has(tx.GetId()) {
		return fmt.Errorf("response transaction (%s) does not have a corresponding response channel", tx.GetId())
	}

	txRespCh, _ := txRespChannels.Get(tx.GetId())
	txRespCh.(chan *proto.Tx) <- tx
	txRespChannels.Remove(tx.GetId())
	return nil
}

// SendTx sends a transaction to the cocoon code
// and saves the response channel. The response channel will
// be passed a response when it is available in the Transact loop.
func (c *Client) SendTx(tx *proto.Tx, respCh chan *proto.Tx) error {
	txRespChannels.Set(tx.GetId(), respCh)
	if err := c.stream.Send(tx); err != nil {
		txRespChannels.Remove(tx.GetId())
		return err
	}
	log.Debugf("Successfully sent transaction (%s) to cocoon code", tx.GetId())
	return nil
}
<|endoftext|>


<commit_before>package maas

import (
	"bytes"
	. "launchpad.net/gocheck"
	"launchpad.net/gomaasapi"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/testing"
)

type EnvironSuite struct {
	ProviderSuite
}

var _ = Suite(new(EnvironSuite))

// getTestConfig creates a customized sample MAAS provider configuration.
func getTestConfig(name, server, oauth, secret string) *config.Config {
	ecfg, err := newConfig(map[string]interface{}{
		"name":         name,
		"maas-server":  server,
		"maas-oauth":   oauth,
		"admin-secret": secret,
	})
	if err != nil {
		panic(err)
	}
	return ecfg.Config
}

// makeEnviron creates a functional maasEnviron for a test.  Its configuration
// is a bit arbitrary and none of the test code's business.
func (suite *EnvironSuite) makeEnviron() *maasEnviron {
	config, err := config.New(map[string]interface{}{
		"name":            suite.environ.Name(),
		"type":            "maas",
		"admin-secret":    "local-secret",
		"authorized-keys": "foo",
		"ca-cert":         testing.CACert,
		"ca-private-key":  testing.CAKey,
		"maas-oauth":      "a:b:c",
		"maas-server":     suite.testMAASObject.URL().String(),
	})
	if err != nil {
		panic(err)
	}
	env, err := NewEnviron(config)
	if err != nil {
		panic(err)
	}
	return env
}

func (EnvironSuite) TestSetConfigUpdatesConfig(c *C) {
	cfg := getTestConfig("test env", "http://maas2.example.com", "a:b:c", "secret")
	env, err := NewEnviron(cfg)
	c.Check(err, IsNil)
	c.Check(env.name, Equals, "test env")

	anotherName := "another name"
	anotherServer := "http://maas.example.com"
	anotherOauth := "c:d:e"
	anotherSecret := "secret2"
	cfg2 := getTestConfig(anotherName, anotherServer, anotherOauth, anotherSecret)
	errSetConfig := env.SetConfig(cfg2)
	c.Check(errSetConfig, IsNil)
	c.Check(env.name, Equals, anotherName)
	authClient, _ := gomaasapi.NewAuthenticatedClient(anotherServer, anotherOauth)
	maas := gomaasapi.NewMAAS(*authClient)
	MAASServer := env.maasClientUnlocked
	c.Check(MAASServer, DeepEquals, maas)
}

func (EnvironSuite) TestNewEnvironSetsConfig(c *C) {
	name := "test env"
	cfg := getTestConfig(name, "http://maas.example.com", "a:b:c", "secret")

	env, err := NewEnviron(cfg)

	c.Check(err, IsNil)
	c.Check(env.name, Equals, name)
}

func (suite *EnvironSuite) TestInstancesReturnsInstances(c *C) {
	input := `{"system_id": "test"}`
	node := suite.testMAASObject.TestServer.NewNode(input)
	resourceURI, _ := node.GetField("resource_uri")
	instanceIds := []state.InstanceId{state.InstanceId(resourceURI)}

	instances, err := suite.environ.Instances(instanceIds)

	c.Check(err, IsNil)
	c.Check(len(instances), Equals, 1)
	c.Check(string(instances[0].Id()), Equals, resourceURI)
}

func (suite *EnvironSuite) TestInstancesReturnsNilIfEmptyParameter(c *C) {
	// Instances returns nil if the given parameter is empty.
	input := `{"system_id": "test"}`
	suite.testMAASObject.TestServer.NewNode(input)
	instances, err := suite.environ.Instances([]state.InstanceId{})

	c.Check(err, IsNil)
	c.Check(instances, IsNil)
}

func (suite *EnvironSuite) TestInstancesReturnsNilIfNilParameter(c *C) {
	// Instances returns nil if the given parameter is nil.
	input := `{"system_id": "test"}`
	suite.testMAASObject.TestServer.NewNode(input)
	instances, err := suite.environ.Instances(nil)

	c.Check(err, IsNil)
	c.Check(instances, IsNil)
}

func (suite *EnvironSuite) TestAllInstancesReturnsAllInstances(c *C) {
	input := `{"system_id": "test"}`
	node := suite.testMAASObject.TestServer.NewNode(input)
	resourceURI, _ := node.GetField("resource_uri")

	instances, err := suite.environ.AllInstances()

	c.Check(err, IsNil)
	c.Check(len(instances), Equals, 1)
	c.Check(string(instances[0].Id()), Equals, resourceURI)
}

func (suite *EnvironSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *C) {
	instances, err := suite.environ.AllInstances()

	c.Check(err, IsNil)
	c.Check(len(instances), Equals, 0)
}

func (suite *EnvironSuite) TestInstancesReturnsErrorIfPartialInstances(c *C) {
	input1 := `{"system_id": "test"}`
	node1 := suite.testMAASObject.TestServer.NewNode(input1)
	resourceURI1, _ := node1.GetField("resource_uri")
	input2 := `{"system_id": "test2"}`
	suite.testMAASObject.TestServer.NewNode(input2)
	instanceId1 := state.InstanceId(resourceURI1)
	instanceId2 := state.InstanceId("unknown systemID")
	instanceIds := []state.InstanceId{instanceId1, instanceId2}

	instances, err := suite.environ.Instances(instanceIds)

	c.Check(err, Equals, environs.ErrPartialInstances)
	c.Check(len(instances), Equals, 1)
	c.Check(string(instances[0].Id()), Equals, resourceURI1)
}

func (suite *EnvironSuite) TestStorageReturnsStorage(c *C) {
	env := suite.makeEnviron()
	storage := env.Storage()
	c.Check(storage, NotNil)
	// The Storage object is really a maasStorage.
	specificStorage := storage.(*maasStorage)
	// Its environment pointer refers back to its environment.
	c.Check(specificStorage.environUnlocked, Equals, env)
}

func (suite *EnvironSuite) TestPublicStorageReturnsEmptyStorage(c *C) {
	env := suite.makeEnviron()
	storage := env.PublicStorage()
	c.Assert(storage, NotNil)
	c.Check(storage, Equals, environs.EmptyStorage)
}

func (suite *EnvironSuite) TestStartInstanceStartsInstance(c *C) {
	input := `{"system_id": "test"}`
	node := suite.testMAASObject.TestServer.NewNode(input)
	resourceURI, _ := node.GetField("resource_uri")

	instance, err := suite.environ.StartInstance(resourceURI, nil, nil, nil)

	c.Check(err, IsNil)
	c.Check(string(instance.Id()), Equals, resourceURI)
	operations := suite.testMAASObject.TestServer.NodeOperations()
	actions, found := operations["test"]
	c.Check(found, Equals, true)
	c.Check(actions, DeepEquals, []string{"start"})
}

func (suite *EnvironSuite) getInstance(systemId string) *maasInstance {
	input := `{"system_id": "` + systemId + `"}`
	node := suite.testMAASObject.TestServer.NewNode(input)
	return &maasInstance{&node, suite.environ}
}

func (suite *EnvironSuite) TestStopInstancesReturnsIfParameterEmpty(c *C) {
	suite.getInstance("test1")

	err := suite.environ.StopInstances([]environs.Instance{})
	c.Check(err, IsNil)
	operations := suite.testMAASObject.TestServer.NodeOperations()
	c.Check(operations, DeepEquals, map[string][]string{})
}

func (suite *EnvironSuite) TestStopInstancesStopsInstances(c *C) {
	instance1 := suite.getInstance("test1")
	instance2 := suite.getInstance("test2")
	suite.getInstance("test3")
	instances := []environs.Instance{instance1, instance2}

	err := suite.environ.StopInstances(instances)

	c.Check(err, IsNil)
	operations := suite.testMAASObject.TestServer.NodeOperations()
	expectedOperations := map[string][]string{"test1": {"stop"}, "test2": {"stop"}}
	c.Check(operations, DeepEquals, expectedOperations)
}

func (suite *EnvironSuite) TestQuiesceStateFileIsHappyWithoutStateFile(c *C) {
	err := suite.makeEnviron().quiesceStateFile()
	c.Check(err, IsNil)
}

func (suite *EnvironSuite) TestQuiesceStateFileFailsWithStateFile(c *C) {
	env := suite.makeEnviron()
	err := env.saveState(&bootstrapState{})
	c.Assert(err, IsNil)

	err = env.quiesceStateFile()

	c.Check(err, Not(IsNil))
}

func (suite *EnvironSuite) TestQuiesceStateFileFailsOnBrokenStateFile(c *C) {
	const content = "@#$(*&Y%!"
	reader := bytes.NewReader([]byte(content))
	env := suite.makeEnviron()
	err := env.Storage().Put(stateFile, reader, int64(len(content)))
	c.Assert(err, IsNil)

	err = env.quiesceStateFile()

	c.Check(err, Not(IsNil))
}

func (suite *EnvironSuite) TestBootstrap(c *C) {
	env := suite.makeEnviron()

	err := env.Bootstrap(true, []byte{}, []byte{})
	// TODO: Get this to succeed.
	unused(err)
	// c.Assert(err, IsNil)

	// TODO: Verify a simile of success.
}
<commit_msg>Start a more realistic env before testing StartInstance.<commit_after>package maas

import (
	"bytes"
	. "launchpad.net/gocheck"
	"launchpad.net/gomaasapi"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/testing"
)

type EnvironSuite struct {
	ProviderSuite
}

var _ = Suite(new(EnvironSuite))

// getTestConfig creates a customized sample MAAS provider configuration.
func getTestConfig(name, server, oauth, secret string) *config.Config {
	ecfg, err := newConfig(map[string]interface{}{
		"name":         name,
		"maas-server":  server,
		"maas-oauth":   oauth,
		"admin-secret": secret,
	})
	if err != nil {
		panic(err)
	}
	return ecfg.Config
}

// makeEnviron creates a functional maasEnviron for a test.  Its configuration
// is a bit arbitrary and none of the test code's business.
func (suite *EnvironSuite) makeEnviron() *maasEnviron {
	config, err := config.New(map[string]interface{}{
		"name":            suite.environ.Name(),
		"type":            "maas",
		"admin-secret":    "local-secret",
		"authorized-keys": "foo",
		"ca-cert":         testing.CACert,
		"ca-private-key":  testing.CAKey,
		"maas-oauth":      "a:b:c",
		"maas-server":     suite.testMAASObject.URL().String(),
	})
	if err != nil {
		panic(err)
	}
	env, err := NewEnviron(config)
	if err != nil {
		panic(err)
	}
	return env
}

func (EnvironSuite) TestSetConfigUpdatesConfig(c *C) {
	cfg := getTestConfig("test env", "http://maas2.example.com", "a:b:c", "secret")
	env, err := NewEnviron(cfg)
	c.Check(err, IsNil)
	c.Check(env.name, Equals, "test env")

	anotherName := "another name"
	anotherServer := "http://maas.example.com"
	anotherOauth := "c:d:e"
	anotherSecret := "secret2"
	cfg2 := getTestConfig(anotherName, anotherServer, anotherOauth, anotherSecret)
	errSetConfig := env.SetConfig(cfg2)
	c.Check(errSetConfig, IsNil)
	c.Check(env.name, Equals, anotherName)
	authClient, _ := gomaasapi.NewAuthenticatedClient(anotherServer, anotherOauth)
	maas := gomaasapi.NewMAAS(*authClient)
	MAASServer := env.maasClientUnlocked
	c.Check(MAASServer, DeepEquals, maas)
}

func (EnvironSuite) TestNewEnvironSetsConfig(c *C) {
	name := "test env"
	cfg := getTestConfig(name, "http://maas.example.com", "a:b:c", "secret")

	env, err := NewEnviron(cfg)

	c.Check(err, IsNil)
	c.Check(env.name, Equals, name)
}

func (suite *EnvironSuite) TestInstancesReturnsInstances(c *C) {
	input := `{"system_id": "test"}`
	node := suite.testMAASObject.TestServer.NewNode(input)
	resourceURI, _ := node.GetField("resource_uri")
	instanceIds := []state.InstanceId{state.InstanceId(resourceURI)}

	instances, err := suite.environ.Instances(instanceIds)

	c.Check(err, IsNil)
	c.Check(len(instances), Equals, 1)
	c.Check(string(instances[0].Id()), Equals, resourceURI)
}

func (suite *EnvironSuite) TestInstancesReturnsNilIfEmptyParameter(c *C) {
	// Instances returns nil if the given parameter is empty.
	input := `{"system_id": "test"}`
	suite.testMAASObject.TestServer.NewNode(input)
	instances, err := suite.environ.Instances([]state.InstanceId{})

	c.Check(err, IsNil)
	c.Check(instances, IsNil)
}

func (suite *EnvironSuite) TestInstancesReturnsNilIfNilParameter(c *C) {
	// Instances returns nil if the given parameter is nil.
	input := `{"system_id": "test"}`
	suite.testMAASObject.TestServer.NewNode(input)
	instances, err := suite.environ.Instances(nil)

	c.Check(err, IsNil)
	c.Check(instances, IsNil)
}

func (suite *EnvironSuite) TestAllInstancesReturnsAllInstances(c *C) {
	input := `{"system_id": "test"}`
	node := suite.testMAASObject.TestServer.NewNode(input)
	resourceURI, _ := node.GetField("resource_uri")

	instances, err := suite.environ.AllInstances()

	c.Check(err, IsNil)
	c.Check(len(instances), Equals, 1)
	c.Check(string(instances[0].Id()), Equals, resourceURI)
}

func (suite *EnvironSuite) TestAllInstancesReturnsEmptySliceIfNoInstance(c *C) {
	instances, err := suite.environ.AllInstances()

	c.Check(err, IsNil)
	c.Check(len(instances), Equals, 0)
}

func (suite *EnvironSuite) TestInstancesReturnsErrorIfPartialInstances(c *C) {
	input1 := `{"system_id": "test"}`
	node1 := suite.testMAASObject.TestServer.NewNode(input1)
	resourceURI1, _ := node1.GetField("resource_uri")
	input2 := `{"system_id": "test2"}`
	suite.testMAASObject.TestServer.NewNode(input2)
	instanceId1 := state.InstanceId(resourceURI1)
	instanceId2 := state.InstanceId("unknown systemID")
	instanceIds := []state.InstanceId{instanceId1, instanceId2}

	instances, err := suite.environ.Instances(instanceIds)

	c.Check(err, Equals, environs.ErrPartialInstances)
	c.Check(len(instances), Equals, 1)
	c.Check(string(instances[0].Id()), Equals, resourceURI1)
}

func (suite *EnvironSuite) TestStorageReturnsStorage(c *C) {
	env := suite.makeEnviron()
	storage := env.Storage()
	c.Check(storage, NotNil)
	// The Storage object is really a maasStorage.
	specificStorage := storage.(*maasStorage)
	// Its environment pointer refers back to its environment.
	c.Check(specificStorage.environUnlocked, Equals, env)
}

func (suite *EnvironSuite) TestPublicStorageReturnsEmptyStorage(c *C) {
	env := suite.makeEnviron()
	storage := env.PublicStorage()
	c.Assert(storage, NotNil)
	c.Check(storage, Equals, environs.EmptyStorage)
}

func (suite *EnvironSuite) TestStartInstanceStartsInstance(c *C) {
	input := `{"system_id": "test"}`
	node := suite.testMAASObject.TestServer.NewNode(input)
	resourceURI, err := node.GetField("resource_uri")
	c.Assert(err, IsNil)
	env := suite.makeEnviron()

	instance, err := env.StartInstance(resourceURI, nil, nil, nil)

	c.Check(err, IsNil)
	c.Check(string(instance.Id()), Equals, resourceURI)
	operations := suite.testMAASObject.TestServer.NodeOperations()
	actions, found := operations["test"]
	c.Check(found, Equals, true)
	c.Check(actions, DeepEquals, []string{"start"})
}

func (suite *EnvironSuite) getInstance(systemId string) *maasInstance {
	input := `{"system_id": "` + systemId + `"}`
	node := suite.testMAASObject.TestServer.NewNode(input)
	return &maasInstance{&node, suite.environ}
}

func (suite *EnvironSuite) TestStopInstancesReturnsIfParameterEmpty(c *C) {
	suite.getInstance("test1")

	err := suite.environ.StopInstances([]environs.Instance{})
	c.Check(err, IsNil)
	operations := suite.testMAASObject.TestServer.NodeOperations()
	c.Check(operations, DeepEquals, map[string][]string{})
}

func (suite *EnvironSuite) TestStopInstancesStopsInstances(c *C) {
	instance1 := suite.getInstance("test1")
	instance2 := suite.getInstance("test2")
	suite.getInstance("test3")
	instances := []environs.Instance{instance1, instance2}

	err := suite.environ.StopInstances(instances)

	c.Check(err, IsNil)
	operations := suite.testMAASObject.TestServer.NodeOperations()
	expectedOperations := map[string][]string{"test1": {"stop"}, "test2": {"stop"}}
	c.Check(operations, DeepEquals, expectedOperations)
}

func (suite *EnvironSuite) TestQuiesceStateFileIsHappyWithoutStateFile(c *C) {
	err := suite.makeEnviron().quiesceStateFile()
	c.Check(err, IsNil)
}

func (suite *EnvironSuite) TestQuiesceStateFileFailsWithStateFile(c *C) {
	env := suite.makeEnviron()
	err := env.saveState(&bootstrapState{})
	c.Assert(err, IsNil)

	err = env.quiesceStateFile()

	c.Check(err, Not(IsNil))
}

func (suite *EnvironSuite) TestQuiesceStateFileFailsOnBrokenStateFile(c *C) {
	const content = "@#$(*&Y%!"
	reader := bytes.NewReader([]byte(content))
	env := suite.makeEnviron()
	err := env.Storage().Put(stateFile, reader, int64(len(content)))
	c.Assert(err, IsNil)

	err = env.quiesceStateFile()

	c.Check(err, Not(IsNil))
}

func (suite *EnvironSuite) TestBootstrap(c *C) {
	env := suite.makeEnviron()

	err := env.Bootstrap(true, []byte{}, []byte{})
	// TODO: Get this to succeed.
	unused(err)
	// c.Assert(err, IsNil)

	// TODO: Verify a simile of success.
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"log"
	"net/http"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

func DownloadHandler(cfg Config, w http.ResponseWriter, r *http.Request, db *sql.DB) {

	whitelist := []string{"image/gif", "image/png", "image/jpeg", "image/bmp", "application/pdf", "text/plain"}
	/* Security checks */
	request_id_t := strings.TrimSpace(r.FormValue("id"))
	if len(request_id_t) == 0 {
		http.Redirect(w, r, cfg.Main.Domain, 303)
		log.Printf("[LOG] No request passed\n")
		return
	}

	/* Remove leading slash if passed via arg */
	if request_id_t[0] == '/' {
		request_id_t = request_id_t[1:]
	}

	request_commands := strings.Split(request_id_t, "/")
	request_id := strings.Split(request_commands[0], ".")[0]
	if len(request_id) == 0 {
		http.Redirect(w, r, cfg.Main.Domain, 303)
		log.Printf("[LOG] Invalid format for download request\n")
		return
	}

	var filename string
	var filesize int64
	var diskid string
	var uploaded int64

	err := db.QueryRow("SELECT name, size, diskid, uploaded FROM files WHERE fileid = ?", request_id).Scan(&filename, &filesize, &diskid, &uploaded)
	if err != nil {
		http.Redirect(w, r, cfg.Main.Domain, 303)
		log.Printf("[LOG] Requested file does not exist\n")
		return
	}

	out, err := exec.Command("file", "-biL", cfg.Directories.Upload+diskid).Output()

	// Tells IE not to try to guess the content type
	w.Header().Set("X-Content-Type-Options", "nosniff")

	// Tells the browser to expect a file
	w.Header().Set("Content-Description", "File Transfer")

	// Enables some XSS protection in IE
	w.Header().Set("X-XSS-Protection", "1; mode=block")

	if StringInArray(strings.Split(string(out), ";")[0], whitelist) && err == nil {
		w.Header().Set("Content-Disposition", "inline; filename=\""+string(filename)+"\"")
	} else {
		w.Header().Set("Content-Disposition", "attachment; filename=\""+string(filename)+"\"")
	}

	// watfile links currently don't expire, so tell the browser
	w.Header().Set("Expires", "Sun, 17 Jan 2038 19:14:07 GMT")
	w.Header().Set("Cache-Control", "max-age=31536000, must-revalidate")

	w.Header().Set("Last-Modified", time.Unix(uploaded, 0).Format("Mon, 2 Jan 2006 15:04:05 MST"))
	w.Header().Set("Content-Length", strconv.FormatInt(filesize, 10))
	if cfg.Toggles.UseXaccel {
		w.Header().Set("X-Accel-Redirect", "/protected/"+diskid)
		w.Header().Set("Content-Transfer-Encoding", "binary")
	} else {
		http.ServeFile(w, r, cfg.Directories.Upload+diskid)
	}
	log.Printf("[LOG] File %s dowloaded\n", request_id)
	return
}
<commit_msg>More testing<commit_after>package main

import (
	"database/sql"
	"log"
	"net/http"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

func DownloadHandler(cfg Config, w http.ResponseWriter, r *http.Request, db *sql.DB) {

	whitelist := []string{"image/gif", "image/png", "image/jpeg", "image/bmp", "application/pdf", "text/plain"}
	/* Security checks */
	request_id_t := strings.TrimSpace(r.FormValue("id"))
	if len(request_id_t) == 0 {
		http.Redirect(w, r, cfg.Main.Domain, 303)
		log.Printf("[LOG] No request passed\n")
		return
	}

	/* Remove leading slash if passed via arg */
	if request_id_t[0] == '/' {
		request_id_t = request_id_t[1:]
	}

	request_commands := strings.Split(request_id_t, "/")
	request_id := strings.Split(request_commands[0], ".")[0]
	if len(request_id) == 0 {
		http.Redirect(w, r, cfg.Main.Domain, 303)
		log.Printf("[LOG] Invalid format for download request\n")
		return
	}

	var filename string
	var filesize int64
	var diskid string
	var uploaded int64

	err := db.QueryRow("SELECT name, size, diskid, uploaded FROM files WHERE fileid = ?", request_id).Scan(&filename, &filesize, &diskid, &uploaded)
	if err != nil {
		http.Redirect(w, r, cfg.Main.Domain, 303)
		log.Printf("[LOG] Requested filewat does not exist\n")
		return
	}

	out, err := exec.Command("file", "-biL", cfg.Directories.Upload+diskid).Output()

	// Tells IE not to try to guess the content type
	w.Header().Set("X-Content-Type-Options", "nosniff")

	// Tells the browser to expect a file
	w.Header().Set("Content-Description", "File Transfer")

	// Enables some XSS protection in IE
	w.Header().Set("X-XSS-Protection", "1; mode=block")

	if StringInArray(strings.Split(string(out), ";")[0], whitelist) && err == nil {
		w.Header().Set("Content-Disposition", "inline; filename=\""+string(filename)+"\"")
	} else {
		w.Header().Set("Content-Disposition", "attachment; filename=\""+string(filename)+"\"")
	}

	// watfile links currently don't expire, so tell the browser
	w.Header().Set("Expires", "Sun, 17 Jan 2038 19:14:07 GMT")
	w.Header().Set("Cache-Control", "max-age=31536000, must-revalidate")

	w.Header().Set("Last-Modified", time.Unix(uploaded, 0).Format("Mon, 2 Jan 2006 15:04:05 MST"))
	w.Header().Set("Content-Length", strconv.FormatInt(filesize, 10))
	if cfg.Toggles.UseXaccel {
		w.Header().Set("X-Accel-Redirect", "/protected/"+diskid)
		w.Header().Set("Content-Transfer-Encoding", "binary")
	} else {
		http.ServeFile(w, r, cfg.Directories.Upload+diskid)
	}
	log.Printf("[LOG] File %s dowloaded\n", request_id)
	return
}
<|endoftext|>