<commit_before>// Copyright 2018 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package pca9685

import (
	"time"

	"periph.io/x/periph/conn/gpio"
	"periph.io/x/periph/conn/i2c"
	"periph.io/x/periph/conn/physic"
)

// I2CAddr i2c default address.
const I2CAddr uint16 = 0x40

// PCA9685 Commands
const (
	mode1      byte = 0x00
	mode2      byte = 0x01
	subAdr1    byte = 0x02
	subAdr2    byte = 0x03
	subAdr3    byte = 0x04
	prescale   byte = 0xFE
	led0OnL    byte = 0x06
	led0OnH    byte = 0x07
	led0OffL   byte = 0x08
	led0OffH   byte = 0x09
	allLedOnL  byte = 0xFA
	allLedOnH  byte = 0xFB
	allLedOffL byte = 0xFC
	allLedOffH byte = 0xFD

	// Bits
	restart byte = 0x80
	sleep   byte = 0x10
	allCall byte = 0x01
	invrt   byte = 0x10
	outDrv  byte = 0x04
)

// Dev is a handler to pca9685 controller
type Dev struct {
	dev *i2c.Dev
}

// NewI2C returns a Dev object that communicates over I2C.
//
// To use on the default address, pca9685.I2CAddr must be passed as argument.
func NewI2C(bus i2c.Bus, address uint16) (*Dev, error) {
	dev := &Dev{
		dev: &i2c.Dev{Bus: bus, Addr: address},
	}
	err := dev.init()
	if err != nil {
		return nil, err
	}

	return dev, nil
}

func (d *Dev) init() error {
	if err := d.SetAllPwm(0, 0); err != nil {
		return err
	}

	if _, err := d.dev.Write([]byte{mode2, outDrv}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{mode1, allCall}); err != nil {
		return err
	}

	time.Sleep(100 * time.Millisecond)

	modeRead := [1]byte{}
	if err := d.dev.Tx([]byte{mode1}, modeRead[:]); err != nil {
		return err
	}

	mode := modeRead[0] & ^sleep
	if _, err := d.dev.Write([]byte{mode1, mode}); err != nil {
		return err
	}

	time.Sleep(5 * time.Millisecond)

	return d.SetPwmFreq(50 * physic.Hertz)
}

// SetPwmFreq set the pwm frequency
func (d *Dev) SetPwmFreq(freqHz physic.Frequency) error {
	p := (25*physic.MegaHertz/4096 + freqHz/2) / freqHz

	modeRead := [1]byte{}
	if err := d.dev.Tx([]byte{mode1}, modeRead[:]); err != nil {
		return err
	}

	oldmode := modeRead[0]
	if _, err := d.dev.Write([]byte{mode1, byte((oldmode & 0x7F) | 0x10)}); err != nil { // go to sleep;
		return err
	}
	if _, err := d.dev.Write([]byte{prescale, byte(p)}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{mode1, oldmode}); err != nil {
		return err
	}

	time.Sleep(100 * time.Millisecond)

	_, err := d.dev.Write([]byte{mode1, (byte)(oldmode | 0x80)})
	return err
}

// SetAllPwm set a pwm value for all outputs
func (d *Dev) SetAllPwm(on, off gpio.Duty) error {
	if _, err := d.dev.Write([]byte{allLedOnL, byte(on)}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{allLedOnH, byte(on >> 8)}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{allLedOffL, byte(off)}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{allLedOffH, byte(off >> 8)}); err != nil {
		return err
	}
	return nil
}

// SetPwm set a pwm value for given pca9685 channel
func (d *Dev) SetPwm(channel int, on, off gpio.Duty) error {
	if _, err := d.dev.Write([]byte{led0OnL + byte(4*channel), byte(on)}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{led0OnH + byte(4*channel), byte(on >> 8)}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{led0OffL + byte(4*channel), byte(off)}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{led0OffH + byte(4*channel), byte(off >> 8)}); err != nil {
		return err
	}
	return nil
}
<commit_msg>pca9685: cleanup (#414)<commit_after>// Copyright 2018 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package pca9685

import (
	"time"

	"periph.io/x/periph/conn/gpio"
	"periph.io/x/periph/conn/i2c"
	"periph.io/x/periph/conn/physic"
)

// I2CAddr i2c default address.
const I2CAddr uint16 = 0x40

// PCA9685 registers.
const (
	mode1    byte = 0x00
	mode2    byte = 0x01
	prescale byte = 0xFE
	// Each channel has two 12-bit registers (on & off time).
	led0OnL   byte = 0x06 // Start address for setting channel 0.
	allLedOnL byte = 0xFA // Start address for setting all channels.
)

// Mode register 1, mode1.
const (
	restart byte = 0x80
	ai      byte = 0x20 // Auto-increment register after each read and write.
	sleep   byte = 0x10
	allCall byte = 0x01
)

// Mode register 2, mode2.
const (
	invrt  byte = 0x10
	outDrv byte = 0x04
)

// Dev is a handler to pca9685 controller.
type Dev struct {
	dev *i2c.Dev
}

// NewI2C returns a Dev object that communicates over I2C.
//
// To use on the default address, pca9685.I2CAddr must be passed as argument.
func NewI2C(bus i2c.Bus, address uint16) (*Dev, error) {
	dev := &Dev{
		dev: &i2c.Dev{Bus: bus, Addr: address},
	}
	err := dev.init()
	if err != nil {
		return nil, err
	}

	return dev, nil
}

func (d *Dev) init() error {
	if err := d.SetAllPwm(0, 0); err != nil {
		return err
	}

	if _, err := d.dev.Write([]byte{mode2, outDrv}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{mode1, allCall}); err != nil {
		return err
	}

	time.Sleep(100 * time.Millisecond)

	modeRead := [1]byte{}
	if err := d.dev.Tx([]byte{mode1}, modeRead[:]); err != nil {
		return err
	}

	mode := (modeRead[0] & ^sleep) | ai
	if _, err := d.dev.Write([]byte{mode1, mode}); err != nil {
		return err
	}

	time.Sleep(5 * time.Millisecond)

	return d.SetPwmFreq(50 * physic.Hertz)
}

// SetPwmFreq set the PWM frequency.
func (d *Dev) SetPwmFreq(freqHz physic.Frequency) error {
	p := (25*physic.MegaHertz/4096 + freqHz/2) / freqHz

	modeRead := [1]byte{}
	if err := d.dev.Tx([]byte{mode1}, modeRead[:]); err != nil {
		return err
	}

	oldmode := modeRead[0]
	if _, err := d.dev.Write([]byte{mode1, (oldmode & ^restart) | sleep}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{prescale, byte(p)}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{mode1, oldmode}); err != nil {
		return err
	}

	time.Sleep(100 * time.Millisecond)

	_, err := d.dev.Write([]byte{mode1, oldmode | restart})
	return err
}

// setPWM writes a PWM value in a specific register.
func (d *Dev) setPWM(register uint8, on, off gpio.Duty) error {
	// Chained writes are possible due to auto-increment.
	_, err := d.dev.Write([]byte{
		register,
		byte(on),
		byte(on >> 8),
		byte(off),
		byte(off >> 8),
	})
	return err
}

// SetAllPwm set a PWM value for all outputs.
func (d *Dev) SetAllPwm(on, off gpio.Duty) error {
	return d.setPWM(allLedOnL, on, off)
}

// SetPwm set a PWM value for a given PCA9685 channel.
func (d *Dev) SetPwm(channel int, on, off gpio.Duty) error {
	return d.setPWM(led0OnL+byte(4*channel), on, off)
}
<|endoftext|>


<commit_before>// Copyright 2016 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package wallet

import (
	"encoding/hex"
	"errors"
	"fmt"
	"regexp"

	"github.com/FactomProject/btcutil/base58"
	"github.com/FactomProject/factom"
	"github.com/FactomProject/factomd/common/factoid"
//	"github.com/FactomProject/factomd/common/interfaces"
	"github.com/FactomProject/factomd/common/primitives"
)

var (
	ErrTXExists      = errors.New("wallet: Transaction name already exists")
	ErrTXNotExists   = errors.New("wallet: Transaction name was not found")
	ErrTXInvalidName = errors.New("wallet: Transaction name is not valid")
)

func (w *Wallet) NewTransaction(name string) error {
	if _, exist := w.transactions[name]; exist {
		return ErrTXExists
	}

	// check that the transaction name is valid
	if name == "" {
		return ErrTXInvalidName
	}
	if len(name) > 32 {
		return ErrTXInvalidName
	}
	if match, err := regexp.MatchString("[^a-zA-Z0-9_-]", name); err != nil {
		return err
	} else if match {
		return ErrTXInvalidName
	}

	t := new(factoid.Transaction)
//	t.SetTimestamp(*interfaces.NewTimestampNow())
	w.transactions[name] = t
	return nil
}

func (w *Wallet) DeleteTransaction(name string) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	delete(w.transactions, name)
	return nil
}

func (w *Wallet) AddInput(name, address string, amount uint64) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	a, err := w.GetFCTAddress(address)
	if err != nil {
		return err
	}
	adr := factoid.NewAddress(a.RCDHash())

	// First look if this is really an update
	for _, input := range trans.GetInputs() {
		if input.GetAddress().IsSameAs(adr) {
			input.SetAmount(amount)
			return nil
		}
	}

	// Add our new input
	trans.AddInput(adr, amount)
	trans.AddRCD(factoid.NewRCD_1(a.PubBytes()))

	return nil
}

func (w *Wallet) AddOutput(name, address string, amount uint64) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	if !factom.IsValidAddress(address) {
		return errors.New("Invalid Address")
	}

	adr := factoid.NewAddress(base58.Decode(address)[2:34])

	trans.AddOutput(adr, amount)

	return nil
}

func (w *Wallet) AddECOutput(name, address string, amount uint64) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	if !factom.IsValidAddress(address) {
		return errors.New("Invalid Address")
	}

	adr := factoid.NewAddress(base58.Decode(address)[2:34])

	trans.AddECOutput(adr, amount)

	return nil
}

func (w *Wallet) AddFee(name, address string, rate uint64) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	{
		ins, err := trans.TotalInputs()
		if err != nil {
			return err
		}
		outs, err := trans.TotalOutputs()
		if err != nil {
			return err
		}
		ecs, err := trans.TotalECs()
		if err != nil {
			return err
		}

		if ins != outs+ecs {
			return fmt.Errorf("Inputs and outputs don't add up")
		}
	}

	transfee, err := trans.CalculateFee(rate)
	if err != nil {
		return err
	}

	a, err := w.GetFCTAddress(address)
	if err != nil {
		return err
	}
	adr := factoid.NewAddress(a.RCDHash())

	for _, input := range trans.GetInputs() {
		if input.GetAddress().IsSameAs(adr) {
			amt, err := factoid.ValidateAmounts(input.GetAmount(), transfee)
			if err != nil {
				return err
			}
			input.SetAmount(amt)
			return nil
		}
	}
	return fmt.Errorf("%s is not an input to the transaction.", address)
}

func (w *Wallet) SubFee(name, address string, rate uint64) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	if !factom.IsValidAddress(address) {
		return errors.New("Invalid Address")
	}

	{
		ins, err := trans.TotalInputs()
		if err != nil {
			return err
		}
		outs, err := trans.TotalOutputs()
		if err != nil {
			return err
		}
		ecs, err := trans.TotalECs()
		if err != nil {
			return err
		}

		if ins != outs+ecs {
			return fmt.Errorf("Inputs and outputs don't add up")
		}
	}

	transfee, err := trans.CalculateFee(rate)
	if err != nil {
		return err
	}

	adr := factoid.NewAddress(base58.Decode(address)[2:34])

	for _, output := range trans.GetOutputs() {
		if output.GetAddress().IsSameAs(adr) {
			output.SetAmount(output.GetAmount() - transfee)
			return nil
		}
	}
	return fmt.Errorf("%s is not an output to the transaction.", address)
}

func (w *Wallet) SignTransaction(name string) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	data, err := trans.MarshalBinarySig()
	if err != nil {
		return err
	}

	for i, rcd := range trans.GetRCDs() {
		a, err := rcd.GetAddress()
		if err != nil {
			return err
		}

		f, err := w.GetFCTAddress(primitives.ConvertFctAddressToUserStr(a))
		if err != nil {
			return err
		}
		sig := factoid.NewSingleSignatureBlock(f.SecBytes(), data)
		trans.SetSignatureBlock(i, sig)
	}

	return nil
}

func (w *Wallet) GetTransactions() map[string]*factoid.Transaction {
	return w.transactions
}

func (w *Wallet) ComposeTransaction(name string) (*factom.JSON2Request, error) {
	if _, exists := w.transactions[name]; !exists {
		return nil, ErrTXNotExists
	}
	trans := w.transactions[name]

	type txreq struct {
		Transaction string `json:"transaction"`
	}

	param := new(txreq)
	if p, err := trans.MarshalBinary(); err != nil {
		return nil, err
	} else {
		param.Transaction = hex.EncodeToString(p)
	}

	req := factom.NewJSON2Request("factoid-submit", apiCounter(), param)

	return req, nil
}
<commit_msg>wallet now makes valid transactions<commit_after>// Copyright 2016 Factom Foundation
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package wallet

import (
	"encoding/hex"
	"errors"
	"fmt"
	"regexp"

	"github.com/FactomProject/btcutil/base58"
	"github.com/FactomProject/factom"
	"github.com/FactomProject/factomd/common/factoid"
	"github.com/FactomProject/factomd/common/interfaces"
	"github.com/FactomProject/factomd/common/primitives"
)

var (
	ErrTXExists      = errors.New("wallet: Transaction name already exists")
	ErrTXNotExists   = errors.New("wallet: Transaction name was not found")
	ErrTXInvalidName = errors.New("wallet: Transaction name is not valid")
)

func (w *Wallet) NewTransaction(name string) error {
	if _, exist := w.transactions[name]; exist {
		return ErrTXExists
	}

	// check that the transaction name is valid
	if name == "" {
		return ErrTXInvalidName
	}
	if len(name) > 32 {
		return ErrTXInvalidName
	}
	if match, err := regexp.MatchString("[^a-zA-Z0-9_-]", name); err != nil {
		return err
	} else if match {
		return ErrTXInvalidName
	}

	t := new(factoid.Transaction)
	t.SetTimestamp(*interfaces.NewTimestampNow())
	w.transactions[name] = t
	return nil
}

func (w *Wallet) DeleteTransaction(name string) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	delete(w.transactions, name)
	return nil
}

func (w *Wallet) AddInput(name, address string, amount uint64) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	a, err := w.GetFCTAddress(address)
	if err != nil {
		return err
	}
	adr := factoid.NewAddress(a.RCDHash())

	// First look if this is really an update
	for _, input := range trans.GetInputs() {
		if input.GetAddress().IsSameAs(adr) {
			input.SetAmount(amount)
			return nil
		}
	}

	// Add our new input
	trans.AddInput(adr, amount)
	trans.AddRCD(factoid.NewRCD_1(a.PubBytes()))

	return nil
}

func (w *Wallet) AddOutput(name, address string, amount uint64) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	if !factom.IsValidAddress(address) {
		return errors.New("Invalid Address")
	}

	adr := factoid.NewAddress(base58.Decode(address)[2:34])

	trans.AddOutput(adr, amount)

	return nil
}

func (w *Wallet) AddECOutput(name, address string, amount uint64) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	if !factom.IsValidAddress(address) {
		return errors.New("Invalid Address")
	}

	adr := factoid.NewAddress(base58.Decode(address)[2:34])

	trans.AddECOutput(adr, amount)

	return nil
}

func (w *Wallet) AddFee(name, address string, rate uint64) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	{
		ins, err := trans.TotalInputs()
		if err != nil {
			return err
		}
		outs, err := trans.TotalOutputs()
		if err != nil {
			return err
		}
		ecs, err := trans.TotalECs()
		if err != nil {
			return err
		}

		if ins != outs+ecs {
			return fmt.Errorf("Inputs and outputs don't add up")
		}
	}

	transfee, err := trans.CalculateFee(rate)
	if err != nil {
		return err
	}

	a, err := w.GetFCTAddress(address)
	if err != nil {
		return err
	}
	adr := factoid.NewAddress(a.RCDHash())

	for _, input := range trans.GetInputs() {
		if input.GetAddress().IsSameAs(adr) {
			amt, err := factoid.ValidateAmounts(input.GetAmount(), transfee)
			if err != nil {
				return err
			}
			input.SetAmount(amt)
			return nil
		}
	}
	return fmt.Errorf("%s is not an input to the transaction.", address)
}

func (w *Wallet) SubFee(name, address string, rate uint64) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	if !factom.IsValidAddress(address) {
		return errors.New("Invalid Address")
	}

	{
		ins, err := trans.TotalInputs()
		if err != nil {
			return err
		}
		outs, err := trans.TotalOutputs()
		if err != nil {
			return err
		}
		ecs, err := trans.TotalECs()
		if err != nil {
			return err
		}

		if ins != outs+ecs {
			return fmt.Errorf("Inputs and outputs don't add up")
		}
	}

	transfee, err := trans.CalculateFee(rate)
	if err != nil {
		return err
	}

	adr := factoid.NewAddress(base58.Decode(address)[2:34])

	for _, output := range trans.GetOutputs() {
		if output.GetAddress().IsSameAs(adr) {
			output.SetAmount(output.GetAmount() - transfee)
			return nil
		}
	}
	return fmt.Errorf("%s is not an output to the transaction.", address)
}

func (w *Wallet) SignTransaction(name string) error {
	if _, exists := w.transactions[name]; !exists {
		return ErrTXNotExists
	}
	trans := w.transactions[name]

	data, err := trans.MarshalBinarySig()
	if err != nil {
		return err
	}

	for i, rcd := range trans.GetRCDs() {
		a, err := rcd.GetAddress()
		if err != nil {
			return err
		}

		f, err := w.GetFCTAddress(primitives.ConvertFctAddressToUserStr(a))
		if err != nil {
			return err
		}
		sig := factoid.NewSingleSignatureBlock(f.SecBytes(), data)
		trans.SetSignatureBlock(i, sig)
	}

	return nil
}

func (w *Wallet) GetTransactions() map[string]*factoid.Transaction {
	return w.transactions
}

func (w *Wallet) ComposeTransaction(name string) (*factom.JSON2Request, error) {
	if _, exists := w.transactions[name]; !exists {
		return nil, ErrTXNotExists
	}
	trans := w.transactions[name]

	type txreq struct {
		Transaction string `json:"transaction"`
	}

	param := new(txreq)
	if p, err := trans.MarshalBinary(); err != nil {
		return nil, err
	} else {
		param.Transaction = hex.EncodeToString(p)
	}

	req := factom.NewJSON2Request("factoid-submit", apiCounter(), param)

	return req, nil
}
<|endoftext|>


<commit_before>// +build integration

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 tests

import (
	"testing"
	"github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/common"
	"github.com/stretchr/testify/assert"
	"os"
	"fmt"
)

var projectPath = "/src/github.com/apache/incubator-openwhisk-wskdeploy/tests/apps/owbp-cloudant-trigger/runtimes/"

func TestCloudantTriggerNode(t *testing.T) {
	manifestPath   := os.Getenv("GOPATH") + projectPath + "node/manifest.yaml"
	deploymentPath := ""
	os.Setenv("CLOUDANT_DATABASE", "testdb")
	wskprops := common.GetWskpropsFromEnvVars(common.BLUEMIX_APIHOST, common.BLUEMIX_NAMESPACE, common.BLUEMIX_AUTH)
	err := common.ValidateWskprops(wskprops)
	if err != nil {
		fmt.Println(err.Error())
		fmt.Println("Wsk properties are not properly configured, so tests are skipped.")
	} else {
		wskdeploy := common.NewWskdeploy()
		_, err := wskdeploy.DeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to deploy the manifest file.")
		_, err = wskdeploy.UndeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to undeploy the manifest file.")
	}
}

func TestCloudantTriggerPhp(t *testing.T) {
	manifestPath   := os.Getenv("GOPATH") + projectPath + "php/manifest.yaml"
	deploymentPath := ""
	os.Setenv("CLOUDANT_DATABASE", "testdb")
	wskprops := common.GetWskpropsFromEnvVars(common.BLUEMIX_APIHOST, common.BLUEMIX_NAMESPACE, common.BLUEMIX_AUTH)
	err := common.ValidateWskprops(wskprops)
	if err != nil {
		fmt.Println(err.Error())
		fmt.Println("Wsk properties are not properly configured, so tests are skipped.")
	} else {
		wskdeploy := common.NewWskdeploy()
		_, err := wskdeploy.DeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to deploy the manifest file.")
		_, err = wskdeploy.UndeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to undeploy the manifest file.")
	}
}

func TestCloudantTriggerPython(t *testing.T) {
	manifestPath   := os.Getenv("GOPATH") + projectPath + "python/manifest.yaml"
	deploymentPath := ""
	os.Setenv("CLOUDANT_DATABASE", "testdb")
	wskprops := common.GetWskpropsFromEnvVars(common.BLUEMIX_APIHOST, common.BLUEMIX_NAMESPACE, common.BLUEMIX_AUTH)
	err := common.ValidateWskprops(wskprops)
	if err != nil {
		fmt.Println(err.Error())
		fmt.Println("Wsk properties are not properly configured, so tests are skipped.")
	} else {
		wskdeploy := common.NewWskdeploy()
		_, err := wskdeploy.DeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to deploy the manifest file.")
		_, err = wskdeploy.UndeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to undeploy the manifest file.")
	}
}

func TestCloudantTriggerSwift(t *testing.T) {
	manifestPath   := os.Getenv("GOPATH") + projectPath + "swift/manifest.yaml"
	deploymentPath := ""
	os.Setenv("CLOUDANT_DATABASE", "testdb")
	wskprops := common.GetWskpropsFromEnvVars(common.BLUEMIX_APIHOST, common.BLUEMIX_NAMESPACE, common.BLUEMIX_AUTH)
	err := common.ValidateWskprops(wskprops)
	if err != nil {
		fmt.Println(err.Error())
		fmt.Println("Wsk properties are not properly configured, so tests are skipped.")
	} else {
		wskdeploy := common.NewWskdeploy()
		_, err := wskdeploy.DeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to deploy the manifest file.")
		_, err = wskdeploy.UndeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to undeploy the manifest file.")
	}
}

<commit_msg>disable integration test (#600)<commit_after>// +build skip_integration

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 tests

import (
	"testing"
	"github.com/apache/incubator-openwhisk-wskdeploy/tests/src/integration/common"
	"github.com/stretchr/testify/assert"
	"os"
	"fmt"
)

var projectPath = "/src/github.com/apache/incubator-openwhisk-wskdeploy/tests/apps/owbp-cloudant-trigger/runtimes/"

func TestCloudantTriggerNode(t *testing.T) {
	manifestPath   := os.Getenv("GOPATH") + projectPath + "node/manifest.yaml"
	deploymentPath := ""
	os.Setenv("CLOUDANT_DATABASE", "testdb")
	wskprops := common.GetWskpropsFromEnvVars(common.BLUEMIX_APIHOST, common.BLUEMIX_NAMESPACE, common.BLUEMIX_AUTH)
	err := common.ValidateWskprops(wskprops)
	if err != nil {
		fmt.Println(err.Error())
		fmt.Println("Wsk properties are not properly configured, so tests are skipped.")
	} else {
		wskdeploy := common.NewWskdeploy()
		_, err := wskdeploy.DeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to deploy the manifest file.")
		_, err = wskdeploy.UndeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to undeploy the manifest file.")
	}
}

func TestCloudantTriggerPhp(t *testing.T) {
	manifestPath   := os.Getenv("GOPATH") + projectPath + "php/manifest.yaml"
	deploymentPath := ""
	os.Setenv("CLOUDANT_DATABASE", "testdb")
	wskprops := common.GetWskpropsFromEnvVars(common.BLUEMIX_APIHOST, common.BLUEMIX_NAMESPACE, common.BLUEMIX_AUTH)
	err := common.ValidateWskprops(wskprops)
	if err != nil {
		fmt.Println(err.Error())
		fmt.Println("Wsk properties are not properly configured, so tests are skipped.")
	} else {
		wskdeploy := common.NewWskdeploy()
		_, err := wskdeploy.DeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to deploy the manifest file.")
		_, err = wskdeploy.UndeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to undeploy the manifest file.")
	}
}

func TestCloudantTriggerPython(t *testing.T) {
	manifestPath   := os.Getenv("GOPATH") + projectPath + "python/manifest.yaml"
	deploymentPath := ""
	os.Setenv("CLOUDANT_DATABASE", "testdb")
	wskprops := common.GetWskpropsFromEnvVars(common.BLUEMIX_APIHOST, common.BLUEMIX_NAMESPACE, common.BLUEMIX_AUTH)
	err := common.ValidateWskprops(wskprops)
	if err != nil {
		fmt.Println(err.Error())
		fmt.Println("Wsk properties are not properly configured, so tests are skipped.")
	} else {
		wskdeploy := common.NewWskdeploy()
		_, err := wskdeploy.DeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to deploy the manifest file.")
		_, err = wskdeploy.UndeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to undeploy the manifest file.")
	}
}

func TestCloudantTriggerSwift(t *testing.T) {
	manifestPath   := os.Getenv("GOPATH") + projectPath + "swift/manifest.yaml"
	deploymentPath := ""
	os.Setenv("CLOUDANT_DATABASE", "testdb")
	wskprops := common.GetWskpropsFromEnvVars(common.BLUEMIX_APIHOST, common.BLUEMIX_NAMESPACE, common.BLUEMIX_AUTH)
	err := common.ValidateWskprops(wskprops)
	if err != nil {
		fmt.Println(err.Error())
		fmt.Println("Wsk properties are not properly configured, so tests are skipped.")
	} else {
		wskdeploy := common.NewWskdeploy()
		_, err := wskdeploy.DeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to deploy the manifest file.")
		_, err = wskdeploy.UndeployWithCredentials(manifestPath, deploymentPath, wskprops)
		assert.Equal(t, nil, err, "Failed to undeploy the manifest file.")
	}
}

<|endoftext|>


<commit_before>// Copyright 2015 trivago GmbH
//
// 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 producer

import (
	"compress/gzip"
	"fmt"
	"github.com/trivago/gollum/core"
	"github.com/trivago/gollum/core/log"
	"github.com/trivago/gollum/shared"
	"hash/fnv"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	fileProducerTimestamp = "2006-01-02_15"
)

// File producer plugin
// Configuration example
//
//   - "producer.File":
//     Enable: true
//     File: "/var/log/gollum.log"
//     BatchSizeMaxKB: 16384
//     BatchSizeByte: 4096
//     BatchTimeoutSec: 2
//     Rotate: false
//     RotateTimeoutMin: 1440
//     RotateSizeMB: 1024
//     RotateAt: "00:00"
//     Compress: true
//
// The file producer writes messages to a file. This producer also allows log
// rotation and compression of the rotated logs.
//
// File contains the path to the log file to write. The wildcard "*" will be
// replaced by the stream name.
// By default this is set to /var/prod/gollum.log.
//
// BatchSizeMaxKB defines the internal file buffer size in KB.
// This producers allocates a front- and a backbuffer of this size. If the
// frontbuffer is filled up completely a flush is triggered and the frontbuffer
// becomes available for writing again. Messages larger than BatchSizeMaxKB are
// rejected.
//
// BatchSizeByte defines the number of bytes to be buffered before they are written
// to disk. By default this is set to 8KB.
//
// BatchTimeoutSec defines the maximum number of seconds to wait after the last
// message arrived before a batch is flushed automatically. By default this is
// set to 5..
//
// Rotate if set to true the logs will rotate after reaching certain thresholds.
//
// RotateTimeoutMin defines a timeout in minutes that will cause the logs to
// rotate. Can be set in parallel with RotateSizeMB. By default this is set to
// 1440 (i.e. 1 Day).
//
// RotateAt defines specific timestamp as in "HH:MM" when the log should be
// rotated. Hours must be given in 24h format. When left empty this setting is
// ignored. By default this setting is disabled.
//
// Compress defines if a rotated logfile is to be gzip compressed or not.
// By default this is set to false.
type File struct {
	core.ProducerBase
	filesByStream    map[core.MessageStreamID]*fileLogState
	files            map[uint32]*fileLogState
	fileDir          string
	fileName         string
	fileExt          string
	wildcardPath     bool
	rotateSizeByte   int64
	bufferSizeMax    int
	batchSize        int
	batchTimeout     time.Duration
	rotateTimeoutMin int
	rotateAtHour     int
	rotateAtMin      int
	rotate           bool
	compress         bool
}

type fileLogState struct {
	file        *os.File
	batch       *core.MessageBatch
	bgWriter    *sync.WaitGroup
	fileCreated time.Time
}

func init() {
	shared.RuntimeType.Register(File{})
}

func newFileLogState(bufferSizeMax int, format core.Formatter) *fileLogState {
	return &fileLogState{
		batch:    core.NewMessageBatch(bufferSizeMax, format),
		bgWriter: new(sync.WaitGroup),
	}
}

// Configure initializes this producer with values from a plugin config.
func (prod *File) Configure(conf core.PluginConfig) error {
	err := prod.ProducerBase.Configure(conf)
	if err != nil {
		return err
	}

	prod.filesByStream = make(map[core.MessageStreamID]*fileLogState)
	prod.files = make(map[uint32]*fileLogState)
	prod.bufferSizeMax = conf.GetInt("BatchSizeMaxKB", 8<<10) << 10 // 8 MB

	prod.batchSize = conf.GetInt("BatchSizeByte", 8192)
	prod.batchTimeout = time.Duration(conf.GetInt("BatchTimeoutSec", 5)) * time.Second

	prod.rotate = conf.GetBool("Rotate", false)
	prod.rotateTimeoutMin = conf.GetInt("RotateTimeoutMin", 1440)
	prod.rotateSizeByte = int64(conf.GetInt("RotateSizeMB", 1024)) << 20
	prod.rotateAtHour = -1
	prod.rotateAtMin = -1
	prod.compress = conf.GetBool("Compress", false)

	logFile := conf.GetString("File", "/var/prod/gollum.log")
	prod.wildcardPath = strings.IndexByte(logFile, '*') != -1

	prod.fileDir = filepath.Dir(logFile)
	prod.fileExt = filepath.Ext(logFile)
	prod.fileName = filepath.Base(logFile)
	prod.fileName = prod.fileName[:len(prod.fileName)-len(prod.fileExt)]

	rotateAt := conf.GetString("RotateAt", "")
	if rotateAt != "" {
		parts := strings.Split(rotateAt, ":")
		rotateAtHour, _ := strconv.ParseInt(parts[0], 10, 8)
		rotateAtMin, _ := strconv.ParseInt(parts[1], 10, 8)

		prod.rotateAtHour = int(rotateAtHour)
		prod.rotateAtMin = int(rotateAtMin)
	}

	return nil
}

func (state *fileLogState) compressAndCloseLog(sourceFile *os.File) {
	state.bgWriter.Add(1)
	defer state.bgWriter.Done()

	// Generate file to zip into
	sourceFileName := sourceFile.Name()
	sourceDir := filepath.Dir(sourceFileName)
	sourceExt := filepath.Ext(sourceFileName)
	sourceBase := filepath.Base(sourceFileName)
	sourceBase = sourceBase[:len(sourceBase)-len(sourceExt)]

	targetFileName := fmt.Sprintf("%s/%s.gz", sourceDir, sourceBase)

	targetFile, err := os.OpenFile(targetFileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		Log.Error.Print("File compress error:", err)
		sourceFile.Close()
		return
	}

	// Create zipfile and compress data
	Log.Note.Print("Compressing " + sourceFileName)

	sourceFile.Seek(0, 0)
	targetWriter := gzip.NewWriter(targetFile)

	for err == nil {
		_, err = io.CopyN(targetWriter, sourceFile, 1<<20) // 1 MB chunks
		runtime.Gosched()                                  // Be async!
	}

	// Cleanup
	sourceFile.Close()
	targetWriter.Close()
	targetFile.Close()

	if err != nil && err != io.EOF {
		Log.Warning.Print("Compression failed:", err)
		err = os.Remove(targetFileName)
		if err != nil {
			Log.Error.Print("Compressed file remove failed:", err)
		}
		return
	}

	// Remove original log
	err = os.Remove(sourceFileName)
	if err != nil {
		Log.Error.Print("Uncompressed file remove failed:", err)
	}
}

func (state *fileLogState) onWriterError(err error) bool {
	Log.Error.Print("File write error:", err)
	return false
}

func (state *fileLogState) writeBatch() {
	state.batch.Flush(state.file, nil, state.onWriterError)
}

func (state *fileLogState) needsRotate(prod *File, forceRotate bool) (bool, error) {
	// File does not exist?
	if state.file == nil {
		return true, nil
	}

	// File can be accessed?
	stats, err := state.file.Stat()
	if err != nil {
		return false, err
	}

	// File needs rotation?
	if !prod.rotate {
		return false, nil
	}

	if forceRotate {
		return true, nil
	}

	// File is too large?
	if stats.Size() >= prod.rotateSizeByte {
		return true, nil // ### return, too large ###
	}

	// File is too old?
	if time.Since(state.fileCreated).Minutes() >= float64(prod.rotateTimeoutMin) {
		return true, nil // ### return, too old ###
	}

	// RotateAt crossed?
	if prod.rotateAtHour > -1 && prod.rotateAtMin > -1 {
		now := time.Now()
		rotateAt := time.Date(now.Year(), now.Month(), now.Day(), prod.rotateAtHour, prod.rotateAtMin, 0, 0, now.Location())

		if state.fileCreated.Sub(rotateAt).Minutes() < 0 {
			return true, nil // ### return, too old ###
		}
	}

	// nope, everything is ok
	return false, nil
}

func (prod *File) getFileLogState(streamID core.MessageStreamID, forceRotate bool) (*fileLogState, error) {
	if state, stateExists := prod.filesByStream[streamID]; stateExists {
		if rotate, err := state.needsRotate(prod, forceRotate); !rotate {
			return state, err // ### return, already open or error ###
		}
	}

	var logFileName, fileDir, fileName, fileExt string
	var fileID uint32

	if prod.wildcardPath {
		// Get state from filename (without timestamp, etc.)
		var streamName string
		switch streamID {
		case core.WildcardStreamID:
			streamName = "all"
		case core.LogInternalStreamID:
			streamName = "gollum"
		case core.DroppedStreamID:
			streamName = "dropped"
		default:
			streamName = core.StreamTypes.GetStreamName(streamID)
		}

		fileDir = strings.Replace(prod.fileDir, "*", streamName, -1)
		fileName = strings.Replace(prod.fileName, "*", streamName, -1)
		fileExt = strings.Replace(prod.fileExt, "*", streamName, -1)

		// Hash the base name
		hash := fnv.New32a()
		hash.Write([]byte(fmt.Sprintf("%s/%s%s", fileDir, fileName, fileExt)))
		fileID = hash.Sum32()
	} else {
		// Simple case: only one file used
		fileDir = prod.fileDir
		fileName = prod.fileName
		fileExt = prod.fileExt
		fileID = 0
	}

	// Assure the file is correctly mapped
	state, stateExists := prod.files[fileID]
	if !stateExists {
		// state does not yet exist: create and map it
		state = newFileLogState(prod.bufferSizeMax, prod.ProducerBase.GetFormatter())
		prod.files[fileID] = state
		prod.filesByStream[streamID] = state
	} else if _, mappingExists := prod.filesByStream[streamID]; !mappingExists {
		// state exists but is not mapped: map it and see if we need to rotate
		prod.filesByStream[streamID] = state
		if rotate, err := state.needsRotate(prod, forceRotate); !rotate {
			return state, err // ### return, already open or error ###
		}
	}

	// Generate the log filename based on rotation, existing files, etc.
	if !prod.rotate {
		logFileName = fmt.Sprintf("%s%s", fileName, fileExt)
	} else {
		timestamp := time.Now().Format(fileProducerTimestamp)
		signature := fmt.Sprintf("%s_%s", fileName, timestamp)
		counter := 0

		if err := os.MkdirAll(fileDir, 0755); err != nil {
			Log.Error.Print("Error creating directory " + fileDir)
		}

		files, _ := ioutil.ReadDir(fileDir)
		for _, file := range files {
			if strings.Contains(file.Name(), signature) {
				counter++
			}
		}

		if counter == 0 {
			logFileName = fmt.Sprintf("%s%s", signature, fileExt)
		} else {
			logFileName = fmt.Sprintf("%s_%d%s", signature, counter, fileExt)
		}
	}

	logFile := fmt.Sprintf("%s/%s", fileDir, logFileName)

	// Close existing log
	if state.file != nil {
		currentLog := state.file
		state.file = nil

		if prod.compress {
			go state.compressAndCloseLog(currentLog)
		} else {
			Log.Note.Print("Rotated " + currentLog.Name())
			currentLog.Close()
		}
	}

	// (Re)open logfile
	var err error
	state.file, err = os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		return state, err // ### return error ###
	}

	// Create "current" symlink
	state.fileCreated = time.Now()
	if prod.rotate {
		symLinkName := fmt.Sprintf("%s/%s_current", fileDir, fileName)
		os.Remove(symLinkName)
		os.Symlink(logFileName, symLinkName)
	}

	return state, err
}

func (prod *File) writeBatchOnTimeOut() {
	for _, state := range prod.files {
		if state.batch.ReachedTimeThreshold(prod.batchTimeout) || state.batch.ReachedSizeThreshold(prod.batchSize) {
			state.writeBatch()
		}
	}
}

func (prod *File) writeMessage(msg core.Message) {
	state, err := prod.getFileLogState(msg.StreamID, false)
	if err != nil {
		Log.Error.Print("File log error:", err)
		msg.Drop(time.Duration(0))
		return // ### return, dropped ###
	}

	if !state.batch.Append(msg) {
		state.writeBatch()
		state.batch.Append(msg)
	}
}

func (prod *File) rotateLog() {
	for streamID := range prod.filesByStream {
		if _, err := prod.getFileLogState(streamID, true); err != nil {
			Log.Error.Print("File rotate error:", err)
		}
	}
}

func (prod *File) flush() {
	for _, state := range prod.files {
		state.writeBatch()
		state.batch.WaitForFlush(5 * time.Second)

		state.bgWriter.Wait()
		state.file.Close()
	}
	prod.WorkerDone()
}

// Produce writes to a buffer that is dumped to a file.
func (prod *File) Produce(workers *sync.WaitGroup) {
	defer prod.flush()

	prod.AddMainWorker(workers)
	prod.TickerControlLoop(prod.batchTimeout, prod.writeMessage, prod.rotateLog, prod.writeBatchOnTimeOut)
}
<commit_msg>File producer symlink uses file extension<commit_after>// Copyright 2015 trivago GmbH
//
// 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 producer

import (
	"compress/gzip"
	"fmt"
	"github.com/trivago/gollum/core"
	"github.com/trivago/gollum/core/log"
	"github.com/trivago/gollum/shared"
	"hash/fnv"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	fileProducerTimestamp = "2006-01-02_15"
)

// File producer plugin
// Configuration example
//
//   - "producer.File":
//     Enable: true
//     File: "/var/log/gollum.log"
//     BatchSizeMaxKB: 16384
//     BatchSizeByte: 4096
//     BatchTimeoutSec: 2
//     Rotate: false
//     RotateTimeoutMin: 1440
//     RotateSizeMB: 1024
//     RotateAt: "00:00"
//     Compress: true
//
// The file producer writes messages to a file. This producer also allows log
// rotation and compression of the rotated logs.
//
// File contains the path to the log file to write. The wildcard "*" will be
// replaced by the stream name.
// By default this is set to /var/prod/gollum.log.
//
// BatchSizeMaxKB defines the internal file buffer size in KB.
// This producers allocates a front- and a backbuffer of this size. If the
// frontbuffer is filled up completely a flush is triggered and the frontbuffer
// becomes available for writing again. Messages larger than BatchSizeMaxKB are
// rejected.
//
// BatchSizeByte defines the number of bytes to be buffered before they are written
// to disk. By default this is set to 8KB.
//
// BatchTimeoutSec defines the maximum number of seconds to wait after the last
// message arrived before a batch is flushed automatically. By default this is
// set to 5..
//
// Rotate if set to true the logs will rotate after reaching certain thresholds.
//
// RotateTimeoutMin defines a timeout in minutes that will cause the logs to
// rotate. Can be set in parallel with RotateSizeMB. By default this is set to
// 1440 (i.e. 1 Day).
//
// RotateAt defines specific timestamp as in "HH:MM" when the log should be
// rotated. Hours must be given in 24h format. When left empty this setting is
// ignored. By default this setting is disabled.
//
// Compress defines if a rotated logfile is to be gzip compressed or not.
// By default this is set to false.
type File struct {
	core.ProducerBase
	filesByStream    map[core.MessageStreamID]*fileLogState
	files            map[uint32]*fileLogState
	fileDir          string
	fileName         string
	fileExt          string
	wildcardPath     bool
	rotateSizeByte   int64
	bufferSizeMax    int
	batchSize        int
	batchTimeout     time.Duration
	rotateTimeoutMin int
	rotateAtHour     int
	rotateAtMin      int
	rotate           bool
	compress         bool
}

type fileLogState struct {
	file        *os.File
	batch       *core.MessageBatch
	bgWriter    *sync.WaitGroup
	fileCreated time.Time
}

func init() {
	shared.RuntimeType.Register(File{})
}

func newFileLogState(bufferSizeMax int, format core.Formatter) *fileLogState {
	return &fileLogState{
		batch:    core.NewMessageBatch(bufferSizeMax, format),
		bgWriter: new(sync.WaitGroup),
	}
}

// Configure initializes this producer with values from a plugin config.
func (prod *File) Configure(conf core.PluginConfig) error {
	err := prod.ProducerBase.Configure(conf)
	if err != nil {
		return err
	}

	prod.filesByStream = make(map[core.MessageStreamID]*fileLogState)
	prod.files = make(map[uint32]*fileLogState)
	prod.bufferSizeMax = conf.GetInt("BatchSizeMaxKB", 8<<10) << 10 // 8 MB

	prod.batchSize = conf.GetInt("BatchSizeByte", 8192)
	prod.batchTimeout = time.Duration(conf.GetInt("BatchTimeoutSec", 5)) * time.Second

	prod.rotate = conf.GetBool("Rotate", false)
	prod.rotateTimeoutMin = conf.GetInt("RotateTimeoutMin", 1440)
	prod.rotateSizeByte = int64(conf.GetInt("RotateSizeMB", 1024)) << 20
	prod.rotateAtHour = -1
	prod.rotateAtMin = -1
	prod.compress = conf.GetBool("Compress", false)

	logFile := conf.GetString("File", "/var/prod/gollum.log")
	prod.wildcardPath = strings.IndexByte(logFile, '*') != -1

	prod.fileDir = filepath.Dir(logFile)
	prod.fileExt = filepath.Ext(logFile)
	prod.fileName = filepath.Base(logFile)
	prod.fileName = prod.fileName[:len(prod.fileName)-len(prod.fileExt)]

	rotateAt := conf.GetString("RotateAt", "")
	if rotateAt != "" {
		parts := strings.Split(rotateAt, ":")
		rotateAtHour, _ := strconv.ParseInt(parts[0], 10, 8)
		rotateAtMin, _ := strconv.ParseInt(parts[1], 10, 8)

		prod.rotateAtHour = int(rotateAtHour)
		prod.rotateAtMin = int(rotateAtMin)
	}

	return nil
}

func (state *fileLogState) compressAndCloseLog(sourceFile *os.File) {
	state.bgWriter.Add(1)
	defer state.bgWriter.Done()

	// Generate file to zip into
	sourceFileName := sourceFile.Name()
	sourceDir := filepath.Dir(sourceFileName)
	sourceExt := filepath.Ext(sourceFileName)
	sourceBase := filepath.Base(sourceFileName)
	sourceBase = sourceBase[:len(sourceBase)-len(sourceExt)]

	targetFileName := fmt.Sprintf("%s/%s.gz", sourceDir, sourceBase)

	targetFile, err := os.OpenFile(targetFileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		Log.Error.Print("File compress error:", err)
		sourceFile.Close()
		return
	}

	// Create zipfile and compress data
	Log.Note.Print("Compressing " + sourceFileName)

	sourceFile.Seek(0, 0)
	targetWriter := gzip.NewWriter(targetFile)

	for err == nil {
		_, err = io.CopyN(targetWriter, sourceFile, 1<<20) // 1 MB chunks
		runtime.Gosched()                                  // Be async!
	}

	// Cleanup
	sourceFile.Close()
	targetWriter.Close()
	targetFile.Close()

	if err != nil && err != io.EOF {
		Log.Warning.Print("Compression failed:", err)
		err = os.Remove(targetFileName)
		if err != nil {
			Log.Error.Print("Compressed file remove failed:", err)
		}
		return
	}

	// Remove original log
	err = os.Remove(sourceFileName)
	if err != nil {
		Log.Error.Print("Uncompressed file remove failed:", err)
	}
}

func (state *fileLogState) onWriterError(err error) bool {
	Log.Error.Print("File write error:", err)
	return false
}

func (state *fileLogState) writeBatch() {
	state.batch.Flush(state.file, nil, state.onWriterError)
}

func (state *fileLogState) needsRotate(prod *File, forceRotate bool) (bool, error) {
	// File does not exist?
	if state.file == nil {
		return true, nil
	}

	// File can be accessed?
	stats, err := state.file.Stat()
	if err != nil {
		return false, err
	}

	// File needs rotation?
	if !prod.rotate {
		return false, nil
	}

	if forceRotate {
		return true, nil
	}

	// File is too large?
	if stats.Size() >= prod.rotateSizeByte {
		return true, nil // ### return, too large ###
	}

	// File is too old?
	if time.Since(state.fileCreated).Minutes() >= float64(prod.rotateTimeoutMin) {
		return true, nil // ### return, too old ###
	}

	// RotateAt crossed?
	if prod.rotateAtHour > -1 && prod.rotateAtMin > -1 {
		now := time.Now()
		rotateAt := time.Date(now.Year(), now.Month(), now.Day(), prod.rotateAtHour, prod.rotateAtMin, 0, 0, now.Location())

		if state.fileCreated.Sub(rotateAt).Minutes() < 0 {
			return true, nil // ### return, too old ###
		}
	}

	// nope, everything is ok
	return false, nil
}

func (prod *File) getFileLogState(streamID core.MessageStreamID, forceRotate bool) (*fileLogState, error) {
	if state, stateExists := prod.filesByStream[streamID]; stateExists {
		if rotate, err := state.needsRotate(prod, forceRotate); !rotate {
			return state, err // ### return, already open or error ###
		}
	}

	var logFileName, fileDir, fileName, fileExt string
	var fileID uint32

	if prod.wildcardPath {
		// Get state from filename (without timestamp, etc.)
		var streamName string
		switch streamID {
		case core.WildcardStreamID:
			streamName = "all"
		case core.LogInternalStreamID:
			streamName = "gollum"
		case core.DroppedStreamID:
			streamName = "dropped"
		default:
			streamName = core.StreamTypes.GetStreamName(streamID)
		}

		fileDir = strings.Replace(prod.fileDir, "*", streamName, -1)
		fileName = strings.Replace(prod.fileName, "*", streamName, -1)
		fileExt = strings.Replace(prod.fileExt, "*", streamName, -1)

		// Hash the base name
		hash := fnv.New32a()
		hash.Write([]byte(fmt.Sprintf("%s/%s%s", fileDir, fileName, fileExt)))
		fileID = hash.Sum32()
	} else {
		// Simple case: only one file used
		fileDir = prod.fileDir
		fileName = prod.fileName
		fileExt = prod.fileExt
		fileID = 0
	}

	// Assure the file is correctly mapped
	state, stateExists := prod.files[fileID]
	if !stateExists {
		// state does not yet exist: create and map it
		state = newFileLogState(prod.bufferSizeMax, prod.ProducerBase.GetFormatter())
		prod.files[fileID] = state
		prod.filesByStream[streamID] = state
	} else if _, mappingExists := prod.filesByStream[streamID]; !mappingExists {
		// state exists but is not mapped: map it and see if we need to rotate
		prod.filesByStream[streamID] = state
		if rotate, err := state.needsRotate(prod, forceRotate); !rotate {
			return state, err // ### return, already open or error ###
		}
	}

	// Generate the log filename based on rotation, existing files, etc.
	if !prod.rotate {
		logFileName = fmt.Sprintf("%s%s", fileName, fileExt)
	} else {
		timestamp := time.Now().Format(fileProducerTimestamp)
		signature := fmt.Sprintf("%s_%s", fileName, timestamp)
		counter := 0

		if err := os.MkdirAll(fileDir, 0755); err != nil {
			Log.Error.Print("Error creating directory " + fileDir)
		}

		files, _ := ioutil.ReadDir(fileDir)
		for _, file := range files {
			if strings.Contains(file.Name(), signature) {
				counter++
			}
		}

		if counter == 0 {
			logFileName = fmt.Sprintf("%s%s", signature, fileExt)
		} else {
			logFileName = fmt.Sprintf("%s_%d%s", signature, counter, fileExt)
		}
	}

	logFile := fmt.Sprintf("%s/%s", fileDir, logFileName)

	// Close existing log
	if state.file != nil {
		currentLog := state.file
		state.file = nil

		if prod.compress {
			go state.compressAndCloseLog(currentLog)
		} else {
			Log.Note.Print("Rotated " + currentLog.Name())
			currentLog.Close()
		}
	}

	// (Re)open logfile
	var err error
	state.file, err = os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		return state, err // ### return error ###
	}

	// Create "current" symlink
	state.fileCreated = time.Now()
	if prod.rotate {
		symLinkName := fmt.Sprintf("%s/%s_current%s", fileDir, fileName, fileExt)
		os.Remove(symLinkName)
		os.Symlink(logFileName, symLinkName)
	}

	return state, err
}

func (prod *File) writeBatchOnTimeOut() {
	for _, state := range prod.files {
		if state.batch.ReachedTimeThreshold(prod.batchTimeout) || state.batch.ReachedSizeThreshold(prod.batchSize) {
			state.writeBatch()
		}
	}
}

func (prod *File) writeMessage(msg core.Message) {
	state, err := prod.getFileLogState(msg.StreamID, false)
	if err != nil {
		Log.Error.Print("File log error:", err)
		msg.Drop(time.Duration(0))
		return // ### return, dropped ###
	}

	if !state.batch.Append(msg) {
		state.writeBatch()
		state.batch.Append(msg)
	}
}

func (prod *File) rotateLog() {
	for streamID := range prod.filesByStream {
		if _, err := prod.getFileLogState(streamID, true); err != nil {
			Log.Error.Print("File rotate error:", err)
		}
	}
}

func (prod *File) flush() {
	for _, state := range prod.files {
		state.writeBatch()
		state.batch.WaitForFlush(5 * time.Second)

		state.bgWriter.Wait()
		state.file.Close()
	}
	prod.WorkerDone()
}

// Produce writes to a buffer that is dumped to a file.
func (prod *File) Produce(workers *sync.WaitGroup) {
	defer prod.flush()

	prod.AddMainWorker(workers)
	prod.TickerControlLoop(prod.batchTimeout, prod.writeMessage, prod.rotateLog, prod.writeBatchOnTimeOut)
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes 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 ghmetrics

import (
	"strings"

	"github.com/sirupsen/logrus"
)

const unmatchedPath = "unmatched"

// GetSimplifiedPath returns a variable-free path that can be used as label for prometheus metrics
func GetSimplifiedPath(path string) string {
	tree := l("", // shadow element mimicing the root
		l("repos",
			v("owner",
				v("repo",
					l("branches", v("branch", l("protection",
						l("restrictions", l("users"), l("teams")),
						l("required_status_checks", l("contexts")),
						l("required_pull_request_reviews"),
						l("required_signatures"),
						l("enforce_admins")))),
					l("issues",
						l("comments", v("commentId")),
						l("events", v("eventId")),
						v("issueId",
							l("lock"),
							l("comments"),
							l("events"),
							l("assignees"),
							l("reactions"),
							l("labels", v("labelId")))),
					l("keys", v("keyId")),
					l("labels", v("labelId")),
					l("milestones", v("milestone")),
					l("pulls", v("pullId")),
					l("releases", v("releaseId")),
					l("statuses", v("statusId")),
					l("subscribers", v("subscriberId")),
					l("assignees", v("assigneeId")),
					l("archive", v("zip")),
					l("collaborators", v("collaboratorId")),
					l("comments", v("commentId")),
					l("compare", v("sha")),
					l("contents", v("contentId")),
					l("commits", v("sha")),
					l("git",
						l("commits", v("sha")),
						l("ref", v("refId")),
						l("tags", v("tagId")),
						l("trees", v("sha")),
						l("refs", l("heads", v("ref")))),
					l("stars"),
					l("merges"),
					l("stargazers"),
					l("notifications"),
					l("hooks"),
					l("deployments"),
					l("downloads"),
					l("events"),
					l("forks"),
					l("topics"),
					l("vulnerability-alerts"),
					l("automated-security-fixes"),
					l("contributors"),
					l("languages"),
					l("teams"),
					l("tags"),
					l("transfer")))),
		l("user",
			l("following", v("userId")),
			l("keys", v("keyId")),
			l("email", l("visibility")),
			l("emails"),
			l("public_emails"),
			l("followers"),
			l("starred"),
			l("issues")),
		l("users",
			v("username",
				l("followers", v("username")),
				l("repos"),
				l("hovercard"),
				l("following"))),
		l("orgs",
			v("orgname",
				l("credential-authorizations", v("credentialId")),
				l("repos"),
				l("issues"),
				l("invitations"),
				l("members", v("login")),
				l("teams"))),
		l("organizations",
			v("orgId",
				l("members"),
				l("repos"),
				l("teams"))),
		l("issues", v("issueId")),
		l("search",
			l("repositories"),
			l("commits"),
			l("code"),
			l("issues"),
			l("users"),
			l("topics"),
			l("labels")),
		l("gists",
			l("public"),
			l("starred")),
		l("notifications", l("threads", v("threadId", l("subscription")))),
		l("repositories"),
		l("emojis"),
		l("events"),
		l("feeds"),
		l("hub"),
		l("rate_limit"),
		l("teams"),
		// end point for gh api v4
		l("graphql"),
		l("licenses"))

	splitPath := strings.Split(path, "/")
	resolvedPath, matches := resolve(tree, splitPath)
	if !matches {
		logrus.WithField("path", path).Warning("Path not handled. This is a bug in GHProxy, please open an issue against the kubernetes/test-infra repository with this error message.")
		return unmatchedPath
	}
	return resolvedPath
}

type node struct {
	PathFragment
	children []node
}

// PathFragment Interface for tree leafs to help resolve paths
type PathFragment interface {
	Matches(part string) bool
	Represent() string
}

type literal string

func (l literal) Matches(part string) bool {
	return string(l) == part
}

func (l literal) Represent() string {
	return string(l)
}

type variable string

func (v variable) Matches(part string) bool {
	return true
}

func (v variable) Represent() string {
	return ":" + string(v)
}

func l(fragment string, children ...node) node {
	return node{
		PathFragment: literal(fragment),
		children:     children,
	}
}

func v(fragment string, children ...node) node {
	return node{
		PathFragment: variable(fragment),
		children:     children,
	}
}

func resolve(parent node, path []string) (string, bool) {
	if !parent.Matches(path[0]) {
		return "", false
	}
	representation := parent.Represent()
	if len(path) == 1 || len(parent.children) == 0 {
		return representation, true
	}
	for _, child := range parent.children {
		suffix, matched := resolve(child, path[1:])
		if matched {
			return strings.Join([]string{representation, suffix}, "/"), true
		}
	}
	return "", false
}
<commit_msg>kill log messages to reduce log spam<commit_after>/*
Copyright 2019 The Kubernetes 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 ghmetrics

import (
	"strings"

	"github.com/sirupsen/logrus"
)

const unmatchedPath = "unmatched"

// GetSimplifiedPath returns a variable-free path that can be used as label for prometheus metrics
func GetSimplifiedPath(path string) string {
	tree := l("", // shadow element mimicing the root
		l("repos",
			v("owner",
				v("repo",
					l("branches", v("branch", l("protection",
						l("restrictions", l("users"), l("teams")),
						l("required_status_checks", l("contexts")),
						l("required_pull_request_reviews"),
						l("required_signatures"),
						l("enforce_admins")))),
					l("issues",
						l("comments", v("commentId")),
						l("events", v("eventId")),
						v("issueId",
							l("lock"),
							l("comments"),
							l("events"),
							l("assignees"),
							l("reactions"),
							l("labels", v("labelId")))),
					l("keys", v("keyId")),
					l("labels", v("labelId")),
					l("milestones", v("milestone")),
					l("pulls", v("pullId")),
					l("releases", v("releaseId")),
					l("statuses", v("statusId")),
					l("subscribers", v("subscriberId")),
					l("assignees", v("assigneeId")),
					l("archive", v("zip")),
					l("collaborators", v("collaboratorId")),
					l("comments", v("commentId")),
					l("compare", v("sha")),
					l("contents", v("contentId")),
					l("commits", v("sha")),
					l("git",
						l("commits", v("sha")),
						l("ref", v("refId")),
						l("tags", v("tagId")),
						l("trees", v("sha")),
						l("refs", l("heads", v("ref")))),
					l("stars"),
					l("merges"),
					l("stargazers"),
					l("notifications"),
					l("hooks"),
					l("deployments"),
					l("downloads"),
					l("events"),
					l("forks"),
					l("topics"),
					l("vulnerability-alerts"),
					l("automated-security-fixes"),
					l("contributors"),
					l("languages"),
					l("teams"),
					l("tags"),
					l("transfer")))),
		l("user",
			l("following", v("userId")),
			l("keys", v("keyId")),
			l("email", l("visibility")),
			l("emails"),
			l("public_emails"),
			l("followers"),
			l("starred"),
			l("issues")),
		l("users",
			v("username",
				l("followers", v("username")),
				l("repos"),
				l("hovercard"),
				l("following"))),
		l("orgs",
			v("orgname",
				l("credential-authorizations", v("credentialId")),
				l("repos"),
				l("issues"),
				l("invitations"),
				l("members", v("login")),
				l("teams"))),
		l("organizations",
			v("orgId",
				l("members"),
				l("repos"),
				l("teams"))),
		l("issues", v("issueId")),
		l("search",
			l("repositories"),
			l("commits"),
			l("code"),
			l("issues"),
			l("users"),
			l("topics"),
			l("labels")),
		l("gists",
			l("public"),
			l("starred")),
		l("notifications", l("threads", v("threadId", l("subscription")))),
		l("repositories"),
		l("emojis"),
		l("events"),
		l("feeds"),
		l("hub"),
		l("rate_limit"),
		l("teams"),
		// end point for gh api v4
		l("graphql"),
		l("licenses"))

	splitPath := strings.Split(path, "/")
	resolvedPath, matches := resolve(tree, splitPath)
	if !matches {
		logrus.WithField("path", path).Debug("Path not handled. This is a bug in GHProxy, please open an issue against the kubernetes/test-infra repository with this error message.")
		return unmatchedPath
	}
	return resolvedPath
}

type node struct {
	PathFragment
	children []node
}

// PathFragment Interface for tree leafs to help resolve paths
type PathFragment interface {
	Matches(part string) bool
	Represent() string
}

type literal string

func (l literal) Matches(part string) bool {
	return string(l) == part
}

func (l literal) Represent() string {
	return string(l)
}

type variable string

func (v variable) Matches(part string) bool {
	return true
}

func (v variable) Represent() string {
	return ":" + string(v)
}

func l(fragment string, children ...node) node {
	return node{
		PathFragment: literal(fragment),
		children:     children,
	}
}

func v(fragment string, children ...node) node {
	return node{
		PathFragment: variable(fragment),
		children:     children,
	}
}

func resolve(parent node, path []string) (string, bool) {
	if !parent.Matches(path[0]) {
		return "", false
	}
	representation := parent.Represent()
	if len(path) == 1 || len(parent.children) == 0 {
		return representation, true
	}
	for _, child := range parent.children {
		suffix, matched := resolve(child, path[1:])
		if matched {
			return strings.Join([]string{representation, suffix}, "/"), true
		}
	}
	return "", false
}
<|endoftext|>


<commit_before>package chat

import (
	"sync"
	"time"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/protocol/keybase1"

	"github.com/keybase/client/go/chat/types"
	"github.com/keybase/client/go/chat/utils"
	"golang.org/x/net/context"
)

// sourceOfflinable implements the chat/types.Offlinable interface.
// It is meant to be embedded in inbox and conversation sources.
// It's main purpose is that IsOffline() will wait for 4s to see if any
// in progress connections succeed before returning.
type sourceOfflinable struct {
	globals.Contextified
	utils.DebugLabeler
	offline, delayed bool
	connected        chan bool
	sync.Mutex
}

var _ types.Offlinable = (*sourceOfflinable)(nil)

func newSourceOfflinable(g *globals.Context, labeler utils.DebugLabeler) *sourceOfflinable {
	return &sourceOfflinable{
		Contextified: globals.NewContextified(g),
		DebugLabeler: labeler,
		connected:    makeConnectedChan(),
	}
}

func (s *sourceOfflinable) Connected(ctx context.Context) {
	defer s.Trace(ctx, func() error { return nil }, "Connected")()
	s.Lock()
	defer s.Unlock()
	s.Debug(ctx, "connected: offline to false")
	s.offline = false
	s.connected <- true
}

func (s *sourceOfflinable) Disconnected(ctx context.Context) {
	defer s.Trace(ctx, func() error { return nil }, "Disconnected")()
	s.Lock()
	defer s.Unlock()
	if s.offline {
		s.Debug(ctx, "already disconnected, ignoring disconnected callback")
		return
	}
	s.Debug(ctx, "disconnected: offline to true")
	s.offline = true
	s.delayed = false
	close(s.connected)
	s.connected = makeConnectedChan()
}

func (s *sourceOfflinable) getOfflineInfo() (offline bool, connectedCh chan bool) {
	s.Lock()
	defer s.Unlock()
	return s.offline, s.connected
}

func (s *sourceOfflinable) IsOffline(ctx context.Context) bool {
	s.Lock()
	offline := s.offline
	connected := s.connected
	delayed := s.delayed
	s.Unlock()

	if offline {
		if delayed {
			s.Debug(ctx, "IsOffline: offline, but skipping delay since we already did it")
			return offline
		}
		if s.G().MobileAppState.State() != keybase1.MobileAppState_FOREGROUND {
			s.Debug(ctx, "IsOffline: offline, but not waiting for anything since not in foreground")
			return offline
		}
		timeoutCh := time.After(5 * time.Second)
		for {
			select {
			case <-connected:
				s.Debug(ctx, "IsOffline: waited and got %v", s.offline)
				s.Lock()
				if s.offline {
					s.Unlock()
					s.Debug(ctx, "IsOffline: since we got word of being offline, we will keep waiting")
					continue
				}
				defer s.Unlock()
				return s.offline
			case <-ctx.Done():
				s.Lock()
				defer s.Unlock()
				s.Debug(ctx, "IsOffline: aborted: %s state: %v", ctx.Err(), s.offline)
				return s.offline
			case <-timeoutCh:
				s.Lock()
				defer s.Unlock()
				select {
				case <-ctx.Done():
					s.Debug(ctx, "IsOffline: timed out, but context canceled so not setting delayed: state: %v",
						s.offline)
					return s.offline
				default:
				}
				s.delayed = true
				s.Debug(ctx, "IsOffline: timed out, setting delay wait: state: %v", s.offline)
				return s.offline
			}
		}
	}
	return offline
}

// makeConnectedChan creates a buffered channel for Connected to signal that
// a connection happened.  The buffer size is 10 just to be extra-safe that
// a send on the channel won't block during its lifetime (a buffer size of
// 1 should be all that is required).
func makeConnectedChan() chan bool {
	return make(chan bool, 10)

}
<commit_msg>dont spin on a closed channel, get the new one (#17944)<commit_after>package chat

import (
	"sync"
	"time"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/protocol/keybase1"

	"github.com/keybase/client/go/chat/types"
	"github.com/keybase/client/go/chat/utils"
	"golang.org/x/net/context"
)

// sourceOfflinable implements the chat/types.Offlinable interface.
// It is meant to be embedded in inbox and conversation sources.
// It's main purpose is that IsOffline() will wait for 4s to see if any
// in progress connections succeed before returning.
type sourceOfflinable struct {
	globals.Contextified
	utils.DebugLabeler
	offline, delayed bool
	connected        chan bool
	sync.Mutex
}

var _ types.Offlinable = (*sourceOfflinable)(nil)

func newSourceOfflinable(g *globals.Context, labeler utils.DebugLabeler) *sourceOfflinable {
	return &sourceOfflinable{
		Contextified: globals.NewContextified(g),
		DebugLabeler: labeler,
		connected:    makeConnectedChan(),
	}
}

func (s *sourceOfflinable) Connected(ctx context.Context) {
	defer s.Trace(ctx, func() error { return nil }, "Connected")()
	s.Lock()
	defer s.Unlock()
	s.Debug(ctx, "connected: offline to false")
	s.offline = false
	s.connected <- true
}

func (s *sourceOfflinable) Disconnected(ctx context.Context) {
	defer s.Trace(ctx, func() error { return nil }, "Disconnected")()
	s.Lock()
	defer s.Unlock()
	if s.offline {
		s.Debug(ctx, "already disconnected, ignoring disconnected callback")
		return
	}
	s.Debug(ctx, "disconnected: offline to true")
	s.offline = true
	s.delayed = false
	close(s.connected)
	s.connected = makeConnectedChan()
}

func (s *sourceOfflinable) getOfflineInfo() (offline bool, connectedCh chan bool) {
	s.Lock()
	defer s.Unlock()
	return s.offline, s.connected
}

func (s *sourceOfflinable) IsOffline(ctx context.Context) bool {
	s.Lock()
	offline := s.offline
	connected := s.connected
	delayed := s.delayed
	s.Unlock()

	if offline {
		if delayed {
			s.Debug(ctx, "IsOffline: offline, but skipping delay since we already did it")
			return offline
		}
		if s.G().MobileAppState.State() != keybase1.MobileAppState_FOREGROUND {
			s.Debug(ctx, "IsOffline: offline, but not waiting for anything since not in foreground")
			return offline
		}
		timeoutCh := time.After(5 * time.Second)
		for {
			select {
			case <-connected:
				s.Debug(ctx, "IsOffline: waited and got %v", s.offline)
				s.Lock()
				if s.offline {
					connected = s.connected
					s.Unlock()
					s.Debug(ctx, "IsOffline: since we got word of being offline, we will keep waiting")
					continue
				}
				defer s.Unlock()
				return s.offline
			case <-ctx.Done():
				s.Lock()
				defer s.Unlock()
				s.Debug(ctx, "IsOffline: aborted: %s state: %v", ctx.Err(), s.offline)
				return s.offline
			case <-timeoutCh:
				s.Lock()
				defer s.Unlock()
				select {
				case <-ctx.Done():
					s.Debug(ctx, "IsOffline: timed out, but context canceled so not setting delayed: state: %v",
						s.offline)
					return s.offline
				default:
				}
				s.delayed = true
				s.Debug(ctx, "IsOffline: timed out, setting delay wait: state: %v", s.offline)
				return s.offline
			}
		}
	}
	return offline
}

// makeConnectedChan creates a buffered channel for Connected to signal that
// a connection happened.  The buffer size is 10 just to be extra-safe that
// a send on the channel won't block during its lifetime (a buffer size of
// 1 should be all that is required).
func makeConnectedChan() chan bool {
	return make(chan bool, 10)

}
<|endoftext|>


<commit_before>package transactions

import (
	"errors"
	"fmt"

	"github.com/jinzhu/gorm"
	"github.com/joshheinrichs/geosource/server/config"
	_ "github.com/lib/pq"
)

var db *gorm.DB

var ErrInsufficientPermission error = errors.New("Insufficient permission.")

func Init(config *config.Config) (err error) {
	db, err = gorm.Open("postgres", fmt.Sprintf("host=%s dbname=%s user=%s password=%s",
		config.Database.Host, config.Database.Database, config.Database.User, config.Database.Password))
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>Make database config fields optional<commit_after>package transactions

import (
	"errors"
	"fmt"

	"github.com/jinzhu/gorm"
	"github.com/joshheinrichs/geosource/server/config"
	_ "github.com/lib/pq"
)

var db *gorm.DB

var ErrInsufficientPermission error = errors.New("Insufficient permission.")

func Init(config *config.Config) (err error) {
	arguments := ""
	if len(config.Database.Host) > 0 {
		arguments += fmt.Sprintf("host=%s ", config.Database.Host)
	}
	if len(config.Database.Database) > 0 {
		arguments += fmt.Sprintf("dbname=%s ", config.Database.Database)
	}
	if len(config.Database.User) > 0 {
		arguments += fmt.Sprintf("user=%s ", config.Database.User)
	}
	if len(config.Database.Password) > 0 {
		arguments += fmt.Sprintf("password=%s ", config.Database.Password)
	}

	db, err = gorm.Open("postgres", arguments)
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package v2

import (
	"fmt"
	"net/url"
	"testing"
	"time"

	"github.com/coreos/etcd/server"
	"github.com/coreos/etcd/tests"
	"github.com/stretchr/testify/assert"
)

// Ensures that a value can be retrieve for a given key.
//
//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
//   $ curl localhost:4001/v2/keys/foo/bar
//
func TestV2GetKey(t *testing.T) {
	tests.RunServer(func(s *server.Server) {
		v := url.Values{}
		v.Set("value", "XXX")
		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
		tests.ReadBody(resp)
		resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"))
		body := tests.ReadBodyJSON(resp)
		assert.Equal(t, body["action"], "get", "")
		node := body["node"].(map[string]interface{})
		assert.Equal(t, node["key"], "/foo/bar", "")
		assert.Equal(t, node["value"], "XXX", "")
		assert.Equal(t, node["modifiedIndex"], 2, "")
	})
}

// Ensures that a directory of values can be recursively retrieved for a given key.
//
//   $ curl -X PUT localhost:4001/v2/keys/foo/x -d value=XXX
//   $ curl -X PUT localhost:4001/v2/keys/foo/y/z -d value=YYY
//   $ curl localhost:4001/v2/keys/foo -d recursive=true
//
func TestV2GetKeyRecursively(t *testing.T) {
	tests.RunServer(func(s *server.Server) {
		v := url.Values{}
		v.Set("value", "XXX")
		v.Set("ttl", "10")
		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/x"), v)
		tests.ReadBody(resp)

		v.Set("value", "YYY")
		resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/y/z"), v)
		tests.ReadBody(resp)

		resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?recursive=true"))
		body := tests.ReadBodyJSON(resp)
		assert.Equal(t, body["action"], "get", "")
		node := body["node"].(map[string]interface{})
		assert.Equal(t, node["key"], "/foo", "")
		assert.Equal(t, node["dir"], true, "")
		assert.Equal(t, node["modifiedIndex"], 2, "")
		assert.Equal(t, len(node["nodes"].([]interface{})), 2, "")

		node0 := node["nodes"].([]interface{})[0].(map[string]interface{})
		assert.Equal(t, node0["key"], "/foo/x", "")
		assert.Equal(t, node0["value"], "XXX", "")
		assert.Equal(t, node0["ttl"], 10, "")

		node1 := node["nodes"].([]interface{})[1].(map[string]interface{})
		assert.Equal(t, node1["key"], "/foo/y", "")
		assert.Equal(t, node1["dir"], true, "")

		node2 := node1["nodes"].([]interface{})[0].(map[string]interface{})
		assert.Equal(t, node2["key"], "/foo/y/z", "")
		assert.Equal(t, node2["value"], "YYY", "")
	})
}

// Ensures that a watcher can wait for a value to be set and return it to the client.
//
//   $ curl localhost:4001/v2/keys/foo/bar?wait=true
//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
//
func TestV2WatchKey(t *testing.T) {
	tests.RunServer(func(s *server.Server) {
		var body map[string]interface{}
		c := make(chan bool)
		go func() {
			resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true"))
			body = tests.ReadBodyJSON(resp)
			c <- true
		}()

		// Make sure response didn't fire early.
		time.Sleep(1 * time.Millisecond)
		assert.Nil(t, body, "")

		// Set a value.
		v := url.Values{}
		v.Set("value", "XXX")
		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
		tests.ReadBody(resp)

		// A response should follow from the GET above.
		time.Sleep(1 * time.Millisecond)

		select {
		case <-c:

		default:
			t.Fatal("cannot get watch result")
		}

		assert.NotNil(t, body, "")
		assert.Equal(t, body["action"], "set", "")

		node := body["node"].(map[string]interface{})
		assert.Equal(t, node["key"], "/foo/bar", "")
		assert.Equal(t, node["value"], "XXX", "")
		assert.Equal(t, node["modifiedIndex"], 2, "")
	})
}

// Ensures that a watcher can wait for a value to be set after a given index.
//
//   $ curl localhost:4001/v2/keys/foo/bar?wait=true&waitIndex=4
//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY
//
func TestV2WatchKeyWithIndex(t *testing.T) {
	tests.RunServer(func(s *server.Server) {
		var body map[string]interface{}
		c := make(chan bool)
		go func() {
			resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true&waitIndex=3"))
			body = tests.ReadBodyJSON(resp)
			c <- true
		}()

		// Make sure response didn't fire early.
		time.Sleep(1 * time.Millisecond)
		assert.Nil(t, body, "")

		// Set a value (before given index).
		v := url.Values{}
		v.Set("value", "XXX")
		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
		tests.ReadBody(resp)

		// Make sure response didn't fire early.
		time.Sleep(1 * time.Millisecond)
		assert.Nil(t, body, "")

		// Set a value (before given index).
		v.Set("value", "YYY")
		resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
		tests.ReadBody(resp)

		// A response should follow from the GET above.
		time.Sleep(1 * time.Millisecond)

		select {
		case <-c:

		default:
			t.Fatal("cannot get watch result")
		}

		assert.NotNil(t, body, "")
		assert.Equal(t, body["action"], "set", "")

		node := body["node"].(map[string]interface{})
		assert.Equal(t, node["key"], "/foo/bar", "")
		assert.Equal(t, node["value"], "YYY", "")
		assert.Equal(t, node["modifiedIndex"], 3, "")
	})
}

// Ensures that a watcher can wait for a value to be set after a given index.
//
//   $ curl localhost:4001/v2/keys/keyindir/bar?wait=true
//   $ curl -X PUT localhost:4001/v2/keys/keyindir -d dir=true -d ttl=1
//   $ curl -X PUT localhost:4001/v2/keys/keyindir/bar -d value=YYY
//
func TestV2WatchKeyInDir(t *testing.T) {
	tests.RunServer(func(s *server.Server) {
		var body map[string]interface{}
		c := make(chan bool)

		// Set a value (before given index).
		v := url.Values{}
		v.Set("dir", "true")
		v.Set("ttl", "1")
		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/keyindir"), v)
		tests.ReadBody(resp)

		// Set a value (before given index).
		v = url.Values{}
		v.Set("value", "XXX")
		resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/keyindir/bar"), v)
		tests.ReadBody(resp)

		go func() {
			resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/keyindir/bar?wait=true"))
			body = tests.ReadBodyJSON(resp)
			c <- true
		}()

		select {
		case <-c:

		default:
			t.Fatal("cannot get watch result")
		}

		assert.NotNil(t, body, "")
		assert.Equal(t, body["action"], "expire", "")

		node := body["node"].(map[string]interface{})
		assert.Equal(t, node["key"], "/keyindir/bar", "")
		assert.Equal(t, node["value"], "XXX", "")
	})
}
<commit_msg>fix TestV2WatchKeyInDir test<commit_after>package v2

import (
	"fmt"
	"net/url"
	"testing"
	"time"

	"github.com/coreos/etcd/server"
	"github.com/coreos/etcd/tests"
	"github.com/stretchr/testify/assert"
)

// Ensures that a value can be retrieve for a given key.
//
//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
//   $ curl localhost:4001/v2/keys/foo/bar
//
func TestV2GetKey(t *testing.T) {
	tests.RunServer(func(s *server.Server) {
		v := url.Values{}
		v.Set("value", "XXX")
		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
		tests.ReadBody(resp)
		resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"))
		body := tests.ReadBodyJSON(resp)
		assert.Equal(t, body["action"], "get", "")
		node := body["node"].(map[string]interface{})
		assert.Equal(t, node["key"], "/foo/bar", "")
		assert.Equal(t, node["value"], "XXX", "")
		assert.Equal(t, node["modifiedIndex"], 2, "")
	})
}

// Ensures that a directory of values can be recursively retrieved for a given key.
//
//   $ curl -X PUT localhost:4001/v2/keys/foo/x -d value=XXX
//   $ curl -X PUT localhost:4001/v2/keys/foo/y/z -d value=YYY
//   $ curl localhost:4001/v2/keys/foo -d recursive=true
//
func TestV2GetKeyRecursively(t *testing.T) {
	tests.RunServer(func(s *server.Server) {
		v := url.Values{}
		v.Set("value", "XXX")
		v.Set("ttl", "10")
		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/x"), v)
		tests.ReadBody(resp)

		v.Set("value", "YYY")
		resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/y/z"), v)
		tests.ReadBody(resp)

		resp, _ = tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo?recursive=true"))
		body := tests.ReadBodyJSON(resp)
		assert.Equal(t, body["action"], "get", "")
		node := body["node"].(map[string]interface{})
		assert.Equal(t, node["key"], "/foo", "")
		assert.Equal(t, node["dir"], true, "")
		assert.Equal(t, node["modifiedIndex"], 2, "")
		assert.Equal(t, len(node["nodes"].([]interface{})), 2, "")

		node0 := node["nodes"].([]interface{})[0].(map[string]interface{})
		assert.Equal(t, node0["key"], "/foo/x", "")
		assert.Equal(t, node0["value"], "XXX", "")
		assert.Equal(t, node0["ttl"], 10, "")

		node1 := node["nodes"].([]interface{})[1].(map[string]interface{})
		assert.Equal(t, node1["key"], "/foo/y", "")
		assert.Equal(t, node1["dir"], true, "")

		node2 := node1["nodes"].([]interface{})[0].(map[string]interface{})
		assert.Equal(t, node2["key"], "/foo/y/z", "")
		assert.Equal(t, node2["value"], "YYY", "")
	})
}

// Ensures that a watcher can wait for a value to be set and return it to the client.
//
//   $ curl localhost:4001/v2/keys/foo/bar?wait=true
//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
//
func TestV2WatchKey(t *testing.T) {
	tests.RunServer(func(s *server.Server) {
		var body map[string]interface{}
		c := make(chan bool)
		go func() {
			resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true"))
			body = tests.ReadBodyJSON(resp)
			c <- true
		}()

		// Make sure response didn't fire early.
		time.Sleep(1 * time.Millisecond)
		assert.Nil(t, body, "")

		// Set a value.
		v := url.Values{}
		v.Set("value", "XXX")
		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
		tests.ReadBody(resp)

		// A response should follow from the GET above.
		time.Sleep(1 * time.Millisecond)

		select {
		case <-c:

		default:
			t.Fatal("cannot get watch result")
		}

		assert.NotNil(t, body, "")
		assert.Equal(t, body["action"], "set", "")

		node := body["node"].(map[string]interface{})
		assert.Equal(t, node["key"], "/foo/bar", "")
		assert.Equal(t, node["value"], "XXX", "")
		assert.Equal(t, node["modifiedIndex"], 2, "")
	})
}

// Ensures that a watcher can wait for a value to be set after a given index.
//
//   $ curl localhost:4001/v2/keys/foo/bar?wait=true&waitIndex=4
//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=XXX
//   $ curl -X PUT localhost:4001/v2/keys/foo/bar -d value=YYY
//
func TestV2WatchKeyWithIndex(t *testing.T) {
	tests.RunServer(func(s *server.Server) {
		var body map[string]interface{}
		c := make(chan bool)
		go func() {
			resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar?wait=true&waitIndex=3"))
			body = tests.ReadBodyJSON(resp)
			c <- true
		}()

		// Make sure response didn't fire early.
		time.Sleep(1 * time.Millisecond)
		assert.Nil(t, body, "")

		// Set a value (before given index).
		v := url.Values{}
		v.Set("value", "XXX")
		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
		tests.ReadBody(resp)

		// Make sure response didn't fire early.
		time.Sleep(1 * time.Millisecond)
		assert.Nil(t, body, "")

		// Set a value (before given index).
		v.Set("value", "YYY")
		resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/foo/bar"), v)
		tests.ReadBody(resp)

		// A response should follow from the GET above.
		time.Sleep(1 * time.Millisecond)

		select {
		case <-c:

		default:
			t.Fatal("cannot get watch result")
		}

		assert.NotNil(t, body, "")
		assert.Equal(t, body["action"], "set", "")

		node := body["node"].(map[string]interface{})
		assert.Equal(t, node["key"], "/foo/bar", "")
		assert.Equal(t, node["value"], "YYY", "")
		assert.Equal(t, node["modifiedIndex"], 3, "")
	})
}

// Ensures that a watcher can wait for a value to be set after a given index.
//
//   $ curl localhost:4001/v2/keys/keyindir/bar?wait=true
//   $ curl -X PUT localhost:4001/v2/keys/keyindir -d dir=true -d ttl=1
//   $ curl -X PUT localhost:4001/v2/keys/keyindir/bar -d value=YYY
//
func TestV2WatchKeyInDir(t *testing.T) {
	tests.RunServer(func(s *server.Server) {
		var body map[string]interface{}
		c := make(chan bool)

		// Set a value (before given index).
		v := url.Values{}
		v.Set("dir", "true")
		v.Set("ttl", "1")
		resp, _ := tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/keyindir"), v)
		tests.ReadBody(resp)

		// Set a value (before given index).
		v = url.Values{}
		v.Set("value", "XXX")
		resp, _ = tests.PutForm(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/keyindir/bar"), v)
		tests.ReadBody(resp)

		go func() {
			resp, _ := tests.Get(fmt.Sprintf("%s%s", s.URL(), "/v2/keys/keyindir/bar?wait=true"))
			body = tests.ReadBodyJSON(resp)
			c <- true
		}()

		// wait for expiration, we do have a up to 500 millisecond delay
		time.Sleep(1500 * time.Millisecond)

		select {
		case <-c:

		default:
			t.Fatal("cannot get watch result")
		}

		assert.NotNil(t, body, "")
		assert.Equal(t, body["action"], "expire", "")

		node := body["node"].(map[string]interface{})
		assert.Equal(t, node["key"], "/keyindir", "")
	})
}
<|endoftext|>


<commit_before>// Copyright 2014 Frustra. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package bbcode

import (
	"fmt"
	"html"
	"net/url"
	"regexp"
	"strconv"
	"strings"
)

type htmlTag struct {
	name     string
	value    string
	attrs    map[string]string
	children []*htmlTag
}

func newHtmlTag(value string) *htmlTag {
	return &htmlTag{
		value:    value,
		attrs:    make(map[string]string),
		children: make([]*htmlTag, 0),
	}
}

func (t *htmlTag) string() string {
	var value string
	if t.value != "" {
		value = sanitize(t.value)
	}
	var attrString string
	for key, value := range t.attrs {
		attrString = fmt.Sprintf(`%s %s="%s"`, attrString, key, escapeQuotes(sanitize(value)))
	}
	if len(t.children) > 0 {
		var childrenString string
		for _, child := range t.children {
			childrenString = fmt.Sprint(childrenString, child.string())
		}
		if t.name != "" {
			return fmt.Sprintf(`%s<%s%s>%s</%s>`, value, t.name, attrString, childrenString, t.name)
		} else {
			return fmt.Sprint(value, childrenString)
		}
	} else if t.name != "" {
		return fmt.Sprintf(`%s<%s%s>`, value, t.name, attrString)
	} else {
		return value
	}
}

func (t *htmlTag) appendChild(child *htmlTag) *htmlTag {
	if child == nil {
		t.children = append(t.children, newHtmlTag(""))
	} else {
		t.children = append(t.children, child)
	}
	return t
}

var tagMap = map[string]string{
	"quote":  "blockquote",
	"strike": "s",
}
var youtubeRegex = regexp.MustCompile(`(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?([a-zA-Z0-9]+)`)

// compile transforms a tag and subexpression into an HTML string.
// It is only used by the generated parser code.
func compile(in bbTag, expr *htmlTag) *htmlTag {
	var out = newHtmlTag("")

	switch in.key {
	case "url":
		out.name = "a"
		if in.value == "" {
			if expr != nil {
				out.attrs["href"] = safeURL(expr.value)
			} else {
				out.attrs["href"] = ""
			}
		} else {
			out.attrs["href"] = safeURL(in.value)
		}
		out.appendChild(expr)
	case "img":
		out.name = "img"
		if in.value == "" {
			if expr != nil {
				out.attrs["src"] = safeURL(expr.value)
			} else {
				out.attrs["src"] = ""
			}
		} else {
			out.attrs["src"] = safeURL(in.value)
			if expr != nil {
				out.attrs["alt"] = expr.value
			}
		}
	case "media":
		if expr == nil {
			out.value = "Embedded video"
		} else {
			matches := youtubeRegex.FindStringSubmatch(expr.value)
			if matches == nil {
				out.value = "Embedded video"
			} else {
				out.name = "object"
				out.attrs["width"] = "620"
				out.attrs["height"] = "349"

				params := map[string]string{
					"movie":             fmt.Sprintf("//www.youtube.com/v/%s?version=3", matches[1]),
					"wmode":             "transparent",
					"allowFullScreen":   "true",
					"allowscriptaccess": "always",
				}

				embed := newHtmlTag("")
				embed.name = "embed"
				embed.attrs["type"] = "application/x-shockwave-flash"
				embed.attrs["width"] = "620"
				embed.attrs["height"] = "349"
				for name, value := range params {
					param := newHtmlTag("")
					param.name = "param"
					param.attrs["name"] = name
					param.attrs["value"] = value
					out.appendChild(param)

					if name == "movie" {
						name = "src"
					}
					embed.attrs[name] = value
				}
				out.appendChild(embed)
			}
		}
	case "center":
		out.name = "div"
		out.attrs["style"] = "text-align: center;"
		out.appendChild(expr)
	case "color":
		return expr
	case "size":
		out.name = "span"
		if size, err := strconv.Atoi(in.value); err == nil {
			out.attrs["style"] = fmt.Sprintf("font-size: %dpx;", size*4)
		}
		out.appendChild(expr)
	case "spoiler":
		out.name = "div"
		out.attrs["class"] = "spoiler-tag"
		out.appendChild(expr)
	case "quote", "strike":
		out.name = tagMap[in.key]
		out.appendChild(expr)
	case "i", "b", "u", "code":
		out.name = in.key
		out.appendChild(expr)
	}
	return out
}

func newline() *htmlTag {
	var out = newHtmlTag("")
	out.name = "br"
	return out
}

func escapeQuotes(raw string) string {
	return strings.Replace(strings.Replace(raw, `"`, `\"`, -1), `\`, `\\`, -1)
}

func safeURL(raw string) string {
	u, err := url.Parse(raw)
	if err != nil {
		return ""
	}
	return strings.Replace(u.String(), `\`, "%5C", -1)
}

func sanitize(raw string) string {
	return html.EscapeString(raw)
}
<commit_msg>Update media tag and spoiler tag html<commit_after>// Copyright 2014 Frustra. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package bbcode

import (
	"fmt"
	"html"
	"net/url"
	"regexp"
	"strconv"
	"strings"
)

type htmlTag struct {
	name     string
	value    string
	attrs    map[string]string
	children []*htmlTag
}

func newHtmlTag(value string) *htmlTag {
	return &htmlTag{
		value:    value,
		attrs:    make(map[string]string),
		children: make([]*htmlTag, 0),
	}
}

func (t *htmlTag) string() string {
	var value string
	if t.value != "" {
		value = sanitize(t.value)
	}
	var attrString string
	for key, value := range t.attrs {
		attrString = fmt.Sprintf(`%s %s="%s"`, attrString, key, escapeQuotes(sanitize(value)))
	}
	if len(t.children) > 0 {
		var childrenString string
		for _, child := range t.children {
			childrenString = fmt.Sprint(childrenString, child.string())
		}
		if t.name != "" {
			return fmt.Sprintf(`%s<%s%s>%s</%s>`, value, t.name, attrString, childrenString, t.name)
		} else {
			return fmt.Sprint(value, childrenString)
		}
	} else if t.name != "" {
		return fmt.Sprintf(`%s<%s%s>`, value, t.name, attrString)
	} else {
		return value
	}
}

func (t *htmlTag) appendChild(child *htmlTag) *htmlTag {
	if child == nil {
		t.children = append(t.children, newHtmlTag(""))
	} else {
		t.children = append(t.children, child)
	}
	return t
}

var youtubeRegex = regexp.MustCompile(`(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?([a-zA-Z0-9]+)`)

// compile transforms a tag and subexpression into an HTML string.
// It is only used by the generated parser code.
func compile(in bbTag, expr *htmlTag) *htmlTag {
	var out = newHtmlTag("")

	switch in.key {
	case "url":
		out.name = "a"
		if in.value == "" {
			if expr != nil {
				out.attrs["href"] = safeURL(expr.value)
			} else {
				out.attrs["href"] = ""
			}
		} else {
			out.attrs["href"] = safeURL(in.value)
		}
		out.appendChild(expr)
	case "img":
		out.name = "img"
		if in.value == "" {
			if expr != nil {
				out.attrs["src"] = safeURL(expr.value)
			} else {
				out.attrs["src"] = ""
			}
		} else {
			out.attrs["src"] = safeURL(in.value)
			if expr != nil {
				out.attrs["alt"] = expr.value
			}
		}
	case "media":
		if expr == nil {
			out.value = "Embedded video"
		} else {
			out.name = "div"
			out.attrs["class"] = "embedded-video"

			obj := newHtmlTag("Embedded video")
			out.appendChild(obj)

			matches := youtubeRegex.FindStringSubmatch(expr.value)
			if matches != nil {
				obj = newHtmlTag("")
				obj.name = "object"
				obj.attrs["width"] = "620"
				obj.attrs["height"] = "349"

				params := map[string]string{
					"movie":             fmt.Sprintf("//www.youtube.com/v/%s?version=3", matches[1]),
					"wmode":             "transparent",
					"allowFullScreen":   "true",
					"allowscriptaccess": "always",
				}

				embed := newHtmlTag("")
				embed.name = "embed"
				embed.attrs["type"] = "application/x-shockwave-flash"
				embed.attrs["width"] = "620"
				embed.attrs["height"] = "349"
				for name, value := range params {
					param := newHtmlTag("")
					param.name = "param"
					param.attrs["name"] = name
					param.attrs["value"] = value
					obj.appendChild(param)

					if name == "movie" {
						name = "src"
					}
					embed.attrs[name] = value
				}
				obj.appendChild(embed)
				out.appendChild(obj)
			}
		}
	case "center":
		out.name = "div"
		out.attrs["style"] = "text-align: center;"
		out.appendChild(expr)
	case "color":
		return expr
	case "size":
		out.name = "span"
		if size, err := strconv.Atoi(in.value); err == nil {
			out.attrs["style"] = fmt.Sprintf("font-size: %dpx;", size*4)
		}
		out.appendChild(expr)
	case "spoiler":
		out.name = "div"
		out.attrs["class"] = "expandable collapsed"
		out.appendChild(expr)
	case "quote":
		out.name = "blockquote"
		who := ""
		if name, ok := in.args["name"]; ok && name != "" {
			who = name
		} else {
			who = in.value
		}
		if who != "" {
			cite := newHtmlTag("")
			cite.name = "cite"
			cite.appendChild(newHtmlTag(who + " said:"))
			out.appendChild(cite)
		}
		out.appendChild(expr)
	case "strike":
		out.name = "s"
		out.appendChild(expr)
	case "i", "b", "u", "code":
		out.name = in.key
		out.appendChild(expr)
	}
	return out
}

func newline() *htmlTag {
	var out = newHtmlTag("")
	out.name = "br"
	return out
}

func escapeQuotes(raw string) string {
	return strings.Replace(strings.Replace(raw, `"`, `\"`, -1), `\`, `\\`, -1)
}

func safeURL(raw string) string {
	u, err := url.Parse(raw)
	if err != nil {
		return ""
	}
	return strings.Replace(u.String(), `\`, "%5C", -1)
}

func sanitize(raw string) string {
	return html.EscapeString(raw)
}
<|endoftext|>