<commit_before>// MIT License
//
// Copyright (c) 2019 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"time"

	log "github.com/Sirupsen/logrus"
	hue "github.com/stefanwichmann/go.hue"
)

const initializationDuration = 5 * time.Second

// Light represents a light kelvin can automate in your system.
type Light struct {
	ID               int        `json:"id"`
	Name             string     `json:"name"`
	HueLight         HueLight   `json:"-"`
	TargetLightState LightState `json:"targetLightState,omitempty"`
	Scheduled        bool       `json:"scheduled"`
	Reachable        bool       `json:"reachable"`
	On               bool       `json:"on"`
	Tracking         bool       `json:"-"`
	Automatic        bool       `json:"automatic"`
	Initializing     bool       `json:"-"`
	Schedule         Schedule   `json:"-"`
	Interval         Interval   `json:"interval"`
	Appearance       time.Time  `json:"-"`
}

func (light *Light) updateCurrentLightState(attr hue.LightAttributes) error {
	light.HueLight.updateCurrentLightState(attr)
	light.Reachable = light.HueLight.Reachable
	light.On = light.HueLight.On
	return nil
}

func (light *Light) update(transistionTime time.Duration) (bool, error) {
	// Is the light associated to any schedule?
	if !light.Scheduled {
		return false, nil
	}

	// If the light is not reachable anymore clean up
	if !light.Reachable {
		if light.Tracking {
			log.Printf("💡 Light %s - Light is no longer reachable. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			light.Initializing = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// If the light was turned off clean up
	if !light.On {
		if light.Tracking {
			log.Printf("💡 Light %s - Light was turned off. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			light.Initializing = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// Did the light just appear?
	if !light.Tracking {
		log.Printf("💡 Light %s - Light just appeared.", light.Name)
		light.Tracking = true
		light.Appearance = time.Now()

		// Should we auto-enable Kelvin?
		if light.Schedule.enableWhenLightsAppear {
			log.Printf("💡 Light %s - Initializing state to %vK at %v%% brightness.", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness, transistionTime)
			if err != nil {
				log.Debugf("💡 Light %s - Could not initialize light after %v", light.Name, time.Since(light.Appearance))
				return true, err
			}

			light.Automatic = true
			light.Initializing = true
			log.Debugf("💡 Light %s - Light was initialized to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}
	}

	// Ignore light if it was changed manually
	if !light.Automatic {
		// return if we should ignore color temperature and brightness
		if light.TargetLightState.ColorTemperature == -1 && light.TargetLightState.Brightness == -1 {
			return false, nil
		}

		// if status == scene state --> Activate Kelvin
		if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
			log.Printf("💡 Light %s - Detected matching target state. Activating Kelvin...", light.Name)
			light.Automatic = true
			light.Initializing = true

			// set correct target lightstate on HueLight
			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness, transistionTime)
			if err != nil {
				return true, err
			}
			log.Debugf("💡 Light %s - Updated light state to %vK at %v%% brightness (Scene detection)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}

		// Light was changed manually and does not conform to scene detection
		return false, nil
	}

	// Keep adjusting the light state for 10 seconds after the light appeared
	if light.Initializing {
		hasChanged := light.HueLight.hasChanged()

		// Disable initialization phase if 10 seconds have passed and the light state has been adopted
		if time.Now().After(light.Appearance.Add(initializationDuration)) && !hasChanged {
			log.Debugf("💡 Light %s - Ending initialization phase after %v", light.Name, time.Since(light.Appearance))
			light.Initializing = false
		}

		if hasChanged {
			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness, transistionTime)
			if err != nil {
				return true, err
			}
			log.Debugf("💡 Light %s - Adjusting light state to %vK at %v%% brightness (Initialization)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}

		return false, nil
	}

	// Did the user manually change the light state?
	if light.HueLight.hasChanged() {
		if log.GetLevel() == log.DebugLevel {
			log.Debugf("💡 Light %s - Light state has been changed manually after %v (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, time.Since(light.Appearance), light.HueLight.TargetColorTemperature, light.HueLight.CurrentColorTemperature, light.HueLight.TargetColor, light.HueLight.CurrentColor, light.HueLight.TargetBrightness, light.HueLight.CurrentBrightness)
		} else {
			log.Printf("💡 Light %s - Light state has been changed manually. Disabling Kelvin...", light.Name)
		}
		light.Automatic = false
		return false, nil
	}

	// Update of lightstate needed?
	if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
		return false, nil
	}

	// Light is turned on and in automatic state. Set target lightstate.
	err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness, transistionTime)
	if err != nil {
		return true, err
	}

	log.Printf("💡 Light %s - Updated light state to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	return true, nil
}

func (light *Light) updateSchedule(schedule Schedule) {
	light.Schedule = schedule
	light.Scheduled = true
	log.Printf("💡 Light %s - Activating schedule for %v (Sunrise: %v, Sunset: %v)", light.Name, light.Schedule.endOfDay.Format("Jan 2 2006"), light.Schedule.sunrise.Time.Format("15:04"), light.Schedule.sunset.Time.Format("15:04"))
	light.updateInterval()
}

func (light *Light) updateInterval() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No interval to update...", light.Name)
		return
	}

	newInterval, err := light.Schedule.currentInterval(time.Now())
	if err != nil {
		log.Warningf("💡 Light %s - Could not determine interval for current schedule: %v", light.Name, err)
		return
	}
	if newInterval != light.Interval {
		light.Interval = newInterval
		log.Printf("💡 Light %s - Activating interval %v - %v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"))
	}
}

func (light *Light) updateTargetLightState() bool {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No target light state to update...", light.Name)
		return false
	}

	// Calculate the target lightstate from the interval
	newLightState := light.Interval.calculateLightStateInInterval(time.Now())

	// Did the target light state change?
	if newLightState.equals(light.TargetLightState) {
		return false
	}

	// First initialization of the TargetLightState?
	if light.TargetLightState.ColorTemperature == 0 && light.TargetLightState.Brightness == 0 {
		log.Debugf("💡 Light %s - Initialized target light state for the interval %v - %v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), newLightState)
	} else {
		log.Debugf("💡 Light %s - Updated target light state for the interval %v - %v from %+v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), light.TargetLightState, newLightState)
	}

	light.TargetLightState = newLightState
	return true
}
<commit_msg>Reduce initialization period to three seconds<commit_after>// MIT License
//
// Copyright (c) 2019 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"time"

	log "github.com/Sirupsen/logrus"
	hue "github.com/stefanwichmann/go.hue"
)

const initializationDuration = 3 * time.Second

// Light represents a light kelvin can automate in your system.
type Light struct {
	ID               int        `json:"id"`
	Name             string     `json:"name"`
	HueLight         HueLight   `json:"-"`
	TargetLightState LightState `json:"targetLightState,omitempty"`
	Scheduled        bool       `json:"scheduled"`
	Reachable        bool       `json:"reachable"`
	On               bool       `json:"on"`
	Tracking         bool       `json:"-"`
	Automatic        bool       `json:"automatic"`
	Initializing     bool       `json:"-"`
	Schedule         Schedule   `json:"-"`
	Interval         Interval   `json:"interval"`
	Appearance       time.Time  `json:"-"`
}

func (light *Light) updateCurrentLightState(attr hue.LightAttributes) error {
	light.HueLight.updateCurrentLightState(attr)
	light.Reachable = light.HueLight.Reachable
	light.On = light.HueLight.On
	return nil
}

func (light *Light) update(transistionTime time.Duration) (bool, error) {
	// Is the light associated to any schedule?
	if !light.Scheduled {
		return false, nil
	}

	// If the light is not reachable anymore clean up
	if !light.Reachable {
		if light.Tracking {
			log.Printf("💡 Light %s - Light is no longer reachable. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			light.Initializing = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// If the light was turned off clean up
	if !light.On {
		if light.Tracking {
			log.Printf("💡 Light %s - Light was turned off. Clearing state...", light.Name)
			light.Tracking = false
			light.Automatic = false
			light.Initializing = false
			return false, nil
		}

		// Ignore light because we are not tracking it.
		return false, nil
	}

	// Did the light just appear?
	if !light.Tracking {
		log.Printf("💡 Light %s - Light just appeared.", light.Name)
		light.Tracking = true
		light.Appearance = time.Now()

		// Should we auto-enable Kelvin?
		if light.Schedule.enableWhenLightsAppear {
			log.Printf("💡 Light %s - Initializing state to %vK at %v%% brightness.", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)

			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness, transistionTime)
			if err != nil {
				log.Debugf("💡 Light %s - Could not initialize light after %v", light.Name, time.Since(light.Appearance))
				return true, err
			}

			light.Automatic = true
			light.Initializing = true
			log.Debugf("💡 Light %s - Light was initialized to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}
	}

	// Ignore light if it was changed manually
	if !light.Automatic {
		// return if we should ignore color temperature and brightness
		if light.TargetLightState.ColorTemperature == -1 && light.TargetLightState.Brightness == -1 {
			return false, nil
		}

		// if status == scene state --> Activate Kelvin
		if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
			log.Printf("💡 Light %s - Detected matching target state. Activating Kelvin...", light.Name)
			light.Automatic = true
			light.Initializing = true

			// set correct target lightstate on HueLight
			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness, transistionTime)
			if err != nil {
				return true, err
			}
			log.Debugf("💡 Light %s - Updated light state to %vK at %v%% brightness (Scene detection)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}

		// Light was changed manually and does not conform to scene detection
		return false, nil
	}

	// Keep adjusting the light state for 10 seconds after the light appeared
	if light.Initializing {
		log.Debugf("💡 Light %s - Light in initialization for %v (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, time.Since(light.Appearance), light.HueLight.TargetColorTemperature, light.HueLight.CurrentColorTemperature, light.HueLight.TargetColor, light.HueLight.CurrentColor, light.HueLight.TargetBrightness, light.HueLight.CurrentBrightness)
		hasChanged := light.HueLight.hasChanged()

		// Disable initialization phase if 10 seconds have passed and the light state has been adopted
		if time.Now().After(light.Appearance.Add(initializationDuration)) && !hasChanged {
			log.Debugf("💡 Light %s - Ending initialization phase after %v", light.Name, time.Since(light.Appearance))
			light.Initializing = false
		}

		if hasChanged {
			err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness, transistionTime)
			if err != nil {
				return true, err
			}
			log.Debugf("💡 Light %s - Adjusting light state to %vK at %v%% brightness (Initialization)", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
			return true, nil
		}

		return false, nil
	}

	// Did the user manually change the light state?
	if light.HueLight.hasChanged() {
		if log.GetLevel() == log.DebugLevel {
			log.Debugf("💡 Light %s - Light state has been changed manually after %v (TargetColorTemperature: %d, CurrentColorTemperature: %d, TargetColor: %v, CurrentColor: %v, TargetBrightness: %d, CurrentBrightness: %d)", light.Name, time.Since(light.Appearance), light.HueLight.TargetColorTemperature, light.HueLight.CurrentColorTemperature, light.HueLight.TargetColor, light.HueLight.CurrentColor, light.HueLight.TargetBrightness, light.HueLight.CurrentBrightness)
		} else {
			log.Printf("💡 Light %s - Light state has been changed manually. Disabling Kelvin...", light.Name)
		}
		light.Automatic = false
		return false, nil
	}

	// Update of lightstate needed?
	if light.HueLight.hasState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness) {
		return false, nil
	}

	// Light is turned on and in automatic state. Set target lightstate.
	err := light.HueLight.setLightState(light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness, transistionTime)
	if err != nil {
		return true, err
	}

	log.Printf("💡 Light %s - Updated light state to %vK at %v%% brightness", light.Name, light.TargetLightState.ColorTemperature, light.TargetLightState.Brightness)
	return true, nil
}

func (light *Light) updateSchedule(schedule Schedule) {
	light.Schedule = schedule
	light.Scheduled = true
	log.Printf("💡 Light %s - Activating schedule for %v (Sunrise: %v, Sunset: %v)", light.Name, light.Schedule.endOfDay.Format("Jan 2 2006"), light.Schedule.sunrise.Time.Format("15:04"), light.Schedule.sunset.Time.Format("15:04"))
	light.updateInterval()
}

func (light *Light) updateInterval() {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No interval to update...", light.Name)
		return
	}

	newInterval, err := light.Schedule.currentInterval(time.Now())
	if err != nil {
		log.Warningf("💡 Light %s - Could not determine interval for current schedule: %v", light.Name, err)
		return
	}
	if newInterval != light.Interval {
		light.Interval = newInterval
		log.Printf("💡 Light %s - Activating interval %v - %v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"))
	}
}

func (light *Light) updateTargetLightState() bool {
	if !light.Scheduled {
		log.Debugf("💡 Light %s - Light is not associated to any schedule. No target light state to update...", light.Name)
		return false
	}

	// Calculate the target lightstate from the interval
	newLightState := light.Interval.calculateLightStateInInterval(time.Now())

	// Did the target light state change?
	if newLightState.equals(light.TargetLightState) {
		return false
	}

	// First initialization of the TargetLightState?
	if light.TargetLightState.ColorTemperature == 0 && light.TargetLightState.Brightness == 0 {
		log.Debugf("💡 Light %s - Initialized target light state for the interval %v - %v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), newLightState)
	} else {
		log.Debugf("💡 Light %s - Updated target light state for the interval %v - %v from %+v to %+v", light.Name, light.Interval.Start.Time.Format("15:04"), light.Interval.End.Time.Format("15:04"), light.TargetLightState, newLightState)
	}

	light.TargetLightState = newLightState
	return true
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"os"
)

var ARGV []string

func main() {
	ARGV = os.Args[1:]

	// If there are no arguments, then we read STDIN
	if len(ARGV) == 0 {
		ARGV = append(ARGV, "-")
	}

	// Iterate arguments to read filenames
	for _, filename := range ARGV {
		// - means read stdin as a special case, we just use /dev/stdin instead
		if filename == "-" {
			filename = "/dev/stdin"
		}

		// Time to try and read the file in question
		f, err := os.Open(filename)
		if err != nil {
			fmt.Fprintf(os.Stderr, "gocat: %s: No such file or directory\n", filename)
			continue
		}

		// Copy our output across!
		io.Copy(os.Stdout, f)
	}

}
<commit_msg>Name ourselves cat<commit_after>package main

import (
	"fmt"
	"io"
	"os"
)

var ARGV []string

func main() {
	ARGV = os.Args[1:]

	// If there are no arguments, then we read STDIN
	if len(ARGV) == 0 {
		ARGV = append(ARGV, "-")
	}

	// Iterate arguments to read filenames
	for _, filename := range ARGV {
		// - means read stdin as a special case, we just use /dev/stdin instead
		if filename == "-" {
			filename = "/dev/stdin"
		}

		// Time to try and read the file in question
		f, err := os.Open(filename)
		if err != nil {
			fmt.Fprintf(os.Stderr, "cat: %s: No such file or directory\n", filename)
			continue
		}

		// Copy our output across!
		io.Copy(os.Stdout, f)
	}

}
<|endoftext|>


<commit_before>package godbg

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"regexp"
	"runtime/debug"
	"strings"
)

// http://stackoverflow.com/a/23554672/6309 https://vividcortex.com/blog/2013/12/03/go-idiom-package-and-object/
// you design a type with methods as usual, and then you also place matching functions at the package level itself.
// These functions simply delegate to a default instance of the type that’s a private package-level variable, created in an init() function.

// Pdbg allows to print debug message with indent and function name added
type Pdbg struct {
	bout     *bytes.Buffer
	berr     *bytes.Buffer
	sout     *bufio.Writer
	serr     *bufio.Writer
	breaks   []string
	excludes []string
}

// Out returns a writer for normal messages.
// By default, os.StdOut
func Out() io.Writer {
	return pdbg.Out()
}

// Out returns a writer for normal messages for a given pdbg instance.
// By default, os.StdOut
func (pdbg *Pdbg) Out() io.Writer {
	if pdbg.sout == nil {
		return os.Stdout
	}
	return pdbg.sout
}

// Err returns a writer for error messages.
// By default, os.StdErr
func Err() io.Writer {
	return pdbg.Err()
}

// Err returns a writer for error messages for a given pdbg instance.
// By default, os.StdErr
func (pdbg *Pdbg) Err() io.Writer {
	if pdbg.serr == nil {
		return os.Stderr
	}
	return pdbg.serr
}

// global pdbg used for printing
var pdbg = NewPdbg()

// Option set an option for a Pdbg
// http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
type Option func(*Pdbg)

// SetBuffers is an option for replacing stdout and stderr by
// bytes buffers (in a bufio.Writer).
// If apdbg is nil, set for the global pdbg instance
func SetBuffers(apdbg *Pdbg) {
	if apdbg == nil {
		apdbg = pdbg
	}
	apdbg.bout = bytes.NewBuffer(nil)
	apdbg.sout = bufio.NewWriter(apdbg.bout)
	apdbg.berr = bytes.NewBuffer(nil)
	apdbg.serr = bufio.NewWriter(apdbg.berr)
}

// SetExcludes set excludes on a pdbg (nil for global pdbg)
func (apdbg *Pdbg) SetExcludes(excludes []string) {
	apdbg.excludes = excludes
}

// OptExcludes is an option to set excludes at the creation of a pdbg
func OptExcludes(excludes []string) Option {
	return func(apdbg *Pdbg) {
		if apdbg == nil {
			apdbg = pdbg
		}
		apdbg.SetExcludes(excludes)
	}
}

// NewPdbg creates a PDbg instance, with options
func NewPdbg(options ...Option) *Pdbg {
	newpdbg := &Pdbg{}
	for _, option := range options {
		option(newpdbg)
	}
	newpdbg.breaks = append(newpdbg.breaks, "smartystreets")
	return newpdbg
}

// ResetIOs reset the out and err buffer of global pdbg instance
func ResetIOs() {
	pdbg.ResetIOs()
}

// ResetIOs reset the out and err buffer
// (unless they were the default stdout and stderr,
// in which case it does nothing)
func (pdbg *Pdbg) ResetIOs() {
	if pdbg.sout != nil {
		pdbg.bout = bytes.NewBuffer(nil)
		pdbg.sout.Reset(pdbg.bout)
		pdbg.berr = bytes.NewBuffer(nil)
		pdbg.serr.Reset(pdbg.berr)
	}
}

// OutString returns the string for out messages for the global pdbg instance.
// It flushes the out buffer.
// If out is set to os.Stdout, returns an empty string
func OutString() string {
	return pdbg.OutString()
}

// OutString returns the string for out messages for a given pdbg instance.
// It flushes the out buffer.
// If out is set to os.Stdout, returns an empty string
func (pdbg *Pdbg) OutString() string {
	if pdbg.sout == nil {
		return ""
	}
	pdbg.sout.Flush()
	return pdbg.bout.String()
}

// ErrString returns the string for error messages for the global pdbg instance.
// It flushes the err buffer.
// If err is set to os.StdErr, returns an empty string
func ErrString() string {
	return pdbg.ErrString()
}

// ErrString returns the string for error messages for a given pdbg instance.
// It flushes the err buffer.
// If err is set to os.StdErr, returns an empty string
func (pdbg *Pdbg) ErrString() string {
	if pdbg.serr == nil {
		return ""
	}
	pdbg.serr.Flush()
	return pdbg.berr.String()
}

// For instance: github.com/VonC/godbg/_test/_obj_test/gogdb.go:174 (0x44711b)
var rxDbgLine, _ = regexp.Compile(`^.*\.go:(\d+)\s`)
var rxDbgFnct, _ = regexp.Compile(`^\s+(?:.*?\(([^\)]+)\))?\.?([^:]+)`)

func pdbgInc(scanner *bufio.Scanner, dbgLine string) string {
	scanner.Scan()
	line := scanner.Text()
	mf := rxDbgFnct.FindSubmatchIndex([]byte(line))
	// fmt.Printf("lineF '%v', mf '%+v'\n", line, mf)
	/*if len(mf) == 0 {
		return ""
	}*/
	dbgFnct := ""
	if mf[2] > -1 {
		dbgFnct = line[mf[2]:mf[3]]
	}
	if dbgFnct != "" {
		dbgFnct = dbgFnct + "."
	}
	dbgFnct = dbgFnct + line[mf[4]:mf[5]]

	return dbgFnct + ":" + dbgLine
}

func pdbgExcluded(dbg string) bool {
	for _, e := range pdbg.excludes {
		if strings.Contains(dbg, e) {
			return true
		}
	}
	return false
}

func (pdbg *Pdbg) pdbgBreak(dbg string) bool {
	for _, b := range pdbg.breaks {
		if strings.Contains(dbg, b) {
			return true
		}
	}
	return false
}

// Pdbgf uses global Pdbg variable for printing strings, with indent and function name
func Pdbgf(format string, args ...interface{}) string {
	return pdbg.Pdbgf(format, args...)
}

// Pdbgf uses custom Pdbg variable for printing strings, with indent and function name
func (pdbg *Pdbg) Pdbgf(format string, args ...interface{}) string {
	msg := fmt.Sprintf(format+"\n", args...)
	msg = strings.TrimSpace(msg)
	bstack := bytes.NewBuffer(debug.Stack())
	// fmt.Printf("%+v\n", bstack)

	scanner := bufio.NewScanner(bstack)
	pmsg := ""
	depth := 0
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Contains(line, "/_obj_test/") {
			depth = 1
			continue
		}
		if pdbg.pdbgBreak(line) {
			break
		}
		m := rxDbgLine.FindSubmatchIndex([]byte(line))
		// fmt.Printf("'%s' (%s) => '%+v'\n", line, rxDbgLine.String(), m)
		/*if len(m) == 0 {
			continue
		}*/
		if depth > 0 && depth < 4 {
			dbg := pdbgInc(scanner, line[m[2]:m[3]])
			if dbg == "" {
				continue
			}
			if depth == 1 {
				if pdbgExcluded(dbg) {
					return ""
				}
				pmsg = "[" + dbg + "]"
			} else {
				pmsg = pmsg + " (" + dbg + ")"
			}
		}
		depth = depth + 1
	}
	spaces := ""
	if depth >= 2 {
		spaces = strings.Repeat(" ", depth-2)
	}
	// fmt.Printf("spaces '%s', depth '%d'\n", spaces, depth)
	res := pmsg
	pmsg = spaces + pmsg
	msg = pmsg + "\n" + spaces + "  " + msg + "\n"
	// fmt.Printf("MSG '%v'\n", msg)
	fmt.Fprint(pdbg.Err(), fmt.Sprint(msg))
	return res
}
<commit_msg>OptExcludes() doesn't need to consider global pdbg<commit_after>package godbg

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"regexp"
	"runtime/debug"
	"strings"
)

// http://stackoverflow.com/a/23554672/6309 https://vividcortex.com/blog/2013/12/03/go-idiom-package-and-object/
// you design a type with methods as usual, and then you also place matching functions at the package level itself.
// These functions simply delegate to a default instance of the type that’s a private package-level variable, created in an init() function.

// Pdbg allows to print debug message with indent and function name added
type Pdbg struct {
	bout     *bytes.Buffer
	berr     *bytes.Buffer
	sout     *bufio.Writer
	serr     *bufio.Writer
	breaks   []string
	excludes []string
}

// Out returns a writer for normal messages.
// By default, os.StdOut
func Out() io.Writer {
	return pdbg.Out()
}

// Out returns a writer for normal messages for a given pdbg instance.
// By default, os.StdOut
func (pdbg *Pdbg) Out() io.Writer {
	if pdbg.sout == nil {
		return os.Stdout
	}
	return pdbg.sout
}

// Err returns a writer for error messages.
// By default, os.StdErr
func Err() io.Writer {
	return pdbg.Err()
}

// Err returns a writer for error messages for a given pdbg instance.
// By default, os.StdErr
func (pdbg *Pdbg) Err() io.Writer {
	if pdbg.serr == nil {
		return os.Stderr
	}
	return pdbg.serr
}

// global pdbg used for printing
var pdbg = NewPdbg()

// Option set an option for a Pdbg
// http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
type Option func(*Pdbg)

// SetBuffers is an option for replacing stdout and stderr by
// bytes buffers (in a bufio.Writer).
// If apdbg is nil, set for the global pdbg instance
func SetBuffers(apdbg *Pdbg) {
	if apdbg == nil {
		apdbg = pdbg
	}
	apdbg.bout = bytes.NewBuffer(nil)
	apdbg.sout = bufio.NewWriter(apdbg.bout)
	apdbg.berr = bytes.NewBuffer(nil)
	apdbg.serr = bufio.NewWriter(apdbg.berr)
}

// SetExcludes set excludes on a pdbg (nil for global pdbg)
func (apdbg *Pdbg) SetExcludes(excludes []string) {
	apdbg.excludes = excludes
}

// OptExcludes is an option to set excludes at the creation of a pdbg
func OptExcludes(excludes []string) Option {
	return func(apdbg *Pdbg) {
		apdbg.SetExcludes(excludes)
	}
}

// NewPdbg creates a PDbg instance, with options
func NewPdbg(options ...Option) *Pdbg {
	newpdbg := &Pdbg{}
	for _, option := range options {
		option(newpdbg)
	}
	newpdbg.breaks = append(newpdbg.breaks, "smartystreets")
	return newpdbg
}

// ResetIOs reset the out and err buffer of global pdbg instance
func ResetIOs() {
	pdbg.ResetIOs()
}

// ResetIOs reset the out and err buffer
// (unless they were the default stdout and stderr,
// in which case it does nothing)
func (pdbg *Pdbg) ResetIOs() {
	if pdbg.sout != nil {
		pdbg.bout = bytes.NewBuffer(nil)
		pdbg.sout.Reset(pdbg.bout)
		pdbg.berr = bytes.NewBuffer(nil)
		pdbg.serr.Reset(pdbg.berr)
	}
}

// OutString returns the string for out messages for the global pdbg instance.
// It flushes the out buffer.
// If out is set to os.Stdout, returns an empty string
func OutString() string {
	return pdbg.OutString()
}

// OutString returns the string for out messages for a given pdbg instance.
// It flushes the out buffer.
// If out is set to os.Stdout, returns an empty string
func (pdbg *Pdbg) OutString() string {
	if pdbg.sout == nil {
		return ""
	}
	pdbg.sout.Flush()
	return pdbg.bout.String()
}

// ErrString returns the string for error messages for the global pdbg instance.
// It flushes the err buffer.
// If err is set to os.StdErr, returns an empty string
func ErrString() string {
	return pdbg.ErrString()
}

// ErrString returns the string for error messages for a given pdbg instance.
// It flushes the err buffer.
// If err is set to os.StdErr, returns an empty string
func (pdbg *Pdbg) ErrString() string {
	if pdbg.serr == nil {
		return ""
	}
	pdbg.serr.Flush()
	return pdbg.berr.String()
}

// For instance: github.com/VonC/godbg/_test/_obj_test/gogdb.go:174 (0x44711b)
var rxDbgLine, _ = regexp.Compile(`^.*\.go:(\d+)\s`)
var rxDbgFnct, _ = regexp.Compile(`^\s+(?:.*?\(([^\)]+)\))?\.?([^:]+)`)

func pdbgInc(scanner *bufio.Scanner, dbgLine string) string {
	scanner.Scan()
	line := scanner.Text()
	mf := rxDbgFnct.FindSubmatchIndex([]byte(line))
	// fmt.Printf("lineF '%v', mf '%+v'\n", line, mf)
	/*if len(mf) == 0 {
		return ""
	}*/
	dbgFnct := ""
	if mf[2] > -1 {
		dbgFnct = line[mf[2]:mf[3]]
	}
	if dbgFnct != "" {
		dbgFnct = dbgFnct + "."
	}
	dbgFnct = dbgFnct + line[mf[4]:mf[5]]

	return dbgFnct + ":" + dbgLine
}

func pdbgExcluded(dbg string) bool {
	for _, e := range pdbg.excludes {
		if strings.Contains(dbg, e) {
			return true
		}
	}
	return false
}

func (pdbg *Pdbg) pdbgBreak(dbg string) bool {
	for _, b := range pdbg.breaks {
		if strings.Contains(dbg, b) {
			return true
		}
	}
	return false
}

// Pdbgf uses global Pdbg variable for printing strings, with indent and function name
func Pdbgf(format string, args ...interface{}) string {
	return pdbg.Pdbgf(format, args...)
}

// Pdbgf uses custom Pdbg variable for printing strings, with indent and function name
func (pdbg *Pdbg) Pdbgf(format string, args ...interface{}) string {
	msg := fmt.Sprintf(format+"\n", args...)
	msg = strings.TrimSpace(msg)
	bstack := bytes.NewBuffer(debug.Stack())
	// fmt.Printf("%+v\n", bstack)

	scanner := bufio.NewScanner(bstack)
	pmsg := ""
	depth := 0
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Contains(line, "/_obj_test/") {
			depth = 1
			continue
		}
		if pdbg.pdbgBreak(line) {
			break
		}
		m := rxDbgLine.FindSubmatchIndex([]byte(line))
		// fmt.Printf("'%s' (%s) => '%+v'\n", line, rxDbgLine.String(), m)
		/*if len(m) == 0 {
			continue
		}*/
		if depth > 0 && depth < 4 {
			dbg := pdbgInc(scanner, line[m[2]:m[3]])
			if dbg == "" {
				continue
			}
			if depth == 1 {
				if pdbgExcluded(dbg) {
					return ""
				}
				pmsg = "[" + dbg + "]"
			} else {
				pmsg = pmsg + " (" + dbg + ")"
			}
		}
		depth = depth + 1
	}
	spaces := ""
	if depth >= 2 {
		spaces = strings.Repeat(" ", depth-2)
	}
	// fmt.Printf("spaces '%s', depth '%d'\n", spaces, depth)
	res := pmsg
	pmsg = spaces + pmsg
	msg = pmsg + "\n" + spaces + "  " + msg + "\n"
	// fmt.Printf("MSG '%v'\n", msg)
	fmt.Fprint(pdbg.Err(), fmt.Sprint(msg))
	return res
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Kaissersoft Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gomdb

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
)

//=======================================================================
//							Const
//=======================================================================

const baseURL string = "http://www.omdbapi.com/?"
const plot string = "full"
const tomatoes string = "true"

//=======================================================================
//							Global vars
//=======================================================================

//=======================================================================
//							Structs
//=======================================================================

//SearchResult is the type for the search results
type SearchResult struct {
	Title  string
	Year   string
	ImdbID string
	Type   string
}

//SearchResponse is the struct of the response in a search
type SearchResponse struct {
	Search   []SearchResult
	Response string
	Error    string
}

//MovieResult is the result struct of an specific movie search
type MovieResult struct {
	Title             string
	Year              string
	Rated             string
	Released          string
	Runtime           string
	Genre             string
	Director          string
	Writer            string
	Actors            string
	Plot              string
	Language          string
	Country           string
	Awards            string
	Poster            string
	Metascore         string
	ImdbRating        string
	ImdbVotes         string
	ImdbID            string
	Type              string
	TomatoMeter       string
	TomatoImage       string
	TomatoRating      string
	TomatoReviews     string
	TomatoFresh       string
	TomatoRotten      string
	TomatoConsensus   string
	TomatoUserMeter   string
	TomatoUserRating  string
	TomatoUserReviews string
	DVD               string
	BoxOffice         string
	Production        string
	Website           string
	Response          string
	Error             string
}

//=======================================================================
//							Funcs
//=======================================================================

//Search search for movies given a Title and year, Year is optional you can pass nil
func Search(title string, year string) (*SearchResponse, error) {
	resp, err := requestAPI(title, "", "", year)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	r := new(SearchResponse)
	err = json.NewDecoder(resp.Body).Decode(r)

	if err != nil {
		return nil, err
	}
	if r.Response == "False" {
		return r, errors.New(r.Error)
	}

	return r, nil
}

//MovieByTitle returns a MovieResult given Title
func MovieByTitle(title string, year string) (*MovieResult, error) {
	resp, err := requestAPI("", "", title, year)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	r := new(MovieResult)
	err = json.NewDecoder(resp.Body).Decode(r)

	if err != nil {
		return nil, err
	}
	if r.Response == "False" {
		return r, errors.New(r.Error)
	}
	return r, nil
}

//MovieByImdbID returns a MovieResult given a ImdbID ex:"tt2015381"
func MovieByImdbID(id string) (*MovieResult, error) {
	resp, err := requestAPI("", id, "", "")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	r := new(MovieResult)
	err = json.NewDecoder(resp.Body).Decode(r)

	if err != nil {
		return nil, err
	}
	if r.Response == "False" {
		return r, errors.New(r.Error)
	}
	return r, nil
}

func requestAPI(s string, i string, t string, y string) (resp *http.Response, err error) {
	//s = Search Parameter, if this is != nil then its a searchMovies
	//i = Id Parameter, if this is != nil then its a getMovieByImdbID
	//t = Title Parameter, if this is != nil then its a getMovieByTitle
	//y = Year Parameter, Optional data for s and t search
	//var res http.Response

	var URL *url.URL
	URL, err = url.Parse(baseURL)

	if err != nil {
		return nil, err
	}
	URL.Path += "/"
	parameters := url.Values{}
	if len(s) > 0 {
		parameters.Add("s", s)
		parameters.Add("y", y)
	} else if len(i) > 0 {
		parameters.Add("i", i)
		parameters.Add("plot", plot)
		parameters.Add("tomatoes", tomatoes)
	} else if len(t) > 0 {
		parameters.Add("t", t)
		parameters.Add("plot", plot)
		parameters.Add("tomatoes", tomatoes)
		parameters.Add("y", y)
	} else {
		return nil, errors.New("Invalid Request")
	}
	URL.RawQuery = parameters.Encode()
	res, err := http.Get(URL.String())
	err = checkErr(res.StatusCode)
	if err != nil {
		return nil, err
	}
	return res, nil
}

func checkErr(status int) error {
	if status != 200 {
		return fmt.Errorf("Status Code %d received from IMDB", status)
	}
	return nil
}

//Stringer Interface for MovieResult
func (mr MovieResult) String() string {
	return fmt.Sprintf("#%s: %s (%s)", mr.ImdbID, mr.Title, mr.Year)
}

//Stringer Interface for SearchResult
func (sr SearchResult) String() string {
	return fmt.Sprintf("#%s: %s (%s) Type: %s", sr.ImdbID, sr.Title, sr.Year, sr.Type)
}
<commit_msg>add tomatoURL<commit_after>/*
Copyright 2014 Kaissersoft Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gomdb

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
)

//=======================================================================
//							Const
//=======================================================================

const baseURL string = "http://www.omdbapi.com/?"
const plot string = "full"
const tomatoes string = "true"

//=======================================================================
//							Global vars
//=======================================================================

//=======================================================================
//							Structs
//=======================================================================

//SearchResult is the type for the search results
type SearchResult struct {
	Title  string
	Year   string
	ImdbID string
	Type   string
}

//SearchResponse is the struct of the response in a search
type SearchResponse struct {
	Search   []SearchResult
	Response string
	Error    string
}

//MovieResult is the result struct of an specific movie search
type MovieResult struct {
	Title             string
	Year              string
	Rated             string
	Released          string
	Runtime           string
	Genre             string
	Director          string
	Writer            string
	Actors            string
	Plot              string
	Language          string
	Country           string
	Awards            string
	Poster            string
	Metascore         string
	ImdbRating        string
	ImdbVotes         string
	ImdbID            string
	Type              string
	TomatoMeter       string
	TomatoImage       string
	TomatoRating      string
	TomatoReviews     string
	TomatoFresh       string
	TomatoRotten      string
	TomatoConsensus   string
	TomatoUserMeter   string
	TomatoUserRating  string
	TomatoUserReviews string
	TomatoURL         string
	DVD               string
	BoxOffice         string
	Production        string
	Website           string
	Response          string
	Error             string
}

//=======================================================================
//							Funcs
//=======================================================================

//Search search for movies given a Title and year, Year is optional you can pass nil
func Search(title string, year string) (*SearchResponse, error) {
	resp, err := requestAPI(title, "", "", year)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	r := new(SearchResponse)
	err = json.NewDecoder(resp.Body).Decode(r)

	if err != nil {
		return nil, err
	}
	if r.Response == "False" {
		return r, errors.New(r.Error)
	}

	return r, nil
}

//MovieByTitle returns a MovieResult given Title
func MovieByTitle(title string, year string) (*MovieResult, error) {
	resp, err := requestAPI("", "", title, year)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	r := new(MovieResult)
	err = json.NewDecoder(resp.Body).Decode(r)

	if err != nil {
		return nil, err
	}
	if r.Response == "False" {
		return r, errors.New(r.Error)
	}
	return r, nil
}

//MovieByImdbID returns a MovieResult given a ImdbID ex:"tt2015381"
func MovieByImdbID(id string) (*MovieResult, error) {
	resp, err := requestAPI("", id, "", "")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	r := new(MovieResult)
	err = json.NewDecoder(resp.Body).Decode(r)

	if err != nil {
		return nil, err
	}
	if r.Response == "False" {
		return r, errors.New(r.Error)
	}
	return r, nil
}

func requestAPI(s string, i string, t string, y string) (resp *http.Response, err error) {
	//s = Search Parameter, if this is != nil then its a searchMovies
	//i = Id Parameter, if this is != nil then its a getMovieByImdbID
	//t = Title Parameter, if this is != nil then its a getMovieByTitle
	//y = Year Parameter, Optional data for s and t search
	//var res http.Response

	var URL *url.URL
	URL, err = url.Parse(baseURL)

	if err != nil {
		return nil, err
	}
	URL.Path += "/"
	parameters := url.Values{}
	if len(s) > 0 {
		parameters.Add("s", s)
		parameters.Add("y", y)
	} else if len(i) > 0 {
		parameters.Add("i", i)
		parameters.Add("plot", plot)
		parameters.Add("tomatoes", tomatoes)
	} else if len(t) > 0 {
		parameters.Add("t", t)
		parameters.Add("plot", plot)
		parameters.Add("tomatoes", tomatoes)
		parameters.Add("y", y)
	} else {
		return nil, errors.New("Invalid Request")
	}
	URL.RawQuery = parameters.Encode()
	res, err := http.Get(URL.String())
	err = checkErr(res.StatusCode)
	if err != nil {
		return nil, err
	}
	return res, nil
}

func checkErr(status int) error {
	if status != 200 {
		return fmt.Errorf("Status Code %d received from IMDB", status)
	}
	return nil
}

//Stringer Interface for MovieResult
func (mr MovieResult) String() string {
	return fmt.Sprintf("#%s: %s (%s)", mr.ImdbID, mr.Title, mr.Year)
}

//Stringer Interface for SearchResult
func (sr SearchResult) String() string {
	return fmt.Sprintf("#%s: %s (%s) Type: %s", sr.ImdbID, sr.Title, sr.Year, sr.Type)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/base64"
	"errors"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
)

// URLCookie - cookie which stores page url
const URLCookie = "X-GoPee-URL"

// GopeeEncPrefix - encoding prefix used while encoding urls
const GopeeEncPrefix = "xox"

// Pre-compile RegEx
var reBase = regexp.MustCompile(`base +href="(.*?)"`)
var reHTML = regexp.MustCompile(`\saction=["']?(.*?)["'\s]|\shref=["']?(.*?)["'\s]|\ssrc=["']?(.*?)["'\s]`)
var reCSS = regexp.MustCompile(`url\(["']?(.*?)["']?\)`)

var reBase64 = regexp.MustCompile("^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=)?$")

var httpClient = &http.Client{}

// Hop-by-hop headers
var hopHeaders = map[string]bool{
	"connection":          true,
	"keep-alive":          true,
	"proxy-authenticate":  true,
	"proxy-authorization": true,
	"te":                true,
	"trailers":          true,
	"transfer-encoding": true,
	"upgrade":           true,
}

// Headers that create problem handling response
// TODO: support gzip compressed response in future
var problemHeaders = map[string]bool{
	"content-security-policy": true, // sent in response
	"accept-encoding":         true, // sent in request
}

type proxyManager struct {
	req  *http.Request
	uri  *url.URL
	resp *http.Response
}

func encodeURL(plainURL string) string {
	return GopeeEncPrefix + base64.URLEncoding.EncodeToString([]byte(plainURL))
}

// ProxyRequest creates a new request to be proxied
func ProxyRequest(r *http.Request, w http.ResponseWriter) {
	var uri *url.URL

	// Path can be of the form
	// /base64-encoded-string
	// /home/ajax.php

	path := strings.TrimSpace(r.URL.Path[1:])

	if strings.HasPrefix(path, GopeeEncPrefix) {
		// URL rewritten by GoPee
		path = strings.TrimLeft(path, GopeeEncPrefix)
		components := strings.Split(path, "/")
		decodedURL, err := base64.URLEncoding.DecodeString(components[0])
		if err != nil {
			log.Println(err.Error(), " for url ", path)
			return
		}
		uri, err = url.Parse(string(decodedURL[:]))
		if err != nil {
			log.Println("Error Parsing " + string(decodedURL[:]))
		}
	} else {
		// Might be a plain-text URL which was not rewritten
		urlCookie, _ := r.Cookie(URLCookie)
		// check if a cookie is set
		if urlCookie != nil {
			baseURL, err := url.Parse(urlCookie.Value)
			if err != nil {
				log.Println("Error Parsing " + urlCookie.Value)
			}
			path += "?" + r.URL.RawQuery
			pathURI, err := url.Parse(path)
			if err != nil {
				log.Println("Error Parsing " + path)
			}
			if pathURI.IsAbs() {
				uri = pathURI
			} else {
				uri = baseURL.ResolveReference(pathURI)
			}
		}
	}
	// log.Println(uri.String())
	if uri == nil {
		// return a 404
		http.NotFound(w, r)
	} else {
		// try fetching the url
		proxyMan := &proxyManager{r, uri, nil}
		proxyMan.Fetch(w)
	}
}

// Fetch makes the actual request to server and writes data with rewritten URLs to response
func (pm *proxyManager) Fetch(w http.ResponseWriter) (err error) {
	if pm.uri == nil {
		return errors.New("No URI specified to fetch")
	}
	// log.Println("Fetch: " + pm.uri.String())
	req, _ := http.NewRequest(pm.req.Method, pm.uri.String(), pm.req.Body)
	// Forward request headers, included User-Agent to server
	copyHeader(req.Header, pm.req.Header)
	req.Proto = "HTTP/1.1"
	req.ProtoMajor = 1
	req.ProtoMinor = 1
	pm.resp, err = httpClient.Do(req)
	if err != nil {
		log.Println("Error Fetching " + pm.uri.String())
		return err
	}
	defer pm.resp.Body.Close()

	contentType := pm.resp.Header.Get("Content-Type")

	// Forward response headers to client
	copyHeader(w.Header(), pm.resp.Header)

	// Rewrite all urls
	if strings.Contains(contentType, "text/html") {
		// HTTP is stateless, store the url in cookie to handle
		// AJAX requests for which the URLs were not rewritten
		// And don't set cookies for responses to AJAX requests
		if strings.ToLower(pm.req.Header.Get("X-Requested-With")) != "xmlhttprequest" {
			pm.uri.Fragment = ""
			// log.Println("Cookie URI ", pm.uri)
			cookie := &http.Cookie{Name: URLCookie, Value: pm.uri.String()}
			http.SetCookie(w, cookie)
		}
		pm.rewriteHTML(w)
	} else if strings.Contains(contentType, "text/css") {
		pm.rewriteCSS(w)
	} else {
		io.Copy(w, pm.resp.Body)
	}
	return nil
}

// Copy Headers from src to dst ignoring hop-by-hop headers
func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		// Don't copy hop-by-hop headers and problem headers
		if hopHeaders[strings.ToLower(k)] || problemHeaders[strings.ToLower(k)] {
			dst.Del(k) // remove any existing hop-by-hop header
			continue
		}
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

func (pm *proxyManager) rewriteHTML(w http.ResponseWriter) {
	body, _ := ioutil.ReadAll(pm.resp.Body)
	// if there's a <base href> specified in the document
	// use that as base to encode all URLs in the page
	baseHrefMatch := reBase.FindSubmatch(body)
	if len(baseHrefMatch) > 0 {
		var err error
		pm.uri, err = url.Parse(string(baseHrefMatch[1][:]))
		if err != nil {
			log.Println("Error Parsing " + string(baseHrefMatch[1][:]))
		}
	}
	encodedBody := reHTML.ReplaceAllFunc(body, func(s []byte) []byte {
		parts := reHTML.FindSubmatchIndex(s)
		if parts != nil {
			// replace src attribute
			srcIndex := parts[2:4]
			if srcIndex[0] != -1 {
				return pm.encodeURL(s, srcIndex[0], srcIndex[1])
			}

			// replace href attribute
			hrefIndex := parts[4:6]
			if hrefIndex[0] != -1 {
				return pm.encodeURL(s, hrefIndex[0], hrefIndex[1])
			}

			// replace form action attribute
			actionIndex := parts[6:8]
			if actionIndex[0] != -1 {
				return pm.encodeURL(s, actionIndex[0], actionIndex[1])
			}
		}
		return s
	})
	w.Write(encodedBody)
}

func (pm *proxyManager) rewriteCSS(w http.ResponseWriter) {
	body, _ := ioutil.ReadAll(pm.resp.Body)
	encodedBody := reCSS.ReplaceAllFunc(body, func(s []byte) []byte {
		parts := reCSS.FindSubmatchIndex(s)
		if parts != nil {
			// replace url attribute in css
			pathIndex := parts[2:4]
			if pathIndex[0] != -1 {
				return pm.encodeURL(s, pathIndex[0], pathIndex[1])
			}
		}
		return s
	})
	w.Write(encodedBody)

}

func (pm *proxyManager) encodeURL(src []byte, start int, end int) []byte {
	relURL := string(src[start:end])
	// keep anchor and javascript links intact
	if relURL == "" || strings.Index(relURL, "#") == 0 || strings.Index(relURL, "javascript") == 0 {
		return src
	}
	// Check if url is relative and make it absolute
	if strings.Index(relURL, "http") != 0 {
		relPath, err := url.Parse(relURL)
		if err != nil {
			return src
		}
		absURL := pm.uri.ResolveReference(relPath).String()
		src = bytes.Replace(src, []byte(relURL), []byte(absURL), -1)
		end = start + len(absURL)
	}
	encodedPath := make([]byte, base64.URLEncoding.EncodedLen(end-start))
	base64.URLEncoding.Encode(encodedPath, src[start:end])
	// add some identifier to encoded urls
	encodedString := GopeeEncPrefix + string(encodedPath)
	return bytes.Replace(src, src[start:end], []byte(encodedString), -1)
}

// Cache templates
var templates = template.Must(template.ParseFiles("home.html"))

func homeHandler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path[1:] == "" {
		r.ParseForm()
		enteredURL := r.FormValue("url")
		if enteredURL != "" {
			// Check if url attribute is set in GET / POST
			uri, _ := url.Parse(enteredURL)
			// prepend http if not specified
			if uri.Scheme == "" {
				uri.Scheme = "http"
			}
			http.Redirect(w, r, "/"+encodeURL(uri.String()), 302)
			return
		}
		templates.ExecuteTemplate(w, "home.html", nil)
	} else {
		ProxyRequest(r, w)
	}
}

func main() {
	httpHost := os.Getenv("HOST")
	httpPort := os.Getenv("PORT")
	if httpPort == "" {
		httpPort = "8080"
	}
	http.HandleFunc("/", homeHandler)

	http.HandleFunc("/assets/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, r.URL.Path[1:])
	})

	log.Printf("web proxy listening on %s:%s\n", httpHost, httpPort)

	http.ListenAndServe(httpHost+":"+httpPort, nil)
}
<commit_msg>Better handling of AJAX requests and other improvements<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"errors"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
)

// URLCookie - cookie which stores page url
const URLCookie = "X-GoPee-URL"

// GopeeEncPrefix - encoding prefix used while encoding urls
const GopeeEncPrefix = "xox"

// Pre-compile RegEx
var reBase = regexp.MustCompile(`base +href="(.*?)"`)
var reHTML = regexp.MustCompile(`\saction=["']?(.*?)["'\s]|\shref=["']?(.*?)["'\s]|\ssrc=["']?(.*?)["'\s]`)
var reCSS = regexp.MustCompile(`url\(["']?(.*?)["']?\)`)

var reBase64 = regexp.MustCompile("^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=)?$")

var httpClient = &http.Client{}

// Hop-by-hop headers
var hopHeaders = map[string]bool{
	"connection":          true,
	"keep-alive":          true,
	"proxy-authenticate":  true,
	"proxy-authorization": true,
	"te":                true,
	"trailers":          true,
	"transfer-encoding": true,
	"upgrade":           true,
}

// Headers that create problem handling response
// TODO: support gzip compressed response in future
var problemHeaders = map[string]bool{
	"content-security-policy":             true, // sent in response
	"content-security-policy-report-only": true, // sent in response
	"accept-encoding":                     true, // sent in request
}

type proxyManager struct {
	req  *http.Request
	uri  *url.URL
	resp *http.Response
}

func encodeURL(plainURL []byte) string {
	return GopeeEncPrefix + base64.URLEncoding.EncodeToString(plainURL)
}

func decodeURL(encodedURL string) (decodedURI *url.URL, err error) {
	encodedURL = strings.TrimSpace(encodedURL)
	if encodedURL == "" || !strings.HasPrefix(encodedURL, GopeeEncPrefix) {
		err = errors.New("invalid path supplied to decode")
		return nil, err
	}
	encodedURL = strings.TrimLeft(encodedURL, GopeeEncPrefix)
	decoded, err := base64.URLEncoding.DecodeString(encodedURL)
	if err != nil {
		log.Println(err.Error(), " for url ", encodedURL)
		return nil, err
	}
	decodedURI, err = url.Parse(string(decoded))
	if err != nil {
		log.Println("error parsing", string(decoded))
		return nil, err
	}
	return decodedURI, err
}

// ProxyRequest creates a new request to be proxied
func ProxyRequest(r *http.Request, w http.ResponseWriter) {
	var uri *url.URL

	// Path can be of the form
	// /base64-encoded-string
	// /home/ajax.php
	// http://thirdparty.com/resource.js
	path := r.URL.Path[1:]

	if decodedURL, err := decodeURL(path); err == nil {
		// URL rewritten by GoPee
		uri = decodedURL
	} else {
		// Might be a plain-text URL which was not rewritten
		// AJAX request ?
		path += "?" + r.URL.RawQuery
		pathURI, err := url.Parse(path)
		if err != nil {
			log.Println("error parsing", path)
		}
		if pathURI.IsAbs() {
			uri = pathURI
		} else {
			referer, err := url.Parse(r.Referer())
			if err != nil {
				log.Println("error parsing", r.Referer())
			}
			if baseURL, err := decodeURL(referer.Path[1:]); err == nil {
				uri = baseURL.ResolveReference(pathURI)
			}
		}
	}
	// log.Println(uri.String())
	if uri == nil {
		// return a 404
		http.NotFound(w, r)
	} else {
		// try fetching the url
		proxyMan := &proxyManager{r, uri, nil}
		proxyMan.Fetch(w)
	}
}

// Fetch makes the actual request to server and writes data with rewritten URLs to response
func (pm *proxyManager) Fetch(w http.ResponseWriter) (err error) {
	if pm.uri == nil {
		return errors.New("No URI specified to fetch")
	}
	req, _ := http.NewRequest(pm.req.Method, pm.uri.String(), pm.req.Body)
	// Forward request headers, included User-Agent to server
	copyHeader(req.Header, pm.req.Header)
	// Set http client protocol version
	req.Proto = "HTTP/1.1"
	req.ProtoMajor = 1
	req.ProtoMinor = 1
	pm.resp, err = httpClient.Do(req)
	if err != nil {
		log.Println("error fetching", pm.uri.String())
		return err
	}
	defer pm.resp.Body.Close()

	// In case there was a url redirect
	// http -> https or non-www -> www
	pm.uri = pm.resp.Request.URL

	contentType := pm.resp.Header.Get("Content-Type")

	// Forward response headers to client
	copyHeader(w.Header(), pm.resp.Header)

	w.WriteHeader(pm.resp.StatusCode)

	// Rewrite all urls
	if strings.Contains(contentType, "text/html") {
		pm.rewriteHTML(w)
	} else if strings.Contains(contentType, "text/css") {
		pm.rewriteCSS(w)
	} else {
		io.Copy(w, pm.resp.Body)
	}
	return nil
}

// Copy Headers from src to dst ignoring hop-by-hop headers
func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		// Don't copy hop-by-hop headers and problem headers
		if hopHeaders[strings.ToLower(k)] || problemHeaders[strings.ToLower(k)] {
			dst.Del(k) // remove any existing hop-by-hop header
			continue
		}
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

func (pm *proxyManager) rewriteHTML(w http.ResponseWriter) {
	body, _ := ioutil.ReadAll(pm.resp.Body)
	// if there's a <base href> specified in the document
	// use that as base to encode all URLs in the page
	baseHrefMatch := reBase.FindSubmatch(body)
	if len(baseHrefMatch) > 0 {
		var err error
		pm.uri, err = url.Parse(string(baseHrefMatch[1][:]))
		if err != nil {
			log.Println("Error Parsing " + string(baseHrefMatch[1][:]))
		}
	}
	encodedBody := reHTML.ReplaceAllFunc(body, func(s []byte) []byte {
		parts := reHTML.FindSubmatchIndex(s)
		if parts != nil {
			// replace src attribute
			srcIndex := parts[2:4]
			if srcIndex[0] != -1 {
				return pm.rewriteURI(s, srcIndex[0], srcIndex[1])
			}

			// replace href attribute
			hrefIndex := parts[4:6]
			if hrefIndex[0] != -1 {
				return pm.rewriteURI(s, hrefIndex[0], hrefIndex[1])
			}

			// replace form action attribute
			actionIndex := parts[6:8]
			if actionIndex[0] != -1 {
				return pm.rewriteURI(s, actionIndex[0], actionIndex[1])
			}
		}
		return s
	})
	w.Write(encodedBody)
}

func (pm *proxyManager) rewriteCSS(w http.ResponseWriter) {
	body, _ := ioutil.ReadAll(pm.resp.Body)
	encodedBody := reCSS.ReplaceAllFunc(body, func(s []byte) []byte {
		parts := reCSS.FindSubmatchIndex(s)
		if parts != nil {
			// replace url attribute in css
			pathIndex := parts[2:4]
			if pathIndex[0] != -1 {
				return pm.rewriteURI(s, pathIndex[0], pathIndex[1])
			}
		}
		return s
	})
	w.Write(encodedBody)

}

func (pm *proxyManager) rewriteURI(src []byte, start int, end int) []byte {
	relURL := string(src[start:end])
	// keep anchor and javascript links intact
	if relURL == "" || strings.HasPrefix(relURL, "#") || strings.HasPrefix(relURL, "javascript") || strings.HasPrefix(relURL, "data") {
		return src
	}
	// Check if url is relative and make it absolute
	if strings.Index(relURL, "http") != 0 {
		relPath, err := url.Parse(relURL)
		if err != nil {
			return src
		}
		absURL := pm.uri.ResolveReference(relPath).String()
		src = bytes.Replace(src, []byte(relURL), []byte(absURL), -1)
		end = start + len(absURL)
	}
	encodedString := encodeURL(src[start:end])
	return bytes.Replace(src, src[start:end], []byte(encodedString), -1)
}

// Cache templates
var templates = template.Must(template.ParseFiles("home.html"))

func homeHandler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path[1:] == "" {
		r.ParseForm()
		enteredURL := r.FormValue("url")
		if enteredURL != "" {
			// Check if url attribute is set in GET / POST
			uri, _ := url.Parse(enteredURL)
			// prepend http if not specified
			if uri.Scheme == "" {
				uri.Scheme = "http"
			}
			http.Redirect(w, r, "/"+encodeURL([]byte(uri.String())), 302)
			return
		}
		templates.ExecuteTemplate(w, "home.html", nil)
	} else {
		ProxyRequest(r, w)
	}
}

func main() {
	httpHost := os.Getenv("HOST")
	httpPort := os.Getenv("PORT")
	if httpPort == "" {
		httpPort = "8080"
	}
	http.HandleFunc("/", homeHandler)

	http.HandleFunc("/assets/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, r.URL.Path[1:])
	})

	log.Printf("web proxy listening on %s:%s\n", httpHost, httpPort)

	http.ListenAndServe(httpHost+":"+httpPort, nil)
}
<|endoftext|>


<commit_before>package gorex

import (
	"bytes"
	"encoding/json"
	"io"
	"mime/multipart"
	"net/http"
	"time"
)

func reqFormFile(r Request, params map[string]string, paramName, fileName string, fileBuffer *bytes.Buffer) (Request, error) {
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(paramName, fileName)
	if err != nil {
		return r, err
	}
	_, err = io.Copy(part, fileBuffer)
	if err != nil {
		return r, err
	}
	for key, val := range params {
		_ = writer.WriteField(key, val)
	}
	err = writer.Close()
	if err != nil {
		return r, err
	}
	req, err := http.NewRequest(r.Method, r.URI, body)
	req.Header.Add("Content-Type", writer.FormDataContentType())
	r.Req = req
	return r, err
}

func reqJSON(r Request, body interface{}) (Request, error) {
	marshalled, err := json.Marshal(body)
	if err != nil {
		return r, err
	}
	jsonBuffer := bytes.NewBuffer(marshalled)
	req, err := http.NewRequest(r.Method, r.URI, jsonBuffer)
	req.Header.Add("Content-Type", "application/json")
	r.Req = req
	return r, err
}

// Request - the request object
type Request struct {
	URI     string
	Method  string
	Req     *http.Request
	Timeout time.Duration
}

// Response - the response object
type Response struct {
	Res *http.Response
	URI string
}

// FormFile - a request for a multipart upload with file buffer with optional params
func (r Request) FormFile(params map[string]string, paramName, fileName string, fileBuffer *bytes.Buffer) (Request, error) {
	return reqFormFile(r, params, paramName, fileName, fileBuffer)
}

// JSON - a request to a JSON endpoint
func (r Request) JSON(body interface{}) (Request, error) {
	return reqJSON(r, body)
}

// Do - process the request with timeout
func (r Request) Do() (*Response, error) {
	client := &http.Client{}
	client.Timeout = r.Timeout
	res, err := client.Do(r.Req)
	return &Response{res, r.URI}, err
}

// JSONDecode - decode JSON to interface
func (r *Response) JSONDecode(decoder interface{}) (*interface{}, error) {
	defer r.Res.Body.Close()
	resp := &decoder
	err := json.NewDecoder(r.Res.Body).Decode(resp)
	return resp, err
}
<commit_msg>fix type<commit_after>package gorex

import (
	"bytes"
	"encoding/json"
	"io"
	"mime/multipart"
	"net/http"
	"time"
)

func reqFormFile(r Request, params map[string]string, paramName, fileName string, fileBuffer *bytes.Buffer) (Request, error) {
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile(paramName, fileName)
	if err != nil {
		return r, err
	}
	_, err = io.Copy(part, fileBuffer)
	if err != nil {
		return r, err
	}
	for key, val := range params {
		_ = writer.WriteField(key, val)
	}
	err = writer.Close()
	if err != nil {
		return r, err
	}
	req, err := http.NewRequest(r.Method, r.URI, body)
	req.Header.Add("Content-Type", writer.FormDataContentType())
	r.Req = req
	return r, err
}

func reqJSON(r Request, body interface{}) (Request, error) {
	marshalled, err := json.Marshal(body)
	if err != nil {
		return r, err
	}
	jsonBuffer := bytes.NewBuffer(marshalled)
	req, err := http.NewRequest(r.Method, r.URI, jsonBuffer)
	req.Header.Add("Content-Type", "application/json")
	r.Req = req
	return r, err
}

// Request - the request object
type Request struct {
	URI     string
	Method  string
	Req     *http.Request
	Timeout time.Duration
}

// Response - the response object
type Response struct {
	Res *http.Response
	URI string
}

// FormFile - a request for a multipart upload with file buffer with optional params
func (r Request) FormFile(params map[string]string, paramName, fileName string, fileBuffer *bytes.Buffer) (Request, error) {
	return reqFormFile(r, params, paramName, fileName, fileBuffer)
}

// JSON - a request to a JSON endpoint
func (r Request) JSON(body interface{}) (Request, error) {
	return reqJSON(r, body)
}

// Do - process the request with timeout
func (r Request) Do() (Response, error) {
	client := &http.Client{}
	client.Timeout = r.Timeout
	res, err := client.Do(r.Req)
	return Response{res, r.URI}, err
}

// JSONDecode - decode JSON to interface
func (r Response) JSONDecode(decoder interface{}) (*interface{}, error) {
	defer r.Res.Body.Close()
	resp := &decoder
	err := json.NewDecoder(r.Res.Body).Decode(resp)
	return resp, err
}
<|endoftext|>


<commit_before>package gorpc

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"math"

	"github.com/gsdocker/gserrors"
)

//gsbuff public errors
var (
	ErrDecode    = errors.New("decode error")
	ErrWriteNone = errors.New("write nothing")
	ErrEncode    = errors.New("encoding error")
)

//Reader the gsbuff Reader interface, Mixin io.ByteReader and io.Reader interfaces
type Reader interface {
	io.ByteReader
	io.Reader
}

//Writer the gsbuff Writer interface, Mixin io.ByteWriterand io.Writer interfaces
type Writer interface {
	io.ByteWriter
	io.Writer
}

//ReadByte read byte from Reader interface
func ReadByte(reader Reader) (byte, error) {
	val, err := reader.ReadByte()

	if err != nil {
		return 0, gserrors.Newf(err, "read byte error")
	}

	return val, err
}

//ReadSByte read sbyte from Reader interface
func ReadSByte(reader Reader) (int8, error) {
	v, err := reader.ReadByte()
	return int8(v), err
}

//ReadUInt16 read uint16 from Reader interface
func ReadUInt16(reader Reader) (uint16, error) {
	buf := make([]byte, 2)

	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint16 error")
	}

	return uint16(buf[0]) | uint16(buf[1])<<8, nil
}

//ReadInt16 read uint16 from Reader interface
func ReadInt16(reader Reader) (int16, error) {
	v, err := ReadUInt16(reader)

	return int16(v), err
}

//ReadUint32 read uint32 from Reader interface
func ReadUint32(reader Reader) (uint32, error) {
	buf := make([]byte, 4)

	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint32 error")
	}

	return uint32(buf[3])<<24 | uint32(buf[2])<<16 | uint32(buf[1])<<8 | uint32(buf[0]), nil
}

//ReadInt32 read int32 from Reader interface
func ReadInt32(reader Reader) (int32, error) {
	v, err := ReadUint32(reader)

	return int32(v), err
}

//ReadUint64 read uint16 from Reader interface
func ReadUint64(reader Reader) (uint64, error) {
	buf := make([]byte, 8)
	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint64 error")
	}

	var ret uint64

	for i, v := range buf {
		ret |= uint64(v) << uint(i*8)
	}

	return ret, nil
}

//ReadInt64 read int16 from Reader interface
func ReadInt64(reader Reader) (int64, error) {
	v, err := ReadUint64(reader)

	return int64(v), err
}

//ReadFloat32 read float32 from Reader interface
func ReadFloat32(reader Reader) (float32, error) {
	x, err := ReadUint32(reader)

	if err != nil {
		return 0, err
	}

	return math.Float32frombits(x), nil
}

//ReadFloat64 read float64 from Reader interface
func ReadFloat64(reader Reader) (float64, error) {
	x, err := ReadUint64(reader)
	if err != nil {
		return 0, err
	}
	return math.Float64frombits(x), nil
}

//ReadString read string
func ReadString(reader Reader) (string, error) {
	len, err := ReadUInt16(reader)

	if err != nil {
		return "", gserrors.Newf(err, "read string len error")
	}

	buff := make([]byte, len)

	_, err = reader.Read(buff)

	if err != nil {
		return "", gserrors.Newf(err, "read string[%d] body error", len)
	}

	return string(buff), nil
}

//ReadBytes read bytes from reader
func ReadBytes(reader Reader, val []byte) error {
	_, err := reader.Read(val)

	if err != nil {
		return gserrors.Newf(err, "read bytes[%d] error", len(val))
	}

	return nil
}

//ReadBool read bool
func ReadBool(reader Reader) (bool, error) {
	b, err := ReadByte(reader)

	if b == 1 {
		return true, err
	}

	return false, err
}

//WriteByte write one byte into bytes buffer
func WriteByte(writer Writer, v byte) error {
	return writer.WriteByte(v)
}

//WriteBool write bool byte into bytes buffer
func WriteBool(writer Writer, v bool) error {
	if v {
		return writer.WriteByte(1)
	}
	return writer.WriteByte(0)
}

//WriteSByte write sbyte into stream
func WriteSByte(writer Writer, v int8) error {
	return writer.WriteByte(byte(v))
}

//WriteUInt16 write a little-endian uint16 into a writer stream
func WriteUInt16(writer Writer, v uint16) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	return nil
}

//WriteInt16 write a little-endian int16 into a writer stream
func WriteInt16(writer Writer, v int16) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	return nil
}

//WriteUint32 write a little-endian uint32 into a writer stream
func WriteUint32(writer Writer, v uint32) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	if err := WriteByte(writer, byte(v>>16)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>24)); err != nil {
		return err
	}

	return nil
}

//WriteInt32 write a little-endian int32 into a writer stream
func WriteInt32(writer Writer, v int32) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	if err := WriteByte(writer, byte(v>>16)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>24)); err != nil {
		return err
	}

	return nil
}

//WriteUint64 write a little-endian uint64 into a writer stream
func WriteUint64(writer Writer, v uint64) error {
	for i := uint(0); i < 8; i++ {
		if err := WriteByte(writer, byte(v>>(i*8))); err != nil {
			return err
		}
	}

	return nil
}

//WriteInt64 write a little-endian int64 into a writer stream
func WriteInt64(writer Writer, v int64) error {
	for i := uint(0); i < 8; i++ {
		if err := WriteByte(writer, byte(v>>(i*8))); err != nil {
			return err
		}
	}

	return nil
}

//WriteFloat32 write a little-endian int64 into a writer stream
func WriteFloat32(writer Writer, n float32) error {
	return WriteUint32(writer, math.Float32bits(n))
}

//WriteFloat64 write a little-endian int64 into a writer stream
func WriteFloat64(writer Writer, n float64) error {
	return WriteUint64(writer, math.Float64bits(n))
}

//WriteString write string into stream
func WriteString(writer Writer, v string) error {
	err := WriteUInt16(writer, uint16(len(v)))
	if err != nil {
		return err
	}
	_, err = writer.Write([]byte(v))

	return err
}

//WriteBytes write string into stream
func WriteBytes(writer Writer, bytes []byte) error {
	_, err := writer.Write(bytes)
	return err
}

//Read read whole package from stream
func Read(reader io.Reader) ([]byte, error) {
	var header [2]byte

	if _, err := io.ReadFull(reader, header[:]); err != nil {
		return nil, gserrors.Newf(err, "read package length error")
	}

	length := binary.LittleEndian.Uint16(header[:])

	buff := make([]byte, length)

	if _, err := io.ReadFull(reader, buff); err != nil {
		return nil, gserrors.Newf(err, "read package body(%d) error", length)
	}

	return buff, nil
}

// Write write whole input data to stream
func Write(writer io.Writer, buff []byte) (int, error) {
	length := len(buff)

	if length > math.MaxUint16 {
		return 0, gserrors.Newf(ErrEncode, "send package out of range :%d", length)
	}

	var buf [2]byte

	binary.LittleEndian.PutUint16(buf[:], uint16(length))

	length, err := writer.Write(buf[:])

	if err != nil {
		return length, err
	}

	return writer.Write(buff)
}

//Stream the read/write stream
type Stream struct {
	reader io.Reader
	writer io.Writer
	rbuff  *bytes.Buffer
	wbuff  bytes.Buffer
}

// NewStream create new stream
func NewStream(reader io.Reader, writer io.Writer) *Stream {
	return &Stream{
		reader: reader,
		writer: writer,
	}
}

func (stream *Stream) Read(buf []byte) (int, error) {
	if stream.rbuff == nil || stream.rbuff.Len() == 0 {
		cached, err := Read(stream.reader)
		if err != nil {
			return 0, err
		}
		stream.rbuff = bytes.NewBuffer(cached)
	}

	return stream.rbuff.Read(buf)
}

//ReadByte implement Read interface
func (stream *Stream) ReadByte() (byte, error) {
	if stream.rbuff == nil || stream.rbuff.Len() == 0 {
		cached, err := Read(stream.reader)
		if err != nil {
			return 0, err
		}
		stream.rbuff = bytes.NewBuffer(cached)
	}

	return stream.rbuff.ReadByte()
}

func (stream *Stream) Write(buf []byte) (int, error) {
	return stream.wbuff.Write(buf)
}

//WriteByte implement Write interface
func (stream *Stream) WriteByte(val byte) error {
	return stream.wbuff.WriteByte(val)
}

// WBuf .
func (stream *Stream) WBuf() []byte {
	return stream.wbuff.Bytes()
}

//Flush .
func (stream *Stream) Flush() (int, error) {
	len, err := Write(stream.writer, stream.wbuff.Bytes())
	stream.wbuff.Reset()
	return len, err
}
<commit_msg>change  read/write uint64 api name<commit_after>package gorpc

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"math"

	"github.com/gsdocker/gserrors"
)

//gsbuff public errors
var (
	ErrDecode    = errors.New("decode error")
	ErrWriteNone = errors.New("write nothing")
	ErrEncode    = errors.New("encoding error")
)

//Reader the gsbuff Reader interface, Mixin io.ByteReader and io.Reader interfaces
type Reader interface {
	io.ByteReader
	io.Reader
}

//Writer the gsbuff Writer interface, Mixin io.ByteWriterand io.Writer interfaces
type Writer interface {
	io.ByteWriter
	io.Writer
}

//ReadByte read byte from Reader interface
func ReadByte(reader Reader) (byte, error) {
	val, err := reader.ReadByte()

	if err != nil {
		return 0, gserrors.Newf(err, "read byte error")
	}

	return val, err
}

//ReadSByte read sbyte from Reader interface
func ReadSByte(reader Reader) (int8, error) {
	v, err := reader.ReadByte()
	return int8(v), err
}

//ReadUInt16 read uint16 from Reader interface
func ReadUInt16(reader Reader) (uint16, error) {
	buf := make([]byte, 2)

	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint16 error")
	}

	return uint16(buf[0]) | uint16(buf[1])<<8, nil
}

//ReadInt16 read uint16 from Reader interface
func ReadInt16(reader Reader) (int16, error) {
	v, err := ReadUInt16(reader)

	return int16(v), err
}

//ReadUint32 read uint32 from Reader interface
func ReadUint32(reader Reader) (uint32, error) {
	buf := make([]byte, 4)

	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint32 error")
	}

	return uint32(buf[3])<<24 | uint32(buf[2])<<16 | uint32(buf[1])<<8 | uint32(buf[0]), nil
}

//ReadInt32 read int32 from Reader interface
func ReadInt32(reader Reader) (int32, error) {
	v, err := ReadUint32(reader)

	return int32(v), err
}

//ReadUInt64 read uint16 from Reader interface
func ReadUInt64(reader Reader) (uint64, error) {
	buf := make([]byte, 8)
	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint64 error")
	}

	var ret uint64

	for i, v := range buf {
		ret |= uint64(v) << uint(i*8)
	}

	return ret, nil
}

//ReadInt64 read int16 from Reader interface
func ReadInt64(reader Reader) (int64, error) {
	v, err := ReadUInt64(reader)

	return int64(v), err
}

//ReadFloat32 read float32 from Reader interface
func ReadFloat32(reader Reader) (float32, error) {
	x, err := ReadUint32(reader)

	if err != nil {
		return 0, err
	}

	return math.Float32frombits(x), nil
}

//ReadFloat64 read float64 from Reader interface
func ReadFloat64(reader Reader) (float64, error) {
	x, err := ReadUInt64(reader)
	if err != nil {
		return 0, err
	}
	return math.Float64frombits(x), nil
}

//ReadString read string
func ReadString(reader Reader) (string, error) {
	len, err := ReadUInt16(reader)

	if err != nil {
		return "", gserrors.Newf(err, "read string len error")
	}

	buff := make([]byte, len)

	_, err = reader.Read(buff)

	if err != nil {
		return "", gserrors.Newf(err, "read string[%d] body error", len)
	}

	return string(buff), nil
}

//ReadBytes read bytes from reader
func ReadBytes(reader Reader, val []byte) error {
	_, err := reader.Read(val)

	if err != nil {
		return gserrors.Newf(err, "read bytes[%d] error", len(val))
	}

	return nil
}

//ReadBool read bool
func ReadBool(reader Reader) (bool, error) {
	b, err := ReadByte(reader)

	if b == 1 {
		return true, err
	}

	return false, err
}

//WriteByte write one byte into bytes buffer
func WriteByte(writer Writer, v byte) error {
	return writer.WriteByte(v)
}

//WriteBool write bool byte into bytes buffer
func WriteBool(writer Writer, v bool) error {
	if v {
		return writer.WriteByte(1)
	}
	return writer.WriteByte(0)
}

//WriteSByte write sbyte into stream
func WriteSByte(writer Writer, v int8) error {
	return writer.WriteByte(byte(v))
}

//WriteUInt16 write a little-endian uint16 into a writer stream
func WriteUInt16(writer Writer, v uint16) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	return nil
}

//WriteInt16 write a little-endian int16 into a writer stream
func WriteInt16(writer Writer, v int16) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	return nil
}

//WriteUint32 write a little-endian uint32 into a writer stream
func WriteUint32(writer Writer, v uint32) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	if err := WriteByte(writer, byte(v>>16)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>24)); err != nil {
		return err
	}

	return nil
}

//WriteInt32 write a little-endian int32 into a writer stream
func WriteInt32(writer Writer, v int32) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	if err := WriteByte(writer, byte(v>>16)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>24)); err != nil {
		return err
	}

	return nil
}

//WriteUInt64 write a little-endian uint64 into a writer stream
func WriteUInt64(writer Writer, v uint64) error {
	for i := uint(0); i < 8; i++ {
		if err := WriteByte(writer, byte(v>>(i*8))); err != nil {
			return err
		}
	}

	return nil
}

//WriteInt64 write a little-endian int64 into a writer stream
func WriteInt64(writer Writer, v int64) error {
	for i := uint(0); i < 8; i++ {
		if err := WriteByte(writer, byte(v>>(i*8))); err != nil {
			return err
		}
	}

	return nil
}

//WriteFloat32 write a little-endian int64 into a writer stream
func WriteFloat32(writer Writer, n float32) error {
	return WriteUint32(writer, math.Float32bits(n))
}

//WriteFloat64 write a little-endian int64 into a writer stream
func WriteFloat64(writer Writer, n float64) error {
	return WriteUInt64(writer, math.Float64bits(n))
}

//WriteString write string into stream
func WriteString(writer Writer, v string) error {
	err := WriteUInt16(writer, uint16(len(v)))
	if err != nil {
		return err
	}
	_, err = writer.Write([]byte(v))

	return err
}

//WriteBytes write string into stream
func WriteBytes(writer Writer, bytes []byte) error {
	_, err := writer.Write(bytes)
	return err
}

//Read read whole package from stream
func Read(reader io.Reader) ([]byte, error) {
	var header [2]byte

	if _, err := io.ReadFull(reader, header[:]); err != nil {
		return nil, gserrors.Newf(err, "read package length error")
	}

	length := binary.LittleEndian.Uint16(header[:])

	buff := make([]byte, length)

	if _, err := io.ReadFull(reader, buff); err != nil {
		return nil, gserrors.Newf(err, "read package body(%d) error", length)
	}

	return buff, nil
}

// Write write whole input data to stream
func Write(writer io.Writer, buff []byte) (int, error) {
	length := len(buff)

	if length > math.MaxUint16 {
		return 0, gserrors.Newf(ErrEncode, "send package out of range :%d", length)
	}

	var buf [2]byte

	binary.LittleEndian.PutUint16(buf[:], uint16(length))

	length, err := writer.Write(buf[:])

	if err != nil {
		return length, err
	}

	return writer.Write(buff)
}

//Stream the read/write stream
type Stream struct {
	reader io.Reader
	writer io.Writer
	rbuff  *bytes.Buffer
	wbuff  bytes.Buffer
}

// NewStream create new stream
func NewStream(reader io.Reader, writer io.Writer) *Stream {
	return &Stream{
		reader: reader,
		writer: writer,
	}
}

func (stream *Stream) Read(buf []byte) (int, error) {
	if stream.rbuff == nil || stream.rbuff.Len() == 0 {
		cached, err := Read(stream.reader)
		if err != nil {
			return 0, err
		}
		stream.rbuff = bytes.NewBuffer(cached)
	}

	return stream.rbuff.Read(buf)
}

//ReadByte implement Read interface
func (stream *Stream) ReadByte() (byte, error) {
	if stream.rbuff == nil || stream.rbuff.Len() == 0 {
		cached, err := Read(stream.reader)
		if err != nil {
			return 0, err
		}
		stream.rbuff = bytes.NewBuffer(cached)
	}

	return stream.rbuff.ReadByte()
}

func (stream *Stream) Write(buf []byte) (int, error) {
	return stream.wbuff.Write(buf)
}

//WriteByte implement Write interface
func (stream *Stream) WriteByte(val byte) error {
	return stream.wbuff.WriteByte(val)
}

// WBuf .
func (stream *Stream) WBuf() []byte {
	return stream.wbuff.Bytes()
}

//Flush .
func (stream *Stream) Flush() (int, error) {
	len, err := Write(stream.writer, stream.wbuff.Bytes())
	stream.wbuff.Reset()
	return len, err
}
<|endoftext|>


<commit_before>package profile

import (
	"github.com/hackform/governor"
	"github.com/hackform/governor/service/db"
	"github.com/hackform/governor/service/objstore"
	"github.com/hackform/governor/service/profile/model"
	"github.com/hackform/governor/service/user/gate"
	"github.com/labstack/echo"
	"github.com/sirupsen/logrus"
	"io"
	"mime"
	"net/http"
)

type (
	reqProfileGetID struct {
		Userid string `json:"userid"`
	}

	reqProfileModel struct {
		Userid string `json:"-"`
		Email  string `json:"contact_email"`
		Bio    string `json:"bio"`
	}

	reqProfileImage struct {
		Userid    string `json:"-"`
		Image     io.Reader
		ImageType string
	}

	resProfileUpdate struct {
		Userid string `json:"userid"`
	}

	resProfileModel struct {
		Email string `json:"contact_email"`
		Bio   string `json:"bio"`
		Image string `json:"image"`
	}
)

func (r *reqProfileGetID) valid() *governor.Error {
	if err := hasUserid(r.Userid); err != nil {
		return err
	}
	return nil
}

func (r *reqProfileModel) valid() *governor.Error {
	if err := hasUserid(r.Userid); err != nil {
		return err
	}
	if err := validEmail(r.Email); err != nil {
		return err
	}
	if err := validBio(r.Email); err != nil {
		return err
	}
	return nil
}

func (r *reqProfileImage) valid() *governor.Error {
	if err := hasUserid(r.Userid); err != nil {
		return err
	}
	if err := validImage(r.Image); err != nil {
		return err
	}
	if err := validImageType(r.ImageType); err != nil {
		return err
	}
	return nil
}

const (
	moduleID    = "profile"
	imageBucket = "profile-image"
)

type (
	// Profile is a service for storing user profile information
	Profile struct {
		db   *db.Database
		obj  *objstore.Bucket
		gate *gate.Gate
	}
)

// New creates a new Profile service
func New(conf governor.Config, l *logrus.Logger, db *db.Database, obj *objstore.Objstore) *Profile {
	ca := conf.Conf().GetStringMapString("userauth")

	b, err := obj.GetBucketDefLoc(imageBucket)
	if err != nil {
		l.Errorf("failed to get bucket: %s\n", err.Error())
	}

	l.Info("initialized profile service")

	return &Profile{
		db:   db,
		obj:  b,
		gate: gate.New(ca["secret"], ca["issuer"]),
	}
}

// Mount is a collection of routes for accessing and modifying profile data
func (p *Profile) Mount(conf governor.Config, r *echo.Group, l *logrus.Logger) error {
	db := p.db.DB()

	r.POST("/:id", func(c echo.Context) error {
		rprofile := &reqProfileModel{}
		if err := c.Bind(rprofile); err != nil {
			return governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
		}
		rprofile.Userid = c.Param("id")
		if err := rprofile.valid(); err != nil {
			return err
		}

		m := &profilemodel.Model{
			Email: rprofile.Email,
			Bio:   rprofile.Bio,
		}

		if err := m.SetIDB64(rprofile.Userid); err != nil {
			err.SetErrorUser()
			return err
		}

		if err := m.Insert(db); err != nil {
			if err.Code() == 3 {
				err.SetErrorUser()
			}
			err.AddTrace(moduleID)
			return err
		}

		userid, _ := m.IDBase64()

		return c.JSON(http.StatusCreated, &resProfileUpdate{
			Userid: userid,
		})
	}, p.gate.Owner("id"))

	r.PUT("/:id", func(c echo.Context) error {
		rprofile := &reqProfileModel{}
		if err := c.Bind(rprofile); err != nil {
			return governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
		}
		rprofile.Userid = c.Param("id")
		if err := rprofile.valid(); err != nil {
			return err
		}

		m, err := profilemodel.GetByIDB64(db, rprofile.Userid)
		if err != nil {
			if err.Code() == 2 {
				err.SetErrorUser()
			}
			err.AddTrace(moduleID)
			return err
		}

		m.Email = rprofile.Email
		m.Bio = rprofile.Bio

		if err := m.Update(db); err != nil {
			err.AddTrace(moduleID)
			return err
		}

		return c.NoContent(http.StatusNoContent)
	}, p.gate.Owner("id"))

	r.PATCH("/:id/image", func(c echo.Context) error {
		file, err := c.FormFile("image")
		if err != nil {
			return governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
		}
		mediaType, _, err := mime.ParseMediaType(file.Header.Get("Content-Type"))
		if err != nil {
			return governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
		}
		image, err := file.Open()
		if err != nil {
			return governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
		}
		defer func(c io.Closer) {
			err := c.Close()
			if err != nil {
				gerr := governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
				l.WithFields(logrus.Fields{
					"origin": gerr.Origin(),
					"source": gerr.Source(),
					"code":   gerr.Code(),
				}).Error(gerr.Message())
			}
		}(image)
		rprofile := &reqProfileImage{
			Userid:    c.Get("userid").(string),
			Image:     image,
			ImageType: mediaType,
		}
		if err := rprofile.valid(); err != nil {
			return err
		}

		if err := p.obj.Put(rprofile.Userid+"-profile", rprofile.ImageType, image); err != nil {
			err.AddTrace(moduleID)
			return err
		}

		return c.NoContent(http.StatusNoContent)
	}, p.gate.Owner("id"))

	r.DELETE("/:id", func(c echo.Context) error {
		rprofile := &reqProfileGetID{
			Userid: c.Param("id"),
		}
		if err := rprofile.valid(); err != nil {
			return err
		}

		m, err := profilemodel.GetByIDB64(db, rprofile.Userid)
		if err != nil {
			if err.Code() == 2 {
				err.SetErrorUser()
			}
			err.AddTrace(moduleID)
			return err
		}

		if err := m.Delete(db); err != nil {
			err.AddTrace(moduleID)
			return err
		}

		return c.NoContent(http.StatusNoContent)
	}, p.gate.OwnerOrAdmin("id"))

	r.GET("/:id", func(c echo.Context) error {
		rprofile := &reqProfileGetID{
			Userid: c.Param("id"),
		}
		if err := rprofile.valid(); err != nil {
			return err
		}

		m, err := profilemodel.GetByIDB64(db, rprofile.Userid)
		if err != nil {
			if err.Code() == 2 {
				err.SetErrorUser()
			}
			err.AddTrace(moduleID)
			return err
		}

		return c.JSON(http.StatusOK, &resProfileModel{
			Email: m.Email,
			Bio:   m.Bio,
			Image: m.Image,
		})
	})

	l.Info("mounted profile service")

	return nil
}

// Health is a check for service health
func (p *Profile) Health() *governor.Error {
	return nil
}

// Setup is run on service setup
func (p *Profile) Setup(conf governor.Config, l *logrus.Logger, rsetup governor.ReqSetupPost) *governor.Error {
	if err := profilemodel.Setup(p.db.DB()); err != nil {
		err.AddTrace(moduleID)
		return err
	}
	l.Info("created new profile table")
	return nil
}
<commit_msg>profile get picture<commit_after>package profile

import (
	"github.com/hackform/governor"
	"github.com/hackform/governor/service/db"
	"github.com/hackform/governor/service/objstore"
	"github.com/hackform/governor/service/profile/model"
	"github.com/hackform/governor/service/user/gate"
	"github.com/labstack/echo"
	"github.com/sirupsen/logrus"
	"io"
	"mime"
	"net/http"
)

type (
	reqProfileGetID struct {
		Userid string `json:"userid"`
	}

	reqProfileModel struct {
		Userid string `json:"-"`
		Email  string `json:"contact_email"`
		Bio    string `json:"bio"`
	}

	reqProfileImage struct {
		Userid    string `json:"-"`
		Image     io.Reader
		ImageType string
	}

	resProfileUpdate struct {
		Userid string `json:"userid"`
	}

	resProfileModel struct {
		Email string `json:"contact_email"`
		Bio   string `json:"bio"`
		Image string `json:"image"`
	}
)

func (r *reqProfileGetID) valid() *governor.Error {
	if err := hasUserid(r.Userid); err != nil {
		return err
	}
	return nil
}

func (r *reqProfileModel) valid() *governor.Error {
	if err := hasUserid(r.Userid); err != nil {
		return err
	}
	if err := validEmail(r.Email); err != nil {
		return err
	}
	if err := validBio(r.Email); err != nil {
		return err
	}
	return nil
}

func (r *reqProfileImage) valid() *governor.Error {
	if err := hasUserid(r.Userid); err != nil {
		return err
	}
	if err := validImage(r.Image); err != nil {
		return err
	}
	if err := validImageType(r.ImageType); err != nil {
		return err
	}
	return nil
}

const (
	moduleID    = "profile"
	imageBucket = "profile-image"
)

type (
	// Profile is a service for storing user profile information
	Profile struct {
		db   *db.Database
		obj  *objstore.Bucket
		gate *gate.Gate
	}
)

// New creates a new Profile service
func New(conf governor.Config, l *logrus.Logger, db *db.Database, obj *objstore.Objstore) *Profile {
	ca := conf.Conf().GetStringMapString("userauth")

	b, err := obj.GetBucketDefLoc(imageBucket)
	if err != nil {
		l.Errorf("failed to get bucket: %s\n", err.Error())
	}

	l.Info("initialized profile service")

	return &Profile{
		db:   db,
		obj:  b,
		gate: gate.New(ca["secret"], ca["issuer"]),
	}
}

// Mount is a collection of routes for accessing and modifying profile data
func (p *Profile) Mount(conf governor.Config, r *echo.Group, l *logrus.Logger) error {
	db := p.db.DB()

	r.POST("/:id", func(c echo.Context) error {
		rprofile := &reqProfileModel{}
		if err := c.Bind(rprofile); err != nil {
			return governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
		}
		rprofile.Userid = c.Param("id")
		if err := rprofile.valid(); err != nil {
			return err
		}

		m := &profilemodel.Model{
			Email: rprofile.Email,
			Bio:   rprofile.Bio,
		}

		if err := m.SetIDB64(rprofile.Userid); err != nil {
			err.SetErrorUser()
			return err
		}

		if err := m.Insert(db); err != nil {
			if err.Code() == 3 {
				err.SetErrorUser()
			}
			err.AddTrace(moduleID)
			return err
		}

		userid, _ := m.IDBase64()

		return c.JSON(http.StatusCreated, &resProfileUpdate{
			Userid: userid,
		})
	}, p.gate.Owner("id"))

	r.PUT("/:id", func(c echo.Context) error {
		rprofile := &reqProfileModel{}
		if err := c.Bind(rprofile); err != nil {
			return governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
		}
		rprofile.Userid = c.Param("id")
		if err := rprofile.valid(); err != nil {
			return err
		}

		m, err := profilemodel.GetByIDB64(db, rprofile.Userid)
		if err != nil {
			if err.Code() == 2 {
				err.SetErrorUser()
			}
			err.AddTrace(moduleID)
			return err
		}

		m.Email = rprofile.Email
		m.Bio = rprofile.Bio

		if err := m.Update(db); err != nil {
			err.AddTrace(moduleID)
			return err
		}

		return c.NoContent(http.StatusNoContent)
	}, p.gate.Owner("id"))

	r.PATCH("/:id/image", func(c echo.Context) error {
		file, err := c.FormFile("image")
		if err != nil {
			return governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
		}
		mediaType, _, err := mime.ParseMediaType(file.Header.Get("Content-Type"))
		if err != nil {
			return governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
		}
		image, err := file.Open()
		if err != nil {
			return governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
		}
		defer func(c io.Closer) {
			err := c.Close()
			if err != nil {
				gerr := governor.NewErrorUser(moduleID, err.Error(), 0, http.StatusBadRequest)
				l.WithFields(logrus.Fields{
					"origin": gerr.Origin(),
					"source": gerr.Source(),
					"code":   gerr.Code(),
				}).Error(gerr.Message())
			}
		}(image)
		rprofile := &reqProfileImage{
			Userid:    c.Get("userid").(string),
			Image:     image,
			ImageType: mediaType,
		}
		if err := rprofile.valid(); err != nil {
			return err
		}

		if err := p.obj.Put(rprofile.Userid+"-profile", rprofile.ImageType, image); err != nil {
			err.AddTrace(moduleID)
			return err
		}

		return c.NoContent(http.StatusNoContent)
	}, p.gate.Owner("id"))

	r.DELETE("/:id", func(c echo.Context) error {
		rprofile := &reqProfileGetID{
			Userid: c.Param("id"),
		}
		if err := rprofile.valid(); err != nil {
			return err
		}

		m, err := profilemodel.GetByIDB64(db, rprofile.Userid)
		if err != nil {
			if err.Code() == 2 {
				err.SetErrorUser()
			}
			err.AddTrace(moduleID)
			return err
		}

		if err := m.Delete(db); err != nil {
			err.AddTrace(moduleID)
			return err
		}

		return c.NoContent(http.StatusNoContent)
	}, p.gate.OwnerOrAdmin("id"))

	r.GET("/:id", func(c echo.Context) error {
		rprofile := &reqProfileGetID{
			Userid: c.Param("id"),
		}
		if err := rprofile.valid(); err != nil {
			return err
		}

		m, err := profilemodel.GetByIDB64(db, rprofile.Userid)
		if err != nil {
			if err.Code() == 2 {
				err.SetErrorUser()
			}
			err.AddTrace(moduleID)
			return err
		}

		return c.JSON(http.StatusOK, &resProfileModel{
			Email: m.Email,
			Bio:   m.Bio,
			Image: m.Image,
		})
	})

	r.GET("/:id/image", func(c echo.Context) error {
		userid := c.Get("userid").(string)
		obj, objinfo, err := p.obj.Get(userid + "-profile")
		if err != nil {
			if err.Code() == 2 {
				err.SetErrorUser()
			}
			err.AddTrace(moduleID)
			return err
		}
		return c.Stream(http.StatusOK, objinfo.ContentType, obj)
	}, p.gate.Owner("id"))

	l.Info("mounted profile service")

	return nil
}

// Health is a check for service health
func (p *Profile) Health() *governor.Error {
	return nil
}

// Setup is run on service setup
func (p *Profile) Setup(conf governor.Config, l *logrus.Logger, rsetup governor.ReqSetupPost) *governor.Error {
	if err := profilemodel.Setup(p.db.DB()); err != nil {
		err.AddTrace(moduleID)
		return err
	}
	l.Info("created new profile table")
	return nil
}
<|endoftext|>