<commit_before>package kiwi

// This file consists of Sink related structures and functions.
// Outputs accepts incoming log records from Loggers, check them with filters
// and write to output streams if checks passed.

/* Copyright (c) 2016-2019, Alexander I.Grafov <grafov@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of kvlog nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

ॐ तारे तुत्तारे तुरे स्व */

import (
	"io"
	"sync"
	"sync/atomic"
	"time"
)

// States of the sink.
const (
	sinkClosed int32 = iota - 1
	sinkStopped
	sinkActive
)

// Sinks accepts records through the chanels.
// Each sink has its own channel.
var collector struct {
	sync.RWMutex
	sinks []*Sink
	count int
}

type (
	// Sink used for filtering incoming log records from all logger instances
	// and decides how to filter them. Each output wraps its own io.Writer.
	// Sink methods are safe for concurrent usage.
	Sink struct {
		id     uint
		In     chan chain
		close  chan struct{}
		writer io.Writer
		format Formatter
		state  *int32

		sync.RWMutex
		positiveFilters map[string]Filter
		negativeFilters map[string]Filter
		hiddenKeys      map[string]bool
	}
	chain struct {
		wg    *sync.WaitGroup
		pairs []*Pair
	}
)

// SinkTo creates a new sink for an arbitrary number of loggers.
// There are any number of sinks may be created for saving incoming log
// records to different places.
// The sink requires explicit start with Start() before usage.
// That allows firstly setup filters before sink will really accept any records.
func SinkTo(w io.Writer, fn Formatter) *Sink {
	collector.RLock()
	for i, sink := range collector.sinks {
		if sink.writer == w {
			collector.sinks[i].format = fn
			collector.RUnlock()
			return collector.sinks[i]
		}
	}
	collector.RUnlock()
	var (
		state = sinkStopped
		sink  = &Sink{
			In:              make(chan chain, 16),
			close:           make(chan struct{}),
			format:          fn,
			state:           &state,
			writer:          w,
			positiveFilters: make(map[string]Filter),
			negativeFilters: make(map[string]Filter),
			hiddenKeys:      make(map[string]bool),
		}
	)
	collector.Lock()
	sink.id = uint(collector.count)
	collector.sinks = append(collector.sinks, sink)
	collector.count++
	collector.Unlock()
	go processSink(sink)
	return sink
}

// HasKey sets restriction for records output.
// Only the records WITH any of the keys will be passed to output.
func (s *Sink) HasKey(keys ...string) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		for _, key := range keys {
			s.positiveFilters[key] = &keyFilter{}
			delete(s.negativeFilters, key)
		}
		s.Unlock()
	}
	return s
}

// HasNotKey sets restriction for records output.
// Only the records WITHOUT any of the keys will be passed to output.
func (s *Sink) HasNotKey(keys ...string) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		for _, key := range keys {
			s.negativeFilters[key] = &keyFilter{}
			delete(s.positiveFilters, key)
		}
		s.Unlock()
	}
	return s
}

// HasValue sets restriction for records output.
// A record passed to output if the key equal one of any of the listed values.
func (s *Sink) HasValue(key string, vals ...string) *Sink {
	if len(vals) == 0 {
		return s.HasKey(key)
	}
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		s.positiveFilters[key] = &valsFilter{Vals: vals}
		delete(s.negativeFilters, key)
		s.Unlock()
	}
	return s
}

// HasNotValue sets restriction for records output.
func (s *Sink) HasNotValue(key string, vals ...string) *Sink {
	if len(vals) == 0 {
		return s.HasNotKey(key)
	}
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		s.negativeFilters[key] = &valsFilter{Vals: vals}
		delete(s.positiveFilters, key)
		s.Unlock()
	}
	return s
}

// Int64Range sets restriction for records output.
func (s *Sink) Int64Range(key string, from, to int64) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.negativeFilters, key)
		s.positiveFilters[key] = &int64RangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// Int64NotRange sets restriction for records output.
func (s *Sink) Int64NotRange(key string, from, to int64) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.positiveFilters, key)
		s.negativeFilters[key] = &int64RangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// Float64Range sets restriction for records output.
func (s *Sink) Float64Range(key string, from, to float64) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.negativeFilters, key)
		s.positiveFilters[key] = &float64RangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// Float64NotRange sets restriction for records output.
func (s *Sink) Float64NotRange(key string, from, to float64) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.positiveFilters, key)
		s.negativeFilters[key] = &float64RangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// TimeRange sets restriction for records output.
func (s *Sink) TimeRange(key string, from, to time.Time) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.negativeFilters, key)
		s.positiveFilters[key] = &timeRangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// TimeNotRange sets restriction for records output.
func (s *Sink) TimeNotRange(key string, from, to time.Time) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.positiveFilters, key)
		s.negativeFilters[key] = &timeRangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// WithFilter setup custom filtering function for values for a specific key.
// Custom filter should realize Filter interface. All custom filters treated
// as positive filters. So if the filter returns true then it will be passed.
func (s *Sink) WithFilter(key string, customFilter Filter) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.negativeFilters, key)
		s.positiveFilters[key] = customFilter
		s.Unlock()
	}
	return s
}

// Reset all filters for the keys for the output.
func (s *Sink) Reset(keys ...string) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		for _, key := range keys {
			delete(s.positiveFilters, key)
			delete(s.negativeFilters, key)
		}
		s.Unlock()
	}
	return s
}

// Hide keys from the output. Other keys in record will be displayed
// but not hidden keys.
func (s *Sink) Hide(keys ...string) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		for _, key := range keys {
			s.hiddenKeys[key] = true
		}
		s.Unlock()
	}
	return s
}

// Unhide previously hidden keys. They will be displayed in the output again.
func (s *Sink) Unhide(keys ...string) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		for _, key := range keys {
			delete(s.hiddenKeys, key)
		}
		s.Unlock()
	}
	return s
}

// Stop stops writing to the output.
func (s *Sink) Stop() *Sink {
	atomic.StoreInt32(s.state, sinkStopped)
	return s
}

// Start writing to the output.
// After creation of a new sink it will paused and you need explicitly start it.
// It allows setup the filters before the sink will accepts any records.
func (s *Sink) Start() *Sink {
	atomic.StoreInt32(s.state, sinkActive)
	return s
}

// Close closes the sink. It flushes records for the sink before closing.
func (s *Sink) Close() {
	return
	if atomic.LoadInt32(s.state) > sinkClosed {
		atomic.StoreInt32(s.state, sinkClosed)
		s.close <- struct{}{}
		collector.Lock()
		collector.count--
		collector.sinks = append(collector.sinks[0:s.id], collector.sinks[s.id+1:]...)
		collector.Unlock()
	}
}

func processSink(s *Sink) {
	var (
		record chain
		ok     bool
	)
	for {
		select {
		case record, ok = <-s.In:
			if !ok {
				return
			}
			if atomic.LoadInt32(s.state) < sinkActive {
				record.wg.Done()
				continue
			}
			s.RLock()
			var filter Filter
			for _, pair := range record.pairs {
				// Negative conditions have highest priority
				if filter, ok = s.negativeFilters[pair.Key]; ok {
					if filter.Check(pair.Key, pair.Val) {
						goto skipRecord
					}
				}
				// At last check for positive conditions
				if filter, ok = s.positiveFilters[pair.Key]; ok {
					if !filter.Check(pair.Key, pair.Val) {
						goto skipRecord
					}
				}
			}
			s.formatRecord(record.pairs)
		skipRecord:
			s.RUnlock()
			record.wg.Done()
		case <-s.close:
			s.Lock()
			s.positiveFilters = nil
			s.negativeFilters = nil
			s.hiddenKeys = nil
			s.Unlock()
			return
		}
	}
}

func (s *Sink) formatRecord(record []*Pair) {
	s.format.Begin()
	for _, pair := range record {
		if ok := s.hiddenKeys[pair.Key]; ok {
			continue
		}
		s.format.Pair(pair.Key, pair.Val, pair.Type)
	}
	s.writer.Write(s.format.Finish())
}

const flushTimeout = 3 * time.Second

func sinkRecord(rec []*Pair) {
	var wg sync.WaitGroup
	collector.RLock()
	for _, s := range collector.sinks {
		if atomic.LoadInt32(s.state) == sinkActive {
			wg.Add(1)
			s.In <- chain{&wg, rec}
		}
	}
	collector.RUnlock()
	var c = make(chan struct{})
	go func() {
		defer close(c)
		wg.Wait()
	}()
	select {
	case <-c:
		return
	case <-time.After(flushTimeout):
		return
	}
}
<commit_msg>Fix (*Sink).Close()<commit_after>package kiwi

// This file consists of Sink related structures and functions.
// Outputs accepts incoming log records from Loggers, check them with filters
// and write to output streams if checks passed.

/* Copyright (c) 2016-2019, Alexander I.Grafov <grafov@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

* Neither the name of kvlog nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

ॐ तारे तुत्तारे तुरे स्व */

import (
	"io"
	"sync"
	"sync/atomic"
	"time"
)

// States of the sink.
const (
	sinkClosed int32 = iota - 1
	sinkStopped
	sinkActive
)

// Sinks accepts records through the chanels.
// Each sink has its own channel.
var collector struct {
	sync.RWMutex
	sinks []*Sink
	count int
}

type (
	// Sink used for filtering incoming log records from all logger instances
	// and decides how to filter them. Each output wraps its own io.Writer.
	// Sink methods are safe for concurrent usage.
	Sink struct {
		id     uint
		In     chan chain
		close  chan struct{}
		writer io.Writer
		format Formatter
		state  *int32

		sync.RWMutex
		positiveFilters map[string]Filter
		negativeFilters map[string]Filter
		hiddenKeys      map[string]bool
	}
	chain struct {
		wg    *sync.WaitGroup
		pairs []*Pair
	}
)

// SinkTo creates a new sink for an arbitrary number of loggers.
// There are any number of sinks may be created for saving incoming log
// records to different places.
// The sink requires explicit start with Start() before usage.
// That allows firstly setup filters before sink will really accept any records.
func SinkTo(w io.Writer, fn Formatter) *Sink {
	collector.RLock()
	for i, sink := range collector.sinks {
		if sink.writer == w {
			collector.sinks[i].format = fn
			collector.RUnlock()
			return collector.sinks[i]
		}
	}
	collector.RUnlock()
	var (
		state = sinkStopped
		sink  = &Sink{
			In:              make(chan chain, 16),
			close:           make(chan struct{}),
			format:          fn,
			state:           &state,
			writer:          w,
			positiveFilters: make(map[string]Filter),
			negativeFilters: make(map[string]Filter),
			hiddenKeys:      make(map[string]bool),
		}
	)
	collector.Lock()
	sink.id = uint(collector.count)
	collector.sinks = append(collector.sinks, sink)
	collector.count++
	collector.Unlock()
	go processSink(sink)
	return sink
}

// HasKey sets restriction for records output.
// Only the records WITH any of the keys will be passed to output.
func (s *Sink) HasKey(keys ...string) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		for _, key := range keys {
			s.positiveFilters[key] = &keyFilter{}
			delete(s.negativeFilters, key)
		}
		s.Unlock()
	}
	return s
}

// HasNotKey sets restriction for records output.
// Only the records WITHOUT any of the keys will be passed to output.
func (s *Sink) HasNotKey(keys ...string) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		for _, key := range keys {
			s.negativeFilters[key] = &keyFilter{}
			delete(s.positiveFilters, key)
		}
		s.Unlock()
	}
	return s
}

// HasValue sets restriction for records output.
// A record passed to output if the key equal one of any of the listed values.
func (s *Sink) HasValue(key string, vals ...string) *Sink {
	if len(vals) == 0 {
		return s.HasKey(key)
	}
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		s.positiveFilters[key] = &valsFilter{Vals: vals}
		delete(s.negativeFilters, key)
		s.Unlock()
	}
	return s
}

// HasNotValue sets restriction for records output.
func (s *Sink) HasNotValue(key string, vals ...string) *Sink {
	if len(vals) == 0 {
		return s.HasNotKey(key)
	}
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		s.negativeFilters[key] = &valsFilter{Vals: vals}
		delete(s.positiveFilters, key)
		s.Unlock()
	}
	return s
}

// Int64Range sets restriction for records output.
func (s *Sink) Int64Range(key string, from, to int64) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.negativeFilters, key)
		s.positiveFilters[key] = &int64RangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// Int64NotRange sets restriction for records output.
func (s *Sink) Int64NotRange(key string, from, to int64) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.positiveFilters, key)
		s.negativeFilters[key] = &int64RangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// Float64Range sets restriction for records output.
func (s *Sink) Float64Range(key string, from, to float64) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.negativeFilters, key)
		s.positiveFilters[key] = &float64RangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// Float64NotRange sets restriction for records output.
func (s *Sink) Float64NotRange(key string, from, to float64) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.positiveFilters, key)
		s.negativeFilters[key] = &float64RangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// TimeRange sets restriction for records output.
func (s *Sink) TimeRange(key string, from, to time.Time) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.negativeFilters, key)
		s.positiveFilters[key] = &timeRangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// TimeNotRange sets restriction for records output.
func (s *Sink) TimeNotRange(key string, from, to time.Time) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.positiveFilters, key)
		s.negativeFilters[key] = &timeRangeFilter{From: from, To: to}
		s.Unlock()
	}
	return s
}

// WithFilter setup custom filtering function for values for a specific key.
// Custom filter should realize Filter interface. All custom filters treated
// as positive filters. So if the filter returns true then it will be passed.
func (s *Sink) WithFilter(key string, customFilter Filter) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		delete(s.negativeFilters, key)
		s.positiveFilters[key] = customFilter
		s.Unlock()
	}
	return s
}

// Reset all filters for the keys for the output.
func (s *Sink) Reset(keys ...string) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		for _, key := range keys {
			delete(s.positiveFilters, key)
			delete(s.negativeFilters, key)
		}
		s.Unlock()
	}
	return s
}

// Hide keys from the output. Other keys in record will be displayed
// but not hidden keys.
func (s *Sink) Hide(keys ...string) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		for _, key := range keys {
			s.hiddenKeys[key] = true
		}
		s.Unlock()
	}
	return s
}

// Unhide previously hidden keys. They will be displayed in the output again.
func (s *Sink) Unhide(keys ...string) *Sink {
	if atomic.LoadInt32(s.state) > sinkClosed {
		s.Lock()
		for _, key := range keys {
			delete(s.hiddenKeys, key)
		}
		s.Unlock()
	}
	return s
}

// Stop stops writing to the output.
func (s *Sink) Stop() *Sink {
	atomic.StoreInt32(s.state, sinkStopped)
	return s
}

// Start writing to the output.
// After creation of a new sink it will paused and you need explicitly start it.
// It allows setup the filters before the sink will accepts any records.
func (s *Sink) Start() *Sink {
	atomic.StoreInt32(s.state, sinkActive)
	return s
}

// Close closes the sink. It flushes records for the sink before closing.
func (s *Sink) Close() {
	if atomic.LoadInt32(s.state) > sinkClosed {
		atomic.StoreInt32(s.state, sinkClosed)
		s.close <- struct{}{}
		collector.Lock()
		collector.count--
		collector.sinks = append(collector.sinks[0:s.id], collector.sinks[s.id+1:]...)
		collector.Unlock()
	}
}

func processSink(s *Sink) {
	var (
		record chain
		ok     bool
	)
	for {
		select {
		case record, ok = <-s.In:
			if !ok {
				return
			}
			if atomic.LoadInt32(s.state) < sinkActive {
				record.wg.Done()
				continue
			}
			s.RLock()
			var filter Filter
			for _, pair := range record.pairs {
				// Negative conditions have highest priority
				if filter, ok = s.negativeFilters[pair.Key]; ok {
					if filter.Check(pair.Key, pair.Val) {
						goto skipRecord
					}
				}
				// At last check for positive conditions
				if filter, ok = s.positiveFilters[pair.Key]; ok {
					if !filter.Check(pair.Key, pair.Val) {
						goto skipRecord
					}
				}
			}
			s.formatRecord(record.pairs)
		skipRecord:
			s.RUnlock()
			record.wg.Done()
		case <-s.close:
			s.Lock()
			s.positiveFilters = nil
			s.negativeFilters = nil
			s.hiddenKeys = nil
			s.Unlock()
			return
		}
	}
}

func (s *Sink) formatRecord(record []*Pair) {
	s.format.Begin()
	for _, pair := range record {
		if ok := s.hiddenKeys[pair.Key]; ok {
			continue
		}
		s.format.Pair(pair.Key, pair.Val, pair.Type)
	}
	s.writer.Write(s.format.Finish())
}

const flushTimeout = 3 * time.Second

func sinkRecord(rec []*Pair) {
	var wg sync.WaitGroup
	collector.RLock()
	for _, s := range collector.sinks {
		if atomic.LoadInt32(s.state) == sinkActive {
			wg.Add(1)
			s.In <- chain{&wg, rec}
		}
	}
	collector.RUnlock()
	var c = make(chan struct{})
	go func() {
		defer close(c)
		wg.Wait()
	}()
	select {
	case <-c:
		return
	case <-time.After(flushTimeout):
		return
	}
}
<|endoftext|>


<commit_before>package main

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

	"github.com/phrase/phraseapp-client/internal/paths"
	"github.com/phrase/phraseapp-client/internal/placeholders"
	"github.com/phrase/phraseapp-client/internal/print"
	"github.com/phrase/phraseapp-go/phraseapp"
)

const (
	timeoutInMinutes = 30
)

type PullCommand struct {
	phraseapp.Config
	Branch string `cli:"opt --branch"`
}

func (cmd *PullCommand) Run() error {
	if cmd.Config.Debug {
		// suppresses content output
		cmd.Config.Debug = false
		Debug = true
	}
	client, err := newClient(cmd.Config.Credentials, cmd.Config.Debug)
	if err != nil {
		return err
	}

	targets, err := TargetsFromConfig(cmd.Config)
	if err != nil {
		return err
	}

	projectIdToLocales, err := LocalesForProjects(client, targets, cmd.Branch)
	if err != nil {
		return err
	}

	for _, target := range targets {
		val, ok := projectIdToLocales[LocaleCacheKey{target.ProjectID, cmd.Branch}]
		if !ok || len(val) == 0 {
			return fmt.Errorf("Could not find any locales for project %q", target.ProjectID)
		}
		target.RemoteLocales = val
	}

	for _, target := range targets {
		err := target.Pull(client, cmd.Branch)
		if err != nil {
			return err
		}
	}

	return nil
}

type PullParams struct {
	phraseapp.LocaleDownloadParams
	LocaleID string
}

func (target *Target) Pull(client *phraseapp.Client, branch string) error {
	if err := target.CheckPreconditions(); err != nil {
		return err
	}

	localeFiles, err := target.LocaleFiles()
	if err != nil {
		return err
	}

	startedAt := time.Now()
	for _, localeFile := range localeFiles {
		if time.Now().Sub(startedAt).Minutes() >= timeoutInMinutes {
			return fmt.Errorf("Timeout of %d minutes exceeded", timeoutInMinutes)
		}

		err := createFile(localeFile.Path)
		if err != nil {
			return err
		}

		err = target.DownloadAndWriteToFile(client, localeFile, branch)
		if err != nil {
			return fmt.Errorf("%s for %s", err, localeFile.Path)
		} else {
			print.Success("Downloaded %s to %s", localeFile.Message(), localeFile.RelPath())
		}
		if Debug {
			fmt.Fprintln(os.Stderr, strings.Repeat("-", 10))
		}
	}

	return nil
}

func (target *Target) DownloadAndWriteToFile(client *phraseapp.Client, localeFile *LocaleFile, branch string) error {
	downloadParams := &phraseapp.LocaleDownloadParams{Branch: &branch}
	if target.Params != nil {
		*downloadParams = target.Params.LocaleDownloadParams
		downloadParams.Branch = &branch
	}

	if downloadParams.FileFormat == nil {
		downloadParams.FileFormat = &localeFile.FileFormat
	}

	if Debug {
		fmt.Fprintln(os.Stderr, "Target file pattern:", target.File)
		fmt.Fprintln(os.Stderr, "Actual file path", localeFile.Path)
		fmt.Fprintln(os.Stderr, "LocaleID", localeFile.ID)
		fmt.Fprintln(os.Stderr, "ProjectID", target.ProjectID)
		fmt.Fprintln(os.Stderr, "FileFormat", downloadParams.FileFormat)
		fmt.Fprintln(os.Stderr, "ConvertEmoji", downloadParams.ConvertEmoji)
		fmt.Fprintln(os.Stderr, "IncludeEmptyTranslations", downloadParams.IncludeEmptyTranslations)
		fmt.Fprintln(os.Stderr, "KeepNotranslateTags", downloadParams.KeepNotranslateTags)
		fmt.Fprintln(os.Stderr, "Tag", downloadParams.Tag)
		fmt.Fprintln(os.Stderr, "FormatOptions", downloadParams.FormatOptions)
	}

	res, err := client.LocaleDownload(target.ProjectID, localeFile.ID, downloadParams)
	if err != nil {
		if rateLimitError, ok := (err).(*phraseapp.RateLimitingError); ok {
			checkRateLimit(rateLimitError)
		} else {
			return err
		}
	}

	err = ioutil.WriteFile(localeFile.Path, res, 0700)
	return err
}

func (target *Target) LocaleFiles() (LocaleFiles, error) {
	files := []*LocaleFile{}

	if target.GetLocaleID() != "" {
		// a specific locale was requested
		remoteLocale, err := target.localeForRemote()
		if err != nil {
			return nil, err
		}

		localeFile, err := createLocaleFile(target, remoteLocale)
		if err != nil {
			return nil, err
		}

		files = append(files, localeFile)

	} else if placeholders.ContainsLocalePlaceholder(target.File) {
		// multiple locales were requested
		for _, remoteLocale := range target.RemoteLocales {
			localeFile, err := createLocaleFile(target, remoteLocale)
			if err != nil {
				return nil, err
			}

			files = append(files, localeFile)
		}
	} else {
		// no local files match remote locale
		return nil, fmt.Errorf("Could not find any files on your system that matches the locales for porject %q.", target.ProjectID)
	}

	return files, nil
}

func checkRateLimit(rateLimitError *phraseapp.RateLimitingError) {
	if rateLimitError.Remaining == 0 {
		reset := rateLimitError.Reset
		resetTime := reset.Add(time.Second * 1).Sub(time.Now())
		fmt.Printf("Rate limit exceeded. Download will resume in %d seconds\n", int64(resetTime.Seconds()))
		time.Sleep(resetTime)
	}
}

func createLocaleFile(target *Target, remoteLocale *phraseapp.Locale) (*LocaleFile, error) {
	localeFile := &LocaleFile{
		Name:       remoteLocale.Name,
		ID:         remoteLocale.ID,
		Code:       remoteLocale.Code,
		Tag:        target.GetTag(),
		FileFormat: target.GetFormat(),
		Path:       target.File,
	}

	absPath, err := target.ReplacePlaceholders(localeFile)
	if err != nil {
		return nil, err
	}

	localeFile.Path = absPath
	return localeFile, nil
}

func createFile(path string) error {
	err := paths.Exists(path)
	if err != nil {
		absDir := filepath.Dir(path)
		err := paths.Exists(absDir)
		if err != nil {
			os.MkdirAll(absDir, 0700)
		}

		f, err := os.Create(path)
		if err != nil {
			return err
		}
		defer f.Close()
	}

	return nil
}
<commit_msg>Retry download after rate limit expired<commit_after>package main

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

	"github.com/phrase/phraseapp-client/internal/paths"
	"github.com/phrase/phraseapp-client/internal/placeholders"
	"github.com/phrase/phraseapp-client/internal/print"
	"github.com/phrase/phraseapp-go/phraseapp"
)

const (
	timeoutInMinutes = 30 * time.Minute
)

type PullCommand struct {
	phraseapp.Config
	Branch string `cli:"opt --branch"`
}

func (cmd *PullCommand) Run() error {
	if cmd.Config.Debug {
		// suppresses content output
		cmd.Config.Debug = false
		Debug = true
	}
	client, err := newClient(cmd.Config.Credentials, cmd.Config.Debug)
	if err != nil {
		return err
	}

	targets, err := TargetsFromConfig(cmd.Config)
	if err != nil {
		return err
	}

	projectIdToLocales, err := LocalesForProjects(client, targets, cmd.Branch)
	if err != nil {
		return err
	}

	for _, target := range targets {
		val, ok := projectIdToLocales[LocaleCacheKey{target.ProjectID, cmd.Branch}]
		if !ok || len(val) == 0 {
			return fmt.Errorf("Could not find any locales for project %q", target.ProjectID)
		}
		target.RemoteLocales = val
	}

	for _, target := range targets {
		err := target.Pull(client, cmd.Branch)
		if err != nil {
			return err
		}
	}

	return nil
}

type PullParams struct {
	phraseapp.LocaleDownloadParams
	LocaleID string
}

func (target *Target) Pull(client *phraseapp.Client, branch string) error {
	if err := target.CheckPreconditions(); err != nil {
		return err
	}

	localeFiles, err := target.LocaleFiles()
	if err != nil {
		return err
	}

	startedAt := time.Now()
	for _, localeFile := range localeFiles {
		if time.Since(startedAt) >= timeoutInMinutes {
			return fmt.Errorf("Timeout of %d minutes exceeded", timeoutInMinutes)
		}

		err := createFile(localeFile.Path)
		if err != nil {
			return err
		}

		err = target.DownloadAndWriteToFile(client, localeFile, branch)
		if err != nil {
			return fmt.Errorf("%s for %s", err, localeFile.Path)
		} else {
			print.Success("Downloaded %s to %s", localeFile.Message(), localeFile.RelPath())
		}
		if Debug {
			fmt.Fprintln(os.Stderr, strings.Repeat("-", 10))
		}
	}

	return nil
}

func (target *Target) DownloadAndWriteToFile(client *phraseapp.Client, localeFile *LocaleFile, branch string) error {
	downloadParams := &phraseapp.LocaleDownloadParams{Branch: &branch}
	if target.Params != nil {
		*downloadParams = target.Params.LocaleDownloadParams
		downloadParams.Branch = &branch
	}

	if downloadParams.FileFormat == nil {
		downloadParams.FileFormat = &localeFile.FileFormat
	}

	if Debug {
		fmt.Fprintln(os.Stderr, "Target file pattern:", target.File)
		fmt.Fprintln(os.Stderr, "Actual file path", localeFile.Path)
		fmt.Fprintln(os.Stderr, "LocaleID", localeFile.ID)
		fmt.Fprintln(os.Stderr, "ProjectID", target.ProjectID)
		fmt.Fprintln(os.Stderr, "FileFormat", downloadParams.FileFormat)
		fmt.Fprintln(os.Stderr, "ConvertEmoji", downloadParams.ConvertEmoji)
		fmt.Fprintln(os.Stderr, "IncludeEmptyTranslations", downloadParams.IncludeEmptyTranslations)
		fmt.Fprintln(os.Stderr, "KeepNotranslateTags", downloadParams.KeepNotranslateTags)
		fmt.Fprintln(os.Stderr, "Tag", downloadParams.Tag)
		fmt.Fprintln(os.Stderr, "FormatOptions", downloadParams.FormatOptions)
	}

	res, err := client.LocaleDownload(target.ProjectID, localeFile.ID, downloadParams)
	if err != nil {
		if rateLimitError, ok := (err).(*phraseapp.RateLimitingError); ok {
			waitForRateLimit(rateLimitError)
			res, err = client.LocaleDownload(target.ProjectID, localeFile.ID, downloadParams)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}

	err = ioutil.WriteFile(localeFile.Path, res, 0700)
	return err
}

func (target *Target) LocaleFiles() (LocaleFiles, error) {
	files := []*LocaleFile{}

	if target.GetLocaleID() != "" {
		// a specific locale was requested
		remoteLocale, err := target.localeForRemote()
		if err != nil {
			return nil, err
		}

		localeFile, err := createLocaleFile(target, remoteLocale)
		if err != nil {
			return nil, err
		}

		files = append(files, localeFile)

	} else if placeholders.ContainsLocalePlaceholder(target.File) {
		// multiple locales were requested
		for _, remoteLocale := range target.RemoteLocales {
			localeFile, err := createLocaleFile(target, remoteLocale)
			if err != nil {
				return nil, err
			}

			files = append(files, localeFile)
		}
	} else {
		// no local files match remote locale
		return nil, fmt.Errorf("could not find any files on your system that matches the locales for porject %q", target.ProjectID)
	}

	return files, nil
}

func waitForRateLimit(rateLimitError *phraseapp.RateLimitingError) {
	if rateLimitError.Remaining == 0 {
		reset := rateLimitError.Reset
		resetTime := reset.Add(time.Second * 5).Sub(time.Now())
		fmt.Printf("Rate limit exceeded. Download will resume in %d seconds\n", int64(resetTime.Seconds()))
		time.Sleep(resetTime)
	}
}

func createLocaleFile(target *Target, remoteLocale *phraseapp.Locale) (*LocaleFile, error) {
	localeFile := &LocaleFile{
		Name:       remoteLocale.Name,
		ID:         remoteLocale.ID,
		Code:       remoteLocale.Code,
		Tag:        target.GetTag(),
		FileFormat: target.GetFormat(),
		Path:       target.File,
	}

	absPath, err := target.ReplacePlaceholders(localeFile)
	if err != nil {
		return nil, err
	}

	localeFile.Path = absPath
	return localeFile, nil
}

func createFile(path string) error {
	err := paths.Exists(path)
	if err != nil {
		absDir := filepath.Dir(path)
		err := paths.Exists(absDir)
		if err != nil {
			os.MkdirAll(absDir, 0700)
		}

		f, err := os.Create(path)
		if err != nil {
			return err
		}
		defer f.Close()
	}

	return nil
}
<|endoftext|>


<commit_before>package main

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

	"github.com/phrase/phraseapp-client/internal/paths"
	"github.com/phrase/phraseapp-client/internal/placeholders"
	"github.com/phrase/phraseapp-client/internal/print"
	"github.com/phrase/phraseapp-go/phraseapp"
)

const (
	timeoutInMinutes = 30 * time.Minute
)

type PullCommand struct {
	phraseapp.Config
	Branch string `cli:"opt --branch"`
}

func (cmd *PullCommand) Run() error {
	if cmd.Config.Debug {
		// suppresses content output
		cmd.Config.Debug = false
		Debug = true
	}
	client, err := newClient(cmd.Config.Credentials, cmd.Config.Debug)
	if err != nil {
		return err
	}

	targets, err := TargetsFromConfig(cmd.Config)
	if err != nil {
		return err
	}

	projectIdToLocales, err := LocalesForProjects(client, targets, cmd.Branch)
	if err != nil {
		return err
	}

	for _, target := range targets {
		val, ok := projectIdToLocales[LocaleCacheKey{target.ProjectID, cmd.Branch}]
		if !ok || len(val) == 0 {
			if cmd.Branch != "" {
				continue
			}
			return fmt.Errorf("Could not find any locales for project %q", target.ProjectID)
		}
		target.RemoteLocales = val
	}

	for _, target := range targets {
		err := target.Pull(client, cmd.Branch)
		if err != nil {
			return err
		}
	}

	return nil
}

type PullParams struct {
	phraseapp.LocaleDownloadParams
	LocaleID string
}

func (target *Target) Pull(client *phraseapp.Client, branch string) error {
	if err := target.CheckPreconditions(); err != nil {
		return err
	}

	localeFiles, err := target.LocaleFiles()
	if err != nil {
		return err
	}

	startedAt := time.Now()
	for _, localeFile := range localeFiles {
		if time.Since(startedAt) >= timeoutInMinutes {
			return fmt.Errorf("Timeout of %d minutes exceeded", timeoutInMinutes)
		}

		err := createFile(localeFile.Path)
		if err != nil {
			return err
		}

		err = target.DownloadAndWriteToFile(client, localeFile, branch)
		if err != nil {
			return fmt.Errorf("%s for %s", err, localeFile.Path)
		} else {
			print.Success("Downloaded %s to %s", localeFile.Message(), localeFile.RelPath())
		}
		if Debug {
			fmt.Fprintln(os.Stderr, strings.Repeat("-", 10))
		}
	}

	return nil
}

func (target *Target) DownloadAndWriteToFile(client *phraseapp.Client, localeFile *LocaleFile, branch string) error {
	downloadParams := &phraseapp.LocaleDownloadParams{Branch: &branch}
	if target.Params != nil {
		*downloadParams = target.Params.LocaleDownloadParams
		downloadParams.Branch = &branch
	}

	if downloadParams.FileFormat == nil {
		downloadParams.FileFormat = &localeFile.FileFormat
	}

	if Debug {
		fmt.Fprintln(os.Stderr, "Target file pattern:", target.File)
		fmt.Fprintln(os.Stderr, "Actual file path", localeFile.Path)
		fmt.Fprintln(os.Stderr, "LocaleID", localeFile.ID)
		fmt.Fprintln(os.Stderr, "ProjectID", target.ProjectID)
		fmt.Fprintln(os.Stderr, "FileFormat", downloadParams.FileFormat)
		fmt.Fprintln(os.Stderr, "ConvertEmoji", downloadParams.ConvertEmoji)
		fmt.Fprintln(os.Stderr, "IncludeEmptyTranslations", downloadParams.IncludeEmptyTranslations)
		fmt.Fprintln(os.Stderr, "KeepNotranslateTags", downloadParams.KeepNotranslateTags)
		fmt.Fprintln(os.Stderr, "Tag", downloadParams.Tag)
		fmt.Fprintln(os.Stderr, "FormatOptions", downloadParams.FormatOptions)
	}

	res, err := client.LocaleDownload(target.ProjectID, localeFile.ID, downloadParams)
	if err != nil {
		if rateLimitError, ok := (err).(*phraseapp.RateLimitingError); ok {
			waitForRateLimit(rateLimitError)
			res, err = client.LocaleDownload(target.ProjectID, localeFile.ID, downloadParams)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}

	err = ioutil.WriteFile(localeFile.Path, res, 0700)
	return err
}

func (target *Target) LocaleFiles() (LocaleFiles, error) {
	files := []*LocaleFile{}

	if target.GetLocaleID() != "" {
		// a specific locale was requested
		remoteLocale, err := target.localeForRemote()
		if err != nil {
			return nil, err
		}

		for _, tag := range target.GetTags() {
			localeFile, err := createLocaleFile(target, remoteLocale, tag)
			if err != nil {
				return nil, err
			}

			files = append(files, localeFile)
		}
	} else if placeholders.ContainsLocalePlaceholder(target.File) {
		// multiple locales were requested
		for _, remoteLocale := range target.RemoteLocales {
			localeFile, err := createLocaleFile(target, remoteLocale, "")
			if err != nil {
				return nil, err
			}

			files = append(files, localeFile)
		}
	} else {
		// no local files match remote locale
		return nil, fmt.Errorf("could not find any files on your system that matches the locales for project %q", target.ProjectID)
	}

	return files, nil
}

func waitForRateLimit(rateLimitError *phraseapp.RateLimitingError) {
	if rateLimitError.Remaining == 0 {
		reset := rateLimitError.Reset
		resetTime := reset.Add(time.Second * 5).Sub(time.Now())
		fmt.Printf("Rate limit exceeded. Download will resume in %d seconds\n", int64(resetTime.Seconds()))
		time.Sleep(resetTime)
	}
}

func createLocaleFile(target *Target, remoteLocale *phraseapp.Locale, tag string) (*LocaleFile, error) {
	localeFile := &LocaleFile{
		Name:       remoteLocale.Name,
		ID:         remoteLocale.ID,
		Code:       remoteLocale.Code,
		Tag:        tag,
		FileFormat: target.GetFormat(),
		Path:       target.File,
	}

	absPath, err := target.ReplacePlaceholders(localeFile)
	if err != nil {
		return nil, err
	}

	localeFile.Path = absPath
	return localeFile, nil
}

func createFile(path string) error {
	err := paths.Exists(path)
	if err != nil {
		absDir := filepath.Dir(path)
		err := paths.Exists(absDir)
		if err != nil {
			os.MkdirAll(absDir, 0700)
		}

		f, err := os.Create(path)
		if err != nil {
			return err
		}
		defer f.Close()
	}

	return nil
}
<commit_msg>Fix when no tags are specified<commit_after>package main

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

	"github.com/phrase/phraseapp-client/internal/paths"
	"github.com/phrase/phraseapp-client/internal/placeholders"
	"github.com/phrase/phraseapp-client/internal/print"
	"github.com/phrase/phraseapp-go/phraseapp"
)

const (
	timeoutInMinutes = 30 * time.Minute
)

type PullCommand struct {
	phraseapp.Config
	Branch string `cli:"opt --branch"`
}

func (cmd *PullCommand) Run() error {
	if cmd.Config.Debug {
		// suppresses content output
		cmd.Config.Debug = false
		Debug = true
	}
	client, err := newClient(cmd.Config.Credentials, cmd.Config.Debug)
	if err != nil {
		return err
	}

	targets, err := TargetsFromConfig(cmd.Config)
	if err != nil {
		return err
	}

	projectIdToLocales, err := LocalesForProjects(client, targets, cmd.Branch)
	if err != nil {
		return err
	}

	for _, target := range targets {
		val, ok := projectIdToLocales[LocaleCacheKey{target.ProjectID, cmd.Branch}]
		if !ok || len(val) == 0 {
			if cmd.Branch != "" {
				continue
			}
			return fmt.Errorf("Could not find any locales for project %q", target.ProjectID)
		}
		target.RemoteLocales = val
	}

	for _, target := range targets {
		err := target.Pull(client, cmd.Branch)
		if err != nil {
			return err
		}
	}

	return nil
}

type PullParams struct {
	phraseapp.LocaleDownloadParams
	LocaleID string
}

func (target *Target) Pull(client *phraseapp.Client, branch string) error {
	if err := target.CheckPreconditions(); err != nil {
		return err
	}

	localeFiles, err := target.LocaleFiles()
	if err != nil {
		return err
	}

	startedAt := time.Now()
	for _, localeFile := range localeFiles {
		if time.Since(startedAt) >= timeoutInMinutes {
			return fmt.Errorf("Timeout of %d minutes exceeded", timeoutInMinutes)
		}

		err := createFile(localeFile.Path)
		if err != nil {
			return err
		}

		err = target.DownloadAndWriteToFile(client, localeFile, branch)
		if err != nil {
			return fmt.Errorf("%s for %s", err, localeFile.Path)
		} else {
			print.Success("Downloaded %s to %s", localeFile.Message(), localeFile.RelPath())
		}
		if Debug {
			fmt.Fprintln(os.Stderr, strings.Repeat("-", 10))
		}
	}

	return nil
}

func (target *Target) DownloadAndWriteToFile(client *phraseapp.Client, localeFile *LocaleFile, branch string) error {
	downloadParams := &phraseapp.LocaleDownloadParams{Branch: &branch}
	if target.Params != nil {
		*downloadParams = target.Params.LocaleDownloadParams
		downloadParams.Branch = &branch
	}

	if downloadParams.FileFormat == nil {
		downloadParams.FileFormat = &localeFile.FileFormat
	}

	if Debug {
		fmt.Fprintln(os.Stderr, "Target file pattern:", target.File)
		fmt.Fprintln(os.Stderr, "Actual file path", localeFile.Path)
		fmt.Fprintln(os.Stderr, "LocaleID", localeFile.ID)
		fmt.Fprintln(os.Stderr, "ProjectID", target.ProjectID)
		fmt.Fprintln(os.Stderr, "FileFormat", downloadParams.FileFormat)
		fmt.Fprintln(os.Stderr, "ConvertEmoji", downloadParams.ConvertEmoji)
		fmt.Fprintln(os.Stderr, "IncludeEmptyTranslations", downloadParams.IncludeEmptyTranslations)
		fmt.Fprintln(os.Stderr, "KeepNotranslateTags", downloadParams.KeepNotranslateTags)
		fmt.Fprintln(os.Stderr, "Tag", downloadParams.Tag)
		fmt.Fprintln(os.Stderr, "FormatOptions", downloadParams.FormatOptions)
	}

	res, err := client.LocaleDownload(target.ProjectID, localeFile.ID, downloadParams)
	if err != nil {
		if rateLimitError, ok := (err).(*phraseapp.RateLimitingError); ok {
			waitForRateLimit(rateLimitError)
			res, err = client.LocaleDownload(target.ProjectID, localeFile.ID, downloadParams)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}

	err = ioutil.WriteFile(localeFile.Path, res, 0700)
	return err
}

func (target *Target) LocaleFiles() (LocaleFiles, error) {
	files := []*LocaleFile{}

	if target.GetLocaleID() != "" {
		// a specific locale was requested
		remoteLocale, err := target.localeForRemote()
		if err != nil {
			return nil, err
		}

		if len(target.GetTags()) == 0 {
			localeFile, err := createLocaleFile(target, remoteLocale, "")
			if err != nil {
				return nil, err
			}

			files = append(files, localeFile)
		} else {
			for _, tag := range target.GetTags() {
				localeFile, err := createLocaleFile(target, remoteLocale, tag)
				if err != nil {
					return nil, err
				}

				files = append(files, localeFile)
			}
		}
	} else if placeholders.ContainsLocalePlaceholder(target.File) {
		// multiple locales were requested
		for _, remoteLocale := range target.RemoteLocales {
			localeFile, err := createLocaleFile(target, remoteLocale, "")
			if err != nil {
				return nil, err
			}

			files = append(files, localeFile)
		}
	} else {
		// no local files match remote locale
		return nil, fmt.Errorf("could not find any files on your system that matches the locales for project %q", target.ProjectID)
	}

	return files, nil
}

func waitForRateLimit(rateLimitError *phraseapp.RateLimitingError) {
	if rateLimitError.Remaining == 0 {
		reset := rateLimitError.Reset
		resetTime := reset.Add(time.Second * 5).Sub(time.Now())
		fmt.Printf("Rate limit exceeded. Download will resume in %d seconds\n", int64(resetTime.Seconds()))
		time.Sleep(resetTime)
	}
}

func createLocaleFile(target *Target, remoteLocale *phraseapp.Locale, tag string) (*LocaleFile, error) {
	localeFile := &LocaleFile{
		Name:       remoteLocale.Name,
		ID:         remoteLocale.ID,
		Code:       remoteLocale.Code,
		Tag:        tag,
		FileFormat: target.GetFormat(),
		Path:       target.File,
	}

	absPath, err := target.ReplacePlaceholders(localeFile)
	if err != nil {
		return nil, err
	}

	localeFile.Path = absPath
	return localeFile, nil
}

func createFile(path string) error {
	err := paths.Exists(path)
	if err != nil {
		absDir := filepath.Dir(path)
		err := paths.Exists(absDir)
		if err != nil {
			os.MkdirAll(absDir, 0700)
		}

		f, err := os.Create(path)
		if err != nil {
			return err
		}
		defer f.Close()
	}

	return nil
}
<|endoftext|>


<commit_before>package medtronic

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/ecc1/radio"
)

const (
	pumpEnvVar       = "MEDTRONIC_PUMP_ID"
	freqEnvVar       = "MEDTRONIC_FREQUENCY"
	defaultFrequency = 916600000
	defaultTimeout   = 500 * time.Millisecond
	defaultRetries   = 3
)

// PumpAddress returns the encoded form of the pump ID.
func PumpAddress() []byte {
	id := os.Getenv(pumpEnvVar)
	if len(id) == 0 {
		log.Fatalf("%s environment variable is not set", pumpEnvVar)
	}
	addr, err := DeviceAddress(id)
	if err != nil {
		log.Fatalf("%s: %v", pumpEnvVar, err)
	}
	return addr
}

// DeviceAddress returns the encoded form of a device ID.
func DeviceAddress(id string) ([]byte, error) {
	if len(id) != 6 {
		return nil, fmt.Errorf("device ID %q must be 6 digits", id)
	}
	h, err := strconv.ParseUint(id, 16, 24)
	if err != nil {
		return nil, fmt.Errorf("device ID %q: %v", id, err)
	}
	return []byte{byte(h >> 16), byte(h >> 8), byte(h >> 0)}, nil
}

// Pump represents a Medtronic insulin pump.
type Pump struct {
	Radio radio.Interface

	// 22 for 522/722, 23 for 523/723, etc.
	family Family

	// Implicit parameters for command execution.
	timeout time.Duration
	retries int
	rssi    int
	err     error
}

// Open opens radio communication with a pump.
func Open() *Pump {
	r := radioInterface()
	pump := &Pump{
		Radio:   r,
		timeout: defaultTimeout,
		retries: defaultRetries,
	}
	if pump.Error() != nil {
		log.Printf("cannot connect to %s radio on %s", r.Name(), r.Device())
		return pump
	}
	log.Printf("connected to %s radio on %s", r.Name(), r.Device())
	precomputePackets()
	freq := getFrequency()
	log.Printf("setting frequency to %s", radio.MegaHertz(freq))
	r.Init(freq)
	go pump.closeWhenSignaled()
	return pump
}

// Close closes communication with the pump.
func (pump *Pump) Close() {
	r := pump.Radio
	log.Printf("disconnecting %s radio on %s", r.Name(), r.Device())
	r.Close()
}

// ParseFrequency interprets the given string as a frequency
// and returns its value in Hertz.
func ParseFrequency(s string) (uint32, error) {
	f, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return 0, err
	}
	if 860.0 <= f && f <= 920.0 {
		return uint32(f * 1000000.0), nil
	}
	if 860000000.0 <= f && f <= 920000000.0 {
		return uint32(f), nil
	}
	return 0, fmt.Errorf("invalid frequency %s", s)
}

func getFrequency() uint32 {
	s := os.Getenv(freqEnvVar)
	if len(s) == 0 {
		return uint32(defaultFrequency)
	}
	f, err := ParseFrequency(s)
	if err != nil {
		log.Fatalf("%s: %v", freqEnvVar, err)
	}
	return f
}

// Timeout returns the timeout used for pump communications.
func (pump *Pump) Timeout() time.Duration {
	return pump.timeout
}

// SetTimeout sets the timeout used for pump communications.
func (pump *Pump) SetTimeout(t time.Duration) {
	pump.timeout = t
}

// Retries returns the number of retries used for pump communications.
func (pump *Pump) Retries() int {
	return pump.retries
}

// SetRetries sets the number of retries used for pump communications.
// For safety, state-changing commands are only tried once.
func (pump *Pump) SetRetries(n int) {
	pump.retries = n
}

// RSSI returns the RSSI received with the most recent packet from the pump.
func (pump *Pump) RSSI() int {
	return pump.rssi
}

// Error returns the error state of the pump.
func (pump *Pump) Error() error {
	err := pump.Radio.Error()
	if err != nil {
		return err
	}
	return pump.err
}

// SetError sets the error state of the pump.
func (pump *Pump) SetError(err error) {
	pump.Radio.SetError(err)
	pump.err = err
}
<commit_msg>Wrap errors using "%w" in fmt.Errorf<commit_after>package medtronic

import (
	"fmt"
	"log"
	"os"
	"strconv"
	"time"

	"github.com/ecc1/radio"
)

const (
	pumpEnvVar       = "MEDTRONIC_PUMP_ID"
	freqEnvVar       = "MEDTRONIC_FREQUENCY"
	defaultFrequency = 916600000
	defaultTimeout   = 500 * time.Millisecond
	defaultRetries   = 3
)

// PumpAddress returns the encoded form of the pump ID.
func PumpAddress() []byte {
	id := os.Getenv(pumpEnvVar)
	if len(id) == 0 {
		log.Fatalf("%s environment variable is not set", pumpEnvVar)
	}
	addr, err := DeviceAddress(id)
	if err != nil {
		log.Fatalf("%s: %v", pumpEnvVar, err)
	}
	return addr
}

// DeviceAddress returns the encoded form of a device ID.
func DeviceAddress(id string) ([]byte, error) {
	if len(id) != 6 {
		return nil, fmt.Errorf("device ID %q must be 6 digits", id)
	}
	h, err := strconv.ParseUint(id, 16, 24)
	if err != nil {
		return nil, fmt.Errorf("device ID %q: %w", id, err)
	}
	return []byte{byte(h >> 16), byte(h >> 8), byte(h >> 0)}, nil
}

// Pump represents a Medtronic insulin pump.
type Pump struct {
	Radio radio.Interface

	// 22 for 522/722, 23 for 523/723, etc.
	family Family

	// Implicit parameters for command execution.
	timeout time.Duration
	retries int
	rssi    int
	err     error
}

// Open opens radio communication with a pump.
func Open() *Pump {
	r := radioInterface()
	pump := &Pump{
		Radio:   r,
		timeout: defaultTimeout,
		retries: defaultRetries,
	}
	if pump.Error() != nil {
		log.Printf("cannot connect to %s radio on %s", r.Name(), r.Device())
		return pump
	}
	log.Printf("connected to %s radio on %s", r.Name(), r.Device())
	precomputePackets()
	freq := getFrequency()
	log.Printf("setting frequency to %s", radio.MegaHertz(freq))
	r.Init(freq)
	go pump.closeWhenSignaled()
	return pump
}

// Close closes communication with the pump.
func (pump *Pump) Close() {
	r := pump.Radio
	log.Printf("disconnecting %s radio on %s", r.Name(), r.Device())
	r.Close()
}

// ParseFrequency interprets the given string as a frequency
// and returns its value in Hertz.
func ParseFrequency(s string) (uint32, error) {
	f, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return 0, err
	}
	if 860.0 <= f && f <= 920.0 {
		return uint32(f * 1000000.0), nil
	}
	if 860000000.0 <= f && f <= 920000000.0 {
		return uint32(f), nil
	}
	return 0, fmt.Errorf("invalid frequency %s", s)
}

func getFrequency() uint32 {
	s := os.Getenv(freqEnvVar)
	if len(s) == 0 {
		return uint32(defaultFrequency)
	}
	f, err := ParseFrequency(s)
	if err != nil {
		log.Fatalf("%s: %v", freqEnvVar, err)
	}
	return f
}

// Timeout returns the timeout used for pump communications.
func (pump *Pump) Timeout() time.Duration {
	return pump.timeout
}

// SetTimeout sets the timeout used for pump communications.
func (pump *Pump) SetTimeout(t time.Duration) {
	pump.timeout = t
}

// Retries returns the number of retries used for pump communications.
func (pump *Pump) Retries() int {
	return pump.retries
}

// SetRetries sets the number of retries used for pump communications.
// For safety, state-changing commands are only tried once.
func (pump *Pump) SetRetries(n int) {
	pump.retries = n
}

// RSSI returns the RSSI received with the most recent packet from the pump.
func (pump *Pump) RSSI() int {
	return pump.rssi
}

// Error returns the error state of the pump.
func (pump *Pump) Error() error {
	err := pump.Radio.Error()
	if err != nil {
		return err
	}
	return pump.err
}

// SetError sets the error state of the pump.
func (pump *Pump) SetError(err error) {
	pump.Radio.SetError(err)
	pump.err = err
}
<|endoftext|>


<commit_before>package glutton

import (
	"bufio"
	"math/rand"
	"net"
	"regexp"
	"strings"
	"time"
)

// maximum lines that can be read after the "DATA" command
const maxDataRead = 500

// Client is a connection container
type Client struct {
	conn   net.Conn
	bufin  *bufio.Reader
	bufout *bufio.Writer
}

func (c *Client) w(s string) {
	c.bufout.WriteString(s + "\r\n")
	c.bufout.Flush()
}
func (c *Client) r(g *Glutton) string {
	reply, err := c.bufin.ReadString('\n')
	if err != nil {
		g.logger.Errorf("[smpt    ] %v", err)
	}
	return reply
}

func rwait() {
	// makes the process sleep for random time
	rand.Seed(time.Now().Unix())
	// between 0.5 - 1.5 seconds
	rtime := rand.Intn(1500) + 500
	duration := time.Duration(rtime) * time.Millisecond
	time.Sleep(duration)
}
func validateMail(query string) bool {
	email := regexp.MustCompile("^MAIL FROM:<.+@.+>$") // naive regex
	if email.MatchString(query) {
		return true
	}
	return false
}
func validateRCPT(query string) bool {
	rcpt := regexp.MustCompile("^RCPT TO:<.+@.+>$")
	if rcpt.MatchString(query) {
		return true
	}
	return false
}

// HandleSMTP takes a net.Conn and does basic SMTP communication
func (g *Glutton) HandleSMTP(conn net.Conn) {
	defer conn.Close()
	client := &Client{
		conn:   conn,
		bufin:  bufio.NewReader(conn),
		bufout: bufio.NewWriter(conn),
	}
	rwait()
	client.w("220 Welcome!")
	for {
		query := strings.Trim(client.r(g), "\r\n")
		g.logger.Infof("[smtp    ] Payload : %q", query)
		if strings.HasPrefix(query, "HELO ") {
			rwait()
			client.w("250 Hello! Pleased to meet you.")
		} else if validateMail(query) {
			rwait()
			client.w("250 OK")
		} else if validateRCPT(query) {
			rwait()
			client.w("250 OK")
		} else if strings.Compare(query, "DATA") == 0 {
			client.w("354 End data with <CRLF>.<CRLF>")
			for readctr := maxDataRead; readctr >= 0; readctr -= 1 {
				data := client.r(g)
				g.logger.Infof("[smtp    ] Data : %q", data)
				// exit condition
				if strings.Compare(data, ".\r\n") == 0 {
					break
				}
			}
			rwait()
			client.w("250 OK")
		} else if strings.Compare(query, "QUIT") == 0 {
			client.w("Bye")
			break
		} else {
			client.w("Recheck the command you entered.")
		}
	}
}
<commit_msg>leave loop properly<commit_after>package glutton

import (
	"bufio"
	"math/rand"
	"net"
	"regexp"
	"strings"
	"time"
)

// maximum lines that can be read after the "DATA" command
const maxDataRead = 500

// Client is a connection container
type Client struct {
	conn   net.Conn
	bufin  *bufio.Reader
	bufout *bufio.Writer
}

func (c *Client) w(s string) {
	c.bufout.WriteString(s + "\r\n")
	c.bufout.Flush()
}
func (c *Client) r(g *Glutton) (string, error) {
	reply, err := c.bufin.ReadString('\n')
	if err != nil {
		g.logger.Errorf("[smpt    ] %v", err)
		return "", err
	}
	return reply, nil
}

func rwait() {
	// makes the process sleep for random time
	rand.Seed(time.Now().Unix())
	// between 0.5 - 1.5 seconds
	rtime := rand.Intn(1500) + 500
	duration := time.Duration(rtime) * time.Millisecond
	time.Sleep(duration)
}
func validateMail(query string) bool {
	email := regexp.MustCompile("^MAIL FROM:<.+@.+>$") // naive regex
	if email.MatchString(query) {
		return true
	}
	return false
}
func validateRCPT(query string) bool {
	rcpt := regexp.MustCompile("^RCPT TO:<.+@.+>$")
	if rcpt.MatchString(query) {
		return true
	}
	return false
}

// HandleSMTP takes a net.Conn and does basic SMTP communication
func (g *Glutton) HandleSMTP(conn net.Conn) {
	defer conn.Close()
	client := &Client{
		conn:   conn,
		bufin:  bufio.NewReader(conn),
		bufout: bufio.NewWriter(conn),
	}
	rwait()
	client.w("220 Welcome!")
	for {
		msg, err := client.r(g)
		if err != nil {
			break
		}
		query := strings.Trim(msg, "\r\n")
		g.logger.Infof("[smtp    ] Payload : %q", query)
		if strings.HasPrefix(query, "HELO ") {
			rwait()
			client.w("250 Hello! Pleased to meet you.")
		} else if validateMail(query) {
			rwait()
			client.w("250 OK")
		} else if validateRCPT(query) {
			rwait()
			client.w("250 OK")
		} else if strings.Compare(query, "DATA") == 0 {
			client.w("354 End data with <CRLF>.<CRLF>")
			for readctr := maxDataRead; readctr >= 0; readctr -= 1 {
				data, err := client.r(g)
				if err != nil {
					break
				}
				g.logger.Infof("[smtp    ] Data : %q", data)
				// exit condition
				if strings.Compare(data, ".\r\n") == 0 {
					break
				}
			}
			rwait()
			client.w("250 OK")
		} else if strings.Compare(query, "QUIT") == 0 {
			client.w("Bye")
			break
		} else {
			client.w("Recheck the command you entered.")
		}
	}
}
<|endoftext|>


<commit_before>package snmp

import (
	"bytes"
	"errors"
	"log"
	"math/rand"
	"net"
	"strings"
	"sync"
	"time"
)

// Session represents an SNMP v3 session to a single device.
type Session struct {
	addr           *net.UDPAddr
	conn           *net.UDPConn
	inbound        chan []byte
	user           []byte
	authPassphrase []byte
	privPassphrase []byte

	engineID    []byte
	engineBoots int32
	engineTime  int32

	authKey []byte
	privKey []byte
	aesIV   int64

	inflight map[int]chan DataType
	lock     sync.Mutex
}

// newSession creates a new SNMP v3 session using "authPriv" mode with
// SHA authentication and AES encryption.
func newSession(address, user, authPassphrase, privPassphrase string) (*Session, error) {
	addr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		return nil, err
	}

	sess := &Session{
		addr:           addr,
		inbound:        make(chan []byte),
		user:           []byte(user),
		authPassphrase: []byte(authPassphrase),
		privPassphrase: []byte(privPassphrase),
		aesIV:          rand.Int63(),
		inflight:       make(map[int]chan DataType),
	}

	go sess.handleListen()

	return sess, nil
}

func (s *Session) doRequest(data []byte, reqId int, c chan DataType) {
	_, err := s.conn.WriteTo(data, s.addr)
	if err != nil {
		close(c)
		return
	}

	s.inflight[reqId] = c
	go func() {
		<-time.After(300 * time.Millisecond) // TODO: make this into a package-level variable
		s.lock.Lock()
		defer s.lock.Unlock()

		// TODO: explain what this is doing
		if c, ok := s.inflight[reqId]; ok {
			// haven't received a response yet
			close(c)
			delete(s.inflight, reqId)
		}
	}()
}

func (s *Session) doRequestStuff(packet []byte, requestID int) (DataType, error) {
	var decoded DataType
	var ok bool

	for i := 0; i < 10; i++ {
		c := make(chan DataType)
		s.doRequest(packet, requestID, c)

		decoded, ok = <-c
		if ok {
			break
		} else {
			if i == 2 {
				return nil, errors.New("snmp: timeout")
			}
		}
	}

	seq, ok := decoded.(Sequence)
	if !ok || len(seq) < 4 {
		return nil, errors.New("snmp: invalid response")
	}

	encrypted, ok := seq[3].(String)
	if !ok {
		return nil, errors.New("snmp: invalid encrypted contents")
	}

	engineData, ok := seq[2].(String)
	if !ok {
		return nil, errors.New("snmp: invalid engine data contents")
	}

	engineStuff, _, err := decode(bytes.NewReader([]byte(engineData)))

	if err != nil {
		return nil, err
	}

	engineSeq, ok := engineStuff.(Sequence)
	if !ok || len(engineSeq) < 6 {
		return nil, errors.New("snmp: invalid engine sequence")
	}

	boots, ok := engineSeq[1].(Int)
	if !ok {
		return nil, errors.New("snmp: invalid engine boots")
	}

	s.engineBoots = int32(boots)

	engineTime, ok := engineSeq[2].(Int)
	if !ok {
		return nil, errors.New("snmp: invalid engine time")
	}

	s.engineTime = int32(engineTime)

	priv, ok := engineSeq[5].(String)
	if !ok {
		return nil, errors.New("snmp: invalid priv")
	}

	result, _, err := decode(bytes.NewReader(s.decrypt([]byte(encrypted), []byte(priv))))

	resultSeq, ok := result.(Sequence)
	if !ok || len(resultSeq) < 3 {
		return nil, errors.New("snmp: invalid result sequence")
	}

	return resultSeq[2], nil
}

// TODO: add comment
func (s *Session) handleListen() {
	for buf := range s.inbound {
		decoded, _, err := decode(bytes.NewReader(buf))
		if err != nil {
			log.Println(err)
			continue
		}

		s.lock.Lock()

		// TODO: make this safe
		reqId := int(decoded.(Sequence)[1].(Sequence)[0].(Int))

		// TODO: make this safe
		switch decoded.(Sequence)[3].(type) {
		case String:
			encrypted := []byte(decoded.(Sequence)[3].(String)) // TODO: make this safe

			engineStuff, _, err := decode(bytes.NewReader([]byte(decoded.(Sequence)[2].(String))))
			if err != nil {
				continue
			}

			s.engineBoots = int32(engineStuff.(Sequence)[1].(Int))
			s.engineTime = int32(engineStuff.(Sequence)[2].(Int))

			priv := []byte(engineStuff.(Sequence)[5].(String))

			result, _, err := decode(bytes.NewReader(s.decrypt(encrypted, priv)))

			if err != nil {
				log.Println(err)
				continue
			}

			responseData := result.(Sequence)[2]

			switch responseData.(type) {
			case GetResponse:
				reqId = responseData.(GetResponse).requestID

			case Report:
				reqId = int(responseData.(Report)[0].(Int)) // TODO: ^
			}
		}

		if c, ok := s.inflight[reqId]; ok {
			c <- decoded // TODO: consider a non-blocking send? Think about deadlocks...
			delete(s.inflight, reqId)
		}

		s.lock.Unlock() // TODO: Non-blocking send might be better.
	}

}

// TODO: add comment
func (s *Session) Discover() error {
	reqId := int(rand.Intn(100000))

	encodedEngineData, err := Sequence{
		String(""),
		Int(0),
		Int(0),
		String(""),
		String(""),
		String(""),
	}.Encode()

	if err != nil {
		return err
	}

	discoverySequence, err := Sequence{
		Int(3),
		Sequence{
			Int(reqId),
			Int(65507),
			String("\x04"), // TODO: \x04?
			Int(3),
		},
		String(encodedEngineData),
		Sequence{
			String(""),
			String(""),
			newGetRequest(reqId, []Varbind{}),
		},
	}.Encode()

	if err != nil {
		return err
	}

	var decoded DataType
	var ok bool

	// TODO: make num of retries a package-level variable
	// TODO: turn this into a function?
	for i := 0; i < 3; i++ {
		c := make(chan DataType)
		s.doRequest(discoverySequence, int(reqId), c)

		decoded, ok = <-c
		if ok {
			break
		} else {
			if i == 2 {
				return errors.New("discovery failed")
			}
		}
	}

	engineStuff, _, err := decode(bytes.NewReader([]byte(decoded.(Sequence)[2].(String))))
	if err != nil {
		return err
	}

	s.engineID = []byte(engineStuff.(Sequence)[0].(String))
	s.engineBoots = int32(engineStuff.(Sequence)[1].(Int))
	s.engineTime = int32(engineStuff.(Sequence)[2].(Int))

	s.privKey = passphraseToKey(s.privPassphrase, s.engineID)[:16]
	s.authKey = passphraseToKey(s.authPassphrase, s.engineID)

	return nil
}

func (s *Session) Get(oid ObjectIdentifier) (*GetResponse, error) {
	reqId := int(rand.Int31())

	getReq, err := Sequence{
		String(s.engineID),
		String(""),
		newGetRequest(reqId, []Varbind{NewVarbind(oid, Null)}),
	}.Encode()

	if err != nil {
		return nil, err
	}

	// TODO: turn this all into a function
	encrypted, priv := s.encrypt(getReq)

	packet, err := s.constructPacket(encrypted, priv)
	if err != nil {
		return nil, err
	}

	result, err := s.doRequestStuff(packet, reqId)
	if err != nil {
		return nil, err
	}

	getRes, ok := result.(GetResponse)
	if !ok {
		return nil, ErrDecodingType
	}

	if len(getRes.varbinds) > 0 && getRes.varbinds[0].value == NoSuchInstance {
		return nil, errors.New("No Such Instance")
	}

	return &getRes, nil
}

func (s *Session) GetNext(oid ObjectIdentifier) (*GetResponse, error) {
	reqId := int(rand.Int31())

	getNextReq, err := Sequence{
		String(s.engineID),
		String(""),
		newGetNextRequest(reqId, []Varbind{NewVarbind(oid, Null)}),
	}.Encode()

	if err != nil {
		return nil, err
	}

	encrypted, priv := s.encrypt(getNextReq)

	packet, err := s.constructPacket(encrypted, priv)
	if err != nil {
		return nil, err
	}

	result, err := s.doRequestStuff(packet, reqId)
	if err != nil {
		return nil, err
	}

	getRes, ok := result.(GetResponse)
	if !ok {
		return nil, ErrDecodingType
	}

	if len(getRes.varbinds) > 0 && getRes.varbinds[0].value == EndOfMIBView {
		return nil, errors.New("End of MIB View")
	}

	return &getRes, nil
}

func (s *Session) constructPacket(encrypted, priv []byte) ([]byte, error) {
	msgId := Int(rand.Int31())

	v3Header, err := Sequence{
		String(s.engineID),
		Int(s.engineBoots),
		Int(s.engineTime),
		String(s.user),
		String(strings.Repeat("\x00", 12)),
		String(priv),
	}.Encode()

	if err != nil {
		return nil, err
	}

	packet, err := Sequence{
		Int(3),
		Sequence{
			msgId,
			Int(65507),
			String("\x07"),
			Int(3),
		},
		String(v3Header),
		String(encrypted),
	}.Encode()

	if err != nil {
		return nil, err
	}

	authParam := s.auth(packet)

	return bytes.Replace(packet, bytes.Repeat([]byte{0}, 12), authParam, 1), nil
}
<commit_msg>fix race<commit_after>package snmp

import (
	"bytes"
	"errors"
	"log"
	"math/rand"
	"net"
	"strings"
	"sync"
	"time"
)

// Session represents an SNMP v3 session to a single device.
type Session struct {
	addr           *net.UDPAddr
	conn           *net.UDPConn
	inbound        chan []byte
	user           []byte
	authPassphrase []byte
	privPassphrase []byte

	engineID    []byte
	engineBoots int32
	engineTime  int32

	authKey []byte
	privKey []byte
	aesIV   int64

	inflight map[int]chan DataType
	lock     sync.Mutex
}

// newSession creates a new SNMP v3 session using "authPriv" mode with
// SHA authentication and AES encryption.
func newSession(address, user, authPassphrase, privPassphrase string) (*Session, error) {
	addr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		return nil, err
	}

	sess := &Session{
		addr:           addr,
		inbound:        make(chan []byte),
		user:           []byte(user),
		authPassphrase: []byte(authPassphrase),
		privPassphrase: []byte(privPassphrase),
		aesIV:          rand.Int63(),
		inflight:       make(map[int]chan DataType),
	}

	go sess.handleListen()

	return sess, nil
}

func (s *Session) doRequest(data []byte, reqId int, c chan DataType) {
	_, err := s.conn.WriteTo(data, s.addr)
	if err != nil {
		close(c)
		return
	}

	s.lock.Lock()
	s.inflight[reqId] = c
	s.lock.Unlock()

	go func() {
		<-time.After(300 * time.Millisecond) // TODO: make this into a package-level variable
		s.lock.Lock()
		defer s.lock.Unlock()

		// TODO: explain what this is doing
		if c, ok := s.inflight[reqId]; ok {
			// haven't received a response yet

			close(c)
			delete(s.inflight, reqId)
		}
	}()
}

func (s *Session) doRequestStuff(packet []byte, requestID int) (DataType, error) {
	var decoded DataType
	var ok bool

	for i := 0; i < 10; i++ {
		c := make(chan DataType)
		s.doRequest(packet, requestID, c)

		decoded, ok = <-c
		if ok {
			break
		} else {
			if i == 2 {
				return nil, errors.New("snmp: timeout")
			}
		}
	}

	seq, ok := decoded.(Sequence)
	if !ok || len(seq) < 4 {
		return nil, errors.New("snmp: invalid response")
	}

	encrypted, ok := seq[3].(String)
	if !ok {
		return nil, errors.New("snmp: invalid encrypted contents")
	}

	engineData, ok := seq[2].(String)
	if !ok {
		return nil, errors.New("snmp: invalid engine data contents")
	}

	engineStuff, _, err := decode(bytes.NewReader([]byte(engineData)))

	if err != nil {
		return nil, err
	}

	engineSeq, ok := engineStuff.(Sequence)
	if !ok || len(engineSeq) < 6 {
		return nil, errors.New("snmp: invalid engine sequence")
	}

	boots, ok := engineSeq[1].(Int)
	if !ok {
		return nil, errors.New("snmp: invalid engine boots")
	}

	s.engineBoots = int32(boots)

	engineTime, ok := engineSeq[2].(Int)
	if !ok {
		return nil, errors.New("snmp: invalid engine time")
	}

	s.engineTime = int32(engineTime)

	priv, ok := engineSeq[5].(String)
	if !ok {
		return nil, errors.New("snmp: invalid priv")
	}

	result, _, err := decode(bytes.NewReader(s.decrypt([]byte(encrypted), []byte(priv))))

	resultSeq, ok := result.(Sequence)
	if !ok || len(resultSeq) < 3 {
		return nil, errors.New("snmp: invalid result sequence")
	}

	return resultSeq[2], nil
}

// TODO: add comment
func (s *Session) handleListen() {
	for buf := range s.inbound {
		decoded, _, err := decode(bytes.NewReader(buf))
		if err != nil {
			log.Println(err)
			continue
		}

		s.lock.Lock()

		// TODO: make this safe
		reqId := int(decoded.(Sequence)[1].(Sequence)[0].(Int))

		// TODO: make this safe
		switch decoded.(Sequence)[3].(type) {
		case String:
			encrypted := []byte(decoded.(Sequence)[3].(String)) // TODO: make this safe

			engineStuff, _, err := decode(bytes.NewReader([]byte(decoded.(Sequence)[2].(String))))
			if err != nil {
				continue
			}

			s.engineBoots = int32(engineStuff.(Sequence)[1].(Int))
			s.engineTime = int32(engineStuff.(Sequence)[2].(Int))

			priv := []byte(engineStuff.(Sequence)[5].(String))

			result, _, err := decode(bytes.NewReader(s.decrypt(encrypted, priv)))

			if err != nil {
				log.Println(err)
				continue
			}

			responseData := result.(Sequence)[2]

			switch responseData.(type) {
			case GetResponse:
				reqId = responseData.(GetResponse).requestID

			case Report:
				reqId = int(responseData.(Report)[0].(Int)) // TODO: ^
			}
		}

		if c, ok := s.inflight[reqId]; ok {
			c <- decoded // TODO: consider a non-blocking send? Think about deadlocks...
			delete(s.inflight, reqId)
		}

		s.lock.Unlock() // TODO: Non-blocking send might be better.
	}

}

// TODO: add comment
func (s *Session) Discover() error {
	reqId := int(rand.Intn(100000))

	encodedEngineData, err := Sequence{
		String(""),
		Int(0),
		Int(0),
		String(""),
		String(""),
		String(""),
	}.Encode()

	if err != nil {
		return err
	}

	discoverySequence, err := Sequence{
		Int(3),
		Sequence{
			Int(reqId),
			Int(65507),
			String("\x04"), // TODO: \x04?
			Int(3),
		},
		String(encodedEngineData),
		Sequence{
			String(""),
			String(""),
			newGetRequest(reqId, []Varbind{}),
		},
	}.Encode()

	if err != nil {
		return err
	}

	var decoded DataType
	var ok bool

	// TODO: make num of retries a package-level variable
	// TODO: turn this into a function?
	for i := 0; i < 3; i++ {
		c := make(chan DataType)
		s.doRequest(discoverySequence, int(reqId), c)

		decoded, ok = <-c
		if ok {
			break
		} else {
			if i == 2 {
				return errors.New("discovery failed")
			}
		}
	}

	engineStuff, _, err := decode(bytes.NewReader([]byte(decoded.(Sequence)[2].(String))))
	if err != nil {
		return err
	}

	s.engineID = []byte(engineStuff.(Sequence)[0].(String))
	s.engineBoots = int32(engineStuff.(Sequence)[1].(Int))
	s.engineTime = int32(engineStuff.(Sequence)[2].(Int))

	s.privKey = passphraseToKey(s.privPassphrase, s.engineID)[:16]
	s.authKey = passphraseToKey(s.authPassphrase, s.engineID)

	return nil
}

func (s *Session) Get(oid ObjectIdentifier) (*GetResponse, error) {
	reqId := int(rand.Int31())

	getReq, err := Sequence{
		String(s.engineID),
		String(""),
		newGetRequest(reqId, []Varbind{NewVarbind(oid, Null)}),
	}.Encode()

	if err != nil {
		return nil, err
	}

	// TODO: turn this all into a function
	encrypted, priv := s.encrypt(getReq)

	packet, err := s.constructPacket(encrypted, priv)
	if err != nil {
		return nil, err
	}

	result, err := s.doRequestStuff(packet, reqId)
	if err != nil {
		return nil, err
	}

	getRes, ok := result.(GetResponse)
	if !ok {
		return nil, ErrDecodingType
	}

	if len(getRes.varbinds) > 0 && getRes.varbinds[0].value == NoSuchInstance {
		return nil, errors.New("No Such Instance")
	}

	return &getRes, nil
}

func (s *Session) GetNext(oid ObjectIdentifier) (*GetResponse, error) {
	reqId := int(rand.Int31())

	getNextReq, err := Sequence{
		String(s.engineID),
		String(""),
		newGetNextRequest(reqId, []Varbind{NewVarbind(oid, Null)}),
	}.Encode()

	if err != nil {
		return nil, err
	}

	encrypted, priv := s.encrypt(getNextReq)

	packet, err := s.constructPacket(encrypted, priv)
	if err != nil {
		return nil, err
	}

	result, err := s.doRequestStuff(packet, reqId)
	if err != nil {
		return nil, err
	}

	getRes, ok := result.(GetResponse)
	if !ok {
		return nil, ErrDecodingType
	}

	if len(getRes.varbinds) > 0 && getRes.varbinds[0].value == EndOfMIBView {
		return nil, errors.New("End of MIB View")
	}

	return &getRes, nil
}

func (s *Session) constructPacket(encrypted, priv []byte) ([]byte, error) {
	msgId := Int(rand.Int31())

	v3Header, err := Sequence{
		String(s.engineID),
		Int(s.engineBoots),
		Int(s.engineTime),
		String(s.user),
		String(strings.Repeat("\x00", 12)),
		String(priv),
	}.Encode()

	if err != nil {
		return nil, err
	}

	packet, err := Sequence{
		Int(3),
		Sequence{
			msgId,
			Int(65507),
			String("\x07"),
			Int(3),
		},
		String(v3Header),
		String(encrypted),
	}.Encode()

	if err != nil {
		return nil, err
	}

	authParam := s.auth(packet)

	return bytes.Replace(packet, bytes.Repeat([]byte{0}, 12), authParam, 1), nil
}
<|endoftext|>


<commit_before>package sorg

import (
	"os"
	"path"
	"time"

	log "github.com/Sirupsen/logrus"
)

const (
	// Release is the asset version of the site. Bump when any assets are
	// updated to blow away any browser caches.
	Release = "15"
)

const (
	// ContentDir is the location of the site's content (articles, fragments,
	// assets, etc.).
	ContentDir = "./content"

	// LayoutsDir is the source directory for view layouts.
	LayoutsDir = "./layouts"

	// MainLayout is the site's main layout.
	MainLayout = LayoutsDir + "/main"

	// PagesDir is the source directory for one-off page content.
	PagesDir = "./pages"

	// ViewsDir is the source directory for views.
	ViewsDir = "./views"
)

// A list of all directories that are in the built static site.
var outputDirs = []string{
	".",
	"articles",
	"assets",
	"assets/" + Release,
	"assets/photos",
	"fragments",
	"photos",
	"reading",
	"runs",
	"twitter",
}

// CreateOutputDirs creates a target directory for the static site and all
// other necessary directories for the build if they don't already exist.
func CreateOutputDirs(targetDir string) error {
	start := time.Now()
	defer func() {
		log.Debugf("Created target directories in %v.", time.Now().Sub(start))
	}()

	for _, dir := range outputDirs {
		dir = path.Join(targetDir, dir)
		err := os.MkdirAll(dir, 0755)
		if err != nil {
			return err
		}
	}

	return nil
}

// InitLog initializes logging for singularity programs.
func InitLog(verbose bool) {
	log.SetFormatter(&plainFormatter{})

	if verbose {
		log.SetLevel(log.DebugLevel)
	}
}

// plainFormatter is a logrus formatter that displays text in a much more
// simple fashion that's more suitable as CLI output.
type plainFormatter struct {
}

// Format takes a logrus.Entry and returns bytes that are suitable for log
// output.
func (f *plainFormatter) Format(entry *log.Entry) ([]byte, error) {
	bytes := []byte(entry.Message + "\n")

	if entry.Level == log.DebugLevel {
		bytes = append([]byte("DEBUG: "), bytes...)
	}

	return bytes, nil
}
<commit_msg>Bump release number for some CSS fixes<commit_after>package sorg

import (
	"os"
	"path"
	"time"

	log "github.com/Sirupsen/logrus"
)

const (
	// Release is the asset version of the site. Bump when any assets are
	// updated to blow away any browser caches.
	Release = "16"
)

const (
	// ContentDir is the location of the site's content (articles, fragments,
	// assets, etc.).
	ContentDir = "./content"

	// LayoutsDir is the source directory for view layouts.
	LayoutsDir = "./layouts"

	// MainLayout is the site's main layout.
	MainLayout = LayoutsDir + "/main"

	// PagesDir is the source directory for one-off page content.
	PagesDir = "./pages"

	// ViewsDir is the source directory for views.
	ViewsDir = "./views"
)

// A list of all directories that are in the built static site.
var outputDirs = []string{
	".",
	"articles",
	"assets",
	"assets/" + Release,
	"assets/photos",
	"fragments",
	"photos",
	"reading",
	"runs",
	"twitter",
}

// CreateOutputDirs creates a target directory for the static site and all
// other necessary directories for the build if they don't already exist.
func CreateOutputDirs(targetDir string) error {
	start := time.Now()
	defer func() {
		log.Debugf("Created target directories in %v.", time.Now().Sub(start))
	}()

	for _, dir := range outputDirs {
		dir = path.Join(targetDir, dir)
		err := os.MkdirAll(dir, 0755)
		if err != nil {
			return err
		}
	}

	return nil
}

// InitLog initializes logging for singularity programs.
func InitLog(verbose bool) {
	log.SetFormatter(&plainFormatter{})

	if verbose {
		log.SetLevel(log.DebugLevel)
	}
}

// plainFormatter is a logrus formatter that displays text in a much more
// simple fashion that's more suitable as CLI output.
type plainFormatter struct {
}

// Format takes a logrus.Entry and returns bytes that are suitable for log
// output.
func (f *plainFormatter) Format(entry *log.Entry) ([]byte, error) {
	bytes := []byte(entry.Message + "\n")

	if entry.Level == log.DebugLevel {
		bytes = append([]byte("DEBUG: "), bytes...)
	}

	return bytes, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"code.google.com/p/go-uuid/uuid"
	"github.com/codegangsta/negroni"
	"github.com/julienschmidt/httprouter"
	"github.com/libgit2/git2go"
	"github.com/unrolled/render"

	"github.com/wantedly/risu/registry"
	"github.com/wantedly/risu/schema"
)

const (
	SourceBasePath = "/var/risu/src/github.com/"
	CacheBasePath  = "/var/risu/cache"
)

var ren = render.New()
var reg = registry.NewRegistry(os.Getenv("REGISTRY_BACKEND"), os.Getenv("REGISTRY_ENDPOINT"))

func create(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	defer r.Body.Close()
	var opts schema.BuildCreateOpts
	err := json.NewDecoder(r.Body).Decode(&opts)
	if err != nil {
		log.Fatal(err)
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}

	if opts.SourceBranch == "" {
		opts.SourceBranch = "master"
	}

	if opts.Dockerfile == "" {
		opts.Dockerfile = "Dockerfile"
	}

	currentTime := time.Now()
	build := schema.Build{
		ID:           uuid.NewUUID(),
		SourceRepo:   opts.SourceRepo,
		SourceBranch: opts.SourceBranch,
		Name:         opts.Name,
		Dockerfile:   opts.Dockerfile,
		Status:       "building",
		CreatedAt:    currentTime,
		UpdatedAt:    currentTime,
	}

	err = reg.Set(build)
	if err != nil {
		log.Fatal(err)
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}
	ren.JSON(w, http.StatusAccepted, build)

	go func() {
		if err := gitClone(build); err != nil {
			return
		}

		if err := dockerBuild(build); err != nil {
			return
		}

		go dockerPush(build)
		go pushCache(build)
	}()
}

func root(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	ren.JSON(w, http.StatusOK, map[string]string{"status": "ok"})
}

func index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	builds, err := reg.List()
	if err != nil {
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}
	ren.JSON(w, http.StatusOK, builds)
}

func show(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	id := ps.ByName("id")
	uuid := uuid.Parse(id)
	build, err := reg.Get(uuid)
	if err != nil {
		ren.JSON(w, http.StatusNotFound, map[string]string{"status": "not found"})
		return
	}
	ren.JSON(w, http.StatusOK, build)
}

// Clone run "git clone <repository_URL>" and "git checkout branch"
func gitClone(build schema.Build) error {
	if _, err := os.Stat(SourceBasePath); err != nil {
		os.MkdirAll(SourceBasePath, 0755)
	}

	// htpps://<token>@github.com/<SourceRepo>.git
	cloneURL := "https://" + os.Getenv("GITHUB_ACCESS_TOKEN") + "@github.com/" + build.SourceRepo + ".git"

	// debug
	fmt.Println(cloneURL)

	clonePath := SourceBasePath + build.SourceRepo

	// debug
	fmt.Println(clonePath)

	_, err := git.Clone(cloneURL, clonePath, &git.CloneOptions{CheckoutBranch: build.SourceBranch})
	if err != nil {
		return err
	}
}

func dockerBuild(build schema.Build) error {
	// TODO (@dtan4)
	return nil
}

func dockerPush(build schema.Build) error {
	// TODO (@koudaii)
	return nil
}

func pushCache(build schema.Build) error {
	// TODO (@dtan4)
	return nil
}

func setUpServer() *negroni.Negroni {
	router := httprouter.New()
	router.GET("/", root)
	router.GET("/builds", index)
	router.GET("/builds/:id", show)
	router.POST("/builds", create)

	n := negroni.Classic()
	n.UseHandler(router)
	return n
}

func main() {
	if os.Getenv("GITHUB_ACCESS_TOKEN") == "" {
		log.Fatal("Please provide 'GITHUB_ACCESS_TOKEN' through environment")
	}
	n := setUpServer()
	n.Run(":8080")
}
<commit_msg>Return nil<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"code.google.com/p/go-uuid/uuid"
	"github.com/codegangsta/negroni"
	"github.com/julienschmidt/httprouter"
	"github.com/libgit2/git2go"
	"github.com/unrolled/render"

	"github.com/wantedly/risu/registry"
	"github.com/wantedly/risu/schema"
)

const (
	SourceBasePath = "/var/risu/src/github.com/"
	CacheBasePath  = "/var/risu/cache"
)

var ren = render.New()
var reg = registry.NewRegistry(os.Getenv("REGISTRY_BACKEND"), os.Getenv("REGISTRY_ENDPOINT"))

func create(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	defer r.Body.Close()
	var opts schema.BuildCreateOpts
	err := json.NewDecoder(r.Body).Decode(&opts)
	if err != nil {
		log.Fatal(err)
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}

	if opts.SourceBranch == "" {
		opts.SourceBranch = "master"
	}

	if opts.Dockerfile == "" {
		opts.Dockerfile = "Dockerfile"
	}

	currentTime := time.Now()
	build := schema.Build{
		ID:           uuid.NewUUID(),
		SourceRepo:   opts.SourceRepo,
		SourceBranch: opts.SourceBranch,
		Name:         opts.Name,
		Dockerfile:   opts.Dockerfile,
		Status:       "building",
		CreatedAt:    currentTime,
		UpdatedAt:    currentTime,
	}

	err = reg.Set(build)
	if err != nil {
		log.Fatal(err)
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}
	ren.JSON(w, http.StatusAccepted, build)

	go func() {
		if err := gitClone(build); err != nil {
			return
		}

		if err := dockerBuild(build); err != nil {
			return
		}

		go dockerPush(build)
		go pushCache(build)
	}()
}

func root(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	ren.JSON(w, http.StatusOK, map[string]string{"status": "ok"})
}

func index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	builds, err := reg.List()
	if err != nil {
		ren.JSON(w, http.StatusInternalServerError, map[string]string{"status": "internal server error"})
		return
	}
	ren.JSON(w, http.StatusOK, builds)
}

func show(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
	id := ps.ByName("id")
	uuid := uuid.Parse(id)
	build, err := reg.Get(uuid)
	if err != nil {
		ren.JSON(w, http.StatusNotFound, map[string]string{"status": "not found"})
		return
	}
	ren.JSON(w, http.StatusOK, build)
}

// Clone run "git clone <repository_URL>" and "git checkout branch"
func gitClone(build schema.Build) error {
	if _, err := os.Stat(SourceBasePath); err != nil {
		os.MkdirAll(SourceBasePath, 0755)
	}

	// htpps://<token>@github.com/<SourceRepo>.git
	cloneURL := "https://" + os.Getenv("GITHUB_ACCESS_TOKEN") + "@github.com/" + build.SourceRepo + ".git"

	// debug
	fmt.Println(cloneURL)

	clonePath := SourceBasePath + build.SourceRepo

	// debug
	fmt.Println(clonePath)

	_, err := git.Clone(cloneURL, clonePath, &git.CloneOptions{CheckoutBranch: build.SourceBranch})
	if err != nil {
		return err
	}
	return nil
}

func dockerBuild(build schema.Build) error {
	// TODO (@dtan4)
	return nil
}

func dockerPush(build schema.Build) error {
	// TODO (@koudaii)
	return nil
}

func pushCache(build schema.Build) error {
	// TODO (@dtan4)
	return nil
}

func setUpServer() *negroni.Negroni {
	router := httprouter.New()
	router.GET("/", root)
	router.GET("/builds", index)
	router.GET("/builds/:id", show)
	router.POST("/builds", create)

	n := negroni.Classic()
	n.UseHandler(router)
	return n
}

func main() {
	if os.Getenv("GITHUB_ACCESS_TOKEN") == "" {
		log.Fatal("Please provide 'GITHUB_ACCESS_TOKEN' through environment")
	}
	n := setUpServer()
	n.Run(":8080")
}
<|endoftext|>


<commit_before>package cfitsio

// #include "go-cfitsio.h"
// #include "go-cfitsio-utils.h"
import "C"
import "unsafe"

// Return the number of existing keywords (not counting the END keyword) and the amount of space currently available for more keywords. It returns morekeys = -1 if the header has not yet been closed. Note that CFITSIO will dynamically add space if required when writing new keywords to a header so in practice there is no limit to the number of keywords that can be added to a header. A null pointer may be entered for the morekeys parameter if it's value is not needed.
func (f *File) HdrSpace() (keysexist, morekeys int, err error) {
	c_key := C.int(0)
	c_more := C.int(0)
	c_status := C.int(0)
	C.fits_get_hdrspace(f.c, &c_key, &c_more, &c_status)
	if c_status > 0 {
		return 0, 0, to_err(c_status)
	}
	return int(c_key), int(c_more), nil
}

/*
// Return the specified keyword. In the first routine, the datatype parameter specifies the desired returned data type of the keyword value and can have one of the following symbolic constant values: TSTRING, TLOGICAL (== int), TBYTE, TSHORT, TUSHORT, TINT, TUINT, TLONG, TULONG, TLONGLONG, TFLOAT, TDOUBLE, TCOMPLEX, and TDBLCOMPLEX. Within the context of this routine, TSTRING corresponds to a 'char*' data type, i.e., a pointer to a character array. Data type conversion will be performed for numeric values if the keyword value does not have the same data type. If the value of the keyword is undefined (i.e., the value field is blank) then an error status = VALUE_UNDEFINED will be returned.
The second routine returns the keyword value as a character string (a literal copy of what is in the value field) regardless of the intrinsic data type of the keyword. The third routine returns the entire 80-character header record of the keyword, with any trailing blank characters stripped off. The fourth routine returns the (next) header record that contains the literal string of characters specified by the 'string' argument.

If a NULL comment pointer is supplied then the comment string will not be returned.

  int fits_read_key / ffgky
      (fitsfile *fptr, int datatype, char *keyname, > DTYPE *value,
       char *comment, int *status)

  int fits_read_keyword / ffgkey
      (fitsfile *fptr, char *keyname, > char *value, char *comment,
       int *status)

  int fits_read_card / ffgcrd
      (fitsfile *fptr, char *keyname, > char *card, int *status)

  int fits_read_str / ffgstr
      (fitsfile *fptr, char *string, > char *card, int *status)
*/

// Return the nth header record in the CHU. The first keyword in the header is at keynum = 1; if keynum = 0 then these routines simply reset the internal CFITSIO pointer to the beginning of the header so that subsequent keyword operations will start at the top of the header (e.g., prior to searching for keywords using wild cards in the keyword name). The first routine returns the entire 80-character header record (with trailing blanks truncated), while the second routine parses the record and returns the name, value, and comment fields as separate (blank truncated) character strings. If a NULL comment pointer is given on input, then the comment string will not be returned.
func (f *File) ReadRecord(n int) (card string, err error) {
	c_key := C.int(n)
	c_status := C.int(0)
	c_card := C.char_buf_array(C.FLEN_CARD)
	defer C.free(unsafe.Pointer(c_card))

	C.fits_read_record(f.c, c_key, c_card, &c_status)
	if c_status > 0 {
		return "", to_err(c_status)
	}
	return C.GoString(c_card), nil
}

// Return the nth header record in the CHU. The first keyword in the header is at keynum = 1; if keynum = 0 then these routines simply reset the internal CFITSIO pointer to the beginning of the header so that subsequent keyword operations will start at the top of the header (e.g., prior to searching for keywords using wild cards in the keyword name). The first routine returns the entire 80-character header record (with trailing blanks truncated), while the second routine parses the record and returns the name, value, and comment fields as separate (blank truncated) character strings. If a NULL comment pointer is given on input, then the comment string will not be returned.
// int fits_read_keyn / ffgkyn
//     (fitsfile *fptr, int keynum, > char *keyname, char *value,
//      char *comment, int *status)

// EOF
<commit_msg>rkey: simplify File API<commit_after><|endoftext|>


<commit_before>package main

import (
    "github.com/hoisie/mustache"
    "fmt"
    "errors"
    "encoding/json"
    "net/http"
    "strings"
    "bytes"
    "os"
)

type Blog struct {
    ID int
    Name string
    Language string
    URL string
    AccentColor string `json:"accent_color"`
}

type Author struct {
    GivenName string `json:"given_name"`
    FamilyName string `json:"family_name"`
    Bio string `json:"bio_html"`
    Website string
    Twitter string
    AvatarURL string
}

func (a Author) Name() string {
    return fmt.Sprintf("%s %s", a.GivenName, a.FamilyName)
}

type Post struct {
    Title string
    ContentHTML string `json:"content_html"`
    ExcerptHTML string `json:"excerpt_html"`
    URL string
    ID int
    Blog Blog
    Author Author `json:"user"`
}

func (p Post) Mustache() map[string]string {
    return map[string]string {
        "post_title": p.Title,
        "post_excerpt_html": p.SanitizedExcerptHTML(),
        "post_url": p.URL,
        "post_content_html": p.ContentHTML,
        "author_name": p.Author.Name(),
        "author_given_name": p.Author.GivenName,
        "author_family_name": p.Author.FamilyName,
        "author_bio_html": p.Author.Bio,
        "author_website": p.Author.Website,
        "author_twitter": p.Author.Twitter,
    }
}

func (p Post) SanitizedExcerptHTML() string {
    output := ""
    s := p.ExcerptHTML

    // Shortcut strings with no tags in them
    if !strings.ContainsAny(s, "<>") {
        output = s
    } else {
        // First remove line breaks etc as these have no meaning outside html tags (except pre)
        // this means pre sections will lose formatting... but will result in less uninentional paras.
        s = strings.Replace(s, "\n", "", -1)

        // Then replace line breaks with newlines, to preserve that formatting
        s = strings.Replace(s, "
", "\n", -1)
        s = strings.Replace(s, "
", "\n", -1)
        s = strings.Replace(s, "
", "\n", -1)
        s = strings.Replace(s, "\n", " ", -1)

        // Walk through the string removing all tags
        b := bytes.NewBufferString("")
        inTag := false
        for _, r := range s {
            switch r {
            case '<':
                inTag = true
            case '>':
                inTag = false
            default:
                if !inTag {
                    b.WriteRune(r)
                }
            }
        }
        output = b.String()
    }

    return output
}

func handler(w http.ResponseWriter, r *http.Request) {
    slug := r.URL.Path[1:]

    if slug == "" {
        fmt.Fprintf(w, "index")
        return
    }

    post, error := Get("sam", slug)
    if error != nil {
        fmt.Println(error)
        fmt.Fprintf(w, "error")
        return
    }

    output := mustache.RenderFile("templates/post.html.mustache", post.Mustache())
    fmt.Fprintf(w, output)
}

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = "3000"
    }

    http.HandleFunc("/", handler)
    http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
}

func Get(blogSubdomain string, postSlug string) (*Post, error) {
    url := fmt.Sprintf("https://roon.io/api/v1/blogs/%s/posts/%s", blogSubdomain, postSlug)
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
        return nil, errors.New(resp.Status)
    }
    r := new(Post)
    err = json.NewDecoder(resp.Body).Decode(r)
    if err != nil {
        return nil, err
    }
    return r, nil
}
<commit_msg>gofmt<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/hoisie/mustache"
	"net/http"
	"os"
	"strings"
)

type Blog struct {
	ID          int
	Name        string
	Language    string
	URL         string
	AccentColor string `json:"accent_color"`
}

type Author struct {
	GivenName  string `json:"given_name"`
	FamilyName string `json:"family_name"`
	Bio        string `json:"bio_html"`
	Website    string
	Twitter    string
	AvatarURL  string
}

func (a Author) Name() string {
	return fmt.Sprintf("%s %s", a.GivenName, a.FamilyName)
}

type Post struct {
	Title       string
	ContentHTML string `json:"content_html"`
	ExcerptHTML string `json:"excerpt_html"`
	URL         string
	ID          int
	Blog        Blog
	Author      Author `json:"user"`
}

func (p Post) Mustache() map[string]string {
	return map[string]string{
		"post_title":         p.Title,
		"post_excerpt_html":  p.SanitizedExcerptHTML(),
		"post_url":           p.URL,
		"post_content_html":  p.ContentHTML,
		"author_name":        p.Author.Name(),
		"author_given_name":  p.Author.GivenName,
		"author_family_name": p.Author.FamilyName,
		"author_bio_html":    p.Author.Bio,
		"author_website":     p.Author.Website,
		"author_twitter":     p.Author.Twitter,
	}
}

func (p Post) SanitizedExcerptHTML() string {
	output := ""
	s := p.ExcerptHTML

	// Shortcut strings with no tags in them
	if !strings.ContainsAny(s, "<>") {
		output = s
	} else {
		// First remove line breaks etc as these have no meaning outside html tags (except pre)
		// this means pre sections will lose formatting... but will result in less uninentional paras.
		s = strings.Replace(s, "\n", "", -1)

		// Then replace line breaks with newlines, to preserve that formatting
		s = strings.Replace(s, "
", "\n", -1)
		s = strings.Replace(s, "
", "\n", -1)
		s = strings.Replace(s, "
", "\n", -1)
		s = strings.Replace(s, "\n", " ", -1)

		// Walk through the string removing all tags
		b := bytes.NewBufferString("")
		inTag := false
		for _, r := range s {
			switch r {
			case '<':
				inTag = true
			case '>':
				inTag = false
			default:
				if !inTag {
					b.WriteRune(r)
				}
			}
		}
		output = b.String()
	}

	return output
}

func handler(w http.ResponseWriter, r *http.Request) {
	slug := r.URL.Path[1:]

	if slug == "" {
		fmt.Fprintf(w, "index")
		return
	}

	post, error := Get("sam", slug)
	if error != nil {
		fmt.Println(error)
		fmt.Fprintf(w, "error")
		return
	}

	output := mustache.RenderFile("templates/post.html.mustache", post.Mustache())
	fmt.Fprintf(w, output)
}

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "3000"
	}

	http.HandleFunc("/", handler)
	http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
}

func Get(blogSubdomain string, postSlug string) (*Post, error) {
	url := fmt.Sprintf("https://roon.io/api/v1/blogs/%s/posts/%s", blogSubdomain, postSlug)
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(resp.Status)
	}
	r := new(Post)
	err = json.NewDecoder(resp.Body).Decode(r)
	if err != nil {
		return nil, err
	}
	return r, nil
}
<|endoftext|>