<commit_before>package glut

import (
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"

	"github.com/256dpi/fire/coal"
)

// Lock will lock the specified value using the specified token for the
// specified duration. Lock may create a new value in the process and lock it
// right away. It will also update the deadline of the value if TTL is set.
func Lock(store *coal.Store, component, name string, token coal.ID, timeout, ttl time.Duration) (bool, error) {
	// check token
	if token.IsZero() {
		return false, fmt.Errorf("invalid token")
	}

	// check timeout
	if timeout == 0 {
		return false, fmt.Errorf("invalid timeout")
	}

	// check ttl
	if ttl > 0 && ttl < timeout {
		return false, fmt.Errorf("invalid ttl")
	}

	// prepare deadline
	var deadline *time.Time
	if ttl > 0 {
		deadline = coal.T(time.Now().Add(ttl))
	}

	// get locked
	locked := time.Now().Add(timeout)

	// prepare bulk
	res, err := store.C(&Value{}).BulkWrite(nil, []mongo.WriteModel{
		// init value
		mongo.NewUpdateOneModel().SetFilter(bson.M{
			coal.F(&Value{}, "Component"): component,
			coal.F(&Value{}, "Name"):      name,
		}).SetUpdate(bson.M{
			"$setOnInsert": bson.M{
				coal.F(&Value{}, "Locked"):   locked,
				coal.F(&Value{}, "Token"):    token,
				coal.F(&Value{}, "Deadline"): deadline,
			},
		}).SetUpsert(true),

		// lock value
		mongo.NewUpdateOneModel().SetFilter(bson.M{
			"$and": []bson.M{
				{
					coal.F(&Value{}, "Component"): component,
					coal.F(&Value{}, "Name"):      name,
				},
				{
					"$or": []bson.M{
						// unlocked
						{
							coal.F(&Value{}, "Token"): nil,
						},
						// lock timed out
						{
							coal.F(&Value{}, "Locked"): bson.M{
								"$lt": time.Now(),
							},
						},
						// we have the lock
						{
							coal.F(&Value{}, "Token"): token,
						},
					},
				},
			},
		}).SetUpdate(bson.M{
			"$set": bson.M{
				coal.F(&Value{}, "Locked"):   locked,
				coal.F(&Value{}, "Token"):    token,
				coal.F(&Value{}, "Deadline"): deadline,
			},
		}),
	})
	if err != nil {
		return false, err
	}

	return res.UpsertedCount > 0 || res.ModifiedCount > 0, nil
}

// SetLocked will update the specified value only if the value is locked by the
// specified token.
func SetLocked(store *coal.Store, component, name string, data coal.Map, token coal.ID) (bool, error) {
	// check token
	if token.IsZero() {
		return false, fmt.Errorf("invalid token")
	}

	// update value
	res, err := store.C(&Value{}).UpdateOne(nil, bson.M{
		coal.F(&Value{}, "Component"): component,
		coal.F(&Value{}, "Name"):      name,
		coal.F(&Value{}, "Token"):     token,
		coal.F(&Value{}, "Locked"): bson.M{
			"$gt": time.Now(),
		},
	}, bson.M{
		"$set": bson.M{
			coal.F(&Value{}, "Data"): data,
		},
	})
	if err != nil {
		return false, err
	}

	return res.ModifiedCount > 0, nil
}

// GetLocked will load the contents of the value with the specified name only
// if the value is locked by the specified token.
func GetLocked(store *coal.Store, component, name string, token coal.ID) (coal.Map, bool, error) {
	// find value
	var value *Value
	err := store.C(&Value{}).FindOne(nil, bson.M{
		coal.F(&Value{}, "Component"): component,
		coal.F(&Value{}, "Name"):      name,
		coal.F(&Value{}, "Token"):     token,
		coal.F(&Value{}, "Locked"): bson.M{
			"$gt": time.Now(),
		},
	}).Decode(&value)
	if err == mongo.ErrNoDocuments {
		return nil, false, nil
	} else if err != nil {
		return nil, false, err
	}

	return value.Data, true, nil
}

// DelLocked will update the specified value only if the value is locked by the
// specified token.
func DelLocked(store *coal.Store, component, name string, token coal.ID) (bool, error) {
	// check token
	if token.IsZero() {
		return false, fmt.Errorf("invalid token")
	}

	// delete value
	res, err := store.C(&Value{}).DeleteOne(nil, bson.M{
		coal.F(&Value{}, "Component"): component,
		coal.F(&Value{}, "Name"):      name,
		coal.F(&Value{}, "Token"):     token,
		coal.F(&Value{}, "Locked"): bson.M{
			"$gt": time.Now(),
		},
	})
	if err != nil {
		return false, err
	}

	return res.DeletedCount > 0, nil
}

// Unlock will unlock the specified value if the provided token does match. It
// will also update the deadline of the value if TTL is set.
func Unlock(store *coal.Store, component, name string, token coal.ID, ttl time.Duration) (bool, error) {
	// check token
	if token.IsZero() {
		return false, fmt.Errorf("invalid token")
	}

	// prepare deadline
	var deadline *time.Time
	if ttl > 0 {
		deadline = coal.T(time.Now().Add(ttl))
	}

	// replace value
	res, err := store.C(&Value{}).UpdateOne(nil, bson.M{
		coal.F(&Value{}, "Component"): component,
		coal.F(&Value{}, "Name"):      name,
		coal.F(&Value{}, "Token"):     token,
		coal.F(&Value{}, "Locked"): bson.M{
			"$gt": time.Now(),
		},
	}, bson.M{
		"$set": bson.M{
			coal.F(&Value{}, "Locked"):   nil,
			coal.F(&Value{}, "Token"):    nil,
			coal.F(&Value{}, "Deadline"): deadline,
		},
	})
	if err != nil {
		return false, err
	}

	return res.ModifiedCount > 0, nil
}
<commit_msg>fixed locking race conditions<commit_after>package glut

import (
	"fmt"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"

	"github.com/256dpi/fire/coal"
)

// Lock will lock the specified value using the specified token for the
// specified duration. Lock may create a new value in the process and lock it
// right away. It will also update the deadline of the value if TTL is set.
func Lock(store *coal.Store, component, name string, token coal.ID, timeout, ttl time.Duration) (bool, error) {
	// check token
	if token.IsZero() {
		return false, fmt.Errorf("invalid token")
	}

	// check timeout
	if timeout == 0 {
		return false, fmt.Errorf("invalid timeout")
	}

	// check ttl
	if ttl > 0 && ttl < timeout {
		return false, fmt.Errorf("invalid ttl")
	}

	// prepare deadline
	var deadline *time.Time
	if ttl > 0 {
		deadline = coal.T(time.Now().Add(ttl))
	}

	// get locked
	locked := time.Now().Add(timeout)

	// ensure value
	res, err := store.C(&Value{}).UpdateOne(nil, bson.M{
		coal.F(&Value{}, "Component"): component,
		coal.F(&Value{}, "Name"):      name,
	}, bson.M{
		"$setOnInsert": bson.M{
			coal.F(&Value{}, "Locked"):   locked,
			coal.F(&Value{}, "Token"):    token,
			coal.F(&Value{}, "Deadline"): deadline,
		},
	}, options.Update().SetUpsert(true))
	if err != nil {
		return false, err
	}

	// check if locked by upsert
	if res.UpsertedCount > 0 {
		return true, nil
	}

	// lock value
	res, err = store.C(&Value{}).UpdateOne(nil, bson.M{
		"$and": []bson.M{
			{
				coal.F(&Value{}, "Component"): component,
				coal.F(&Value{}, "Name"):      name,
			},
			{
				"$or": []bson.M{
					// unlocked
					{
						coal.F(&Value{}, "Token"): nil,
					},
					// lock timed out
					{
						coal.F(&Value{}, "Locked"): bson.M{
							"$lt": time.Now(),
						},
					},
					// we have the lock
					{
						coal.F(&Value{}, "Token"): token,
					},
				},
			},
		},
	}, bson.M{
		"$set": bson.M{
			coal.F(&Value{}, "Locked"):   locked,
			coal.F(&Value{}, "Token"):    token,
			coal.F(&Value{}, "Deadline"): deadline,
		},
	})
	if err != nil {
		return false, err
	}

	return res.ModifiedCount > 0, nil
}

// SetLocked will update the specified value only if the value is locked by the
// specified token.
func SetLocked(store *coal.Store, component, name string, data coal.Map, token coal.ID) (bool, error) {
	// check token
	if token.IsZero() {
		return false, fmt.Errorf("invalid token")
	}

	// update value
	res, err := store.C(&Value{}).UpdateOne(nil, bson.M{
		coal.F(&Value{}, "Component"): component,
		coal.F(&Value{}, "Name"):      name,
		coal.F(&Value{}, "Token"):     token,
		coal.F(&Value{}, "Locked"): bson.M{
			"$gt": time.Now(),
		},
	}, bson.M{
		"$set": bson.M{
			coal.F(&Value{}, "Data"): data,
		},
	})
	if err != nil {
		return false, err
	}

	return res.ModifiedCount > 0, nil
}

// GetLocked will load the contents of the value with the specified name only
// if the value is locked by the specified token.
func GetLocked(store *coal.Store, component, name string, token coal.ID) (coal.Map, bool, error) {
	// find value
	var value *Value
	err := store.C(&Value{}).FindOne(nil, bson.M{
		coal.F(&Value{}, "Component"): component,
		coal.F(&Value{}, "Name"):      name,
		coal.F(&Value{}, "Token"):     token,
		coal.F(&Value{}, "Locked"): bson.M{
			"$gt": time.Now(),
		},
	}).Decode(&value)
	if err == mongo.ErrNoDocuments {
		return nil, false, nil
	} else if err != nil {
		return nil, false, err
	}

	return value.Data, true, nil
}

// DelLocked will update the specified value only if the value is locked by the
// specified token.
func DelLocked(store *coal.Store, component, name string, token coal.ID) (bool, error) {
	// check token
	if token.IsZero() {
		return false, fmt.Errorf("invalid token")
	}

	// delete value
	res, err := store.C(&Value{}).DeleteOne(nil, bson.M{
		coal.F(&Value{}, "Component"): component,
		coal.F(&Value{}, "Name"):      name,
		coal.F(&Value{}, "Token"):     token,
		coal.F(&Value{}, "Locked"): bson.M{
			"$gt": time.Now(),
		},
	})
	if err != nil {
		return false, err
	}

	return res.DeletedCount > 0, nil
}

// Unlock will unlock the specified value if the provided token does match. It
// will also update the deadline of the value if TTL is set.
func Unlock(store *coal.Store, component, name string, token coal.ID, ttl time.Duration) (bool, error) {
	// check token
	if token.IsZero() {
		return false, fmt.Errorf("invalid token")
	}

	// prepare deadline
	var deadline *time.Time
	if ttl > 0 {
		deadline = coal.T(time.Now().Add(ttl))
	}

	// replace value
	res, err := store.C(&Value{}).UpdateOne(nil, bson.M{
		coal.F(&Value{}, "Component"): component,
		coal.F(&Value{}, "Name"):      name,
		coal.F(&Value{}, "Token"):     token,
		coal.F(&Value{}, "Locked"): bson.M{
			"$gt": time.Now(),
		},
	}, bson.M{
		"$set": bson.M{
			coal.F(&Value{}, "Locked"):   nil,
			coal.F(&Value{}, "Token"):    nil,
			coal.F(&Value{}, "Deadline"): deadline,
		},
	})
	if err != nil {
		return false, err
	}

	return res.ModifiedCount > 0, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"koding/tools/config"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"text/template"

	"github.com/koding/kite/cmd/build"
)

var (
	profile = flag.String("c", "", "Define config profile to be included")
	region  = flag.String("r", "", "Define region profile to be included")

	// Proxy only
	proxy = flag.String("p", "", "Select user proxy or koding proxy")
)

type pkg struct {
	appName       string
	importPath    string
	files         []string
	version       string
	upstartScript string
}

func main() {
	flag.Parse()
	if *profile == "" || *region == "" {
		fmt.Println("Please define config -c and region -r")
		os.Exit(1)
	}

	fmt.Println(*profile, *region, *proxy)

	err := buildPackages()
	if err != nil {
		fmt.Println(err)
	}
}

func buildPackages() error {
	if err := buildKontrolProxy(); err != nil {
		return err
	}

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

	return nil
}

func buildOsKite() error {
	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		return errors.New("GOPATH is not set")
	}

	oskitePath := "koding/kites/os"
	temps := struct {
		Profile string
		Region  string
	}{
		Profile: *profile,
		Region:  *region,
	}

	var files = make([]string, 0)
	files = append(files, filepath.Join(gopath, "src", oskitePath, "files"))

	// change our upstartscript because it's a template
	oskiteUpstart := filepath.Join(gopath, "src", oskitePath, "files/oskite.conf")
	configUpstart, err := prepareUpstart(oskiteUpstart, temps)
	if err != nil {
		return err
	}
	defer os.Remove(configUpstart)

	oskite := pkg{
		appName:       "oskite",
		importPath:    oskitePath,
		files:         files,
		version:       "0.1.0",
		upstartScript: configUpstart,
	}

	return oskite.build()
}

func buildKontrolProxy() error {
	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		return errors.New("GOPATH is not set")
	}

	kdproxyPath := "koding/kontrol/kontrolproxy"

	// include certs
	if *proxy == "" {
		return errors.New("Please define proxy target. Example: -p koding or -p user")
	}

	temps := struct {
		Profile   string
		Region    string
		UserProxy string
	}{
		Profile: *profile,
		Region:  *region,
	}

	var files = make([]string, 0)
	switch *proxy {
	case "koding":
		files = append(files, "certs/koding_com_cert.pem", "certs/koding_com_key.pem")
	case "y":
		files = append(files, "certs/y_koding_com_cert.pem", "certs/y_koding_com_key.pem")
	case "x":
		files = append(files, "certs/x_koding_com_cert.pem", "certs/x_koding_com_key.pem")
	case "user":
		temps.UserProxy = "-v"
		files = append(files, "certs/kd_io_cert.pem", "certs/kd_io_key.pem")
	default:
		return errors.New("-p can accept either user or koding")
	}

	files = append(files, filepath.Join(gopath, "src", kdproxyPath, "files"))

	// change our upstartscript because it's a template
	kdproxyUpstart := filepath.Join(gopath, "src", kdproxyPath, "files/kontrolproxy.conf")
	configUpstart, err := prepareUpstart(kdproxyUpstart, temps)
	if err != nil {
		return err
	}
	defer os.Remove(configUpstart)

	kontrolproxy := pkg{
		appName:       "kontrolproxy",
		importPath:    kdproxyPath,
		files:         files,
		version:       "0.0.3",
		upstartScript: configUpstart,
	}

	return kontrolproxy.build()
}

func (p *pkg) build() error {
	fmt.Printf("building '%s' for config '%s' and region '%s'\n", p.appName, *profile, *region)

	// prepare config folder
	tempDir, err := ioutil.TempDir(".", "gopackage_")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tempDir)

	configDir := filepath.Join(tempDir, "config")
	os.MkdirAll(configDir, 0755)

	// koding-config-manager needs it
	err = ioutil.WriteFile("VERSION", []byte(p.version), 0755)
	if err != nil {
		return err
	}
	defer os.Remove("VERSION")

	c, err := config.ReadConfigManager(*profile)
	if err != nil {
		return err
	}

	configPretty, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return err
	}

	configFile := filepath.Join(configDir, fmt.Sprintf("main.%s.json", *profile))
	err = ioutil.WriteFile(configFile, configPretty, 0644)
	if err != nil {
		return err
	}

	p.files = append(p.files, configDir)

	// Now it's time to build
	if runtime.GOOS != "linux" {
		return errors.New("Not supported. Please run on a linux machine.")
	}

	deb := &build.Deb{
		AppName:       p.appName,
		Version:       p.version,
		ImportPath:    p.importPath,
		Files:         strings.Join(p.files, ","),
		InstallPrefix: "opt/kite",
		UpstartScript: p.upstartScript,
	}

	debFile, err := deb.Build()
	if err != nil {
		log.Println("linux:", err)
	}

	// rename file to see for which region and env it is created
	oldname := debFile
	newname := fmt.Sprintf("%s_%s_%s-%s_%s.deb", p.appName, p.version, *profile, *region, deb.Arch)

	if err := os.Rename(oldname, newname); err != nil {
		return err
	}

	fmt.Printf("success '%s' is ready. Some helpful commands for you:\n\n", newname)
	fmt.Printf("  show deb content   : dpkg -c %s\n", newname)
	fmt.Printf("  show basic info    : dpkg -f %s\n", newname)
	fmt.Printf("  install to machine : dpkg -i %s\n\n", newname)

	return nil
}

func prepareUpstart(path string, v interface{}) (string, error) {
	file, err := ioutil.TempFile(".", "gopackage_")
	if err != nil {
		return "", err
	}
	defer file.Close()

	t, err := template.ParseFiles(path)
	if err != nil {
		return "", err
	}

	if err := t.Execute(file, v); err != nil {
		return "", err
	}

	return file.Name(), nil
}
<commit_msg>oskite: v0.1.1<commit_after>package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"koding/tools/config"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"text/template"

	"github.com/koding/kite/cmd/build"
)

var (
	profile = flag.String("c", "", "Define config profile to be included")
	region  = flag.String("r", "", "Define region profile to be included")

	// Proxy only
	proxy = flag.String("p", "", "Select user proxy or koding proxy")
)

type pkg struct {
	appName       string
	importPath    string
	files         []string
	version       string
	upstartScript string
}

func main() {
	flag.Parse()
	if *profile == "" || *region == "" {
		fmt.Println("Please define config -c and region -r")
		os.Exit(1)
	}

	fmt.Println(*profile, *region, *proxy)

	err := buildPackages()
	if err != nil {
		fmt.Println(err)
	}
}

func buildPackages() error {
	if err := buildKontrolProxy(); err != nil {
		return err
	}

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

	return nil
}

func buildOsKite() error {
	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		return errors.New("GOPATH is not set")
	}

	oskitePath := "koding/kites/os"
	temps := struct {
		Profile string
		Region  string
	}{
		Profile: *profile,
		Region:  *region,
	}

	var files = make([]string, 0)
	files = append(files, filepath.Join(gopath, "src", oskitePath, "files"))

	// change our upstartscript because it's a template
	oskiteUpstart := filepath.Join(gopath, "src", oskitePath, "files/oskite.conf")
	configUpstart, err := prepareUpstart(oskiteUpstart, temps)
	if err != nil {
		return err
	}
	defer os.Remove(configUpstart)

	oskite := pkg{
		appName:       "oskite",
		importPath:    oskitePath,
		files:         files,
		version:       "0.1.1",
		upstartScript: configUpstart,
	}

	return oskite.build()
}

func buildKontrolProxy() error {
	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		return errors.New("GOPATH is not set")
	}

	kdproxyPath := "koding/kontrol/kontrolproxy"

	// include certs
	if *proxy == "" {
		return errors.New("Please define proxy target. Example: -p koding or -p user")
	}

	temps := struct {
		Profile   string
		Region    string
		UserProxy string
	}{
		Profile: *profile,
		Region:  *region,
	}

	var files = make([]string, 0)
	switch *proxy {
	case "koding":
		files = append(files, "certs/koding_com_cert.pem", "certs/koding_com_key.pem")
	case "y":
		files = append(files, "certs/y_koding_com_cert.pem", "certs/y_koding_com_key.pem")
	case "x":
		files = append(files, "certs/x_koding_com_cert.pem", "certs/x_koding_com_key.pem")
	case "user":
		temps.UserProxy = "-v"
		files = append(files, "certs/kd_io_cert.pem", "certs/kd_io_key.pem")
	default:
		return errors.New("-p can accept either user or koding")
	}

	files = append(files, filepath.Join(gopath, "src", kdproxyPath, "files"))

	// change our upstartscript because it's a template
	kdproxyUpstart := filepath.Join(gopath, "src", kdproxyPath, "files/kontrolproxy.conf")
	configUpstart, err := prepareUpstart(kdproxyUpstart, temps)
	if err != nil {
		return err
	}
	defer os.Remove(configUpstart)

	kontrolproxy := pkg{
		appName:       "kontrolproxy",
		importPath:    kdproxyPath,
		files:         files,
		version:       "0.0.3",
		upstartScript: configUpstart,
	}

	return kontrolproxy.build()
}

func (p *pkg) build() error {
	fmt.Printf("building '%s' for config '%s' and region '%s'\n", p.appName, *profile, *region)

	// prepare config folder
	tempDir, err := ioutil.TempDir(".", "gopackage_")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tempDir)

	configDir := filepath.Join(tempDir, "config")
	os.MkdirAll(configDir, 0755)

	// koding-config-manager needs it
	err = ioutil.WriteFile("VERSION", []byte(p.version), 0755)
	if err != nil {
		return err
	}
	defer os.Remove("VERSION")

	c, err := config.ReadConfigManager(*profile)
	if err != nil {
		return err
	}

	configPretty, err := json.MarshalIndent(c, "", "  ")
	if err != nil {
		return err
	}

	configFile := filepath.Join(configDir, fmt.Sprintf("main.%s.json", *profile))
	err = ioutil.WriteFile(configFile, configPretty, 0644)
	if err != nil {
		return err
	}

	p.files = append(p.files, configDir)

	// Now it's time to build
	if runtime.GOOS != "linux" {
		return errors.New("Not supported. Please run on a linux machine.")
	}

	deb := &build.Deb{
		AppName:       p.appName,
		Version:       p.version,
		ImportPath:    p.importPath,
		Files:         strings.Join(p.files, ","),
		InstallPrefix: "opt/kite",
		UpstartScript: p.upstartScript,
	}

	debFile, err := deb.Build()
	if err != nil {
		log.Println("linux:", err)
	}

	// rename file to see for which region and env it is created
	oldname := debFile
	newname := fmt.Sprintf("%s_%s_%s-%s_%s.deb", p.appName, p.version, *profile, *region, deb.Arch)

	if err := os.Rename(oldname, newname); err != nil {
		return err
	}

	fmt.Printf("success '%s' is ready. Some helpful commands for you:\n\n", newname)
	fmt.Printf("  show deb content   : dpkg -c %s\n", newname)
	fmt.Printf("  show basic info    : dpkg -f %s\n", newname)
	fmt.Printf("  install to machine : dpkg -i %s\n\n", newname)

	return nil
}

func prepareUpstart(path string, v interface{}) (string, error) {
	file, err := ioutil.TempFile(".", "gopackage_")
	if err != nil {
		return "", err
	}
	defer file.Close()

	t, err := template.ParseFiles(path)
	if err != nil {
		return "", err
	}

	if err := t.Execute(file, v); err != nil {
		return "", err
	}

	return file.Name(), nil
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2013 Matt Jibson <matt.jibson@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package goapp

import (
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"time"

	"appengine"
	"appengine/datastore"
	"github.com/MiniProfiler/go/miniprofiler"
	mpg "github.com/MiniProfiler/go/miniprofiler_gae"
	"github.com/gorilla/mux"
	"github.com/mjibson/goon"
)

var router = new(mux.Router)
var templates *template.Template

func init() {
	var err error

	if templates, err = template.New("").Funcs(funcs).
		ParseFiles(
		"templates/base.html",
		"templates/admin-all-feeds.html",
		"templates/admin-date-formats.html",
		"templates/admin-feed.html",
		"templates/admin-stats.html",
		"templates/admin-user.html",
	); err != nil {
		log.Fatal(err)
	}

	router.Handle("/", mpg.NewHandler(Main)).Name("main")
	router.Handle("/login/google", mpg.NewHandler(LoginGoogle)).Name("login-google")
	router.Handle("/logout", mpg.NewHandler(Logout)).Name("logout")
	router.Handle("/push/{feed}", mpg.NewHandler(SubscribeCallback))
	router.Handle("/push", mpg.NewHandler(SubscribeCallback)).Name("subscribe-callback")
	router.Handle("/tasks/import-opml", mpg.NewHandler(ImportOpmlTask)).Name("import-opml-task")
	router.Handle("/tasks/subscribe-feed", mpg.NewHandler(SubscribeFeed)).Name("subscribe-feed")
	router.Handle("/tasks/update-feed-last", mpg.NewHandler(UpdateFeedLast)).Name("update-feed-last")
	router.Handle("/tasks/update-feed-manual", mpg.NewHandler(UpdateFeed)).Name("update-feed-manual")
	router.Handle("/tasks/update-feed", mpg.NewHandler(UpdateFeed)).Name("update-feed")
	router.Handle("/tasks/update-feeds", mpg.NewHandler(UpdateFeeds)).Name("update-feeds")
	router.Handle("/user/add-subscription", mpg.NewHandler(AddSubscription)).Name("add-subscription")
	router.Handle("/user/clear-feeds", mpg.NewHandler(ClearFeeds)).Name("clear-feeds")
	router.Handle("/user/delete-account", mpg.NewHandler(DeleteAccount)).Name("delete-account")
	router.Handle("/user/export-opml", mpg.NewHandler(ExportOpml)).Name("export-opml")
	router.Handle("/user/feed-history", mpg.NewHandler(FeedHistory)).Name("feed-history")
	router.Handle("/user/get-contents", mpg.NewHandler(GetContents)).Name("get-contents")
	router.Handle("/user/get-feed", mpg.NewHandler(GetFeed)).Name("get-feed")
	router.Handle("/user/import/opml", mpg.NewHandler(ImportOpml)).Name("import-opml")
	router.Handle("/user/list-feeds", mpg.NewHandler(ListFeeds)).Name("list-feeds")
	router.Handle("/user/mark-read", mpg.NewHandler(MarkRead)).Name("mark-read")
	router.Handle("/user/mark-unread", mpg.NewHandler(MarkUnread)).Name("mark-unread")
	router.Handle("/user/save-options", mpg.NewHandler(SaveOptions)).Name("save-options")
	router.Handle("/user/upload-opml", mpg.NewHandler(UploadOpml)).Name("upload-opml")

	router.Handle("/admin/all-feeds", mpg.NewHandler(AllFeeds)).Name("all-feeds")
	router.Handle("/admin/all-feeds-opml", mpg.NewHandler(AllFeedsOpml)).Name("all-feeds-opml")
	router.Handle("/admin/user", mpg.NewHandler(AdminUser)).Name("admin-user")
	router.Handle("/date-formats", mpg.NewHandler(AdminDateFormats)).Name("admin-date-formats")
	router.Handle("/admin/feed", mpg.NewHandler(AdminFeed)).Name("admin-feed")
	router.Handle("/admin/stats", mpg.NewHandler(AdminStats)).Name("admin-stats")
	router.Handle("/admin/update-feed", mpg.NewHandler(AdminUpdateFeed)).Name("admin-update-feed")
	router.Handle("/tasks/cfixer", mpg.NewHandler(CFixer))
	router.Handle("/tasks/cfix", mpg.NewHandler(CFix))
	router.Handle("/user/charge", mpg.NewHandler(Charge)).Name("charge")
	router.Handle("/user/account", mpg.NewHandler(Account)).Name("account")
	router.Handle("/user/uncheckout", mpg.NewHandler(Uncheckout)).Name("uncheckout")

	http.Handle("/", router)

	miniprofiler.ToggleShortcut = "Alt+C"
	miniprofiler.Position = "right"
}

func Main(c mpg.Context, w http.ResponseWriter, r *http.Request) {
	if err := templates.ExecuteTemplate(w, "base.html", includes(c, w, r)); err != nil {
		c.Errorf("%v", err)
		serveError(w, err)
	}
	return
}

func addFeed(c mpg.Context, userid string, outline *OpmlOutline) error {
	gn := goon.FromContext(c)
	o := outline.Outline[0]
	c.Infof("adding feed %v to user %s", o.XmlUrl, userid)
	fu, ferr := url.Parse(o.XmlUrl)
	if ferr != nil {
		return ferr
	}
	fu.Fragment = ""
	o.XmlUrl = fu.String()

	f := Feed{Url: o.XmlUrl}
	if err := gn.Get(&f); err == datastore.ErrNoSuchEntity {
		if feed, stories, err := fetchFeed(c, o.XmlUrl, o.XmlUrl); err != nil {
			return fmt.Errorf("could not add feed %s: %v", o.XmlUrl, err)
		} else {
			f = *feed
			f.Updated = time.Time{}
			f.Checked = f.Updated
			f.NextUpdate = f.Updated
			f.LastViewed = time.Now()
			gn.Put(&f)
			for _, s := range stories {
				s.Created = s.Published
			}
			if err := updateFeed(c, f.Url, feed, stories, false, false, false); err != nil {
				return err
			}

			o.XmlUrl = feed.Url
			o.HtmlUrl = feed.Link
			if o.Title == "" {
				o.Title = feed.Title
			}
		}
	} else if err != nil {
		return err
	} else {
		o.HtmlUrl = f.Link
		if o.Title == "" {
			o.Title = f.Title
		}
	}
	o.Text = ""

	return nil
}

func mergeUserOpml(c appengine.Context, ud *UserData, outlines ...*OpmlOutline) error {
	var fs Opml
	json.Unmarshal(ud.Opml, &fs)
	urls := make(map[string]bool)

	for _, o := range fs.Outline {
		if o.XmlUrl != "" {
			urls[o.XmlUrl] = true
		} else {
			for _, so := range o.Outline {
				urls[so.XmlUrl] = true
			}
		}
	}

	mergeOutline := func(label string, outline *OpmlOutline) {
		if _, present := urls[outline.XmlUrl]; present {
			return
		} else {
			urls[outline.XmlUrl] = true

			if label == "" {
				fs.Outline = append(fs.Outline, outline)
			} else {
				done := false
				for _, ol := range fs.Outline {
					if ol.Title == label && ol.XmlUrl == "" {
						ol.Outline = append(ol.Outline, outline)
						done = true
						break
					}
				}
				if !done {
					fs.Outline = append(fs.Outline, &OpmlOutline{
						Title:   label,
						Outline: []*OpmlOutline{outline},
					})
				}
			}
		}
	}

	for _, outline := range outlines {
		if outline.XmlUrl != "" {
			mergeOutline("", outline)
		} else {
			for _, o := range outline.Outline {
				mergeOutline(outline.Title, o)
			}
		}
	}

	b, err := json.Marshal(&fs)
	if err != nil {
		return err
	}
	ud.Opml = b
	return nil
}
<commit_msg>Move miniprofiler to bottom<commit_after>/*
 * Copyright (c) 2013 Matt Jibson <matt.jibson@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package goapp

import (
	"encoding/json"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"time"

	"appengine"
	"appengine/datastore"
	"github.com/MiniProfiler/go/miniprofiler"
	mpg "github.com/MiniProfiler/go/miniprofiler_gae"
	"github.com/gorilla/mux"
	"github.com/mjibson/goon"
)

var router = new(mux.Router)
var templates *template.Template

func init() {
	var err error

	if templates, err = template.New("").Funcs(funcs).
		ParseFiles(
		"templates/base.html",
		"templates/admin-all-feeds.html",
		"templates/admin-date-formats.html",
		"templates/admin-feed.html",
		"templates/admin-stats.html",
		"templates/admin-user.html",
	); err != nil {
		log.Fatal(err)
	}

	router.Handle("/", mpg.NewHandler(Main)).Name("main")
	router.Handle("/login/google", mpg.NewHandler(LoginGoogle)).Name("login-google")
	router.Handle("/logout", mpg.NewHandler(Logout)).Name("logout")
	router.Handle("/push/{feed}", mpg.NewHandler(SubscribeCallback))
	router.Handle("/push", mpg.NewHandler(SubscribeCallback)).Name("subscribe-callback")
	router.Handle("/tasks/import-opml", mpg.NewHandler(ImportOpmlTask)).Name("import-opml-task")
	router.Handle("/tasks/subscribe-feed", mpg.NewHandler(SubscribeFeed)).Name("subscribe-feed")
	router.Handle("/tasks/update-feed-last", mpg.NewHandler(UpdateFeedLast)).Name("update-feed-last")
	router.Handle("/tasks/update-feed-manual", mpg.NewHandler(UpdateFeed)).Name("update-feed-manual")
	router.Handle("/tasks/update-feed", mpg.NewHandler(UpdateFeed)).Name("update-feed")
	router.Handle("/tasks/update-feeds", mpg.NewHandler(UpdateFeeds)).Name("update-feeds")
	router.Handle("/user/add-subscription", mpg.NewHandler(AddSubscription)).Name("add-subscription")
	router.Handle("/user/clear-feeds", mpg.NewHandler(ClearFeeds)).Name("clear-feeds")
	router.Handle("/user/delete-account", mpg.NewHandler(DeleteAccount)).Name("delete-account")
	router.Handle("/user/export-opml", mpg.NewHandler(ExportOpml)).Name("export-opml")
	router.Handle("/user/feed-history", mpg.NewHandler(FeedHistory)).Name("feed-history")
	router.Handle("/user/get-contents", mpg.NewHandler(GetContents)).Name("get-contents")
	router.Handle("/user/get-feed", mpg.NewHandler(GetFeed)).Name("get-feed")
	router.Handle("/user/import/opml", mpg.NewHandler(ImportOpml)).Name("import-opml")
	router.Handle("/user/list-feeds", mpg.NewHandler(ListFeeds)).Name("list-feeds")
	router.Handle("/user/mark-read", mpg.NewHandler(MarkRead)).Name("mark-read")
	router.Handle("/user/mark-unread", mpg.NewHandler(MarkUnread)).Name("mark-unread")
	router.Handle("/user/save-options", mpg.NewHandler(SaveOptions)).Name("save-options")
	router.Handle("/user/upload-opml", mpg.NewHandler(UploadOpml)).Name("upload-opml")

	router.Handle("/admin/all-feeds", mpg.NewHandler(AllFeeds)).Name("all-feeds")
	router.Handle("/admin/all-feeds-opml", mpg.NewHandler(AllFeedsOpml)).Name("all-feeds-opml")
	router.Handle("/admin/user", mpg.NewHandler(AdminUser)).Name("admin-user")
	router.Handle("/date-formats", mpg.NewHandler(AdminDateFormats)).Name("admin-date-formats")
	router.Handle("/admin/feed", mpg.NewHandler(AdminFeed)).Name("admin-feed")
	router.Handle("/admin/stats", mpg.NewHandler(AdminStats)).Name("admin-stats")
	router.Handle("/admin/update-feed", mpg.NewHandler(AdminUpdateFeed)).Name("admin-update-feed")
	router.Handle("/tasks/cfixer", mpg.NewHandler(CFixer))
	router.Handle("/tasks/cfix", mpg.NewHandler(CFix))
	router.Handle("/user/charge", mpg.NewHandler(Charge)).Name("charge")
	router.Handle("/user/account", mpg.NewHandler(Account)).Name("account")
	router.Handle("/user/uncheckout", mpg.NewHandler(Uncheckout)).Name("uncheckout")

	http.Handle("/", router)

	miniprofiler.ToggleShortcut = "Alt+C"
	miniprofiler.Position = "bottomleft"
}

func Main(c mpg.Context, w http.ResponseWriter, r *http.Request) {
	if err := templates.ExecuteTemplate(w, "base.html", includes(c, w, r)); err != nil {
		c.Errorf("%v", err)
		serveError(w, err)
	}
	return
}

func addFeed(c mpg.Context, userid string, outline *OpmlOutline) error {
	gn := goon.FromContext(c)
	o := outline.Outline[0]
	c.Infof("adding feed %v to user %s", o.XmlUrl, userid)
	fu, ferr := url.Parse(o.XmlUrl)
	if ferr != nil {
		return ferr
	}
	fu.Fragment = ""
	o.XmlUrl = fu.String()

	f := Feed{Url: o.XmlUrl}
	if err := gn.Get(&f); err == datastore.ErrNoSuchEntity {
		if feed, stories, err := fetchFeed(c, o.XmlUrl, o.XmlUrl); err != nil {
			return fmt.Errorf("could not add feed %s: %v", o.XmlUrl, err)
		} else {
			f = *feed
			f.Updated = time.Time{}
			f.Checked = f.Updated
			f.NextUpdate = f.Updated
			f.LastViewed = time.Now()
			gn.Put(&f)
			for _, s := range stories {
				s.Created = s.Published
			}
			if err := updateFeed(c, f.Url, feed, stories, false, false, false); err != nil {
				return err
			}

			o.XmlUrl = feed.Url
			o.HtmlUrl = feed.Link
			if o.Title == "" {
				o.Title = feed.Title
			}
		}
	} else if err != nil {
		return err
	} else {
		o.HtmlUrl = f.Link
		if o.Title == "" {
			o.Title = f.Title
		}
	}
	o.Text = ""

	return nil
}

func mergeUserOpml(c appengine.Context, ud *UserData, outlines ...*OpmlOutline) error {
	var fs Opml
	json.Unmarshal(ud.Opml, &fs)
	urls := make(map[string]bool)

	for _, o := range fs.Outline {
		if o.XmlUrl != "" {
			urls[o.XmlUrl] = true
		} else {
			for _, so := range o.Outline {
				urls[so.XmlUrl] = true
			}
		}
	}

	mergeOutline := func(label string, outline *OpmlOutline) {
		if _, present := urls[outline.XmlUrl]; present {
			return
		} else {
			urls[outline.XmlUrl] = true

			if label == "" {
				fs.Outline = append(fs.Outline, outline)
			} else {
				done := false
				for _, ol := range fs.Outline {
					if ol.Title == label && ol.XmlUrl == "" {
						ol.Outline = append(ol.Outline, outline)
						done = true
						break
					}
				}
				if !done {
					fs.Outline = append(fs.Outline, &OpmlOutline{
						Title:   label,
						Outline: []*OpmlOutline{outline},
					})
				}
			}
		}
	}

	for _, outline := range outlines {
		if outline.XmlUrl != "" {
			mergeOutline("", outline)
		} else {
			for _, o := range outline.Outline {
				mergeOutline(outline.Title, o)
			}
		}
	}

	b, err := json.Marshal(&fs)
	if err != nil {
		return err
	}
	ud.Opml = b
	return nil
}
<|endoftext|>


<commit_before>package goreq

import (
	"fmt"
	. "github.com/franela/goblin"
	. "github.com/onsi/gomega"
	"io"
	"io/ioutil"
	"math"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

type Query struct {
	Limit int
	Skip  int
}

func TestRequest(t *testing.T) {

	query := Query{
		Limit: 3,
		Skip:  5,
	}

	g := Goblin(t)

	RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) })

	g.Describe("Request", func() {

		g.Describe("General request methods", func() {
			var ts *httptest.Server

			g.Before(func() {
				ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					if (r.Method == "GET" || r.Method == "OPTIONS" || r.Method == "TRACE" || r.Method == "PATCH" || r.Method == "FOOBAR") && r.URL.Path == "/foo" {
						w.WriteHeader(200)
						fmt.Fprint(w, "bar")
					}
					if r.Method == "GET" && r.URL.Path == "/getquery" {
						w.WriteHeader(200)
						fmt.Fprint(w, fmt.Sprintf("%v", r.URL))
					}
					if r.Method == "POST" && r.URL.Path == "/" {
						w.Header().Add("Location", ts.URL+"/123")
						w.WriteHeader(201)
						io.Copy(w, r.Body)
					}
					if r.Method == "PUT" && r.URL.Path == "/foo/123" {
						w.WriteHeader(200)
						io.Copy(w, r.Body)
					}
					if r.Method == "DELETE" && r.URL.Path == "/foo/123" {
						w.WriteHeader(204)
					}
				}))
			})

			g.After(func() {
				ts.Close()
			})

			g.Describe("GET", func() {

				g.It("Should do a GET", func() {
					res, err := Request{Uri: ts.URL + "/foo"}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("bar"))
					Expect(res.StatusCode).Should(Equal(200))
				})

				g.It("Should do a GET with querystring", func() {
					res, err := Request{
						Uri:         ts.URL + "/getquery",
						QueryString: query,
					}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("/getquery?limit=3&skip=5"))
					Expect(res.StatusCode).Should(Equal(200))
				})

			})

			g.Describe("POST", func() {
				g.It("Should send a string", func() {
					res, err := Request{Method: "POST", Uri: ts.URL, Body: "foo"}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("foo"))
					Expect(res.StatusCode).Should(Equal(201))
					Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123"))
				})

				g.It("Should send a Reader", func() {
					res, err := Request{Method: "POST", Uri: ts.URL, Body: strings.NewReader("foo")}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("foo"))
					Expect(res.StatusCode).Should(Equal(201))
					Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123"))
				})

				g.It("Send any object that is json encodable", func() {
					obj := map[string]string{"foo": "bar"}
					res, err := Request{Method: "POST", Uri: ts.URL, Body: obj}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal(`{"foo":"bar"}`))
					Expect(res.StatusCode).Should(Equal(201))
					Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123"))
				})

				g.It("Send an array of bytes", func() {
					bdy := []byte{'f', 'o', 'o'}
					res, err := Request{Method: "POST", Uri: ts.URL, Body: bdy}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("foo"))
					Expect(res.StatusCode).Should(Equal(201))
					Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123"))
				})

				g.It("Should return an error when body is not JSON encodable", func() {
					res, err := Request{Method: "POST", Uri: ts.URL, Body: math.NaN()}.Do()

					Expect(res).Should(BeNil())
					Expect(err).ShouldNot(BeNil())
				})
			})

			g.It("Should do a PUT", func() {
				res, err := Request{Method: "PUT", Uri: ts.URL + "/foo/123", Body: "foo"}.Do()

				Expect(err).Should(BeNil())
				str, _ := res.Body.ToString()
				Expect(str).Should(Equal("foo"))
				Expect(res.StatusCode).Should(Equal(200))
			})

			g.It("Should do a DELETE", func() {
				res, err := Request{Method: "DELETE", Uri: ts.URL + "/foo/123"}.Do()

				Expect(err).Should(BeNil())
				Expect(res.StatusCode).Should(Equal(204))
			})

			g.It("Should do a OPTIONS", func() {
				res, err := Request{Method: "OPTIONS", Uri: ts.URL + "/foo"}.Do()

				Expect(err).Should(BeNil())
				str, _ := res.Body.ToString()
				Expect(str).Should(Equal("bar"))
				Expect(res.StatusCode).Should(Equal(200))
			})

			g.It("Should do a PATCH", func() {
				res, err := Request{Method: "PATCH", Uri: ts.URL + "/foo"}.Do()

				Expect(err).Should(BeNil())
				str, _ := res.Body.ToString()
				Expect(str).Should(Equal("bar"))
				Expect(res.StatusCode).Should(Equal(200))
			})

			g.It("Should do a TRACE", func() {
				res, err := Request{Method: "TRACE", Uri: ts.URL + "/foo"}.Do()

				Expect(err).Should(BeNil())
				str, _ := res.Body.ToString()
				Expect(str).Should(Equal("bar"))
				Expect(res.StatusCode).Should(Equal(200))
			})

			g.It("Should do a custom method", func() {
				res, err := Request{Method: "FOOBAR", Uri: ts.URL + "/foo"}.Do()

				Expect(err).Should(BeNil())
				str, _ := res.Body.ToString()
				Expect(str).Should(Equal("bar"))
				Expect(res.StatusCode).Should(Equal(200))
			})

			g.Describe("Responses", func() {
				g.It("Should handle strings", func() {
					res, _ := Request{Method: "POST", Uri: ts.URL, Body: "foo bar"}.Do()

					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("foo bar"))
				})

				g.It("Should handle io.ReaderCloser", func() {
					res, _ := Request{Method: "POST", Uri: ts.URL, Body: "foo bar"}.Do()

					body, _ := ioutil.ReadAll(res.Body)
					Expect(string(body)).Should(Equal("foo bar"))
				})

				g.It("Should handle parsing JSON", func() {
					res, _ := Request{Method: "POST", Uri: ts.URL, Body: `{"foo": "bar"}`}.Do()

					var foobar map[string]string

					res.Body.FromJsonTo(&foobar)

					Expect(foobar).Should(Equal(map[string]string{"foo": "bar"}))
				})
			})
		})

		g.Describe("Timeouts", func() {

			g.Describe("Connection timeouts", func() {
				g.It("Should connect timeout after a default of 1000 ms", func() {
					start := time.Now()
					res, err := Request{Uri: "http://10.255.255.1"}.Do()
					elapsed := time.Since(start)

					Expect(elapsed).Should(BeNumerically("<", 1100*time.Millisecond))
					Expect(elapsed).Should(BeNumerically(">=", 1000*time.Millisecond))
					Expect(res).Should(BeNil())
					Expect(err.(*Error).Timeout()).Should(BeTrue())
				})
				g.It("Should connect timeout after a custom amount of time", func() {
					SetConnectTimeout(100 * time.Millisecond)
					start := time.Now()
					res, err := Request{Uri: "http://10.255.255.1"}.Do()
					elapsed := time.Since(start)

					Expect(elapsed).Should(BeNumerically("<", 150*time.Millisecond))
					Expect(elapsed).Should(BeNumerically(">=", 100*time.Millisecond))
					Expect(res).Should(BeNil())
					Expect(err.(*Error).Timeout()).Should(BeTrue())
				})
			})

			g.Describe("Request timeout", func() {
				var ts *httptest.Server
				stop := make(chan bool)

				g.Before(func() {
					ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
						<-stop
						// just wait for someone to tell you when to end the request. this is used to simulate a slow server
					}))
				})
				g.After(func() {
					stop <- true
					ts.Close()
				})
				g.It("Should request timeout after a custom amount of time", func() {
					SetConnectTimeout(1000 * time.Millisecond)

					start := time.Now()
					res, err := Request{Uri: ts.URL, Timeout: 500 * time.Millisecond}.Do()
					elapsed := time.Since(start)

					Expect(elapsed).Should(BeNumerically("<", 550*time.Millisecond))
					Expect(elapsed).Should(BeNumerically(">=", 500*time.Millisecond))
					Expect(res).Should(BeNil())
					Expect(err.(*Error).Timeout()).Should(BeTrue())
				})
			})
		})

		g.Describe("Misc", func() {
			g.It("Should offer to set request headers", func() {
				ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					Expect(r.Header.Get("User-Agent")).Should(Equal("foobaragent"))
					Expect(r.Host).Should(Equal("foobar.com"))
					Expect(r.Header.Get("Accept")).Should(Equal("application/json"))
					Expect(r.Header.Get("Content-Type")).Should(Equal("application/json"))
					Expect(r.Header.Get("X-Custom")).Should(Equal("foobar"))

					w.WriteHeader(200)
				}))
				defer ts.Close()

				req := Request{Uri: ts.URL, Accept: "application/json", ContentType: "application/json", UserAgent: "foobaragent", Host: "foobar.com"}
				req.AddHeader("X-Custom", "foobar")
				res, _ := req.Do()

				Expect(res.StatusCode).Should(Equal(200))
			})
			g.It("Should change transport TLS config if Request.Insecure is set", func() {
				ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					w.WriteHeader(200)
				}))
				defer ts.Close()

				res, _ := Request{
					Insecure: true,
					Uri:      ts.URL,
					Host:     "foobar.com",
				}.Do()

				Expect(transport.TLSClientConfig.InsecureSkipVerify).Should(Equal(true))
				Expect(res.StatusCode).Should(Equal(200))

				// Test that the TLSClientConfig is set back to RequireVerify for subsequent requests
				res, _ = Request{
					Uri:  ts.URL,
					Host: "foobar.com",
				}.Do()

				Expect(transport.TLSClientConfig.InsecureSkipVerify).Should(Equal(false))
				Expect(res.StatusCode).Should(Equal(200))
			})
		})

		g.Describe("Errors", func() {
			var ts *httptest.Server

			g.Before(func() {
				ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					if r.Method == "POST" && r.URL.Path == "/" {
						w.Header().Add("Location", ts.URL+"/123")
						w.WriteHeader(201)
						io.Copy(w, r.Body)
					}
				}))
			})

			g.After(func() {
				ts.Close()
			})
			g.It("Should thorw an error when FromJsonTo fails", func() {
				res, _ := Request{Method: "POST", Uri: ts.URL, Body: `{"foo": "bar"`}.Do()
				var foobar map[string]string

				err := res.Body.FromJsonTo(&foobar)
				Expect(err).Should(HaveOccurred())
			})
			g.It("Should handle Url parsing errors", func() {
				_, err := Request{Uri: ":"}.Do()

				Expect(err).ShouldNot(BeNil())
			})
			g.It("Should handle DNS errors", func() {
				_, err := Request{Uri: "http://*.localhost"}.Do()

				Expect(err).ShouldNot(BeNil())
			})
		})
	})
}
<commit_msg>add test for local transport<commit_after>package goreq

import (
	"fmt"
	. "github.com/franela/goblin"
	. "github.com/onsi/gomega"
	"io"
	"io/ioutil"
	"math"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

type Query struct {
	Limit int
	Skip  int
}

func TestRequest(t *testing.T) {

	query := Query{
		Limit: 3,
		Skip:  5,
	}

	g := Goblin(t)

	RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) })

	g.Describe("Request", func() {

		g.Describe("General request methods", func() {
			var ts *httptest.Server

			g.Before(func() {
				ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					if (r.Method == "GET" || r.Method == "OPTIONS" || r.Method == "TRACE" || r.Method == "PATCH" || r.Method == "FOOBAR") && r.URL.Path == "/foo" {
						w.WriteHeader(200)
						fmt.Fprint(w, "bar")
					}
					if r.Method == "GET" && r.URL.Path == "/getquery" {
						w.WriteHeader(200)
						fmt.Fprint(w, fmt.Sprintf("%v", r.URL))
					}
					if r.Method == "POST" && r.URL.Path == "/" {
						w.Header().Add("Location", ts.URL+"/123")
						w.WriteHeader(201)
						io.Copy(w, r.Body)
					}
					if r.Method == "PUT" && r.URL.Path == "/foo/123" {
						w.WriteHeader(200)
						io.Copy(w, r.Body)
					}
					if r.Method == "DELETE" && r.URL.Path == "/foo/123" {
						w.WriteHeader(204)
					}
				}))
			})

			g.After(func() {
				ts.Close()
			})

			g.Describe("GET", func() {

				g.It("Should do a GET", func() {
					res, err := Request{Uri: ts.URL + "/foo"}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("bar"))
					Expect(res.StatusCode).Should(Equal(200))
				})

				g.It("Should do a GET with querystring", func() {
					res, err := Request{
						Uri:         ts.URL + "/getquery",
						QueryString: query,
					}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("/getquery?limit=3&skip=5"))
					Expect(res.StatusCode).Should(Equal(200))
				})

			})

			g.Describe("POST", func() {
				g.It("Should send a string", func() {
					res, err := Request{Method: "POST", Uri: ts.URL, Body: "foo"}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("foo"))
					Expect(res.StatusCode).Should(Equal(201))
					Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123"))
				})

				g.It("Should send a Reader", func() {
					res, err := Request{Method: "POST", Uri: ts.URL, Body: strings.NewReader("foo")}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("foo"))
					Expect(res.StatusCode).Should(Equal(201))
					Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123"))
				})

				g.It("Send any object that is json encodable", func() {
					obj := map[string]string{"foo": "bar"}
					res, err := Request{Method: "POST", Uri: ts.URL, Body: obj}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal(`{"foo":"bar"}`))
					Expect(res.StatusCode).Should(Equal(201))
					Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123"))
				})

				g.It("Send an array of bytes", func() {
					bdy := []byte{'f', 'o', 'o'}
					res, err := Request{Method: "POST", Uri: ts.URL, Body: bdy}.Do()

					Expect(err).Should(BeNil())
					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("foo"))
					Expect(res.StatusCode).Should(Equal(201))
					Expect(res.Header.Get("Location")).Should(Equal(ts.URL + "/123"))
				})

				g.It("Should return an error when body is not JSON encodable", func() {
					res, err := Request{Method: "POST", Uri: ts.URL, Body: math.NaN()}.Do()

					Expect(res).Should(BeNil())
					Expect(err).ShouldNot(BeNil())
				})
			})

			g.It("Should do a PUT", func() {
				res, err := Request{Method: "PUT", Uri: ts.URL + "/foo/123", Body: "foo"}.Do()

				Expect(err).Should(BeNil())
				str, _ := res.Body.ToString()
				Expect(str).Should(Equal("foo"))
				Expect(res.StatusCode).Should(Equal(200))
			})

			g.It("Should do a DELETE", func() {
				res, err := Request{Method: "DELETE", Uri: ts.URL + "/foo/123"}.Do()

				Expect(err).Should(BeNil())
				Expect(res.StatusCode).Should(Equal(204))
			})

			g.It("Should do a OPTIONS", func() {
				res, err := Request{Method: "OPTIONS", Uri: ts.URL + "/foo"}.Do()

				Expect(err).Should(BeNil())
				str, _ := res.Body.ToString()
				Expect(str).Should(Equal("bar"))
				Expect(res.StatusCode).Should(Equal(200))
			})

			g.It("Should do a PATCH", func() {
				res, err := Request{Method: "PATCH", Uri: ts.URL + "/foo"}.Do()

				Expect(err).Should(BeNil())
				str, _ := res.Body.ToString()
				Expect(str).Should(Equal("bar"))
				Expect(res.StatusCode).Should(Equal(200))
			})

			g.It("Should do a TRACE", func() {
				res, err := Request{Method: "TRACE", Uri: ts.URL + "/foo"}.Do()

				Expect(err).Should(BeNil())
				str, _ := res.Body.ToString()
				Expect(str).Should(Equal("bar"))
				Expect(res.StatusCode).Should(Equal(200))
			})

			g.It("Should do a custom method", func() {
				res, err := Request{Method: "FOOBAR", Uri: ts.URL + "/foo"}.Do()

				Expect(err).Should(BeNil())
				str, _ := res.Body.ToString()
				Expect(str).Should(Equal("bar"))
				Expect(res.StatusCode).Should(Equal(200))
			})

			g.Describe("Responses", func() {
				g.It("Should handle strings", func() {
					res, _ := Request{Method: "POST", Uri: ts.URL, Body: "foo bar"}.Do()

					str, _ := res.Body.ToString()
					Expect(str).Should(Equal("foo bar"))
				})

				g.It("Should handle io.ReaderCloser", func() {
					res, _ := Request{Method: "POST", Uri: ts.URL, Body: "foo bar"}.Do()

					body, _ := ioutil.ReadAll(res.Body)
					Expect(string(body)).Should(Equal("foo bar"))
				})

				g.It("Should handle parsing JSON", func() {
					res, _ := Request{Method: "POST", Uri: ts.URL, Body: `{"foo": "bar"}`}.Do()

					var foobar map[string]string

					res.Body.FromJsonTo(&foobar)

					Expect(foobar).Should(Equal(map[string]string{"foo": "bar"}))
				})
			})
		})

		g.Describe("Timeouts", func() {

			g.Describe("Connection timeouts", func() {
				g.It("Should connect timeout after a default of 1000 ms", func() {
					start := time.Now()
					res, err := Request{Uri: "http://10.255.255.1"}.Do()
					elapsed := time.Since(start)

					Expect(elapsed).Should(BeNumerically("<", 1100*time.Millisecond))
					Expect(elapsed).Should(BeNumerically(">=", 1000*time.Millisecond))
					Expect(res).Should(BeNil())
					Expect(err.(*Error).Timeout()).Should(BeTrue())
				})
				g.It("Should connect timeout after a custom amount of time", func() {
					SetConnectTimeout(100 * time.Millisecond)
					start := time.Now()
					res, err := Request{Uri: "http://10.255.255.1"}.Do()
					elapsed := time.Since(start)

					Expect(elapsed).Should(BeNumerically("<", 150*time.Millisecond))
					Expect(elapsed).Should(BeNumerically(">=", 100*time.Millisecond))
					Expect(res).Should(BeNil())
					Expect(err.(*Error).Timeout()).Should(BeTrue())
				})
			})

			g.Describe("Request timeout", func() {
				var ts *httptest.Server
				stop := make(chan bool)

				g.Before(func() {
					ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
						<-stop
						// just wait for someone to tell you when to end the request. this is used to simulate a slow server
					}))
				})
				g.After(func() {
					stop <- true
					ts.Close()
				})
				g.It("Should request timeout after a custom amount of time", func() {
					SetConnectTimeout(1000 * time.Millisecond)

					start := time.Now()
					res, err := Request{Uri: ts.URL, Timeout: 500 * time.Millisecond}.Do()
					elapsed := time.Since(start)

					Expect(elapsed).Should(BeNumerically("<", 550*time.Millisecond))
					Expect(elapsed).Should(BeNumerically(">=", 500*time.Millisecond))
					Expect(res).Should(BeNil())
					Expect(err.(*Error).Timeout()).Should(BeTrue())
				})
			})
		})

		g.Describe("Misc", func() {
			g.It("Should offer to set request headers", func() {
				ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					Expect(r.Header.Get("User-Agent")).Should(Equal("foobaragent"))
					Expect(r.Host).Should(Equal("foobar.com"))
					Expect(r.Header.Get("Accept")).Should(Equal("application/json"))
					Expect(r.Header.Get("Content-Type")).Should(Equal("application/json"))
					Expect(r.Header.Get("X-Custom")).Should(Equal("foobar"))

					w.WriteHeader(200)
				}))
				defer ts.Close()

				req := Request{Uri: ts.URL, Accept: "application/json", ContentType: "application/json", UserAgent: "foobaragent", Host: "foobar.com"}
				req.AddHeader("X-Custom", "foobar")
				res, _ := req.Do()

				Expect(res.StatusCode).Should(Equal(200))
			})
			g.It("Should change transport TLS config if Request.Insecure is set", func() {
				ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					w.WriteHeader(200)
				}))
				defer ts.Close()

				trans := &http.Transport{Dial: dialer.Dial}
				req := Request{
					transport: trans,
					Insecure:  true,
					Uri:       ts.URL,
					Host:      "foobar.com",
				}
				res, _ := req.Do()

				Expect(trans.TLSClientConfig.InsecureSkipVerify).Should(Equal(true))
				Expect(res.StatusCode).Should(Equal(200))
			})
		})

		g.Describe("Errors", func() {
			var ts *httptest.Server

			g.Before(func() {
				ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					if r.Method == "POST" && r.URL.Path == "/" {
						w.Header().Add("Location", ts.URL+"/123")
						w.WriteHeader(201)
						io.Copy(w, r.Body)
					}
				}))
			})

			g.After(func() {
				ts.Close()
			})
			g.It("Should thorw an error when FromJsonTo fails", func() {
				res, _ := Request{Method: "POST", Uri: ts.URL, Body: `{"foo": "bar"`}.Do()
				var foobar map[string]string

				err := res.Body.FromJsonTo(&foobar)
				Expect(err).Should(HaveOccurred())
			})
			g.It("Should handle Url parsing errors", func() {
				_, err := Request{Uri: ":"}.Do()

				Expect(err).ShouldNot(BeNil())
			})
			g.It("Should handle DNS errors", func() {
				_, err := Request{Uri: "http://*.localhost"}.Do()

				Expect(err).ShouldNot(BeNil())
			})
		})
	})
}
<|endoftext|>


<commit_before>package gosideways

import (
	"bufio"
	"errors"
	"fmt"
	"log"
	"net"
	"strconv"
	"strings"
	"time"
)

type Node struct {
	Addr     string
	Port     int
	Data     map[string]Data
	Siblings map[string]*Node
}

type Data struct {
	Key     string
	Text    string
	Date    time.Time
	Expires time.Time
}

func (n *Node) AddSibling(addr string, port int) error {
	key := addr + ":" + fmt.Sprint(port)
	if _, exists := n.Siblings[key]; exists {
		return errors.New("Sibling " + key + " already registered")
	}

	n.Siblings[key] = newNode(addr, port)

	return nil
}

func Listen(port int) *Node {
	node := newNode("127.0.0.1", port)
	go node.clean()
	go node.Run()
	return node
}

func (n *Node) Run() {
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", n.Port))
	if err != nil {
		log.Println(err.Error())
		return
	}
	defer listener.Close()

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err.Error())
			continue
		}

		go n.handleConnection(conn)
	}
}

func (n *Node) set(key string, data string, exp time.Duration) {
	n.Data[key] = Data{
		Key:     key,
		Text:    data,
		Date:    time.Now(),
		Expires: time.Now().Add(exp),
	}
}

func (n *Node) replicate(key string) {
	d := n.get(key)
	if d == nil {
		return
	}

	for _, sibling := range n.Siblings {
		if sibling.Addr == n.Addr && sibling.Port == n.Port {
			continue
		}

		go func(addr string, port int, d *Data) {
			conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", addr, port))
			if err != nil {
				log.Println(err.Error())
				return
			}

			exp := int(d.Expires.Sub(time.Now()).Seconds() + .5)

			fmt.Fprintf(conn, "REPLICATE %s %d %s", d.Key, exp, d.Text)
			conn.Close()
		}(sibling.Addr, sibling.Port, d)
	}
}

func (n *Node) get(key string) *Data {
	var data *Data

	if aux, ok := n.Data[key]; ok {
		if aux.Expires.Before(time.Now()) {
			n.del(key)
		} else {
			data = &aux
		}
	}

	return data
}

func (n *Node) del(key string) {
	if _, ok := n.Data[key]; ok {
		delete(n.Data, key)
	}
}

func (n *Node) clean() {
	for {
		now := time.Now()
		for key, data := range n.Data {
			if data.Expires.Before(now) {
				n.del(key)
			}
		}

		time.Sleep(time.Second)
	}
}

func newNode(addr string, port int) *Node {
	return &Node{
		Addr:     addr,
		Port:     port,
		Data:     make(map[string]Data),
		Siblings: make(map[string]*Node),
	}
}

func (n *Node) handleConnection(c net.Conn) {
	defer c.Close()

	scanner := bufio.NewScanner(c)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		command := strings.SplitN(line, " ", 3)
		if len(command) < 2 {
			continue
		}

		command[0] = strings.ToUpper(command[0])
		for i := 1; i < len(command); i++ {
			command[i] = strings.TrimSpace(command[i])
		}

		switch command[0] {
		case "GET":
			data := n.get(command[1])
			if data != nil {
				fmt.Fprintln(c, data.Text)
			}
			break
		case "SET", "REPLICATE":
			if len(command) < 3 {
				continue
			}

			aux := strings.SplitN(command[2], " ", 2)
			if len(aux) < 2 {
				continue
			}

			seconds, _ := strconv.Atoi(aux[0])
			if seconds <= 0 {
				continue
			}

			n.set(command[1], aux[1], time.Duration(seconds)*time.Second)

			if command[0] == "SET" {
				n.replicate(command[1])
			}
			break
		case "DELETE":
			n.del(command[1])
			break
		}
	}
}
<commit_msg>Creating local getter and setter<commit_after>package gosideways

import (
	"bufio"
	"errors"
	"fmt"
	"log"
	"net"
	"strconv"
	"strings"
	"time"
)

type Node struct {
	Addr     string
	Port     int
	Data     map[string]Data
	Siblings map[string]*Node
}

type Data struct {
	Key     string
	Text    string
	Date    time.Time
	Expires time.Time
}

func (n *Node) AddSibling(addr string, port int) error {
	key := addr + ":" + fmt.Sprint(port)
	if _, exists := n.Siblings[key]; exists {
		return errors.New("Sibling " + key + " already registered")
	}

	n.Siblings[key] = newNode(addr, port)

	return nil
}

func Listen(port int) *Node {
	node := newNode("127.0.0.1", port)
	go node.clean()
	go node.Run()
	return node
}

func (n *Node) Run() {
	listener, err := net.Listen("tcp", fmt.Sprintf(":%d", n.Port))
	if err != nil {
		log.Println(err.Error())
		return
	}
	defer listener.Close()

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err.Error())
			continue
		}

		go n.handleConnection(conn)
	}
}

func (n *Node) Get(key string) *Data {
	return n.get(key)
}

func (n *Node) Set(key string, data string, valid time.Duration) {
	n.set(key, data, valid)
	n.replicate(key)
}

func (n *Node) set(key string, data string, valid time.Duration) {
	n.Data[key] = Data{
		Key:     key,
		Text:    data,
		Date:    time.Now(),
		Expires: time.Now().Add(valid),
	}
}

func (n *Node) replicate(key string) {
	d := n.get(key)
	if d == nil {
		return
	}

	for _, sibling := range n.Siblings {
		if sibling.Addr == n.Addr && sibling.Port == n.Port {
			continue
		}

		go func(addr string, port int, d *Data) {
			conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", addr, port))
			if err != nil {
				log.Println(err.Error())
				return
			}

			exp := int(d.Expires.Sub(time.Now()).Seconds() + .5)

			fmt.Fprintf(conn, "REPLICATE %s %d %s", d.Key, exp, d.Text)
			conn.Close()
		}(sibling.Addr, sibling.Port, d)
	}
}

func (n *Node) get(key string) *Data {
	var data *Data

	if aux, ok := n.Data[key]; ok {
		if aux.Expires.Before(time.Now()) {
			n.del(key)
		} else {
			data = &aux
		}
	}

	return data
}

func (n *Node) del(key string) {
	if _, ok := n.Data[key]; ok {
		delete(n.Data, key)
	}
}

func (n *Node) clean() {
	for {
		now := time.Now()
		for key, data := range n.Data {
			if data.Expires.Before(now) {
				n.del(key)
			}
		}

		time.Sleep(time.Second)
	}
}

func newNode(addr string, port int) *Node {
	return &Node{
		Addr:     addr,
		Port:     port,
		Data:     make(map[string]Data),
		Siblings: make(map[string]*Node),
	}
}

func (n *Node) handleConnection(c net.Conn) {
	defer c.Close()

	scanner := bufio.NewScanner(c)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}

		command := strings.SplitN(line, " ", 3)
		if len(command) < 2 {
			continue
		}

		command[0] = strings.ToUpper(command[0])
		for i := 1; i < len(command); i++ {
			command[i] = strings.TrimSpace(command[i])
		}

		switch command[0] {
		case "GET":
			data := n.get(command[1])
			if data != nil {
				fmt.Fprintln(c, data.Text)
			}
			break
		case "SET", "REPLICATE":
			if len(command) < 3 {
				continue
			}

			aux := strings.SplitN(command[2], " ", 2)
			if len(aux) < 2 {
				continue
			}

			seconds, _ := strconv.Atoi(aux[0])
			if seconds <= 0 {
				continue
			}

			n.set(command[1], aux[1], time.Duration(seconds)*time.Second)

			if command[0] == "SET" {
				n.replicate(command[1])
			}
			break
		case "DELETE":
			n.del(command[1])
			break
		}
	}
}
<|endoftext|>


<commit_before>// +build freebsd netbsd openbsd dragonfly darwin

package immortal

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

// ScanDir struct
type ScanDir struct {
	scandir  string
	sdir     string
	services map[string]string
	watch    chan string
	sync.Mutex
}

// NewScanDir returns ScanDir struct
func NewScanDir(path string) (*ScanDir, error) {
	if !isDir(path) {
		return nil, fmt.Errorf("%q is not a directory", path)
	}

	dir, err := filepath.EvalSymlinks(path)
	if err != nil {
		return nil, err
	}

	dir, err = filepath.Abs(dir)
	if err != nil {
		return nil, err
	}

	d, err := os.Open(dir)
	if err != nil {
		if os.IsPermission(err) {
			return nil, os.ErrPermission
		}
		return nil, err
	}
	defer d.Close()

	return &ScanDir{
		scandir:  dir,
		sdir:     GetSdir(),
		services: map[string]string{},
		watch:    make(chan string, 1),
	}, nil
}

// Start check for changes on directory
func (s *ScanDir) Start(ctl Control) {
	log.Printf("immortal scandir: %s", s.scandir)

	// check for new services on scandir
	go WatchDir(s.scandir, s.watch)
	s.watch <- s.scandir

	// check for changes in sdir (/var/run/immortal)
	fmt.Printf("s.sdir = %+v\n", s.sdir)
	go WatchDir(s.sdir, s.watch)

	for {
		select {
		case watch := <-s.watch:
			switch watch {
			case s.sdir:
				fmt.Printf("s.sdir = %+v\n", s.sdir)
			case s.scandir:
				fmt.Printf("s.scandir = %+v\n", s.scandir)
				if err := s.Scandir(ctl); err != nil && !os.IsPermission(err) {
					log.Printf("Scandir error: %s", err)
				}
			default:
				serviceFile := filepath.Base(watch)
				serviceName := strings.TrimSuffix(serviceFile, filepath.Ext(serviceFile))
				if isFile(watch) {
					md5, err := md5sum(watch)
					if err != nil {
						log.Fatalf("Error getting the md5sum: %s", err)
					}
					s.Lock()
					// restart if file changed
					if md5 != s.services[serviceName] {
						s.services[serviceName] = md5
						log.Printf("Stopping: %s\n", serviceName)
						ctl.SendSignal(filepath.Join(s.sdir, serviceName, "immortal.sock"), "halt")
					}
					s.Unlock()
					log.Printf("Starting: %s\n", serviceName)
					// try to start before via socket
					if _, err := ctl.SendSignal(filepath.Join(s.sdir, serviceName, "immortal.sock"), "start"); err != nil {
						if out, err := ctl.Run(fmt.Sprintf("immortal -c %s -ctl %s", watch, serviceName)); err != nil {
							// keep retrying
							delete(s.services, serviceName)
							log.Println(err)
						} else {
							log.Printf("%s\n", out)
						}
					}
					go func() {
						if err := WatchFile(watch, s.watch); err != nil {
							log.Printf("WatchFile error: %s", err)
							// try 3 times sleeping i*100ms between retries
							for i := int32(100); i <= 300; i += 100 {
								time.Sleep(time.Duration(rand.Int31n(i)) * time.Millisecond)
								err := WatchFile(watch, s.watch)
								if err == nil {
									return
								}
							}
							log.Printf("Could not watch file %q error: %s", watch, err)
						}
					}()
				} else {
					// remove service
					s.Lock()
					delete(s.services, serviceName)
					s.Unlock()
					ctl.SendSignal(filepath.Join(s.sdir, serviceName, "immortal.sock"), "halt")
					log.Printf("Exiting: %s\n", serviceName)
				}
			}
		}
	}
}

// Scaner searches for *.yml if file changes it will reload(stop-start)
func (s *ScanDir) Scandir(ctl Control) error {
	s.Lock()
	defer s.Unlock()
	find := func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if f.Mode().IsRegular() {
			if filepath.Ext(f.Name()) == ".yml" {
				name := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
				md5, err := md5sum(path)
				if err != nil {
					return fmt.Errorf("Error getting the md5sum: %s", err)
				}
				if _, ok := s.services[name]; !ok {
					s.services[name] = md5
					log.Printf("Starting: %s\n", name)
					if out, err := ctl.Run(fmt.Sprintf("immortal -c %s -ctl %s", path, name)); err != nil {
						log.Println(err)
					} else {
						log.Printf("%s\n", out)
					}
					go func() {
						if err := WatchFile(path, s.watch); err != nil {
							log.Printf("WatchFile error: %s", err)
							// try 3 times sleeping i*100ms between retries
							for i := int32(100); i <= 300; i += 100 {
								time.Sleep(time.Duration(rand.Int31n(i)) * time.Millisecond)
								err := WatchFile(path, s.watch)
								if err == nil {
									return
								}
							}
							log.Printf("Could not watch file %q error: %s", path, err)
						}
					}()
				}
			}
		}

		// Block for 100 ms on each call to kevent (WatchFile)
		time.Sleep(100 * time.Millisecond)

		return err
	}
	return filepath.Walk(s.scandir, find)
}
<commit_msg>restart after 1 second stopd services by watching IMMORTAL_SDIR<commit_after>// +build freebsd netbsd openbsd dragonfly darwin

package immortal

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

// ScanDir struct
type ScanDir struct {
	scandir  string
	sdir     string
	services map[string]string
	watch    chan string
	sync.Mutex
}

// NewScanDir returns ScanDir struct
func NewScanDir(path string) (*ScanDir, error) {
	if !isDir(path) {
		return nil, fmt.Errorf("%q is not a directory", path)
	}

	dir, err := filepath.EvalSymlinks(path)
	if err != nil {
		return nil, err
	}

	dir, err = filepath.Abs(dir)
	if err != nil {
		return nil, err
	}

	d, err := os.Open(dir)
	if err != nil {
		if os.IsPermission(err) {
			return nil, os.ErrPermission
		}
		return nil, err
	}
	defer d.Close()

	return &ScanDir{
		scandir:  dir,
		sdir:     GetSdir(),
		services: map[string]string{},
		watch:    make(chan string, 1),
	}, nil
}

// Start check for changes on directory
func (s *ScanDir) Start(ctl Control) {
	log.Printf("immortal scandir: %s", s.scandir)

	// create supervise directory (/var/run/immortal) if doesn't exists
	// IMMORTAL_SDIR
	if !isDir(s.sdir) {
		if err := os.MkdirAll(s.sdir, os.ModePerm); err != nil {
			log.Fatalf("Could not create supervise dir: %q, %v", s.sdir, err)
		}
	}

	// check for changes on sdir helps to restart stoped services
	go WatchDir(s.sdir, s.watch)

	// check for new services on scandir
	go WatchDir(s.scandir, s.watch)
	s.watch <- s.scandir

	for {
		select {
		case watch := <-s.watch:
			switch watch {
			case s.sdir:
				// RESTART, after halting a service, this will
				// start stopped services after 1 second of receiving the signal
				time.Sleep(time.Second)
				if err := s.Scandir(ctl); err != nil && !os.IsPermission(err) {
					log.Printf("Scandir error: %s", err)
				}
			case s.scandir:
				if err := s.Scandir(ctl); err != nil && !os.IsPermission(err) {
					log.Printf("Scandir error: %s", err)
				}
			default:
				serviceFile := filepath.Base(watch)
				serviceName := strings.TrimSuffix(serviceFile, filepath.Ext(serviceFile))
				if isFile(watch) {
					md5, err := md5sum(watch)
					if err != nil {
						log.Fatalf("Error getting the md5sum: %s", err)
					}
					s.Lock()
					// restart if file changed
					if md5 != s.services[serviceName] {
						s.services[serviceName] = md5
						log.Printf("Stopping: %s\n", serviceName)
						ctl.SendSignal(filepath.Join(s.sdir, serviceName, "immortal.sock"), "halt")
					}
					s.Unlock()
					log.Printf("Starting: %s\n", serviceName)
					// try to start before via socket
					if _, err := ctl.SendSignal(filepath.Join(s.sdir, serviceName, "immortal.sock"), "start"); err != nil {
						if out, err := ctl.Run(fmt.Sprintf("immortal -c %s -ctl %s", watch, serviceName)); err != nil {
							// keep retrying
							delete(s.services, serviceName)
							log.Println(err)
						} else {
							log.Printf("%s\n", out)
						}
					}
					go func() {
						if err := WatchFile(watch, s.watch); err != nil {
							log.Printf("WatchFile error: %s", err)
							// try 3 times sleeping i*100ms between retries
							for i := int32(100); i <= 300; i += 100 {
								time.Sleep(time.Duration(rand.Int31n(i)) * time.Millisecond)
								err := WatchFile(watch, s.watch)
								if err == nil {
									return
								}
							}
							log.Printf("Could not watch file %q error: %s", watch, err)
						}
					}()
				} else {
					// remove service
					s.Lock()
					delete(s.services, serviceName)
					s.Unlock()
					ctl.SendSignal(filepath.Join(s.sdir, serviceName, "immortal.sock"), "halt")
					log.Printf("Exiting: %s\n", serviceName)
				}
			}
		}
	}
}

// Scaner searches for *.yml if file changes it will reload(stop-start)
func (s *ScanDir) Scandir(ctl Control) error {
	s.Lock()
	defer s.Unlock()
	find := func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if f.Mode().IsRegular() {
			if filepath.Ext(f.Name()) == ".yml" {
				name := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
				md5, err := md5sum(path)
				if err != nil {
					return fmt.Errorf("Error getting the md5sum: %s", err)
				}
				if _, ok := s.services[name]; !ok {
					s.services[name] = md5
					log.Printf("Starting: %s\n", name)
					if out, err := ctl.Run(fmt.Sprintf("immortal -c %s -ctl %s", path, name)); err != nil {
						log.Println(err)
					} else {
						log.Printf("%s\n", out)
					}
					go func() {
						if err := WatchFile(path, s.watch); err != nil {
							log.Printf("WatchFile error: %s", err)
							// try 3 times sleeping i*100ms between retries
							for i := int32(100); i <= 300; i += 100 {
								time.Sleep(time.Duration(rand.Int31n(i)) * time.Millisecond)
								err := WatchFile(path, s.watch)
								if err == nil {
									return
								}
							}
							log.Printf("Could not watch file %q error: %s", path, err)
						}
					}()
				}
			}
		}

		// Block for 100 ms on each call to kevent (WatchFile)
		time.Sleep(100 * time.Millisecond)

		return err
	}
	return filepath.Walk(s.scandir, find)
}
<|endoftext|>


<commit_before>package jo

import (
	"fmt"
)

// Valid scanner states.
const (
	sClean = iota
	sEOF

	sObjectKeyOrBrace
	sObjectColon
	sObjectCommaOrBrace
	sObjectKey

	sArrayElementOrBracket
	sArrayCommaOrBracket

	sString
	sStringEsc
	sStringUnicode
	sStringUnicode1
	sStringUnicode12
	sStringUnicode123

	sNumberNeg
	sNumberZero
	sNumberDigit
	sNumberDot
	sNumberDotDigit
	sNumberExp
	sNumberExpSign
	sNumberExpDigit

	sBoolT
	sBoolTr
	sBoolTru

	sBoolF
	sBoolFa
	sBoolFal
	sBoolFals

	sNullN
	sNullNu
	sNullNul
)

// Scanner is a JSON scanning state machine. The zero value for Scanner is
// ready to be used without any further initialization.
type Scanner struct {
	state int
	err   error
	stack []int
	size  int
	isKey bool
}

// Scan feeds another byte to the scanner.
//
// The first return value, refered to as an opcode, tells the caller about
// significant parsing events like beginning and ending values, so that the
// caller can follow along if it wishes.
//
// The second return value is used to indicate whether or not, which is
// necessary because of number literals (is 123 a whole value, or just the
// beginning of 12345?). Even though it will only ever be either 1 or 0, it is
// an integer rather than a boolean to allow for these kinds of constructs:
//
//     for i := 0; i < len(input); {
//         _, n := s.Scan(input[i])
//         i += n
//     }
func (s *Scanner) Scan(c byte) (Op, int) {
rewind:
	switch s.state {
	case sClean:
		switch c {
		case ' ', '\t', '\r', '\n':
			return OpSpace, 1
		case '{':
			s.state = sObjectKeyOrBrace
			return OpObjectStart, 1
		case '[':
			s.state = sArrayElementOrBracket
			return OpArrayStart, 1
		case '"':
			s.state = sString
			s.isKey = false
			return OpStringStart, 1
		case '-':
			s.state = sNumberNeg
			return OpNumberStart, 1
		case '0':
			s.state = sNumberZero
			return OpNumberStart, 1
		case '1', '2', '3', '4', '5', '6', '7', '8', '9':
			s.state = sNumberDigit
			return OpNumberStart, 1
		case 't':
			s.state = sBoolT
			return OpBoolStart, 1
		case 'f':
			s.state = sBoolF
			return OpBoolStart, 1
		case 'n':
			s.state = sNullN
			return OpNullStart, 1
		}
		return s.errorf(`expected start of JSON value, found %q`, c)

	case sObjectKeyOrBrace:
		switch c {
		case ' ', '\t', '\r', '\n':
			return OpSpace, 1
		case '"':
			s.push(sObjectColon)
			s.state = sString
			s.isKey = true
			return OpObjectKeyStart, 1
		case '}':
			s.state = s.pop()
			return OpObjectEnd, 1
		}
		return s.errorf(`expected object key or '}', found %q`, c)

	case sObjectColon:
		switch c {
		case ' ', '\t', 'r', '\n':
			return OpSpace, 1
		case ':':
			s.push(sObjectCommaOrBrace)
			s.state = sClean
			return OpContinue, 1
		}
		return s.errorf(`expected ':' after object key, found %q`, c)

	case sObjectCommaOrBrace:
		switch c {
		case ' ', '\t', '\r', '\n':
			return OpSpace, 1
		case ',':
			s.state = sObjectKey
			return OpContinue, 1
		case '}':
			s.state = s.pop()
			return OpObjectEnd, 1
		}
		return s.errorf(`expected object key or '}' after object value, found %q`, c)

	case sObjectKey:
		switch c {
		case ' ', '\t', '\r', '\n':
			return OpSpace, 1
		case '"':
			s.push(sObjectColon)
			s.state = sString
			s.isKey = true
			return OpObjectKeyStart, 1
		}
		return s.errorf(`expected object key after ',', found %q`, c)

	case sArrayElementOrBracket:
		switch c {
		case ' ', '\t', '\r', '\n':
			return OpSpace, 1
		case ']':
			s.state = s.pop()
			return OpArrayEnd, 1
		}
		s.push(sArrayCommaOrBracket)
		s.state = sClean
		goto rewind

	case sArrayCommaOrBracket:
		switch c {
		case ' ', '\t', '\n', '\r':
			return OpSpace, 1
		case ',':
			s.state = sArrayElementOrBracket
			return OpContinue, 1
		case ']':
			s.state = s.pop()
			return OpArrayEnd, 1
		}
		return s.errorf(`expected ',' or ']' after array element, found %q`, c)

	case sString:
		if c == '"' {
			s.state = s.pop()
			if s.isKey {
				return OpObjectKeyEnd, 1
			}
			return OpStringEnd, 1
		}
		if c == '\\' {
			s.state = sStringEsc
			return OpContinue, 1
		}
		if c >= 0x20 {
			return OpContinue, 1
		}
		return s.errorf(`unexpected control character in string literal`)

	case sStringEsc:
		switch c {
		case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
			s.state = sString
			return OpContinue, 1
		case 'u':
			s.state = sStringUnicode
			return OpContinue, 1
		}
		return s.errorf(`illegal escape sequence in string literal`)

	case sStringUnicode:
		if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
			s.state = sStringUnicode1
			return OpContinue, 1
		}
		return s.errorf(`illegal unicode escape sequence in string literal`)

	case sStringUnicode1:
		if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
			s.state = sStringUnicode12
			return OpContinue, 1
		}
		return s.errorf(`illegal unicode escape sequence in string literal`)

	case sStringUnicode12:
		if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
			s.state = sStringUnicode123
			return OpContinue, 1
		}
		return s.errorf(`illegal unicode escape sequence in string literal`)

	case sStringUnicode123:
		if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
			s.state = sString
			return OpContinue, 1
		}
		return s.errorf(`illegal unicode escape sequence in string literal`)

	case sNumberNeg:
		if c == '0' {
			s.state = sNumberZero
			return OpContinue, 1
		}
		if '1' <= c && c <= '9' {
			s.state = sNumberDigit
			return OpContinue, 1
		}
		return s.errorf(`expected digit after "-", found %q`, c)

	case sNumberDigit:
		if '0' <= c && c <= '9' {
			return OpContinue, 1
		}
		fallthrough

	case sNumberZero:
		if c == '.' {
			s.state = sNumberDot
			return OpContinue, 1
		}
		if c == 'e' || c == 'E' {
			s.state = sNumberExp
			return OpContinue, 1
		}
		s.state = s.pop()
		return OpNumberEnd, 0

	case sNumberDot:
		if '0' <= c && c <= '9' {
			s.state = sNumberDotDigit
			return OpContinue, 1
		}
		return s.errorf(`expected digit after decimal point, found %q`, c)

	case sNumberDotDigit:
		if '0' <= c && c <= '9' {
			return OpContinue, 1
		}
		if c == 'e' || c == 'E' {
			s.state = sNumberExp
			return OpContinue, 1
		}
		s.state = s.pop()
		return OpNumberEnd, 0

	case sNumberExp:
		if '0' <= c && c <= '9' {
			s.state = sNumberExpDigit
			return OpContinue, 1
		}
		if c == '-' || c == '+' {
			s.state = sNumberExpSign
			return OpContinue, 1
		}
		return s.errorf(`expected sign or digit in exponent, found %q`, c)

	case sNumberExpSign:
		if '0' <= c && c <= '9' {
			s.state = sNumberExpDigit
			return OpContinue, 1
		}
		return s.errorf(`expected digit after exponent sign, found %q`)

	case sNumberExpDigit:
		if '0' <= c && c <= '9' {
			return OpContinue, 1
		}
		s.state = s.pop()
		return OpNumberEnd, 0

	case sBoolT:
		if c == 'r' {
			s.state = sBoolTr
			return OpContinue, 1
		}
		return s.errorf(`expected 'r' after "t", found %q`, c)

	case sBoolTr:
		if c == 'u' {
			s.state = sBoolTru
			return OpContinue, 1
		}
		return s.errorf(`expected 'u' after "tr", found %q`, c)

	case sBoolTru:
		if c == 'e' {
			s.state = s.pop()
			return OpBoolEnd, 1
		}
		return s.errorf(`expected 'e' after "tru", found %q`, c)

	case sBoolF:
		if c == 'a' {
			s.state = sBoolFa
			return OpContinue, 1
		}
		return s.errorf(`expected 'a' after "f", found %q`, c)

	case sBoolFa:
		if c == 'l' {
			s.state = sBoolFal
			return OpContinue, 1
		}
		return s.errorf(`expected 'l' after "fa", found %q`, c)

	case sBoolFal:
		if c == 's' {
			s.state = sBoolFals
			return OpContinue, 1
		}
		return s.errorf(`expected 's' after "fal", found %q`, c)

	case sBoolFals:
		if c == 'e' {
			s.state = s.pop()
			return OpBoolEnd, 1
		}
		return s.errorf(`expected 'e' after "fals", found %q`, c)

	case sNullN:
		if c == 'u' {
			s.state = sNullNu
			return OpContinue, 1
		}
		return s.errorf(`expected 'u' after "n", found %q`, c)

	case sNullNu:
		if c == 'l' {
			s.state = sNullNul
			return OpContinue, 1
		}
		return s.errorf(`expected 'l' after "nu", found %q`, c)

	case sNullNul:
		if c == 'l' {
			s.state = s.pop()
			return OpNullEnd, 1
		}
		return s.errorf(`expected 'l' after "nul", found %q`, c)

	case sEOF:
		if c <= ' ' && (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
			return OpSpace, 1
		}
		return s.errorf(`unexpected %q after top-level value`, c)
	}

	panic("invalid internal state")
}

// Eof signals the scanner that the end of input has been reached. It returns
// an opcode, just as s.Scan does, which will be either OpEOF, OpNumberEnd or
// OpSyntaxError.
func (s *Scanner) Eof() Op {
	switch s.state {
	case sNumberZero, sNumberDigit, sNumberDotDigit, sNumberExpDigit:
		if len(s.stack) == 0 {
			s.state = sEOF
			return OpNumberEnd
		}
	case sEOF:
		return OpEOF
	}

	op, _ := s.errorf(`unexpected end of JSON input`)
	return op
}

// LastError returns the last error raised by the Scan or Eof methods.
func (s *Scanner) LastError() error {
	return s.err
}

// Reset resets the scanner's internal state.
func (s *Scanner) Reset() {
	s.state = 0
	s.err = nil
	s.stack = s.stack[:0]
	s.size = 0
}

// push adds a new state to the stack.
func (s *Scanner) push(x int) {
	if len(s.stack) == s.size {
		s.stack = append(s.stack, x)
	} else {
		s.stack[s.size] = x
	}
	s.size++
}

// pop takes the top state from the stack.
func (s *Scanner) pop() int {
	if s.size == 0 {
		return sEOF
	}

	s.size--
	return s.stack[s.size]
}

// errorf is a convenience function which both sets the scanner's error
// field and returns an OpSyntaxError opcode.
func (s *Scanner) errorf(format string, args ...interface{}) (Op, int) {
	s.err = fmt.Errorf(format, args...)
	return OpSyntaxError, 0
}
<commit_msg>Only emit OpNumberEnd events if the next byte is legal<commit_after>package jo

import (
	"fmt"
)

// Valid scanner states.
const (
	sClean = iota
	sEOF

	sObjectKeyOrBrace
	sObjectColon
	sObjectCommaOrBrace
	sObjectKey

	sArrayElementOrBracket
	sArrayCommaOrBracket

	sString
	sStringEsc
	sStringUnicode
	sStringUnicode1
	sStringUnicode12
	sStringUnicode123

	sNumberNeg
	sNumberZero
	sNumberDigit
	sNumberDot
	sNumberDotDigit
	sNumberExp
	sNumberExpSign
	sNumberExpDigit

	sBoolT
	sBoolTr
	sBoolTru

	sBoolF
	sBoolFa
	sBoolFal
	sBoolFals

	sNullN
	sNullNu
	sNullNul
)

// Scanner is a JSON scanning state machine. The zero value for Scanner is
// ready to be used without any further initialization.
type Scanner struct {
	state int
	err   error
	stack []int
	size  int
	isKey bool
}

// Scan feeds another byte to the scanner.
//
// The first return value, refered to as an opcode, tells the caller about
// significant parsing events like beginning and ending values, so that the
// caller can follow along if it wishes.
//
// The second return value is used to indicate whether or not, which is
// necessary because of number literals (is 123 a whole value, or just the
// beginning of 12345?). Even though it will only ever be either 1 or 0, it is
// an integer rather than a boolean to allow for these kinds of constructs:
//
//     for i := 0; i < len(input); {
//         _, n := s.Scan(input[i])
//         i += n
//     }
func (s *Scanner) Scan(c byte) (Op, int) {
rewind:
	switch s.state {
	case sClean:
		switch c {
		case ' ', '\t', '\r', '\n':
			return OpSpace, 1
		case '{':
			s.state = sObjectKeyOrBrace
			return OpObjectStart, 1
		case '[':
			s.state = sArrayElementOrBracket
			return OpArrayStart, 1
		case '"':
			s.state = sString
			s.isKey = false
			return OpStringStart, 1
		case '-':
			s.state = sNumberNeg
			return OpNumberStart, 1
		case '0':
			s.state = sNumberZero
			return OpNumberStart, 1
		case '1', '2', '3', '4', '5', '6', '7', '8', '9':
			s.state = sNumberDigit
			return OpNumberStart, 1
		case 't':
			s.state = sBoolT
			return OpBoolStart, 1
		case 'f':
			s.state = sBoolF
			return OpBoolStart, 1
		case 'n':
			s.state = sNullN
			return OpNullStart, 1
		}
		return s.errorf(`expected start of JSON value, found %q`, c)

	case sObjectKeyOrBrace:
		switch c {
		case ' ', '\t', '\r', '\n':
			return OpSpace, 1
		case '"':
			s.push(sObjectColon)
			s.state = sString
			s.isKey = true
			return OpObjectKeyStart, 1
		case '}':
			s.state = s.pop()
			return OpObjectEnd, 1
		}
		return s.errorf(`expected object key or '}', found %q`, c)

	case sObjectColon:
		switch c {
		case ' ', '\t', 'r', '\n':
			return OpSpace, 1
		case ':':
			s.push(sObjectCommaOrBrace)
			s.state = sClean
			return OpContinue, 1
		}
		return s.errorf(`expected ':' after object key, found %q`, c)

	case sObjectCommaOrBrace:
		switch c {
		case ' ', '\t', '\r', '\n':
			return OpSpace, 1
		case ',':
			s.state = sObjectKey
			return OpContinue, 1
		case '}':
			s.state = s.pop()
			return OpObjectEnd, 1
		}
		return s.errorf(`expected object key or '}' after object value, found %q`, c)

	case sObjectKey:
		switch c {
		case ' ', '\t', '\r', '\n':
			return OpSpace, 1
		case '"':
			s.push(sObjectColon)
			s.state = sString
			s.isKey = true
			return OpObjectKeyStart, 1
		}
		return s.errorf(`expected object key after ',', found %q`, c)

	case sArrayElementOrBracket:
		switch c {
		case ' ', '\t', '\r', '\n':
			return OpSpace, 1
		case ']':
			s.state = s.pop()
			return OpArrayEnd, 1
		}
		s.push(sArrayCommaOrBracket)
		s.state = sClean
		goto rewind

	case sArrayCommaOrBracket:
		switch c {
		case ' ', '\t', '\n', '\r':
			return OpSpace, 1
		case ',':
			s.state = sArrayElementOrBracket
			return OpContinue, 1
		case ']':
			s.state = s.pop()
			return OpArrayEnd, 1
		}
		return s.errorf(`expected ',' or ']' after array element, found %q`, c)

	case sString:
		if c == '"' {
			s.state = s.pop()
			if s.isKey {
				return OpObjectKeyEnd, 1
			}
			return OpStringEnd, 1
		}
		if c == '\\' {
			s.state = sStringEsc
			return OpContinue, 1
		}
		if c >= 0x20 {
			return OpContinue, 1
		}
		return s.errorf(`unexpected control character in string literal`)

	case sStringEsc:
		switch c {
		case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
			s.state = sString
			return OpContinue, 1
		case 'u':
			s.state = sStringUnicode
			return OpContinue, 1
		}
		return s.errorf(`illegal escape sequence in string literal`)

	case sStringUnicode:
		if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
			s.state = sStringUnicode1
			return OpContinue, 1
		}
		return s.errorf(`illegal unicode escape sequence in string literal`)

	case sStringUnicode1:
		if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
			s.state = sStringUnicode12
			return OpContinue, 1
		}
		return s.errorf(`illegal unicode escape sequence in string literal`)

	case sStringUnicode12:
		if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
			s.state = sStringUnicode123
			return OpContinue, 1
		}
		return s.errorf(`illegal unicode escape sequence in string literal`)

	case sStringUnicode123:
		if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
			s.state = sString
			return OpContinue, 1
		}
		return s.errorf(`illegal unicode escape sequence in string literal`)

	case sNumberNeg:
		if c == '0' {
			s.state = sNumberZero
			return OpContinue, 1
		}
		if '1' <= c && c <= '9' {
			s.state = sNumberDigit
			return OpContinue, 1
		}
		return s.errorf(`expected digit after "-", found %q`, c)

	case sNumberDigit:
		if '0' <= c && c <= '9' {
			return OpContinue, 1
		}
		fallthrough

	case sNumberZero:
		if c == '.' {
			s.state = sNumberDot
			return OpContinue, 1
		}
		if c == 'e' || c == 'E' {
			s.state = sNumberExp
			return OpContinue, 1
		}
		goto numend

	case sNumberDot:
		if '0' <= c && c <= '9' {
			s.state = sNumberDotDigit
			return OpContinue, 1
		}
		return s.errorf(`expected digit after decimal point, found %q`, c)

	case sNumberDotDigit:
		if '0' <= c && c <= '9' {
			return OpContinue, 1
		}
		if c == 'e' || c == 'E' {
			s.state = sNumberExp
			return OpContinue, 1
		}
		goto numend

	case sNumberExp:
		if '0' <= c && c <= '9' {
			s.state = sNumberExpDigit
			return OpContinue, 1
		}
		if c == '-' || c == '+' {
			s.state = sNumberExpSign
			return OpContinue, 1
		}
		return s.errorf(`expected sign or digit in exponent, found %q`, c)

	case sNumberExpSign:
		if '0' <= c && c <= '9' {
			s.state = sNumberExpDigit
			return OpContinue, 1
		}
		return s.errorf(`expected digit after exponent sign, found %q`)

	case sNumberExpDigit:
		if '0' <= c && c <= '9' {
			return OpContinue, 1
		}
		goto numend

	case sBoolT:
		if c == 'r' {
			s.state = sBoolTr
			return OpContinue, 1
		}
		return s.errorf(`expected 'r' after "t", found %q`, c)

	case sBoolTr:
		if c == 'u' {
			s.state = sBoolTru
			return OpContinue, 1
		}
		return s.errorf(`expected 'u' after "tr", found %q`, c)

	case sBoolTru:
		if c == 'e' {
			s.state = s.pop()
			return OpBoolEnd, 1
		}
		return s.errorf(`expected 'e' after "tru", found %q`, c)

	case sBoolF:
		if c == 'a' {
			s.state = sBoolFa
			return OpContinue, 1
		}
		return s.errorf(`expected 'a' after "f", found %q`, c)

	case sBoolFa:
		if c == 'l' {
			s.state = sBoolFal
			return OpContinue, 1
		}
		return s.errorf(`expected 'l' after "fa", found %q`, c)

	case sBoolFal:
		if c == 's' {
			s.state = sBoolFals
			return OpContinue, 1
		}
		return s.errorf(`expected 's' after "fal", found %q`, c)

	case sBoolFals:
		if c == 'e' {
			s.state = s.pop()
			return OpBoolEnd, 1
		}
		return s.errorf(`expected 'e' after "fals", found %q`, c)

	case sNullN:
		if c == 'u' {
			s.state = sNullNu
			return OpContinue, 1
		}
		return s.errorf(`expected 'u' after "n", found %q`, c)

	case sNullNu:
		if c == 'l' {
			s.state = sNullNul
			return OpContinue, 1
		}
		return s.errorf(`expected 'l' after "nu", found %q`, c)

	case sNullNul:
		if c == 'l' {
			s.state = s.pop()
			return OpNullEnd, 1
		}
		return s.errorf(`expected 'l' after "nul", found %q`, c)

	case sEOF:
		if c <= ' ' && (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
			return OpSpace, 1
		}
		return s.errorf(`unexpected %q after top-level value`, c)
	}

	panic("invalid internal state")

numend:
	dup := *s
	dup.state = dup.pop()

	if op, _ := dup.Scan(c); op == OpSyntaxError {
		return OpSyntaxError, 0
	}

	s.state = s.pop()
	return OpNumberEnd, 0
}

// Eof signals the scanner that the end of input has been reached. It returns
// an opcode, just as s.Scan does, which will be either OpEOF, OpNumberEnd or
// OpSyntaxError.
func (s *Scanner) Eof() Op {
	switch s.state {
	case sNumberZero, sNumberDigit, sNumberDotDigit, sNumberExpDigit:
		if len(s.stack) == 0 {
			s.state = sEOF
			return OpNumberEnd
		}
	case sEOF:
		return OpEOF
	}

	op, _ := s.errorf(`unexpected end of JSON input`)
	return op
}

// LastError returns the last error raised by the Scan or Eof methods.
func (s *Scanner) LastError() error {
	return s.err
}

// Reset resets the scanner's internal state.
func (s *Scanner) Reset() {
	s.state = 0
	s.err = nil
	s.stack = s.stack[:0]
	s.size = 0
}

// push adds a new state to the stack.
func (s *Scanner) push(x int) {
	if len(s.stack) == s.size {
		s.stack = append(s.stack, x)
	} else {
		s.stack[s.size] = x
	}
	s.size++
}

// pop takes the top state from the stack.
func (s *Scanner) pop() int {
	if s.size == 0 {
		return sEOF
	}

	s.size--
	return s.stack[s.size]
}

// errorf is a convenience function which both sets the scanner's error
// field and returns an OpSyntaxError opcode.
func (s *Scanner) errorf(format string, args ...interface{}) (Op, int) {
	s.err = fmt.Errorf(format, args...)
	return OpSyntaxError, 0
}
<|endoftext|>