<commit_before>package main

import (
	"context"
	"fmt"
	"sync"
	"time"

	"k8s.io/client-go/kubernetes"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/internalversion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/client-go/tools/cache"
)

type ControllerOptions struct {
	Namespace        string
	InclusionMatcher Matcher
	ExclusionMatcher Matcher
	SinceStart       bool
}

type (
	ContainerEnterFunc func(
		pod *v1.Pod,
		container *v1.Container,
		initialAddPhase bool) bool

	ContainerExitFunc func(pod *v1.Pod,
		container *v1.Container)

	ContainerErrorFunc func(pod *v1.Pod,
		container *v1.Container, err error)
)

type Callbacks struct {
	OnEvent LogEventFunc
	OnEnter ContainerEnterFunc
	OnExit  ContainerExitFunc
	OnError ContainerErrorFunc
}

type Controller struct {
	ControllerOptions
	client    kubernetes.Interface
	tailers   map[string]*ContainerTailer
	callbacks Callbacks
	context   context.Context
	sync.Mutex
}

func NewController(
	client kubernetes.Interface,
	options ControllerOptions,
	ctx context.Context,
	callbacks Callbacks) *Controller {
	return &Controller{
		ControllerOptions: options,
		client:            client,
		tailers:           map[string]*ContainerTailer{},
		callbacks:         callbacks,
		context:           ctx,
	}
}

func (ctl *Controller) Run() {
	podListWatcher := cache.NewListWatchFromClient(
		ctl.client.CoreV1().RESTClient(), "pods", ctl.Namespace, fields.Everything())

	obj, err := podListWatcher.List(metav1.ListOptions{})
	if err != nil {
		panic(err)
	}
	switch t := obj.(type) {
	case *v1.PodList:
		for _, pod := range t.Items {
			ctl.onInitialAdd(&pod)
		}
	case *internalversion.List:
		for _, item := range t.Items {
			if pod, ok := item.(*v1.Pod); ok {
				ctl.onInitialAdd(pod)
			}
		}
	default:
		panic("unable to get pod list")
	}

	_, informer := cache.NewIndexerInformer(
		podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				if pod, ok := obj.(*v1.Pod); ok {
					ctl.onAdd(pod)
				}
			},
			UpdateFunc: func(old interface{}, new interface{}) {
				if pod, ok := new.(*v1.Pod); ok {
					ctl.onUpdate(pod)
				}
			},
			DeleteFunc: func(obj interface{}) {
				if pod, ok := obj.(*v1.Pod); ok {
					ctl.onDelete(pod)
				}
			},
		}, cache.Indexers{})

	stopCh := make(chan struct{}, 1)
	go informer.Run(stopCh)
	<-stopCh
}

func (ctl *Controller) onInitialAdd(pod *v1.Pod) {
	for _, container := range pod.Spec.InitContainers {
		if ctl.shouldIncludeContainer(pod, &container) {
			ctl.addContainer(pod, &container, true)
		}
	}
	for _, container := range pod.Spec.Containers {
		if ctl.shouldIncludeContainer(pod, &container) {
			ctl.addContainer(pod, &container, true)
		}
	}
}

func (ctl *Controller) onAdd(pod *v1.Pod) {
	for _, container := range pod.Spec.InitContainers {
		if ctl.shouldIncludeContainer(pod, &container) {
			ctl.addContainer(pod, &container, false)
		}
	}
	for _, container := range pod.Spec.Containers {
		if ctl.shouldIncludeContainer(pod, &container) {
			ctl.addContainer(pod, &container, false)
		}
	}
}

func (ctl *Controller) onUpdate(pod *v1.Pod) {
	containers := pod.Spec.Containers
	containerStatuses := allContainerStatusesForPod(pod)
	for _, containerStatus := range containerStatuses {
		var container *v1.Container
		for i, c := range containers {
			if c.Name == containerStatus.Name {
				container = &containers[i]
				break
			}
		}
		if container == nil {
			// Should be impossible; means there's a status for a container that isn't
			// part of the spec
			continue
		}

		if ctl.shouldIncludeContainer(pod, container) {
			ctl.addContainer(pod, container, false)
		} else {
			ctl.deleteContainer(pod, container)
		}
	}
}

func (ctl *Controller) onDelete(pod *v1.Pod) {
	for _, container := range pod.Spec.Containers {
		ctl.deleteContainer(pod, &container)
	}
}

func (ctl *Controller) shouldIncludeContainer(pod *v1.Pod, container *v1.Container) bool {
	if !(pod.Status.Phase == v1.PodRunning || pod.Status.Phase == v1.PodPending) {
		return false
	}

	running := false
	for _, s := range allContainerStatusesForPod(pod) {
		if s.Name == container.Name && (s.State.Waiting != nil || s.State.Terminated != nil ||
			s.State.Running != nil) {
			running = true
			break
		}
	}
	if !running {
		return false
	}

	if ctl.ExclusionMatcher.Match(pod) {
		return false
	}
	if !(ctl.InclusionMatcher.Match(pod) || ctl.InclusionMatcher.Match(container)) {
		return false
	}
	return !ctl.ExclusionMatcher.Match(container)
}

func (ctl *Controller) addContainer(pod *v1.Pod, container *v1.Container, initialAdd bool) {
	ctl.Lock()
	defer ctl.Unlock()

	key := buildKey(pod, container)
	if _, ok := ctl.tailers[key]; ok {
		return
	}

	if !ctl.callbacks.OnEnter(pod, container, initialAdd) {
		return
	}

	timestamp, ok := ctl.getStartTimestamp(pod, container, initialAdd)
	if !ok {
		return
	}

	targetPod, targetContainer := *pod, *container // Copy to avoid mutation

	tailer := NewContainerTailer(ctl.client, targetPod, targetContainer,
		ctl.callbacks.OnEvent, timestamp)
	ctl.tailers[key] = tailer

	go func() {
		tailer.Run(ctl.context, func(err error) {
			ctl.callbacks.OnError(&targetPod, &targetContainer, err)
		})
	}()
}

func (ctl *Controller) deleteContainer(pod *v1.Pod, container *v1.Container) {
	ctl.Lock()
	defer ctl.Unlock()

	key := buildKey(pod, container)
	if tailer, ok := ctl.tailers[key]; ok {
		delete(ctl.tailers, key)
		tailer.Stop()
		ctl.callbacks.OnExit(pod, container)
	}
}

func (ctl *Controller) getStartTimestamp(
	pod *v1.Pod,
	container *v1.Container,
	initialAdd bool,
) (*time.Time, bool) {
	if ctl.SinceStart {
		return nil, true
	}

	if initialAdd && !ctl.SinceStart {
		// Don't show any history, but add a small amount of buffer to
		// account for clock skew
		now := time.Now().Add(time.Second * -5)
		return &now, true
	}

	var t *time.Time
	for _, status := range allContainerStatusesForPod(pod) {
		if status.Name == container.Name && status.State.Running != nil {
			startTime := status.State.Running.StartedAt.Time
			if t == nil || startTime.Before(*t) {
				t = &startTime
			}
		}
	}
	if t == nil {
		return nil, false
	}
	return t, true
}

func buildKey(pod *v1.Pod, container *v1.Container) string {
	return fmt.Sprintf("%s/%s/%s", pod.Namespace, pod.Name, findContainerID(pod, container))
}

func findContainerID(pod *v1.Pod, container *v1.Container) string {
	for _, c := range allContainerStatusesForPod(pod) {
		if c.Name == container.Name {
			return c.ContainerID
		}
	}
	return container.Name // Fallback, should never happen
}

func allContainerStatusesForPod(pod *v1.Pod) []v1.ContainerStatus {
	statuses := make([]v1.ContainerStatus, len(pod.Status.ContainerStatuses)+len(pod.Status.InitContainerStatuses))
	return append(
		append(statuses, pod.Status.InitContainerStatuses...),
		pod.Status.ContainerStatuses...)
}
<commit_msg>simplfiy buildKey when containerID doesn't exist so multiple tailers aren't created<commit_after>package main

import (
	"context"
	"fmt"
	"sync"
	"time"

	"k8s.io/client-go/kubernetes"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/internalversion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/client-go/tools/cache"
)

type ControllerOptions struct {
	Namespace        string
	InclusionMatcher Matcher
	ExclusionMatcher Matcher
	SinceStart       bool
}

type (
	ContainerEnterFunc func(
		pod *v1.Pod,
		container *v1.Container,
		initialAddPhase bool) bool

	ContainerExitFunc func(pod *v1.Pod,
		container *v1.Container)

	ContainerErrorFunc func(pod *v1.Pod,
		container *v1.Container, err error)
)

type Callbacks struct {
	OnEvent LogEventFunc
	OnEnter ContainerEnterFunc
	OnExit  ContainerExitFunc
	OnError ContainerErrorFunc
}

type Controller struct {
	ControllerOptions
	client    kubernetes.Interface
	tailers   map[string]*ContainerTailer
	callbacks Callbacks
	context   context.Context
	sync.Mutex
}

func NewController(
	client kubernetes.Interface,
	options ControllerOptions,
	ctx context.Context,
	callbacks Callbacks) *Controller {
	return &Controller{
		ControllerOptions: options,
		client:            client,
		tailers:           map[string]*ContainerTailer{},
		callbacks:         callbacks,
		context:           ctx,
	}
}

func (ctl *Controller) Run() {
	podListWatcher := cache.NewListWatchFromClient(
		ctl.client.CoreV1().RESTClient(), "pods", ctl.Namespace, fields.Everything())

	obj, err := podListWatcher.List(metav1.ListOptions{})
	if err != nil {
		panic(err)
	}
	switch t := obj.(type) {
	case *v1.PodList:
		for _, pod := range t.Items {
			ctl.onInitialAdd(&pod)
		}
	case *internalversion.List:
		for _, item := range t.Items {
			if pod, ok := item.(*v1.Pod); ok {
				ctl.onInitialAdd(pod)
			}
		}
	default:
		panic("unable to get pod list")
	}

	_, informer := cache.NewIndexerInformer(
		podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				if pod, ok := obj.(*v1.Pod); ok {
					ctl.onAdd(pod)
				}
			},
			UpdateFunc: func(old interface{}, new interface{}) {
				if pod, ok := new.(*v1.Pod); ok {
					ctl.onUpdate(pod)
				}
			},
			DeleteFunc: func(obj interface{}) {
				if pod, ok := obj.(*v1.Pod); ok {
					ctl.onDelete(pod)
				}
			},
		}, cache.Indexers{})

	stopCh := make(chan struct{}, 1)
	go informer.Run(stopCh)
	<-stopCh
}

func (ctl *Controller) onInitialAdd(pod *v1.Pod) {
	for _, container := range pod.Spec.InitContainers {
		if ctl.shouldIncludeContainer(pod, &container) {
			ctl.addContainer(pod, &container, true)
		}
	}
	for _, container := range pod.Spec.Containers {
		if ctl.shouldIncludeContainer(pod, &container) {
			ctl.addContainer(pod, &container, true)
		}
	}
}

func (ctl *Controller) onAdd(pod *v1.Pod) {
	for _, container := range pod.Spec.InitContainers {
		if ctl.shouldIncludeContainer(pod, &container) {
			ctl.addContainer(pod, &container, false)
		}
	}
	for _, container := range pod.Spec.Containers {
		if ctl.shouldIncludeContainer(pod, &container) {
			ctl.addContainer(pod, &container, false)
		}
	}
}

func (ctl *Controller) onUpdate(pod *v1.Pod) {
	containers := pod.Spec.Containers
	containerStatuses := allContainerStatusesForPod(pod)
	for _, containerStatus := range containerStatuses {
		var container *v1.Container
		for i, c := range containers {
			if c.Name == containerStatus.Name {
				container = &containers[i]
				break
			}
		}
		if container == nil {
			// Should be impossible; means there's a status for a container that isn't
			// part of the spec
			continue
		}

		if ctl.shouldIncludeContainer(pod, container) {
			ctl.addContainer(pod, container, false)
		} else {
			ctl.deleteContainer(pod, container)
		}
	}
}

func (ctl *Controller) onDelete(pod *v1.Pod) {
	for _, container := range pod.Spec.Containers {
		ctl.deleteContainer(pod, &container)
	}
}

func (ctl *Controller) shouldIncludeContainer(pod *v1.Pod, container *v1.Container) bool {
	if !(pod.Status.Phase == v1.PodRunning || pod.Status.Phase == v1.PodPending) {
		return false
	}

	running := false
	for _, s := range allContainerStatusesForPod(pod) {
		if s.Name == container.Name && (s.State.Waiting != nil || s.State.Terminated != nil ||
			s.State.Running != nil) {
			running = true
			break
		}
	}
	if !running {
		return false
	}

	if ctl.ExclusionMatcher.Match(pod) {
		return false
	}
	if !(ctl.InclusionMatcher.Match(pod) || ctl.InclusionMatcher.Match(container)) {
		return false
	}
	return !ctl.ExclusionMatcher.Match(container)
}

func (ctl *Controller) addContainer(pod *v1.Pod, container *v1.Container, initialAdd bool) {
	ctl.Lock()
	defer ctl.Unlock()

	key := buildKey(pod, container)
	if _, ok := ctl.tailers[key]; ok {
		return
	}

	if !ctl.callbacks.OnEnter(pod, container, initialAdd) {
		return
	}

	timestamp, ok := ctl.getStartTimestamp(pod, container, initialAdd)
	if !ok {
		return
	}

	targetPod, targetContainer := *pod, *container // Copy to avoid mutation

	tailer := NewContainerTailer(ctl.client, targetPod, targetContainer,
		ctl.callbacks.OnEvent, timestamp)
	ctl.tailers[key] = tailer

	go func() {
		tailer.Run(ctl.context, func(err error) {
			ctl.callbacks.OnError(&targetPod, &targetContainer, err)
		})
	}()
}

func (ctl *Controller) deleteContainer(pod *v1.Pod, container *v1.Container) {
	ctl.Lock()
	defer ctl.Unlock()

	key := buildKey(pod, container)
	if tailer, ok := ctl.tailers[key]; ok {
		delete(ctl.tailers, key)
		tailer.Stop()
		ctl.callbacks.OnExit(pod, container)
	}
}

func (ctl *Controller) getStartTimestamp(
	pod *v1.Pod,
	container *v1.Container,
	initialAdd bool,
) (*time.Time, bool) {
	if ctl.SinceStart {
		return nil, true
	}

	if initialAdd && !ctl.SinceStart {
		// Don't show any history, but add a small amount of buffer to
		// account for clock skew
		now := time.Now().Add(time.Second * -5)
		return &now, true
	}

	var t *time.Time
	for _, status := range allContainerStatusesForPod(pod) {
		if status.Name == container.Name && status.State.Running != nil {
			startTime := status.State.Running.StartedAt.Time
			if t == nil || startTime.Before(*t) {
				t = &startTime
			}
		}
	}
	if t == nil {
		return nil, false
	}
	return t, true
}

func buildKey(pod *v1.Pod, container *v1.Container) string {
	return fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)
}

func findContainerID(pod *v1.Pod, container *v1.Container) string {
	for _, c := range allContainerStatusesForPod(pod) {
		if c.Name == container.Name {
			return c.ContainerID
		}
	}
	return container.Name // Fallback, should never happen
}

func allContainerStatusesForPod(pod *v1.Pod) []v1.ContainerStatus {
	statuses := make([]v1.ContainerStatus, len(pod.Status.ContainerStatuses)+len(pod.Status.InitContainerStatuses))
	return append(
		append(statuses, pod.Status.InitContainerStatuses...),
		pod.Status.ContainerStatuses...)
}
<|endoftext|>


<commit_before>package orbengine

import (
	"log"
	"runtime"

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

/*
Destroy the controller object.
*/
func (c *Controller) Destroy() {
	c.window.Destroy()
	// TODO can I call this anywhere? is this... ok?
	sdl.Quit()
	// MUST unlock OS thread
	runtime.UnlockOSThread()
}

/*
AddEntity will add the entity to the controller. If the entity does not match
any orbengine interface, an error is returned.
*/
func (c *Controller) AddEntity(entity Identifier) error {
	// check if entity already exists -> id must be unique
	_, exists := c.entities[entity.Identification()]
	if exists {
		return ErrEntityIDExists
	}
	// include is the flag for entites that can be drawn
	var include bool
	// ensure that interface fullfills at least ONE interface
	switch entity.(type) {
	case Drawable:
		include = true
	case Renderable:
		include = true
	case Actionable:
	case Placeable: // Renderable and Drawable fullfull this
	default:
		return ErrMissingComponents
	}
	// if include is true, include the entity in the ordered draw list
	if include {
		// include entity in sorted renderable
		p, valid := entity.(Placeable)
		if !valid {
			// shouldn't happen because of above switch, so catch
			log.Fatal("include was triggered for non-includeable entity!")
		}
		// append: this sorts the new entity into the complete list
		err := c.renderable.append(p)
		if err != nil {
			return err
		}
	}
	// if we reach this -> add
	c.entities[entity.Identification()] = entity
	return nil
}

/*
RegisterKey allows entities to receive key presses by registering the functions
to execute when a key is either pressed or released.
*/
func (c *Controller) RegisterKey(key string, onPress, onRelease func()) error {
	// check if key is valid
	code := sdl.GetKeyFromName(key)
	// FIXME determine how we can check for illegal values
	log.Println("DEBUG: illegal code? :", code)
	// make keyReceive struct
	receive := &keyReceive{
		lastState: false,
		onPress:   onPress,
		onRelease: onRelease}
	_, exists := c.keyreceivers[key]
	if !exists {
		// if new key --> create array with first chan
		c.keyreceivers[key] = []*keyReceive{receive}
	} else {
		// if other entities already registered -> append chan
		c.keyreceivers[key] = append(c.keyreceivers[key], receive)
	}
	return nil
}

/*
Iterate advances the controller by one step.
*/
func (c *Controller) Iterate() {
	// first handle all the events
	c.handleEvents()
	// then step each entity
	c.iterateEntities()
}

func (c *Controller) handleEvents() {
	// read all events
	for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
		switch et := event.(type) {
		case *sdl.QuitEvent:
			// TODO handle special stuff, for exampe quit
			log.Println("TODO: how do we apply this... ?")
		case *sdl.KeyDownEvent:
			c.sendKeyEvents(sdl.GetKeyName(et.Keysym.Sym), true)
		case *sdl.KeyUpEvent:
			c.sendKeyEvents(sdl.GetKeyName(et.Keysym.Sym), false)
		// these are events we know but currently don't use FIXME implement
		case *sdl.WindowEvent:
		case *sdl.TextInputEvent:
		case *sdl.TextEditingEvent:
		case *sdl.MouseButtonEvent:
		case *sdl.MouseMotionEvent:
		case *sdl.MouseWheelEvent:
		default:
			log.Printf("Unknown event: %T\n", et)
		}
	}
}

func (c *Controller) iterateEntities() {
	// prepare to draw
	renderer, err := c.window.GetRenderer()
	if err != nil {
		log.Println("window.GetRenderer error:", err, renderer)
		return
	}
	renderer.SetDrawColor(0, 0, 0, 255)
	renderer.Clear()
	// viewport is used to check if we even have to draw something
	viewport := &sdl.Rect{}
	renderer.GetViewport(viewport)
	// run for all ordered, renderable entities
	for _, entity := range c.renderable.get() {
		// allow execute of actions
		if actionEntity, actionable := entity.(Actionable); actionable {
			actionEntity.Action()
		}
		// determine draw method. Texture is preferred over Render!
		eR, renderable := entity.(Renderable)
		eD, drawable := entity.(Drawable)
		if drawable || renderable {
			// drawable or renderable both are placeable
			e, _ := entity.(Placeable)
			// TODO this can be precalculated and reused if not changed, plus multithreaded for all entities
			// TODO apply scale / unit
			worldBound := &sdl.Rect{
				X: e.Position().X - int32(e.Width()/2),
				Y: e.Position().Y - int32(e.Height()/2),
				W: int32(e.Width()),
				H: int32(e.Height())}
			// check if we even need to draw it
			if _, intersects := viewport.Intersect(worldBound); !intersects {
				continue
			}
			// check if we need to draw / redraw entity
			text, cacheExists := c.textCache[entity.Identification()]
			if !cacheExists || e.Redraw() {
				// if previous existed, destroy
				if cacheExists {
					text.Destroy()
				}
				if drawable {
					// create texture to draw to
					text, err = renderer.CreateTexture(sdl.PIXELFORMAT_RGBA8888, sdl.TEXTUREACCESS_STATIC,
						e.Width(), e.Height())
					if err != nil {
						log.Println("renderer.CreateTexture error:", err)
						continue
					}
					// allow entity to draw to texture FIXME: reuse?
					eD.Texture(text)
				} else {
					text, _ = renderer.CreateTexture(sdl.PIXELFORMAT_RGBA8888, sdl.TEXTUREACCESS_TARGET,
						e.Width(), e.Height())
					renderer.SetRenderTarget(text)
					eR.Render(c.wrapped)
					renderer.SetRenderTarget(nil)
				}
				// update cache
				c.textCache[entity.Identification()] = text
			}
			// TODO last nil is rotation center, use offset to calculate
			renderer.CopyEx(c.textCache[entity.Identification()], nil, worldBound, e.Rotation(), nil, 0)
		}
	} // for entities
	renderer.Present()
}

/*
sendKeyEvents is a helper function that sends the given key with the given state
to all known keyreceivers that have registered for it.
*/
func (c *Controller) sendKeyEvents(key string, state bool) {
	// check if any interest exists
	receivers, exist := c.keyreceivers[key]
	if !exist {
		return
	}
	// if yes send state to all channels
	for index := range receivers { // use index because we'll set a value directly
		// don't send multiple times
		if receivers[index].lastState == state {
			continue
		}
		receivers[index].lastState = state
		// execute if available
		if state {
			if receivers[index].onPress != nil {
				receivers[index].onPress()
			}
		} else {
			if receivers[index].onRelease != nil {
				receivers[index].onRelease()
			}
		}
	}
}
<commit_msg>log and error improvements<commit_after>package orbengine

import (
	"log"
	"runtime"

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

/*
Destroy the controller object.
*/
func (c *Controller) Destroy() {
	c.window.Destroy()
	// TODO can I call this anywhere? is this... ok?
	sdl.Quit()
	// MUST unlock OS thread
	runtime.UnlockOSThread()
}

/*
AddEntity will add the entity to the controller. If the entity does not match
any orbengine interface, an error is returned.
*/
func (c *Controller) AddEntity(entity Identifier) error {
	// check if entity already exists -> id must be unique
	_, exists := c.entities[entity.Identification()]
	if exists {
		return ErrEntityIDExists
	}
	// include is the flag for entites that can be drawn
	var include bool
	// ensure that interface fullfills at least ONE interface
	switch entity.(type) {
	case Drawable:
		include = true
	case Renderable:
		include = true
	case Actionable:
	case Placeable: // Renderable and Drawable fullfull this
	default:
		return ErrMissingComponents
	}
	// if include is true, include the entity in the ordered draw list
	if include {
		// include entity in sorted renderable
		p, valid := entity.(Placeable)
		if !valid {
			// shouldn't happen because of above switch, so catch
			log.Fatal("include was triggered for non-includeable entity!")
		}
		// append: this sorts the new entity into the complete list
		err := c.renderable.append(p)
		if err != nil {
			return err
		}
	}
	// if we reach this -> add
	c.entities[entity.Identification()] = entity
	return nil
}

/*
RegisterKey allows entities to receive key presses by registering the functions
to execute when a key is either pressed or released.
*/
func (c *Controller) RegisterKey(key string, onPress, onRelease func()) error {
	// check if key is valid
	code := sdl.GetKeyFromName(key)
	// FIXME determine how we can check for illegal values
	log.Println("DEBUG: illegal code? :", code)
	// make keyReceive struct
	receive := &keyReceive{
		lastState: false,
		onPress:   onPress,
		onRelease: onRelease}
	_, exists := c.keyreceivers[key]
	if !exists {
		// if new key --> create array with first chan
		c.keyreceivers[key] = []*keyReceive{receive}
	} else {
		// if other entities already registered -> append chan
		c.keyreceivers[key] = append(c.keyreceivers[key], receive)
	}
	return nil
}

/*
Iterate advances the controller by one step.
*/
func (c *Controller) Iterate() {
	// first handle all the events
	c.handleEvents()
	// then step each entity
	c.iterateEntities()
}

func (c *Controller) handleEvents() {
	// read all events
	for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
		switch et := event.(type) {
		case *sdl.QuitEvent:
			// TODO handle special stuff, for exampe quit
			log.Println("TODO: how do we apply this... ?")
		case *sdl.KeyDownEvent:
			c.sendKeyEvents(sdl.GetKeyName(et.Keysym.Sym), true)
		case *sdl.KeyUpEvent:
			c.sendKeyEvents(sdl.GetKeyName(et.Keysym.Sym), false)
		// these are events we know but currently don't use FIXME implement
		case *sdl.WindowEvent:
		case *sdl.TextInputEvent:
		case *sdl.TextEditingEvent:
		case *sdl.MouseButtonEvent:
		case *sdl.MouseMotionEvent:
		case *sdl.MouseWheelEvent:
		default:
			log.Printf("Unknown event: %T\n", et)
		}
	}
}

func (c *Controller) iterateEntities() {
	// prepare to draw
	renderer, err := c.window.GetRenderer()
	if err != nil {
		log.Println("window.GetRenderer error:", err, renderer)
		return
	}
	renderer.SetDrawColor(0, 0, 0, 255)
	renderer.Clear()
	// viewport is used to check if we even have to draw something
	viewport := &sdl.Rect{}
	renderer.GetViewport(viewport)
	// run for all ordered, renderable entities
	for _, entity := range c.renderable.get() {
		// allow execute of actions
		if actionEntity, actionable := entity.(Actionable); actionable {
			actionEntity.Action()
		}
		// determine draw method. Texture is preferred over Render!
		eR, renderable := entity.(Renderable)
		eD, drawable := entity.(Drawable)
		if drawable || renderable {
			// drawable or renderable both are placeable
			e, _ := entity.(Placeable)
			// TODO this can be precalculated and reused if not changed, plus multithreaded for all entities
			// TODO apply scale / unit
			worldBound := &sdl.Rect{
				X: e.Position().X - int32(e.Width()/2),
				Y: e.Position().Y - int32(e.Height()/2),
				W: int32(e.Width()),
				H: int32(e.Height())}
			// check if we even need to draw it
			if _, intersects := viewport.Intersect(worldBound); !intersects {
				continue
			}
			// check if we need to draw / redraw entity
			text, cacheExists := c.textCache[entity.Identification()]
			if !cacheExists {
				log.Println(e.Identification(), "cache miss: (re)drawing texture")
			}
			if !cacheExists || e.Redraw() {
				// if previous existed, destroy
				if cacheExists {
					text.Destroy()
				}
				if drawable {
					// create texture to draw to
					text, err = renderer.CreateTexture(sdl.PIXELFORMAT_RGBA8888, sdl.TEXTUREACCESS_STATIC,
						e.Width(), e.Height())
					if err != nil {
						log.Println("renderer.CreateTexture error:", err)
						continue
					}
					// allow entity to draw to texture
					eD.Texture(text)
				} else if renderable {
					text, _ = renderer.CreateTexture(sdl.PIXELFORMAT_RGBA8888, sdl.TEXTUREACCESS_TARGET,
						e.Width(), e.Height())
					renderer.SetRenderTarget(text)
					eR.Render(c.wrapped)
					renderer.SetRenderTarget(nil)
				} else {
					// fail loudly if invalid entity
					log.Fatal("iterateEntities:", e.Identification(), "neither drawable nor renderable!")
				}
				// update cache
				c.textCache[entity.Identification()] = text
			}
			// TODO last nil is rotation center, use offset to calculate
			renderer.CopyEx(c.textCache[entity.Identification()], nil, worldBound, e.Rotation(), nil, 0)
		}
	} // for entities
	renderer.Present()
}

/*
sendKeyEvents is a helper function that sends the given key with the given state
to all known keyreceivers that have registered for it.
*/
func (c *Controller) sendKeyEvents(key string, state bool) {
	// check if any interest exists
	receivers, exist := c.keyreceivers[key]
	if !exist {
		return
	}
	// if yes send state to all channels
	for index := range receivers { // use index because we'll set a value directly
		// don't send multiple times
		if receivers[index].lastState == state {
			continue
		}
		receivers[index].lastState = state
		// execute if available
		if state {
			if receivers[index].onPress != nil {
				receivers[index].onPress()
			}
		} else {
			if receivers[index].onRelease != nil {
				receivers[index].onRelease()
			}
		}
	}
}
<|endoftext|>


<commit_before>package i18n

import "github.com/qor/admin"

type i18nController struct {
	*I18n
}

func (controller *i18nController) Index(context *admin.Context) {
	context.Execute("index", controller.I18n)
}

func (controller *i18nController) Update(context *admin.Context) {
	form := context.Request.Form
	translation := Translation{Key: form.Get("Key"), Locale: form.Get("Locale"), Value: form.Get("Value")}

	if results := controller.I18n.Translations[translation.Locale]; results != nil {
		if result := results[translation.Key]; result != nil {
			translation.Backend = result.Backend
		}
	}

	if err := controller.I18n.SaveTranslation(&translation); err == nil {
		context.Writer.Write([]byte("OK"))
	} else {
		context.Writer.WriteHeader(422)
		context.Writer.Write([]byte(err.Error()))
	}
}
<commit_msg>Sanitize user's input for translation<commit_after>package i18n

import (
	"github.com/microcosm-cc/bluemonday"
	"github.com/qor/admin"
)

var htmlSanitizer = bluemonday.UGCPolicy()

type i18nController struct {
	*I18n
}

func (controller *i18nController) Index(context *admin.Context) {
	context.Execute("index", controller.I18n)
}

func (controller *i18nController) Update(context *admin.Context) {
	form := context.Request.Form
	translation := Translation{Key: form.Get("Key"), Locale: form.Get("Locale"), Value: htmlSanitizer.Sanitize(form.Get("Value"))}

	if results := controller.I18n.Translations[translation.Locale]; results != nil {
		if result := results[translation.Key]; result != nil {
			translation.Backend = result.Backend
		}
	}

	if err := controller.I18n.SaveTranslation(&translation); err == nil {
		context.Writer.Write([]byte("OK"))
	} else {
		context.Writer.WriteHeader(422)
		context.Writer.Write([]byte(err.Error()))
	}
}
<|endoftext|>


<commit_before>//	Copyright 2013 slowfei And The Contributors 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.
//
//  Create on 2013-8-16
//  Update on 2013-10-23
//  Email  slowfei@foxmail.com
//  Home   http://www.slowfei.com

//	leafveingo web 的控制器操作
//
package leafveingo

import (
	"errors"
	"fmt"
	"github.com/slowfei/gosfcore/encoding/json"
	"github.com/slowfei/gosfcore/utils/filemanager"
	"github.com/slowfei/leafveingo/template"
	"mime"
	"mime/multipart"
	"net/http"
	"net/url"
	"path"
	"reflect"
	"strings"
)

//	控制器返回参数处理
func (lv *sfLeafvein) returnValue(v []reflect.Value, ctrURLPath string, context *HttpContext) (stuctCode HttpStatus, err error) {
	stuctCode = Status200

	if 1 == len(v) {
		rv := reflect.Indirect(v[0])
		// rw.Header().Set("Content-Length", "10000")
		switch cvt := rv.Interface().(type) {
		case string:
			context.RespWrite.Header().Set("Content-Type", "text/plain; charset="+lv.charset)
			context.RespBodyWrite([]byte(cvt), Status200)
		case ByteOut:
			if 0 == len(cvt.ContentType) {
				context.RespWrite.Header().Set("Content-Type", "text/plain; charset="+lv.charset)
			} else {
				context.RespWrite.Header().Set("Content-Type", cvt.ContentType)
			}
			for k, v := range cvt.Headers {
				context.RespWrite.Header().Set(k, v)
			}
			context.RespBodyWrite(cvt.Body, Status200)
		case SFJson.Json:
			context.RespWrite.Header().Set("Content-Type", "application/json")
			context.RespBodyWrite(cvt.Byte(), Status200)
		case HtmlOut:
			context.RespWrite.Header().Set("Content-Type", "text/html; charset="+lv.charset)
			context.RespBodyWrite([]byte(cvt), Status200)
		case HttpStatusValue:
			err := context.StatusPageWriteByValue(cvt)
			if nil != err {
				lvLog.Debug(err.Error())
			}
		case LVTemplate.TemplateValue:
			context.RespWrite.Header().Set("Content-Type", "text/html; charset="+lv.charset)
			if "" == cvt.TplPath {
				cvt.TplPath = ctrURLPath + lv.templateSuffix
			}
			e := lv.template.Execute(context.ComperssWriter(), cvt)
			if nil != e {
				panic(e.Error())
			}
		case Redirect:
			context.RespWrite.Header().Del("Content-Encoding")
			http.Redirect(context.RespWrite, context.Request, string(cvt), int(Status301))
			stuctCode = Status301
		case Dispatcher:
			if 0 == len(cvt.MethodName) {
				cvt.MethodName = CONTROLLER_DEFAULT_METHOD
			}

			if ctrlVal, ok := lv.controllers[cvt.Router]; ok {
				//	需要重新设置控制器路径，以便转发后能够查找到相应的模板
				dispCtrURLPath := strings.ToLower(cvt.Router + cvt.MethodName)

				//	request的一些设置由调用者直接进行设置Header
				for k, v := range cvt.Headers {
					context.Request.Header.Set(k, v)
				}

				var e error = nil
				stuctCode, e = lv.cellController(cvt.Router, cvt.MethodName, dispCtrURLPath, context)
				if nil != e {
					lvLog.Error("dispatcher: (%v)controller (%v)method error:%v", ctrlVal.Type().String(), cvt.MethodName, e)
				}
			} else {
				stuctCode = Status500
				//	这个是自定义写代码的转发，如果查找不到相当于是调用者代码问题，所以直接抛出异常（恐慌）。
				ErrControllerDispatcherNotFound.Message = lvLog.Error("dispatcher: controller not found router key:%v", cvt.Router)
				panic(ErrControllerDispatcherNotFound)
			}
		case ServeFilePath:
			context.RespWrite.Header().Del("Content-Encoding")
			filePath := path.Join(lv.WebRootDir(), string(cvt))

			if isExists, isDir, _ := SFFileManager.Exists(filePath); isExists && !isDir {
				http.ServeFile(context.RespWrite, context.Request, filePath)
			} else {
				//	404
				http.NotFound(context.RespWrite, context.Request)
			}

		default:
			panic(ErrControllerReturnParam)
		}

	} else if 1 < len(v) {
		panic(ErrControllerReturnParamNum)
	}

	return
}

//	解析http提交的参数，包括上传文件的信息
func (lv *sfLeafvein) parseFormParams(req *http.Request) (
	urlValues url.Values,
	files map[string][]*multipart.FileHeader,
	fileNum int,
	err error,
	stuctCode HttpStatus) {

	switch req.Method {
	case "GET":
		urlValues = req.URL.Query()
	case "POST":
		contentType := req.Header.Get("Content-Type")
		enctype, _, e := mime.ParseMediaType(contentType)
		if nil != e {
			err = e
			stuctCode = Status400
			return
		}

		switch {
		case enctype == "application/x-www-form-urlencoded":
			err = req.ParseForm()
			if nil != err {
				stuctCode = Status400
				return
			}

			//	考虑安全因素，让调用则知道请求的参数来自Form还是Query所以进行POST请求就只获取Form的参数
			urlValues = req.PostForm

		case enctype == "multipart/form-data":
			err = req.ParseMultipartForm(lv.fileUploadSize)
			if nil != err {
				stuctCode = Status400
				return
			}
			// ParseMultipartForm()解析已经调用了ParseForm()
			urlValues = url.Values(req.MultipartForm.Value)

			if 0 < len(req.MultipartForm.File) {
				for k, v := range req.MultipartForm.File {
					//	添加空字符串的主要目的是为了能够在创建结构时初始化切片的数量
					urlValues.Set(k, "")
					fNum := len(v)
					if 1 < fNum {
						fileNum += fNum
					} else {
						fileNum++
					}
				}
				files = req.MultipartForm.File
			}

		}
	default:
		urlValues = make(url.Values)
	}

	stuctCode = Status200
	return
}

//	操作控制器请求的函数
//	@isPackStruct 是否封装用户自定义的struct
func (lv *sfLeafvein) cellMethod(
	methodValue reflect.Value,
	urlValues url.Values,
	files map[string][]*multipart.FileHeader,
	context *HttpContext,
	isPackStruct bool) []reflect.Value {

	//	内部使用的结构设值
	setStructValue := func(refValue reflect.Value) {
		for k, v := range urlValues {
			count := len(v)
			switch {
			case 1 == count:
				lv.setStructFieldValue(refValue, k, v[0])
			case 1 < count:
				lv.setStructFieldValue(refValue, k, v)
			}

		}
		for fk, fv := range files {
			count := len(fv)
			switch {
			case 1 == count:
				lv.setStructFieldValue(refValue, fk, fv[0])
			case 1 < count:
				lv.setStructFieldValue(refValue, fk, fv)
			}
		}
	}

	methodType := methodValue.Type()
	argsNum := methodType.NumIn()
	args := make([]reflect.Value, argsNum, argsNum)

	for i := 0; i < argsNum; i++ {
		in := methodType.In(i)
		typeString := in.String()
		var argsValue reflect.Value

		switch typeString {
		case "*http.Request":
			argsValue = reflect.ValueOf(context.Request)
		case "http.Request":
			argsValue = reflect.ValueOf(context.Request).Elem()
		case "*url.URL":
			argsValue = reflect.ValueOf(context.Request.URL)
		case "url.URL":
			argsValue = reflect.ValueOf(context.Request.URL).Elem()
		case "*leafveingo.HttpContext":
			argsValue = reflect.ValueOf(context)
		case "leafveingo.HttpContext":
			argsValue = reflect.ValueOf(context).Elem()
		case "[]uint8":
			body := context.RequestBody()
			if nil != body {
				argsValue = reflect.ValueOf(body)
			} else {
				argsValue = reflect.Zero(in)
			}
		case "http.ResponseWriter":
			argsValue = reflect.ValueOf(context.RespWrite)
		case "LVSession.HttpSession":
			session, _ := context.Session(false)
			if nil != session {
				argsValue = reflect.ValueOf(session)
			} else {
				argsValue = reflect.Zero(in)
			}
		default:
			switch in.Kind() {
			case reflect.Ptr:
				switch in.Elem().Kind() {
				case reflect.Struct:
					if isPackStruct {
						newValue := lv.newStructPtr(in.Elem(), urlValues)
						setStructValue(newValue)
						argsValue = newValue
					}
				}
			case reflect.Struct:
				if isPackStruct {
					newValue := lv.newStructPtr(in, urlValues)
					setStructValue(newValue)
					argsValue = newValue.Elem()
				}
			}
		}

		if reflect.Invalid == argsValue.Kind() {
			argsValue = reflect.Zero(in)
		}

		args[i] = argsValue
	}
	return methodValue.Call(args)
}

//	操作控制器请求的处理
//	@controller		请求的控制器
//	@funcName		控制器函数名
//	@ctrURLPath		控制器url路径
//	@rw
//	@req
func (lv *sfLeafvein) cellController(routerKey, methodName, ctrlPath string, context *HttpContext) (stuctCode HttpStatus, err error) {
	ctrlVal, ok := lv.controllers[routerKey]

	if !ok {
		err = errors.New(lvLog.Error("cellController not found router key:%v", routerKey))
		stuctCode = Status404
		return
	}

	//	TODO 考虑看是否真的需要使用slice来进行存储。
	context.routerKeys = append(context.routerKeys, routerKey)
	context.methodNames = append(context.methodNames, methodName)

	switch ctrlVal.Kind() {
	case reflect.Ptr:
		//	指针类型不做操作，直接使用该对象。
	default:
		//	如果判断不为指针结构的值，就创建一个新的
		ctrlVal = reflect.New(ctrlVal.Type())
	}

	//	控制器调用方法
	var requestMethodValue reflect.Value

	//	控制器调用前和后处理的函数
	var beforeMethodValue reflect.Value
	isBefore := false
	var afterMethodValue reflect.Value
	isAfter := false

	//	是否执行调用控制器方法
	isCell := false

	if requestMethodValue = ctrlVal.MethodByName(methodName); requestMethodValue.IsValid() {
		isCell = true

		if beforeMethodValue = ctrlVal.MethodByName(CONTROLLER_BEFORE_METHOD); beforeMethodValue.IsValid() {
			isBefore = true
		}
		if afterMethodValue = ctrlVal.MethodByName(CONTROLLER_AFTER_METHOD); afterMethodValue.IsValid() {
			isAfter = true
		}

	} else {
		err = errors.New(fmt.Sprintf("cellController not found method name:%v", methodName))
		stuctCode = Status404
		return
	}

	if isCell {

		//	请求参数
		var urlValues url.Values
		//	上传文件
		var files map[string][]*multipart.FileHeader
		//	记录文件请求数量
		fileNum := 0

		urlValues, files, fileNum, err, stuctCode = lv.parseFormParams(context.Request)
		if Status200 != stuctCode {
			return
		}

		logInfo := fmt.Sprintf("Request controller: %s \nRequest param: %v\nRequest fileNum: %d\n", ctrlVal.Type(), urlValues, fileNum)
		lvLog.Info(logInfo)

		isCellCtrMethod := true
		if isBefore {
			returnValues := lv.cellMethod(beforeMethodValue, urlValues, files, context, false)
			if 1 == len(returnValues) {
				retVal := returnValues[0]
				if retVal.Kind() == reflect.Bool {
					isCellCtrMethod = retVal.Bool()
				}
			}
		}

		if isCellCtrMethod {
			//	request controller cell method
			rvs := lv.cellMethod(requestMethodValue, urlValues, files, context, true)
			//	处理控制器返回值
			stuctCode, err = lv.returnValue(rvs, ctrlPath, context)
		} else {
			//	请求拒绝
			stuctCode = Status403
			//	error info is return?
		}

		if isAfter {
			lv.cellMethod(afterMethodValue, urlValues, files, context, false)
		}

	}
	return
}
<commit_msg>修改输出json输出context-type编码设置<commit_after>//	Copyright 2013 slowfei And The Contributors 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.
//
//  Create on 2013-8-16
//  Update on 2013-10-23
//  Email  slowfei@foxmail.com
//  Home   http://www.slowfei.com

//	leafveingo web 的控制器操作
//
package leafveingo

import (
	"errors"
	"fmt"
	"github.com/slowfei/gosfcore/encoding/json"
	"github.com/slowfei/gosfcore/utils/filemanager"
	"github.com/slowfei/leafveingo/template"
	"mime"
	"mime/multipart"
	"net/http"
	"net/url"
	"path"
	"reflect"
	"strings"
)

//	控制器返回参数处理
func (lv *sfLeafvein) returnValue(v []reflect.Value, ctrURLPath string, context *HttpContext) (stuctCode HttpStatus, err error) {
	stuctCode = Status200

	if 1 == len(v) {
		rv := reflect.Indirect(v[0])
		// rw.Header().Set("Content-Length", "10000")
		switch cvt := rv.Interface().(type) {
		case string:
			context.RespWrite.Header().Set("Content-Type", "text/plain; charset="+lv.charset)
			context.RespBodyWrite([]byte(cvt), Status200)
		case ByteOut:
			if 0 == len(cvt.ContentType) {
				context.RespWrite.Header().Set("Content-Type", "text/plain; charset="+lv.charset)
			} else {
				context.RespWrite.Header().Set("Content-Type", cvt.ContentType)
			}
			for k, v := range cvt.Headers {
				context.RespWrite.Header().Set(k, v)
			}
			context.RespBodyWrite(cvt.Body, Status200)
		case SFJson.Json:
			context.RespWrite.Header().Set("Content-Type", "application/json; charset="+lv.charset)
			context.RespBodyWrite(cvt.Byte(), Status200)
		case HtmlOut:
			context.RespWrite.Header().Set("Content-Type", "text/html; charset="+lv.charset)
			context.RespBodyWrite([]byte(cvt), Status200)
		case HttpStatusValue:
			err := context.StatusPageWriteByValue(cvt)
			if nil != err {
				lvLog.Debug(err.Error())
			}
		case LVTemplate.TemplateValue:
			context.RespWrite.Header().Set("Content-Type", "text/html; charset="+lv.charset)
			if "" == cvt.TplPath {
				cvt.TplPath = ctrURLPath + lv.templateSuffix
			}
			e := lv.template.Execute(context.ComperssWriter(), cvt)
			if nil != e {
				panic(e.Error())
			}
		case Redirect:
			context.RespWrite.Header().Del("Content-Encoding")
			http.Redirect(context.RespWrite, context.Request, string(cvt), int(Status301))
			stuctCode = Status301
		case Dispatcher:
			if 0 == len(cvt.MethodName) {
				cvt.MethodName = CONTROLLER_DEFAULT_METHOD
			}

			if ctrlVal, ok := lv.controllers[cvt.Router]; ok {
				//	需要重新设置控制器路径，以便转发后能够查找到相应的模板
				dispCtrURLPath := strings.ToLower(cvt.Router + cvt.MethodName)

				//	request的一些设置由调用者直接进行设置Header
				for k, v := range cvt.Headers {
					context.Request.Header.Set(k, v)
				}

				var e error = nil
				stuctCode, e = lv.cellController(cvt.Router, cvt.MethodName, dispCtrURLPath, context)
				if nil != e {
					lvLog.Error("dispatcher: (%v)controller (%v)method error:%v", ctrlVal.Type().String(), cvt.MethodName, e)
				}
			} else {
				stuctCode = Status500
				//	这个是自定义写代码的转发，如果查找不到相当于是调用者代码问题，所以直接抛出异常（恐慌）。
				ErrControllerDispatcherNotFound.Message = lvLog.Error("dispatcher: controller not found router key:%v", cvt.Router)
				panic(ErrControllerDispatcherNotFound)
			}
		case ServeFilePath:
			context.RespWrite.Header().Del("Content-Encoding")
			filePath := path.Join(lv.WebRootDir(), string(cvt))

			if isExists, isDir, _ := SFFileManager.Exists(filePath); isExists && !isDir {
				http.ServeFile(context.RespWrite, context.Request, filePath)
			} else {
				//	404
				http.NotFound(context.RespWrite, context.Request)
			}

		default:
			panic(ErrControllerReturnParam)
		}

	} else if 1 < len(v) {
		panic(ErrControllerReturnParamNum)
	}

	return
}

//	解析http提交的参数，包括上传文件的信息
func (lv *sfLeafvein) parseFormParams(req *http.Request) (
	urlValues url.Values,
	files map[string][]*multipart.FileHeader,
	fileNum int,
	err error,
	stuctCode HttpStatus) {

	switch req.Method {
	case "GET":
		urlValues = req.URL.Query()
	case "POST":
		contentType := req.Header.Get("Content-Type")
		enctype, _, e := mime.ParseMediaType(contentType)
		if nil != e {
			err = e
			stuctCode = Status400
			return
		}

		switch {
		case enctype == "application/x-www-form-urlencoded":
			err = req.ParseForm()
			if nil != err {
				stuctCode = Status400
				return
			}

			//	考虑安全因素，让调用则知道请求的参数来自Form还是Query所以进行POST请求就只获取Form的参数
			urlValues = req.PostForm

		case enctype == "multipart/form-data":
			err = req.ParseMultipartForm(lv.fileUploadSize)
			if nil != err {
				stuctCode = Status400
				return
			}
			// ParseMultipartForm()解析已经调用了ParseForm()
			urlValues = url.Values(req.MultipartForm.Value)

			if 0 < len(req.MultipartForm.File) {
				for k, v := range req.MultipartForm.File {
					//	添加空字符串的主要目的是为了能够在创建结构时初始化切片的数量
					urlValues.Set(k, "")
					fNum := len(v)
					if 1 < fNum {
						fileNum += fNum
					} else {
						fileNum++
					}
				}
				files = req.MultipartForm.File
			}

		}
	default:
		urlValues = make(url.Values)
	}

	stuctCode = Status200
	return
}

//	操作控制器请求的函数
//	@isPackStruct 是否封装用户自定义的struct
func (lv *sfLeafvein) cellMethod(
	methodValue reflect.Value,
	urlValues url.Values,
	files map[string][]*multipart.FileHeader,
	context *HttpContext,
	isPackStruct bool) []reflect.Value {

	//	内部使用的结构设值
	setStructValue := func(refValue reflect.Value) {
		for k, v := range urlValues {
			count := len(v)
			switch {
			case 1 == count:
				lv.setStructFieldValue(refValue, k, v[0])
			case 1 < count:
				lv.setStructFieldValue(refValue, k, v)
			}

		}
		for fk, fv := range files {
			count := len(fv)
			switch {
			case 1 == count:
				lv.setStructFieldValue(refValue, fk, fv[0])
			case 1 < count:
				lv.setStructFieldValue(refValue, fk, fv)
			}
		}
	}

	methodType := methodValue.Type()
	argsNum := methodType.NumIn()
	args := make([]reflect.Value, argsNum, argsNum)

	for i := 0; i < argsNum; i++ {
		in := methodType.In(i)
		typeString := in.String()
		var argsValue reflect.Value

		switch typeString {
		case "*http.Request":
			argsValue = reflect.ValueOf(context.Request)
		case "http.Request":
			argsValue = reflect.ValueOf(context.Request).Elem()
		case "*url.URL":
			argsValue = reflect.ValueOf(context.Request.URL)
		case "url.URL":
			argsValue = reflect.ValueOf(context.Request.URL).Elem()
		case "*leafveingo.HttpContext":
			argsValue = reflect.ValueOf(context)
		case "leafveingo.HttpContext":
			argsValue = reflect.ValueOf(context).Elem()
		case "[]uint8":
			body := context.RequestBody()
			if nil != body {
				argsValue = reflect.ValueOf(body)
			} else {
				argsValue = reflect.Zero(in)
			}
		case "http.ResponseWriter":
			argsValue = reflect.ValueOf(context.RespWrite)
		case "LVSession.HttpSession":
			session, _ := context.Session(false)
			if nil != session {
				argsValue = reflect.ValueOf(session)
			} else {
				argsValue = reflect.Zero(in)
			}
		default:
			switch in.Kind() {
			case reflect.Ptr:
				switch in.Elem().Kind() {
				case reflect.Struct:
					if isPackStruct {
						newValue := lv.newStructPtr(in.Elem(), urlValues)
						setStructValue(newValue)
						argsValue = newValue
					}
				}
			case reflect.Struct:
				if isPackStruct {
					newValue := lv.newStructPtr(in, urlValues)
					setStructValue(newValue)
					argsValue = newValue.Elem()
				}
			}
		}

		if reflect.Invalid == argsValue.Kind() {
			argsValue = reflect.Zero(in)
		}

		args[i] = argsValue
	}
	return methodValue.Call(args)
}

//	操作控制器请求的处理
//	@controller		请求的控制器
//	@funcName		控制器函数名
//	@ctrURLPath		控制器url路径
//	@rw
//	@req
func (lv *sfLeafvein) cellController(routerKey, methodName, ctrlPath string, context *HttpContext) (stuctCode HttpStatus, err error) {
	ctrlVal, ok := lv.controllers[routerKey]

	if !ok {
		err = errors.New(lvLog.Error("cellController not found router key:%v", routerKey))
		stuctCode = Status404
		return
	}

	//	TODO 考虑看是否真的需要使用slice来进行存储。
	context.routerKeys = append(context.routerKeys, routerKey)
	context.methodNames = append(context.methodNames, methodName)

	switch ctrlVal.Kind() {
	case reflect.Ptr:
		//	指针类型不做操作，直接使用该对象。
	default:
		//	如果判断不为指针结构的值，就创建一个新的
		ctrlVal = reflect.New(ctrlVal.Type())
	}

	//	控制器调用方法
	var requestMethodValue reflect.Value

	//	控制器调用前和后处理的函数
	var beforeMethodValue reflect.Value
	isBefore := false
	var afterMethodValue reflect.Value
	isAfter := false

	//	是否执行调用控制器方法
	isCell := false

	if requestMethodValue = ctrlVal.MethodByName(methodName); requestMethodValue.IsValid() {
		isCell = true

		if beforeMethodValue = ctrlVal.MethodByName(CONTROLLER_BEFORE_METHOD); beforeMethodValue.IsValid() {
			isBefore = true
		}
		if afterMethodValue = ctrlVal.MethodByName(CONTROLLER_AFTER_METHOD); afterMethodValue.IsValid() {
			isAfter = true
		}

	} else {
		err = errors.New(fmt.Sprintf("cellController not found method name:%v", methodName))
		stuctCode = Status404
		return
	}

	if isCell {

		//	请求参数
		var urlValues url.Values
		//	上传文件
		var files map[string][]*multipart.FileHeader
		//	记录文件请求数量
		fileNum := 0

		urlValues, files, fileNum, err, stuctCode = lv.parseFormParams(context.Request)
		if Status200 != stuctCode {
			return
		}

		logInfo := fmt.Sprintf("Request controller: %s \nRequest param: %v\nRequest fileNum: %d\n", ctrlVal.Type(), urlValues, fileNum)
		lvLog.Info(logInfo)

		isCellCtrMethod := true
		if isBefore {
			returnValues := lv.cellMethod(beforeMethodValue, urlValues, files, context, false)
			if 1 == len(returnValues) {
				retVal := returnValues[0]
				if retVal.Kind() == reflect.Bool {
					isCellCtrMethod = retVal.Bool()
				}
			}
		}

		if isCellCtrMethod {
			//	request controller cell method
			rvs := lv.cellMethod(requestMethodValue, urlValues, files, context, true)
			//	处理控制器返回值
			stuctCode, err = lv.returnValue(rvs, ctrlPath, context)
		} else {
			//	请求拒绝
			stuctCode = Status403
			//	error info is return?
		}

		if isAfter {
			lv.cellMethod(afterMethodValue, urlValues, files, context, false)
		}

	}
	return
}
<|endoftext|>


<commit_before>// Ogo

package ogo

import (
	//"fmt"
	"github.com/zenazn/goji"
	"github.com/zenazn/goji/web"
	"net/http"
	"strings"
)

type Handler func(c *HttpContext)

type Route struct {
	Pattern string
	Method  string
	Handler Handler
}

type Controller struct {
	Endpoint string
	Routes   map[string]*Route
	Request  int
}

type ControllerInterface interface {
	//Init(endpoint string, c ControllerInterface)
	Get(c *HttpContext)
	Post(c *HttpContext)
	Put(c *HttpContext)
	Delete(c *HttpContext)
	Patch(c *HttpContext)
	Head(c *HttpContext)
}

func NewRoute(p string, m string, h Handler) *Route {
	return &Route{
		Pattern: p,
		Method:  m,
		Handler: h,
	}
}

// 封装
func handlerWrap(f Handler) web.HandlerFunc { //这里封装了webC到本地的结构中
	return func(c web.C, w http.ResponseWriter, r *http.Request) {
		f(newContext(c, w, r))
	}
}

func (ctr *Controller) Init(endpoint string, c ControllerInterface) {
	ctr.Endpoint = endpoint
	ctr.Routes = make(map[string]*Route)
	//默认路由
	ctr.DefaultRoutes(c)
}

func (ctr *Controller) Get(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (ctr *Controller) Post(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (ctr *Controller) Put(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (ctr *Controller) Delete(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (ctr *Controller) Patch(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (ctr *Controller) Head(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}

func (ctr *Controller) AddRoute(m string, p string, h Handler) {
	rt := NewRoute(p, m, h)
	switch strings.ToLower(m) {
	case "get":
		ctr.RouteGet(rt)
	case "post":
		ctr.RoutePost(rt)
	case "put":
		ctr.RoutePut(rt)
	case "delete":
		ctr.RouteDelete(rt)
	case "patch":
		ctr.RoutePatch(rt)
	case "head":
		ctr.RouteHead(rt)
	default:
		// unknow method
	}
}

// controller default route
// 默认路由, 如果已经定义了则忽略，没有定义则加上
//func (ctr *Controller) DefaultRoutes() {
func (ctr *Controller) DefaultRoutes(c ControllerInterface) {
	// GET /{endpoint}
	ctr.RouteGet(NewRoute("/"+ctr.Endpoint, "GET", c.Get))

	// GET /{endpoint}/{id}
	ctr.RouteGet(NewRoute("/"+ctr.Endpoint+"/:_id_", "GET", c.Get))

	// POST /{endpoint}
	ctr.RoutePost(NewRoute("/"+ctr.Endpoint, "POST", c.Post))

	// DELETE /{endpoint}/{id}
	ctr.RouteDelete(NewRoute("/"+ctr.Endpoint+"/:_id_", "DELETE", c.Delete))

	// PATCH /{endpoint}/{id}
	ctr.RouteDelete(NewRoute("/"+ctr.Endpoint+"/:_id_", "PATCH", c.Patch))

	// PUT /{endpoint}/{id}
	ctr.RouteDelete(NewRoute("/"+ctr.Endpoint+"/:_id_", "PUT", c.Put))

}

func (ctr *Controller) RouteGet(rt *Route) {
	key := strings.ToUpper(rt.Method) + " " + rt.Pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, log overwrite
	}
	goji.Get(rt.Pattern, handlerWrap(rt.Handler))
	ctr.Routes[key] = rt
}
func (ctr *Controller) RoutePost(rt *Route) {
	key := strings.ToUpper(rt.Method) + " " + rt.Pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, log overwrite
	}
	goji.Post(rt.Pattern, handlerWrap(rt.Handler))
	ctr.Routes[key] = rt
}
func (ctr *Controller) RoutePut(rt *Route) {
	key := strings.ToUpper(rt.Method) + " " + rt.Pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, log overwrite
	}
	goji.Put(rt.Pattern, handlerWrap(rt.Handler))
	ctr.Routes[key] = rt
}
func (ctr *Controller) RouteDelete(rt *Route) {
	key := strings.ToUpper(rt.Method) + " " + rt.Pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, log overwrite
	}
	goji.Delete(rt.Pattern, handlerWrap(rt.Handler))
	ctr.Routes[key] = rt
}
func (ctr *Controller) RoutePatch(rt *Route) {
	key := strings.ToUpper(rt.Method) + " " + rt.Pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, log overwrite
	}
	goji.Patch(rt.Pattern, handlerWrap(rt.Handler))
	ctr.Routes[key] = rt
}
func (ctr *Controller) RouteHead(rt *Route) {
	key := strings.ToUpper(rt.Method) + " " + rt.Pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, log overwrite
	}
	goji.Head(rt.Pattern, handlerWrap(rt.Handler))
	ctr.Routes[key] = rt
}
<commit_msg>update<commit_after>// Ogo

package ogo

import (
	//"fmt"
	"github.com/zenazn/goji"
	"github.com/zenazn/goji/web"
	"net/http"
	"strings"
)

type Handler func(c *HttpContext)

type Route struct {
	Pattern string
	Method  string
	Handler Handler
}

type Controller struct {
	Endpoint string
	Routes   map[string]*Route
	Request  int
}

type ControllerInterface interface {
	//Init(endpoint string, c ControllerInterface)
	Get(c *HttpContext)
	Post(c *HttpContext)
	Put(c *HttpContext)
	Delete(c *HttpContext)
	Patch(c *HttpContext)
	Head(c *HttpContext)
}

func NewRoute(p string, m string, h Handler) *Route {
	return &Route{
		Pattern: p,
		Method:  m,
		Handler: h,
	}
}

// 封装
func handlerWrap(f Handler) web.HandlerFunc { //这里封装了webC到本地的结构中
	return func(c web.C, w http.ResponseWriter, r *http.Request) {
		f(newContext(c, w, r))
	}
}

func (ctr *Controller) Init(endpoint string, c ControllerInterface) {
	ctr.Endpoint = endpoint
	//ctr.Routes = make(map[string]*Route)
	//默认路由
	ctr.DefaultRoutes(c)
	if len(ctr.Routes) > 0 { //如果有自定义路由, 则优先增加
		for _, rt := range ctr.Routes {
			switch strings.ToLower(rt.Method) {
			case "get":
				ctr.RouteGet(rt)
			case "post":
				ctr.RoutePost(rt)
			case "put":
				ctr.RoutePut(rt)
			case "delete":
				ctr.RouteDelete(rt)
			case "patch":
				ctr.RoutePatch(rt)
			case "head":
				ctr.RouteHead(rt)
			default:
				// unknow method
			}
		}
	}
}

func (ctr *Controller) Get(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (ctr *Controller) Post(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (ctr *Controller) Put(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (ctr *Controller) Delete(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (ctr *Controller) Patch(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}
func (ctr *Controller) Head(c *HttpContext) {
	http.Error(c.Response, "Method Not Allowed", http.StatusMethodNotAllowed)
}

func (ctr *Controller) AddRoute(m string, p string, h Handler) {
	key := strings.ToUpper(m) + " " + p
	if _, ok := ctr.Routes[key]; ok {
		//手动加路由, 以最后加的为准,overwrite
	}
	ctr.Routes[key] = NewRoute(p, m, h)
	//switch strings.ToLower(m) {
	//case "get":
	//	ctr.RouteGet(rt)
	//case "post":
	//	ctr.RoutePost(rt)
	//case "put":
	//	ctr.RoutePut(rt)
	//case "delete":
	//	ctr.RouteDelete(rt)
	//case "patch":
	//	ctr.RoutePatch(rt)
	//case "head":
	//	ctr.RouteHead(rt)
	//default:
	//	// unknow method
	//}
}

// controller default route
// 默认路由, 如果已经定义了则忽略，没有定义则加上
//func (ctr *Controller) DefaultRoutes() {
func (ctr *Controller) DefaultRoutes(c ControllerInterface) {
	var pattern, method, key string
	// GET /{endpoint}
	pattern = "/" + ctr.Endpoint
	method = "GET"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Get)
		ctr.Routes[key] = rt
	}

	// GET /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "GET"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Get)
		ctr.Routes[key] = rt
	}

	// POST /{endpoint}
	pattern = "/" + ctr.Endpoint
	method = "POST"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Post)
		ctr.Routes[key] = rt
	}

	// DELETE /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "DELETE"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Delete)
		ctr.Routes[key] = rt
	}

	// PATCH /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "PATCH"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Patch)
		ctr.Routes[key] = rt
	}

	// PUT /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "PUT"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Put)
		ctr.Routes[key] = rt
	}
}

func (ctr *Controller) RouteGet(rt *Route) {
	goji.Get(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RoutePost(rt *Route) {
	goji.Post(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RoutePut(rt *Route) {
	goji.Put(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RouteDelete(rt *Route) {
	goji.Delete(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RoutePatch(rt *Route) {
	goji.Patch(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RouteHead(rt *Route) {
	goji.Head(rt.Pattern, handlerWrap(rt.Handler))
}
<|endoftext|>


<commit_before><commit_msg>Added timeout in select statement for "fanin" example<commit_after><|endoftext|>


<commit_before>package aufs_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"syscall"
	"time"

	"code.cloudfoundry.org/garden-shed/docker_drivers/aufs"
	fakes "code.cloudfoundry.org/garden-shed/docker_drivers/aufs/aufsfakes"
	"code.cloudfoundry.org/lager/lagertest"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

const (
	pollingInterval = 100 * time.Millisecond
	numRetries      = 3 // At least 3 so that we can test the case where it works after multiple (2) retries
)

var _ = Describe("LoopLinux", func() {
	var (
		bsFilePath string
		destPath   string
		loop       *aufs.Loop

		fakeRetrier *fakes.FakeRetrier
	)

	BeforeEach(func() {
		var err error

		tempFile, err := ioutil.TempFile("", "loop")
		Expect(err).NotTo(HaveOccurred())
		bsFilePath = tempFile.Name()
		_, err = exec.Command("truncate", "-s", "10M", bsFilePath).CombinedOutput()
		Expect(err).NotTo(HaveOccurred())
		_, err = exec.Command("mkfs.ext4", "-F", bsFilePath).CombinedOutput()
		Expect(err).NotTo(HaveOccurred())

		destPath, err = ioutil.TempDir("", "loop")
		Expect(err).NotTo(HaveOccurred())

		fakeRetrier = new(fakes.FakeRetrier)
		fakeRetrier.RunStub = func(fn func() error) error {
			return fn()
		}

		loop = &aufs.Loop{
			Logger:  lagertest.NewTestLogger("test"),
			Retrier: fakeRetrier,
		}
	})

	AfterEach(func() {
		syscall.Unmount(destPath, 0)
		Expect(os.RemoveAll(destPath)).To(Succeed())
		Expect(os.Remove(bsFilePath)).To(Succeed())
	})

	Describe("MountFile", func() {
		It("mounts the file with noatime but not the journal_data_writeback options", func() {
			Expect(loop.MountFile(bsFilePath, destPath)).To(Succeed())

			session, err := gexec.Start(exec.Command("losetup", "-j", bsFilePath), GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())
			loopDev := strings.TrimSpace(strings.Split(string(session.Out.Contents()), ":")[0])

			session, err = gexec.Start(exec.Command("cat", "/proc/mounts"), GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())
			Eventually(session).Should(gbytes.Say(fmt.Sprintf("%s %s ext4 rw,noatime", loopDev, destPath)))
			Expect(session).NotTo(gbytes.Say(",data=writeback"))
		})

		Context("when using a file that does not exist", func() {
			It("should return an error", func() {
				Expect(loop.MountFile("/path/to/my/nonexisting/banana", "/path/to/dest")).To(HaveOccurred())
			})
		})
	})

	Describe("Unmount", func() {
		It("should not leak devices", func() {
			var devicesAfterCreate, devicesAfterRelease int

			destPaths := make([]string, 10)
			for i := 0; i < 10; i++ {
				var err error

				tempFile, err := ioutil.TempFile("", "")
				Expect(err).NotTo(HaveOccurred())

				_, err = exec.Command("truncate", "-s", "10M", tempFile.Name()).CombinedOutput()
				Expect(err).NotTo(HaveOccurred())
				_, err = exec.Command("mkfs.ext4", "-F", tempFile.Name()).CombinedOutput()
				Expect(err).NotTo(HaveOccurred())

				destPaths[i], err = ioutil.TempDir("", "")
				Expect(err).NotTo(HaveOccurred())

				Expect(loop.MountFile(tempFile.Name(), destPaths[i])).To(Succeed())
			}

			output, err := exec.Command("sh", "-c", "losetup -a | wc -l").CombinedOutput()
			Expect(err).NotTo(HaveOccurred())
			devicesAfterCreate, err = strconv.Atoi(strings.TrimSpace(string(output)))
			Expect(err).NotTo(HaveOccurred())

			for i := 0; i < 10; i++ {
				Expect(loop.Unmount(destPaths[i])).To(Succeed())
			}

			output, err = exec.Command("sh", "-c", "losetup -a | wc -l").CombinedOutput()
			Expect(err).NotTo(HaveOccurred())
			devicesAfterRelease, err = strconv.Atoi(strings.TrimSpace(string(output)))
			Expect(err).NotTo(HaveOccurred())

			Expect(devicesAfterRelease).To(BeNumerically("~", devicesAfterCreate-10, 2))
		})

		Describe("retrying the unmount when it doesn't immediately work", func() {
			var testFile *os.File

			BeforeEach(func() {
				Expect(loop.MountFile(bsFilePath, destPath)).To(Succeed())
				var err error
				testFile, err = ioutil.TempFile(destPath, "")
				Expect(err).NotTo(HaveOccurred())
			})

			It("fails when the unmount never succeeds", func() {
				defer func() {
					Expect(testFile.Close()).To(Succeed())
					Expect(loop.Unmount(destPath)).To(Succeed())
				}()

				Expect(loop.Unmount(destPath)).To(MatchError(ContainSubstring("unmounting file: exit status 1")))
			})

			It("suceeds when the unmount eventually succeeds", func() {
				fakeRetrier.RunStub = func(fn func() error) error {
					Expect(fn()).NotTo(Succeed())
					Expect(testFile.Close()).To(Succeed())
					Expect(fn()).To(Succeed())
					return nil
				}

				Expect(loop.Unmount(destPath)).To(Succeed())
			})
		})

		Context("when the provided mount point does not exist", func() {
			It("should succeed", func() {
				Expect(loop.Unmount("/dev/loopbanana")).To(Succeed())
			})
		})
	})
})
<commit_msg>Filter only the loopdevices created by test to address flakiness<commit_after>package aufs_test

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"syscall"
	"time"

	"code.cloudfoundry.org/garden-shed/docker_drivers/aufs"
	fakes "code.cloudfoundry.org/garden-shed/docker_drivers/aufs/aufsfakes"
	"code.cloudfoundry.org/lager/lagertest"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
)

const (
	pollingInterval = 100 * time.Millisecond
	numRetries      = 3 // At least 3 so that we can test the case where it works after multiple (2) retries
)

var _ = Describe("LoopLinux", func() {
	var (
		bsFilePath string
		destPath   string
		loop       *aufs.Loop

		fakeRetrier *fakes.FakeRetrier
	)

	BeforeEach(func() {
		var err error

		tempFile, err := ioutil.TempFile("", "loop")
		Expect(err).NotTo(HaveOccurred())
		bsFilePath = tempFile.Name()
		_, err = exec.Command("truncate", "-s", "10M", bsFilePath).CombinedOutput()
		Expect(err).NotTo(HaveOccurred())
		_, err = exec.Command("mkfs.ext4", "-F", bsFilePath).CombinedOutput()
		Expect(err).NotTo(HaveOccurred())

		destPath, err = ioutil.TempDir("", "loop")
		Expect(err).NotTo(HaveOccurred())

		fakeRetrier = new(fakes.FakeRetrier)
		fakeRetrier.RunStub = func(fn func() error) error {
			return fn()
		}

		loop = &aufs.Loop{
			Logger:  lagertest.NewTestLogger("test"),
			Retrier: fakeRetrier,
		}
	})

	AfterEach(func() {
		syscall.Unmount(destPath, 0)
		Expect(os.RemoveAll(destPath)).To(Succeed())
		Expect(os.Remove(bsFilePath)).To(Succeed())
	})

	Describe("MountFile", func() {
		It("mounts the file with noatime but not the journal_data_writeback options", func() {
			Expect(loop.MountFile(bsFilePath, destPath)).To(Succeed())

			session, err := gexec.Start(exec.Command("losetup", "-j", bsFilePath), GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())
			loopDev := strings.TrimSpace(strings.Split(string(session.Out.Contents()), ":")[0])

			session, err = gexec.Start(exec.Command("cat", "/proc/mounts"), GinkgoWriter, GinkgoWriter)
			Expect(err).NotTo(HaveOccurred())
			Eventually(session).Should(gbytes.Say(fmt.Sprintf("%s %s ext4 rw,noatime", loopDev, destPath)))
			Expect(session).NotTo(gbytes.Say(",data=writeback"))
		})

		Context("when using a file that does not exist", func() {
			It("should return an error", func() {
				Expect(loop.MountFile("/path/to/my/nonexisting/banana", "/path/to/dest")).To(HaveOccurred())
			})
		})
	})

	Describe("Unmount", func() {
		It("should not leak devices", func() {
			var devicesAfterCreate, devicesAfterRelease int

			destPaths := make([]string, 10)
			for i := 0; i < 10; i++ {
				var err error

				tempFile, err := ioutil.TempFile("", "dinosaur")
				Expect(err).NotTo(HaveOccurred())

				_, err = exec.Command("truncate", "-s", "10M", tempFile.Name()).CombinedOutput()
				Expect(err).NotTo(HaveOccurred())
				_, err = exec.Command("mkfs.ext4", "-F", tempFile.Name()).CombinedOutput()
				Expect(err).NotTo(HaveOccurred())

				destPaths[i], err = ioutil.TempDir("", "")
				Expect(err).NotTo(HaveOccurred())

				Expect(loop.MountFile(tempFile.Name(), destPaths[i])).To(Succeed())
			}

			output, err := exec.Command("sh", "-c", "losetup -a | grep dinosaur | wc -l").CombinedOutput()
			Expect(err).NotTo(HaveOccurred())
			devicesAfterCreate, err = strconv.Atoi(strings.TrimSpace(string(output)))
			Expect(err).NotTo(HaveOccurred())

			for i := 0; i < 10; i++ {
				Expect(loop.Unmount(destPaths[i])).To(Succeed())
			}

			output, err = exec.Command("sh", "-c", "losetup -a | grep dinosaur | wc -l").CombinedOutput()
			Expect(err).NotTo(HaveOccurred())
			devicesAfterRelease, err = strconv.Atoi(strings.TrimSpace(string(output)))
			Expect(err).NotTo(HaveOccurred())

			Expect(devicesAfterRelease).To(BeNumerically("~", devicesAfterCreate-10, 2))
		})

		Describe("retrying the unmount when it doesn't immediately work", func() {
			var testFile *os.File

			BeforeEach(func() {
				Expect(loop.MountFile(bsFilePath, destPath)).To(Succeed())
				var err error
				testFile, err = ioutil.TempFile(destPath, "")
				Expect(err).NotTo(HaveOccurred())
			})

			It("fails when the unmount never succeeds", func() {
				defer func() {
					Expect(testFile.Close()).To(Succeed())
					Expect(loop.Unmount(destPath)).To(Succeed())
				}()

				Expect(loop.Unmount(destPath)).To(MatchError(ContainSubstring("unmounting file: exit status 1")))
			})

			It("suceeds when the unmount eventually succeeds", func() {
				fakeRetrier.RunStub = func(fn func() error) error {
					Expect(fn()).NotTo(Succeed())
					Expect(testFile.Close()).To(Succeed())
					Expect(fn()).To(Succeed())
					return nil
				}

				Expect(loop.Unmount(destPath)).To(Succeed())
			})
		})

		Context("when the provided mount point does not exist", func() {
			It("should succeed", func() {
				Expect(loop.Unmount("/dev/loopbanana")).To(Succeed())
			})
		})
	})
})
<|endoftext|>


<commit_before>// Ogo

package ogo

import (
	//"fmt"
	"github.com/zenazn/goji"
	"github.com/zenazn/goji/web"
	"net/http"
	"strings"
)

type Handler func(c *RESTContext)

type Route struct {
	Pattern string
	Method  string
	Handler Handler
}

type Controller struct {
	Endpoint string
	Routes   map[string]*Route
	ReqCount int //访问计数
}

type ControllerInterface interface {
	//Init(endpoint string, c ControllerInterface)
	Get(c *RESTContext)
	Post(c *RESTContext)
	Put(c *RESTContext)
	Delete(c *RESTContext)
	Patch(c *RESTContext)
	Head(c *RESTContext)
	Options(c *RESTContext)
	Trace(c *RESTContext)
	NotFound(c *RESTContext)
}

func NewRoute(p string, m string, h Handler) *Route {
	return &Route{
		Pattern: p,
		Method:  m,
		Handler: h,
	}
}

// 封装
func handlerWrap(f Handler) web.HandlerFunc { //这里封装了webC到本地的结构中
	return func(c web.C, w http.ResponseWriter, r *http.Request) {
		f(getContext(c, w, r))
	}
}

func (ctr *Controller) Init(endpoint string, c ControllerInterface) {
	ctr.Endpoint = endpoint
	//ctr.Routes = make(map[string]*Route)
	//默认路由
	ctr.DefaultRoutes(c)
	if len(ctr.Routes) > 0 {
		for _, rt := range ctr.Routes {
			switch strings.ToLower(rt.Method) {
			case "get":
				ctr.RouteGet(rt)
			case "post":
				ctr.RoutePost(rt)
			case "put":
				ctr.RoutePut(rt)
			case "delete":
				ctr.RouteDelete(rt)
			case "patch":
				ctr.RoutePatch(rt)
			case "head":
				ctr.RouteHead(rt)
			default:
				// unknow method
			}
		}
	}
	// not found
	ctr.RouteNotFound(c.NotFound)
}

func (ctr *Controller) Get(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Post(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Put(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Delete(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Patch(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Head(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Options(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Trace(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) NotFound(c *RESTContext) {
	c.HTTPError(http.StatusNotFound)
}

func (ctr *Controller) AddRoute(m string, p string, h Handler) {
	key := strings.ToUpper(m) + " " + p
	if ctr.Routes == nil {
		ctr.Routes = make(map[string]*Route)
	}
	if _, ok := ctr.Routes[key]; ok {
		//手动加路由, 以最后加的为准,overwrite
	}
	ctr.Routes[key] = NewRoute(p, m, h)
}

// controller default route
// 默认路由, 如果已经定义了则忽略，没有定义则加上
//func (ctr *Controller) DefaultRoutes() {
func (ctr *Controller) DefaultRoutes(c ControllerInterface) {
	var pattern, method, key string
	// GET /{endpoint}
	pattern = "/" + ctr.Endpoint
	method = "GET"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Get)
		ctr.Routes[key] = rt
	}

	// GET /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "GET"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Get)
		ctr.Routes[key] = rt
	}

	// POST /{endpoint}
	pattern = "/" + ctr.Endpoint
	method = "POST"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Post)
		ctr.Routes[key] = rt
	}

	// DELETE /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "DELETE"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Delete)
		ctr.Routes[key] = rt
	}

	// PATCH /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "PATCH"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Patch)
		ctr.Routes[key] = rt
	}

	// PUT /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "PUT"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Put)
		ctr.Routes[key] = rt
	}
}

func (ctr *Controller) RouteGet(rt *Route) {
	goji.Get(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RoutePost(rt *Route) {
	goji.Post(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RoutePut(rt *Route) {
	goji.Put(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RouteDelete(rt *Route) {
	goji.Delete(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RoutePatch(rt *Route) {
	goji.Patch(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RouteHead(rt *Route) {
	goji.Head(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RouteNotFound(h Handler) {
	goji.NotFound(handlerWrap(h))
}
<commit_msg>bugfix<commit_after>// Ogo

package ogo

import (
	//"fmt"
	"github.com/zenazn/goji"
	"github.com/zenazn/goji/web"
	"net/http"
	"strings"
)

type Handler func(c *RESTContext)

type Route struct {
	Pattern string
	Method  string
	Handler Handler
}

type Controller struct {
	Endpoint string
	Routes   map[string]*Route
	ReqCount int //访问计数
}

type ControllerInterface interface {
	//Init(endpoint string, c ControllerInterface)
	Get(c *RESTContext)
	Post(c *RESTContext)
	Put(c *RESTContext)
	Delete(c *RESTContext)
	Patch(c *RESTContext)
	Head(c *RESTContext)
	Options(c *RESTContext)
	Trace(c *RESTContext)
	NotFound(c *RESTContext)
}

func NewRoute(p string, m string, h Handler) *Route {
	return &Route{
		Pattern: p,
		Method:  m,
		Handler: h,
	}
}

// 封装
func handlerWrap(f Handler) web.HandlerFunc { //这里封装了webC到本地的结构中
	return func(c web.C, w http.ResponseWriter, r *http.Request) {
		f(getContext(c, w, r))
	}
}

func (ctr *Controller) Init(endpoint string, c ControllerInterface) {
	ctr.Endpoint = endpoint
	//ctr.Routes = make(map[string]*Route)
	//默认路由
	ctr.DefaultRoutes(c)
	if len(ctr.Routes) > 0 {
		for _, rt := range ctr.Routes {
			switch strings.ToLower(rt.Method) {
			case "get":
				ctr.RouteGet(rt)
			case "post":
				ctr.RoutePost(rt)
			case "put":
				ctr.RoutePut(rt)
			case "delete":
				ctr.RouteDelete(rt)
			case "patch":
				ctr.RoutePatch(rt)
			case "head":
				ctr.RouteHead(rt)
			default:
				// unknow method
			}
		}
	}
	// not found
	ctr.RouteNotFound(c.NotFound)
}

func (ctr *Controller) Get(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Post(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Put(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Delete(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Patch(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Head(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Options(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) Trace(c *RESTContext) {
	c.HTTPError(http.StatusMethodNotAllowed)
}
func (ctr *Controller) NotFound(c *RESTContext) {
	c.HTTPError(http.StatusNotFound)
}

func (ctr *Controller) AddRoute(m string, p string, h Handler) {
	key := strings.ToUpper(m) + " " + p
	if ctr.Routes == nil {
		ctr.Routes = make(map[string]*Route)
	}
	if _, ok := ctr.Routes[key]; ok {
		//手动加路由, 以最后加的为准,overwrite
	}
	ctr.Routes[key] = NewRoute(p, m, h)
}

// controller default route
// 默认路由, 如果已经定义了则忽略，没有定义则加上
//func (ctr *Controller) DefaultRoutes() {
func (ctr *Controller) DefaultRoutes(c ControllerInterface) {
	var pattern, method, key string
	// GET /{endpoint}
	pattern = "/" + ctr.Endpoint
	method = "GET"
	key = method + " " + pattern
	if ctr.Routes == nil {
		ctr.Routes = make(map[string]*Route)
	}
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Get)
		ctr.Routes[key] = rt
	}

	// GET /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "GET"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Get)
		ctr.Routes[key] = rt
	}

	// POST /{endpoint}
	pattern = "/" + ctr.Endpoint
	method = "POST"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Post)
		ctr.Routes[key] = rt
	}

	// DELETE /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "DELETE"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Delete)
		ctr.Routes[key] = rt
	}

	// PATCH /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "PATCH"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Patch)
		ctr.Routes[key] = rt
	}

	// PUT /{endpoint}/{id}
	pattern = "/" + ctr.Endpoint + "/:_id_"
	method = "PUT"
	key = method + " " + pattern
	if _, ok := ctr.Routes[key]; ok {
		// exists, warning, 默认路由不能覆盖自定义路由
	} else {
		rt := NewRoute(pattern, method, c.Put)
		ctr.Routes[key] = rt
	}
}

func (ctr *Controller) RouteGet(rt *Route) {
	goji.Get(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RoutePost(rt *Route) {
	goji.Post(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RoutePut(rt *Route) {
	goji.Put(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RouteDelete(rt *Route) {
	goji.Delete(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RoutePatch(rt *Route) {
	goji.Patch(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RouteHead(rt *Route) {
	goji.Head(rt.Pattern, handlerWrap(rt.Handler))
}

func (ctr *Controller) RouteNotFound(h Handler) {
	goji.NotFound(handlerWrap(h))
}
<|endoftext|>