<commit_before>// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package versioner

import (
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/syncthing/syncthing/osutil"
)

func init() {
	// Register the constructor for this type of versioner with the name "staggered"
	Factories["staggered"] = NewStaggered
}

type Interval struct {
	step int64
	end  int64
}

// The type holds our configuration
type Staggered struct {
	versionsPath  string
	cleanInterval int64
	repoPath      string
	interval      [4]Interval
	mutex         *sync.Mutex
}

// Check if file or dir
func isFile(path string) bool {
	fileInfo, err := os.Stat(path)
	if err != nil {
		l.Infoln("versioner isFile:", err)
		return false
	}
	return fileInfo.Mode().IsRegular()
}

const TimeLayout = "20060102-150405"

func versionExt(path string) string {
	pathSplit := strings.Split(path, "~")
	if len(pathSplit) > 1 {
		return pathSplit[len(pathSplit)-1]
	} else {
		return ""
	}
}

// Rename versions with old version format
func (v Staggered) renameOld() {
	err := filepath.Walk(v.versionsPath, func(path string, f os.FileInfo, err error) error {
		if f.Mode().IsRegular() {
			versionUnix, err := strconv.ParseInt(strings.Replace(filepath.Ext(path), ".v", "", 1), 10, 0)
			if err == nil {
				l.Infoln("Renaming file", path, "from old to new version format")
				versiondate := time.Unix(versionUnix, 0)
				name := path[:len(path)-len(filepath.Ext(path))]
				err = osutil.Rename(path, name+"~"+versiondate.Format(TimeLayout))
				if err != nil {
					l.Infoln("Error renaming to new format", err)
				}
			}
		}
		return nil
	})
	if err != nil {
		l.Infoln("Versioner: error scanning versions dir", err)
		return
	}
}

// The constructor function takes a map of parameters and creates the type.
func NewStaggered(repoID, repoPath string, params map[string]string) Versioner {
	maxAge, err := strconv.ParseInt(params["maxAge"], 10, 0)
	if err != nil {
		maxAge = 31536000 // Default: ~1 year
	}
	cleanInterval, err := strconv.ParseInt(params["cleanInterval"], 10, 0)
	if err != nil {
		cleanInterval = 3600 // Default: clean once per hour
	}

	// Use custom path if set, otherwise .stversions in repoPath
	var versionsDir string
	if params["versionsPath"] == "" {
		if debug {
			l.Debugln("using default dir .stversions")
		}
		versionsDir = filepath.Join(repoPath, ".stversions")
	} else {
		if debug {
			l.Debugln("using dir", params["versionsPath"])
		}
		versionsDir = params["versionsPath"]
	}

	var mutex sync.Mutex
	s := Staggered{
		versionsPath:  versionsDir,
		cleanInterval: cleanInterval,
		repoPath:      repoPath,
		interval: [4]Interval{
			Interval{30, 3600},               // first hour -> 30 sec between versions
			Interval{3600, 86400},            // next day -> 1 h between versions
			Interval{86400, 592000},          // next 30 days -> 1 day between versions
			Interval{604800, maxAge * 86400}, // next year -> 1 week between versions
		},
		mutex: &mutex,
	}

	if debug {
		l.Debugf("instantiated %#v", s)
	}

	// Rename version with old version format
	s.renameOld()

	go func() {
		s.clean()
		for _ = range time.Tick(time.Duration(cleanInterval) * time.Second) {
			s.clean()
		}
	}()

	return s
}

func (v Staggered) clean() {
	if debug {
		l.Debugln("Versioner clean: Waiting for lock on", v.versionsPath)
	}
	v.mutex.Lock()
	defer v.mutex.Unlock()
	if debug {
		l.Debugln("Versioner clean: Cleaning", v.versionsPath)
	}

	_, err := os.Stat(v.versionsPath)
	if err != nil {
		if os.IsNotExist(err) {
			if debug {
				l.Debugln("creating versions dir", v.versionsPath)
			}
			os.MkdirAll(v.versionsPath, 0755)
			osutil.HideFile(v.versionsPath)
		} else {
			l.Warnln("Versioner: can't create versions dir", err)
		}
	}

	versionsPerFile := make(map[string][]string)
	filesPerDir := make(map[string]int)

	err = filepath.Walk(v.versionsPath, func(path string, f os.FileInfo, err error) error {
		switch mode := f.Mode(); {
		case mode.IsDir():
			filesPerDir[path] = 0
			if path != v.versionsPath {
				dir := filepath.Dir(path)
				filesPerDir[dir]++
			}
		case mode.IsRegular():
			extension := versionExt(path)
			dir := filepath.Dir(path)
			name := path[:len(path)-len(extension)-1]

			filesPerDir[dir]++
			versionsPerFile[name] = append(versionsPerFile[name], path)
		}

		return nil
	})
	if err != nil {
		l.Warnln("Versioner: error scanning versions dir", err)
		return
	}

	for _, versionList := range versionsPerFile {
		// List from filepath.Walk is sorted
		v.expire(versionList)
	}

	for path, numFiles := range filesPerDir {
		if numFiles > 0 {
			continue
		}

		if path == v.versionsPath {
			if debug {
				l.Debugln("Cleaner: versions dir is empty, don't delete", path)
			}
			continue
		}

		if debug {
			l.Debugln("Cleaner: deleting empty directory", path)
		}
		err = os.Remove(path)
		if err != nil {
			l.Warnln("Versioner: can't remove directory", path, err)
		}
	}
	if debug {
		l.Debugln("Cleaner: Finished cleaning", v.versionsPath)
	}
}

func (v Staggered) expire(versions []string) {
	if debug {
		l.Debugln("Versioner: Expiring versions", versions)
	}
	var prevAge int64
	firstFile := true
	for _, file := range versions {
		if isFile(file) {
			versionTime, err := time.Parse(TimeLayout, versionExt(file))
			if err != nil {
				l.Infof("Versioner: file name %q is invalid: %v", file, err)
				continue
			}
			age := int64(time.Since(versionTime).Seconds())

			// If the file is older than the max age of the last interval, remove it
			if lastIntv := v.interval[len(v.interval)-1]; lastIntv.end > 0 && age > lastIntv.end {
				if debug {
					l.Debugln("Versioner: File over maximum age -> delete ", file)
				}
				err = os.Remove(file)
				if err != nil {
					l.Warnf("Versioner: can't remove %q: %v", file, err)
				}
				continue
			}

			// If it's the first (oldest) file in the list we can skip the interval checks
			if firstFile {
				prevAge = age
				firstFile = false
				continue
			}

			// Find the interval the file fits in
			var usedInterval Interval
			for _, usedInterval = range v.interval {
				if age < usedInterval.end {
					break
				}
			}

			if prevAge-age < usedInterval.step {
				if debug {
					l.Debugln("too many files in step -> delete", file)
				}
				err = os.Remove(file)
				if err != nil {
					l.Warnf("Versioner: can't remove %q: %v", file, err)
				}
				continue
			}

			prevAge = age
		} else {
			l.Infof("non-file %q is named like a file version", file)
		}
	}
}

// Move away the named file to a version archive. If this function returns
// nil, the named file does not exist any more (has been archived).
func (v Staggered) Archive(filePath string) error {
	if debug {
		l.Debugln("Waiting for lock on ", v.versionsPath)
	}
	v.mutex.Lock()
	defer v.mutex.Unlock()

	fileInfo, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			if debug {
				l.Debugln("not archiving nonexistent file", filePath)
			}
			return nil
		} else {
			return err
		}
	}

	_, err = os.Stat(v.versionsPath)
	if err != nil {
		if os.IsNotExist(err) {
			if debug {
				l.Debugln("creating versions dir", v.versionsPath)
			}
			os.MkdirAll(v.versionsPath, 0755)
			osutil.HideFile(v.versionsPath)
		} else {
			return err
		}
	}

	if debug {
		l.Debugln("archiving", filePath)
	}

	file := filepath.Base(filePath)
	inRepoPath, err := filepath.Rel(v.repoPath, filepath.Dir(filePath))
	if err != nil {
		return err
	}

	dir := filepath.Join(v.versionsPath, inRepoPath)
	err = os.MkdirAll(dir, 0755)
	if err != nil && !os.IsExist(err) {
		return err
	}

	ver := file + "~" + fileInfo.ModTime().Format(TimeLayout)
	dst := filepath.Join(dir, ver)
	if debug {
		l.Debugln("moving to", dst)
	}
	err = osutil.Rename(filePath, dst)
	if err != nil {
		return err
	}

	versions, err := filepath.Glob(filepath.Join(dir, file+"~[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]"))
	if err != nil {
		l.Warnln("Versioner: error finding versions for", file, err)
		return nil
	}

	sort.Strings(versions)
	v.expire(versions)

	return nil
}
<commit_msg>Don't crash on walk error (fixes #663)<commit_after>// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package versioner

import (
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/syncthing/syncthing/osutil"
)

func init() {
	// Register the constructor for this type of versioner with the name "staggered"
	Factories["staggered"] = NewStaggered
}

type Interval struct {
	step int64
	end  int64
}

// The type holds our configuration
type Staggered struct {
	versionsPath  string
	cleanInterval int64
	repoPath      string
	interval      [4]Interval
	mutex         *sync.Mutex
}

// Check if file or dir
func isFile(path string) bool {
	fileInfo, err := os.Stat(path)
	if err != nil {
		l.Infoln("versioner isFile:", err)
		return false
	}
	return fileInfo.Mode().IsRegular()
}

const TimeLayout = "20060102-150405"

func versionExt(path string) string {
	pathSplit := strings.Split(path, "~")
	if len(pathSplit) > 1 {
		return pathSplit[len(pathSplit)-1]
	} else {
		return ""
	}
}

// Rename versions with old version format
func (v Staggered) renameOld() {
	err := filepath.Walk(v.versionsPath, func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if f.Mode().IsRegular() {
			versionUnix, err := strconv.ParseInt(strings.Replace(filepath.Ext(path), ".v", "", 1), 10, 0)
			if err == nil {
				l.Infoln("Renaming file", path, "from old to new version format")
				versiondate := time.Unix(versionUnix, 0)
				name := path[:len(path)-len(filepath.Ext(path))]
				err = osutil.Rename(path, name+"~"+versiondate.Format(TimeLayout))
				if err != nil {
					l.Infoln("Error renaming to new format", err)
				}
			}
		}
		return nil
	})
	if err != nil {
		l.Infoln("Versioner: error scanning versions dir", err)
		return
	}
}

// The constructor function takes a map of parameters and creates the type.
func NewStaggered(repoID, repoPath string, params map[string]string) Versioner {
	maxAge, err := strconv.ParseInt(params["maxAge"], 10, 0)
	if err != nil {
		maxAge = 31536000 // Default: ~1 year
	}
	cleanInterval, err := strconv.ParseInt(params["cleanInterval"], 10, 0)
	if err != nil {
		cleanInterval = 3600 // Default: clean once per hour
	}

	// Use custom path if set, otherwise .stversions in repoPath
	var versionsDir string
	if params["versionsPath"] == "" {
		if debug {
			l.Debugln("using default dir .stversions")
		}
		versionsDir = filepath.Join(repoPath, ".stversions")
	} else {
		if debug {
			l.Debugln("using dir", params["versionsPath"])
		}
		versionsDir = params["versionsPath"]
	}

	var mutex sync.Mutex
	s := Staggered{
		versionsPath:  versionsDir,
		cleanInterval: cleanInterval,
		repoPath:      repoPath,
		interval: [4]Interval{
			Interval{30, 3600},               // first hour -> 30 sec between versions
			Interval{3600, 86400},            // next day -> 1 h between versions
			Interval{86400, 592000},          // next 30 days -> 1 day between versions
			Interval{604800, maxAge * 86400}, // next year -> 1 week between versions
		},
		mutex: &mutex,
	}

	if debug {
		l.Debugf("instantiated %#v", s)
	}

	// Rename version with old version format
	s.renameOld()

	go func() {
		s.clean()
		for _ = range time.Tick(time.Duration(cleanInterval) * time.Second) {
			s.clean()
		}
	}()

	return s
}

func (v Staggered) clean() {
	if debug {
		l.Debugln("Versioner clean: Waiting for lock on", v.versionsPath)
	}
	v.mutex.Lock()
	defer v.mutex.Unlock()
	if debug {
		l.Debugln("Versioner clean: Cleaning", v.versionsPath)
	}

	_, err := os.Stat(v.versionsPath)
	if err != nil {
		if os.IsNotExist(err) {
			if debug {
				l.Debugln("creating versions dir", v.versionsPath)
			}
			os.MkdirAll(v.versionsPath, 0755)
			osutil.HideFile(v.versionsPath)
		} else {
			l.Warnln("Versioner: can't create versions dir", err)
		}
	}

	versionsPerFile := make(map[string][]string)
	filesPerDir := make(map[string]int)

	err = filepath.Walk(v.versionsPath, func(path string, f os.FileInfo, err error) error {
		switch mode := f.Mode(); {
		case mode.IsDir():
			filesPerDir[path] = 0
			if path != v.versionsPath {
				dir := filepath.Dir(path)
				filesPerDir[dir]++
			}
		case mode.IsRegular():
			extension := versionExt(path)
			dir := filepath.Dir(path)
			name := path[:len(path)-len(extension)-1]

			filesPerDir[dir]++
			versionsPerFile[name] = append(versionsPerFile[name], path)
		}

		return nil
	})
	if err != nil {
		l.Warnln("Versioner: error scanning versions dir", err)
		return
	}

	for _, versionList := range versionsPerFile {
		// List from filepath.Walk is sorted
		v.expire(versionList)
	}

	for path, numFiles := range filesPerDir {
		if numFiles > 0 {
			continue
		}

		if path == v.versionsPath {
			if debug {
				l.Debugln("Cleaner: versions dir is empty, don't delete", path)
			}
			continue
		}

		if debug {
			l.Debugln("Cleaner: deleting empty directory", path)
		}
		err = os.Remove(path)
		if err != nil {
			l.Warnln("Versioner: can't remove directory", path, err)
		}
	}
	if debug {
		l.Debugln("Cleaner: Finished cleaning", v.versionsPath)
	}
}

func (v Staggered) expire(versions []string) {
	if debug {
		l.Debugln("Versioner: Expiring versions", versions)
	}
	var prevAge int64
	firstFile := true
	for _, file := range versions {
		if isFile(file) {
			versionTime, err := time.Parse(TimeLayout, versionExt(file))
			if err != nil {
				l.Infof("Versioner: file name %q is invalid: %v", file, err)
				continue
			}
			age := int64(time.Since(versionTime).Seconds())

			// If the file is older than the max age of the last interval, remove it
			if lastIntv := v.interval[len(v.interval)-1]; lastIntv.end > 0 && age > lastIntv.end {
				if debug {
					l.Debugln("Versioner: File over maximum age -> delete ", file)
				}
				err = os.Remove(file)
				if err != nil {
					l.Warnf("Versioner: can't remove %q: %v", file, err)
				}
				continue
			}

			// If it's the first (oldest) file in the list we can skip the interval checks
			if firstFile {
				prevAge = age
				firstFile = false
				continue
			}

			// Find the interval the file fits in
			var usedInterval Interval
			for _, usedInterval = range v.interval {
				if age < usedInterval.end {
					break
				}
			}

			if prevAge-age < usedInterval.step {
				if debug {
					l.Debugln("too many files in step -> delete", file)
				}
				err = os.Remove(file)
				if err != nil {
					l.Warnf("Versioner: can't remove %q: %v", file, err)
				}
				continue
			}

			prevAge = age
		} else {
			l.Infof("non-file %q is named like a file version", file)
		}
	}
}

// Move away the named file to a version archive. If this function returns
// nil, the named file does not exist any more (has been archived).
func (v Staggered) Archive(filePath string) error {
	if debug {
		l.Debugln("Waiting for lock on ", v.versionsPath)
	}
	v.mutex.Lock()
	defer v.mutex.Unlock()

	fileInfo, err := os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			if debug {
				l.Debugln("not archiving nonexistent file", filePath)
			}
			return nil
		} else {
			return err
		}
	}

	_, err = os.Stat(v.versionsPath)
	if err != nil {
		if os.IsNotExist(err) {
			if debug {
				l.Debugln("creating versions dir", v.versionsPath)
			}
			os.MkdirAll(v.versionsPath, 0755)
			osutil.HideFile(v.versionsPath)
		} else {
			return err
		}
	}

	if debug {
		l.Debugln("archiving", filePath)
	}

	file := filepath.Base(filePath)
	inRepoPath, err := filepath.Rel(v.repoPath, filepath.Dir(filePath))
	if err != nil {
		return err
	}

	dir := filepath.Join(v.versionsPath, inRepoPath)
	err = os.MkdirAll(dir, 0755)
	if err != nil && !os.IsExist(err) {
		return err
	}

	ver := file + "~" + fileInfo.ModTime().Format(TimeLayout)
	dst := filepath.Join(dir, ver)
	if debug {
		l.Debugln("moving to", dst)
	}
	err = osutil.Rename(filePath, dst)
	if err != nil {
		return err
	}

	versions, err := filepath.Glob(filepath.Join(dir, file+"~[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]"))
	if err != nil {
		l.Warnln("Versioner: error finding versions for", file, err)
		return nil
	}

	sort.Strings(versions)
	v.expire(versions)

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net"
	"strconv"

	linuxproc "github.com/c9s/goprocinfo/linux"
)

func get_idle() (out int) {
	stat, err := linuxproc.ReadStat("/proc/stat")
	if err != nil {
		log.Fatal("stat read fail")
	}
	all := stat.CPUStatAll
	total := all.User + all.Nice + all.System + all.Idle + all.IOWait +
		all.IRQ + all.SoftIRQ + all.Steal + all.Guest + all.GuestNice
	idlePercent := 100.00 * (float64(all.Idle) / float64(total))
	return int(idlePercent)
}

func handleConnection(conn net.Conn) {
	idle := strconv.Itoa(get_idle())
	conn.Write([]byte(idle))
	conn.Close()
	return
}

func main() {
	ln, err := net.Listen("tcp", ":7777")
	if err != nil {
		//handle err
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			//handle err
			continue
		}
		go handleConnection(conn)
	}
}
<commit_msg>switching libs<commit_after>package main

import (
	"log"
	"net"
	"strconv"

	linuxproc "github.com/c9s/goprocinfo/linux"
)

func get_idle() (out int) {
	stat, err := linuxproc.ReadStat("/proc/stat")
	if err != nil {
		log.Fatal("stat read fail")
	}
	all := stat.CPUStatAll
	total := all.User + all.Nice + all.System + all.Idle + all.IOWait +
		all.IRQ + all.SoftIRQ + all.Steal + all.Guest + all.GuestNice
	idlePercent := 100.00 * (float64(all.Idle) / float64(total))
	log.Println("idlePercent:", idlePercent)
	return int(idlePercent)
}

func handleConnection(conn net.Conn) {
	idle := strconv.Itoa(get_idle())
	conn.Write([]byte(idle))
	conn.Close()
	return
}

func main() {
	ln, err := net.Listen("tcp", ":7777")
	if err != nil {
		//handle err
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			//handle err
			continue
		}
		go handleConnection(conn)
	}
}
<|endoftext|>


<commit_before>package discord

import (
	"fmt"
	"sync"
	"testing"
	"time"
)

func TestBasicTicker(t *testing.T) {
	// t.Skip("TickOnce not implemented")

	var counter = 1
	var i = 0

	mu := sync.Mutex{}

	f := func(to *Ticker) {
		fmt.Println("Iteration: " + string(i))
		mu.Lock()
		defer mu.Unlock()

		if i >= 5 {
			to.Done()
		}

		counter++
		i++
	}

	cleanUp := func(to *Ticker) {
		return
	}

	ticker := NewTicker(1, f, cleanUp)

	time.AfterFunc(6, func() {
		ticker.Done()
	})

	select {
	case q := <-ticker.Quit:
		if counter != 5 && q {
			t.Errorf("Incorrect `counter` value.")
			t.Errorf("Expected: %d | Received: %d", 5, counter)
		}
	}
}

func TestTickerStop(t *testing.T) {
	t.Skip("TickerStop not impelemnted.")
}
<commit_msg>Simplify test case. Tweak logging.<commit_after>package discord

import (
	"fmt"
	"testing"
	"time"
)

func TestBasicTicker(t *testing.T) {
	// t.Skip("TickOnce not implemented")

	var counter = 1
	var i = 0

	f := func(to *Ticker) {

		fmt.Printf("Iteration: %d\n", i)
		if i >= 5 {
			to.Done()
		}

		counter++
		i++
	}

	cleanUp := func(to *Ticker) {
		return
	}

	ticker := NewTicker(1, f, cleanUp)

	time.AfterFunc(6, func() {
		ticker.Done()
	})

	select {
	case q := <-ticker.Quit:
		if counter != 5 && q {
			t.Errorf("Incorrect `counter` value.")
			t.Errorf("Expected: %d | Received: %d", 5, counter)
		}
	}
}

func TestTickerStop(t *testing.T) {
	t.Skip("TickerStop not impelemnted.")
}
<|endoftext|>


<commit_before>package views

import (
	"log"
	"net/http"

	mbase "github.com/philpearl/tt_goji_middleware/base"
	"github.com/philpearl/tt_goji_oauth/base"
	"github.com/zenazn/goji/web"
	"github.com/philpearl/oauth2"
)

/*
When the OAUTH service is done it redirects the user to this view with
a temporary code that it can exchange for an OAUTH token.  It then calls
the provider to get identifying information for the user.
*/
func OauthCallback(c web.C, w http.ResponseWriter, r *http.Request) {
	context := c.Env["oauth:context"].(*base.Context)

	// Get the session
	session, ok := mbase.SessionFromEnv(&c)

	if !ok {
		log.Printf("Could not retrieve session in oauth callback.")
		http.Error(w, "no session", http.StatusBadRequest)
		return
	}

	// Extract interesting parameters from response.
	r.ParseForm()
	haveState := false
	providerName := r.Form.Get("provider")
	if providerName == "" {
		stateParam := r.Form.Get("state")
		if stateParam == "" {
			log.Printf("State parameter not included")
			http.Error(w, "OAUTH state parameter not present", http.StatusBadRequest)
			return
		}
		state, err := newOauthStateFromString(stateParam)
		if err != nil {
			log.Printf("Failed to decode returned oauth state. %v", err)
			http.Error(w, "OAUTH protocol error detected", http.StatusBadRequest)
			return
		}

		// Important to check we've been passed back our random value.
		val, ok := session.Get("oauth:secret")
		if !ok {
			log.Printf("No secret available in oauth callback")
			http.Error(w, "OAUTH protocol error detected", http.StatusBadRequest)
			return
		}
		secret := val.(int64)

		if state.Secret != secret {
			log.Printf("Mismatched state in oauth callback.  Have %s expected %s", secret, state.Secret)
			http.Error(w, "OAUTH protocol error detected", http.StatusBadRequest)
			return
		}
		providerName = state.ProviderName
		haveState = true
	}

	// Get an access token in exchange for our temporary token
	// In fact we get a "transport" that can issue requests with authentication, then
	// ask for some user info.  This will get our token as a side effect
	// Not that we really need a token - we just want to identify our user

	provider, ok := context.ProviderStore.GetProvider(providerName)
	if !ok {
		log.Printf("Provider %s not found", providerName)
		http.Error(w, "Oops!", http.StatusInternalServerError)
		return
	}

	if provider.NeedState() && !haveState {
		// We expected the state parameter, but it did not arrive.  Security issue
		log.Printf("Expected state parameter not present")
		http.Error(w, "OAUTH protocol error detected", http.StatusBadRequest)
		return
	}

	conf := provider.GetConfig(r)
	rcode := r.Form.Get("code")
	token, err := conf.Exchange(oauth2.NoContext, rcode)
	if err != nil {
		log.Printf("Could not exchange code %s for a token. %v", rcode, err)
		http.Error(w, "Authentication error", http.StatusBadGateway)
		return
	}

	// Get some user info.
	client := conf.Client(oauth2.NoContext, token)
	user, err := provider.GetUserInfo(r, client, token)
	if err != nil {
		log.Printf("Failed to get information from user.  %v", err)
		http.Error(w, "Couldn't retrieve user info", http.StatusServiceUnavailable)
		return
	}
	log.Printf("Have user info %v", user)

	// Get or Create a user object.  Again, some kind of plug-in storage would make sense
	url, err := context.Callbacks.GetOrCreateUser(c, providerName, user)
	if err != nil {
		log.Printf("GetOrCreateUser callback failed. %v", err)
		http.Error(w, "", http.StatusServiceUnavailable)
		return
	}

	// Mark the session as logged in
	session.Put("logged_in", true)

	// Redirect to final destination.  We use the value returned by GetOrCreateUser if set
	if url == "" {
		val, ok := session.Get("next")
		if !ok || val == "" {
			url = "/"
		} else {
			url = val.(string)
			// Don't reuse next
			session.Del("next")
		}
	}

	w.Header().Set("Location", url)
	w.WriteHeader(http.StatusFound)
}
<commit_msg>log session id when we get user info<commit_after>package views

import (
	"log"
	"net/http"

	"github.com/philpearl/oauth2"
	mbase "github.com/philpearl/tt_goji_middleware/base"
	"github.com/philpearl/tt_goji_oauth/base"
	"github.com/zenazn/goji/web"
)

/*
When the OAUTH service is done it redirects the user to this view with
a temporary code that it can exchange for an OAUTH token.  It then calls
the provider to get identifying information for the user.
*/
func OauthCallback(c web.C, w http.ResponseWriter, r *http.Request) {
	context := c.Env["oauth:context"].(*base.Context)

	// Get the session
	session, ok := mbase.SessionFromEnv(&c)

	if !ok {
		log.Printf("Could not retrieve session in oauth callback.")
		http.Error(w, "no session", http.StatusBadRequest)
		return
	}

	// Extract interesting parameters from response.
	r.ParseForm()
	haveState := false
	providerName := r.Form.Get("provider")
	if providerName == "" {
		stateParam := r.Form.Get("state")
		if stateParam == "" {
			log.Printf("State parameter not included")
			http.Error(w, "OAUTH state parameter not present", http.StatusBadRequest)
			return
		}
		state, err := newOauthStateFromString(stateParam)
		if err != nil {
			log.Printf("Failed to decode returned oauth state. %v", err)
			http.Error(w, "OAUTH protocol error detected", http.StatusBadRequest)
			return
		}

		// Important to check we've been passed back our random value.
		val, ok := session.Get("oauth:secret")
		if !ok {
			log.Printf("No secret available in oauth callback")
			http.Error(w, "OAUTH protocol error detected", http.StatusBadRequest)
			return
		}
		secret := val.(int64)

		if state.Secret != secret {
			log.Printf("Mismatched state in oauth callback.  Have %s expected %s", secret, state.Secret)
			http.Error(w, "OAUTH protocol error detected", http.StatusBadRequest)
			return
		}
		providerName = state.ProviderName
		haveState = true
	}

	// Get an access token in exchange for our temporary token
	// In fact we get a "transport" that can issue requests with authentication, then
	// ask for some user info.  This will get our token as a side effect
	// Not that we really need a token - we just want to identify our user

	provider, ok := context.ProviderStore.GetProvider(providerName)
	if !ok {
		log.Printf("Provider %s not found", providerName)
		http.Error(w, "Oops!", http.StatusInternalServerError)
		return
	}

	if provider.NeedState() && !haveState {
		// We expected the state parameter, but it did not arrive.  Security issue
		log.Printf("Expected state parameter not present")
		http.Error(w, "OAUTH protocol error detected", http.StatusBadRequest)
		return
	}

	conf := provider.GetConfig(r)
	rcode := r.Form.Get("code")
	token, err := conf.Exchange(oauth2.NoContext, rcode)
	if err != nil {
		log.Printf("Could not exchange code %s for a token. %v", rcode, err)
		http.Error(w, "Authentication error", http.StatusBadGateway)
		return
	}

	// Get some user info.
	client := conf.Client(oauth2.NoContext, token)
	user, err := provider.GetUserInfo(r, client, token)
	if err != nil {
		log.Printf("Failed to get information from user.  %v", err)
		http.Error(w, "Couldn't retrieve user info", http.StatusServiceUnavailable)
		return
	}
	log.Printf("Have user info %v on session %s", user, session.Id())

	// Get or Create a user object.  Again, some kind of plug-in storage would make sense
	url, err := context.Callbacks.GetOrCreateUser(c, providerName, user)
	if err != nil {
		log.Printf("GetOrCreateUser callback failed. %v", err)
		http.Error(w, "", http.StatusServiceUnavailable)
		return
	}

	// Mark the session as logged in
	session.Put("logged_in", true)

	// Redirect to final destination.  We use the value returned by GetOrCreateUser if set
	if url == "" {
		val, ok := session.Get("next")
		if !ok || val == "" {
			url = "/"
		} else {
			url = val.(string)
			// Don't reuse next
			session.Del("next")
		}
	}

	w.Header().Set("Location", url)
	w.WriteHeader(http.StatusFound)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"time"
)

// Generate a stable SHA256 for a commit.
func createCommitID(c commit) string {
	var b bytes.Buffer
	b.WriteString(fmt.Sprintf("tree %s\n", c.Tree))
	if c.Parent != "" {
		b.WriteString(fmt.Sprintf("parent %s\n", c.Parent))
	}
	b.WriteString(fmt.Sprintf("author %s <%s> %v\n", c.AuthorName, c.AuthorEmail,
		c.Timestamp.Format(time.UnixDate)))
	if c.CommitterEmail != "" {
		b.WriteString(fmt.Sprintf("committer %s <%s> %v\n", c.CommitterName, c.CommitterEmail,
			c.Timestamp.Format(time.UnixDate)))
	}
	b.WriteString("\n" + c.Message)
	b.WriteByte(0)
	s := sha256.Sum256(b.Bytes())
	return hex.EncodeToString(s[:])
}

// Creates the user visible commit text for a commit.
func createCommitText(c commit) (string, error) {
	if c.ID == "" {
		c.ID = createCommitID(c)
	}
	s := fmt.Sprintf("commit %s\n", c.ID)
	s += fmt.Sprintf("Author: %s <%s>\n", c.AuthorName, c.AuthorEmail)
	s += fmt.Sprintf("Date: %v\n\n", c.Timestamp.Format(time.UnixDate))
	if c.Message != "" {
		s += fmt.Sprintf("    %s\n", c.Message)
	}
	return s, nil
}

// Generate the SHA256 for a tree.
// Tree entry structure is:
// * [ type ] [ sha256 ] [ db name ] [ last modified (timestamp) ] [ db size (bytes) ]
func createDBTreeID(entries []dbTreeEntry) string {
	var b bytes.Buffer
	for _, j := range entries {
		b.WriteString(string(j.AType))
		b.WriteByte(0)
		b.WriteString(j.Sha256)
		b.WriteByte(0)
		b.WriteString(j.Name)
		b.WriteByte(0)
		b.WriteString(j.Last_Modified.Format(time.RFC3339))
		b.WriteByte(0)
		b.WriteString(fmt.Sprintf("%d\n", j.Size))
	}
	s := sha256.Sum256(b.Bytes())
	return hex.EncodeToString(s[:])
}

// Check if a database already exists.
func dbExists(dbName string) bool {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the db doesn't exist
		// TODO: Proper handling for errors here.  It may not mean the file doesn't exist.
		return false
	}
	return true
}

// Load the branch heads for a database.
// TODO: It might be better to have the default branch name be returned as part of this list, by indicating in the list
// TODO  which of the branches is the default.
func getBranches(dbName string) (map[string]string, error) {
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "branchHeads"))
	if err != nil {
		return nil, err
	}
	var i map[string]string
	err = json.Unmarshal(b, &i)
	if err != nil {
		log.Printf("Something went wrong unserialising the branchHeads data: %v\n", err.Error())
		return nil, err
	}
	return i, nil
}

// Retrieve the default branch name for a database.
func getDefaultBranchName(dbName string) string {
	if !dbExists(dbName) {
		return "master" // Database doesn't exist, so use "master" as the initial default
	}

	// Return the default branch name
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "defaultBranch"))
	if err != nil {
		log.Printf("Error when reading default branch for '%s': %v\n", dbName, err.Error())
		return "master" // An error occurred reading the default branch name, so default to master
	}
	return string(b[:])
}

// Reads a commit from disk.
func getCommit(id string) (commit, error) {
	var c commit
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		return c, err
	}
	err = json.Unmarshal(b, &c)
	if err != nil {
		log.Printf("Something went wrong unserialising a commit's data: %v\n", err.Error())
		return c, err
	}
	return c, nil
}

// Reads a database from disk.
func getDatabase(id string) ([]byte, error) {
	d, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		log.Printf("Error reading file: '%s': %v\n", id, err.Error())
		return []byte{}, err
	}
	return d, nil
}

// Load the tags (standard, non-annotated type) for a database.
func getTags(dbName string) (map[string]tagEntry, error) {
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "tags"))
	if err != nil {
		_, ok := err.(*os.PathError)
		if ok {
			// There are no tags for the database yet
			return make(map[string]tagEntry), nil
		}

		log.Printf("Something went wrong reading the tags data: %v\n", err.Error())
		return nil, err
	}
	var i map[string]tagEntry
	err = json.Unmarshal(b, &i)
	if err != nil {
		log.Printf("Something went wrong unserialising the tags data: %v\n", err.Error())
		return nil, err
	}
	return i, nil
}

// Reads a tree from disk.
func getTree(id string) (dbTree, error) {
	var t dbTree
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		log.Printf("Error reading file: '%s': %v\n", id, err.Error())
		return t, err
	}
	err = json.Unmarshal(b, &t)
	if err != nil {
		log.Printf("Something went wrong unserialising a commit's data: %v\n", err.Error())
		return t, err
	}
	return t, nil
}

// Returns the list of available databases.
func listDatabases() ([]byte, error) {
	// For now, just use the entries in the "meta" directory as the list
	p := filepath.Join(STORAGEDIR, "meta")
	dirEntries, err := ioutil.ReadDir(p)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the db doesn't exist
		log.Printf("Error when reading database list: %v\n", err)
		return []byte{}, err
	}
	var dbs []dbListEntry
	for _, j := range dirEntries {
		d := dbListEntry{Database: j.Name(), LastModified: j.ModTime(), Size: int(j.Size())}
		dbs = append(dbs, d)
	}

	// Convert into json
	j, err := json.MarshalIndent(dbs, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return []byte{}, err
	}

	return j, nil
}

// Store the branch heads for a database.
func storeBranches(dbName string, branches map[string]string) error {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the dir needs creating
		// TODO: Proper handling for errors here.  It may not mean the dir doesn't exist.
		err := os.MkdirAll(filepath.Join(STORAGEDIR, "meta", dbName), os.ModeDir|0755)
		if err != nil {
			log.Printf("Something went wrong creating the database meta dir: %v\n", err.Error())
			return err
		}
	}

	j, err := json.MarshalIndent(branches, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "branchHeads"), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the branchHeads file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a commit.
func storeCommit(c commit) error {
	j, err := json.MarshalIndent(c, "", " ")
	if err != nil {
		log.Printf("Something went wrong when serialising the commit data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "files", c.ID), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the commit file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a database file.
func storeDatabase(db []byte) error {
	// Create the database file if it doesn't already exist
	a := sha256.Sum256(db)
	sha := hex.EncodeToString(a[:])
	path := filepath.Join(STORAGEDIR, "files", sha)
	f, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the file needs creating
		// TODO: Proper handling for errors here.  It may not mean the file doesn't exist.
		err = ioutil.WriteFile(path, db, os.ModePerm)
		if err != nil {
			log.Printf("Something went wrong writing the database file: %v\n", err.Error())
			return err
		}
		return nil
	}

	// The file already exists.
	// Check if the file size matches the buffer size we're intending on writing, and skip it if so
	// (Obviously this is just a super lightweight check, not a real world approach)
	// TODO: Add real world checks to ensure the file contents are identical.  Maybe read the file contents into
	// TODO  memory, then binary compare them?  Prob not great for memory efficiency, but it would likely do as a
	// TODO  first go for something accurate.
	if len(db) != int(f.Size()) {
		err = ioutil.WriteFile(path, db, os.ModePerm)
		if err != nil {
			log.Printf("Something went wrong writing the database file: %v\n", err.Error())
			return err
		}
	}
	return nil
}

// Stores the default branch name for a database.
func storeDefaultBranchName(dbName string, branchName string) error {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the dir needs creating
		// TODO: Proper handling for errors here.  It may not mean the dir doesn't exist.
		err := os.MkdirAll(filepath.Join(STORAGEDIR, "meta", dbName), os.ModeDir|0755)
		if err != nil {
			log.Printf("Something went wrong creating the database meta dir: %v\n", err.Error())
			return err
		}
	}

	var buf bytes.Buffer
	buf.WriteString(branchName)
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "defaultBranch"), buf.Bytes(), os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the default branch name for '%s': %v\n", dbName,
			err.Error())
		return err
	}
	return nil
}

// Store the tags (standard, non-annotated type) for a database.
func storeTags(dbName string, tags map[string]tagEntry) error {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the dir needs creating
		// TODO: Proper handling for errors here.  It may not mean the dir doesn't exist.
		err := os.MkdirAll(filepath.Join(STORAGEDIR, "meta", dbName), os.ModeDir|0755)
		if err != nil {
			log.Printf("Something went wrong creating the database meta dir: %v\n", err.Error())
			return err
		}
	}

	j, err := json.MarshalIndent(tags, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "tags"), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the tags file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a tree.
func storeTree(t dbTree) error {
	j, err := json.MarshalIndent(t, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the tree data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "files", t.ID), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the tree file: %v\n", err.Error())
		return err
	}
	return nil
}
<commit_msg>listDatabases() now returns correct info<commit_after>package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"time"
)

// Generate a stable SHA256 for a commit.
func createCommitID(c commit) string {
	var b bytes.Buffer
	b.WriteString(fmt.Sprintf("tree %s\n", c.Tree))
	if c.Parent != "" {
		b.WriteString(fmt.Sprintf("parent %s\n", c.Parent))
	}
	b.WriteString(fmt.Sprintf("author %s <%s> %v\n", c.AuthorName, c.AuthorEmail,
		c.Timestamp.Format(time.UnixDate)))
	if c.CommitterEmail != "" {
		b.WriteString(fmt.Sprintf("committer %s <%s> %v\n", c.CommitterName, c.CommitterEmail,
			c.Timestamp.Format(time.UnixDate)))
	}
	b.WriteString("\n" + c.Message)
	b.WriteByte(0)
	s := sha256.Sum256(b.Bytes())
	return hex.EncodeToString(s[:])
}

// Creates the user visible commit text for a commit.
func createCommitText(c commit) (string, error) {
	if c.ID == "" {
		c.ID = createCommitID(c)
	}
	s := fmt.Sprintf("commit %s\n", c.ID)
	s += fmt.Sprintf("Author: %s <%s>\n", c.AuthorName, c.AuthorEmail)
	s += fmt.Sprintf("Date: %v\n\n", c.Timestamp.Format(time.UnixDate))
	if c.Message != "" {
		s += fmt.Sprintf("    %s\n", c.Message)
	}
	return s, nil
}

// Generate the SHA256 for a tree.
// Tree entry structure is:
// * [ type ] [ sha256 ] [ db name ] [ last modified (timestamp) ] [ db size (bytes) ]
func createDBTreeID(entries []dbTreeEntry) string {
	var b bytes.Buffer
	for _, j := range entries {
		b.WriteString(string(j.AType))
		b.WriteByte(0)
		b.WriteString(j.Sha256)
		b.WriteByte(0)
		b.WriteString(j.Name)
		b.WriteByte(0)
		b.WriteString(j.Last_Modified.Format(time.RFC3339))
		b.WriteByte(0)
		b.WriteString(fmt.Sprintf("%d\n", j.Size))
	}
	s := sha256.Sum256(b.Bytes())
	return hex.EncodeToString(s[:])
}

// Check if a database already exists.
func dbExists(dbName string) bool {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the db doesn't exist
		// TODO: Proper handling for errors here.  It may not mean the file doesn't exist.
		return false
	}
	return true
}

// Load the branch heads for a database.
// TODO: It might be better to have the default branch name be returned as part of this list, by indicating in the list
// TODO  which of the branches is the default.
func getBranches(dbName string) (map[string]string, error) {
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "branchHeads"))
	if err != nil {
		return nil, err
	}
	var i map[string]string
	err = json.Unmarshal(b, &i)
	if err != nil {
		log.Printf("Something went wrong unserialising the branchHeads data: %v\n", err.Error())
		return nil, err
	}
	return i, nil
}

// Retrieve the default branch name for a database.
func getDefaultBranchName(dbName string) string {
	if !dbExists(dbName) {
		return "master" // Database doesn't exist, so use "master" as the initial default
	}

	// Return the default branch name
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "defaultBranch"))
	if err != nil {
		log.Printf("Error when reading default branch for '%s': %v\n", dbName, err.Error())
		return "master" // An error occurred reading the default branch name, so default to master
	}
	return string(b[:])
}

// Reads a commit from disk.
func getCommit(id string) (commit, error) {
	var c commit
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		return c, err
	}
	err = json.Unmarshal(b, &c)
	if err != nil {
		log.Printf("Something went wrong unserialising a commit's data: %v\n", err.Error())
		return c, err
	}
	return c, nil
}

// Reads a database from disk.
func getDatabase(id string) ([]byte, error) {
	d, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		log.Printf("Error reading file: '%s': %v\n", id, err.Error())
		return []byte{}, err
	}
	return d, nil
}

// Load the tags (standard, non-annotated type) for a database.
func getTags(dbName string) (map[string]tagEntry, error) {
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "meta", dbName, "tags"))
	if err != nil {
		_, ok := err.(*os.PathError)
		if ok {
			// There are no tags for the database yet
			return make(map[string]tagEntry), nil
		}

		log.Printf("Something went wrong reading the tags data: %v\n", err.Error())
		return nil, err
	}
	var i map[string]tagEntry
	err = json.Unmarshal(b, &i)
	if err != nil {
		log.Printf("Something went wrong unserialising the tags data: %v\n", err.Error())
		return nil, err
	}
	return i, nil
}

// Reads a tree from disk.
func getTree(id string) (dbTree, error) {
	var t dbTree
	b, err := ioutil.ReadFile(filepath.Join(STORAGEDIR, "files", id))
	if err != nil {
		log.Printf("Error reading file: '%s': %v\n", id, err.Error())
		return t, err
	}
	err = json.Unmarshal(b, &t)
	if err != nil {
		log.Printf("Something went wrong unserialising a commit's data: %v\n", err.Error())
		return t, err
	}
	return t, nil
}

// Returns the list of available databases.
func listDatabases() ([]byte, error) {
	// For now, just use the entries in the "meta" directory as the list
	p := filepath.Join(STORAGEDIR, "meta")
	dirEntries, err := ioutil.ReadDir(p)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the db doesn't exist
		log.Printf("Error when reading database list: %v\n", err)
		return []byte{}, err
	}
	var dbs []dbListEntry
	for _, i := range dirEntries {
		// Get the size and last modified date of each of the databases from it's commit tree entry
		def := getDefaultBranchName(i.Name())
		b, err := getBranches(i.Name())
		if err != nil {
			return []byte{}, err
		}
		c, err := getCommit(b[def])
		if err != nil {
			return []byte{}, err
		}
		t, err := getTree(c.Tree)
		if err != nil {
			return []byte{}, err
		}
		var lastMod time.Time
		var dbSize int
		for _, j := range t.Entries {
			if j.Name == i.Name() {
				lastMod = j.Last_Modified
				dbSize = j.Size
			}
		}
		d := dbListEntry{Database: i.Name(), LastModified: lastMod, Size: dbSize}
		dbs = append(dbs, d)
	}

	// Convert into json
	j, err := json.MarshalIndent(dbs, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return []byte{}, err
	}

	return j, nil
}

// Store the branch heads for a database.
func storeBranches(dbName string, branches map[string]string) error {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the dir needs creating
		// TODO: Proper handling for errors here.  It may not mean the dir doesn't exist.
		err := os.MkdirAll(filepath.Join(STORAGEDIR, "meta", dbName), os.ModeDir|0755)
		if err != nil {
			log.Printf("Something went wrong creating the database meta dir: %v\n", err.Error())
			return err
		}
	}

	j, err := json.MarshalIndent(branches, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "branchHeads"), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the branchHeads file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a commit.
func storeCommit(c commit) error {
	j, err := json.MarshalIndent(c, "", " ")
	if err != nil {
		log.Printf("Something went wrong when serialising the commit data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "files", c.ID), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the commit file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a database file.
func storeDatabase(db []byte) error {
	// Create the database file if it doesn't already exist
	a := sha256.Sum256(db)
	sha := hex.EncodeToString(a[:])
	path := filepath.Join(STORAGEDIR, "files", sha)
	f, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the file needs creating
		// TODO: Proper handling for errors here.  It may not mean the file doesn't exist.
		err = ioutil.WriteFile(path, db, os.ModePerm)
		if err != nil {
			log.Printf("Something went wrong writing the database file: %v\n", err.Error())
			return err
		}
		return nil
	}

	// The file already exists.
	// Check if the file size matches the buffer size we're intending on writing, and skip it if so
	// (Obviously this is just a super lightweight check, not a real world approach)
	// TODO: Add real world checks to ensure the file contents are identical.  Maybe read the file contents into
	// TODO  memory, then binary compare them?  Prob not great for memory efficiency, but it would likely do as a
	// TODO  first go for something accurate.
	if len(db) != int(f.Size()) {
		err = ioutil.WriteFile(path, db, os.ModePerm)
		if err != nil {
			log.Printf("Something went wrong writing the database file: %v\n", err.Error())
			return err
		}
	}
	return nil
}

// Stores the default branch name for a database.
func storeDefaultBranchName(dbName string, branchName string) error {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the dir needs creating
		// TODO: Proper handling for errors here.  It may not mean the dir doesn't exist.
		err := os.MkdirAll(filepath.Join(STORAGEDIR, "meta", dbName), os.ModeDir|0755)
		if err != nil {
			log.Printf("Something went wrong creating the database meta dir: %v\n", err.Error())
			return err
		}
	}

	var buf bytes.Buffer
	buf.WriteString(branchName)
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "defaultBranch"), buf.Bytes(), os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the default branch name for '%s': %v\n", dbName,
			err.Error())
		return err
	}
	return nil
}

// Store the tags (standard, non-annotated type) for a database.
func storeTags(dbName string, tags map[string]tagEntry) error {
	path := filepath.Join(STORAGEDIR, "meta", dbName)
	_, err := os.Stat(path)
	if err != nil {
		// As this is just experimental code, we'll assume a failure above means the dir needs creating
		// TODO: Proper handling for errors here.  It may not mean the dir doesn't exist.
		err := os.MkdirAll(filepath.Join(STORAGEDIR, "meta", dbName), os.ModeDir|0755)
		if err != nil {
			log.Printf("Something went wrong creating the database meta dir: %v\n", err.Error())
			return err
		}
	}

	j, err := json.MarshalIndent(tags, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the branch data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "meta", dbName, "tags"), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the tags file: %v\n", err.Error())
		return err
	}
	return nil
}

// Store a tree.
func storeTree(t dbTree) error {
	j, err := json.MarshalIndent(t, "", " ")
	if err != nil {
		log.Printf("Something went wrong serialising the tree data: %v\n", err.Error())
		return err
	}
	err = ioutil.WriteFile(filepath.Join(STORAGEDIR, "files", t.ID), j, os.ModePerm)
	if err != nil {
		log.Printf("Something went wrong writing the tree file: %v\n", err.Error())
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package api

import (
	"errors"
	"fmt"
	"net/http"

	"github.com/ltick/tick-framework/session"
	"github.com/ltick/tick-routing"
)

var errNotEnableSession = errors.New("before using the session, must set config `session::enable = true`...")

// Headers
const (
	HeaderAccept                        = "Accept"
	HeaderAcceptEncoding                = "Accept-Encoding"
	HeaderAuthorization                 = "Authorization"
	HeaderContentDisposition            = "Content-Disposition"
	HeaderContentEncoding               = "Content-Encoding"
	HeaderContentLength                 = "Content-Length"
	HeaderContentType                   = "Content-Type"
	HeaderContentDescription            = "Content-Description"
	HeaderContentTransferEncoding       = "Content-Transfer-Encoding"
	HeaderCookie                        = "Cookie"
	HeaderSetCookie                     = "Set-Cookie"
	HeaderIfModifiedSince               = "If-Modified-Since"
	HeaderLastModified                  = "Last-Modified"
	HeaderLocation                      = "Location"
	HeaderReferer                       = "Referer"
	HeaderUserAgent                     = "User-Agent"
	HeaderUpgrade                       = "Upgrade"
	HeaderVary                          = "Vary"
	HeaderWWWAuthenticate               = "WWW-Authenticate"
	HeaderXForwardedProto               = "X-Forwarded-Proto"
	HeaderXHTTPMethodOverride           = "X-HTTP-Method-Override"
	HeaderXForwardedFor                 = "X-Forwarded-For"
	HeaderXRealIP                       = "X-Real-IP"
	HeaderXRequestedWith                = "X-Requested-With"
	HeaderServer                        = "Server"
	HeaderOrigin                        = "Origin"
	HeaderAccessControlRequestMethod    = "Access-Control-Request-Method"
	HeaderAccessControlRequestHeaders   = "Access-Control-Request-Headers"
	HeaderAccessControlAllowOrigin      = "Access-Control-Allow-Origin"
	HeaderAccessControlAllowMethods     = "Access-Control-Allow-Methods"
	HeaderAccessControlAllowHeaders     = "Access-Control-Allow-Headers"
	HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
	HeaderAccessControlExposeHeaders    = "Access-Control-Expose-Headers"
	HeaderAccessControlMaxAge           = "Access-Control-Max-Age"
	HeaderExpires                       = "Expires"
	HeaderCacheControl                  = "Cache-Control"
	HeaderPragma                        = "Pragma"

	// Security
	HeaderStrictTransportSecurity = "Strict-Transport-Security"
	HeaderXContentTypeOptions     = "X-Content-Type-Options"
	HeaderXXSSProtection          = "X-XSS-Protection"
	HeaderXFrameOptions           = "X-Frame-Options"
	HeaderContentSecurityPolicy   = "Content-Security-Policy"
	HeaderXCSRFToken              = "X-CSRF-Token"
)

// MIME types
const (
	MIMEApplicationJSON                  = "application/json"
	MIMEApplicationJSONCharsetUTF8       = MIMEApplicationJSON + "; " + CharsetUTF8
	MIMEApplicationJavaScript            = "application/javascript"
	MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + CharsetUTF8
	MIMEApplicationXML                   = "application/xml"
	MIMEApplicationXMLCharsetUTF8        = MIMEApplicationXML + "; " + CharsetUTF8
	MIMETextXML                          = "text/xml"
	MIMETextXMLCharsetUTF8               = MIMETextXML + "; " + CharsetUTF8
	MIMEApplicationForm                  = "application/x-www-form-urlencoded"
	MIMEApplicationProtobuf              = "application/protobuf"
	MIMEApplicationMsgpack               = "application/msgpack"
	MIMETextHTML                         = "text/html"
	MIMETextHTMLCharsetUTF8              = MIMETextHTML + "; " + CharsetUTF8
	MIMETextPlain                        = "text/plain"
	MIMETextPlainCharsetUTF8             = MIMETextPlain + "; " + CharsetUTF8
	MIMEMultipartForm                    = "multipart/form-data"
	MIMEOctetStream                      = "application/octet-stream"
)

const (
	//---------
	// Charset
	//---------

	CharsetUTF8 = "charset=utf-8"

	//---------
	// Headers
	//---------

	AcceptEncoding     = "Accept-Encoding"
	Authorization      = "Authorization"
	ContentDisposition = "Content-Disposition"
	ContentEncoding    = "Content-Encoding"
	ContentLength      = "Content-Length"
	ContentType        = "Content-Type"
	Location           = "Location"
	Upgrade            = "Upgrade"
	Vary               = "Vary"
	WWWAuthenticate    = "WWW-Authenticate"
	XForwardedFor      = "X-Forwarded-For"
	XRealIP            = "X-Real-IP"
)

type (
	// Context is resetting every time a request is coming to the server
	// it is not good practice to use this object in goroutines, for these cases use the .Clone()
	Context struct {
		*routing.Context

		apiParams ApiParams // The parameter values on the URL path
		Response  *Response
		enableGzip bool

		// create param name from struct field name
		paramNameMapper ParamNameMapper
		// multipart max memory
		multipartMaxMemory int64
		// memory store byte
		memoryStoreByte []byte
		// save file dir
		fileStoreDir string

		Session       *session.Session
		sessionStore  session.Store
		enableSession bool // Note: Never reset!
	}
)

// startSession starts session and load old session data info this controller.
func (ctx *Context) startSession() (session.Store, error) {
	if ctx.sessionStore != nil {
		return ctx.sessionStore, nil
	}
	if !ctx.enableSession {
		return nil, errNotEnableSession
	}
	var err error
	ctx.sessionStore, err = ctx.Session.Start(ctx.ResponseWriter, ctx.Request)
	return ctx.sessionStore, err
}

// getSessionStore return SessionStore.
func (ctx *Context) getSessionStore() (session.Store, error) {
	if ctx.sessionStore != nil {
		return ctx.sessionStore, nil
	}
	if !ctx.enableSession {
		return nil, errNotEnableSession
	}
	var err error
	ctx.sessionStore, err = ctx.Session.GetStore(ctx.ResponseWriter, ctx.Request)
	return ctx.sessionStore, err
}

// SetSession puts value into session.
func (ctx *Context) SetSession(key interface{}, value interface{}) {
	if _, err := ctx.startSession(); err != nil {
		return
	}
	ctx.sessionStore.Set(key, value)
}

// GetSession gets value from session.
func (ctx *Context) GetSession(key interface{}) interface{} {
	if _, err := ctx.getSessionStore(); err != nil {
		return nil
	}
	return ctx.sessionStore.Get(key)
}

// DelSession removes value from session.
func (ctx *Context) DelSession(key interface{}) {
	if _, err := ctx.getSessionStore(); err != nil {
		return
	}
	ctx.sessionStore.Delete(key)
}

// SessionRegenerateID regenerates session id for this session.
// the session data have no changes.
func (ctx *Context) SessionRegenerateID() {
	if _, err := ctx.getSessionStore(); err != nil {
		return
	}
	ctx.Session.Destroy(ctx.ResponseWriter, ctx.Request)
	ctx.sessionStore = ctx.Session.RegenerateID(ctx, ctx.ResponseWriter, ctx.Request)
}

// DestroySession cleans session data and session cookie.
func (ctx *Context) DestroySession() {
	if _, err := ctx.getSessionStore(); err != nil {
		return
	}
	ctx.sessionStore.Flush()
	ctx.sessionStore = nil
	ctx.Session.Destroy(ctx.ResponseWriter, ctx.Request)
}

// Redirect replies to the request with a redirect to url,
// which may be a path relative to the request path.
//
// The provided status code should be in the 3xx range and is usually
// StatusMovedPermanently, StatusFound or StatusSeeOther.
func (ctx *Context) Redirect(status int, urlStr string) error {
	if status < http.StatusMultipleChoices || status > http.StatusPermanentRedirect {
		return fmt.Errorf("The provided status code should be in the 3xx range and is usually 301, 302 or 303, yours: %d", status)
	}
	http.Redirect(ctx.ResponseWriter, ctx.Request, urlStr, status)
	return nil
}

func (ctx *Context) beforeWriteHeader() {
	if ctx.enableSession {
		if ctx.sessionStore != nil {
			ctx.Session.Destroy(ctx.ResponseWriter, ctx.Request)
			ctx.sessionStore = nil
		}
	}
}
<commit_msg>update<commit_after>package api

import (
	"errors"
	"fmt"
	"net/http"

	"github.com/ltick/tick-framework/session"
	"github.com/ltick/tick-routing"
)

var errNotEnableSession = errors.New("before using the session, must set config `session::enable = true`...")

// Headers
const (
	HeaderAccept                        = "Accept"
	HeaderAcceptEncoding                = "Accept-Encoding"
	HeaderAuthorization                 = "Authorization"
	HeaderContentDisposition            = "Content-Disposition"
	HeaderContentEncoding               = "Content-Encoding"
	HeaderContentLength                 = "Content-Length"
	HeaderContentType                   = "Content-Type"
	HeaderContentDescription            = "Content-Description"
	HeaderContentTransferEncoding       = "Content-Transfer-Encoding"
	HeaderCookie                        = "Cookie"
	HeaderSetCookie                     = "Set-Cookie"
	HeaderIfModifiedSince               = "If-Modified-Since"
	HeaderLastModified                  = "Last-Modified"
	HeaderLocation                      = "Location"
	HeaderReferer                       = "Referer"
	HeaderUserAgent                     = "User-Agent"
	HeaderUpgrade                       = "Upgrade"
	HeaderVary                          = "Vary"
	HeaderWWWAuthenticate               = "WWW-Authenticate"
	HeaderXForwardedProto               = "X-Forwarded-Proto"
	HeaderXHTTPMethodOverride           = "X-HTTP-Method-Override"
	HeaderXForwardedFor                 = "X-Forwarded-For"
	HeaderXRealIP                       = "X-Real-IP"
	HeaderXRequestedWith                = "X-Requested-With"
	HeaderServer                        = "Server"
	HeaderOrigin                        = "Origin"
	HeaderAccessControlRequestMethod    = "Access-Control-Request-Method"
	HeaderAccessControlRequestHeaders   = "Access-Control-Request-Headers"
	HeaderAccessControlAllowOrigin      = "Access-Control-Allow-Origin"
	HeaderAccessControlAllowMethods     = "Access-Control-Allow-Methods"
	HeaderAccessControlAllowHeaders     = "Access-Control-Allow-Headers"
	HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
	HeaderAccessControlExposeHeaders    = "Access-Control-Expose-Headers"
	HeaderAccessControlMaxAge           = "Access-Control-Max-Age"
	HeaderExpires                       = "Expires"
	HeaderCacheControl                  = "Cache-Control"
	HeaderPragma                        = "Pragma"

	// Security
	HeaderStrictTransportSecurity = "Strict-Transport-Security"
	HeaderXContentTypeOptions     = "X-Content-Type-Options"
	HeaderXXSSProtection          = "X-XSS-Protection"
	HeaderXFrameOptions           = "X-Frame-Options"
	HeaderContentSecurityPolicy   = "Content-Security-Policy"
	HeaderXCSRFToken              = "X-CSRF-Token"
)

// MIME types
const (
	MIMEApplicationJSON                  = "application/json"
	MIMEApplicationJSONCharsetUTF8       = MIMEApplicationJSON + "; " + CharsetUTF8
	MIMEApplicationJavaScript            = "application/javascript"
	MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + CharsetUTF8
	MIMEApplicationXML                   = "application/xml"
	MIMEApplicationXMLCharsetUTF8        = MIMEApplicationXML + "; " + CharsetUTF8
	MIMETextXML                          = "text/xml"
	MIMETextXMLCharsetUTF8               = MIMETextXML + "; " + CharsetUTF8
	MIMEApplicationForm                  = "application/x-www-form-urlencoded"
	MIMEApplicationProtobuf              = "application/protobuf"
	MIMEApplicationMsgpack               = "application/msgpack"
	MIMETextHTML                         = "text/html"
	MIMETextHTMLCharsetUTF8              = MIMETextHTML + "; " + CharsetUTF8
	MIMETextPlain                        = "text/plain"
	MIMETextPlainCharsetUTF8             = MIMETextPlain + "; " + CharsetUTF8
	MIMEMultipartForm                    = "multipart/form-data"
	MIMEOctetStream                      = "application/octet-stream"
)

const (
	//---------
	// Charset
	//---------

	CharsetUTF8 = "charset=utf-8"

	//---------
	// Headers
	//---------

	AcceptEncoding     = "Accept-Encoding"
	Authorization      = "Authorization"
	ContentDisposition = "Content-Disposition"
	ContentEncoding    = "Content-Encoding"
	ContentLength      = "Content-Length"
	ContentType        = "Content-Type"
	Location           = "Location"
	Upgrade            = "Upgrade"
	Vary               = "Vary"
	WWWAuthenticate    = "WWW-Authenticate"
	XForwardedFor      = "X-Forwarded-For"
	XRealIP            = "X-Real-IP"
)

type (
	// Context is resetting every time a request is coming to the server
	// it is not good practice to use this object in goroutines, for these cases use the .Clone()
	Context struct {
		*routing.Context

		apiParams ApiParams // The parameter values on the URL path
		enableGzip bool

		// create param name from struct field name
		paramNameMapper ParamNameMapper
		// multipart max memory
		multipartMaxMemory int64
		// memory store byte
		memoryStoreByte []byte
		// save file dir
		fileStoreDir string

		Session       *session.Session
		sessionStore  session.Store
		enableSession bool // Note: Never reset!
	}
)

// startSession starts session and load old session data info this controller.
func (ctx *Context) startSession() (session.Store, error) {
	if ctx.sessionStore != nil {
		return ctx.sessionStore, nil
	}
	if !ctx.enableSession {
		return nil, errNotEnableSession
	}
	var err error
	ctx.sessionStore, err = ctx.Session.Start(ctx.ResponseWriter, ctx.Request)
	return ctx.sessionStore, err
}

// getSessionStore return SessionStore.
func (ctx *Context) getSessionStore() (session.Store, error) {
	if ctx.sessionStore != nil {
		return ctx.sessionStore, nil
	}
	if !ctx.enableSession {
		return nil, errNotEnableSession
	}
	var err error
	ctx.sessionStore, err = ctx.Session.GetStore(ctx.ResponseWriter, ctx.Request)
	return ctx.sessionStore, err
}

// SetSession puts value into session.
func (ctx *Context) SetSession(key interface{}, value interface{}) {
	if _, err := ctx.startSession(); err != nil {
		return
	}
	ctx.sessionStore.Set(key, value)
}

// GetSession gets value from session.
func (ctx *Context) GetSession(key interface{}) interface{} {
	if _, err := ctx.getSessionStore(); err != nil {
		return nil
	}
	return ctx.sessionStore.Get(key)
}

// DelSession removes value from session.
func (ctx *Context) DelSession(key interface{}) {
	if _, err := ctx.getSessionStore(); err != nil {
		return
	}
	ctx.sessionStore.Delete(key)
}

// SessionRegenerateID regenerates session id for this session.
// the session data have no changes.
func (ctx *Context) SessionRegenerateID() {
	if _, err := ctx.getSessionStore(); err != nil {
		return
	}
	ctx.Session.Destroy(ctx.ResponseWriter, ctx.Request)
	ctx.sessionStore = ctx.Session.RegenerateID(ctx, ctx.ResponseWriter, ctx.Request)
}

// DestroySession cleans session data and session cookie.
func (ctx *Context) DestroySession() {
	if _, err := ctx.getSessionStore(); err != nil {
		return
	}
	ctx.sessionStore.Flush()
	ctx.sessionStore = nil
	ctx.Session.Destroy(ctx.ResponseWriter, ctx.Request)
}

// Redirect replies to the request with a redirect to url,
// which may be a path relative to the request path.
//
// The provided status code should be in the 3xx range and is usually
// StatusMovedPermanently, StatusFound or StatusSeeOther.
func (ctx *Context) Redirect(status int, urlStr string) error {
	if status < http.StatusMultipleChoices || status > http.StatusPermanentRedirect {
		return fmt.Errorf("The provided status code should be in the 3xx range and is usually 301, 302 or 303, yours: %d", status)
	}
	http.Redirect(ctx.ResponseWriter, ctx.Request, urlStr, status)
	return nil
}

func (ctx *Context) beforeWriteHeader() {
	if ctx.enableSession {
		if ctx.sessionStore != nil {
			ctx.Session.Destroy(ctx.ResponseWriter, ctx.Request)
			ctx.sessionStore = nil
		}
	}
}
<|endoftext|>


<commit_before>package api

import (
	"errors"
	"path"
	"strings"
)

type elementResult struct {
	Element    string `json:"ELEMENT"`
	W3CElement string `json:"element-6066-11e4-a52e-4f735466cecf"`
}

func (er elementResult) ID() string {
	if er.Element != "" {
		return er.Element
	}
	return er.W3CElement
}

type Element struct {
	ID      string
	Session *Session
}

func (e *Element) Send(method, endpoint string, body, result interface{}) error {
	return e.Session.Send(method, path.Join("element", e.ID, endpoint), body, result)
}

func (e *Element) GetID() string {
	return e.ID
}

func (e *Element) GetElement(selector Selector) (*Element, error) {
	var result elementResult

	if err := e.Send("POST", "element", selector, &result); err != nil {
		return nil, err
	}

	return &Element{result.Element, e.Session}, nil
}

func (e *Element) GetElements(selector Selector) ([]*Element, error) {
	var results []elementResult

	if err := e.Send("POST", "elements", selector, &results); err != nil {
		return nil, err
	}

	elements := []*Element{}
	for _, result := range results {
		elements = append(elements, &Element{result.Element, e.Session})
	}

	return elements, nil
}

func (e *Element) GetText() (string, error) {
	var text string
	if err := e.Send("GET", "text", nil, &text); err != nil {
		return "", err
	}
	return text, nil
}

func (e *Element) GetName() (string, error) {
	var name string
	if err := e.Send("GET", "name", nil, &name); err != nil {
		return "", err
	}
	return name, nil
}

func (e *Element) GetAttribute(attribute string) (string, error) {
	var value string
	if err := e.Send("GET", path.Join("attribute", attribute), nil, &value); err != nil {
		return "", err
	}
	return value, nil
}

func (e *Element) GetCSS(property string) (string, error) {
	var value string
	if err := e.Send("GET", path.Join("css", property), nil, &value); err != nil {
		return "", err
	}
	return value, nil
}

func (e *Element) Click() error {
	return e.Send("POST", "click", nil, nil)
}

func (e *Element) Clear() error {
	return e.Send("POST", "clear", nil, nil)
}

func (e *Element) Value(text string) error {
	splitText := strings.Split(text, "")
	request := struct {
		Value []string `json:"value"`
	}{splitText}
	return e.Send("POST", "value", request, nil)
}

func (e *Element) IsSelected() (bool, error) {
	var selected bool
	if err := e.Send("GET", "selected", nil, &selected); err != nil {
		return false, err
	}
	return selected, nil
}

func (e *Element) IsDisplayed() (bool, error) {
	var displayed bool
	if err := e.Send("GET", "displayed", nil, &displayed); err != nil {
		return false, err
	}
	return displayed, nil
}

func (e *Element) IsEnabled() (bool, error) {
	var enabled bool
	if err := e.Send("GET", "enabled", nil, &enabled); err != nil {
		return false, err
	}
	return enabled, nil
}

func (e *Element) Submit() error {
	return e.Send("POST", "submit", nil, nil)
}

func (e *Element) IsEqualTo(other *Element) (bool, error) {
	if other == nil {
		return false, errors.New("nil element is invalid")
	}
	var equal bool
	if err := e.Send("GET", path.Join("equals", other.ID), nil, &equal); err != nil {
		return false, err
	}
	return equal, nil
}

func (e *Element) GetLocation() (x, y int, err error) {
	var location struct {
		X float64 `json:"x"`
		Y float64 `json:"y"`
	}
	if err := e.Send("GET", "location", nil, &location); err != nil {
		return 0, 0, err
	}
	return round(location.X), round(location.Y), nil
}

func (e *Element) GetSize() (width, height int, err error) {
	var size struct {
		Width  float64 `json:"width"`
		Height float64 `json:"height"`
	}
	if err := e.Send("GET", "size", nil, &size); err != nil {
		return 0, 0, err
	}
	return round(size.Width), round(size.Height), nil
}

func round(number float64) int {
	return int(number + 0.5)
}
<commit_msg>use ID() method<commit_after>package api

import (
	"errors"
	"path"
	"strings"
)

type elementResult struct {
	Element    string `json:"ELEMENT"`
	W3CElement string `json:"element-6066-11e4-a52e-4f735466cecf"`
}

func (er elementResult) ID() string {
	if er.Element != "" {
		return er.Element
	}
	return er.W3CElement
}

type Element struct {
	ID      string
	Session *Session
}

func (e *Element) Send(method, endpoint string, body, result interface{}) error {
	return e.Session.Send(method, path.Join("element", e.ID, endpoint), body, result)
}

func (e *Element) GetID() string {
	return e.ID
}

func (e *Element) GetElement(selector Selector) (*Element, error) {
	var result elementResult

	if err := e.Send("POST", "element", selector, &result); err != nil {
		return nil, err
	}

	return &Element{result.ID(), e.Session}, nil
}

func (e *Element) GetElements(selector Selector) ([]*Element, error) {
	var results []elementResult

	if err := e.Send("POST", "elements", selector, &results); err != nil {
		return nil, err
	}

	elements := []*Element{}
	for _, result := range results {
		elements = append(elements, &Element{result.ID(), e.Session})
	}

	return elements, nil
}

func (e *Element) GetText() (string, error) {
	var text string
	if err := e.Send("GET", "text", nil, &text); err != nil {
		return "", err
	}
	return text, nil
}

func (e *Element) GetName() (string, error) {
	var name string
	if err := e.Send("GET", "name", nil, &name); err != nil {
		return "", err
	}
	return name, nil
}

func (e *Element) GetAttribute(attribute string) (string, error) {
	var value string
	if err := e.Send("GET", path.Join("attribute", attribute), nil, &value); err != nil {
		return "", err
	}
	return value, nil
}

func (e *Element) GetCSS(property string) (string, error) {
	var value string
	if err := e.Send("GET", path.Join("css", property), nil, &value); err != nil {
		return "", err
	}
	return value, nil
}

func (e *Element) Click() error {
	return e.Send("POST", "click", nil, nil)
}

func (e *Element) Clear() error {
	return e.Send("POST", "clear", nil, nil)
}

func (e *Element) Value(text string) error {
	splitText := strings.Split(text, "")
	request := struct {
		Value []string `json:"value"`
	}{splitText}
	return e.Send("POST", "value", request, nil)
}

func (e *Element) IsSelected() (bool, error) {
	var selected bool
	if err := e.Send("GET", "selected", nil, &selected); err != nil {
		return false, err
	}
	return selected, nil
}

func (e *Element) IsDisplayed() (bool, error) {
	var displayed bool
	if err := e.Send("GET", "displayed", nil, &displayed); err != nil {
		return false, err
	}
	return displayed, nil
}

func (e *Element) IsEnabled() (bool, error) {
	var enabled bool
	if err := e.Send("GET", "enabled", nil, &enabled); err != nil {
		return false, err
	}
	return enabled, nil
}

func (e *Element) Submit() error {
	return e.Send("POST", "submit", nil, nil)
}

func (e *Element) IsEqualTo(other *Element) (bool, error) {
	if other == nil {
		return false, errors.New("nil element is invalid")
	}
	var equal bool
	if err := e.Send("GET", path.Join("equals", other.ID), nil, &equal); err != nil {
		return false, err
	}
	return equal, nil
}

func (e *Element) GetLocation() (x, y int, err error) {
	var location struct {
		X float64 `json:"x"`
		Y float64 `json:"y"`
	}
	if err := e.Send("GET", "location", nil, &location); err != nil {
		return 0, 0, err
	}
	return round(location.X), round(location.Y), nil
}

func (e *Element) GetSize() (width, height int, err error) {
	var size struct {
		Width  float64 `json:"width"`
		Height float64 `json:"height"`
	}
	if err := e.Send("GET", "size", nil, &size); err != nil {
		return 0, 0, err
	}
	return round(size.Width), round(size.Height), nil
}

func round(number float64) int {
	return int(number + 0.5)
}
<|endoftext|>


<commit_before>// Copyright 2016 The CMux Authors. All rights reserved.
//
// 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 cmux

import (
	"fmt"
	"io"
	"net"
	"sync"
	"time"
)

// Matcher matches a connection based on its content.
type Matcher func(io.Reader) bool

// MatchWriter is a match that can also write response (say to do handshake).
type MatchWriter func(io.Writer, io.Reader) bool

// ErrorHandler handles an error and returns whether
// the mux should continue serving the listener.
type ErrorHandler func(error) bool

var _ net.Error = ErrNotMatched{}

// ErrNotMatched is returned whenever a connection is not matched by any of
// the matchers registered in the multiplexer.
type ErrNotMatched struct {
	c net.Conn
}

func (e ErrNotMatched) Error() string {
	return fmt.Sprintf("mux: connection %v not matched by an matcher",
		e.c.RemoteAddr())
}

// Temporary implements the net.Error interface.
func (e ErrNotMatched) Temporary() bool { return true }

// Timeout implements the net.Error interface.
func (e ErrNotMatched) Timeout() bool { return false }

type errListenerClosed string

func (e errListenerClosed) Error() string   { return string(e) }
func (e errListenerClosed) Temporary() bool { return false }
func (e errListenerClosed) Timeout() bool   { return false }

// ErrListenerClosed is returned from muxListener.Accept when the underlying
// listener is closed.
var ErrListenerClosed = errListenerClosed("mux: listener closed")

// for readability of readTimeout
var noTimeout time.Duration

// New instantiates a new connection multiplexer.
func New(l net.Listener) CMux {
	return &cMux{
		root:        l,
		bufLen:      1024,
		errh:        func(_ error) bool { return true },
		donec:       make(chan struct{}),
		readTimeout: noTimeout,
	}
}

// CMux is a multiplexer for network connections.
type CMux interface {
	// Match returns a net.Listener that sees (i.e., accepts) only
	// the connections matched by at least one of the matcher.
	//
	// The order used to call Match determines the priority of matchers.
	Match(...Matcher) net.Listener
	// MatchWithWriters returns a net.Listener that accepts only the
	// connections that matched by at least of the matcher writers.
	//
	// Prefer Matchers over MatchWriters, since the latter can write on the
	// connection before the actual handler.
	//
	// The order used to call Match determines the priority of matchers.
	MatchWithWriters(...MatchWriter) net.Listener
	// Serve starts multiplexing the listener. Serve blocks and perhaps
	// should be invoked concurrently within a go routine.
	Serve() error
	// HandleError registers an error handler that handles listener errors.
	HandleError(ErrorHandler)
	// sets a timeout for the read of matchers
	SetReadTimeout(time.Duration)
}

type matchersListener struct {
	ss []MatchWriter
	l  muxListener
}

type cMux struct {
	root        net.Listener
	bufLen      int
	errh        ErrorHandler
	donec       chan struct{}
	sls         []matchersListener
	readTimeout time.Duration
}

func matchersToMatchWriters(matchers []Matcher) []MatchWriter {
	mws := make([]MatchWriter, 0, len(matchers))
	for _, m := range matchers {
		mws = append(mws, func(w io.Writer, r io.Reader) bool {
			return m(r)
		})
	}
	return mws
}

func (m *cMux) Match(matchers ...Matcher) net.Listener {
	mws := matchersToMatchWriters(matchers)
	return m.MatchWithWriters(mws...)
}

func (m *cMux) MatchWithWriters(matchers ...MatchWriter) net.Listener {
	ml := muxListener{
		Listener: m.root,
		connc:    make(chan net.Conn, m.bufLen),
	}
	m.sls = append(m.sls, matchersListener{ss: matchers, l: ml})
	return ml
}

func (m *cMux) SetReadTimeout(t time.Duration) {
	m.readTimeout = t
}

func (m *cMux) Serve() error {
	var wg sync.WaitGroup

	defer func() {
		close(m.donec)
		wg.Wait()

		for _, sl := range m.sls {
			close(sl.l.connc)
			// Drain the connections enqueued for the listener.
			for c := range sl.l.connc {
				_ = c.Close()
			}
		}
	}()

	for {
		c, err := m.root.Accept()
		if err != nil {
			if !m.handleErr(err) {
				return err
			}
			continue
		}

		wg.Add(1)
		go m.serve(c, m.donec, &wg)
	}
}

func (m *cMux) serve(c net.Conn, donec <-chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	muc := newMuxConn(c)
	if m.readTimeout > noTimeout {
		_ = c.SetReadDeadline(time.Now().Add(m.readTimeout))
	}
	for _, sl := range m.sls {
		for _, s := range sl.ss {
			matched := s(muc.Conn, muc.startSniffing())
			if matched {
				muc.doneSniffing()
				if m.readTimeout > noTimeout {
					_ = c.SetReadDeadline(time.Time{})
				}
				select {
				case sl.l.connc <- muc:
				case <-donec:
					_ = c.Close()
				}
				return
			}
		}
	}

	_ = c.Close()
	err := ErrNotMatched{c: c}
	if !m.handleErr(err) {
		_ = m.root.Close()
	}
}

func (m *cMux) HandleError(h ErrorHandler) {
	m.errh = h
}

func (m *cMux) handleErr(err error) bool {
	if !m.errh(err) {
		return false
	}

	if ne, ok := err.(net.Error); ok {
		return ne.Temporary()
	}

	return false
}

type muxListener struct {
	net.Listener
	connc chan net.Conn
}

func (l muxListener) Accept() (net.Conn, error) {
	c, ok := <-l.connc
	if !ok {
		return nil, ErrListenerClosed
	}
	return c, nil
}

// MuxConn wraps a net.Conn and provides transparent sniffing of connection data.
type MuxConn struct {
	net.Conn
	buf bufferedReader
}

func newMuxConn(c net.Conn) *MuxConn {
	return &MuxConn{
		Conn: c,
		buf:  bufferedReader{source: c},
	}
}

// From the io.Reader documentation:
//
// When Read encounters an error or end-of-file condition after
// successfully reading n > 0 bytes, it returns the number of
// bytes read.  It may return the (non-nil) error from the same call
// or return the error (and n == 0) from a subsequent call.
// An instance of this general case is that a Reader returning
// a non-zero number of bytes at the end of the input stream may
// return either err == EOF or err == nil.  The next Read should
// return 0, EOF.
func (m *MuxConn) Read(p []byte) (int, error) {
	return m.buf.Read(p)
}

func (m *MuxConn) startSniffing() io.Reader {
	m.buf.reset(true)
	return &m.buf
}

func (m *MuxConn) doneSniffing() {
	m.buf.reset(false)
}
<commit_msg>Fix bug matchers are ignored except last one<commit_after>// Copyright 2016 The CMux Authors. All rights reserved.
//
// 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 cmux

import (
	"fmt"
	"io"
	"net"
	"sync"
	"time"
)

// Matcher matches a connection based on its content.
type Matcher func(io.Reader) bool

// MatchWriter is a match that can also write response (say to do handshake).
type MatchWriter func(io.Writer, io.Reader) bool

// ErrorHandler handles an error and returns whether
// the mux should continue serving the listener.
type ErrorHandler func(error) bool

var _ net.Error = ErrNotMatched{}

// ErrNotMatched is returned whenever a connection is not matched by any of
// the matchers registered in the multiplexer.
type ErrNotMatched struct {
	c net.Conn
}

func (e ErrNotMatched) Error() string {
	return fmt.Sprintf("mux: connection %v not matched by an matcher",
		e.c.RemoteAddr())
}

// Temporary implements the net.Error interface.
func (e ErrNotMatched) Temporary() bool { return true }

// Timeout implements the net.Error interface.
func (e ErrNotMatched) Timeout() bool { return false }

type errListenerClosed string

func (e errListenerClosed) Error() string   { return string(e) }
func (e errListenerClosed) Temporary() bool { return false }
func (e errListenerClosed) Timeout() bool   { return false }

// ErrListenerClosed is returned from muxListener.Accept when the underlying
// listener is closed.
var ErrListenerClosed = errListenerClosed("mux: listener closed")

// for readability of readTimeout
var noTimeout time.Duration

// New instantiates a new connection multiplexer.
func New(l net.Listener) CMux {
	return &cMux{
		root:        l,
		bufLen:      1024,
		errh:        func(_ error) bool { return true },
		donec:       make(chan struct{}),
		readTimeout: noTimeout,
	}
}

// CMux is a multiplexer for network connections.
type CMux interface {
	// Match returns a net.Listener that sees (i.e., accepts) only
	// the connections matched by at least one of the matcher.
	//
	// The order used to call Match determines the priority of matchers.
	Match(...Matcher) net.Listener
	// MatchWithWriters returns a net.Listener that accepts only the
	// connections that matched by at least of the matcher writers.
	//
	// Prefer Matchers over MatchWriters, since the latter can write on the
	// connection before the actual handler.
	//
	// The order used to call Match determines the priority of matchers.
	MatchWithWriters(...MatchWriter) net.Listener
	// Serve starts multiplexing the listener. Serve blocks and perhaps
	// should be invoked concurrently within a go routine.
	Serve() error
	// HandleError registers an error handler that handles listener errors.
	HandleError(ErrorHandler)
	// sets a timeout for the read of matchers
	SetReadTimeout(time.Duration)
}

type matchersListener struct {
	ss []MatchWriter
	l  muxListener
}

type cMux struct {
	root        net.Listener
	bufLen      int
	errh        ErrorHandler
	donec       chan struct{}
	sls         []matchersListener
	readTimeout time.Duration
}

func matchersToMatchWriters(matchers []Matcher) []MatchWriter {
	mws := make([]MatchWriter, 0, len(matchers))
	for _, _m := range matchers {
		m := _m
		mws = append(mws, func(w io.Writer, r io.Reader) bool {
			return m(r)
		})
	}
	return mws
}

func (m *cMux) Match(matchers ...Matcher) net.Listener {
	mws := matchersToMatchWriters(matchers)
	return m.MatchWithWriters(mws...)
}

func (m *cMux) MatchWithWriters(matchers ...MatchWriter) net.Listener {
	ml := muxListener{
		Listener: m.root,
		connc:    make(chan net.Conn, m.bufLen),
	}
	m.sls = append(m.sls, matchersListener{ss: matchers, l: ml})
	return ml
}

func (m *cMux) SetReadTimeout(t time.Duration) {
	m.readTimeout = t
}

func (m *cMux) Serve() error {
	var wg sync.WaitGroup

	defer func() {
		close(m.donec)
		wg.Wait()

		for _, sl := range m.sls {
			close(sl.l.connc)
			// Drain the connections enqueued for the listener.
			for c := range sl.l.connc {
				_ = c.Close()
			}
		}
	}()

	for {
		c, err := m.root.Accept()
		if err != nil {
			if !m.handleErr(err) {
				return err
			}
			continue
		}

		wg.Add(1)
		go m.serve(c, m.donec, &wg)
	}
}

func (m *cMux) serve(c net.Conn, donec <-chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done()

	muc := newMuxConn(c)
	if m.readTimeout > noTimeout {
		_ = c.SetReadDeadline(time.Now().Add(m.readTimeout))
	}
	for _, sl := range m.sls {
		for _, s := range sl.ss {
			matched := s(muc.Conn, muc.startSniffing())
			if matched {
				muc.doneSniffing()
				if m.readTimeout > noTimeout {
					_ = c.SetReadDeadline(time.Time{})
				}
				select {
				case sl.l.connc <- muc:
				case <-donec:
					_ = c.Close()
				}
				return
			}
		}
	}

	_ = c.Close()
	err := ErrNotMatched{c: c}
	if !m.handleErr(err) {
		_ = m.root.Close()
	}
}

func (m *cMux) HandleError(h ErrorHandler) {
	m.errh = h
}

func (m *cMux) handleErr(err error) bool {
	if !m.errh(err) {
		return false
	}

	if ne, ok := err.(net.Error); ok {
		return ne.Temporary()
	}

	return false
}

type muxListener struct {
	net.Listener
	connc chan net.Conn
}

func (l muxListener) Accept() (net.Conn, error) {
	c, ok := <-l.connc
	if !ok {
		return nil, ErrListenerClosed
	}
	return c, nil
}

// MuxConn wraps a net.Conn and provides transparent sniffing of connection data.
type MuxConn struct {
	net.Conn
	buf bufferedReader
}

func newMuxConn(c net.Conn) *MuxConn {
	return &MuxConn{
		Conn: c,
		buf:  bufferedReader{source: c},
	}
}

// From the io.Reader documentation:
//
// When Read encounters an error or end-of-file condition after
// successfully reading n > 0 bytes, it returns the number of
// bytes read.  It may return the (non-nil) error from the same call
// or return the error (and n == 0) from a subsequent call.
// An instance of this general case is that a Reader returning
// a non-zero number of bytes at the end of the input stream may
// return either err == EOF or err == nil.  The next Read should
// return 0, EOF.
func (m *MuxConn) Read(p []byte) (int, error) {
	return m.buf.Read(p)
}

func (m *MuxConn) startSniffing() io.Reader {
	m.buf.reset(true)
	return &m.buf
}

func (m *MuxConn) doneSniffing() {
	m.buf.reset(false)
}
<|endoftext|>


<commit_before>package epp

import (
	"crypto/tls"
	"encoding/binary"
	"encoding/xml"
	"io"
	"net"
	"os"

	"github.com/davecgh/go-spew/spew"
	"github.com/wsxiaoys/terminal/color"
)

// Conn represents a single connection to an EPP server.
// This implementation is not safe for concurrent use.
type Conn struct {
	// Greeting holds the last recieved greeting message from the server,
	// indicating server name, status, data policy and capabilities.
	Greeting *Greeting

	net.Conn
	reqID uint64
}

// Dial connects to an EPP server via TCP.
// Returns an error if unable to connect, including certificate mismatch errors.
func Dial(addr string) (*Conn, error) {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}
	return newConn(conn)
}

// DialTLS connects to an EPP server via TLS.
// Returns an error if unable to connect, including certificate mismatch errors.
func DialTLS(addr string, cfg *tls.Config) (*Conn, error) {
	conn, err := tls.Dial("tcp", addr, cfg)
	if err != nil {
		return nil, err
	}
	return newConn(conn)
}

func newConn(conn net.Conn) (*Conn, error) {
	c := &Conn{Conn: conn}
	err := c.readGreeting()
	if err != nil {
		c.Close()
		return nil, err
	}
	return c, nil
}

// WriteMsg serializes msg into XML and writes it to c.
func (c *Conn) WriteMsg(msg *Msg) error {
	data, err := xml.Marshal(msg)
	if err != nil {
		return err
	}
	// color.Printf("@{|}<!-- REQUEST -->\n%s\n", string(data))
	return c.WriteDataUnit(data)
}

// WriteDataUnit writes a slice of bytes to c.
// Bytes written are prefixed with 32-bit header specifying the total size
// of the data unit (message + 4 byte header), in network (big-endian) order.
// http://www.ietf.org/rfc/rfc4934.txt
func (c *Conn) WriteDataUnit(p []byte) error {
	s := uint32(4 + len(xml.Header) + len(p))
	err := binary.Write(c.Conn, binary.BigEndian, s)
	if err != nil {
		return err
	}
	_, err = c.Conn.Write([]byte(xml.Header))
	if err != nil {
		return err
	}
	_, err = c.Conn.Write(p)
	return err
}

// ReadResponse reads a single EPP response message. It returns an error
// if the EPP message does not contain a valid response or contains an error.
func (c *Conn) ReadResponse() (res *Response, err error) {
	msg := Msg{}
	err = c.ReadMsg(&msg)
	if err != nil {
		return nil, err
	}
	return msg.GetResponse()
}

// ReadMsg reads a single EPP message from c and parses the XML into msg.
func (c *Conn) ReadMsg(msg *Msg) error {
	data, err := c.ReadDataUnit()
	if err != nil {
		return err
	}
	color.Printf("@{c}<!-- RESPONSE -->\n%s", string(data))
	err = xml.Unmarshal(data, msg)
	if err != nil {
		return err
	}
	color.Fprintf(os.Stderr, "@{y}%s\n", spew.Sprintf("%+v", msg))
	return nil
}

// ReadDataUnit reads a single EPP message from c.
// It returns the bytes read and/or an error.
// FIXME: allocate a single buffer per Conn to reduce GC pressure?
func (c *Conn) ReadDataUnit() (data []byte, err error) {
	var s uint32
	err = binary.Read(c.Conn, binary.BigEndian, &s)
	if err != nil {
		return
	}
	data = make([]byte, s)
	n, err := c.Conn.Read(data)
	if err != nil {
		return
	}
	if 4+n != int(s) {
		return data, io.ErrNoProgress
	}
	return data, nil
}
<commit_msg>epp: *Msg -> Message interface type<commit_after>package epp

import (
	"crypto/tls"
	"encoding/binary"
	"encoding/xml"
	"io"
	"net"
	"os"

	"github.com/davecgh/go-spew/spew"
	"github.com/wsxiaoys/terminal/color"
)

// Conn represents a single connection to an EPP server.
// This implementation is not safe for concurrent use.
type Conn struct {
	// Greeting holds the last recieved greeting message from the server,
	// indicating server name, status, data policy and capabilities.
	Greeting *Greeting

	net.Conn
	reqID uint64
}

// Dial connects to an EPP server via TCP.
// Returns an error if unable to connect, including certificate mismatch errors.
func Dial(addr string) (*Conn, error) {
	conn, err := net.Dial("tcp", addr)
	if err != nil {
		return nil, err
	}
	return newConn(conn)
}

// DialTLS connects to an EPP server via TLS.
// Returns an error if unable to connect, including certificate mismatch errors.
func DialTLS(addr string, cfg *tls.Config) (*Conn, error) {
	conn, err := tls.Dial("tcp", addr, cfg)
	if err != nil {
		return nil, err
	}
	return newConn(conn)
}

func newConn(conn net.Conn) (*Conn, error) {
	c := &Conn{Conn: conn}
	err := c.readGreeting()
	if err != nil {
		c.Close()
		return nil, err
	}
	return c, nil
}

// WriteMsg serializes msg into XML and writes it to c.
func (c *Conn) WriteMsg(msg Message) error {
	data, err := xml.Marshal(msg)
	if err != nil {
		return err
	}
	// color.Printf("@{|}<!-- REQUEST -->\n%s\n", string(data))
	return c.WriteDataUnit(data)
}

// WriteDataUnit writes a slice of bytes to c.
// Bytes written are prefixed with 32-bit header specifying the total size
// of the data unit (message + 4 byte header), in network (big-endian) order.
// http://www.ietf.org/rfc/rfc4934.txt
func (c *Conn) WriteDataUnit(p []byte) error {
	s := uint32(4 + len(xml.Header) + len(p))
	err := binary.Write(c.Conn, binary.BigEndian, s)
	if err != nil {
		return err
	}
	_, err = c.Conn.Write([]byte(xml.Header))
	if err != nil {
		return err
	}
	_, err = c.Conn.Write(p)
	return err
}

// ReadResponse reads a single EPP response message. It returns an error
// if the EPP message does not contain a valid response or contains an error.
func (c *Conn) ReadResponse() (res *Response, err error) {
	msg := Msg{}
	err = c.ReadMsg(&msg)
	if err != nil {
		return nil, err
	}
	return msg.GetResponse()
}

// ReadMsg reads a single EPP message from c and parses the XML into msg.
func (c *Conn) ReadMsg(msg Message) error {
	data, err := c.ReadDataUnit()
	if err != nil {
		return err
	}
	color.Printf("@{c}<!-- RESPONSE -->\n%s", string(data))
	err = xml.Unmarshal(data, msg)
	if err != nil {
		return err
	}
	color.Fprintf(os.Stderr, "@{y}%s\n", spew.Sprintf("%+v", msg))
	return nil
}

// ReadDataUnit reads a single EPP message from c.
// It returns the bytes read and/or an error.
// FIXME: allocate a single buffer per Conn to reduce GC pressure?
func (c *Conn) ReadDataUnit() (data []byte, err error) {
	var s uint32
	err = binary.Read(c.Conn, binary.BigEndian, &s)
	if err != nil {
		return
	}
	data = make([]byte, s)
	n, err := c.Conn.Read(data)
	if err != nil {
		return
	}
	if 4+n != int(s) {
		return data, io.ErrNoProgress
	}
	return data, nil
}
<|endoftext|>