<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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 proxy

import (
	"bytes"
	"compress/flate"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"path"
	"strings"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
	"k8s.io/klog/v2"

	"k8s.io/apimachinery/pkg/util/net"
	"k8s.io/apimachinery/pkg/util/sets"
)

// atomsToAttrs states which attributes of which tags require URL substitution.
// Sources: http://www.w3.org/TR/REC-html40/index/attributes.html
//          http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1
var atomsToAttrs = map[atom.Atom]sets.String{
	atom.A:          sets.NewString("href"),
	atom.Applet:     sets.NewString("codebase"),
	atom.Area:       sets.NewString("href"),
	atom.Audio:      sets.NewString("src"),
	atom.Base:       sets.NewString("href"),
	atom.Blockquote: sets.NewString("cite"),
	atom.Body:       sets.NewString("background"),
	atom.Button:     sets.NewString("formaction"),
	atom.Command:    sets.NewString("icon"),
	atom.Del:        sets.NewString("cite"),
	atom.Embed:      sets.NewString("src"),
	atom.Form:       sets.NewString("action"),
	atom.Frame:      sets.NewString("longdesc", "src"),
	atom.Head:       sets.NewString("profile"),
	atom.Html:       sets.NewString("manifest"),
	atom.Iframe:     sets.NewString("longdesc", "src"),
	atom.Img:        sets.NewString("longdesc", "src", "usemap"),
	atom.Input:      sets.NewString("src", "usemap", "formaction"),
	atom.Ins:        sets.NewString("cite"),
	atom.Link:       sets.NewString("href"),
	atom.Object:     sets.NewString("classid", "codebase", "data", "usemap"),
	atom.Q:          sets.NewString("cite"),
	atom.Script:     sets.NewString("src"),
	atom.Source:     sets.NewString("src"),
	atom.Video:      sets.NewString("poster", "src"),

	// TODO: css URLs hidden in style elements.
}

// Transport is a transport for text/html content that replaces URLs in html
// content with the prefix of the proxy server
type Transport struct {
	Scheme      string
	Host        string
	PathPrepend string

	http.RoundTripper
}

// RoundTrip implements the http.RoundTripper interface
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	// Add reverse proxy headers.
	forwardedURI := path.Join(t.PathPrepend, req.URL.Path)
	if strings.HasSuffix(req.URL.Path, "/") {
		forwardedURI = forwardedURI + "/"
	}
	req.Header.Set("X-Forwarded-Uri", forwardedURI)
	if len(t.Host) > 0 {
		req.Header.Set("X-Forwarded-Host", t.Host)
	}
	if len(t.Scheme) > 0 {
		req.Header.Set("X-Forwarded-Proto", t.Scheme)
	}

	rt := t.RoundTripper
	if rt == nil {
		rt = http.DefaultTransport
	}
	resp, err := rt.RoundTrip(req)

	if err != nil {
		return nil, fmt.Errorf("error trying to reach service: %w", err)
	}

	if redirect := resp.Header.Get("Location"); redirect != "" {
		resp.Header.Set("Location", t.rewriteURL(redirect, req.URL, req.Host))
		return resp, nil
	}

	cType := resp.Header.Get("Content-Type")
	cType = strings.TrimSpace(strings.SplitN(cType, ";", 2)[0])
	if cType != "text/html" {
		// Do nothing, simply pass through
		return resp, nil
	}

	return t.rewriteResponse(req, resp)
}

var _ = net.RoundTripperWrapper(&Transport{})

func (rt *Transport) WrappedRoundTripper() http.RoundTripper {
	return rt.RoundTripper
}

// rewriteURL rewrites a single URL to go through the proxy, if the URL refers
// to the same host as sourceURL, which is the page on which the target URL
// occurred, or if the URL matches the sourceRequestHost. If any error occurs (e.g.
// parsing), it returns targetURL.
func (t *Transport) rewriteURL(targetURL string, sourceURL *url.URL, sourceRequestHost string) string {
	url, err := url.Parse(targetURL)
	if err != nil {
		return targetURL
	}

	// Example:
	//      When API server processes a proxy request to a service (e.g. /api/v1/namespace/foo/service/bar/proxy/),
	//      the sourceURL.Host (i.e. req.URL.Host) is the endpoint IP address of the service. The
	//      sourceRequestHost (i.e. req.Host) is the Host header that specifies the host on which the
	//      URL is sought, which can be different from sourceURL.Host. For example, if user sends the
	//      request through "kubectl proxy" locally (i.e. localhost:8001/api/v1/namespace/foo/service/bar/proxy/),
	//      sourceRequestHost is "localhost:8001".
	//
	//      If the service's response URL contains non-empty host, and url.Host is equal to either sourceURL.Host
	//      or sourceRequestHost, we should not consider the returned URL to be a completely different host.
	//      It's the API server's responsibility to rewrite a same-host-and-absolute-path URL and append the
	//      necessary URL prefix (i.e. /api/v1/namespace/foo/service/bar/proxy/).
	isDifferentHost := url.Host != "" && url.Host != sourceURL.Host && url.Host != sourceRequestHost
	isRelative := !strings.HasPrefix(url.Path, "/")
	if isDifferentHost || isRelative {
		return targetURL
	}

	// Do not rewrite scheme and host if the Transport has empty scheme and host
	// when targetURL already contains the sourceRequestHost
	if !(url.Host == sourceRequestHost && t.Scheme == "" && t.Host == "") {
		url.Scheme = t.Scheme
		url.Host = t.Host
	}

	origPath := url.Path
	// Do not rewrite URL if the sourceURL already contains the necessary prefix.
	if strings.HasPrefix(url.Path, t.PathPrepend) {
		return url.String()
	}
	url.Path = path.Join(t.PathPrepend, url.Path)
	if strings.HasSuffix(origPath, "/") {
		// Add back the trailing slash, which was stripped by path.Join().
		url.Path += "/"
	}

	return url.String()
}

// rewriteHTML scans the HTML for tags with url-valued attributes, and updates
// those values with the urlRewriter function. The updated HTML is output to the
// writer.
func rewriteHTML(reader io.Reader, writer io.Writer, urlRewriter func(string) string) error {
	// Note: This assumes the content is UTF-8.
	tokenizer := html.NewTokenizer(reader)

	var err error
	for err == nil {
		tokenType := tokenizer.Next()
		switch tokenType {
		case html.ErrorToken:
			err = tokenizer.Err()
		case html.StartTagToken, html.SelfClosingTagToken:
			token := tokenizer.Token()
			if urlAttrs, ok := atomsToAttrs[token.DataAtom]; ok {
				for i, attr := range token.Attr {
					if urlAttrs.Has(attr.Key) {
						token.Attr[i].Val = urlRewriter(attr.Val)
					}
				}
			}
			_, err = writer.Write([]byte(token.String()))
		default:
			_, err = writer.Write(tokenizer.Raw())
		}
	}
	if err != io.EOF {
		return err
	}
	return nil
}

// rewriteResponse modifies an HTML response by updating absolute links referring
// to the original host to instead refer to the proxy transport.
func (t *Transport) rewriteResponse(req *http.Request, resp *http.Response) (*http.Response, error) {
	origBody := resp.Body
	defer origBody.Close()

	newContent := &bytes.Buffer{}
	var reader io.Reader = origBody
	var writer io.Writer = newContent
	encoding := resp.Header.Get("Content-Encoding")
	switch encoding {
	case "gzip":
		var err error
		reader, err = gzip.NewReader(reader)
		if err != nil {
			return nil, fmt.Errorf("errorf making gzip reader: %v", err)
		}
		gzw := gzip.NewWriter(writer)
		defer gzw.Close()
		writer = gzw
	case "deflate":
		var err error
		reader = flate.NewReader(reader)
		flw, err := flate.NewWriter(writer, flate.BestCompression)
		if err != nil {
			return nil, fmt.Errorf("errorf making flate writer: %v", err)
		}
		defer func() {
			flw.Close()
			flw.Flush()
		}()
		writer = flw
	case "":
		// This is fine
	default:
		// Some encoding we don't understand-- don't try to parse this
		klog.Errorf("Proxy encountered encoding %v for text/html; can't understand this so not fixing links.", encoding)
		return resp, nil
	}

	urlRewriter := func(targetUrl string) string {
		return t.rewriteURL(targetUrl, req.URL, req.Host)
	}
	err := rewriteHTML(reader, writer, urlRewriter)
	if err != nil {
		klog.Errorf("Failed to rewrite URLs: %v", err)
		return resp, err
	}

	resp.Body = ioutil.NopCloser(newContent)
	// Update header node with new content-length
	// TODO: Remove any hash/signature headers here?
	resp.Header.Del("Content-Length")
	resp.ContentLength = int64(newContent.Len())

	return resp, err
}
<commit_msg>make proxy Transport return metav1.Status error<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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 proxy

import (
	"bytes"
	"compress/flate"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"path"
	"strings"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
	"k8s.io/klog/v2"

	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/util/net"
	"k8s.io/apimachinery/pkg/util/sets"
)

// atomsToAttrs states which attributes of which tags require URL substitution.
// Sources: http://www.w3.org/TR/REC-html40/index/attributes.html
//          http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1
var atomsToAttrs = map[atom.Atom]sets.String{
	atom.A:          sets.NewString("href"),
	atom.Applet:     sets.NewString("codebase"),
	atom.Area:       sets.NewString("href"),
	atom.Audio:      sets.NewString("src"),
	atom.Base:       sets.NewString("href"),
	atom.Blockquote: sets.NewString("cite"),
	atom.Body:       sets.NewString("background"),
	atom.Button:     sets.NewString("formaction"),
	atom.Command:    sets.NewString("icon"),
	atom.Del:        sets.NewString("cite"),
	atom.Embed:      sets.NewString("src"),
	atom.Form:       sets.NewString("action"),
	atom.Frame:      sets.NewString("longdesc", "src"),
	atom.Head:       sets.NewString("profile"),
	atom.Html:       sets.NewString("manifest"),
	atom.Iframe:     sets.NewString("longdesc", "src"),
	atom.Img:        sets.NewString("longdesc", "src", "usemap"),
	atom.Input:      sets.NewString("src", "usemap", "formaction"),
	atom.Ins:        sets.NewString("cite"),
	atom.Link:       sets.NewString("href"),
	atom.Object:     sets.NewString("classid", "codebase", "data", "usemap"),
	atom.Q:          sets.NewString("cite"),
	atom.Script:     sets.NewString("src"),
	atom.Source:     sets.NewString("src"),
	atom.Video:      sets.NewString("poster", "src"),

	// TODO: css URLs hidden in style elements.
}

// Transport is a transport for text/html content that replaces URLs in html
// content with the prefix of the proxy server
type Transport struct {
	Scheme      string
	Host        string
	PathPrepend string

	http.RoundTripper
}

// RoundTrip implements the http.RoundTripper interface
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	// Add reverse proxy headers.
	forwardedURI := path.Join(t.PathPrepend, req.URL.Path)
	if strings.HasSuffix(req.URL.Path, "/") {
		forwardedURI = forwardedURI + "/"
	}
	req.Header.Set("X-Forwarded-Uri", forwardedURI)
	if len(t.Host) > 0 {
		req.Header.Set("X-Forwarded-Host", t.Host)
	}
	if len(t.Scheme) > 0 {
		req.Header.Set("X-Forwarded-Proto", t.Scheme)
	}

	rt := t.RoundTripper
	if rt == nil {
		rt = http.DefaultTransport
	}
	resp, err := rt.RoundTrip(req)

	if err != nil {
		return nil, errors.NewServiceUnavailable(fmt.Sprintf("error trying to reach service: %v", err))
	}

	if redirect := resp.Header.Get("Location"); redirect != "" {
		resp.Header.Set("Location", t.rewriteURL(redirect, req.URL, req.Host))
		return resp, nil
	}

	cType := resp.Header.Get("Content-Type")
	cType = strings.TrimSpace(strings.SplitN(cType, ";", 2)[0])
	if cType != "text/html" {
		// Do nothing, simply pass through
		return resp, nil
	}

	return t.rewriteResponse(req, resp)
}

var _ = net.RoundTripperWrapper(&Transport{})

func (rt *Transport) WrappedRoundTripper() http.RoundTripper {
	return rt.RoundTripper
}

// rewriteURL rewrites a single URL to go through the proxy, if the URL refers
// to the same host as sourceURL, which is the page on which the target URL
// occurred, or if the URL matches the sourceRequestHost. If any error occurs (e.g.
// parsing), it returns targetURL.
func (t *Transport) rewriteURL(targetURL string, sourceURL *url.URL, sourceRequestHost string) string {
	url, err := url.Parse(targetURL)
	if err != nil {
		return targetURL
	}

	// Example:
	//      When API server processes a proxy request to a service (e.g. /api/v1/namespace/foo/service/bar/proxy/),
	//      the sourceURL.Host (i.e. req.URL.Host) is the endpoint IP address of the service. The
	//      sourceRequestHost (i.e. req.Host) is the Host header that specifies the host on which the
	//      URL is sought, which can be different from sourceURL.Host. For example, if user sends the
	//      request through "kubectl proxy" locally (i.e. localhost:8001/api/v1/namespace/foo/service/bar/proxy/),
	//      sourceRequestHost is "localhost:8001".
	//
	//      If the service's response URL contains non-empty host, and url.Host is equal to either sourceURL.Host
	//      or sourceRequestHost, we should not consider the returned URL to be a completely different host.
	//      It's the API server's responsibility to rewrite a same-host-and-absolute-path URL and append the
	//      necessary URL prefix (i.e. /api/v1/namespace/foo/service/bar/proxy/).
	isDifferentHost := url.Host != "" && url.Host != sourceURL.Host && url.Host != sourceRequestHost
	isRelative := !strings.HasPrefix(url.Path, "/")
	if isDifferentHost || isRelative {
		return targetURL
	}

	// Do not rewrite scheme and host if the Transport has empty scheme and host
	// when targetURL already contains the sourceRequestHost
	if !(url.Host == sourceRequestHost && t.Scheme == "" && t.Host == "") {
		url.Scheme = t.Scheme
		url.Host = t.Host
	}

	origPath := url.Path
	// Do not rewrite URL if the sourceURL already contains the necessary prefix.
	if strings.HasPrefix(url.Path, t.PathPrepend) {
		return url.String()
	}
	url.Path = path.Join(t.PathPrepend, url.Path)
	if strings.HasSuffix(origPath, "/") {
		// Add back the trailing slash, which was stripped by path.Join().
		url.Path += "/"
	}

	return url.String()
}

// rewriteHTML scans the HTML for tags with url-valued attributes, and updates
// those values with the urlRewriter function. The updated HTML is output to the
// writer.
func rewriteHTML(reader io.Reader, writer io.Writer, urlRewriter func(string) string) error {
	// Note: This assumes the content is UTF-8.
	tokenizer := html.NewTokenizer(reader)

	var err error
	for err == nil {
		tokenType := tokenizer.Next()
		switch tokenType {
		case html.ErrorToken:
			err = tokenizer.Err()
		case html.StartTagToken, html.SelfClosingTagToken:
			token := tokenizer.Token()
			if urlAttrs, ok := atomsToAttrs[token.DataAtom]; ok {
				for i, attr := range token.Attr {
					if urlAttrs.Has(attr.Key) {
						token.Attr[i].Val = urlRewriter(attr.Val)
					}
				}
			}
			_, err = writer.Write([]byte(token.String()))
		default:
			_, err = writer.Write(tokenizer.Raw())
		}
	}
	if err != io.EOF {
		return err
	}
	return nil
}

// rewriteResponse modifies an HTML response by updating absolute links referring
// to the original host to instead refer to the proxy transport.
func (t *Transport) rewriteResponse(req *http.Request, resp *http.Response) (*http.Response, error) {
	origBody := resp.Body
	defer origBody.Close()

	newContent := &bytes.Buffer{}
	var reader io.Reader = origBody
	var writer io.Writer = newContent
	encoding := resp.Header.Get("Content-Encoding")
	switch encoding {
	case "gzip":
		var err error
		reader, err = gzip.NewReader(reader)
		if err != nil {
			return nil, fmt.Errorf("errorf making gzip reader: %v", err)
		}
		gzw := gzip.NewWriter(writer)
		defer gzw.Close()
		writer = gzw
	case "deflate":
		var err error
		reader = flate.NewReader(reader)
		flw, err := flate.NewWriter(writer, flate.BestCompression)
		if err != nil {
			return nil, fmt.Errorf("errorf making flate writer: %v", err)
		}
		defer func() {
			flw.Close()
			flw.Flush()
		}()
		writer = flw
	case "":
		// This is fine
	default:
		// Some encoding we don't understand-- don't try to parse this
		klog.Errorf("Proxy encountered encoding %v for text/html; can't understand this so not fixing links.", encoding)
		return resp, nil
	}

	urlRewriter := func(targetUrl string) string {
		return t.rewriteURL(targetUrl, req.URL, req.Host)
	}
	err := rewriteHTML(reader, writer, urlRewriter)
	if err != nil {
		klog.Errorf("Failed to rewrite URLs: %v", err)
		return resp, err
	}

	resp.Body = ioutil.NopCloser(newContent)
	// Update header node with new content-length
	// TODO: Remove any hash/signature headers here?
	resp.Header.Del("Content-Length")
	resp.ContentLength = int64(newContent.Len())

	return resp, err
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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 cache

import (
	"fmt"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"

	"github.com/golang/glog"
)

// if you use this, there is one behavior change compared to a standard Informer.
// When you receive a notification, the cache will be AT LEAST as fresh as the
// notification, but it MAY be more fresh.  You should NOT depend on the contents
// of the cache exactly matching the notification you've received in handler
// functions.  If there was a create, followed by a delete, the cache may NOT
// have your item.  This has advantages over the broadcaster since it allows us
// to share a common cache across many controllers. Extending the broadcaster
// would have required us keep duplicate caches for each watch.
type SharedInformer interface {
	// events to a single handler are delivered sequentially, but there is no coordination between different handlers
	// You may NOT add a handler *after* the SharedInformer is running.  That will result in an error being returned.
	// TODO we should try to remove this restriction eventually.
	AddEventHandler(handler ResourceEventHandler) error
	GetStore() Store
	// GetController gives back a synthetic interface that "votes" to start the informer
	GetController() Controller
	Run(stopCh <-chan struct{})
	HasSynced() bool
	LastSyncResourceVersion() string
}

type SharedIndexInformer interface {
	SharedInformer
	// AddIndexers add indexers to the informer before it starts.
	AddIndexers(indexers Indexers) error
	GetIndexer() Indexer
}

// NewSharedInformer creates a new instance for the listwatcher.
// TODO: create a cache/factory of these at a higher level for the list all, watch all of a given resource that can
// be shared amongst all consumers.
func NewSharedInformer(lw ListerWatcher, objType runtime.Object, resyncPeriod time.Duration) SharedInformer {
	return NewSharedIndexInformer(lw, objType, resyncPeriod, Indexers{})
}

// NewSharedIndexInformer creates a new instance for the listwatcher.
// TODO: create a cache/factory of these at a higher level for the list all, watch all of a given resource that can
// be shared amongst all consumers.
func NewSharedIndexInformer(lw ListerWatcher, objType runtime.Object, resyncPeriod time.Duration, indexers Indexers) SharedIndexInformer {
	sharedIndexInformer := &sharedIndexInformer{
		processor:             &sharedProcessor{},
		indexer:               NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers),
		listerWatcher:         lw,
		objectType:            objType,
		fullResyncPeriod:      resyncPeriod,
		cacheMutationDetector: NewCacheMutationDetector(fmt.Sprintf("%T", objType)),
	}
	return sharedIndexInformer
}

// InformerSynced is a function that can be used to determine if an informer has synced.  This is useful for determining if caches have synced.
type InformerSynced func() bool

// syncedPollPeriod controls how often you look at the status of your sync funcs
const syncedPollPeriod = 100 * time.Millisecond

// WaitForCacheSync waits for caches to populate.  It returns true if it was successful, false
// if the contoller should shutdown
func WaitForCacheSync(stopCh <-chan struct{}, cacheSyncs ...InformerSynced) bool {
	err := wait.PollUntil(syncedPollPeriod,
		func() (bool, error) {
			for _, syncFunc := range cacheSyncs {
				if !syncFunc() {
					return false, nil
				}
			}
			return true, nil
		},
		stopCh)
	if err != nil {
		glog.V(2).Infof("stop requested")
		return false
	}

	glog.V(4).Infof("caches populated")
	return true
}

type sharedIndexInformer struct {
	indexer    Indexer
	controller Controller

	processor             *sharedProcessor
	cacheMutationDetector CacheMutationDetector

	// This block is tracked to handle late initialization of the controller
	listerWatcher    ListerWatcher
	objectType       runtime.Object
	fullResyncPeriod time.Duration

	started     bool
	startedLock sync.Mutex

	// blockDeltas gives a way to stop all event distribution so that a late event handler
	// can safely join the shared informer.
	blockDeltas sync.Mutex
	// stopCh is the channel used to stop the main Run process.  We have to track it so that
	// late joiners can have a proper stop
	stopCh <-chan struct{}
}

// dummyController hides the fact that a SharedInformer is different from a dedicated one
// where a caller can `Run`.  The run method is disonnected in this case, because higher
// level logic will decide when to start the SharedInformer and related controller.
// Because returning information back is always asynchronous, the legacy callers shouldn't
// notice any change in behavior.
type dummyController struct {
	informer *sharedIndexInformer
}

func (v *dummyController) Run(stopCh <-chan struct{}) {
}

func (v *dummyController) HasSynced() bool {
	return v.informer.HasSynced()
}

func (c *dummyController) LastSyncResourceVersion() string {
	return ""
}

type updateNotification struct {
	oldObj interface{}
	newObj interface{}
}

type addNotification struct {
	newObj interface{}
}

type deleteNotification struct {
	oldObj interface{}
}

func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, nil, s.indexer)

	cfg := &Config{
		Queue:            fifo,
		ListerWatcher:    s.listerWatcher,
		ObjectType:       s.objectType,
		FullResyncPeriod: s.fullResyncPeriod,
		RetryOnError:     false,

		Process: s.HandleDeltas,
	}

	func() {
		s.startedLock.Lock()
		defer s.startedLock.Unlock()

		s.controller = New(cfg)
		s.started = true
	}()

	s.stopCh = stopCh
	s.cacheMutationDetector.Run(stopCh)
	s.processor.run(stopCh)
	s.controller.Run(stopCh)
}

func (s *sharedIndexInformer) isStarted() bool {
	s.startedLock.Lock()
	defer s.startedLock.Unlock()
	return s.started
}

func (s *sharedIndexInformer) HasSynced() bool {
	s.startedLock.Lock()
	defer s.startedLock.Unlock()

	if s.controller == nil {
		return false
	}
	return s.controller.HasSynced()
}

func (s *sharedIndexInformer) LastSyncResourceVersion() string {
	s.startedLock.Lock()
	defer s.startedLock.Unlock()

	if s.controller == nil {
		return ""
	}
	return s.controller.LastSyncResourceVersion()
}

func (s *sharedIndexInformer) GetStore() Store {
	return s.indexer
}

func (s *sharedIndexInformer) GetIndexer() Indexer {
	return s.indexer
}

func (s *sharedIndexInformer) AddIndexers(indexers Indexers) error {
	s.startedLock.Lock()
	defer s.startedLock.Unlock()

	if s.started {
		return fmt.Errorf("informer has already started")
	}

	return s.indexer.AddIndexers(indexers)
}

func (s *sharedIndexInformer) GetController() Controller {
	return &dummyController{informer: s}
}

func (s *sharedIndexInformer) AddEventHandler(handler ResourceEventHandler) error {
	s.startedLock.Lock()
	defer s.startedLock.Unlock()

	if !s.started {
		listener := newProcessListener(handler)
		s.processor.listeners = append(s.processor.listeners, listener)
		return nil
	}

	// in order to safely join, we have to
	// 1. stop sending add/update/delete notifications
	// 2. do a list against the store
	// 3. send synthetic "Add" events to the new handler
	// 4. unblock
	s.blockDeltas.Lock()
	defer s.blockDeltas.Unlock()

	listener := newProcessListener(handler)
	s.processor.listeners = append(s.processor.listeners, listener)

	go listener.run(s.stopCh)
	go listener.pop(s.stopCh)

	items := s.indexer.List()
	for i := range items {
		listener.add(addNotification{newObj: items[i]})
	}

	return nil
}

func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
	s.blockDeltas.Lock()
	defer s.blockDeltas.Unlock()

	// from oldest to newest
	for _, d := range obj.(Deltas) {
		switch d.Type {
		case Sync, Added, Updated:
			s.cacheMutationDetector.AddObject(d.Object)
			if old, exists, err := s.indexer.Get(d.Object); err == nil && exists {
				if err := s.indexer.Update(d.Object); err != nil {
					return err
				}
				s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object})
			} else {
				if err := s.indexer.Add(d.Object); err != nil {
					return err
				}
				s.processor.distribute(addNotification{newObj: d.Object})
			}
		case Deleted:
			if err := s.indexer.Delete(d.Object); err != nil {
				return err
			}
			s.processor.distribute(deleteNotification{oldObj: d.Object})
		}
	}
	return nil
}

type sharedProcessor struct {
	listeners []*processorListener
}

func (p *sharedProcessor) distribute(obj interface{}) {
	for _, listener := range p.listeners {
		listener.add(obj)
	}
}

func (p *sharedProcessor) run(stopCh <-chan struct{}) {
	for _, listener := range p.listeners {
		go listener.run(stopCh)
		go listener.pop(stopCh)
	}
}

type processorListener struct {
	// lock/cond protects access to 'pendingNotifications'.
	lock sync.RWMutex
	cond sync.Cond

	// pendingNotifications is an unbounded slice that holds all notifications not yet distributed
	// there is one per listener, but a failing/stalled listener will have infinite pendingNotifications
	// added until we OOM.
	// TODO This is no worse that before, since reflectors were backed by unbounded DeltaFIFOs, but
	// we should try to do something better
	pendingNotifications []interface{}

	nextCh chan interface{}

	handler ResourceEventHandler
}

func newProcessListener(handler ResourceEventHandler) *processorListener {
	ret := &processorListener{
		pendingNotifications: []interface{}{},
		nextCh:               make(chan interface{}),
		handler:              handler,
	}

	ret.cond.L = &ret.lock
	return ret
}

func (p *processorListener) add(notification interface{}) {
	p.lock.Lock()
	defer p.lock.Unlock()

	p.pendingNotifications = append(p.pendingNotifications, notification)
	p.cond.Broadcast()
}

func (p *processorListener) pop(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	for {
		blockingGet := func() (interface{}, bool) {
			p.lock.Lock()
			defer p.lock.Unlock()

			for len(p.pendingNotifications) == 0 {
				// check if we're shutdown
				select {
				case <-stopCh:
					return nil, true
				default:
				}
				p.cond.Wait()
			}

			nt := p.pendingNotifications[0]
			p.pendingNotifications = p.pendingNotifications[1:]
			return nt, false
		}

		notification, stopped := blockingGet()
		if stopped {
			return
		}

		select {
		case <-stopCh:
			return
		case p.nextCh <- notification:
		}
	}
}

func (p *processorListener) run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	for {
		var next interface{}
		select {
		case <-stopCh:
			func() {
				p.lock.Lock()
				defer p.lock.Unlock()
				p.cond.Broadcast()
			}()
			return
		case next = <-p.nextCh:
		}

		switch notification := next.(type) {
		case updateNotification:
			p.handler.OnUpdate(notification.oldObj, notification.newObj)
		case addNotification:
			p.handler.OnAdd(notification.newObj)
		case deleteNotification:
			p.handler.OnDelete(notification.oldObj)
		default:
			utilruntime.HandleError(fmt.Errorf("unrecognized notification: %#v", next))
		}
	}
}
<commit_msg>OWNERS: Create sig-node alias Create an alias group for sig-node WaitForCacheSync method comment "contoller" is error word<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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 cache

import (
	"fmt"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"

	"github.com/golang/glog"
)

// if you use this, there is one behavior change compared to a standard Informer.
// When you receive a notification, the cache will be AT LEAST as fresh as the
// notification, but it MAY be more fresh.  You should NOT depend on the contents
// of the cache exactly matching the notification you've received in handler
// functions.  If there was a create, followed by a delete, the cache may NOT
// have your item.  This has advantages over the broadcaster since it allows us
// to share a common cache across many controllers. Extending the broadcaster
// would have required us keep duplicate caches for each watch.
type SharedInformer interface {
	// events to a single handler are delivered sequentially, but there is no coordination between different handlers
	// You may NOT add a handler *after* the SharedInformer is running.  That will result in an error being returned.
	// TODO we should try to remove this restriction eventually.
	AddEventHandler(handler ResourceEventHandler) error
	GetStore() Store
	// GetController gives back a synthetic interface that "votes" to start the informer
	GetController() Controller
	Run(stopCh <-chan struct{})
	HasSynced() bool
	LastSyncResourceVersion() string
}

type SharedIndexInformer interface {
	SharedInformer
	// AddIndexers add indexers to the informer before it starts.
	AddIndexers(indexers Indexers) error
	GetIndexer() Indexer
}

// NewSharedInformer creates a new instance for the listwatcher.
// TODO: create a cache/factory of these at a higher level for the list all, watch all of a given resource that can
// be shared amongst all consumers.
func NewSharedInformer(lw ListerWatcher, objType runtime.Object, resyncPeriod time.Duration) SharedInformer {
	return NewSharedIndexInformer(lw, objType, resyncPeriod, Indexers{})
}

// NewSharedIndexInformer creates a new instance for the listwatcher.
// TODO: create a cache/factory of these at a higher level for the list all, watch all of a given resource that can
// be shared amongst all consumers.
func NewSharedIndexInformer(lw ListerWatcher, objType runtime.Object, resyncPeriod time.Duration, indexers Indexers) SharedIndexInformer {
	sharedIndexInformer := &sharedIndexInformer{
		processor:             &sharedProcessor{},
		indexer:               NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers),
		listerWatcher:         lw,
		objectType:            objType,
		fullResyncPeriod:      resyncPeriod,
		cacheMutationDetector: NewCacheMutationDetector(fmt.Sprintf("%T", objType)),
	}
	return sharedIndexInformer
}

// InformerSynced is a function that can be used to determine if an informer has synced.  This is useful for determining if caches have synced.
type InformerSynced func() bool

// syncedPollPeriod controls how often you look at the status of your sync funcs
const syncedPollPeriod = 100 * time.Millisecond

// WaitForCacheSync waits for caches to populate.  It returns true if it was successful, false
// if the controller should shutdown
func WaitForCacheSync(stopCh <-chan struct{}, cacheSyncs ...InformerSynced) bool {
	err := wait.PollUntil(syncedPollPeriod,
		func() (bool, error) {
			for _, syncFunc := range cacheSyncs {
				if !syncFunc() {
					return false, nil
				}
			}
			return true, nil
		},
		stopCh)
	if err != nil {
		glog.V(2).Infof("stop requested")
		return false
	}

	glog.V(4).Infof("caches populated")
	return true
}

type sharedIndexInformer struct {
	indexer    Indexer
	controller Controller

	processor             *sharedProcessor
	cacheMutationDetector CacheMutationDetector

	// This block is tracked to handle late initialization of the controller
	listerWatcher    ListerWatcher
	objectType       runtime.Object
	fullResyncPeriod time.Duration

	started     bool
	startedLock sync.Mutex

	// blockDeltas gives a way to stop all event distribution so that a late event handler
	// can safely join the shared informer.
	blockDeltas sync.Mutex
	// stopCh is the channel used to stop the main Run process.  We have to track it so that
	// late joiners can have a proper stop
	stopCh <-chan struct{}
}

// dummyController hides the fact that a SharedInformer is different from a dedicated one
// where a caller can `Run`.  The run method is disconnected in this case, because higher
// level logic will decide when to start the SharedInformer and related controller.
// Because returning information back is always asynchronous, the legacy callers shouldn't
// notice any change in behavior.
type dummyController struct {
	informer *sharedIndexInformer
}

func (v *dummyController) Run(stopCh <-chan struct{}) {
}

func (v *dummyController) HasSynced() bool {
	return v.informer.HasSynced()
}

func (c *dummyController) LastSyncResourceVersion() string {
	return ""
}

type updateNotification struct {
	oldObj interface{}
	newObj interface{}
}

type addNotification struct {
	newObj interface{}
}

type deleteNotification struct {
	oldObj interface{}
}

func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, nil, s.indexer)

	cfg := &Config{
		Queue:            fifo,
		ListerWatcher:    s.listerWatcher,
		ObjectType:       s.objectType,
		FullResyncPeriod: s.fullResyncPeriod,
		RetryOnError:     false,

		Process: s.HandleDeltas,
	}

	func() {
		s.startedLock.Lock()
		defer s.startedLock.Unlock()

		s.controller = New(cfg)
		s.started = true
	}()

	s.stopCh = stopCh
	s.cacheMutationDetector.Run(stopCh)
	s.processor.run(stopCh)
	s.controller.Run(stopCh)
}

func (s *sharedIndexInformer) isStarted() bool {
	s.startedLock.Lock()
	defer s.startedLock.Unlock()
	return s.started
}

func (s *sharedIndexInformer) HasSynced() bool {
	s.startedLock.Lock()
	defer s.startedLock.Unlock()

	if s.controller == nil {
		return false
	}
	return s.controller.HasSynced()
}

func (s *sharedIndexInformer) LastSyncResourceVersion() string {
	s.startedLock.Lock()
	defer s.startedLock.Unlock()

	if s.controller == nil {
		return ""
	}
	return s.controller.LastSyncResourceVersion()
}

func (s *sharedIndexInformer) GetStore() Store {
	return s.indexer
}

func (s *sharedIndexInformer) GetIndexer() Indexer {
	return s.indexer
}

func (s *sharedIndexInformer) AddIndexers(indexers Indexers) error {
	s.startedLock.Lock()
	defer s.startedLock.Unlock()

	if s.started {
		return fmt.Errorf("informer has already started")
	}

	return s.indexer.AddIndexers(indexers)
}

func (s *sharedIndexInformer) GetController() Controller {
	return &dummyController{informer: s}
}

func (s *sharedIndexInformer) AddEventHandler(handler ResourceEventHandler) error {
	s.startedLock.Lock()
	defer s.startedLock.Unlock()

	if !s.started {
		listener := newProcessListener(handler)
		s.processor.listeners = append(s.processor.listeners, listener)
		return nil
	}

	// in order to safely join, we have to
	// 1. stop sending add/update/delete notifications
	// 2. do a list against the store
	// 3. send synthetic "Add" events to the new handler
	// 4. unblock
	s.blockDeltas.Lock()
	defer s.blockDeltas.Unlock()

	listener := newProcessListener(handler)
	s.processor.listeners = append(s.processor.listeners, listener)

	go listener.run(s.stopCh)
	go listener.pop(s.stopCh)

	items := s.indexer.List()
	for i := range items {
		listener.add(addNotification{newObj: items[i]})
	}

	return nil
}

func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
	s.blockDeltas.Lock()
	defer s.blockDeltas.Unlock()

	// from oldest to newest
	for _, d := range obj.(Deltas) {
		switch d.Type {
		case Sync, Added, Updated:
			s.cacheMutationDetector.AddObject(d.Object)
			if old, exists, err := s.indexer.Get(d.Object); err == nil && exists {
				if err := s.indexer.Update(d.Object); err != nil {
					return err
				}
				s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object})
			} else {
				if err := s.indexer.Add(d.Object); err != nil {
					return err
				}
				s.processor.distribute(addNotification{newObj: d.Object})
			}
		case Deleted:
			if err := s.indexer.Delete(d.Object); err != nil {
				return err
			}
			s.processor.distribute(deleteNotification{oldObj: d.Object})
		}
	}
	return nil
}

type sharedProcessor struct {
	listeners []*processorListener
}

func (p *sharedProcessor) distribute(obj interface{}) {
	for _, listener := range p.listeners {
		listener.add(obj)
	}
}

func (p *sharedProcessor) run(stopCh <-chan struct{}) {
	for _, listener := range p.listeners {
		go listener.run(stopCh)
		go listener.pop(stopCh)
	}
}

type processorListener struct {
	// lock/cond protects access to 'pendingNotifications'.
	lock sync.RWMutex
	cond sync.Cond

	// pendingNotifications is an unbounded slice that holds all notifications not yet distributed
	// there is one per listener, but a failing/stalled listener will have infinite pendingNotifications
	// added until we OOM.
	// TODO This is no worse that before, since reflectors were backed by unbounded DeltaFIFOs, but
	// we should try to do something better
	pendingNotifications []interface{}

	nextCh chan interface{}

	handler ResourceEventHandler
}

func newProcessListener(handler ResourceEventHandler) *processorListener {
	ret := &processorListener{
		pendingNotifications: []interface{}{},
		nextCh:               make(chan interface{}),
		handler:              handler,
	}

	ret.cond.L = &ret.lock
	return ret
}

func (p *processorListener) add(notification interface{}) {
	p.lock.Lock()
	defer p.lock.Unlock()

	p.pendingNotifications = append(p.pendingNotifications, notification)
	p.cond.Broadcast()
}

func (p *processorListener) pop(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	for {
		blockingGet := func() (interface{}, bool) {
			p.lock.Lock()
			defer p.lock.Unlock()

			for len(p.pendingNotifications) == 0 {
				// check if we're shutdown
				select {
				case <-stopCh:
					return nil, true
				default:
				}
				p.cond.Wait()
			}

			nt := p.pendingNotifications[0]
			p.pendingNotifications = p.pendingNotifications[1:]
			return nt, false
		}

		notification, stopped := blockingGet()
		if stopped {
			return
		}

		select {
		case <-stopCh:
			return
		case p.nextCh <- notification:
		}
	}
}

func (p *processorListener) run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()

	for {
		var next interface{}
		select {
		case <-stopCh:
			func() {
				p.lock.Lock()
				defer p.lock.Unlock()
				p.cond.Broadcast()
			}()
			return
		case next = <-p.nextCh:
		}

		switch notification := next.(type) {
		case updateNotification:
			p.handler.OnUpdate(notification.oldObj, notification.newObj)
		case addNotification:
			p.handler.OnAdd(notification.newObj)
		case deleteNotification:
			p.handler.OnDelete(notification.oldObj)
		default:
			utilruntime.HandleError(fmt.Errorf("unrecognized notification: %#v", next))
		}
	}
}
<|endoftext|>


<commit_before>// DirWalk written in go.  (C) 2017.  All rights reserved
// dirwalk.go
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	//	"getcommandline"
)

const LastAltered = " 5 Nov 2017"

/*
  REVISION HISTORY
  -------- -------
   5 Nov 2017 -- First version, based on code I got from a book on Go, and is in GoLang.txt.

*/

func main() {
	var dirTotal uint64
	var startDirectory string
	fmt.Println()
	fmt.Println(" dirwalk sums the directories it walks.  Written in Go.  Last altered ", LastAltered)

	if len(os.Args) < 2 {
		startDirectory, _ = os.Getwd()
	} else {
		startDirectory = os.Args[1]
	}
	start, err := os.Stat(startDirectory)
	if err != nil || !start.IsDir() {
		fmt.Println(" usage: diskwalk <directoryname>")
		os.Exit(1)
	}

	var filesList []string
	filepath.Walk(startDirectory, func(fpath string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !fi.Mode().IsRegular() {
			return nil
		}

		filesList = append(filesList, fpath)
		dirTotal += uint64(fi.Size())
		return nil
	})

	DirTotalString := strconv.FormatUint(dirTotal, 10)
	DirTotalString = AddCommas(DirTotalString)
	fmt.Print(" start dir is ", startDirectory, ".  Found ", len(filesList), " files in this tree. ")
	fmt.Println(" Total Size of walked tree is", DirTotalString)

	fmt.Println()
	//	for i := 0; i < 30; i++ {
	//		fmt.Print(filesList[i], ", ")
	//	}
	//	fmt.Println()
	//	fmt.Println()

} // main

//-------------------------------------------------------------------- InsertByteSlice
func InsertIntoByteSlice(slice, insertion []byte, index int) []byte {
	return append(slice[:index], append(insertion, slice[index:]...)...)
}

//---------------------------------------------------------------------- AddCommas
func AddCommas(instr string) string {
	var Comma []byte = []byte{','}

	BS := make([]byte, 0, 15)
	BS = append(BS, instr...)

	i := len(BS)

	for NumberOfCommas := i / 3; (NumberOfCommas > 0) && (i > 3); NumberOfCommas-- {
		i -= 3
		BS = InsertIntoByteSlice(BS, Comma, i)
	}
	return string(BS)
} // AddCommas
//---------------------------------------------------------------------------------------------------
<commit_msg>modified:   dirwalk/dirwalk.go -- more tweaks<commit_after>// DirWalk written in go.  (C) 2017.  All rights reserved
// dirwalk.go
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	//	"getcommandline"
)

const LastAltered = " 8 Nov 2017"

/*
  REVISION HISTORY
  -------- -------
   5 Nov 2017 -- First version, based on code I got from a book on Go, and is in GoLang.txt.

*/

func main() {
	var dirTotal uint64
	var startDirectory string
	fmt.Println()
	fmt.Println(" dirwalk sums the directories it walks.  Written in Go.  Last altered ", LastAltered)

	if len(os.Args) < 2 {
		startDirectory, _ = os.Getwd()
	} else {
		startDirectory = os.Args[1]
	}
	start, err := os.Stat(startDirectory)
	if err != nil || !start.IsDir() {
		fmt.Println(" usage: diskwalk <directoryname>")
		os.Exit(1)
	}

	var filesList []string
	filepath.Walk(startDirectory, func(fpath string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !fi.Mode().IsRegular() {
			return nil
		}

		filesList = append(filesList, fpath)
		dirTotal += uint64(fi.Size())
		return nil
	})

	DirTotalString := strconv.FormatUint(dirTotal, 10)
	DirTotalString = AddCommas(DirTotalString)
	fmt.Print(" start dir is ", startDirectory, "; found ", len(filesList), " files in this tree. ")
	fmt.Println(" Total Size of walked tree is", DirTotalString)

	fmt.Println()
	//	for i := 0; i < 30; i++ {
	//		fmt.Print(filesList[i], ", ")
	//	}
	//	fmt.Println()
	//	fmt.Println()

} // main

//-------------------------------------------------------------------- InsertByteSlice
func InsertIntoByteSlice(slice, insertion []byte, index int) []byte {
	return append(slice[:index], append(insertion, slice[index:]...)...)
}

//---------------------------------------------------------------------- AddCommas
func AddCommas(instr string) string {
	var Comma []byte = []byte{','}

	BS := make([]byte, 0, 15)
	BS = append(BS, instr...)

	i := len(BS)

	for NumberOfCommas := i / 3; (NumberOfCommas > 0) && (i > 3); NumberOfCommas-- {
		i -= 3
		BS = InsertIntoByteSlice(BS, Comma, i)
	}
	return string(BS)
} // AddCommas
//---------------------------------------------------------------------------------------------------
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// 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 runners

import (
	"os"

	"github.com/GoogleContainerTools/kpt-functions-sdk/go/pkg/framework/io"
	"github.com/GoogleContainerTools/kpt-functions-sdk/go/pkg/framework/types"
	"github.com/spf13/cobra"
)

var (
	input   string
	output  string
	useJSON bool
)

func addInputFlag(cmd *cobra.Command) {
	cmd.Flags().StringVarP(&input, "input", "i", io.Stdin,
		`path to the input JSON file`)
}

func addOutputFlag(cmd *cobra.Command) {
	cmd.Flags().StringVarP(&output, "output", "o", io.Stdout,
		`path to the output JSON file`)
}

func addFormatFlag(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&useJSON, "json", false,
		`input and output is JSON instead of YAML`)
}

func getFormat() io.Format {
	if useJSON {
		return io.JSON
	}
	return io.YAML
}

// RunFunc runs a ConfigFunc.
func RunFunc(f types.ConfigFunc, usage string) {
	cmd := &cobra.Command{Long: usage}
	//TODO(b/138231979): Make text output match more closely with go vs typescript.

	addInputFlag(cmd)
	addOutputFlag(cmd)
	addFormatFlag(cmd)

	cmd.RunE = func(cmd *cobra.Command, args []string) error {
		// Since printing the usage message since we know all required fields are present.
		cmd.SilenceUsage = true

		configs, err := io.ReadConfigs(input, getFormat())
		if err != nil {
			return err
		}

		err = f(&configs)
		if err != nil {
			return err
		}

		return io.WriteConfigs(output, configs, getFormat())
	}

	if err := cmd.Execute(); err != nil {
		os.Exit(1)
	}
	os.Exit(0)
}
<commit_msg>Fix help description for input/output flags<commit_after>// Copyright 2019 Google LLC
//
// 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 runners

import (
	"os"

	"github.com/GoogleContainerTools/kpt-functions-sdk/go/pkg/framework/io"
	"github.com/GoogleContainerTools/kpt-functions-sdk/go/pkg/framework/types"
	"github.com/spf13/cobra"
)

var (
	input   string
	output  string
	useJSON bool
)

func addInputFlag(cmd *cobra.Command) {
	cmd.Flags().StringVarP(&input, "input", "i", io.Stdin,
		`path to the input file`)
}

func addOutputFlag(cmd *cobra.Command) {
	cmd.Flags().StringVarP(&output, "output", "o", io.Stdout,
		`path to the output file`)
}

func addFormatFlag(cmd *cobra.Command) {
	cmd.Flags().BoolVar(&useJSON, "json", false,
		`input and output is JSON instead of YAML`)
}

func getFormat() io.Format {
	if useJSON {
		return io.JSON
	}
	return io.YAML
}

// RunFunc runs a ConfigFunc.
func RunFunc(f types.ConfigFunc, usage string) {
	cmd := &cobra.Command{Long: usage}
	//TODO(b/138231979): Make text output match more closely with go vs typescript.

	addInputFlag(cmd)
	addOutputFlag(cmd)
	addFormatFlag(cmd)

	cmd.RunE = func(cmd *cobra.Command, args []string) error {
		// Since printing the usage message since we know all required fields are present.
		cmd.SilenceUsage = true

		configs, err := io.ReadConfigs(input, getFormat())
		if err != nil {
			return err
		}

		err = f(&configs)
		if err != nil {
			return err
		}

		return io.WriteConfigs(output, configs, getFormat())
	}

	if err := cmd.Execute(); err != nil {
		os.Exit(1)
	}
	os.Exit(0)
}
<|endoftext|>


<commit_before>package mongodb

import (
	"errors"
	"fmt"
	"os"
	"sync"

	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

type MongoDB struct {
	Session *mgo.Session
	URL     string
}

var (
	Mongo *MongoDB
	mu    sync.Mutex
)

func NewMongoDB(url string) *MongoDB {
	m := &MongoDB{
		URL: url,
	}

	mgo.SetStats(true)
	m.CreateSession(m.URL)
	return m
}

func (m *MongoDB) CreateSession(url string) {
	var err error
	m.Session, err = mgo.Dial(url)
	if err != nil {
		fmt.Printf("mongodb connection error: %s\n", err)
		os.Exit(1)
		return
	}

	m.Session.SetSafe(&mgo.Safe{})
	m.Session.SetMode(mgo.Monotonic, true)
}

func (m *MongoDB) Close() {
	m.Session.Close()
}

func (m *MongoDB) Refresh() {
	m.Session.Refresh()
}

func (m *MongoDB) Copy() *mgo.Session {
	return m.Session.Copy()
}

func (m *MongoDB) Clone() *mgo.Session {
	return m.Session.Clone()
}

func (m *MongoDB) GetSession() *mgo.Session {
	if m.Session == nil {
		m.CreateSession(m.URL)
	}

	return m.Copy()
}

func (m *MongoDB) Run(collection string, s func(*mgo.Collection) error) error {
	session := m.GetSession()
	defer session.Close()
	c := session.DB("").C(collection)
	return s(c)
}

// RunOnDatabase runs command on given database, instead of current database
func (m *MongoDB) RunOnDatabase(database, collection string, s func(*mgo.Collection) error) error {
	session := m.GetSession()
	defer session.Close()
	c := session.DB(database).C(collection)
	return s(c)
}

func (m *MongoDB) One(collection, id string, result interface{}) error {
	session := m.GetSession()
	defer session.Close()
	return session.DB("").C(collection).FindId(bson.ObjectIdHex(id)).One(result)
}

func (m *MongoDB) Iter(cl string, q func(*mgo.Collection) *mgo.Query, i func(*mgo.Iter) error) error {
	session := m.GetSession()
	defer session.Close()
	c := session.DB("").C(cl)

	var iter = q(c).Iter()
	var err = i(iter)
	if err != nil {
		return err
	}

	err = iter.Close()
	if err != nil {
		return err
	}

	if iter.Timeout() {
		return errors.New("iter timed out")
	}

	return nil
}
<commit_msg>Mongo: remove unused variables<commit_after>package mongodb

import (
	"errors"
	"fmt"
	"os"

	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

type MongoDB struct {
	Session *mgo.Session
	URL     string
}

func NewMongoDB(url string) *MongoDB {
	m := &MongoDB{
		URL: url,
	}

	mgo.SetStats(true)
	m.CreateSession(m.URL)
	return m
}

func (m *MongoDB) CreateSession(url string) {
	var err error
	m.Session, err = mgo.Dial(url)
	if err != nil {
		fmt.Printf("mongodb connection error: %s\n", err)
		os.Exit(1)
		return
	}

	m.Session.SetSafe(&mgo.Safe{})
	m.Session.SetMode(mgo.Monotonic, true)
}

func (m *MongoDB) Close() {
	m.Session.Close()
}

func (m *MongoDB) Refresh() {
	m.Session.Refresh()
}

func (m *MongoDB) Copy() *mgo.Session {
	return m.Session.Copy()
}

func (m *MongoDB) Clone() *mgo.Session {
	return m.Session.Clone()
}

func (m *MongoDB) GetSession() *mgo.Session {
	if m.Session == nil {
		m.CreateSession(m.URL)
	}

	return m.Copy()
}

func (m *MongoDB) Run(collection string, s func(*mgo.Collection) error) error {
	session := m.GetSession()
	defer session.Close()
	c := session.DB("").C(collection)
	return s(c)
}

// RunOnDatabase runs command on given database, instead of current database
func (m *MongoDB) RunOnDatabase(database, collection string, s func(*mgo.Collection) error) error {
	session := m.GetSession()
	defer session.Close()
	c := session.DB(database).C(collection)
	return s(c)
}

func (m *MongoDB) One(collection, id string, result interface{}) error {
	session := m.GetSession()
	defer session.Close()
	return session.DB("").C(collection).FindId(bson.ObjectIdHex(id)).One(result)
}

func (m *MongoDB) Iter(cl string, q func(*mgo.Collection) *mgo.Query, i func(*mgo.Iter) error) error {
	session := m.GetSession()
	defer session.Close()
	c := session.DB("").C(cl)

	var iter = q(c).Iter()
	var err = i(iter)
	if err != nil {
		return err
	}

	err = iter.Close()
	if err != nil {
		return err
	}

	if iter.Timeout() {
		return errors.New("iter timed out")
	}

	return nil
}
<|endoftext|>


<commit_before>package opengl

// #cgo LDFLAGS: -framework OpenGL
//
// #include <stdlib.h>
// #include <OpenGL/gl.h>
import "C"
import (
	"fmt"
	"github.com/hajimehoshi/go.ebiten/graphics"
	"github.com/hajimehoshi/go.ebiten/graphics/matrix"
	"image"
	"image/color"
	"math"
	"unsafe"
)

type GraphicsContext struct {
	screenWidth            int
	screenHeight           int
	screenScale            int
	textures               map[graphics.TextureID]*Texture
	currentOffscreenWidth  int
	currentOffscreenHeight int
	projectionMatrix       [16]float32
	currentShaderProgram   C.GLuint
	mainFramebuffer        C.GLuint
	framebuffers           map[C.GLuint]C.GLuint
}

// This method should be called on the UI thread.
func newGraphicsContext(screenWidth, screenHeight, screenScale int) *GraphicsContext {
	context := &GraphicsContext{
		screenWidth:     screenWidth,
		screenHeight:    screenHeight,
		screenScale:     screenScale,
		textures:        map[graphics.TextureID]*Texture{},
		mainFramebuffer: 0,
		framebuffers:    map[C.GLuint]C.GLuint{},
	}
	// main framebuffer should be created sooner than any other framebuffers!
	mainFramebuffer := C.GLint(0)
	C.glGetIntegerv(C.GL_FRAMEBUFFER_BINDING, &mainFramebuffer)
	context.mainFramebuffer = C.GLuint(mainFramebuffer)

	initializeShaders()

	return context
}

func (context *GraphicsContext) Clear() {
	C.glClearColor(0, 0, 0, 1)
	C.glClear(C.GL_COLOR_BUFFER_BIT)
}

func (context *GraphicsContext) Fill(clr color.Color) {
	r, g, b, a := clr.RGBA()
	max := float64(math.MaxUint16)
	C.glClearColor(
		C.GLclampf(float64(r)/max),
		C.GLclampf(float64(g)/max),
		C.GLclampf(float64(b)/max),
		C.GLclampf(float64(a)/max))
	C.glClear(C.GL_COLOR_BUFFER_BIT)
}

func (context *GraphicsContext) DrawRect(rect graphics.Rect, clr color.Color) {
	width := float32(context.currentOffscreenWidth)
	height := float32(context.currentOffscreenHeight)
	textureWidth := float32(clp2(uint64(width)))
	textureHeight := float32(clp2(uint64(height)))

	// Normalize the coord between -1.0 and 1.0.
	x1 := float32(rect.X)/textureWidth*2.0 - 1.0
	x2 := float32(rect.X+rect.Width)/textureHeight*2.0 - 1.0
	y1 := float32(rect.Y)/textureHeight*2.0 - 1.0
	y2 := float32(rect.Y+rect.Height)/textureHeight*2.0 - 1.0
	vertex := [...]float32{
		x1, y1,
		x2, y1,
		x1, y2,
		x2, y2,
	}

	origR, origG, origB, origA := clr.RGBA()
	max := float32(math.MaxUint16)
	r := float32(origR) / max
	g := float32(origG) / max
	b := float32(origB) / max
	a := float32(origA) / max
	color := [...]float32{
		r, g, b, a,
		r, g, b, a,
		r, g, b, a,
		r, g, b, a,
	}

	C.glUseProgram(0)
	context.currentShaderProgram = 0
	C.glDisable(C.GL_TEXTURE_2D)
	C.glEnableClientState(C.GL_VERTEX_ARRAY)
	C.glEnableClientState(C.GL_COLOR_ARRAY)
	C.glVertexPointer(2, C.GL_FLOAT, C.GL_FALSE, unsafe.Pointer(&vertex[0]))
	C.glColorPointer(4, C.GL_FLOAT, C.GL_FALSE, unsafe.Pointer(&color[0]))
	C.glDrawArrays(C.GL_TRIANGLE_STRIP, 0, 4)
	C.glDisableClientState(C.GL_COLOR_ARRAY)
	C.glDisableClientState(C.GL_VERTEX_ARRAY)
	C.glEnable(C.GL_TEXTURE_2D)

	if glError := C.glGetError(); glError != C.GL_NO_ERROR {
		panic("OpenGL error")
	}
}

func (context *GraphicsContext) DrawTexture(
	textureID graphics.TextureID,
	geometryMatrix matrix.Geometry, colorMatrix matrix.Color) {
	texture := context.textures[textureID]

	source := graphics.Rect{0, 0, texture.width, texture.height}
	locations := []graphics.TexturePart{{0, 0, source}}
	context.DrawTextureParts(textureID, locations,
		geometryMatrix, colorMatrix)
}

func (context *GraphicsContext) DrawTextureParts(
	textureID graphics.TextureID, locations []graphics.TexturePart,
	geometryMatrix matrix.Geometry, colorMatrix matrix.Color) {

	texture := context.textures[textureID]

	context.setShaderProgram(geometryMatrix, colorMatrix)
	C.glBindTexture(C.GL_TEXTURE_2D, texture.id)

	vertexAttrLocation := getAttributeLocation(context.currentShaderProgram, "vertex")
	textureAttrLocation := getAttributeLocation(context.currentShaderProgram, "texture")

	C.glEnableClientState(C.GL_VERTEX_ARRAY)
	C.glEnableClientState(C.GL_TEXTURE_COORD_ARRAY)
	C.glEnableVertexAttribArray(C.GLuint(vertexAttrLocation))
	C.glEnableVertexAttribArray(C.GLuint(textureAttrLocation))
	// TODO: Refactoring
	for _, location := range locations {
		x1 := float32(location.LocationX)
		x2 := float32(location.LocationX + location.Source.Width)
		y1 := float32(location.LocationY)
		y2 := float32(location.LocationY + location.Source.Height)
		vertex := [...]float32{
			x1, y1,
			x2, y1,
			x1, y2,
			x2, y2,
		}

		src := location.Source
		tu1 := float32(src.X) / float32(texture.textureWidth)
		tu2 := float32(src.X+src.Width) / float32(texture.textureWidth)
		tv1 := float32(src.Y) / float32(texture.textureHeight)
		tv2 := float32(src.Y+src.Height) / float32(texture.textureHeight)
		texCoord := [...]float32{
			tu1, tv1,
			tu2, tv1,
			tu1, tv2,
			tu2, tv2,
		}
		C.glVertexAttribPointer(C.GLuint(vertexAttrLocation), 2, C.GL_FLOAT, C.GL_FALSE,
			0, unsafe.Pointer(&vertex[0]))
		C.glVertexAttribPointer(C.GLuint(textureAttrLocation), 2, C.GL_FLOAT, C.GL_FALSE,
			0, unsafe.Pointer(&texCoord[0]))
		C.glDrawArrays(C.GL_TRIANGLE_STRIP, 0, 4)
	}
	C.glDisableVertexAttribArray(C.GLuint(textureAttrLocation))
	C.glDisableVertexAttribArray(C.GLuint(vertexAttrLocation))
	C.glDisableClientState(C.GL_TEXTURE_COORD_ARRAY)
	C.glDisableClientState(C.GL_VERTEX_ARRAY)
}

func abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

func (context *GraphicsContext) SetOffscreen(textureID graphics.TextureID) {
	texture := context.textures[textureID]
	context.currentOffscreenWidth = texture.width
	context.currentOffscreenHeight = texture.height

	framebuffer := context.getFramebuffer(texture.id)
	if framebuffer == context.mainFramebuffer {
		panic("invalid framebuffer")
	}
	context.setOffscreenFramebuffer(framebuffer,
		texture.textureWidth, texture.textureHeight)
}

func (context *GraphicsContext) setOffscreenFramebuffer(framebuffer C.GLuint,
	textureWidth, textureHeight int) {
	if framebuffer == context.mainFramebuffer {
		textureWidth = int(clp2(uint64(context.screenWidth * context.screenScale)))
		textureHeight = int(clp2(uint64(context.screenHeight * context.screenScale)))
	}

	C.glFlush()

	C.glBindFramebuffer(C.GL_FRAMEBUFFER, framebuffer)
	if err := C.glCheckFramebufferStatus(C.GL_FRAMEBUFFER); err != C.GL_FRAMEBUFFER_COMPLETE {
		panic(fmt.Sprintf("glBindFramebuffer failed: %d", err))
	}
	C.glEnable(C.GL_BLEND)
	C.glBlendFunc(C.GL_SRC_ALPHA, C.GL_ONE_MINUS_SRC_ALPHA)

	C.glViewport(0, 0, C.GLsizei(abs(textureWidth)), C.GLsizei(abs(textureHeight)))

	var e11, e22, e41, e42 float32
	if framebuffer != context.mainFramebuffer {
		e11 = float32(2) / float32(textureWidth)
		e22 = float32(2) / float32(textureWidth)
		e41 = -1
		e42 = -1
	} else {
		height := float32(context.screenHeight) * float32(context.screenScale)
		e11 = float32(2) / float32(textureWidth)
		e22 = -1 * float32(2) / float32(textureHeight)
		e41 = -1
		e42 = -1 + height/float32(textureHeight)*2
	}

	context.projectionMatrix = [...]float32{
		e11, 0, 0, 0,
		0, e22, 0, 0,
		0, 0, 1, 0,
		e41, e42, 0, 1,
	}
}

func (context *GraphicsContext) resetOffscreen() {
	context.setOffscreenFramebuffer(context.mainFramebuffer, 0, 0)
	context.currentOffscreenWidth = context.screenWidth * context.screenScale
	context.currentOffscreenHeight = context.screenHeight * context.screenScale
}

// This method should be called on the UI thread.
func (context *GraphicsContext) flush() {
	C.glFlush()
}

// This method should be called on the UI thread.
func (context *GraphicsContext) setShaderProgram(
	geometryMatrix matrix.Geometry, colorMatrix matrix.Color) {
	program := C.GLuint(0)
	if colorMatrix.IsIdentity() {
		program = regularShaderProgram
	} else {
		program = colorMatrixShaderProgram
	}
	// TODO: cache and skip?
	C.glUseProgram(program)
	context.currentShaderProgram = program

	C.glUniformMatrix4fv(getUniformLocation(program, "projection_matrix"),
		1, C.GL_FALSE,
		(*C.GLfloat)(&context.projectionMatrix[0]))

	a := float32(geometryMatrix.Elements[0][0])
	b := float32(geometryMatrix.Elements[0][1])
	c := float32(geometryMatrix.Elements[1][0])
	d := float32(geometryMatrix.Elements[1][1])
	tx := float32(geometryMatrix.Elements[0][2])
	ty := float32(geometryMatrix.Elements[1][2])
	glModelviewMatrix := [...]float32{
		a, c, 0, 0,
		b, d, 0, 0,
		0, 0, 1, 0,
		tx, ty, 0, 1,
	}
	C.glUniformMatrix4fv(getUniformLocation(program, "modelview_matrix"),
		1, C.GL_FALSE,
		(*C.GLfloat)(&glModelviewMatrix[0]))

	C.glUniform1i(getUniformLocation(program, "texture"), 0)

	if program != colorMatrixShaderProgram {
		return
	}

	e := [4][5]float32{}
	for i := 0; i < 4; i++ {
		for j := 0; j < 5; j++ {
			e[i][j] = float32(colorMatrix.Elements[i][j])
		}
	}

	glColorMatrix := [...]float32{
		e[0][0], e[1][0], e[2][0], e[3][0],
		e[0][1], e[1][1], e[2][1], e[3][1],
		e[0][2], e[1][2], e[2][2], e[3][2],
		e[0][3], e[1][3], e[2][3], e[3][3],
	}
	C.glUniformMatrix4fv(getUniformLocation(program, "color_matrix"),
		1, C.GL_FALSE, (*C.GLfloat)(&glColorMatrix[0]))

	glColorMatrixTranslation := [...]float32{
		e[0][4], e[1][4], e[2][4], e[3][4],
	}
	C.glUniform4fv(getUniformLocation(program, "color_matrix_translation"),
		1, (*C.GLfloat)(&glColorMatrixTranslation[0]))
}

func (context *GraphicsContext) getFramebuffer(textureID C.GLuint) C.GLuint {
	framebuffer, ok := context.framebuffers[textureID]
	if ok {
		return framebuffer
	}

	newFramebuffer := C.GLuint(0)
	C.glGenFramebuffers(1, &newFramebuffer)

	origFramebuffer := C.GLint(0)
	C.glGetIntegerv(C.GL_FRAMEBUFFER_BINDING, &origFramebuffer)
	C.glBindFramebuffer(C.GL_FRAMEBUFFER, newFramebuffer)
	C.glFramebufferTexture2D(C.GL_FRAMEBUFFER, C.GL_COLOR_ATTACHMENT0,
		C.GL_TEXTURE_2D, textureID, 0)
	C.glBindFramebuffer(C.GL_FRAMEBUFFER, C.GLuint(origFramebuffer))
	if C.glCheckFramebufferStatus(C.GL_FRAMEBUFFER) != C.GL_FRAMEBUFFER_COMPLETE {
		panic("creating framebuffer failed")
	}

	context.framebuffers[textureID] = newFramebuffer
	return newFramebuffer
}

func (context *GraphicsContext) deleteFramebuffer(textureID C.GLuint) {
	framebuffer, ok := context.framebuffers[textureID]
	if !ok {
		// TODO: panic?
		return
	}
	C.glDeleteFramebuffers(1, &framebuffer)
	delete(context.framebuffers, textureID)
}

func (context *GraphicsContext) NewTexture(width, height int) graphics.Texture {
	texture := newTexture(width, height)
	id := graphics.TextureID(texture.id)
	context.textures[id] = texture

	context.SetOffscreen(id)
	context.Clear()
	context.resetOffscreen()

	return graphics.Texture{
		ID:     id,
		Width:  texture.width,
		Height: texture.height,
	}
}

func (context *GraphicsContext) NewTextureFromImage(img image.Image) (graphics.Texture, error) {
	texture, err := newTextureFromImage(img)
	if err != nil {
		return graphics.Texture{}, err
	}
	id := graphics.TextureID(texture.id)
	context.textures[id] = texture
	return graphics.Texture{
		ID:     id,
		Width:  texture.width,
		Height: texture.height,
	}, nil
}
<commit_msg>Change the color of clearing<commit_after>package opengl

// #cgo LDFLAGS: -framework OpenGL
//
// #include <stdlib.h>
// #include <OpenGL/gl.h>
import "C"
import (
	"fmt"
	"github.com/hajimehoshi/go.ebiten/graphics"
	"github.com/hajimehoshi/go.ebiten/graphics/matrix"
	"image"
	"image/color"
	"math"
	"unsafe"
)

type GraphicsContext struct {
	screenWidth            int
	screenHeight           int
	screenScale            int
	textures               map[graphics.TextureID]*Texture
	currentOffscreenWidth  int
	currentOffscreenHeight int
	projectionMatrix       [16]float32
	currentShaderProgram   C.GLuint
	mainFramebuffer        C.GLuint
	framebuffers           map[C.GLuint]C.GLuint
}

// This method should be called on the UI thread.
func newGraphicsContext(screenWidth, screenHeight, screenScale int) *GraphicsContext {
	context := &GraphicsContext{
		screenWidth:     screenWidth,
		screenHeight:    screenHeight,
		screenScale:     screenScale,
		textures:        map[graphics.TextureID]*Texture{},
		mainFramebuffer: 0,
		framebuffers:    map[C.GLuint]C.GLuint{},
	}
	// main framebuffer should be created sooner than any other framebuffers!
	mainFramebuffer := C.GLint(0)
	C.glGetIntegerv(C.GL_FRAMEBUFFER_BINDING, &mainFramebuffer)
	context.mainFramebuffer = C.GLuint(mainFramebuffer)

	initializeShaders()

	return context
}

func (context *GraphicsContext) Clear() {
	C.glClearColor(0, 0, 0, 0)
	C.glClear(C.GL_COLOR_BUFFER_BIT)
}

func (context *GraphicsContext) Fill(clr color.Color) {
	r, g, b, a := clr.RGBA()
	max := float64(math.MaxUint16)
	C.glClearColor(
		C.GLclampf(float64(r)/max),
		C.GLclampf(float64(g)/max),
		C.GLclampf(float64(b)/max),
		C.GLclampf(float64(a)/max))
	C.glClear(C.GL_COLOR_BUFFER_BIT)
}

func (context *GraphicsContext) DrawRect(rect graphics.Rect, clr color.Color) {
	width := float32(context.currentOffscreenWidth)
	height := float32(context.currentOffscreenHeight)
	textureWidth := float32(clp2(uint64(width)))
	textureHeight := float32(clp2(uint64(height)))

	// Normalize the coord between -1.0 and 1.0.
	x1 := float32(rect.X)/textureWidth*2.0 - 1.0
	x2 := float32(rect.X+rect.Width)/textureHeight*2.0 - 1.0
	y1 := float32(rect.Y)/textureHeight*2.0 - 1.0
	y2 := float32(rect.Y+rect.Height)/textureHeight*2.0 - 1.0
	vertex := [...]float32{
		x1, y1,
		x2, y1,
		x1, y2,
		x2, y2,
	}

	origR, origG, origB, origA := clr.RGBA()
	max := float32(math.MaxUint16)
	r := float32(origR) / max
	g := float32(origG) / max
	b := float32(origB) / max
	a := float32(origA) / max
	color := [...]float32{
		r, g, b, a,
		r, g, b, a,
		r, g, b, a,
		r, g, b, a,
	}

	C.glUseProgram(0)
	context.currentShaderProgram = 0
	C.glDisable(C.GL_TEXTURE_2D)
	C.glEnableClientState(C.GL_VERTEX_ARRAY)
	C.glEnableClientState(C.GL_COLOR_ARRAY)
	C.glVertexPointer(2, C.GL_FLOAT, C.GL_FALSE, unsafe.Pointer(&vertex[0]))
	C.glColorPointer(4, C.GL_FLOAT, C.GL_FALSE, unsafe.Pointer(&color[0]))
	C.glDrawArrays(C.GL_TRIANGLE_STRIP, 0, 4)
	C.glDisableClientState(C.GL_COLOR_ARRAY)
	C.glDisableClientState(C.GL_VERTEX_ARRAY)
	C.glEnable(C.GL_TEXTURE_2D)

	if glError := C.glGetError(); glError != C.GL_NO_ERROR {
		panic("OpenGL error")
	}
}

func (context *GraphicsContext) DrawTexture(
	textureID graphics.TextureID,
	geometryMatrix matrix.Geometry, colorMatrix matrix.Color) {
	texture := context.textures[textureID]

	source := graphics.Rect{0, 0, texture.width, texture.height}
	locations := []graphics.TexturePart{{0, 0, source}}
	context.DrawTextureParts(textureID, locations,
		geometryMatrix, colorMatrix)
}

func (context *GraphicsContext) DrawTextureParts(
	textureID graphics.TextureID, locations []graphics.TexturePart,
	geometryMatrix matrix.Geometry, colorMatrix matrix.Color) {

	texture := context.textures[textureID]

	context.setShaderProgram(geometryMatrix, colorMatrix)
	C.glBindTexture(C.GL_TEXTURE_2D, texture.id)

	vertexAttrLocation := getAttributeLocation(context.currentShaderProgram, "vertex")
	textureAttrLocation := getAttributeLocation(context.currentShaderProgram, "texture")

	C.glEnableClientState(C.GL_VERTEX_ARRAY)
	C.glEnableClientState(C.GL_TEXTURE_COORD_ARRAY)
	C.glEnableVertexAttribArray(C.GLuint(vertexAttrLocation))
	C.glEnableVertexAttribArray(C.GLuint(textureAttrLocation))
	// TODO: Refactoring
	for _, location := range locations {
		x1 := float32(location.LocationX)
		x2 := float32(location.LocationX + location.Source.Width)
		y1 := float32(location.LocationY)
		y2 := float32(location.LocationY + location.Source.Height)
		vertex := [...]float32{
			x1, y1,
			x2, y1,
			x1, y2,
			x2, y2,
		}

		src := location.Source
		tu1 := float32(src.X) / float32(texture.textureWidth)
		tu2 := float32(src.X+src.Width) / float32(texture.textureWidth)
		tv1 := float32(src.Y) / float32(texture.textureHeight)
		tv2 := float32(src.Y+src.Height) / float32(texture.textureHeight)
		texCoord := [...]float32{
			tu1, tv1,
			tu2, tv1,
			tu1, tv2,
			tu2, tv2,
		}
		C.glVertexAttribPointer(C.GLuint(vertexAttrLocation), 2, C.GL_FLOAT, C.GL_FALSE,
			0, unsafe.Pointer(&vertex[0]))
		C.glVertexAttribPointer(C.GLuint(textureAttrLocation), 2, C.GL_FLOAT, C.GL_FALSE,
			0, unsafe.Pointer(&texCoord[0]))
		C.glDrawArrays(C.GL_TRIANGLE_STRIP, 0, 4)
	}
	C.glDisableVertexAttribArray(C.GLuint(textureAttrLocation))
	C.glDisableVertexAttribArray(C.GLuint(vertexAttrLocation))
	C.glDisableClientState(C.GL_TEXTURE_COORD_ARRAY)
	C.glDisableClientState(C.GL_VERTEX_ARRAY)
}

func abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

func (context *GraphicsContext) SetOffscreen(textureID graphics.TextureID) {
	texture := context.textures[textureID]
	context.currentOffscreenWidth = texture.width
	context.currentOffscreenHeight = texture.height

	framebuffer := context.getFramebuffer(texture.id)
	if framebuffer == context.mainFramebuffer {
		panic("invalid framebuffer")
	}
	context.setOffscreenFramebuffer(framebuffer,
		texture.textureWidth, texture.textureHeight)
}

func (context *GraphicsContext) setOffscreenFramebuffer(framebuffer C.GLuint,
	textureWidth, textureHeight int) {
	if framebuffer == context.mainFramebuffer {
		textureWidth = int(clp2(uint64(context.screenWidth * context.screenScale)))
		textureHeight = int(clp2(uint64(context.screenHeight * context.screenScale)))
	}

	C.glFlush()

	C.glBindFramebuffer(C.GL_FRAMEBUFFER, framebuffer)
	if err := C.glCheckFramebufferStatus(C.GL_FRAMEBUFFER); err != C.GL_FRAMEBUFFER_COMPLETE {
		panic(fmt.Sprintf("glBindFramebuffer failed: %d", err))
	}
	C.glEnable(C.GL_BLEND)
	C.glBlendFunc(C.GL_SRC_ALPHA, C.GL_ONE_MINUS_SRC_ALPHA)

	C.glViewport(0, 0, C.GLsizei(abs(textureWidth)), C.GLsizei(abs(textureHeight)))

	var e11, e22, e41, e42 float32
	if framebuffer != context.mainFramebuffer {
		e11 = float32(2) / float32(textureWidth)
		e22 = float32(2) / float32(textureWidth)
		e41 = -1
		e42 = -1
	} else {
		height := float32(context.screenHeight) * float32(context.screenScale)
		e11 = float32(2) / float32(textureWidth)
		e22 = -1 * float32(2) / float32(textureHeight)
		e41 = -1
		e42 = -1 + height/float32(textureHeight)*2
	}

	context.projectionMatrix = [...]float32{
		e11, 0, 0, 0,
		0, e22, 0, 0,
		0, 0, 1, 0,
		e41, e42, 0, 1,
	}
}

func (context *GraphicsContext) resetOffscreen() {
	context.setOffscreenFramebuffer(context.mainFramebuffer, 0, 0)
	context.currentOffscreenWidth = context.screenWidth * context.screenScale
	context.currentOffscreenHeight = context.screenHeight * context.screenScale
}

// This method should be called on the UI thread.
func (context *GraphicsContext) flush() {
	C.glFlush()
}

// This method should be called on the UI thread.
func (context *GraphicsContext) setShaderProgram(
	geometryMatrix matrix.Geometry, colorMatrix matrix.Color) {
	program := C.GLuint(0)
	if colorMatrix.IsIdentity() {
		program = regularShaderProgram
	} else {
		program = colorMatrixShaderProgram
	}
	// TODO: cache and skip?
	C.glUseProgram(program)
	context.currentShaderProgram = program

	C.glUniformMatrix4fv(getUniformLocation(program, "projection_matrix"),
		1, C.GL_FALSE,
		(*C.GLfloat)(&context.projectionMatrix[0]))

	a := float32(geometryMatrix.Elements[0][0])
	b := float32(geometryMatrix.Elements[0][1])
	c := float32(geometryMatrix.Elements[1][0])
	d := float32(geometryMatrix.Elements[1][1])
	tx := float32(geometryMatrix.Elements[0][2])
	ty := float32(geometryMatrix.Elements[1][2])
	glModelviewMatrix := [...]float32{
		a, c, 0, 0,
		b, d, 0, 0,
		0, 0, 1, 0,
		tx, ty, 0, 1,
	}
	C.glUniformMatrix4fv(getUniformLocation(program, "modelview_matrix"),
		1, C.GL_FALSE,
		(*C.GLfloat)(&glModelviewMatrix[0]))

	C.glUniform1i(getUniformLocation(program, "texture"), 0)

	if program != colorMatrixShaderProgram {
		return
	}

	e := [4][5]float32{}
	for i := 0; i < 4; i++ {
		for j := 0; j < 5; j++ {
			e[i][j] = float32(colorMatrix.Elements[i][j])
		}
	}

	glColorMatrix := [...]float32{
		e[0][0], e[1][0], e[2][0], e[3][0],
		e[0][1], e[1][1], e[2][1], e[3][1],
		e[0][2], e[1][2], e[2][2], e[3][2],
		e[0][3], e[1][3], e[2][3], e[3][3],
	}
	C.glUniformMatrix4fv(getUniformLocation(program, "color_matrix"),
		1, C.GL_FALSE, (*C.GLfloat)(&glColorMatrix[0]))

	glColorMatrixTranslation := [...]float32{
		e[0][4], e[1][4], e[2][4], e[3][4],
	}
	C.glUniform4fv(getUniformLocation(program, "color_matrix_translation"),
		1, (*C.GLfloat)(&glColorMatrixTranslation[0]))
}

func (context *GraphicsContext) getFramebuffer(textureID C.GLuint) C.GLuint {
	framebuffer, ok := context.framebuffers[textureID]
	if ok {
		return framebuffer
	}

	newFramebuffer := C.GLuint(0)
	C.glGenFramebuffers(1, &newFramebuffer)

	origFramebuffer := C.GLint(0)
	C.glGetIntegerv(C.GL_FRAMEBUFFER_BINDING, &origFramebuffer)
	C.glBindFramebuffer(C.GL_FRAMEBUFFER, newFramebuffer)
	C.glFramebufferTexture2D(C.GL_FRAMEBUFFER, C.GL_COLOR_ATTACHMENT0,
		C.GL_TEXTURE_2D, textureID, 0)
	C.glBindFramebuffer(C.GL_FRAMEBUFFER, C.GLuint(origFramebuffer))
	if C.glCheckFramebufferStatus(C.GL_FRAMEBUFFER) != C.GL_FRAMEBUFFER_COMPLETE {
		panic("creating framebuffer failed")
	}

	context.framebuffers[textureID] = newFramebuffer
	return newFramebuffer
}

func (context *GraphicsContext) deleteFramebuffer(textureID C.GLuint) {
	framebuffer, ok := context.framebuffers[textureID]
	if !ok {
		// TODO: panic?
		return
	}
	C.glDeleteFramebuffers(1, &framebuffer)
	delete(context.framebuffers, textureID)
}

func (context *GraphicsContext) NewTexture(width, height int) graphics.Texture {
	texture := newTexture(width, height)
	id := graphics.TextureID(texture.id)
	context.textures[id] = texture

	context.SetOffscreen(id)
	context.Clear()
	context.resetOffscreen()

	return graphics.Texture{
		ID:     id,
		Width:  texture.width,
		Height: texture.height,
	}
}

func (context *GraphicsContext) NewTextureFromImage(img image.Image) (graphics.Texture, error) {
	texture, err := newTextureFromImage(img)
	if err != nil {
		return graphics.Texture{}, err
	}
	id := graphics.TextureID(texture.id)
	context.textures[id] = texture
	return graphics.Texture{
		ID:     id,
		Width:  texture.width,
		Height: texture.height,
	}, nil
}
<|endoftext|>


<commit_before>package handler

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

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"github.com/Lunchr/luncher-api/db"
	"github.com/Lunchr/luncher-api/db/model"
	"github.com/Lunchr/luncher-api/router"
	"github.com/Lunchr/luncher-api/session"
	"github.com/deiwin/facebook"
	"github.com/julienschmidt/httprouter"
)

// OfferGroupPost handles GET requests to /restaurant/posts/:date. It returns all current day's offers for the region.
func OfferGroupPost(c db.OfferGroupPosts, sessionManager session.Manager, users db.Users, restaurants db.Restaurants) router.HandlerWithParams {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User, restaurant *model.Restaurant,
		date model.DateWithoutTime) *router.HandlerError {
		post, err := c.GetByDate(date, restaurant.ID)
		if err == mgo.ErrNotFound {
			return router.NewHandlerError(err, "Offer group post not found", http.StatusNotFound)
		} else if err != nil {
			return router.NewHandlerError(err, "An error occured while trying to fetch a offer group post", http.StatusInternalServerError)
		}
		return writeJSON(w, post)
	}
	return forDate(sessionManager, users, restaurants, handler)
}

// PostOfferGroupPost handles POST requests to /restaurant/posts. It stores the info in the DB and updates the post in FB.
func PostOfferGroupPost(c db.OfferGroupPosts, sessionManager session.Manager, users db.Users, restaurants db.Restaurants,
	offers db.Offers, regions db.Regions, fbAuth facebook.Authenticator) router.Handler {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User, restaurant *model.Restaurant) *router.HandlerError {
		post, handlerErr := parseOfferGroupPost(r, restaurant)
		if handlerErr != nil {
			return handlerErr
		}
		insertedPosts, err := c.Insert(post)
		if err != nil {
			return router.NewHandlerError(err, "Failed to store the post in the DB", http.StatusInternalServerError)
		}
		insertedPost := insertedPosts[0]
		if handlerErr = updateGroupPost(insertedPost, user, restaurant, offers, regions, c, fbAuth); handlerErr != nil {
			return handlerErr
		}
		return writeJSON(w, insertedPost)
	}
	return forRestaurant(sessionManager, users, restaurants, handler)
}

// PutOfferGroupPost handles PUT requests to /restaurant/posts/:date. It stores the info in the DB and updates the post in FB.
func PutOfferGroupPost(c db.OfferGroupPosts, sessionManager session.Manager, users db.Users, restaurants db.Restaurants,
	offers db.Offers, regions db.Regions, fbAuth facebook.Authenticator) router.HandlerWithParams {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User, restaurant *model.Restaurant,
		date model.DateWithoutTime) *router.HandlerError {
		post, handlerErr := parseOfferGroupPost(r, restaurant)
		if handlerErr != nil {
			return handlerErr
		}
		if post.Date != date {
			return router.NewSimpleHandlerError("Unexpected date value", http.StatusBadRequest)
		}
		err := c.UpdateByID(post.ID, post)
		if err != nil {
			return router.NewSimpleHandlerError("Failed to insert the post to DB", http.StatusBadRequest)
		}
		return writeJSON(w, post)
	}
	return forDate(sessionManager, users, restaurants, handler)
}

type HandlerWithRestaurantAndDate func(w http.ResponseWriter, r *http.Request, user *model.User, restaurant *model.Restaurant,
	date model.DateWithoutTime) *router.HandlerError

func forDate(sessionManager session.Manager, users db.Users, restaurants db.Restaurants,
	handler HandlerWithRestaurantAndDate) router.HandlerWithParams {
	handlerWithRestaurant := func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, user *model.User,
		restaurant *model.Restaurant) *router.HandlerError {
		date := model.DateWithoutTime(ps.ByName("date"))
		if date == "" {
			return router.NewStringHandlerError("Date not specified!", "Please specify a date", http.StatusBadRequest)
		}
		if !date.IsValid() {
			return router.NewSimpleHandlerError("Invalid date specified", http.StatusBadRequest)
		}
		return handler(w, r, user, restaurant, date)
	}
	return forRestaurantWithParams(sessionManager, users, restaurants, handlerWithRestaurant)
}

func parseOfferGroupPost(r *http.Request, restaurant *model.Restaurant) (*model.OfferGroupPost, *router.HandlerError) {
	var post struct {
		ID              bson.ObjectId `json:"_id"`
		MessageTemplate string        `json:"message_template"`
		Date            string        `json:"date"`
	}
	err := json.NewDecoder(r.Body).Decode(&post)
	if err != nil {
		return nil, router.NewHandlerError(err, "Failed to parse the post", http.StatusBadRequest)
	}
	date := model.DateWithoutTime(post.Date)
	if date == "" {
		return nil, router.NewStringHandlerError("Date not specified!", "Please specify a date", http.StatusBadRequest)
	}
	if !date.IsValid() {
		return nil, router.NewSimpleHandlerError("Invalid date specified", http.StatusBadRequest)
	}
	return &model.OfferGroupPost{
		ID:              post.ID,
		MessageTemplate: post.MessageTemplate,
		Date:            date,
		RestaurantID:    restaurant.ID,
	}, nil
}

func updateGroupPostForDate(date model.DateWithoutTime, user *model.User, restaurant *model.Restaurant, offers db.Offers,
	regions db.Regions, groupPosts db.OfferGroupPosts, fbAuth facebook.Authenticator) *router.HandlerError {
	post, err := groupPosts.GetByDate(date, restaurant.ID)
	if err == mgo.ErrNotFound {
		postToInsert := &model.OfferGroupPost{
			RestaurantID:    restaurant.ID,
			Date:            date,
			MessageTemplate: restaurant.DefaultGroupPostMessageTemplate,
		}
		insertedPosts, err := groupPosts.Insert(postToInsert)
		if err != nil {
			router.NewHandlerError(err, "Failed to create a group post with restaurant defaults", http.StatusInternalServerError)
		}
		post = insertedPosts[0]
	} else if err != nil {
		return router.NewHandlerError(err, "Failed to fetch a group post for that date", http.StatusInternalServerError)
	}
	return updateGroupPost(post, user, restaurant, offers, regions, groupPosts, fbAuth)
}

func updateGroupPost(post *model.OfferGroupPost, user *model.User, restaurant *model.Restaurant, offers db.Offers,
	regions db.Regions, groupPosts db.OfferGroupPosts, fbAuth facebook.Authenticator) *router.HandlerError {
	if restaurant.FacebookPageID == "" {
		return nil
	}
	fbAPI := fbAuth.APIConnection(&user.Session.FacebookUserToken)
	offersForDate, handlerErr := getOffersForDate(post.Date, restaurant, offers, regions)
	if handlerErr != nil {
		return handlerErr
	} else if len(offersForDate) == 0 {
		return nil
	}
	message := formFBMessage(post, offersForDate)
	// Remove the current post from FB, if it's already there
	if post.FBPostID != "" {
		err := fbAPI.PostDelete(user.Session.FacebookPageToken, post.FBPostID)
		if err != nil {
			return router.NewHandlerError(err, "Failed to delete the current post from Facebook", http.StatusBadGateway)
		}
	}
	// Add the new version
	fbPost, err := fbAPI.PagePublish(user.Session.FacebookPageToken, restaurant.FacebookPageID, message)
	if err != nil {
		return router.NewHandlerError(err, "Failed to post the offer to Facebook", http.StatusBadGateway)
	}
	post.FBPostID = fbPost.ID
	if err = groupPosts.UpdateByID(post.ID, post); err != nil {
		return router.NewHandlerError(err, "Failed to update a group post in the DB", http.StatusInternalServerError)
	}
	return nil
}

func formFBMessage(post *model.OfferGroupPost, offers []*model.Offer) string {
	offerMessages := make([]string, len(offers))
	for i, offer := range offers {
		offerMessages[i] = formFBOfferMessage(offer)
	}
	offersMessage := strings.Join(offerMessages, "\n")
	return fmt.Sprintf("%s\n\n%s", post.MessageTemplate, offersMessage)
}

func formFBOfferMessage(o *model.Offer) string {
	return fmt.Sprintf("%s - %.2f€", o.Title, o.Price)
}

func getOffersForDate(date model.DateWithoutTime, restaurant *model.Restaurant, offers db.Offers, regions db.Regions) ([]*model.Offer, *router.HandlerError) {
	region, err := regions.GetName(restaurant.Region)
	if err != nil {
		return nil, router.NewHandlerError(err, "Failed to find the restaurant's region", http.StatusInternalServerError)
	}
	location, err := time.LoadLocation(region.Location)
	if err != nil {
		return nil, router.NewHandlerError(err, "Failed to load region's location", http.StatusInternalServerError)
	}
	startTime, endTime, err := date.TimeBounds(location)
	if err != nil {
		return nil, router.NewHandlerError(err, "Failed to parse a date", http.StatusInternalServerError)
	}
	offersForDate, err := offers.GetForRestaurantWithinTimeBounds(restaurant.ID, startTime, endTime)
	if err != nil {
		return nil, router.NewHandlerError(err, "Failed to find offers for this date", http.StatusInternalServerError)
	}
	return offersForDate, nil
}
<commit_msg>Delete the post from FB even if no offers left over<commit_after>package handler

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

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"github.com/Lunchr/luncher-api/db"
	"github.com/Lunchr/luncher-api/db/model"
	"github.com/Lunchr/luncher-api/router"
	"github.com/Lunchr/luncher-api/session"
	"github.com/deiwin/facebook"
	"github.com/julienschmidt/httprouter"
)

// OfferGroupPost handles GET requests to /restaurant/posts/:date. It returns all current day's offers for the region.
func OfferGroupPost(c db.OfferGroupPosts, sessionManager session.Manager, users db.Users, restaurants db.Restaurants) router.HandlerWithParams {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User, restaurant *model.Restaurant,
		date model.DateWithoutTime) *router.HandlerError {
		post, err := c.GetByDate(date, restaurant.ID)
		if err == mgo.ErrNotFound {
			return router.NewHandlerError(err, "Offer group post not found", http.StatusNotFound)
		} else if err != nil {
			return router.NewHandlerError(err, "An error occured while trying to fetch a offer group post", http.StatusInternalServerError)
		}
		return writeJSON(w, post)
	}
	return forDate(sessionManager, users, restaurants, handler)
}

// PostOfferGroupPost handles POST requests to /restaurant/posts. It stores the info in the DB and updates the post in FB.
func PostOfferGroupPost(c db.OfferGroupPosts, sessionManager session.Manager, users db.Users, restaurants db.Restaurants,
	offers db.Offers, regions db.Regions, fbAuth facebook.Authenticator) router.Handler {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User, restaurant *model.Restaurant) *router.HandlerError {
		post, handlerErr := parseOfferGroupPost(r, restaurant)
		if handlerErr != nil {
			return handlerErr
		}
		insertedPosts, err := c.Insert(post)
		if err != nil {
			return router.NewHandlerError(err, "Failed to store the post in the DB", http.StatusInternalServerError)
		}
		insertedPost := insertedPosts[0]
		if handlerErr = updateGroupPost(insertedPost, user, restaurant, offers, regions, c, fbAuth); handlerErr != nil {
			return handlerErr
		}
		return writeJSON(w, insertedPost)
	}
	return forRestaurant(sessionManager, users, restaurants, handler)
}

// PutOfferGroupPost handles PUT requests to /restaurant/posts/:date. It stores the info in the DB and updates the post in FB.
func PutOfferGroupPost(c db.OfferGroupPosts, sessionManager session.Manager, users db.Users, restaurants db.Restaurants,
	offers db.Offers, regions db.Regions, fbAuth facebook.Authenticator) router.HandlerWithParams {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User, restaurant *model.Restaurant,
		date model.DateWithoutTime) *router.HandlerError {
		post, handlerErr := parseOfferGroupPost(r, restaurant)
		if handlerErr != nil {
			return handlerErr
		}
		if post.Date != date {
			return router.NewSimpleHandlerError("Unexpected date value", http.StatusBadRequest)
		}
		err := c.UpdateByID(post.ID, post)
		if err != nil {
			return router.NewSimpleHandlerError("Failed to insert the post to DB", http.StatusBadRequest)
		}
		return writeJSON(w, post)
	}
	return forDate(sessionManager, users, restaurants, handler)
}

type HandlerWithRestaurantAndDate func(w http.ResponseWriter, r *http.Request, user *model.User, restaurant *model.Restaurant,
	date model.DateWithoutTime) *router.HandlerError

func forDate(sessionManager session.Manager, users db.Users, restaurants db.Restaurants,
	handler HandlerWithRestaurantAndDate) router.HandlerWithParams {
	handlerWithRestaurant := func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, user *model.User,
		restaurant *model.Restaurant) *router.HandlerError {
		date := model.DateWithoutTime(ps.ByName("date"))
		if date == "" {
			return router.NewStringHandlerError("Date not specified!", "Please specify a date", http.StatusBadRequest)
		}
		if !date.IsValid() {
			return router.NewSimpleHandlerError("Invalid date specified", http.StatusBadRequest)
		}
		return handler(w, r, user, restaurant, date)
	}
	return forRestaurantWithParams(sessionManager, users, restaurants, handlerWithRestaurant)
}

func parseOfferGroupPost(r *http.Request, restaurant *model.Restaurant) (*model.OfferGroupPost, *router.HandlerError) {
	var post struct {
		ID              bson.ObjectId `json:"_id"`
		MessageTemplate string        `json:"message_template"`
		Date            string        `json:"date"`
	}
	err := json.NewDecoder(r.Body).Decode(&post)
	if err != nil {
		return nil, router.NewHandlerError(err, "Failed to parse the post", http.StatusBadRequest)
	}
	date := model.DateWithoutTime(post.Date)
	if date == "" {
		return nil, router.NewStringHandlerError("Date not specified!", "Please specify a date", http.StatusBadRequest)
	}
	if !date.IsValid() {
		return nil, router.NewSimpleHandlerError("Invalid date specified", http.StatusBadRequest)
	}
	return &model.OfferGroupPost{
		ID:              post.ID,
		MessageTemplate: post.MessageTemplate,
		Date:            date,
		RestaurantID:    restaurant.ID,
	}, nil
}

func updateGroupPostForDate(date model.DateWithoutTime, user *model.User, restaurant *model.Restaurant, offers db.Offers,
	regions db.Regions, groupPosts db.OfferGroupPosts, fbAuth facebook.Authenticator) *router.HandlerError {
	post, err := groupPosts.GetByDate(date, restaurant.ID)
	if err == mgo.ErrNotFound {
		postToInsert := &model.OfferGroupPost{
			RestaurantID:    restaurant.ID,
			Date:            date,
			MessageTemplate: restaurant.DefaultGroupPostMessageTemplate,
		}
		insertedPosts, err := groupPosts.Insert(postToInsert)
		if err != nil {
			router.NewHandlerError(err, "Failed to create a group post with restaurant defaults", http.StatusInternalServerError)
		}
		post = insertedPosts[0]
	} else if err != nil {
		return router.NewHandlerError(err, "Failed to fetch a group post for that date", http.StatusInternalServerError)
	}
	return updateGroupPost(post, user, restaurant, offers, regions, groupPosts, fbAuth)
}

func updateGroupPost(post *model.OfferGroupPost, user *model.User, restaurant *model.Restaurant, offers db.Offers,
	regions db.Regions, groupPosts db.OfferGroupPosts, fbAuth facebook.Authenticator) *router.HandlerError {
	if restaurant.FacebookPageID == "" {
		return nil
	}
	fbAPI := fbAuth.APIConnection(&user.Session.FacebookUserToken)
	// Remove the current post from FB, if it's already there
	if post.FBPostID != "" {
		err := fbAPI.PostDelete(user.Session.FacebookPageToken, post.FBPostID)
		if err != nil {
			return router.NewHandlerError(err, "Failed to delete the current post from Facebook", http.StatusBadGateway)
		}
	}
	offersForDate, handlerErr := getOffersForDate(post.Date, restaurant, offers, regions)
	if handlerErr != nil {
		return handlerErr
	} else if len(offersForDate) == 0 {
		return nil
	}
	message := formFBMessage(post, offersForDate)
	// Add the new version
	fbPost, err := fbAPI.PagePublish(user.Session.FacebookPageToken, restaurant.FacebookPageID, message)
	if err != nil {
		return router.NewHandlerError(err, "Failed to post the offer to Facebook", http.StatusBadGateway)
	}
	post.FBPostID = fbPost.ID
	if err = groupPosts.UpdateByID(post.ID, post); err != nil {
		return router.NewHandlerError(err, "Failed to update a group post in the DB", http.StatusInternalServerError)
	}
	return nil
}

func formFBMessage(post *model.OfferGroupPost, offers []*model.Offer) string {
	offerMessages := make([]string, len(offers))
	for i, offer := range offers {
		offerMessages[i] = formFBOfferMessage(offer)
	}
	offersMessage := strings.Join(offerMessages, "\n")
	return fmt.Sprintf("%s\n\n%s", post.MessageTemplate, offersMessage)
}

func formFBOfferMessage(o *model.Offer) string {
	return fmt.Sprintf("%s - %.2f€", o.Title, o.Price)
}

func getOffersForDate(date model.DateWithoutTime, restaurant *model.Restaurant, offers db.Offers, regions db.Regions) ([]*model.Offer, *router.HandlerError) {
	region, err := regions.GetName(restaurant.Region)
	if err != nil {
		return nil, router.NewHandlerError(err, "Failed to find the restaurant's region", http.StatusInternalServerError)
	}
	location, err := time.LoadLocation(region.Location)
	if err != nil {
		return nil, router.NewHandlerError(err, "Failed to load region's location", http.StatusInternalServerError)
	}
	startTime, endTime, err := date.TimeBounds(location)
	if err != nil {
		return nil, router.NewHandlerError(err, "Failed to parse a date", http.StatusInternalServerError)
	}
	offersForDate, err := offers.GetForRestaurantWithinTimeBounds(restaurant.ID, startTime, endTime)
	if err != nil {
		return nil, router.NewHandlerError(err, "Failed to find offers for this date", http.StatusInternalServerError)
	}
	return offersForDate, nil
}
<|endoftext|>