<commit_before>package main

import (
	"log"
	"os"
	"sync"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"github.com/keybase/client/protocol/go"
	"github.com/keybase/kbfs/libkbfs"
	"golang.org/x/net/context"
)

type FolderList struct {
	fs *FS
	// only accept public folders
	public bool

	mu      sync.Mutex
	folders map[string]*Dir
}

var _ fs.Node = (*FolderList)(nil)

// Attr implements the fs.Node interface.
func (*FolderList) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Mode = os.ModeDir | 0755
	return nil
}

var _ fs.NodeRequestLookuper = (*FolderList)(nil)

// Lookup implements the fs.NodeRequestLookuper interface.
func (fl *FolderList) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (node fs.Node, err error) {
	defer func() { fl.fs.reportErr(err) }()
	fl.mu.Lock()
	defer fl.mu.Unlock()

	if req.Name == libkbfs.ErrorFile {
		resp.EntryValid = 0
		n := &ErrorFile{
			fs: fl.fs,
		}
		return n, nil
	}

	if child, ok := fl.folders[req.Name]; ok {
		return child, nil
	}

	dh, err := libkbfs.ParseTlfHandle(ctx, fl.fs.config, req.Name)
	if err != nil {
		return nil, err
	}

	if fl.public && !dh.HasPublic() {
		// no public folder exists for this folder
		return nil, fuse.ENOENT
	}

	if canon := dh.ToString(ctx, fl.fs.config); canon != req.Name {
		n := &Alias{
			canon: canon,
		}
		return n, nil
	}

	if fl.public {
		dh = &libkbfs.TlfHandle{
			Writers: dh.Writers,
			Readers: []keybase1.UID{keybase1.PublicUID},
		}
	}

	rootNode, _, err := fl.fs.config.KBFSOps().GetOrCreateRootNodeForHandle(ctx, dh, libkbfs.MasterBranch)
	if err != nil {
		// TODO make errors aware of fuse
		return nil, err
	}

	folderBranch := rootNode.GetFolderBranch()
	folder := &Folder{
		fs:           fl.fs,
		list:         fl,
		name:         req.Name,
		folderBranch: folderBranch,
		nodes:        map[libkbfs.NodeID]fs.Node{},
	}

	// TODO unregister all at unmount
	if err := fl.fs.config.Notifier().RegisterForChanges([]libkbfs.FolderBranch{folderBranch}, folder); err != nil {
		return nil, err
	}

	child := newDir(folder, rootNode)
	folder.nodes[rootNode.GetID()] = child

	fl.folders[req.Name] = child
	return child, nil
}

func (fl *FolderList) forgetFolder(f *Folder) {
	fl.mu.Lock()
	defer fl.mu.Unlock()

	if err := fl.fs.config.Notifier().UnregisterFromChanges([]libkbfs.FolderBranch{f.folderBranch}, f); err != nil {
		log.Printf("cannot unregister change notifier for folder %q: %v", f.name, err)
	}
	delete(fl.folders, f.name)
}

var _ fs.Handle = (*FolderList)(nil)

var _ fs.HandleReadDirAller = (*FolderList)(nil)

func (fl *FolderList) getDirent(ctx context.Context, work <-chan libkbfs.TlfID, results chan<- fuse.Dirent) error {
	for {
		select {
		case tlfID, ok := <-work:
			if !ok {
				return nil
			}
			_, _, dh, err := fl.fs.config.KBFSOps().GetRootNode(ctx,
				libkbfs.FolderBranch{Tlf: tlfID, Branch: libkbfs.MasterBranch})
			if err != nil {
				return err
			}
			name := dh.ToString(ctx, fl.fs.config)
			results <- fuse.Dirent{
				Type: fuse.DT_Dir,
				Name: name,
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

// ReadDirAll implements the ReadDirAll interface.
func (fl *FolderList) ReadDirAll(ctx context.Context) (res []fuse.Dirent, err error) {
	defer func() { fl.fs.reportErr(err) }()
	favs, err := fl.fs.config.KBFSOps().GetFavorites(ctx)
	if err != nil {
		return nil, err
	}
	work := make(chan libkbfs.TlfID)
	results := make(chan fuse.Dirent)
	errCh := make(chan error, 1)
	const workers = 10
	var wg sync.WaitGroup
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	for i := 0; i < workers; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if err := fl.getDirent(ctx, work, results); err != nil {
				select {
				case errCh <- err:
				default:
				}
			}
		}()
	}

	go func() {
		// feed work
		for _, tlfID := range favs {
			work <- tlfID
		}
		close(work)
		wg.Wait()
		// workers are done
		close(results)
	}()

outer:
	for {
		select {
		case dirent, ok := <-results:
			if !ok {
				break outer
			}
			res = append(res, dirent)
		case err := <-errCh:
			return nil, err
		}
	}
	return res, nil
}
<commit_msg>FUSE: Limit folder lookup workers to number of favorites<commit_after>package main

import (
	"log"
	"os"
	"sync"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"github.com/keybase/client/protocol/go"
	"github.com/keybase/kbfs/libkbfs"
	"golang.org/x/net/context"
)

type FolderList struct {
	fs *FS
	// only accept public folders
	public bool

	mu      sync.Mutex
	folders map[string]*Dir
}

var _ fs.Node = (*FolderList)(nil)

// Attr implements the fs.Node interface.
func (*FolderList) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Mode = os.ModeDir | 0755
	return nil
}

var _ fs.NodeRequestLookuper = (*FolderList)(nil)

// Lookup implements the fs.NodeRequestLookuper interface.
func (fl *FolderList) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (node fs.Node, err error) {
	defer func() { fl.fs.reportErr(err) }()
	fl.mu.Lock()
	defer fl.mu.Unlock()

	if req.Name == libkbfs.ErrorFile {
		resp.EntryValid = 0
		n := &ErrorFile{
			fs: fl.fs,
		}
		return n, nil
	}

	if child, ok := fl.folders[req.Name]; ok {
		return child, nil
	}

	dh, err := libkbfs.ParseTlfHandle(ctx, fl.fs.config, req.Name)
	if err != nil {
		return nil, err
	}

	if fl.public && !dh.HasPublic() {
		// no public folder exists for this folder
		return nil, fuse.ENOENT
	}

	if canon := dh.ToString(ctx, fl.fs.config); canon != req.Name {
		n := &Alias{
			canon: canon,
		}
		return n, nil
	}

	if fl.public {
		dh = &libkbfs.TlfHandle{
			Writers: dh.Writers,
			Readers: []keybase1.UID{keybase1.PublicUID},
		}
	}

	rootNode, _, err := fl.fs.config.KBFSOps().GetOrCreateRootNodeForHandle(ctx, dh, libkbfs.MasterBranch)
	if err != nil {
		// TODO make errors aware of fuse
		return nil, err
	}

	folderBranch := rootNode.GetFolderBranch()
	folder := &Folder{
		fs:           fl.fs,
		list:         fl,
		name:         req.Name,
		folderBranch: folderBranch,
		nodes:        map[libkbfs.NodeID]fs.Node{},
	}

	// TODO unregister all at unmount
	if err := fl.fs.config.Notifier().RegisterForChanges([]libkbfs.FolderBranch{folderBranch}, folder); err != nil {
		return nil, err
	}

	child := newDir(folder, rootNode)
	folder.nodes[rootNode.GetID()] = child

	fl.folders[req.Name] = child
	return child, nil
}

func (fl *FolderList) forgetFolder(f *Folder) {
	fl.mu.Lock()
	defer fl.mu.Unlock()

	if err := fl.fs.config.Notifier().UnregisterFromChanges([]libkbfs.FolderBranch{f.folderBranch}, f); err != nil {
		log.Printf("cannot unregister change notifier for folder %q: %v", f.name, err)
	}
	delete(fl.folders, f.name)
}

var _ fs.Handle = (*FolderList)(nil)

var _ fs.HandleReadDirAller = (*FolderList)(nil)

func (fl *FolderList) getDirent(ctx context.Context, work <-chan libkbfs.TlfID, results chan<- fuse.Dirent) error {
	for {
		select {
		case tlfID, ok := <-work:
			if !ok {
				return nil
			}
			_, _, dh, err := fl.fs.config.KBFSOps().GetRootNode(ctx,
				libkbfs.FolderBranch{Tlf: tlfID, Branch: libkbfs.MasterBranch})
			if err != nil {
				return err
			}
			name := dh.ToString(ctx, fl.fs.config)
			results <- fuse.Dirent{
				Type: fuse.DT_Dir,
				Name: name,
			}
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}

// ReadDirAll implements the ReadDirAll interface.
func (fl *FolderList) ReadDirAll(ctx context.Context) (res []fuse.Dirent, err error) {
	defer func() { fl.fs.reportErr(err) }()
	favs, err := fl.fs.config.KBFSOps().GetFavorites(ctx)
	if err != nil {
		return nil, err
	}
	work := make(chan libkbfs.TlfID)
	results := make(chan fuse.Dirent)
	errCh := make(chan error, 1)
	const maxWorkers = 10
	var wg sync.WaitGroup
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()
	for i := 0; i < maxWorkers && i < len(favs); i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			if err := fl.getDirent(ctx, work, results); err != nil {
				select {
				case errCh <- err:
				default:
				}
			}
		}()
	}

	go func() {
		// feed work
		for _, tlfID := range favs {
			work <- tlfID
		}
		close(work)
		wg.Wait()
		// workers are done
		close(results)
	}()

outer:
	for {
		select {
		case dirent, ok := <-results:
			if !ok {
				break outer
			}
			res = append(res, dirent)
		case err := <-errCh:
			return nil, err
		}
	}
	return res, nil
}
<|endoftext|>


<commit_before>package ttf

import (
	"math/rand"
	"testing"

	"github.com/veandco/go-sdl2/sdl"
)

var characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ `!@#$%^&*()-=~_+[]\\{}|;':\",./<>?"

func randomString(t *testing.T) string {
	s := ""
	length := 16 + rand.Int()%16

	for i := 0; i < length; i++ {
		s += string(characters[rand.Int()%len(characters)])
	}
	t.Log("text:", s)

	return s
}

func TestTTF(t *testing.T) {
	var font *Font
	var solid *sdl.Surface
	var err error

	if err := Init(); err != nil {
		t.Errorf("Failed to initialize TTF: %s\n", err)
	}

	if font, err = OpenFont("../assets/test.ttf", 32); err != nil {
		t.Errorf("Failed to open font: %s\n", err)
	}
	defer font.Close()

	for i := 0; i < 10000; i++ {
		if solid, err = font.RenderUTF8Solid(randomString(t), sdl.Color{255, 0, 0, 255}); err != nil {
			t.Errorf("Failed to render text: %s\n", err)
		}
		defer solid.Free()
	}
}
<commit_msg>ttf: sdl_ttf_test.go: Fix font path<commit_after>package ttf

import (
	"math/rand"
	"testing"

	"github.com/veandco/go-sdl2/sdl"
)

var characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ `!@#$%^&*()-=~_+[]\\{}|;':\",./<>?"

func randomString(t *testing.T) string {
	s := ""
	length := 16 + rand.Int()%16

	for i := 0; i < length; i++ {
		s += string(characters[rand.Int()%len(characters)])
	}
	t.Log("text:", s)

	return s
}

func TestTTF(t *testing.T) {
	var font *Font
	var solid *sdl.Surface
	var err error

	if err := Init(); err != nil {
		t.Errorf("Failed to initialize TTF: %s\n", err)
	}

	if font, err = OpenFont("../.go-sdl2-examples/assets/test.ttf", 32); err != nil {
		t.Errorf("Failed to open font: %s\n", err)
	}
	defer font.Close()

	for i := 0; i < 10000; i++ {
		if solid, err = font.RenderUTF8Solid(randomString(t), sdl.Color{255, 0, 0, 255}); err != nil {
			t.Errorf("Failed to render text: %s\n", err)
		}
		defer solid.Free()
	}
}
<|endoftext|>


<commit_before>package service

import "github.com/conformal/winsvc/eventlog"
import "github.com/conformal/winsvc/mgr"
import "github.com/conformal/winsvc/svc"
import "flag"
import "os"
import "time"
import "fmt"

var serviceFlag = fs.String("service", "", "service command (one of: start, stop, install, remove)")
var _serviceFlag = flag.String("service", "", "service command (one of: start, stop, install, remove)")

func systemdUpdateStatus(status string) error {
	return fmt.Errorf("not supported")
}

func setproctitle(status string) error {
	return nil
}

type handler struct {
	info        *Info
	startedChan chan struct{}
	stopChan    chan struct{}
	status      string
}

func (h *handler) DropPrivileges() error {
	h.dropped = true
	return nil
}

func (h *handler) SetStarted() {
	if !h.dropped {
		panic("service must call DropPrivileges before calling SetStarted")
	}

	select {
	case h.startedChan <- struct{}{}:
	default:
	}
}

func (h *handler) StopChan() chan<- struct{} {
	return h.stopChan
}

func (h *handler) SetStatus(status string) {
	h.status = status
}

func (h *handler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
	changes <- svc.Status{State: svc.StartPending}

	h.startedChan = make(chan struct{}, 1)
	h.stopChan = make(chan struct{})
	doneChan := make(chan error)
	started := false
	stopping := false

	go func() {
		err := info.RunFunc(h)
		doneChan <- err
	}()

loop:
	for {
		select {
		case c := <-r:
			switch c.Cmd {
			case svc.Interrogate:
				changes <- c.CurrentStatus

			case svc.Stop, svc.Shutdown:
				// Service stop is pending. Don't accept any more commands while pending.
				changes <- svc.Status{State: svc.StopPending}
				if !stopping {
					stopping = true
					close(h.stopChan)
				}

			default:
				// Unexpected control request
			}

		case <-h.startedChan:
			if started {
				panic("must not call SetStarted() more than once")
			}
			started = true
			changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}

		case err := <-doneChan:
			break loop
		}
	}

	changes <- svc.Status{State: svc.Stopped}
	return false, 0
}

func isInteractive() bool {
	interactive, err := svc.IsAnInteractiveSession()
	if err != nil {
		return false
	}
	return interactive
}

func (info *Info) installService() error {
	svcName := info.Name

	// Connect to the Windows service manager.
	serviceManager, err := mgr.Connect()
	if err != nil {
		return err
	}

	defer serviceManager.Disconnect()

	// Ensure the service doesn't already exist.
	service, err := serviceManager.OpenService(svcName)
	if err == nil {
		service.Close()
		return fmt.Errorf("service %s already exists", svcName)
	}

	// Install the service.
	service, err = serviceManager.CreateService(svcName, exePath, mgr.Config{
		DisplayName: info.Title,
		Description: info.Description,
	})
	if err != nil {
		return err
	}
	defer service.Close()

	// TODO: event log

	return nil
}

func (info *Info) removeService() error {
	svcName := info.Name

	// Connect to the Windows service manager.
	serviceManager, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer serviceManager.Disconnect()

	// Ensure the service exists.
	service, err := serviceManager.OpenService(svcName)
	if err != nil {
		return fmt.Errorf("service %s is not installed", svcName)
	}
	defer service.Close()

	// Remove the service.
	err = service.Delete()
	if err != nil {
		return err
	}

	return nil
}

func (info *Info) startService() error {
	svcName := info.Name

	// Connect to the Windows service manager.
	serviceManager, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer serviceManager.Disconnect()

	service, err := serviceManager.OpenService(svcName)
	if err != nil {
		return fmt.Errorf("could not access service: %v", err)
	}
	defer service.Close()

	err = service.Start(os.Args)
	if err != nil {
		return fmt.Errorf("could not start service: %v", err)
	}

	return nil
}

func (info *Info) controlService(c svc.Cmd, to svc.State) error {
	svcName := info.Name

	// Connect to the Windows service manager.
	serviceManager, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer serviceManager.Disconnect()

	service, err := serviceManager.OpenService(svcName)
	if err != nil {
		return fmt.Errorf("could not access service: %v", err)
	}
	defer service.Close()

	// Send the control message.
	status, err := service.Control(c)
	if err != nil {
		return fmt.Errorf("could not send control=%d: %v", c, err)
	}

	// Wait.
	timeout := time.Now().Add(10 * time.Second)
	for status.State != to {
		if timeout.Before(time.Now()) {
			return fmt.Errorf("timeout waiting for service to go to state=%d", to)
		}
		time.Sleep(300 * time.Millisecond)
		status, err = service.Query()
		if err != nil {
			return fmt.Errorf("could not retrieve service status: %v", err)
		}
	}

	return nil
}

func (info *Info) stopService() error {
	return info.controlService(svc.Stop, svc.Stopped)
}

func (info *Info) runAsService() error {
	// TODO: event log

	err = svc.Run(info.Name, &handler{info})
	if err != nil {
		return err
	}

	return nil
}

func (info *Info) serviceMain() error {
	switch *serviceFlag {
	case "install":
		return info.installService()
	case "remove":
		return info.removeService()
	case "start":
		return info.startService()
	case "stop":
		return info.stopService()
	default:
		// ...
	}

	interactive := isInteractive()
	if !interactive {
		return info.runAsService()
	}

	return info.runInteractively()
}

// Copyright © 2013-2014 Conformal Systems LLC.
//
// 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.
//
// © 2014 Hugo Landau <hlandau@devever.net>  ISC License
<commit_msg>service fixes<commit_after>package service

import "github.com/conformal/winsvc/eventlog"
import "github.com/conformal/winsvc/mgr"
import "github.com/conformal/winsvc/svc"
import "flag"
import "os"
import "time"
import "fmt"

var serviceFlag = fs.String("service", "", "service command (one of: start, stop, install, remove)")
var _serviceFlag = flag.String("service", "", "service command (one of: start, stop, install, remove)")

func systemdUpdateStatus(status string) error {
	return fmt.Errorf("not supported")
}

func setproctitle(status string) error {
	return nil
}

type handler struct {
	info        *Info
	startedChan chan struct{}
	stopChan    chan struct{}
	status      string
}

func (h *handler) DropPrivileges() error {
	h.dropped = true
	return nil
}

func (h *handler) SetStarted() {
	if !h.dropped {
		panic("service must call DropPrivileges before calling SetStarted")
	}

	select {
	case h.startedChan <- struct{}{}:
	default:
	}
}

func (h *handler) StopChan() chan<- struct{} {
	return h.stopChan
}

func (h *handler) SetStatus(status string) {
	h.status = status
}

func (h *handler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
	changes <- svc.Status{State: svc.StartPending}

	h.startedChan = make(chan struct{}, 1)
	h.stopChan = make(chan struct{})
	doneChan := make(chan error)
	started := false
	stopping := false

	go func() {
		err := info.RunFunc(h)
		doneChan <- err
	}()

	var err error

loop:
	for {
		select {
		case c := <-r:
			switch c.Cmd {
			case svc.Interrogate:
				changes <- c.CurrentStatus

			case svc.Stop, svc.Shutdown:
				// Service stop is pending. Don't accept any more commands while pending.
				changes <- svc.Status{State: svc.StopPending}
				if !stopping {
					stopping = true
					close(h.stopChan)
				}

			default:
				// Unexpected control request
			}

		case <-h.startedChan:
			if started {
				panic("must not call SetStarted() more than once")
			}
			started = true
			changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}

		case err = <-doneChan:
			break loop
		}
	}

	if err == nil {
		changes <- svc.Status{State: svc.Stopped}
		return false, 0
	} else {
		return false, 1
	}
}

func isInteractive() bool {
	interactive, err := svc.IsAnInteractiveSession()
	if err != nil {
		return false
	}
	return interactive
}

func (info *Info) installService() error {
	svcName := info.Name

	// Connect to the Windows service manager.
	serviceManager, err := mgr.Connect()
	if err != nil {
		return err
	}

	defer serviceManager.Disconnect()

	// Ensure the service doesn't already exist.
	service, err := serviceManager.OpenService(svcName)
	if err == nil {
		service.Close()
		return fmt.Errorf("service %s already exists", svcName)
	}

	// Install the service.
	service, err = serviceManager.CreateService(svcName, exePath, mgr.Config{
		DisplayName:  info.Title,
		Description:  info.Description,
		StartType:    mgr.StartAutomatic,
		ErrorControl: mgr.ErrorNormal,
	})
	if err != nil {
		return err
	}
	defer service.Close()

	// TODO: event log

	return nil
}

func (info *Info) removeService() error {
	svcName := info.Name

	// Connect to the Windows service manager.
	serviceManager, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer serviceManager.Disconnect()

	// Ensure the service exists.
	service, err := serviceManager.OpenService(svcName)
	if err != nil {
		return fmt.Errorf("service %s is not installed", svcName)
	}
	defer service.Close()

	// Remove the service.
	err = service.Delete()
	if err != nil {
		return err
	}

	return nil
}

func (info *Info) startService() error {
	svcName := info.Name

	// Connect to the Windows service manager.
	serviceManager, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer serviceManager.Disconnect()

	service, err := serviceManager.OpenService(svcName)
	if err != nil {
		return fmt.Errorf("could not access service: %v", err)
	}
	defer service.Close()

	err = service.Start(os.Args)
	if err != nil {
		return fmt.Errorf("could not start service: %v", err)
	}

	return nil
}

func (info *Info) controlService(c svc.Cmd, to svc.State) error {
	svcName := info.Name

	// Connect to the Windows service manager.
	serviceManager, err := mgr.Connect()
	if err != nil {
		return err
	}
	defer serviceManager.Disconnect()

	service, err := serviceManager.OpenService(svcName)
	if err != nil {
		return fmt.Errorf("could not access service: %v", err)
	}
	defer service.Close()

	// Send the control message.
	status, err := service.Control(c)
	if err != nil {
		return fmt.Errorf("could not send control=%d: %v", c, err)
	}

	// Wait.
	timeout := time.Now().Add(10 * time.Second)
	for status.State != to {
		if timeout.Before(time.Now()) {
			return fmt.Errorf("timeout waiting for service to go to state=%d", to)
		}
		time.Sleep(300 * time.Millisecond)
		status, err = service.Query()
		if err != nil {
			return fmt.Errorf("could not retrieve service status: %v", err)
		}
	}

	return nil
}

func (info *Info) stopService() error {
	return info.controlService(svc.Stop, svc.Stopped)
}

func (info *Info) runAsService() error {
	// TODO: event log

	err = svc.Run(info.Name, &handler{info})
	if err != nil {
		return err
	}

	return nil
}

func (info *Info) serviceMain() error {
	switch *serviceFlag {
	case "install":
		return info.installService()
	case "remove":
		return info.removeService()
	case "start":
		return info.startService()
	case "stop":
		return info.stopService()
	default:
		// ...
	}

	interactive := isInteractive()
	if !interactive {
		return info.runAsService()
	}

	return info.runInteractively()
}

// Copyright © 2013-2014 Conformal Systems LLC.
//
// 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.
//
// © 2014 Hugo Landau <hlandau@devever.net>  ISC License
<|endoftext|>


<commit_before>package services

import (
	"encoding/json"
	"strings"

	"github.com/khades/servbot/bot"
	"github.com/khades/servbot/httpclient"
	"github.com/khades/servbot/models"
	"github.com/khades/servbot/repos"
)

type links struct {
	Next string `json:"next"`
}
type follows struct {
	User user `json:"user"`
}
type user struct {
	DisplayName string `json:"display_name"`
	Name        string `json:"name"`
}
type followerResponse struct {
	Cursor  string    `json:"_cursor"`
	Links   links     `json:"_links"`
	Follows []follows `json:"follows"`
}

// https://api.twitch.tv/kraken/channels/areyouready_88/follows?client_id=1icafjrio64l3n0uwg5qlkdseu0j8d6&direction=ASC&cursor=1494757816579821000
func CheckChannelsFollowers() {
	userIDs, error := repos.GetUsersID(&repos.Config.Channels)
	if error != nil {
		return
	}
	for key, value := range *userIDs {
		checkOneChannelFollowers(&key, &value)
	}

}
func checkOneChannelFollowers(channel *string, channelID *string) {
	cursor := ""
	cursorObject, error := repos.GetFollowerCursor(channelID)
	if error != nil && error.Error() != "not found" {
		return
	}

	if error != nil && error.Error() == "not found" || cursorObject.Cursor == "" {
		cursor = getInitialCursor(channelID)
		if cursor != "" {
			repos.SetFollowerCursor(channelID, &cursor)
		}

	} else {
		cursor = cursorObject.Cursor
	}
	if cursor == "" {
		return
	}

	followers, followersError := getFollowers(channelID, &cursor)
	if followersError != nil || followers.Cursor == "" || len(followers.Follows) == 0 {
		return
	}

	repos.SetFollowerCursor(channelID, &followers.Cursor)
	followersToGreet := []string{}
	for _, follow := range followers.Follows {
		alreadyGreeted, _ := repos.CheckIfFollowerGreeted(channelID, &follow.User.Name)
		if alreadyGreeted == false {
			followersToGreet = append(followersToGreet, follow.User.Name)

			repos.AddFollowerToList(channelID, &follow.User.Name)
			// alertInfo, alertError := repos.GetSubAlert(channelID)
			// if alertError == nil && alertInfo.Enabled == true && alertInfo.FollowerMessage != "" {
			// 	bot.IrcClientInstance.SendPublic(&models.OutgoingMessage{
			// 		Channel: *channel,
			// 		Body:    alertInfo.FollowerMessage,
			// 		User:    follow.User.Name})
			// }

		}

	}

	alertInfo, alertError := repos.GetSubAlert(channelID)
	if alertError == nil && alertInfo.Enabled == true && alertInfo.FollowerMessage != "" {
		bot.IrcClientInstance.SendPublic(&models.OutgoingMessage{
			Channel: *channel,
			Body:    "@" + strings.Join(followersToGreet, " @") + " " + alertInfo.FollowerMessage})
	}

}
func getFollowers(channelID *string, cursor *string) (*followerResponse, error) {
	url := "https://api.twitch.tv/kraken/channels/" + *channelID + "/follows?direction=ASC&cursor=" + *cursor

	resp, respError := httpclient.TwitchV5(repos.Config.ClientID, "GET", url, nil)
	if respError != nil {
		return nil, respError
	}
	if resp != nil {
		defer resp.Body.Close()
	}
	var responseBody = new(followerResponse)

	marshallError := json.NewDecoder(resp.Body).Decode(responseBody)

	if marshallError != nil {
		return nil, marshallError
	}
	return responseBody, nil

}
func getInitialCursor(channelID *string) string {
	url := "https://api.twitch.tv/kraken/channels/" + *channelID + "/follows?direction=DESC&limit=1"
	resp, respError := httpclient.TwitchV5(repos.Config.ClientID, "GET", url, nil)
	if respError != nil {
		return ""
	}
	if resp != nil {
		defer resp.Body.Close()
	}
	var responseBody = new(followerResponse)

	marshallError := json.NewDecoder(resp.Body).Decode(responseBody)
	if marshallError != nil {

		return ""
	}
	return responseBody.Cursor
}
<commit_msg>Fix: empty follower alerts<commit_after>package services

import (
	"encoding/json"
	"strings"

	"github.com/khades/servbot/bot"
	"github.com/khades/servbot/httpclient"
	"github.com/khades/servbot/models"
	"github.com/khades/servbot/repos"
)

type links struct {
	Next string `json:"next"`
}
type follows struct {
	User user `json:"user"`
}
type user struct {
	DisplayName string `json:"display_name"`
	Name        string `json:"name"`
}
type followerResponse struct {
	Cursor  string    `json:"_cursor"`
	Links   links     `json:"_links"`
	Follows []follows `json:"follows"`
}

// https://api.twitch.tv/kraken/channels/areyouready_88/follows?client_id=1icafjrio64l3n0uwg5qlkdseu0j8d6&direction=ASC&cursor=1494757816579821000
func CheckChannelsFollowers() {
	userIDs, error := repos.GetUsersID(&repos.Config.Channels)
	if error != nil {
		return
	}
	for key, value := range *userIDs {
		checkOneChannelFollowers(&key, &value)
	}

}
func checkOneChannelFollowers(channel *string, channelID *string) {
	cursor := ""
	cursorObject, error := repos.GetFollowerCursor(channelID)
	if error != nil && error.Error() != "not found" {
		return
	}

	if error != nil && error.Error() == "not found" || cursorObject.Cursor == "" {
		cursor = getInitialCursor(channelID)
		if cursor != "" {
			repos.SetFollowerCursor(channelID, &cursor)
		}

	} else {
		cursor = cursorObject.Cursor
	}
	if cursor == "" {
		return
	}

	followers, followersError := getFollowers(channelID, &cursor)
	if followersError != nil || followers.Cursor == "" || len(followers.Follows) == 0 {
		return
	}

	repos.SetFollowerCursor(channelID, &followers.Cursor)
	followersToGreet := []string{}
	for _, follow := range followers.Follows {
		alreadyGreeted, _ := repos.CheckIfFollowerGreeted(channelID, &follow.User.Name)
		if alreadyGreeted == false {
			followersToGreet = append(followersToGreet, follow.User.Name)

			repos.AddFollowerToList(channelID, &follow.User.Name)
			// alertInfo, alertError := repos.GetSubAlert(channelID)
			// if alertError == nil && alertInfo.Enabled == true && alertInfo.FollowerMessage != "" {
			// 	bot.IrcClientInstance.SendPublic(&models.OutgoingMessage{
			// 		Channel: *channel,
			// 		Body:    alertInfo.FollowerMessage,
			// 		User:    follow.User.Name})
			// }

		}

	}
	if len(followersToGreet) > 0 {
		alertInfo, alertError := repos.GetSubAlert(channelID)
		if alertError == nil && alertInfo.Enabled == true && alertInfo.FollowerMessage != "" {
			bot.IrcClientInstance.SendPublic(&models.OutgoingMessage{
				Channel: *channel,
				Body:    "@" + strings.Join(followersToGreet, " @") + " " + alertInfo.FollowerMessage})
		}
	}

}
func getFollowers(channelID *string, cursor *string) (*followerResponse, error) {
	url := "https://api.twitch.tv/kraken/channels/" + *channelID + "/follows?direction=ASC&cursor=" + *cursor

	resp, respError := httpclient.TwitchV5(repos.Config.ClientID, "GET", url, nil)
	if respError != nil {
		return nil, respError
	}
	if resp != nil {
		defer resp.Body.Close()
	}
	var responseBody = new(followerResponse)

	marshallError := json.NewDecoder(resp.Body).Decode(responseBody)

	if marshallError != nil {
		return nil, marshallError
	}
	return responseBody, nil

}
func getInitialCursor(channelID *string) string {
	url := "https://api.twitch.tv/kraken/channels/" + *channelID + "/follows?direction=DESC&limit=1"
	resp, respError := httpclient.TwitchV5(repos.Config.ClientID, "GET", url, nil)
	if respError != nil {
		return ""
	}
	if resp != nil {
		defer resp.Body.Close()
	}
	var responseBody = new(followerResponse)

	marshallError := json.NewDecoder(resp.Body).Decode(responseBody)
	if marshallError != nil {

		return ""
	}
	return responseBody.Cursor
}
<|endoftext|>


<commit_before>package gitlab

import (
	"fmt"
	"time"
)

// PagesDomainsService handles communication with the pages domains
// related methods of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pages_domains.html
type PagesDomainsService struct {
	client *Client
}

// PagesDomain represents a pages domain.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pages_domains.html
type PagesDomain struct {
	Domain           string     `json:"domain"`
	AutoSslEnabled   bool       `json:"auto_ssl_enabled"`
	URL              string     `json:"url"`
	ProjectID        int        `json:"project_id"`
	Verified         bool       `json:"verified"`
	VerificationCode string     `json:"verification_code"`
	EnabledUntil     *time.Time `json:"enabled_until"`
	Certificate      struct {
		Expired    bool       `json:"expired"`
		Expiration *time.Time `json:"expiration"`
	} `json:"certificate"`
}

// ListPagesDomainsOptions represents the available ListPagesDomains() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#list-pages-domains
type ListPagesDomainsOptions ListOptions

// ListPagesDomains gets a list of project pages domains.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#list-pages-domains
func (s *PagesDomainsService) ListPagesDomains(pid interface{}, opt *ListPagesDomainsOptions, options ...OptionFunc) ([]*PagesDomain, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pages/domains", pathEscape(project))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var pd []*PagesDomain
	resp, err := s.client.Do(req, &pd)
	if err != nil {
		return nil, resp, err
	}

	return pd, resp, err
}

// ListAllPagesDomains gets a list of all pages domains.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#list-all-pages-domains
func (s *PagesDomainsService) ListAllPagesDomains(options ...OptionFunc) ([]*PagesDomain, *Response, error) {
	req, err := s.client.NewRequest("GET", "pages/domains", nil, options)
	if err != nil {
		return nil, nil, err
	}

	var pd []*PagesDomain
	resp, err := s.client.Do(req, &pd)
	if err != nil {
		return nil, resp, err
	}

	return pd, resp, err
}

// GetPagesDomain get a specific pages domain for a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#single-pages-domain
func (s *PagesDomainsService) GetPagesDomain(pid interface{}, domain string, options ...OptionFunc) (*PagesDomain, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pages/domains/%s", pathEscape(project), domain)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	pd := new(PagesDomain)
	resp, err := s.client.Do(req, pd)
	if err != nil {
		return nil, resp, err
	}

	return pd, resp, err
}

// CreatePagesDomainOptions represents the available CreatePagesDomain() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#create-new-pages-domain
type CreatePagesDomainOptions struct {
	Domain         *string `url:"domain,omitempty" json:"domain,omitempty"`
	AutoSslEnabled *bool   `url:"auto_ssl_enabled,omitempty" json:"auto_ssl_enabled,omitempty"`
	Certificate    *string `url:"certifiate,omitempty" json:"certifiate,omitempty"`
	Key            *string `url:"key,omitempty" json:"key,omitempty"`
}

// CreatePagesDomain creates a new project pages domain.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#create-new-pages-domain
func (s *PagesDomainsService) CreatePagesDomain(pid interface{}, opt *CreatePagesDomainOptions, options ...OptionFunc) (*PagesDomain, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pages/domains", pathEscape(project))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	pd := new(PagesDomain)
	resp, err := s.client.Do(req, pd)
	if err != nil {
		return nil, resp, err
	}

	return pd, resp, err
}

// UpdatePagesDomainOptions represents the available UpdatePagesDomain() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#update-pages-domain
type UpdatePagesDomainOptions struct {
	AutoSslEnabled *bool   `url:"auto_ssl_enabled,omitempty" json:"auto_ssl_enabled,omitempty"`
	Certificate    *string `url:"certifiate" json:"certifiate"`
	Key            *string `url:"key" json:"key"`
}

// UpdatePagesDomain updates an existing project pages domain.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#update-pages-domain
func (s *PagesDomainsService) UpdatePagesDomain(pid interface{}, domain string, opt *UpdatePagesDomainOptions, options ...OptionFunc) (*PagesDomain, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pages/domains/%s", pathEscape(project), domain)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	pd := new(PagesDomain)
	resp, err := s.client.Do(req, pd)
	if err != nil {
		return nil, resp, err
	}

	return pd, resp, err
}

// DeletePagesDomain deletes an existing prject pages domain.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#delete-pages-domain
func (s *PagesDomainsService) DeletePagesDomain(pid interface{}, domain string, options ...OptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/pages/domains/%s", pathEscape(project), domain)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<commit_msg>Add some missing struct options<commit_after>package gitlab

import (
	"fmt"
	"time"
)

// PagesDomainsService handles communication with the pages domains
// related methods of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pages_domains.html
type PagesDomainsService struct {
	client *Client
}

// PagesDomain represents a pages domain.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/pages_domains.html
type PagesDomain struct {
	Domain           string     `json:"domain"`
	AutoSslEnabled   bool       `json:"auto_ssl_enabled"`
	URL              string     `json:"url"`
	ProjectID        int        `json:"project_id"`
	Verified         bool       `json:"verified"`
	VerificationCode string     `json:"verification_code"`
	EnabledUntil     *time.Time `json:"enabled_until"`
	Certificate      struct {
		Expired    bool       `json:"expired"`
		Expiration *time.Time `json:"expiration"`
	} `json:"certificate"`
}

// ListPagesDomainsOptions represents the available ListPagesDomains() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#list-pages-domains
type ListPagesDomainsOptions ListOptions

// ListPagesDomains gets a list of project pages domains.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#list-pages-domains
func (s *PagesDomainsService) ListPagesDomains(pid interface{}, opt *ListPagesDomainsOptions, options ...OptionFunc) ([]*PagesDomain, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pages/domains", pathEscape(project))

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	var pd []*PagesDomain
	resp, err := s.client.Do(req, &pd)
	if err != nil {
		return nil, resp, err
	}

	return pd, resp, err
}

// ListAllPagesDomains gets a list of all pages domains.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#list-all-pages-domains
func (s *PagesDomainsService) ListAllPagesDomains(options ...OptionFunc) ([]*PagesDomain, *Response, error) {
	req, err := s.client.NewRequest("GET", "pages/domains", nil, options)
	if err != nil {
		return nil, nil, err
	}

	var pd []*PagesDomain
	resp, err := s.client.Do(req, &pd)
	if err != nil {
		return nil, resp, err
	}

	return pd, resp, err
}

// GetPagesDomain get a specific pages domain for a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#single-pages-domain
func (s *PagesDomainsService) GetPagesDomain(pid interface{}, domain string, options ...OptionFunc) (*PagesDomain, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pages/domains/%s", pathEscape(project), domain)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	pd := new(PagesDomain)
	resp, err := s.client.Do(req, pd)
	if err != nil {
		return nil, resp, err
	}

	return pd, resp, err
}

// CreatePagesDomainOptions represents the available CreatePagesDomain() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#create-new-pages-domain
type CreatePagesDomainOptions struct {
	Domain         *string `url:"domain,omitempty" json:"domain,omitempty"`
	AutoSslEnabled *bool   `url:"auto_ssl_enabled,omitempty" json:"auto_ssl_enabled,omitempty"`
	Certificate    *string `url:"certifiate,omitempty" json:"certifiate,omitempty"`
	Key            *string `url:"key,omitempty" json:"key,omitempty"`
}

// CreatePagesDomain creates a new project pages domain.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#create-new-pages-domain
func (s *PagesDomainsService) CreatePagesDomain(pid interface{}, opt *CreatePagesDomainOptions, options ...OptionFunc) (*PagesDomain, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pages/domains", pathEscape(project))

	req, err := s.client.NewRequest("POST", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	pd := new(PagesDomain)
	resp, err := s.client.Do(req, pd)
	if err != nil {
		return nil, resp, err
	}

	return pd, resp, err
}

// UpdatePagesDomainOptions represents the available UpdatePagesDomain() options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#update-pages-domain
type UpdatePagesDomainOptions struct {
	AutoSslEnabled *bool   `url:"auto_ssl_enabled,omitempty" json:"auto_ssl_enabled,omitempty"`
	Certificate    *string `url:"certifiate,omitempty" json:"certifiate,omitempty"`
	Key            *string `url:"key,omitempty" json:"key,omitempty"`
}

// UpdatePagesDomain updates an existing project pages domain.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#update-pages-domain
func (s *PagesDomainsService) UpdatePagesDomain(pid interface{}, domain string, opt *UpdatePagesDomainOptions, options ...OptionFunc) (*PagesDomain, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pages/domains/%s", pathEscape(project), domain)

	req, err := s.client.NewRequest("PUT", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	pd := new(PagesDomain)
	resp, err := s.client.Do(req, pd)
	if err != nil {
		return nil, resp, err
	}

	return pd, resp, err
}

// DeletePagesDomain deletes an existing prject pages domain.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/pages_domains.html#delete-pages-domain
func (s *PagesDomainsService) DeletePagesDomain(pid interface{}, domain string, options ...OptionFunc) (*Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, err
	}
	u := fmt.Sprintf("projects/%s/pages/domains/%s", pathEscape(project), domain)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, err
	}

	return s.client.Do(req, nil)
}
<|endoftext|>


<commit_before>// +build json

package point

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"
	"strings"

	"v2ray.com/core/app/dns"
	"v2ray.com/core/app/router"
	"v2ray.com/core/common"
	"v2ray.com/core/common/log"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/transport"
	"v2ray.com/core/transport/internet"
)

const (
	DefaultRefreshMinute = int(9999)
)

func (this *Config) UnmarshalJSON(data []byte) error {
	type JsonConfig struct {
		Port            v2net.Port                `json:"port"` // Port of this Point server.
		LogConfig       *LogConfig                `json:"log"`
		RouterConfig    *router.Config            `json:"routing"`
		DNSConfig       *dns.Config               `json:"dns"`
		InboundConfig   *InboundConnectionConfig  `json:"inbound"`
		OutboundConfig  *OutboundConnectionConfig `json:"outbound"`
		InboundDetours  []*InboundDetourConfig    `json:"inboundDetour"`
		OutboundDetours []*OutboundDetourConfig   `json:"outboundDetour"`
		Transport       *transport.Config         `json:"transport"`
	}
	jsonConfig := new(JsonConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse config: " + err.Error())
	}
	this.Port = jsonConfig.Port
	this.LogConfig = jsonConfig.LogConfig
	this.RouterConfig = jsonConfig.RouterConfig

	if jsonConfig.InboundConfig == nil {
		return errors.New("Point: Inbound config is not specified.")
	}
	this.InboundConfig = jsonConfig.InboundConfig

	if jsonConfig.OutboundConfig == nil {
		return errors.New("Point: Outbound config is not specified.")
	}
	this.OutboundConfig = jsonConfig.OutboundConfig
	this.InboundDetours = jsonConfig.InboundDetours
	this.OutboundDetours = jsonConfig.OutboundDetours
	if jsonConfig.DNSConfig == nil {
		jsonConfig.DNSConfig = &dns.Config{
			NameServers: []v2net.Destination{
				v2net.UDPDestination(v2net.DomainAddress("localhost"), v2net.Port(53)),
			},
		}
	}
	this.DNSConfig = jsonConfig.DNSConfig
	this.TransportConfig = jsonConfig.Transport
	return nil
}

func (this *InboundConnectionConfig) UnmarshalJSON(data []byte) error {
	type JsonConfig struct {
		Port          uint16                   `json:"port"`
		Listen        *v2net.AddressPB         `json:"listen"`
		Protocol      string                   `json:"protocol"`
		StreamSetting *internet.StreamSettings `json:"streamSettings"`
		Settings      json.RawMessage          `json:"settings"`
		AllowPassive  bool                     `json:"allowPassive"`
	}

	jsonConfig := new(JsonConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse inbound config: " + err.Error())
	}
	this.Port = v2net.Port(jsonConfig.Port)
	this.ListenOn = v2net.AnyIP
	if jsonConfig.Listen != nil {
		if jsonConfig.Listen.AsAddress().Family().IsDomain() {
			return errors.New("Point: Unable to listen on domain address: " + jsonConfig.Listen.AsAddress().Domain())
		}
		this.ListenOn = jsonConfig.Listen.AsAddress()
	}
	if jsonConfig.StreamSetting != nil {
		this.StreamSettings = jsonConfig.StreamSetting
	}

	this.Protocol = jsonConfig.Protocol
	this.Settings = jsonConfig.Settings
	this.AllowPassiveConnection = jsonConfig.AllowPassive
	return nil
}

func (this *OutboundConnectionConfig) UnmarshalJSON(data []byte) error {
	type JsonConnectionConfig struct {
		Protocol      string                   `json:"protocol"`
		SendThrough   *v2net.AddressPB         `json:"sendThrough"`
		StreamSetting *internet.StreamSettings `json:"streamSettings"`
		Settings      json.RawMessage          `json:"settings"`
	}
	jsonConfig := new(JsonConnectionConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse outbound config: " + err.Error())
	}
	this.Protocol = jsonConfig.Protocol
	this.Settings = jsonConfig.Settings

	if jsonConfig.SendThrough != nil {
		address := jsonConfig.SendThrough.AsAddress()
		if address.Family().IsDomain() {
			return errors.New("Point: Unable to send through: " + address.String())
		}
		this.SendThrough = address
	}
	if jsonConfig.StreamSetting != nil {
		this.StreamSettings = jsonConfig.StreamSetting
	}
	return nil
}

func (this *LogConfig) UnmarshalJSON(data []byte) error {
	type JsonLogConfig struct {
		AccessLog string `json:"access"`
		ErrorLog  string `json:"error"`
		LogLevel  string `json:"loglevel"`
	}
	jsonConfig := new(JsonLogConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse log config: " + err.Error())
	}
	this.AccessLog = jsonConfig.AccessLog
	this.ErrorLog = jsonConfig.ErrorLog

	level := strings.ToLower(jsonConfig.LogLevel)
	switch level {
	case "debug":
		this.LogLevel = log.DebugLevel
	case "info":
		this.LogLevel = log.InfoLevel
	case "error":
		this.LogLevel = log.ErrorLevel
	case "none":
		this.LogLevel = log.NoneLevel
	default:
		this.LogLevel = log.WarningLevel
	}
	return nil
}

func (this *InboundDetourAllocationConfig) UnmarshalJSON(data []byte) error {
	type JsonInboundDetourAllocationConfig struct {
		Strategy    string `json:"strategy"`
		Concurrency int    `json:"concurrency"`
		RefreshMin  int    `json:"refresh"`
	}
	jsonConfig := new(JsonInboundDetourAllocationConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse inbound detour allocation config: " + err.Error())
	}
	this.Strategy = jsonConfig.Strategy
	this.Concurrency = jsonConfig.Concurrency
	this.Refresh = jsonConfig.RefreshMin
	if this.Strategy == AllocationStrategyRandom {
		if this.Refresh == 0 {
			this.Refresh = 5
		}
		if this.Concurrency == 0 {
			this.Concurrency = 3
		}
	}
	if this.Refresh == 0 {
		this.Refresh = DefaultRefreshMinute
	}
	return nil
}

func (this *InboundDetourConfig) UnmarshalJSON(data []byte) error {
	type JsonInboundDetourConfig struct {
		Protocol      string                         `json:"protocol"`
		PortRange     *v2net.PortRange               `json:"port"`
		ListenOn      *v2net.AddressPB               `json:"listen"`
		Settings      json.RawMessage                `json:"settings"`
		Tag           string                         `json:"tag"`
		Allocation    *InboundDetourAllocationConfig `json:"allocate"`
		StreamSetting *internet.StreamSettings       `json:"streamSettings"`
		AllowPassive  bool                           `json:"allowPassive"`
	}
	jsonConfig := new(JsonInboundDetourConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse inbound detour config: " + err.Error())
	}
	if jsonConfig.PortRange == nil {
		log.Error("Point: Port range not specified in InboundDetour.")
		return common.ErrBadConfiguration
	}
	this.ListenOn = v2net.AnyIP
	if jsonConfig.ListenOn != nil {
		if jsonConfig.ListenOn.AsAddress().Family().IsDomain() {
			return errors.New("Point: Unable to listen on domain address: " + jsonConfig.ListenOn.AsAddress().Domain())
		}
		this.ListenOn = jsonConfig.ListenOn.AsAddress()
	}
	this.Protocol = jsonConfig.Protocol
	this.PortRange = *jsonConfig.PortRange
	this.Settings = jsonConfig.Settings
	this.Tag = jsonConfig.Tag
	this.Allocation = jsonConfig.Allocation
	if this.Allocation == nil {
		this.Allocation = &InboundDetourAllocationConfig{
			Strategy: AllocationStrategyAlways,
			Refresh:  DefaultRefreshMinute,
		}
	}
	if jsonConfig.StreamSetting != nil {
		this.StreamSettings = jsonConfig.StreamSetting
	}
	this.AllowPassiveConnection = jsonConfig.AllowPassive
	return nil
}

func (this *OutboundDetourConfig) UnmarshalJSON(data []byte) error {
	type JsonOutboundDetourConfig struct {
		Protocol      string                   `json:"protocol"`
		SendThrough   *v2net.AddressPB         `json:"sendThrough"`
		Tag           string                   `json:"tag"`
		Settings      json.RawMessage          `json:"settings"`
		StreamSetting *internet.StreamSettings `json:"streamSettings"`
	}
	jsonConfig := new(JsonOutboundDetourConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse outbound detour config: " + err.Error())
	}
	this.Protocol = jsonConfig.Protocol
	this.Tag = jsonConfig.Tag
	this.Settings = jsonConfig.Settings

	if jsonConfig.SendThrough != nil {
		address := jsonConfig.SendThrough.AsAddress()
		if address.Family().IsDomain() {
			return errors.New("Point: Unable to send through: " + address.String())
		}
		this.SendThrough = address
	}

	if jsonConfig.StreamSetting != nil {
		this.StreamSettings = jsonConfig.StreamSetting
	}
	return nil
}

func JsonLoadConfig(file string) (*Config, error) {
	fixedFile := os.ExpandEnv(file)
	rawConfig, err := ioutil.ReadFile(fixedFile)
	if err != nil {
		log.Error("Failed to read server config file (", file, "): ", file, err)
		return nil, err
	}

	jsonConfig := &Config{}
	err = json.Unmarshal(rawConfig, jsonConfig)
	if err != nil {
		log.Error("Failed to load server config: ", err)
		return nil, err
	}

	return jsonConfig, err
}

func init() {
	configLoader = JsonLoadConfig
}
<commit_msg>format log<commit_after>// +build json

package point

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"
	"strings"

	"v2ray.com/core/app/dns"
	"v2ray.com/core/app/router"
	"v2ray.com/core/common"
	"v2ray.com/core/common/log"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/transport"
	"v2ray.com/core/transport/internet"
)

const (
	DefaultRefreshMinute = int(9999)
)

func (this *Config) UnmarshalJSON(data []byte) error {
	type JsonConfig struct {
		Port            v2net.Port                `json:"port"` // Port of this Point server.
		LogConfig       *LogConfig                `json:"log"`
		RouterConfig    *router.Config            `json:"routing"`
		DNSConfig       *dns.Config               `json:"dns"`
		InboundConfig   *InboundConnectionConfig  `json:"inbound"`
		OutboundConfig  *OutboundConnectionConfig `json:"outbound"`
		InboundDetours  []*InboundDetourConfig    `json:"inboundDetour"`
		OutboundDetours []*OutboundDetourConfig   `json:"outboundDetour"`
		Transport       *transport.Config         `json:"transport"`
	}
	jsonConfig := new(JsonConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse config: " + err.Error())
	}
	this.Port = jsonConfig.Port
	this.LogConfig = jsonConfig.LogConfig
	this.RouterConfig = jsonConfig.RouterConfig

	if jsonConfig.InboundConfig == nil {
		return errors.New("Point: Inbound config is not specified.")
	}
	this.InboundConfig = jsonConfig.InboundConfig

	if jsonConfig.OutboundConfig == nil {
		return errors.New("Point: Outbound config is not specified.")
	}
	this.OutboundConfig = jsonConfig.OutboundConfig
	this.InboundDetours = jsonConfig.InboundDetours
	this.OutboundDetours = jsonConfig.OutboundDetours
	if jsonConfig.DNSConfig == nil {
		jsonConfig.DNSConfig = &dns.Config{
			NameServers: []v2net.Destination{
				v2net.UDPDestination(v2net.DomainAddress("localhost"), v2net.Port(53)),
			},
		}
	}
	this.DNSConfig = jsonConfig.DNSConfig
	this.TransportConfig = jsonConfig.Transport
	return nil
}

func (this *InboundConnectionConfig) UnmarshalJSON(data []byte) error {
	type JsonConfig struct {
		Port          uint16                   `json:"port"`
		Listen        *v2net.AddressPB         `json:"listen"`
		Protocol      string                   `json:"protocol"`
		StreamSetting *internet.StreamSettings `json:"streamSettings"`
		Settings      json.RawMessage          `json:"settings"`
		AllowPassive  bool                     `json:"allowPassive"`
	}

	jsonConfig := new(JsonConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse inbound config: " + err.Error())
	}
	this.Port = v2net.Port(jsonConfig.Port)
	this.ListenOn = v2net.AnyIP
	if jsonConfig.Listen != nil {
		if jsonConfig.Listen.AsAddress().Family().IsDomain() {
			return errors.New("Point: Unable to listen on domain address: " + jsonConfig.Listen.AsAddress().Domain())
		}
		this.ListenOn = jsonConfig.Listen.AsAddress()
	}
	if jsonConfig.StreamSetting != nil {
		this.StreamSettings = jsonConfig.StreamSetting
	}

	this.Protocol = jsonConfig.Protocol
	this.Settings = jsonConfig.Settings
	this.AllowPassiveConnection = jsonConfig.AllowPassive
	return nil
}

func (this *OutboundConnectionConfig) UnmarshalJSON(data []byte) error {
	type JsonConnectionConfig struct {
		Protocol      string                   `json:"protocol"`
		SendThrough   *v2net.AddressPB         `json:"sendThrough"`
		StreamSetting *internet.StreamSettings `json:"streamSettings"`
		Settings      json.RawMessage          `json:"settings"`
	}
	jsonConfig := new(JsonConnectionConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse outbound config: " + err.Error())
	}
	this.Protocol = jsonConfig.Protocol
	this.Settings = jsonConfig.Settings

	if jsonConfig.SendThrough != nil {
		address := jsonConfig.SendThrough.AsAddress()
		if address.Family().IsDomain() {
			return errors.New("Point: Unable to send through: " + address.String())
		}
		this.SendThrough = address
	}
	if jsonConfig.StreamSetting != nil {
		this.StreamSettings = jsonConfig.StreamSetting
	}
	return nil
}

func (this *LogConfig) UnmarshalJSON(data []byte) error {
	type JsonLogConfig struct {
		AccessLog string `json:"access"`
		ErrorLog  string `json:"error"`
		LogLevel  string `json:"loglevel"`
	}
	jsonConfig := new(JsonLogConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse log config: " + err.Error())
	}
	this.AccessLog = jsonConfig.AccessLog
	this.ErrorLog = jsonConfig.ErrorLog

	level := strings.ToLower(jsonConfig.LogLevel)
	switch level {
	case "debug":
		this.LogLevel = log.DebugLevel
	case "info":
		this.LogLevel = log.InfoLevel
	case "error":
		this.LogLevel = log.ErrorLevel
	case "none":
		this.LogLevel = log.NoneLevel
	default:
		this.LogLevel = log.WarningLevel
	}
	return nil
}

func (this *InboundDetourAllocationConfig) UnmarshalJSON(data []byte) error {
	type JsonInboundDetourAllocationConfig struct {
		Strategy    string `json:"strategy"`
		Concurrency int    `json:"concurrency"`
		RefreshMin  int    `json:"refresh"`
	}
	jsonConfig := new(JsonInboundDetourAllocationConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse inbound detour allocation config: " + err.Error())
	}
	this.Strategy = jsonConfig.Strategy
	this.Concurrency = jsonConfig.Concurrency
	this.Refresh = jsonConfig.RefreshMin
	if this.Strategy == AllocationStrategyRandom {
		if this.Refresh == 0 {
			this.Refresh = 5
		}
		if this.Concurrency == 0 {
			this.Concurrency = 3
		}
	}
	if this.Refresh == 0 {
		this.Refresh = DefaultRefreshMinute
	}
	return nil
}

func (this *InboundDetourConfig) UnmarshalJSON(data []byte) error {
	type JsonInboundDetourConfig struct {
		Protocol      string                         `json:"protocol"`
		PortRange     *v2net.PortRange               `json:"port"`
		ListenOn      *v2net.AddressPB               `json:"listen"`
		Settings      json.RawMessage                `json:"settings"`
		Tag           string                         `json:"tag"`
		Allocation    *InboundDetourAllocationConfig `json:"allocate"`
		StreamSetting *internet.StreamSettings       `json:"streamSettings"`
		AllowPassive  bool                           `json:"allowPassive"`
	}
	jsonConfig := new(JsonInboundDetourConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse inbound detour config: " + err.Error())
	}
	if jsonConfig.PortRange == nil {
		log.Error("Point: Port range not specified in InboundDetour.")
		return common.ErrBadConfiguration
	}
	this.ListenOn = v2net.AnyIP
	if jsonConfig.ListenOn != nil {
		if jsonConfig.ListenOn.AsAddress().Family().IsDomain() {
			return errors.New("Point: Unable to listen on domain address: " + jsonConfig.ListenOn.AsAddress().Domain())
		}
		this.ListenOn = jsonConfig.ListenOn.AsAddress()
	}
	this.Protocol = jsonConfig.Protocol
	this.PortRange = *jsonConfig.PortRange
	this.Settings = jsonConfig.Settings
	this.Tag = jsonConfig.Tag
	this.Allocation = jsonConfig.Allocation
	if this.Allocation == nil {
		this.Allocation = &InboundDetourAllocationConfig{
			Strategy: AllocationStrategyAlways,
			Refresh:  DefaultRefreshMinute,
		}
	}
	if jsonConfig.StreamSetting != nil {
		this.StreamSettings = jsonConfig.StreamSetting
	}
	this.AllowPassiveConnection = jsonConfig.AllowPassive
	return nil
}

func (this *OutboundDetourConfig) UnmarshalJSON(data []byte) error {
	type JsonOutboundDetourConfig struct {
		Protocol      string                   `json:"protocol"`
		SendThrough   *v2net.AddressPB         `json:"sendThrough"`
		Tag           string                   `json:"tag"`
		Settings      json.RawMessage          `json:"settings"`
		StreamSetting *internet.StreamSettings `json:"streamSettings"`
	}
	jsonConfig := new(JsonOutboundDetourConfig)
	if err := json.Unmarshal(data, jsonConfig); err != nil {
		return errors.New("Point: Failed to parse outbound detour config: " + err.Error())
	}
	this.Protocol = jsonConfig.Protocol
	this.Tag = jsonConfig.Tag
	this.Settings = jsonConfig.Settings

	if jsonConfig.SendThrough != nil {
		address := jsonConfig.SendThrough.AsAddress()
		if address.Family().IsDomain() {
			return errors.New("Point: Unable to send through: " + address.String())
		}
		this.SendThrough = address
	}

	if jsonConfig.StreamSetting != nil {
		this.StreamSettings = jsonConfig.StreamSetting
	}
	return nil
}

func JsonLoadConfig(file string) (*Config, error) {
	fixedFile := os.ExpandEnv(file)
	rawConfig, err := ioutil.ReadFile(fixedFile)
	if err != nil {
		log.Error("Point: Failed to read server config file (", file, "): ", file, err)
		return nil, err
	}

	jsonConfig := &Config{}
	err = json.Unmarshal(rawConfig, jsonConfig)
	if err != nil {
		log.Error("Point: Failed to load server config: ", err)
		return nil, err
	}

	return jsonConfig, err
}

func init() {
	configLoader = JsonLoadConfig
}
<|endoftext|>


<commit_before>// Copyright 2014 GoIncremental Limited. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package oauth2 contains Negroni middleware to provide
// user login via an OAuth 2.0 backend.

package oauth2

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"github.com/codegangsta/negroni"
	"github.com/goincremental/negroni-sessions"
	"github.com/golang/oauth2"
)

const (
	keyToken    = "oauth2_token"
	keyNextPage = "next"
)

var (
	// Path to handle OAuth 2.0 logins.
	PathLogin = "/login"
	// Path to handle OAuth 2.0 logouts.
	PathLogout = "/logout"
	// Path to handle callback from OAuth 2.0 backend
	// to exchange credentials.
	PathCallback = "/oauth2callback"
	// Path to handle error cases.
	PathError = "/oauth2error"
)

type Options struct {
	// ClientID is the OAuth client identifier used when communicating with
	// the configured OAuth provider.
	ClientID string `json:"client_id"`

	// ClientSecret is the OAuth client secret used when communicating with
	// the configured OAuth provider.
	ClientSecret string `json:"client_secret"`

	// RedirectURL is the URL to which the user will be returned after
	// granting (or denying) access.
	RedirectURL string `json:"redirect_url"`

	// Optional, identifies the level of access being requested.
	Scopes []string `json:"scopes"`

	// Optional, "online" (default) or "offline", no refresh token if "online"
	AccessType string `json:"omit"`

	// ApprovalPrompt indicates whether the user should be
	// re-prompted for consent. If set to "auto" (default) the
	// user will be prompted only if they haven't previously
	// granted consent and the code can only be exchanged for an
	// access token.
	// If set to "force" the user will always be prompted, and the
	// code can be exchanged for a refresh token.
	ApprovalPrompt string `json:"omit"`
}

// Represents a container that contains
// user's OAuth 2.0 access and refresh tokens.
type Tokens interface {
	Access() string
	Refresh() string
	IsExpired() bool
	ExpiryTime() time.Time
	ExtraData() map[string]string
}

type token struct {
	oauth2.Token
}

func (t *token) ExtraData() map[string]string {
	return t.Extra
}

// Returns the access token.
func (t *token) Access() string {
	return t.AccessToken
}

// Returns the refresh token.
func (t *token) Refresh() string {
	return t.RefreshToken
}

// Returns whether the access token is
// expired or not.
func (t *token) IsExpired() bool {
	if t == nil {
		return true
	}
	return t.Expired()
}

// Returns the expiry time of the user's
// access token.
func (t *token) ExpiryTime() time.Time {
	return t.Expiry
}

// // Formats tokens into string.
// func (t *token) String() string {
// 	return fmt.Sprintf("tokens: %v", t)
// }

// Returns a new Google OAuth 2.0 backend endpoint.
func Google(opts *Options) negroni.Handler {
	authUrl := "https://accounts.google.com/o/oauth2/auth"
	tokenUrl := "https://accounts.google.com/o/oauth2/token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

// Returns a new Github OAuth 2.0 backend endpoint.
func Github(opts *Options) negroni.Handler {
	authUrl := "https://github.com/login/oauth/authorize"
	tokenUrl := "https://github.com/login/oauth/access_token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

func Facebook(opts *Options) negroni.Handler {
	authUrl := "https://www.facebook.com/dialog/oauth"
	tokenUrl := "https://graph.facebook.com/oauth/access_token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

func LinkedIn(opts *Options) negroni.Handler {
	authUrl := "https://www.linkedin.com/uas/oauth2/authorization"
	tokenUrl := "https://www.linkedin.com/uas/oauth2/accessToken"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

// Returns a generic OAuth 2.0 backend endpoint.
func NewOAuth2Provider(opts *Options, authUrl, tokenUrl string) negroni.HandlerFunc {

	options := &oauth2.Options{
		ClientID:     opts.ClientID,
		ClientSecret: opts.ClientSecret,
		RedirectURL:  opts.RedirectURL,
		Scopes:       opts.Scopes,
	}

	config, err := oauth2.NewConfig(options, authUrl, tokenUrl)
	if err != nil {
		panic(fmt.Sprintf("oauth2: %s", err))
	}

	return func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		s := sessions.GetSession(r)

		if r.Method == "GET" {
			switch r.URL.Path {
			case PathLogin:
				login(opts, config, s, rw, r)
			case PathLogout:
				logout(s, rw, r)
			case PathCallback:
				handleOAuth2Callback(config, s, rw, r)
			default:
				next(rw, r)
			}
		} else {
			next(rw, r)
		}

	}
}

func GetToken(r *http.Request) Tokens {
	s := sessions.GetSession(r)
	t := unmarshallToken(s)

	//not doing this doesn't pass through the
	//nil return, causing a test to fail - not sure why??
	if t == nil {
		return nil
	} else {
		return t
	}
}

func SetToken(r *http.Request, t interface{}) {
	s := sessions.GetSession(r)
	val, _ := json.Marshal(t)
	s.Set(keyToken, val)
	//Check immediately to see if the token is expired
	tk := unmarshallToken(s)
	if tk != nil {
		// check if the access token is expired
		if tk.IsExpired() && tk.Refresh() == "" {
			s.Delete(keyToken)
			tk = nil
		}
	}
}

// Handler that redirects user to the login page
// if user is not logged in.
func LoginRequired() negroni.HandlerFunc {
	return func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		s := sessions.GetSession(r)
		token := unmarshallToken(s)
		if token == nil || token.IsExpired() {
			// Set token to null to avoid redirection loop
			SetToken(r, nil)
			next := url.QueryEscape(r.URL.RequestURI())
			http.Redirect(rw, r, PathLogin+"?next="+next, http.StatusFound)
		} else {
			next(rw, r)
		}
	}
}

func login(opts *Options, c *oauth2.Config, s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get(keyNextPage))
	if s.Get(keyToken) == nil {
		// User is not logged in.
		if next == "" {
			next = "/"
		}
		http.Redirect(w, r, c.AuthCodeURL(next, opts.AccessType, opts.ApprovalPrompt), http.StatusFound)
		return
	}
	// No need to login, redirect to the next page.
	http.Redirect(w, r, next, http.StatusFound)
}

func logout(s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get(keyNextPage))
	s.Delete(keyToken)
	http.Redirect(w, r, next, http.StatusFound)
}

func handleOAuth2Callback(c *oauth2.Config, s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get("state"))
	code := r.URL.Query().Get("code")
	t, err := c.NewTransportWithCode(code)
	if err != nil {
		// Pass the error message, or allow dev to provide its own
		// error handler.
		http.Redirect(w, r, PathError, http.StatusFound)
		return
	}
	// Store the credentials in the session.
	val, _ := json.Marshal(t.Token())
	s.Set(keyToken, val)
	http.Redirect(w, r, next, http.StatusFound)
}

func unmarshallToken(s sessions.Session) *token {

	if s.Get(keyToken) == nil {
		return nil
	}

	data := s.Get(keyToken).([]byte)
	var tk oauth2.Token
	json.Unmarshal(data, &tk)
	return &token{tk}

}

func extractPath(next string) string {
	n, err := url.Parse(next)
	if err != nil {
		return "/"
	}
	return n.Path
}
<commit_msg>Made updates to match golang/oauth2 latest (breaking) changes.<commit_after>// Copyright 2014 GoIncremental Limited. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package oauth2 contains Negroni middleware to provide
// user login via an OAuth 2.0 backend.

package oauth2

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"github.com/codegangsta/negroni"
	"github.com/goincremental/negroni-sessions"
	"github.com/golang/oauth2"
)

const (
	keyToken    = "oauth2_token"
	keyNextPage = "next"
)

var (
	// Path to handle OAuth 2.0 logins.
	PathLogin = "/login"
	// Path to handle OAuth 2.0 logouts.
	PathLogout = "/logout"
	// Path to handle callback from OAuth 2.0 backend
	// to exchange credentials.
	PathCallback = "/oauth2callback"
	// Path to handle error cases.
	PathError = "/oauth2error"
)

type Options struct {
	// ClientID is the OAuth client identifier used when communicating with
	// the configured OAuth provider.
	ClientID string `json:"client_id"`

	// ClientSecret is the OAuth client secret used when communicating with
	// the configured OAuth provider.
	ClientSecret string `json:"client_secret"`

	// RedirectURL is the URL to which the user will be returned after
	// granting (or denying) access.
	RedirectURL string `json:"redirect_url"`

	// Optional, identifies the level of access being requested.
	Scopes []string `json:"scopes"`

	// Optional, "online" (default) or "offline", no refresh token if "online"
	AccessType string `json:"omit"`

	// ApprovalPrompt indicates whether the user should be
	// re-prompted for consent. If set to "auto" (default) the
	// user will be prompted only if they haven't previously
	// granted consent and the code can only be exchanged for an
	// access token.
	// If set to "force" the user will always be prompted, and the
	// code can be exchanged for a refresh token.
	ApprovalPrompt string `json:"omit"`
}

// Represents a container that contains
// user's OAuth 2.0 access and refresh tokens.
type Tokens interface {
	Access() string
	Refresh() string
	IsExpired() bool
	ExpiryTime() time.Time
	ExtraData(string) string
}

type token struct {
	oauth2.Token
}

func (t *token) ExtraData(key string) string {
	return t.Extra(key)
}

// Returns the access token.
func (t *token) Access() string {
	return t.AccessToken
}

// Returns the refresh token.
func (t *token) Refresh() string {
	return t.RefreshToken
}

// Returns whether the access token is
// expired or not.
func (t *token) IsExpired() bool {
	if t == nil {
		return true
	}
	return t.Expired()
}

// Returns the expiry time of the user's
// access token.
func (t *token) ExpiryTime() time.Time {
	return t.Expiry
}

// // Formats tokens into string.
// func (t *token) String() string {
// 	return fmt.Sprintf("tokens: %v", t)
// }

// Returns a new Google OAuth 2.0 backend endpoint.
func Google(opts *Options) negroni.Handler {
	authUrl := "https://accounts.google.com/o/oauth2/auth"
	tokenUrl := "https://accounts.google.com/o/oauth2/token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

// Returns a new Github OAuth 2.0 backend endpoint.
func Github(opts *Options) negroni.Handler {
	authUrl := "https://github.com/login/oauth/authorize"
	tokenUrl := "https://github.com/login/oauth/access_token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

func Facebook(opts *Options) negroni.Handler {
	authUrl := "https://www.facebook.com/dialog/oauth"
	tokenUrl := "https://graph.facebook.com/oauth/access_token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

func LinkedIn(opts *Options) negroni.Handler {
	authUrl := "https://www.linkedin.com/uas/oauth2/authorization"
	tokenUrl := "https://www.linkedin.com/uas/oauth2/accessToken"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

// Returns a generic OAuth 2.0 backend endpoint.
func NewOAuth2Provider(opts *Options, authUrl, tokenUrl string) negroni.HandlerFunc {

	options := &oauth2.Options{
		ClientID:     opts.ClientID,
		ClientSecret: opts.ClientSecret,
		RedirectURL:  opts.RedirectURL,
		Scopes:       opts.Scopes,
	}

	config, err := oauth2.NewConfig(options, authUrl, tokenUrl)
	if err != nil {
		panic(fmt.Sprintf("oauth2: %s", err))
	}

	return func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		s := sessions.GetSession(r)

		if r.Method == "GET" {
			switch r.URL.Path {
			case PathLogin:
				login(opts, config, s, rw, r)
			case PathLogout:
				logout(s, rw, r)
			case PathCallback:
				handleOAuth2Callback(config, s, rw, r)
			default:
				next(rw, r)
			}
		} else {
			next(rw, r)
		}

	}
}

func GetToken(r *http.Request) Tokens {
	s := sessions.GetSession(r)
	t := unmarshallToken(s)

	//not doing this doesn't pass through the
	//nil return, causing a test to fail - not sure why??
	if t == nil {
		return nil
	} else {
		return t
	}
}

func SetToken(r *http.Request, t interface{}) {
	s := sessions.GetSession(r)
	val, _ := json.Marshal(t)
	s.Set(keyToken, val)
	//Check immediately to see if the token is expired
	tk := unmarshallToken(s)
	if tk != nil {
		// check if the access token is expired
		if tk.IsExpired() && tk.Refresh() == "" {
			s.Delete(keyToken)
			tk = nil
		}
	}
}

// Handler that redirects user to the login page
// if user is not logged in.
func LoginRequired() negroni.HandlerFunc {
	return func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
		s := sessions.GetSession(r)
		token := unmarshallToken(s)
		if token == nil || token.IsExpired() {
			// Set token to null to avoid redirection loop
			SetToken(r, nil)
			next := url.QueryEscape(r.URL.RequestURI())
			http.Redirect(rw, r, PathLogin+"?next="+next, http.StatusFound)
		} else {
			next(rw, r)
		}
	}
}

func login(opts *Options, c *oauth2.Config, s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get(keyNextPage))
	if s.Get(keyToken) == nil {
		// User is not logged in.
		if next == "" {
			next = "/"
		}
		http.Redirect(w, r, c.AuthCodeURL(next, opts.AccessType, opts.ApprovalPrompt), http.StatusFound)
		return
	}
	// No need to login, redirect to the next page.
	http.Redirect(w, r, next, http.StatusFound)
}

func logout(s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get(keyNextPage))
	s.Delete(keyToken)
	http.Redirect(w, r, next, http.StatusFound)
}

func handleOAuth2Callback(c *oauth2.Config, s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get("state"))
	code := r.URL.Query().Get("code")
	t, err := c.NewTransportWithCode(code)
	if err != nil {
		// Pass the error message, or allow dev to provide its own
		// error handler.
		http.Redirect(w, r, PathError, http.StatusFound)
		return
	}
	// Store the credentials in the session.
	val, _ := json.Marshal(t.Token())
	s.Set(keyToken, val)
	http.Redirect(w, r, next, http.StatusFound)
}

func unmarshallToken(s sessions.Session) *token {

	if s.Get(keyToken) == nil {
		return nil
	}

	data := s.Get(keyToken).([]byte)
	var tk oauth2.Token
	json.Unmarshal(data, &tk)
	return &token{tk}

}

func extractPath(next string) string {
	n, err := url.Parse(next)
	if err != nil {
		return "/"
	}
	return n.Path
}
<|endoftext|>


<commit_before>package jsh

import (
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"

	"github.com/asaskevich/govalidator"
)

// Object represents the default JSON spec for objects
type Object struct {
	Type          string                   `json:"type" valid:"required"`
	ID            string                   `json:"id"`
	Attributes    json.RawMessage          `json:"attributes,omitempty"`
	Links         map[string]*Link         `json:"links,omitempty"`
	Relationships map[string]*Relationship `json:"relationships,omitempty"`
	Meta          map[string]interface{}   `json:"meta,omitempty"`
	// Status is the HTTP Status Code that should be associated with the object
	// when it is sent.
	Status int `json:"-"`
}

// NewObject prepares a new JSON Object for an API response. Whatever is provided
// as attributes will be marshalled to JSON.
func NewObject(id string, resourceType string, attributes interface{}) (*Object, *Error) {
	object := &Object{
		ID:            id,
		Type:          resourceType,
		Links:         map[string]*Link{},
		Relationships: map[string]*Relationship{},
	}

	rawJSON, err := json.MarshalIndent(attributes, "", " ")
	if err != nil {
		return nil, ISE(fmt.Sprintf("Error marshaling attrs while creating a new JSON Object: %s", err))
	}

	object.Attributes = rawJSON
	return object, nil
}

/*
Unmarshal puts an Object's Attributes into a more useful target resourceType defined
by the user. A correct object resourceType specified must also be provided otherwise
an error is returned to prevent hard to track down situations.

Optionally, used https://github.com/go-validator/validator for request input validation.
Simply define your struct with valid input tags:

	struct {
		Username string `json:"username" valid:"required,alphanum"`
	}

As the final action, the Unmarshal function will run govalidator on the unmarshal
result. If the validator fails, a Sendable error response of HTTP Status 422 will
be returned containing each validation error with a populated Error.Source.Pointer
specifying each struct attribute that failed. In this case, all you need to do is:

	errors := obj.Unmarshal("mytype", &myType)
	if errors != nil {
		// log errors via error.ISE
		jsh.Send(w, r, errors)
	}
*/
func (o *Object) Unmarshal(resourceType string, target interface{}) ErrorList {

	if resourceType != o.Type {
		return []*Error{ConflictError(o.Type, "")}
	}

	jsonErr := json.Unmarshal(o.Attributes, target)
	if jsonErr != nil {
		return []*Error{BadRequestError(fmt.Sprintf(
			"For type '%s' unable to unmarshal",
			resourceType,
		), jsonErr.Error())}
	}

	return validateInput(target)
}

/*
Marshal allows you to load a modified payload back into an object to preserve
all of the data it has.
*/
func (o *Object) Marshal(attributes interface{}) *Error {
	raw, err := json.MarshalIndent(attributes, "", " ")
	if err != nil {
		return ISE(fmt.Sprintf("Error marshaling attrs while creating a new JSON Object: %s", err))
	}

	o.Attributes = raw
	return nil
}

/*
Validate ensures that an object is JSON API compatible. Has a side effect of also
setting the Object's Status attribute to be used as the Response HTTP Code if one
has not already been set.
*/
func (o *Object) Validate(r *http.Request, response bool) *Error {

	if o.ID == "" {

		// don't error if the client is attempting to performing a POST request, in
		// which case, ID shouldn't actually be set
		if !response && r.Method != "POST" {
			return SpecificationError("ID must be set for Object response")
		}
	}

	if o.Type == "" {
		return SpecificationError("Type must be set for Object response")
	}

	switch r.Method {
	case "POST":
		acceptable := map[int]bool{201: true, 202: true, 204: true}

		if o.Status != 0 {
			if _, validCode := acceptable[o.Status]; !validCode {
				return SpecificationError("POST Status must be one of 201, 202, or 204.")
			}
			break
		}

		o.Status = http.StatusCreated
		break
	case "PATCH":
		acceptable := map[int]bool{200: true, 202: true, 204: true}

		if o.Status != 0 {
			if _, validCode := acceptable[o.Status]; !validCode {
				return SpecificationError("PATCH Status must be one of 200, 202, or 204.")
			}
			break
		}

		o.Status = http.StatusOK
		break
	case "HEAD":
		fallthrough
	case "GET":
		o.Status = http.StatusOK
		break
	// If we hit this it means someone is attempting to use an unsupported HTTP
	// method. Return a 406 error instead
	default:
		return SpecificationError(fmt.Sprintf(
			"The JSON Specification does not accept '%s' requests.",
			r.Method,
		))
	}

	return nil
}

/*
ProcessCreate unmarshals the object to the given struct (see Object.Unmarshal) and uses JSH tags
to validate that there is no missing attributes or forbidden ones.

Simply define your struct with jsh tags to allow for the model to be created with the tagged attributes.

	struct {
		Username string `json:"username" jsh:"create"`
	}

You can also add a required option to ensure a specific attribute is non-zero.

	struct {
		Username string `json:"username" jsh:"create/required"`
	}

The model must be a non-nil pointer to a struct.
If valid, the model contains the valid request attributes after the call (even on validation error)

The string slice returned contains the name of the attributes that were unmarshaled to the model.
*/
func (o *Object) ProcessCreate(resourceType string, model interface{}) ([]string, ErrorList) {
	return o.process(tagCreate, resourceType, model)
}

/*
ProcessUpdate unmarshals the object to the given struct (see Object.Unmarshal) and uses JSH tags
to validate that there is no missing attributes or forbidden ones.

Simply define your struct with jsh tags to allow for the model to be created with the tagged attributes.

	struct {
		Username string `json:"username" jsh:"update"`
	}

You can also add a required option to ensure a specific attribute is non-zero.

	struct {
		Username string `json:"username" jsh:"update/required"`
	}

The model must be a non-nil pointer to a struct.
If valid, the model contains the valid request attributes after the call (even on validation error)

If dest is not nil, it must be of the same type than model and it will be updated with the valid attributes.
Pass nil if you want to handle the update yourself.

The string slice returned contains the names of the attributes that were unmarshaled to the model.
If dest is not nil, it also represents the name of the updated attributes.
*/
func (o *Object) ProcessUpdate(resourceType string, model interface{}, dest interface{}) ([]string, ErrorList) {
	// Assert model is of the same type than dest
	drv := reflect.ValueOf(dest)
	if drv.IsValid() && drv.Type() != reflect.TypeOf(model) {
		return nil, ErrorList{ISE(fmt.Sprintf("The arguments to %s should be of the same type", tagUpdate))}
	}
	attrs, err := o.process(tagUpdate, resourceType, model)
	if err != nil {
		return nil, err
	}
	// Return if no dest is provided
	if !drv.IsValid() {
		return attrs, nil
	}
	// Update dest with decoded values
	mrv := reflect.ValueOf(model).Elem()
	drv = drv.Elem()
	for _, attr := range attrs {
		// NOTE: We can assume field names are correct
		drv.FieldByName(attr).Set(mrv.FieldByName(attr))
	}
	return attrs, nil
}

// String prints a formatted string representation of the object
func (o *Object) String() string {
	raw, err := json.MarshalIndent(o, "", " ")
	if err != nil {
		return err.Error()
	}

	return string(raw)
}

// validateInput runs go-validator on each attribute on the struct and returns all
// errors that it picks up
func validateInput(target interface{}) ErrorList {

	_, validationError := govalidator.ValidateStruct(target)
	if validationError != nil {

		errorList, isType := validationError.(govalidator.Errors)
		if isType {

			errors := ErrorList{}
			for _, singleErr := range errorList.Errors() {

				// parse out validation error
				goValidErr, _ := singleErr.(govalidator.Error)
				inputErr := InputError(goValidErr.Err.Error(), goValidErr.Name)

				errors = append(errors, inputErr)
			}

			return errors
		}
	}

	return nil
}

// process validates that the object's attributes are valid for the given action.
// It unmarshals the attributes to the model's fields that are tagged with the action.
func (o *Object) process(action, resourceType string, model interface{}) ([]string, ErrorList) {
	// Check argument is a non-nil pointer
	rv := reflect.ValueOf(model)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
		return nil, ErrorList{ISE(fmt.Sprintf("The argument to %s must be a non-nil pointer", action))}
	}
	// Get pointer element
	rv = rv.Elem()
	// Check pointer element is a struct
	if rv.Kind() != reflect.Struct {
		return nil, ErrorList{ISE(fmt.Sprintf("The argument to %s must be a pointer to a struct", action))}
	}
	// Unmarshal to model and validates input against govalidator rules
	err := o.Unmarshal(resourceType, model)
	if err != nil {
		return nil, err
	}
	// Look for missing and forbidden attributes for action
	return validateStruct(rv, action)
}
<commit_msg>Add support for nil attributes<commit_after>package jsh

import (
	"encoding/json"
	"fmt"
	"net/http"
	"reflect"

	"github.com/asaskevich/govalidator"
)

// Object represents the default JSON spec for objects
type Object struct {
	Type          string                   `json:"type" valid:"required"`
	ID            string                   `json:"id"`
	Attributes    json.RawMessage          `json:"attributes,omitempty"`
	Links         map[string]*Link         `json:"links,omitempty"`
	Relationships map[string]*Relationship `json:"relationships,omitempty"`
	Meta          map[string]interface{}   `json:"meta,omitempty"`
	// Status is the HTTP Status Code that should be associated with the object
	// when it is sent.
	Status int `json:"-"`
}

// NewObject prepares a new JSON Object for an API response. Whatever is provided
// as attributes will be marshalled to JSON.
func NewObject(id string, resourceType string, attributes interface{}) (*Object, *Error) {
	object := &Object{
		ID:            id,
		Type:          resourceType,
		Links:         map[string]*Link{},
		Relationships: map[string]*Relationship{},
	}
	if err := object.Marshal(attributes); err != nil {
		return nil, err
	}
	return object, nil
}

/*
Unmarshal puts an Object's Attributes into a more useful target resourceType defined
by the user. A correct object resourceType specified must also be provided otherwise
an error is returned to prevent hard to track down situations.

Optionally, used https://github.com/go-validator/validator for request input validation.
Simply define your struct with valid input tags:

	struct {
		Username string `json:"username" valid:"required,alphanum"`
	}

As the final action, the Unmarshal function will run govalidator on the unmarshal
result. If the validator fails, a Sendable error response of HTTP Status 422 will
be returned containing each validation error with a populated Error.Source.Pointer
specifying each struct attribute that failed. In this case, all you need to do is:

	errors := obj.Unmarshal("mytype", &myType)
	if errors != nil {
		// log errors via error.ISE
		jsh.Send(w, r, errors)
	}
*/
func (o *Object) Unmarshal(resourceType string, target interface{}) ErrorList {

	if resourceType != o.Type {
		return []*Error{ConflictError(o.Type, "")}
	}

	if len(o.Attributes) == 0 {
		return nil
	}

	jsonErr := json.Unmarshal(o.Attributes, target)
	if jsonErr != nil {
		return []*Error{BadRequestError(fmt.Sprintf(
			"For type '%s' unable to unmarshal",
			resourceType,
		), jsonErr.Error())}
	}

	return validateInput(target)
}

/*
Marshal allows you to load a modified payload back into an object to preserve
all of the data it has.
*/
func (o *Object) Marshal(attributes interface{}) *Error {
	if attributes == nil {
		o.Attributes = json.RawMessage{}
		return nil
	}
	raw, err := json.MarshalIndent(attributes, "", " ")
	if err != nil {
		return ISE(fmt.Sprintf("Error marshaling attrs while creating a new JSON Object: %s", err))
	}

	o.Attributes = raw
	return nil
}

/*
Validate ensures that an object is JSON API compatible. Has a side effect of also
setting the Object's Status attribute to be used as the Response HTTP Code if one
has not already been set.
*/
func (o *Object) Validate(r *http.Request, response bool) *Error {

	if o.ID == "" {

		// don't error if the client is attempting to performing a POST request, in
		// which case, ID shouldn't actually be set
		if !response && r.Method != "POST" {
			return SpecificationError("ID must be set for Object response")
		}
	}

	if o.Type == "" {
		return SpecificationError("Type must be set for Object response")
	}

	switch r.Method {
	case "POST":
		acceptable := map[int]bool{201: true, 202: true, 204: true}

		if o.Status != 0 {
			if _, validCode := acceptable[o.Status]; !validCode {
				return SpecificationError("POST Status must be one of 201, 202, or 204.")
			}
			break
		}

		o.Status = http.StatusCreated
		break
	case "PATCH":
		acceptable := map[int]bool{200: true, 202: true, 204: true}

		if o.Status != 0 {
			if _, validCode := acceptable[o.Status]; !validCode {
				return SpecificationError("PATCH Status must be one of 200, 202, or 204.")
			}
			break
		}

		o.Status = http.StatusOK
		break
	case "HEAD":
		fallthrough
	case "GET":
		o.Status = http.StatusOK
		break
	// If we hit this it means someone is attempting to use an unsupported HTTP
	// method. Return a 406 error instead
	default:
		return SpecificationError(fmt.Sprintf(
			"The JSON Specification does not accept '%s' requests.",
			r.Method,
		))
	}

	return nil
}

/*
ProcessCreate unmarshals the object to the given struct (see Object.Unmarshal) and uses JSH tags
to validate that there is no missing attributes or forbidden ones.

Simply define your struct with jsh tags to allow for the model to be created with the tagged attributes.

	struct {
		Username string `json:"username" jsh:"create"`
	}

You can also add a required option to ensure a specific attribute is non-zero.

	struct {
		Username string `json:"username" jsh:"create/required"`
	}

The model must be a non-nil pointer to a struct.
If valid, the model contains the valid request attributes after the call (even on validation error)

The string slice returned contains the name of the attributes that were unmarshaled to the model.
*/
func (o *Object) ProcessCreate(resourceType string, model interface{}) ([]string, ErrorList) {
	return o.process(tagCreate, resourceType, model)
}

/*
ProcessUpdate unmarshals the object to the given struct (see Object.Unmarshal) and uses JSH tags
to validate that there is no missing attributes or forbidden ones.

Simply define your struct with jsh tags to allow for the model to be created with the tagged attributes.

	struct {
		Username string `json:"username" jsh:"update"`
	}

You can also add a required option to ensure a specific attribute is non-zero.

	struct {
		Username string `json:"username" jsh:"update/required"`
	}

The model must be a non-nil pointer to a struct.
If valid, the model contains the valid request attributes after the call (even on validation error)

If dest is not nil, it must be of the same type than model and it will be updated with the valid attributes.
Pass nil if you want to handle the update yourself.

The string slice returned contains the names of the attributes that were unmarshaled to the model.
If dest is not nil, it also represents the name of the updated attributes.
*/
func (o *Object) ProcessUpdate(resourceType string, model interface{}, dest interface{}) ([]string, ErrorList) {
	// Assert model is of the same type than dest
	drv := reflect.ValueOf(dest)
	if drv.IsValid() && drv.Type() != reflect.TypeOf(model) {
		return nil, ErrorList{ISE(fmt.Sprintf("The arguments to %s should be of the same type", tagUpdate))}
	}
	attrs, err := o.process(tagUpdate, resourceType, model)
	if err != nil {
		return nil, err
	}
	// Return if no dest is provided
	if !drv.IsValid() {
		return attrs, nil
	}
	// Update dest with decoded values
	mrv := reflect.ValueOf(model).Elem()
	drv = drv.Elem()
	for _, attr := range attrs {
		// NOTE: We can assume field names are correct
		drv.FieldByName(attr).Set(mrv.FieldByName(attr))
	}
	return attrs, nil
}

// String prints a formatted string representation of the object
func (o *Object) String() string {
	raw, err := json.MarshalIndent(o, "", " ")
	if err != nil {
		return err.Error()
	}

	return string(raw)
}

// validateInput runs go-validator on each attribute on the struct and returns all
// errors that it picks up
func validateInput(target interface{}) ErrorList {

	_, validationError := govalidator.ValidateStruct(target)
	if validationError != nil {

		errorList, isType := validationError.(govalidator.Errors)
		if isType {

			errors := ErrorList{}
			for _, singleErr := range errorList.Errors() {

				// parse out validation error
				goValidErr, _ := singleErr.(govalidator.Error)
				inputErr := InputError(goValidErr.Err.Error(), goValidErr.Name)

				errors = append(errors, inputErr)
			}

			return errors
		}
	}

	return nil
}

// process validates that the object's attributes are valid for the given action.
// It unmarshals the attributes to the model's fields that are tagged with the action.
func (o *Object) process(action, resourceType string, model interface{}) ([]string, ErrorList) {
	// Check argument is a non-nil pointer
	rv := reflect.ValueOf(model)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
		return nil, ErrorList{ISE(fmt.Sprintf("The argument to %s must be a non-nil pointer", action))}
	}
	// Get pointer element
	rv = rv.Elem()
	// Check pointer element is a struct
	if rv.Kind() != reflect.Struct {
		return nil, ErrorList{ISE(fmt.Sprintf("The argument to %s must be a pointer to a struct", action))}
	}
	// Unmarshal to model and validates input against govalidator rules
	err := o.Unmarshal(resourceType, model)
	if err != nil {
		return nil, err
	}
	// Look for missing and forbidden attributes for action
	return validateStruct(rv, action)
}
<|endoftext|>