<commit_before>package commandline

import "strings"

func Parse(s string) *Command {
	ss := strings.Split(s, " ")
	if ss[0] == "" {
		return nil
	}
	return &Command{
		Name: ss[0],
		Args: ss[1:],
	}
}

type Command struct {
	Name string
	Args []string
}

type scanner struct {
	src  []byte
	size int
	off  int
}

func newScanner(src []byte) *scanner {
	return &scanner{
		src:  src,
		size: len(src),
	}
}

func (s *scanner) scan() (byte, bool) {
	if s.off >= s.size {
		return 0, true
	}

	ret := s.src[s.off]

	s.off++
	return ret, false
}

func (s *scanner) lex() *token {
	ch, eof := s.scan()
	if eof {
		return nil
	}
	switch {
	case isIdent(ch):
		ch, eof := s.scan()
		if eof {
			return nil
		}
		for isIdent(ch) {
			ch, eof = s.scan()
			if eof {
				break
			}
		}
		return &token{tt: ident}
	case ch == '"':
	}
	return nil
}

func isIdent(r byte) bool {
	return 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z'
}

type tokenType int

type token struct {
	tt tokenType
}

const (
	ident tokenType = iota
	str
)
<commit_msg>Rename scan -> next<commit_after>package commandline

import "strings"

func Parse(s string) *Command {
	ss := strings.Split(s, " ")
	if ss[0] == "" {
		return nil
	}
	return &Command{
		Name: ss[0],
		Args: ss[1:],
	}
}

type Command struct {
	Name string
	Args []string
}

type scanner struct {
	src  []byte
	size int
	off  int
}

func newScanner(src []byte) *scanner {
	return &scanner{
		src:  src,
		size: len(src),
	}
}

func (s *scanner) next() (byte, bool) {
	if s.off >= s.size {
		return 0, true
	}

	ret := s.src[s.off]

	s.off++
	return ret, false
}

func (s *scanner) lex() *token {
	ch, eof := s.next()
	if eof {
		return nil
	}
	switch {
	case isIdent(ch):
		ch, eof := s.next()
		if eof {
			return nil
		}
		for isIdent(ch) {
			ch, eof = s.next()
			if eof {
				break
			}
		}
		return &token{tt: ident}
	case ch == '"':
	}
	return nil
}

func isIdent(r byte) bool {
	return 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z'
}

type tokenType int

type token struct {
	tt tokenType
}

const (
	ident tokenType = iota
	str
)
<|endoftext|>


<commit_before>package server

import (
	"../hashtree"
	"../network"
	"bytes"
	"io"
	"os"
	"testing"
)

const (
	testDatabase = ".testDatabase"
	testLevelLow = 0
)

func TestFileIO(t *testing.T) {
	err := os.RemoveAll(testDatabase)
	if err != nil {
		t.Fatal(err)
	}
	testFileSize(0, t)
	testFileSize(1, t)
	testFileSize(1024, t)
	testFileSize(1025, t)
	testFileSize(2345, t)
	testFileSize(12345, t)
}

func testFileSize(size hashtree.Bytes, t *testing.T) {
	t.Log("testing size:", size)
	d := OpenSimpleDatabase(testDatabase, testLevelLow)

	id := d.ImportFromReader(&testFile{length: size})
	if hashtree.Bytes(id.GetLength()) != size {
		t.Fatalf("Length is %x, should be %x", id.GetLength(), size)
	}

	buf := make([]byte, 1024)
	n := 0
	for i := 0; i < int(size); i += n {
		n, _ = d.GetAt(buf, id, hashtree.Bytes(i))
		for j := 0; j < n; j++ {
			if buf[j] != testFileG(i+j) {
				t.Fatalf("at:%d, got:%x, expected:%x, for file:%s", i+j, buf[j], testFileG(i+j), id.CompactId())
			}
		}
	}

	hash := hashtree.NewTree()
	leafs := refHash.Nodes(size)
	t.Log("leafs:", leafs)
	levels := hash.Levels(leafs)
	for i := hashtree.Level(testLevelLow); i < levels-1; i++ {
		req := network.InnerHashes{
			Height: int32p(int(i)),
			From:   int32p(0),
			Length: int32p(int(hash.LevelWidth(leafs, i))),
		}
		got, _ := d.GetInnerHashes(id, req)
		list := got.GetHashes()
		hash.Write(list)
		listSum := hash.Sum(nil)
		hash.Reset()
		if !bytes.Equal(listSum, id.Hash) {
			t.Fatalf("Req:%s , got hashes:%x, len:%d, sums to:%x, expected:%x", req.String(), list, len(list), listSum, id.Hash)
		}
	}
}

type testFile struct {
	index  hashtree.Bytes
	length hashtree.Bytes
}

func (f *testFile) Read(b []byte) (int, error) {
	if f.index == f.length {
		return 0, io.EOF
	}
	b[0] = testFileG(int(f.index))
	f.index++
	return 1, nil
}

func testFileG(index int) byte {
	return byte(index * index / 10007)
}

func int32p(n int) *int32 {
	m := int32(n)
	return &m
}
<commit_msg>make generation of large test files faster<commit_after>package server

import (
	"../hashtree"
	"../network"
	"bytes"
	"io"
	"os"
	"testing"
)

const (
	testDatabase = ".testDatabase"
	testLevelLow = 0
)

func TestFileIO(t *testing.T) {
	err := os.RemoveAll(testDatabase)
	if err != nil {
		t.Fatal(err)
	}
	testFileSize(0, t)
	testFileSize(1, t)
	testFileSize(1024, t)
	testFileSize(1025, t)
	testFileSize(2345, t)
	testFileSize(12345, t)
}

func testFileSize(size hashtree.Bytes, t *testing.T) {
	t.Log("testing size:", size)
	d := OpenSimpleDatabase(testDatabase, testLevelLow)

	id := d.ImportFromReader(&testFile{length: size})
	if hashtree.Bytes(id.GetLength()) != size {
		t.Fatalf("Length is %x, should be %x", id.GetLength(), size)
	}

	buf := make([]byte, 1024)
	n := 0
	for i := 0; i < int(size); i += n {
		n, _ = d.GetAt(buf, id, hashtree.Bytes(i))
		for j := 0; j < n; j++ {
			if buf[j] != testFileG(i+j) {
				t.Fatalf("at:%d, got:%x, expected:%x, for file:%s", i+j, buf[j], testFileG(i+j), id.CompactId())
			}
		}
	}

	hash := hashtree.NewTree()
	leafs := refHash.Nodes(size)
	t.Log("leafs:", leafs)
	levels := hash.Levels(leafs)
	for i := hashtree.Level(testLevelLow); i < levels-1; i++ {
		req := network.InnerHashes{
			Height: int32p(int(i)),
			From:   int32p(0),
			Length: int32p(int(hash.LevelWidth(leafs, i))),
		}
		got, _ := d.GetInnerHashes(id, req)
		list := got.GetHashes()
		hash.Write(list)
		listSum := hash.Sum(nil)
		hash.Reset()
		if !bytes.Equal(listSum, id.Hash) {
			t.Fatalf("Req:%s , got hashes:%x, len:%d, sums to:%x, expected:%x", req.String(), list, len(list), listSum, id.Hash)
		}
	}
}

type testFile struct {
	index  hashtree.Bytes
	length hashtree.Bytes
}

func (f *testFile) Read(b []byte) (int, error) {
	i := 0
	for ; i < len(b); i++ {
		if f.index == f.length {
			return i, io.EOF
		}
		b[i] = testFileG(int(f.index))
		f.index++
	}

	return i, nil
}

func testFileG(index int) byte {
	return byte(index * index / 10007)
}

func int32p(n int) *int32 {
	m := int32(n)
	return &m
}
<|endoftext|>


<commit_before>package s3

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"strings"
	"text/template"

	"github.com/gogo/protobuf/types"
	"github.com/gorilla/mux"
	log "github.com/sirupsen/logrus"

	"github.com/pachyderm/pachyderm/src/client"
)

const locationResponse = `<?xml version="1.0" encoding="UTF-8"?>
<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">PACHYDERM</LocationConstraint>`

const listBucketSource = `<?xml version="1.0" encoding="UTF-8"?>
<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
    <Owner>
    	<ID>000000000000000000000000000000</ID>
    	<DisplayName>pachyderm</DisplayName>
    </Owner>
    <Buckets>
        {{ range . }}
            <Bucket>
                <Name>{{ .Repo.Name }}</Name>
                <CreationDate>{{ formatTime .Created }}</CreationDate>
            </Bucket>
        {{ end }}
    </Buckets>
</ListAllMyBucketsResult>`

func writeOK(w http.ResponseWriter) {
	w.Write([]byte("OK"))
}

func writeBadRequest(w http.ResponseWriter, err error) {
	http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest)
}

func writeMaybeNotFound(w http.ResponseWriter, err error) {
	if strings.Contains(err.Error(), "not found") {
		// This error message matches what the mux router returns when it 404s
		http.Error(w, "404 page not found", http.StatusNotFound)
	} else {
		writeServerError(w, err)
	}
}

func writeServerError(w http.ResponseWriter, err error) {
	http.Error(w, fmt.Sprintf("%v", err), http.StatusInternalServerError)
}

type handler struct {
	pc                  *client.APIClient
	listBucketsTemplate *template.Template
}

func newHandler(pc *client.APIClient) handler {
	funcMap := template.FuncMap{
		"formatTime": func(timestamp *types.Timestamp) string {
			return timestamp.String()
		},
	}

	listBucketsTemplate := template.Must(template.New("list-buckets").
		Funcs(funcMap).
		Parse(listBucketSource))

	return handler{
		pc:                  pc,
		listBucketsTemplate: listBucketsTemplate,
	}
}

func (h handler) ping(w http.ResponseWriter, r *http.Request) {
	writeOK(w)
}

func (h handler) root(w http.ResponseWriter, r *http.Request) {
	buckets, err := h.pc.ListRepo()
	if err != nil {
		writeServerError(w, err)
		return
	}

	if err = h.listBucketsTemplate.Execute(w, buckets); err != nil {
		writeServerError(w, err)
		return
	}

	w.Header().Set("Content-Type", "application/xml")
}

func (h handler) repo(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	repo := vars["repo"]

	if err := r.ParseForm(); err != nil {
		writeBadRequest(w, err)
		return
	}

	if _, err := h.pc.InspectRepo(repo); err != nil {
		writeMaybeNotFound(w, err)
		return
	}

	if _, ok := r.Form["location"]; ok {
		w.Header().Set("Content-Type", "application/xml")
		w.Write([]byte(locationResponse))
	} else {
		writeOK(w)
	}
}

func (h handler) getObject(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	repo := vars["repo"]
	branch := vars["branch"]
	file := vars["file"]

	fileInfo, err := h.pc.InspectFile(repo, branch, file)
	if err != nil {
		writeMaybeNotFound(w, err)
		return
	}

	timestamp, err := types.TimestampFromProto(fileInfo.Committed)
	if err != nil {
		writeServerError(w, err)
		return
	}

	reader, err := h.pc.GetFileReadSeeker(repo, branch, file)
	if err != nil {
		writeServerError(w, err)
		return
	}

	http.ServeContent(w, r, "", timestamp, reader)
}

func (h handler) putObject(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	repo := vars["repo"]
	branch := vars["branch"]
	file := vars["file"]

	expectedHash := r.Header.Get("Content-MD5")

	if expectedHash != "" {
		expectedHashBytes, err := base64.StdEncoding.DecodeString(expectedHash)

		if err != nil {
			writeBadRequest(w, fmt.Errorf("could not decode `Content-MD5`, as it is not base64-encoded"))
			return
		}

		h.putObjectVerifying(w, r, repo, branch, file, expectedHashBytes)
		return
	}

	h.putObjectUnverified(w, r, repo, branch, file)
}

func (h handler) putObjectVerifying(w http.ResponseWriter, r *http.Request, repo, branch, file string, expectedHash []byte) {
	hasher := md5.New()
	reader := io.TeeReader(r.Body, hasher)

	_, err := h.pc.PutFileOverwrite(repo, branch, file, reader, 0)
	if err != nil {
		// the error may be because the repo or branch does not exist -
		// double-check that by inspecting the branch, so we can serve a 404
		// instead
		_, inspectError := h.pc.InspectBranch(repo, branch)
		writeMaybeNotFound(w, inspectError)
		return
	}

	actualHash := hasher.Sum(nil)

	if !bytes.Equal(expectedHash, actualHash) {
		err = fmt.Errorf("content checksums differ; expected=%x, actual=%x", expectedHash, actualHash)
		writeServerError(w, err)
		return
	}

	writeOK(w)
}

func (h handler) putObjectUnverified(w http.ResponseWriter, r *http.Request, repo, branch, file string) {
	_, err := h.pc.PutFileOverwrite(repo, branch, file, r.Body, 0)
	if err != nil {
		// the error may be because the repo or branch does not exist -
		// double-check that by inspecting the branch, so we can serve a 404
		// instead
		_, inspectError := h.pc.InspectBranch(repo, branch)
		writeMaybeNotFound(w, inspectError)
		return
	}

	writeOK(w)
}

// Server runs an HTTP server with an S3-like API for PFS. This allows you to
// use s3 clients to acccess PFS contents.
//
// This returns an `http.Server` instance. It is the responsibility of the
// caller to start the server. This also makes it possible for the caller to
// enable graceful shutdown if desired; see the `http` package for details.
//
// Bucket names correspond to repo names, and files are accessible via the s3
// key pattern "<branch>/<filepath>". For example, to get the file "a/b/c.txt"
// on the "foo" repo's "master" branch, you'd making an s3 get request with
// bucket = "foo", key = "master/a/b/c.txt".
//
// Note: in s3, bucket names are constrained by IETF RFC 1123, (and its
// predecessor RFC 952) but pachyderm's repo naming constraints are slightly
// more liberal. If the s3 client does any kind of bucket name validation
// (this includes minio), repos whose names do not comply with RFC 1123 will
// not be accessible.
//
// Note: In `s3cmd`, you must set the access key and secret key, even though
// this API will ignore them - otherwise, you'll get an opaque config error:
// https://github.com/s3tools/s3cmd/issues/845#issuecomment-464885959
func Server(pc *client.APIClient, port uint16) *http.Server {
	handler := newHandler(pc)

	router := mux.NewRouter()
	router.HandleFunc("/", handler.root).Methods("GET")
	router.HandleFunc("/{repo}/", handler.repo).Methods("GET")
	router.HandleFunc("/{repo}/{branch}/{file:.+}", handler.getObject).Methods("GET")
	router.HandleFunc("/{repo}/{branch}/{file:.+}", handler.putObject).Methods("PUT")
	router.HandleFunc("/_ping", handler.ping).Methods("GET")

	// Note: error log is not customized on this `http.Server`, which means
	// it'll default to using the stdlib logger and produce log messages that
	// don't look like the ones produced elsewhere, and aren't configured
	// properly. In testing, this didn't seem to be a big deal because it's
	// rather hard to trigger it anyways, but if we find a reliable way to
	// create error logs, it might be worthwhile to fix.
	return &http.Server{
		Addr: fmt.Sprintf(":%d", port),
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			log.Debugf("s3 gateway request: %s %s", r.Method, r.RequestURI)
			router.ServeHTTP(w, r)
		}),
	}
}
<commit_msg>Serve empty responses on OK<commit_after>package s3

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"strings"
	"text/template"

	"github.com/gogo/protobuf/types"
	"github.com/gorilla/mux"
	log "github.com/sirupsen/logrus"

	"github.com/pachyderm/pachyderm/src/client"
)

const locationResponse = `<?xml version="1.0" encoding="UTF-8"?>
<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/">PACHYDERM</LocationConstraint>`

const listBucketSource = `<?xml version="1.0" encoding="UTF-8"?>
<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
    <Owner>
    	<ID>000000000000000000000000000000</ID>
    	<DisplayName>pachyderm</DisplayName>
    </Owner>
    <Buckets>
        {{ range . }}
            <Bucket>
                <Name>{{ .Repo.Name }}</Name>
                <CreationDate>{{ formatTime .Created }}</CreationDate>
            </Bucket>
        {{ end }}
    </Buckets>
</ListAllMyBucketsResult>`

func writeBadRequest(w http.ResponseWriter, err error) {
	http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest)
}

func writeMaybeNotFound(w http.ResponseWriter, err error) {
	if strings.Contains(err.Error(), "not found") {
		// This error message matches what the mux router returns when it 404s
		http.Error(w, "404 page not found", http.StatusNotFound)
	} else {
		writeServerError(w, err)
	}
}

func writeServerError(w http.ResponseWriter, err error) {
	http.Error(w, fmt.Sprintf("%v", err), http.StatusInternalServerError)
}

type handler struct {
	pc                  *client.APIClient
	listBucketsTemplate *template.Template
}

func newHandler(pc *client.APIClient) handler {
	funcMap := template.FuncMap{
		"formatTime": func(timestamp *types.Timestamp) string {
			return timestamp.String()
		},
	}

	listBucketsTemplate := template.Must(template.New("list-buckets").
		Funcs(funcMap).
		Parse(listBucketSource))

	return handler{
		pc:                  pc,
		listBucketsTemplate: listBucketsTemplate,
	}
}

func (h handler) ping(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte{})
}

func (h handler) root(w http.ResponseWriter, r *http.Request) {
	buckets, err := h.pc.ListRepo()
	if err != nil {
		writeServerError(w, err)
		return
	}

	if err = h.listBucketsTemplate.Execute(w, buckets); err != nil {
		writeServerError(w, err)
		return
	}

	w.Header().Set("Content-Type", "application/xml")
}

func (h handler) repo(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	repo := vars["repo"]

	if err := r.ParseForm(); err != nil {
		writeBadRequest(w, err)
		return
	}

	if _, err := h.pc.InspectRepo(repo); err != nil {
		writeMaybeNotFound(w, err)
		return
	}

	if _, ok := r.Form["location"]; ok {
		w.Header().Set("Content-Type", "application/xml")
		w.Write([]byte(locationResponse))
	} else {
		w.Write([]byte{})
	}
}

func (h handler) getObject(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	repo := vars["repo"]
	branch := vars["branch"]
	file := vars["file"]

	fileInfo, err := h.pc.InspectFile(repo, branch, file)
	if err != nil {
		writeMaybeNotFound(w, err)
		return
	}

	timestamp, err := types.TimestampFromProto(fileInfo.Committed)
	if err != nil {
		writeServerError(w, err)
		return
	}

	reader, err := h.pc.GetFileReadSeeker(repo, branch, file)
	if err != nil {
		writeServerError(w, err)
		return
	}

	http.ServeContent(w, r, "", timestamp, reader)
}

func (h handler) putObject(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	repo := vars["repo"]
	branch := vars["branch"]
	file := vars["file"]

	expectedHash := r.Header.Get("Content-MD5")

	if expectedHash != "" {
		expectedHashBytes, err := base64.StdEncoding.DecodeString(expectedHash)

		if err != nil {
			writeBadRequest(w, fmt.Errorf("could not decode `Content-MD5`, as it is not base64-encoded"))
			return
		}

		h.putObjectVerifying(w, r, repo, branch, file, expectedHashBytes)
		return
	}

	h.putObjectUnverified(w, r, repo, branch, file)
}

func (h handler) putObjectVerifying(w http.ResponseWriter, r *http.Request, repo, branch, file string, expectedHash []byte) {
	hasher := md5.New()
	reader := io.TeeReader(r.Body, hasher)

	_, err := h.pc.PutFileOverwrite(repo, branch, file, reader, 0)
	if err != nil {
		// the error may be because the repo or branch does not exist -
		// double-check that by inspecting the branch, so we can serve a 404
		// instead
		_, inspectError := h.pc.InspectBranch(repo, branch)
		writeMaybeNotFound(w, inspectError)
		return
	}

	actualHash := hasher.Sum(nil)

	if !bytes.Equal(expectedHash, actualHash) {
		err = fmt.Errorf("content checksums differ; expected=%x, actual=%x", expectedHash, actualHash)
		writeServerError(w, err)
		return
	}

	w.Write([]byte{})
}

func (h handler) putObjectUnverified(w http.ResponseWriter, r *http.Request, repo, branch, file string) {
	_, err := h.pc.PutFileOverwrite(repo, branch, file, r.Body, 0)
	if err != nil {
		// the error may be because the repo or branch does not exist -
		// double-check that by inspecting the branch, so we can serve a 404
		// instead
		_, inspectError := h.pc.InspectBranch(repo, branch)
		writeMaybeNotFound(w, inspectError)
		return
	}

	w.Write([]byte{})
}

// Server runs an HTTP server with an S3-like API for PFS. This allows you to
// use s3 clients to acccess PFS contents.
//
// This returns an `http.Server` instance. It is the responsibility of the
// caller to start the server. This also makes it possible for the caller to
// enable graceful shutdown if desired; see the `http` package for details.
//
// Bucket names correspond to repo names, and files are accessible via the s3
// key pattern "<branch>/<filepath>". For example, to get the file "a/b/c.txt"
// on the "foo" repo's "master" branch, you'd making an s3 get request with
// bucket = "foo", key = "master/a/b/c.txt".
//
// Note: in s3, bucket names are constrained by IETF RFC 1123, (and its
// predecessor RFC 952) but pachyderm's repo naming constraints are slightly
// more liberal. If the s3 client does any kind of bucket name validation
// (this includes minio), repos whose names do not comply with RFC 1123 will
// not be accessible.
//
// Note: In `s3cmd`, you must set the access key and secret key, even though
// this API will ignore them - otherwise, you'll get an opaque config error:
// https://github.com/s3tools/s3cmd/issues/845#issuecomment-464885959
func Server(pc *client.APIClient, port uint16) *http.Server {
	handler := newHandler(pc)

	router := mux.NewRouter()
	router.HandleFunc("/", handler.root).Methods("GET")
	router.HandleFunc("/{repo}/", handler.repo).Methods("GET")
	router.HandleFunc("/{repo}/{branch}/{file:.+}", handler.getObject).Methods("GET")
	router.HandleFunc("/{repo}/{branch}/{file:.+}", handler.putObject).Methods("PUT")
	router.HandleFunc("/_ping", handler.ping).Methods("GET")

	// Note: error log is not customized on this `http.Server`, which means
	// it'll default to using the stdlib logger and produce log messages that
	// don't look like the ones produced elsewhere, and aren't configured
	// properly. In testing, this didn't seem to be a big deal because it's
	// rather hard to trigger it anyways, but if we find a reliable way to
	// create error logs, it might be worthwhile to fix.
	return &http.Server{
		Addr: fmt.Sprintf(":%d", port),
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			log.Debugf("s3 gateway request: %s %s", r.Method, r.RequestURI)
			router.ServeHTTP(w, r)
		}),
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 apiserver

import (
	"net/http"
	"time"

	"k8s.io/apimachinery/pkg/apimachinery/announced"
	"k8s.io/apimachinery/pkg/apimachinery/registered"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/util/sets"
	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/registry/rest"
	genericapiserver "k8s.io/apiserver/pkg/server"
	"k8s.io/client-go/pkg/version"

	"k8s.io/kube-aggregator/pkg/apis/apiregistration"
	"k8s.io/kube-aggregator/pkg/apis/apiregistration/install"
	"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
	"k8s.io/kube-aggregator/pkg/client/clientset_generated/internalclientset"
	informers "k8s.io/kube-aggregator/pkg/client/informers/internalversion"
	listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion"
	openapicontroller "k8s.io/kube-aggregator/pkg/controllers/openapi"
	statuscontrollers "k8s.io/kube-aggregator/pkg/controllers/status"
	apiservicestorage "k8s.io/kube-aggregator/pkg/registry/apiservice/etcd"
)

var (
	groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
	registry             = registered.NewOrDie("")
	Scheme               = runtime.NewScheme()
	Codecs               = serializer.NewCodecFactory(Scheme)
)

func init() {
	install.Install(groupFactoryRegistry, registry, Scheme)

	// we need to add the options (like ListOptions) to empty v1
	metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Group: "", Version: "v1"})

	unversioned := schema.GroupVersion{Group: "", Version: "v1"}
	Scheme.AddUnversionedTypes(unversioned,
		&metav1.Status{},
		&metav1.APIVersions{},
		&metav1.APIGroupList{},
		&metav1.APIGroup{},
		&metav1.APIResourceList{},
	)
}

// legacyAPIServiceName is the fixed name of the only non-groupified API version
const legacyAPIServiceName = "v1."

type ExtraConfig struct {
	// ProxyClientCert/Key are the client cert used to identify this proxy. Backing APIServices use
	// this to confirm the proxy's identity
	ProxyClientCert []byte
	ProxyClientKey  []byte

	// If present, the Dial method will be used for dialing out to delegate
	// apiservers.
	ProxyTransport *http.Transport

	// Mechanism by which the Aggregator will resolve services. Required.
	ServiceResolver ServiceResolver
}

type Config struct {
	GenericConfig *genericapiserver.RecommendedConfig
	ExtraConfig   ExtraConfig
}

type completedConfig struct {
	GenericConfig genericapiserver.CompletedConfig
	ExtraConfig   *ExtraConfig
}

type CompletedConfig struct {
	// Embed a private pointer that cannot be instantiated outside of this package.
	*completedConfig
}

// APIAggregator contains state for a Kubernetes cluster master/api server.
type APIAggregator struct {
	GenericAPIServer *genericapiserver.GenericAPIServer

	delegateHandler http.Handler

	contextMapper genericapirequest.RequestContextMapper

	// proxyClientCert/Key are the client cert used to identify this proxy. Backing APIServices use
	// this to confirm the proxy's identity
	proxyClientCert []byte
	proxyClientKey  []byte
	proxyTransport  *http.Transport

	// proxyHandlers are the proxy handlers that are currently registered, keyed by apiservice.name
	proxyHandlers map[string]*proxyHandler
	// handledGroups are the groups that already have routes
	handledGroups sets.String

	// lister is used to add group handling for /apis/<group> aggregator lookups based on
	// controller state
	lister listers.APIServiceLister

	// provided for easier embedding
	APIRegistrationInformers informers.SharedInformerFactory

	// Information needed to determine routing for the aggregator
	serviceResolver ServiceResolver

	openAPIAggregationController *openapicontroller.AggregationController
}

// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
func (cfg *Config) Complete() CompletedConfig {
	c := completedConfig{
		cfg.GenericConfig.Complete(),
		&cfg.ExtraConfig,
	}

	// the kube aggregator wires its own discovery mechanism
	// TODO eventually collapse this by extracting all of the discovery out
	c.GenericConfig.EnableDiscovery = false
	version := version.Get()
	c.GenericConfig.Version = &version

	return CompletedConfig{&c}
}

// New returns a new instance of APIAggregator from the given config.
func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.DelegationTarget) (*APIAggregator, error) {
	// Prevent generic API server to install OpenAPI handler. Aggregator server
	// has its own customized OpenAPI handler.
	openApiConfig := c.GenericConfig.OpenAPIConfig
	c.GenericConfig.OpenAPIConfig = nil

	genericServer, err := c.GenericConfig.New("kube-aggregator", delegationTarget)
	if err != nil {
		return nil, err
	}

	apiregistrationClient, err := internalclientset.NewForConfig(c.GenericConfig.LoopbackClientConfig)
	if err != nil {
		return nil, err
	}
	informerFactory := informers.NewSharedInformerFactory(
		apiregistrationClient,
		5*time.Minute, // this is effectively used as a refresh interval right now.  Might want to do something nicer later on.
	)

	s := &APIAggregator{
		GenericAPIServer: genericServer,
		delegateHandler:  delegationTarget.UnprotectedHandler(),
		contextMapper:    c.GenericConfig.RequestContextMapper,
		proxyClientCert:  c.ExtraConfig.ProxyClientCert,
		proxyClientKey:   c.ExtraConfig.ProxyClientKey,
		proxyTransport:   c.ExtraConfig.ProxyTransport,
		proxyHandlers:    map[string]*proxyHandler{},
		handledGroups:    sets.String{},
		lister:           informerFactory.Apiregistration().InternalVersion().APIServices().Lister(),
		APIRegistrationInformers: informerFactory,
		serviceResolver:          c.ExtraConfig.ServiceResolver,
	}

	apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiregistration.GroupName, registry, Scheme, metav1.ParameterCodec, Codecs)
	apiGroupInfo.GroupMeta.GroupVersion = v1beta1.SchemeGroupVersion
	v1beta1storage := map[string]rest.Storage{}
	apiServiceREST := apiservicestorage.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
	v1beta1storage["apiservices"] = apiServiceREST
	v1beta1storage["apiservices/status"] = apiservicestorage.NewStatusREST(Scheme, apiServiceREST)
	apiGroupInfo.VersionedResourcesStorageMap["v1beta1"] = v1beta1storage

	if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
		return nil, err
	}

	apisHandler := &apisHandler{
		codecs: Codecs,
		lister: s.lister,
		mapper: s.contextMapper,
	}
	s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", apisHandler)
	s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandle("/apis/", apisHandler)

	apiserviceRegistrationController := NewAPIServiceRegistrationController(informerFactory.Apiregistration().InternalVersion().APIServices(), c.GenericConfig.SharedInformerFactory.Core().V1().Services(), s)
	availableController := statuscontrollers.NewAvailableConditionController(
		informerFactory.Apiregistration().InternalVersion().APIServices(),
		c.GenericConfig.SharedInformerFactory.Core().V1().Services(),
		c.GenericConfig.SharedInformerFactory.Core().V1().Endpoints(),
		apiregistrationClient.Apiregistration(),
		c.ExtraConfig.ProxyTransport,
		s.serviceResolver,
	)

	s.GenericAPIServer.AddPostStartHook("start-kube-aggregator-informers", func(context genericapiserver.PostStartHookContext) error {
		informerFactory.Start(context.StopCh)
		c.GenericConfig.SharedInformerFactory.Start(context.StopCh)
		return nil
	})
	s.GenericAPIServer.AddPostStartHook("apiservice-registration-controller", func(context genericapiserver.PostStartHookContext) error {
		go apiserviceRegistrationController.Run(context.StopCh)
		return nil
	})
	s.GenericAPIServer.AddPostStartHook("apiservice-status-available-controller", func(context genericapiserver.PostStartHookContext) error {
		// if we end up blocking for long periods of time, we may need to increase threadiness.
		go availableController.Run(5, context.StopCh)
		return nil
	})

	if openApiConfig != nil {
		specDownloader := openapicontroller.NewDownloader(s.contextMapper)
		openAPIAggregator, err := openapicontroller.BuildAndRegisterAggregator(
			&specDownloader,
			delegationTarget,
			s.GenericAPIServer.Handler.GoRestfulContainer.RegisteredWebServices(),
			openApiConfig,
			s.GenericAPIServer.Handler.NonGoRestfulMux)
		if err != nil {
			return nil, err
		}
		s.openAPIAggregationController = openapicontroller.NewAggregationController(&specDownloader, openAPIAggregator)

		s.GenericAPIServer.AddPostStartHook("apiservice-openapi-controller", func(context genericapiserver.PostStartHookContext) error {
			go s.openAPIAggregationController.Run(context.StopCh)
			return nil
		})
	}

	return s, nil
}

// AddAPIService adds an API service.  It is not thread-safe, so only call it on one thread at a time please.
// It's a slow moving API, so its ok to run the controller on a single thread
func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService) error {
	// if the proxyHandler already exists, it needs to be updated. The aggregation bits do not
	// since they are wired against listers because they require multiple resources to respond
	if proxyHandler, exists := s.proxyHandlers[apiService.Name]; exists {
		proxyHandler.updateAPIService(apiService)
		if s.openAPIAggregationController != nil {
			s.openAPIAggregationController.UpdateAPIService(proxyHandler, apiService)
		}
		return nil
	}

	proxyPath := "/apis/" + apiService.Spec.Group + "/" + apiService.Spec.Version
	// v1. is a special case for the legacy API.  It proxies to a wider set of endpoints.
	if apiService.Name == legacyAPIServiceName {
		proxyPath = "/api"
	}

	// register the proxy handler
	proxyHandler := &proxyHandler{
		contextMapper:   s.contextMapper,
		localDelegate:   s.delegateHandler,
		proxyClientCert: s.proxyClientCert,
		proxyClientKey:  s.proxyClientKey,
		proxyTransport:  s.proxyTransport,
		serviceResolver: s.serviceResolver,
	}
	proxyHandler.updateAPIService(apiService)
	if s.openAPIAggregationController != nil {
		s.openAPIAggregationController.AddAPIService(proxyHandler, apiService)
	}
	s.proxyHandlers[apiService.Name] = proxyHandler
	s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler)
	s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix(proxyPath+"/", proxyHandler)

	// if we're dealing with the legacy group, we're done here
	if apiService.Name == legacyAPIServiceName {
		return nil
	}

	// if we've already registered the path with the handler, we don't want to do it again.
	if s.handledGroups.Has(apiService.Spec.Group) {
		return nil
	}

	// it's time to register the group aggregation endpoint
	groupPath := "/apis/" + apiService.Spec.Group
	groupDiscoveryHandler := &apiGroupHandler{
		codecs:        Codecs,
		groupName:     apiService.Spec.Group,
		lister:        s.lister,
		delegate:      s.delegateHandler,
		contextMapper: s.contextMapper,
	}
	// aggregation is protected
	s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(groupPath, groupDiscoveryHandler)
	s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandle(groupPath+"/", groupDiscoveryHandler)
	s.handledGroups.Insert(apiService.Spec.Group)
	return nil
}

// RemoveAPIService removes the APIService from being handled.  It is not thread-safe, so only call it on one thread at a time please.
// It's a slow moving API, so its ok to run the controller on a single thread.
func (s *APIAggregator) RemoveAPIService(apiServiceName string) {
	version := apiregistration.APIServiceNameToGroupVersion(apiServiceName)

	proxyPath := "/apis/" + version.Group + "/" + version.Version
	// v1. is a special case for the legacy API.  It proxies to a wider set of endpoints.
	if apiServiceName == legacyAPIServiceName {
		proxyPath = "/api"
	}
	s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath)
	s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath + "/")
	if s.openAPIAggregationController != nil {
		s.openAPIAggregationController.RemoveAPIService(apiServiceName)
	}
	delete(s.proxyHandlers, apiServiceName)

	// TODO unregister group level discovery when there are no more versions for the group
	// We don't need this right away because the handler properly delegates when no versions are present
}
<commit_msg>UPSTREAM: <carry>: aggregator to proxy oapi to apps.openshift.io server<commit_after>/*
Copyright 2016 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 apiserver

import (
	"net/http"
	"time"

	"k8s.io/apimachinery/pkg/apimachinery/announced"
	"k8s.io/apimachinery/pkg/apimachinery/registered"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/util/sets"
	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/registry/rest"
	genericapiserver "k8s.io/apiserver/pkg/server"
	"k8s.io/client-go/pkg/version"

	"k8s.io/kube-aggregator/pkg/apis/apiregistration"
	"k8s.io/kube-aggregator/pkg/apis/apiregistration/install"
	"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
	"k8s.io/kube-aggregator/pkg/client/clientset_generated/internalclientset"
	informers "k8s.io/kube-aggregator/pkg/client/informers/internalversion"
	listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/internalversion"
	openapicontroller "k8s.io/kube-aggregator/pkg/controllers/openapi"
	statuscontrollers "k8s.io/kube-aggregator/pkg/controllers/status"
	apiservicestorage "k8s.io/kube-aggregator/pkg/registry/apiservice/etcd"
)

var (
	groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
	registry             = registered.NewOrDie("")
	Scheme               = runtime.NewScheme()
	Codecs               = serializer.NewCodecFactory(Scheme)
)

func init() {
	install.Install(groupFactoryRegistry, registry, Scheme)

	// we need to add the options (like ListOptions) to empty v1
	metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Group: "", Version: "v1"})

	unversioned := schema.GroupVersion{Group: "", Version: "v1"}
	Scheme.AddUnversionedTypes(unversioned,
		&metav1.Status{},
		&metav1.APIVersions{},
		&metav1.APIGroupList{},
		&metav1.APIGroup{},
		&metav1.APIResourceList{},
	)
}

// legacyAPIServiceName is the fixed name of the only non-groupified API version
const legacyAPIServiceName = "v1."

type ExtraConfig struct {
	// ProxyClientCert/Key are the client cert used to identify this proxy. Backing APIServices use
	// this to confirm the proxy's identity
	ProxyClientCert []byte
	ProxyClientKey  []byte

	// If present, the Dial method will be used for dialing out to delegate
	// apiservers.
	ProxyTransport *http.Transport

	// Mechanism by which the Aggregator will resolve services. Required.
	ServiceResolver ServiceResolver
}

type Config struct {
	GenericConfig *genericapiserver.RecommendedConfig
	ExtraConfig   ExtraConfig
}

type completedConfig struct {
	GenericConfig genericapiserver.CompletedConfig
	ExtraConfig   *ExtraConfig
}

type CompletedConfig struct {
	// Embed a private pointer that cannot be instantiated outside of this package.
	*completedConfig
}

// APIAggregator contains state for a Kubernetes cluster master/api server.
type APIAggregator struct {
	GenericAPIServer *genericapiserver.GenericAPIServer

	delegateHandler http.Handler

	contextMapper genericapirequest.RequestContextMapper

	// proxyClientCert/Key are the client cert used to identify this proxy. Backing APIServices use
	// this to confirm the proxy's identity
	proxyClientCert []byte
	proxyClientKey  []byte
	proxyTransport  *http.Transport

	// proxyHandlers are the proxy handlers that are currently registered, keyed by apiservice.name
	proxyHandlers map[string]*proxyHandler
	// handledGroups are the groups that already have routes
	handledGroups sets.String

	// lister is used to add group handling for /apis/<group> aggregator lookups based on
	// controller state
	lister listers.APIServiceLister

	// provided for easier embedding
	APIRegistrationInformers informers.SharedInformerFactory

	// Information needed to determine routing for the aggregator
	serviceResolver ServiceResolver

	openAPIAggregationController *openapicontroller.AggregationController
}

// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
func (cfg *Config) Complete() CompletedConfig {
	c := completedConfig{
		cfg.GenericConfig.Complete(),
		&cfg.ExtraConfig,
	}

	// the kube aggregator wires its own discovery mechanism
	// TODO eventually collapse this by extracting all of the discovery out
	c.GenericConfig.EnableDiscovery = false
	version := version.Get()
	c.GenericConfig.Version = &version

	return CompletedConfig{&c}
}

// New returns a new instance of APIAggregator from the given config.
func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.DelegationTarget) (*APIAggregator, error) {
	// Prevent generic API server to install OpenAPI handler. Aggregator server
	// has its own customized OpenAPI handler.
	openApiConfig := c.GenericConfig.OpenAPIConfig
	c.GenericConfig.OpenAPIConfig = nil

	genericServer, err := c.GenericConfig.New("kube-aggregator", delegationTarget)
	if err != nil {
		return nil, err
	}

	apiregistrationClient, err := internalclientset.NewForConfig(c.GenericConfig.LoopbackClientConfig)
	if err != nil {
		return nil, err
	}
	informerFactory := informers.NewSharedInformerFactory(
		apiregistrationClient,
		5*time.Minute, // this is effectively used as a refresh interval right now.  Might want to do something nicer later on.
	)

	s := &APIAggregator{
		GenericAPIServer: genericServer,
		delegateHandler:  delegationTarget.UnprotectedHandler(),
		contextMapper:    c.GenericConfig.RequestContextMapper,
		proxyClientCert:  c.ExtraConfig.ProxyClientCert,
		proxyClientKey:   c.ExtraConfig.ProxyClientKey,
		proxyTransport:   c.ExtraConfig.ProxyTransport,
		proxyHandlers:    map[string]*proxyHandler{},
		handledGroups:    sets.String{},
		lister:           informerFactory.Apiregistration().InternalVersion().APIServices().Lister(),
		APIRegistrationInformers: informerFactory,
		serviceResolver:          c.ExtraConfig.ServiceResolver,
	}

	apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiregistration.GroupName, registry, Scheme, metav1.ParameterCodec, Codecs)
	apiGroupInfo.GroupMeta.GroupVersion = v1beta1.SchemeGroupVersion
	v1beta1storage := map[string]rest.Storage{}
	apiServiceREST := apiservicestorage.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
	v1beta1storage["apiservices"] = apiServiceREST
	v1beta1storage["apiservices/status"] = apiservicestorage.NewStatusREST(Scheme, apiServiceREST)
	apiGroupInfo.VersionedResourcesStorageMap["v1beta1"] = v1beta1storage

	if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
		return nil, err
	}

	apisHandler := &apisHandler{
		codecs: Codecs,
		lister: s.lister,
		mapper: s.contextMapper,
	}
	s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", apisHandler)
	s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandle("/apis/", apisHandler)

	apiserviceRegistrationController := NewAPIServiceRegistrationController(informerFactory.Apiregistration().InternalVersion().APIServices(), c.GenericConfig.SharedInformerFactory.Core().V1().Services(), s)
	availableController := statuscontrollers.NewAvailableConditionController(
		informerFactory.Apiregistration().InternalVersion().APIServices(),
		c.GenericConfig.SharedInformerFactory.Core().V1().Services(),
		c.GenericConfig.SharedInformerFactory.Core().V1().Endpoints(),
		apiregistrationClient.Apiregistration(),
		c.ExtraConfig.ProxyTransport,
		s.serviceResolver,
	)

	s.GenericAPIServer.AddPostStartHook("start-kube-aggregator-informers", func(context genericapiserver.PostStartHookContext) error {
		informerFactory.Start(context.StopCh)
		c.GenericConfig.SharedInformerFactory.Start(context.StopCh)
		return nil
	})
	s.GenericAPIServer.AddPostStartHook("apiservice-registration-controller", func(context genericapiserver.PostStartHookContext) error {
		go apiserviceRegistrationController.Run(context.StopCh)
		return nil
	})
	s.GenericAPIServer.AddPostStartHook("apiservice-status-available-controller", func(context genericapiserver.PostStartHookContext) error {
		// if we end up blocking for long periods of time, we may need to increase threadiness.
		go availableController.Run(5, context.StopCh)
		return nil
	})

	if openApiConfig != nil {
		specDownloader := openapicontroller.NewDownloader(s.contextMapper)
		openAPIAggregator, err := openapicontroller.BuildAndRegisterAggregator(
			&specDownloader,
			delegationTarget,
			s.GenericAPIServer.Handler.GoRestfulContainer.RegisteredWebServices(),
			openApiConfig,
			s.GenericAPIServer.Handler.NonGoRestfulMux)
		if err != nil {
			return nil, err
		}
		s.openAPIAggregationController = openapicontroller.NewAggregationController(&specDownloader, openAPIAggregator)

		s.GenericAPIServer.AddPostStartHook("apiservice-openapi-controller", func(context genericapiserver.PostStartHookContext) error {
			go s.openAPIAggregationController.Run(context.StopCh)
			return nil
		})
	}

	return s, nil
}

// AddAPIService adds an API service.  It is not thread-safe, so only call it on one thread at a time please.
// It's a slow moving API, so its ok to run the controller on a single thread
func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService) error {
	// if the proxyHandler already exists, it needs to be updated. The aggregation bits do not
	// since they are wired against listers because they require multiple resources to respond
	if proxyHandler, exists := s.proxyHandlers[apiService.Name]; exists {
		proxyHandler.updateAPIService(apiService)
		if s.openAPIAggregationController != nil {
			s.openAPIAggregationController.UpdateAPIService(proxyHandler, apiService)
		}
		return nil
	}

	proxyPath := "/apis/" + apiService.Spec.Group + "/" + apiService.Spec.Version
	// v1. is a special case for the legacy API.  It proxies to a wider set of endpoints.
	if apiService.Name == legacyAPIServiceName {
		proxyPath = "/api"
	}

	// register the proxy handler
	proxyHandler := &proxyHandler{
		contextMapper:   s.contextMapper,
		localDelegate:   s.delegateHandler,
		proxyClientCert: s.proxyClientCert,
		proxyClientKey:  s.proxyClientKey,
		proxyTransport:  s.proxyTransport,
		serviceResolver: s.serviceResolver,
	}
	proxyHandler.updateAPIService(apiService)
	if s.openAPIAggregationController != nil {
		s.openAPIAggregationController.AddAPIService(proxyHandler, apiService)
	}
	s.proxyHandlers[apiService.Name] = proxyHandler
	s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler)
	s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix(proxyPath+"/", proxyHandler)

	// this exists to proxy /oapi
	if apiService.Spec.Group == "apps.openshift.io" {
		s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/oapi", proxyHandler)
		s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix("/oapi/", proxyHandler)
	}

	// if we're dealing with the legacy group, we're done here
	if apiService.Name == legacyAPIServiceName {
		return nil
	}

	// if we've already registered the path with the handler, we don't want to do it again.
	if s.handledGroups.Has(apiService.Spec.Group) {
		return nil
	}

	// it's time to register the group aggregation endpoint
	groupPath := "/apis/" + apiService.Spec.Group
	groupDiscoveryHandler := &apiGroupHandler{
		codecs:        Codecs,
		groupName:     apiService.Spec.Group,
		lister:        s.lister,
		delegate:      s.delegateHandler,
		contextMapper: s.contextMapper,
	}
	// aggregation is protected
	s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(groupPath, groupDiscoveryHandler)
	s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandle(groupPath+"/", groupDiscoveryHandler)
	s.handledGroups.Insert(apiService.Spec.Group)
	return nil
}

// RemoveAPIService removes the APIService from being handled.  It is not thread-safe, so only call it on one thread at a time please.
// It's a slow moving API, so its ok to run the controller on a single thread.
func (s *APIAggregator) RemoveAPIService(apiServiceName string) {
	version := apiregistration.APIServiceNameToGroupVersion(apiServiceName)

	proxyPath := "/apis/" + version.Group + "/" + version.Version
	// v1. is a special case for the legacy API.  It proxies to a wider set of endpoints.
	if apiServiceName == legacyAPIServiceName {
		proxyPath = "/api"
	}
	s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath)
	s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath + "/")
	if s.openAPIAggregationController != nil {
		s.openAPIAggregationController.RemoveAPIService(apiServiceName)
	}
	delete(s.proxyHandlers, apiServiceName)

	// TODO unregister group level discovery when there are no more versions for the group
	// We don't need this right away because the handler properly delegates when no versions are present
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
)

//HeaderReporter defines the required method a SIA client or pool client should implement for miners to be able to report solved headers
type HeaderReporter interface {
	//SubmitHeader reports a solved header
	SubmitHeader(header []byte) (err error)
}

// SiadClient is used to connect to siad
type SiadClient struct {
	siadurl string
}

// NewSiadClient creates a new SiadClient given a 'host:port' connectionstring
func NewSiadClient(connectionstring string) *SiadClient {
	s := SiadClient{}
	s.siadurl = "http://" + connectionstring + "/miner/header"
	return &s
}

//GetHeaderForWork fetches new work from the SIA daemon
func (sc *SiadClient) GetHeaderForWork() (target, header []byte, err error) {
	client := &http.Client{}

	req, err := http.NewRequest("GET", sc.siadurl, nil)
	if err != nil {
		return
	}

	req.Header.Add("User-Agent", "Sia-Agent")
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
	case 400:
		err = fmt.Errorf("Invalid siad response, status code %d, is your wallet initialized and unlocked?", resp.StatusCode)
		return
	default:
		err = fmt.Errorf("Invalid siad response, status code %d", resp.StatusCode)
		return
	}
	buf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	if len(buf) < 112 {
		err = fmt.Errorf("Invalid siad response, only received %d bytes, is your wallet initialized and unlocked?", len(buf))
		return
	}

	target = buf[:32]
	header = buf[32:112]

	return
}

//SubmitHeader reports a solved header to the SIA daemon
func (sc *SiadClient) SubmitHeader(header []byte) (err error) {
	req, err := http.NewRequest("POST", sc.siadurl, bytes.NewReader(header))
	if err != nil {
		return
	}

	req.Header.Add("User-Agent", "Sia-Agent")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	switch resp.StatusCode {
	case 204:
	default:
		err = fmt.Errorf("Invalid siad response, status code %d", resp.StatusCode)
		return
	}
	return
}
<commit_msg>Display error messages from siad<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

//HeaderReporter defines the required method a SIA client or pool client should implement for miners to be able to report solved headers
type HeaderReporter interface {
	//SubmitHeader reports a solved header
	SubmitHeader(header []byte) (err error)
}

// SiadClient is used to connect to siad
type SiadClient struct {
	siadurl string
}

// NewSiadClient creates a new SiadClient given a 'host:port' connectionstring
func NewSiadClient(connectionstring string) *SiadClient {
	s := SiadClient{}
	s.siadurl = "http://" + connectionstring + "/miner/header"
	return &s
}

func decodeMessage(resp *http.Response) (msg string, err error) {
	buf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}
	var data struct{Message string `json:"message"`}
	if err = json.Unmarshal(buf, &data); err == nil {
		msg = data.Message
	}
	return
}

//GetHeaderForWork fetches new work from the SIA daemon
func (sc *SiadClient) GetHeaderForWork() (target, header []byte, err error) {
	client := &http.Client{}

	req, err := http.NewRequest("GET", sc.siadurl, nil)
	if err != nil {
		return
	}

	req.Header.Add("User-Agent", "Sia-Agent")
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	defer resp.Body.Close()
	switch resp.StatusCode {
	case 200:
	case 400:
		msg, errd := decodeMessage(resp)
		if errd != nil {
			err = fmt.Errorf("Status code %d", resp.StatusCode)
		} else {
			err = fmt.Errorf("Status code %d, message: %s", resp.StatusCode, msg)
		}
		return
	default:
		err = fmt.Errorf("Status code %d", resp.StatusCode)
		return
	}
	buf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	if len(buf) < 112 {
		err = fmt.Errorf("Invalid response, only received %d bytes", len(buf))
		return
	}

	target = buf[:32]
	header = buf[32:112]

	return
}

//SubmitHeader reports a solved header to the SIA daemon
func (sc *SiadClient) SubmitHeader(header []byte) (err error) {
	req, err := http.NewRequest("POST", sc.siadurl, bytes.NewReader(header))
	if err != nil {
		return
	}

	req.Header.Add("User-Agent", "Sia-Agent")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return
	}
	switch resp.StatusCode {
	case 204:
	default:
		msg, errd := decodeMessage(resp)
		if errd != nil {
			err = fmt.Errorf("Status code %d", resp.StatusCode)
		} else {
			err = fmt.Errorf("%s", msg)
		}
		return
	}
	return
}
<|endoftext|>


<commit_before>package version

import (
	"fmt"
	"github.com/Aptomi/aptomi/cmd/common"
	"github.com/Aptomi/aptomi/pkg/client/rest"
	"github.com/Aptomi/aptomi/pkg/client/rest/http"
	"github.com/Aptomi/aptomi/pkg/config"
	"github.com/Aptomi/aptomi/pkg/version"
	log "github.com/Sirupsen/logrus"
	"github.com/spf13/cobra"
)

// NewCommand returns instance of cobra command that shows version from version package (injected at build tome)
func NewCommand(cfg *config.Client) *cobra.Command {
	var client, server bool

	cmd := &cobra.Command{
		Use:   "version",
		Short: "Print the Aptomi Client (and Server) versions",
		Run: func(cmd *cobra.Command, args []string) {
			if !client && !server {
				client = true
				server = true
			}

			if client {
				info := version.GetBuildInfo()

				data, err := common.Format(cfg.Output, false, info)
				if err != nil {
					panic(fmt.Sprintf("Error while formating policy: %s", err))
				}

				log.Infof("Client: ")
				fmt.Println(string(data))
			}
			if server {
				info, err := rest.New(cfg, http.NewClient(cfg)).Version().Show()
				if err != nil {
					panic(fmt.Sprintf("Error while getting server version: %s", err))
				}

				data, err := common.Format(cfg.Output, false, info)
				if err != nil {
					panic(fmt.Sprintf("Error while formating server version: %s", err))
				}

				log.Infof("Server: ")
				fmt.Println(string(data))
			}
		},
	}

	cmd.Flags().BoolVarP(&client, "client", "c", false, "Show client version")
	cmd.Flags().BoolVarP(&server, "server", "s", false, "Show server version")

	return cmd
}
<commit_msg>Add --short flag for aptomictl version<commit_after>package version

import (
	"fmt"
	"github.com/Aptomi/aptomi/cmd/common"
	"github.com/Aptomi/aptomi/pkg/client/rest"
	"github.com/Aptomi/aptomi/pkg/client/rest/http"
	"github.com/Aptomi/aptomi/pkg/config"
	"github.com/Aptomi/aptomi/pkg/version"
	log "github.com/Sirupsen/logrus"
	"github.com/spf13/cobra"
)

// NewCommand returns instance of cobra command that shows version from version package (injected at build tome)
func NewCommand(cfg *config.Client) *cobra.Command {
	var client, server, short bool

	cmd := &cobra.Command{
		Use:   "version",
		Short: "Print the Aptomi Client (and Server) versions",
		Run: func(cmd *cobra.Command, args []string) {
			if !client && !server {
				client = true
				server = true
			}

			if client {
				info := version.GetBuildInfo()
				if short {
					fmt.Println("Client Version:", info.GitVersion)
				} else {
					data, err := common.Format(cfg.Output, false, info)
					if err != nil {
						panic(fmt.Sprintf("Error while formating policy: %s", err))
					}

					log.Infof("Client: ")
					fmt.Println(string(data))
				}
			}
			if server {
				info, infoErr := rest.New(cfg, http.NewClient(cfg)).Version().Show()
				if infoErr != nil {
					panic(fmt.Sprintf("Error while getting server version: %s", infoErr))
				}

				if short {
					fmt.Println("Server Version:", info.GitVersion)
				} else {
					data, err := common.Format(cfg.Output, false, info)
					if err != nil {
						panic(fmt.Sprintf("Error while formating server version: %s", err))
					}

					log.Infof("Server: ")
					fmt.Println(string(data))
				}
			}
		},
	}

	cmd.Flags().BoolVarP(&client, "client", "c", false, "Show client version")
	cmd.Flags().BoolVarP(&server, "server", "s", false, "Show server version")
	cmd.Flags().BoolVarP(&short, "short", "", false, "Print just the version number")

	return cmd
}
<|endoftext|>


<commit_before>// TODO: fix a proper api poller or something
// I need an easier way to say "hey I need 500 stream statuses", and it should then call it 5 times with 100 users in each request
// Check out API rate limiting
package main

import (
	"fmt"
	"sync"
	"time"

	"github.com/dankeroni/gotwitch"
	"github.com/pajbot/pajbot2/pkg"
	"github.com/pajbot/pajbot2/pkg/apirequest"
	"github.com/pajbot/pajbot2/pkg/twitch"
	"github.com/pajbot/utils"
)

const PollInterval = 5 * time.Second

var _ pkg.StreamStore = &StreamStore{}

type StreamStore struct {
	mutex   *sync.Mutex
	streams map[string]*twitch.Stream
}

func NewStreamStore() *StreamStore {
	s := &StreamStore{
		mutex:   &sync.Mutex{},
		streams: make(map[string]*twitch.Stream),
	}

	return s
}

func (s *StreamStore) PollStreams() {
	s.mutex.Lock()

	var remaining []string

	for streamID, stream := range s.streams {
		if stream.NeedsInitialPoll() {
			remaining = append(remaining, streamID)
		}
	}

	s.mutex.Unlock()

	if len(remaining) == 0 {
		return
	}

	fmt.Println("Poll streams")

	var wg sync.WaitGroup

	batches, _ := utils.ChunkStringSlice(remaining, 100)
	for _, batch := range batches {
		wg.Add(1)

		// Subscribe to the streams webhook topic for the channels as well
		go func(batch []string) {
			for _, userID := range batch {
				go func(userID string) {
					err := apirequest.TwitchWrapper.WebhookSubscribe(gotwitch.WebhookTopicStreams, userID)
					if err != nil {
						fmt.Println("Error subscribing to webhook for user", userID, err)
					}
				}(userID)
			}
		}(batch)

		go func() {
			defer wg.Done()
			data, err := apirequest.TwitchWrapper.GetStreams(batch, nil)
			if err != nil {
				fmt.Println("api error:", err)
			}

			s.mutex.Lock()
			defer s.mutex.Unlock()
			for _, activeStream := range data {
				if stream, ok := s.streams[activeStream.UserID]; ok {
					stream.Update(&activeStream)
				}
			}
		}()
	}

	wg.Wait()
}

func (s *StreamStore) Run() {
	for {
		select {
		case <-time.After(PollInterval):
			s.PollStreams()
		}
	}
}

func (s *StreamStore) GetStream(account pkg.Account) pkg.Stream {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if stream, ok := s.streams[account.ID()]; ok {
		return stream
	}

	stream := twitch.NewTwitchStream(account)

	s.streams[account.ID()] = stream

	return stream
}

func (s *StreamStore) JoinStream(account pkg.Account) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if _, ok := s.streams[account.ID()]; !ok {
		// Insert stream
		s.streams[account.ID()] = twitch.NewTwitchStream(account)
	}
}
<commit_msg>Fix potential thread issue if more than 100 channels were batched<commit_after>// TODO: fix a proper api poller or something
// I need an easier way to say "hey I need 500 stream statuses", and it should then call it 5 times with 100 users in each request
// Check out API rate limiting
package main

import (
	"fmt"
	"sync"
	"time"

	"github.com/dankeroni/gotwitch"
	"github.com/pajbot/pajbot2/pkg"
	"github.com/pajbot/pajbot2/pkg/apirequest"
	"github.com/pajbot/pajbot2/pkg/twitch"
	"github.com/pajbot/utils"
)

const PollInterval = 5 * time.Second

var _ pkg.StreamStore = &StreamStore{}

type StreamStore struct {
	mutex   *sync.Mutex
	streams map[string]*twitch.Stream
}

func NewStreamStore() *StreamStore {
	s := &StreamStore{
		mutex:   &sync.Mutex{},
		streams: make(map[string]*twitch.Stream),
	}

	return s
}

func (s *StreamStore) PollStreams() {
	s.mutex.Lock()

	var remaining []string

	for streamID, stream := range s.streams {
		if stream.NeedsInitialPoll() {
			remaining = append(remaining, streamID)
		}
	}

	s.mutex.Unlock()

	if len(remaining) == 0 {
		return
	}

	fmt.Println("Poll streams")

	var wg sync.WaitGroup

	batches, _ := utils.ChunkStringSlice(remaining, 100)
	for _, batch := range batches {
		wg.Add(1)

		// Subscribe to the streams webhook topic for the channels as well
		go func(batch []string) {
			for _, userID := range batch {
				go func(userID string) {
					err := apirequest.TwitchWrapper.WebhookSubscribe(gotwitch.WebhookTopicStreams, userID)
					if err != nil {
						fmt.Println("Error subscribing to webhook for user", userID, err)
					}
				}(userID)
			}
		}(batch)

		go func(batch []string) {
			defer wg.Done()
			data, err := apirequest.TwitchWrapper.GetStreams(batch, nil)
			if err != nil {
				fmt.Println("api error:", err)
			}

			s.mutex.Lock()
			defer s.mutex.Unlock()
			for _, activeStream := range data {
				if stream, ok := s.streams[activeStream.UserID]; ok {
					stream.Update(&activeStream)
				}
			}
		}(batch)
	}

	wg.Wait()
}

func (s *StreamStore) Run() {
	for {
		select {
		case <-time.After(PollInterval):
			s.PollStreams()
		}
	}
}

func (s *StreamStore) GetStream(account pkg.Account) pkg.Stream {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if stream, ok := s.streams[account.ID()]; ok {
		return stream
	}

	stream := twitch.NewTwitchStream(account)

	s.streams[account.ID()] = stream

	return stream
}

func (s *StreamStore) JoinStream(account pkg.Account) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if _, ok := s.streams[account.ID()]; !ok {
		// Insert stream
		s.streams[account.ID()] = twitch.NewTwitchStream(account)
	}
}
<|endoftext|>


<commit_before>package deletefile

import (
	"github.com/ncw/rclone/cmd"
	"github.com/ncw/rclone/fs/operations"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

func init() {
	cmd.Root.AddCommand(commandDefintion)
}

var commandDefintion = &cobra.Command{
	Use:   "deletefile remote:path",
	Short: `Remove a single file path from remote.`,
	Long: `
Remove a single file path from remote.  Unlike ` + "`" + `delete` + "`" + ` it cannot be used to
remove a directory and it doesn't obey include/exclude filters - if the specified file exists,
it will always be removed.
`,
	Run: func(command *cobra.Command, args []string) {
		cmd.CheckArgs(1, 1, command, args)
		fs, fileName := cmd.NewFsFile(args[0])
		cmd.Run(true, false, command, func() error {
			if fileName == "" {
				return errors.Errorf("%s is a directory or doesn't exist", args[0])
			}
			fileObj, err := fs.NewObject(fileName)
			if err != nil {
				return err
			}
			return operations.DeleteFile(fileObj)
		})
	},
}
<commit_msg>deletefile: fix typo in docs<commit_after>package deletefile

import (
	"github.com/ncw/rclone/cmd"
	"github.com/ncw/rclone/fs/operations"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

func init() {
	cmd.Root.AddCommand(commandDefintion)
}

var commandDefintion = &cobra.Command{
	Use:   "deletefile remote:path",
	Short: `Remove a single file from remote.`,
	Long: `
Remove a single file from remote.  Unlike ` + "`" + `delete` + "`" + ` it cannot be used to
remove a directory and it doesn't obey include/exclude filters - if the specified file exists,
it will always be removed.
`,
	Run: func(command *cobra.Command, args []string) {
		cmd.CheckArgs(1, 1, command, args)
		fs, fileName := cmd.NewFsFile(args[0])
		cmd.Run(true, false, command, func() error {
			if fileName == "" {
				return errors.Errorf("%s is a directory or doesn't exist", args[0])
			}
			fileObj, err := fs.NewObject(fileName)
			if err != nil {
				return err
			}
			return operations.DeleteFile(fileObj)
		})
	},
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/yml/nsqthumbnailer"
)

var (
	addr     = flag.String("addr", "127.0.0.1:9900", "http addr (default is 127.0.0.1:9900)")
	URLNames = make(map[string]string)
)

func thumbHandler(w http.ResponseWriter, r *http.Request) {
	var thumbReq bytes.Buffer
	if r.Method == "GET" {
		// In this case we are going to look for the thumbReq in the URL.
		// It should be base 64 encoded
		path := strings.TrimPrefix(r.URL.Path, URLNames["/thumb/"])
		_, err := io.Copy(&thumbReq, base64.NewDecoder(base64.URLEncoding, strings.NewReader(path)))
		if err != nil {
			http.Error(w, fmt.Sprintf("failed to decode the thumb generation request: %s", err), http.StatusBadRequest)
			return
		}
	} else if r.Method == "POST" {
		// Grab the JSON representation of thumbReq directly from the r.Body
		_, err := io.Copy(&thumbReq, r.Body)
		defer r.Body.Close()
		if err != nil {
			http.Error(w, fmt.Sprintf("failed to decode the thumb generation request: %s", err), http.StatusBadRequest)
			return
		}
	} else {
		http.Error(w, fmt.Sprintf("Request method not supported: %s", r.Method), http.StatusBadRequest)
		return
	}
	fmt.Printf("thumbReq: %s\n", thumbReq.String())

	tm := nsqthumbnailer.ThumbnailerMessage{}
	err := json.Unmarshal(thumbReq.Bytes(), &tm)
	if err != nil {
		err = errors.New(fmt.Sprintf("ERROR: failed to unmarshal `thumbReq` into a thumbnailerMessage - %s", err))
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	resultChan := make(chan nsqthumbnailer.ThumbnailResult)
	go tm.GenerateThumbnails(resultChan)

	results := make([]nsqthumbnailer.ThumbnailResult, 0)
	for i := 0; i < len(tm.Opts); i++ {
		result := <-resultChan
		results = append(results, result)
	}
	for _, r := range results {
		if r.Err != nil {
			err := fmt.Errorf("Error: At least one thumb generation failed - %s", r.Err, results)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	if tm.DeleteSrc == true {
		fmt.Println("Deleting", tm.SrcImage)
		err = tm.DeleteImage()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	body, err := json.Marshal(results)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	w.Write(body)
	return
}

func main() {
	flag.Parse()
	fmt.Println("Starting HTTP thumbnailer on: ", *addr)
	URLNames["/"] = "/"
	URLNames["/thumb/"] = "/thumb/"
	URLNames["/debug/"] = "/debug/"

	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("This is the home page"))
	})
	mux.HandleFunc(URLNames["/debug/"], func(w http.ResponseWriter, r *http.Request) {
		thumbReq := []byte("{\"srcImage\": \"file:///home/yml/Dropbox/Devs/golang/nsq_sandbox/nsq-thumb-src-test/baignade.jpg\", \"opts\": [{\"rect\":{\"min\":[200, 200], \"max\":[600,600]},\"width\":150, \"height\":0}], \"dstFolder\":\"file:///home/yml/Dropbox/Devs/golang/nsq_sandbox/nsq-thumb-dst-test/\"}")
		fmt.Printf("thumbReq : %s\n", thumbReq)
		w.Write([]byte("\n"))
		enc := base64.NewEncoder(base64.URLEncoding, w)
		enc.Write(thumbReq)
		enc.Close()
		w.Write([]byte("\n\n"))
	})
	mux.HandleFunc(URLNames["/thumb/"], thumbHandler)
	http.ListenAndServe(*addr, mux)

}
<commit_msg>Hook the thumbsHandler under /thumbs/ instead of /thumb/<commit_after>package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"net/http"
	"strings"

	"github.com/yml/nsqthumbnailer"
)

var (
	addr     = flag.String("addr", "127.0.0.1:9900", "http addr (default is 127.0.0.1:9900)")
	URLNames = make(map[string]string)
)

func thumbsHandler(w http.ResponseWriter, r *http.Request) {
	var thumbReq bytes.Buffer
	if r.Method == "GET" {
		// In this case we are going to look for the thumbReq in the URL.
		// It should be base 64 encoded
		path := strings.TrimPrefix(r.URL.Path, URLNames["/thumb/"])
		_, err := io.Copy(&thumbReq, base64.NewDecoder(base64.URLEncoding, strings.NewReader(path)))
		if err != nil {
			http.Error(w, fmt.Sprintf("failed to decode the thumb generation request: %s", err), http.StatusBadRequest)
			return
		}
	} else if r.Method == "POST" {
		// Grab the JSON representation of thumbReq directly from the r.Body
		_, err := io.Copy(&thumbReq, r.Body)
		defer r.Body.Close()
		if err != nil {
			http.Error(w, fmt.Sprintf("failed to decode the thumb generation request: %s", err), http.StatusBadRequest)
			return
		}
	} else {
		http.Error(w, fmt.Sprintf("Request method not supported: %s", r.Method), http.StatusBadRequest)
		return
	}
	fmt.Printf("thumbReq: %s\n", thumbReq.String())

	tm := nsqthumbnailer.ThumbnailerMessage{}
	err := json.Unmarshal(thumbReq.Bytes(), &tm)
	if err != nil {
		err = errors.New(fmt.Sprintf("ERROR: failed to unmarshal `thumbReq` into a thumbnailerMessage - %s", err))
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	resultChan := make(chan nsqthumbnailer.ThumbnailResult)
	go tm.GenerateThumbnails(resultChan)

	results := make([]nsqthumbnailer.ThumbnailResult, 0)
	for i := 0; i < len(tm.Opts); i++ {
		result := <-resultChan
		results = append(results, result)
	}
	for _, r := range results {
		if r.Err != nil {
			err := fmt.Errorf("Error: At least one thumb generation failed - %s", r.Err, results)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	if tm.DeleteSrc == true {
		fmt.Println("Deleting", tm.SrcImage)
		err = tm.DeleteImage()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	body, err := json.Marshal(results)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	w.Write(body)
	return
}

func main() {
	flag.Parse()
	fmt.Println("Starting HTTP thumbnailer on: ", *addr)
	URLNames["/"] = "/"
	URLNames["/thumbs/"] = "/thumbs/"
	URLNames["/debug/"] = "/debug/"

	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("This is the home page"))
	})
	mux.HandleFunc(URLNames["/debug/"], func(w http.ResponseWriter, r *http.Request) {
		thumbReq := []byte("{\"srcImage\": \"file:///home/yml/Dropbox/Devs/golang/nsq_sandbox/nsq-thumb-src-test/baignade.jpg\", \"opts\": [{\"rect\":{\"min\":[200, 200], \"max\":[600,600]},\"width\":150, \"height\":0}], \"dstFolder\":\"file:///home/yml/Dropbox/Devs/golang/nsq_sandbox/nsq-thumb-dst-test/\"}")
		fmt.Printf("thumbReq : %s\n", thumbReq)
		w.Write([]byte("\n"))
		enc := base64.NewEncoder(base64.URLEncoding, w)
		enc.Write(thumbReq)
		enc.Close()
		w.Write([]byte("\n\n"))
	})
	mux.HandleFunc(URLNames["/thumbs/"], thumbsHandler)
	http.ListenAndServe(*addr, mux)

}
<|endoftext|>


<commit_before>package emailnotifier

import (
	"errors"
	"fmt"
	"koding/db/mongodb/modelhelper"
	socialmodels "socialapi/models"
	"socialapi/workers/notification/models"

	"github.com/koding/logging"
	"github.com/koding/rabbitmq"
	"github.com/koding/worker"
	"github.com/robfig/cron"
	"github.com/sendgrid/sendgrid-go"
	"github.com/streadway/amqp"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

const SCHEDULE = "0 0 0 * * *"

var cronJob *cron.Cron

var emailConfig = map[string]string{
	models.NotificationContent_TYPE_COMMENT: "comment",
	models.NotificationContent_TYPE_LIKE:    "likeActivities",
	models.NotificationContent_TYPE_FOLLOW:  "followActions",
	models.NotificationContent_TYPE_JOIN:    "groupJoined",
	models.NotificationContent_TYPE_LEAVE:   "groupLeft",
	models.NotificationContent_TYPE_MENTION: "mention",
}

type Action func(*Controller, []byte) error

type Controller struct {
	routes   map[string]Action
	log      logging.Logger
	rmqConn  *amqp.Connection
	settings *EmailSettings
}

type EmailSettings struct {
	Username string
	Password string
	FromName string
	FromMail string
}

func (n *Controller) DefaultErrHandler(delivery amqp.Delivery, err error) bool {
	n.log.Error("an error occured: %s", err)
	delivery.Ack(false)

	return false
}

func (n *Controller) HandleEvent(event string, data []byte) error {
	n.log.Debug("New Event Received %s", event)
	handler, ok := n.routes[event]
	if !ok {
		return worker.HandlerNotFoundErr
	}

	return handler(n, data)
}

func New(rmq *rabbitmq.RabbitMQ, log logging.Logger, es *EmailSettings) (*Controller, error) {
	rmqConn, err := rmq.Connect("NewEmailNotifierWorkerController")
	if err != nil {
		return nil, err
	}

	nwc := &Controller{
		log:      log,
		rmqConn:  rmqConn.Conn(),
		settings: es,
	}

	routes := map[string]Action{
		"notification.notification_created": (*Controller).SendInstantEmail,
		"notification.notification_updated": (*Controller).SendInstantEmail,
	}

	nwc.routes = routes

	nwc.initDailyEmailCron()

	return nwc, nil
}

func (n *EmailNotifierWorkerController) initDailyEmailCron() {

	cronJob = cron.New()
	cronJob.AddFunc(SCHEDULE, n.sendDailyMails)
	cronJob.Start()
}

func (n *Controller) SendInstantEmail(data []byte) error {
	channel, err := n.rmqConn.Channel()
	if err != nil {
		return errors.New("channel connection error")
	}
	defer channel.Close()

	notification := models.NewNotification()
	if err := notification.MapMessage(data); err != nil {
		return err
	}

	// fetch latest activity for checking actor
	activity, nc, err := notification.FetchLastActivity()
	if err != nil {
		return err
	}

	if !validNotification(activity, notification) {
		return nil
	}

	uc, err := fetchUserContact(notification.AccountId)
	if err != nil {
		return fmt.Errorf("an error occurred while fetching user contact: %s", err)
	}

	if !checkMailSettings(uc, nc) {
		return nil
	}

	container, err := buildContainer(activity, nc, notification)
	if err != nil {
		return err
	}

	body, err := renderTemplate(uc, container)
	if err != nil {
		return fmt.Errorf("an error occurred while preparing notification email: %s", err)
	}
	subject := prepareSubject(container)

	if err := createToken(uc, nc, container.Token); err != nil {
		return err
	}

	return n.SendMail(uc, body, subject)
}

type UserContact struct {
	AccountId     int64
	UserOldId     bson.ObjectId
	Email         string
	FirstName     string
	LastName      string
	Username      string
	Hash          string
	Token         string
	EmailSettings map[string]bool
}

func validNotification(a *models.NotificationActivity, n *models.Notification) bool {
	// do not notify actor for her own action
	if a.ActorId == n.AccountId {
		return false
	}

	// do not notify user when notification is not yet activated
	return !n.ActivatedAt.IsZero()
}

func checkMailSettings(uc *UserContact, nc *models.NotificationContent) bool {
	// notifications are disabled
	if val := uc.EmailSettings["global"]; !val {
		return false
	}

	// daily notifications are enabled
	if val := uc.EmailSettings["daily"]; val {
		return false
	}

	// get config
	return uc.EmailSettings[emailConfig[nc.TypeConstant]]
}

func buildContainer(accountId int64, a *models.NotificationActivity,
	nc *models.NotificationContent) (*NotificationContainer, error) {

	// if content type not valid return
	contentType, err := nc.GetContentType()
	if err != nil {
		return nil, err
	}

	container := &NotificationContainer{
		Activity:  a,
		Content:   nc,
		AccountId: accountId,
	}

	// if notification target is related with an object (comment/status update)
	if containsObject(nc) {
		target := socialmodels.NewChannelMessage()
		if err := target.ById(nc.TargetId); err != nil {
			return nil, fmt.Errorf("target message not found")
		}

		prepareGroup(container, target)
		prepareSlug(container, target)
		prepareObjectType(container, target)
		container.Message = fetchContentBody(nc, target)
		contentType.SetActorId(target.AccountId)
		contentType.SetListerId(accountId)
	}

	container.ActivityMessage = contentType.GetActivity()

	return container, nil
}

func prepareGroup(container *NotificationContainer, cm *socialmodels.ChannelMessage) {
	c := socialmodels.NewChannel()
	if err := c.ById(cm.InitialChannelId); err != nil {
		return
	}
	// TODO fix these Slug and Name
	container.Group = GroupContent{
		Slug: c.GroupName,
		Name: c.GroupName,
	}
}

func prepareSlug(container *NotificationContainer, cm *socialmodels.ChannelMessage) {
	switch cm.TypeConstant {
	case socialmodels.ChannelMessage_TYPE_POST:
		container.Slug = cm.Slug
	case socialmodels.ChannelMessage_TYPE_REPLY:
		// TODO we need append something like comment id to parent message slug
		container.Slug = fetchRepliedMessage(cm.Id).Slug
	}
}

func prepareObjectType(container *NotificationContainer, cm *socialmodels.ChannelMessage) {
	switch cm.TypeConstant {
	case socialmodels.ChannelMessage_TYPE_POST:
		container.ObjectType = "status update"
	case socialmodels.ChannelMessage_TYPE_REPLY:
		container.ObjectType = "comment"
	}
}

// fetchUserContact gets user and account details with given account id
func fetchUserContact(accountId int64) (*UserContact, error) {
	a := socialmodels.NewAccount()
	if err := a.ById(accountId); err != nil {
		return nil, err
	}

	account, err := modelhelper.GetAccountById(a.OldId)
	if err != nil {
		if err == mgo.ErrNotFound {
			return nil, errors.New("old account not found")
		}

		return nil, err
	}

	user, err := modelhelper.GetUser(account.Profile.Nickname)
	if err != nil {
		if err == mgo.ErrNotFound {
			return nil, errors.New("user not found")
		}

		return nil, err
	}

	token, err := generateToken()
	if err != nil {
		return nil, err
	}

	uc := &UserContact{
		AccountId:     accountId,
		UserOldId:     user.ObjectId,
		Email:         user.Email,
		FirstName:     account.Profile.FirstName,
		LastName:      account.Profile.LastName,
		Username:      account.Profile.Nickname,
		Hash:          account.Profile.Hash,
		EmailSettings: user.EmailFrequency,
		Token:         token,
	}

	return uc, nil
}

func containsObject(nc *models.NotificationContent) bool {
	return nc.TypeConstant == models.NotificationContent_TYPE_LIKE ||
		nc.TypeConstant == models.NotificationContent_TYPE_MENTION ||
		nc.TypeConstant == models.NotificationContent_TYPE_COMMENT
}

func fetchContentBody(nc *models.NotificationContent, cm *socialmodels.ChannelMessage) string {

	switch nc.TypeConstant {
	case models.NotificationContent_TYPE_LIKE:
		return cm.Body
	case models.NotificationContent_TYPE_MENTION:
		return cm.Body
	case models.NotificationContent_TYPE_COMMENT:
		return fetchLastReplyBody(cm.Id)
	}

	return ""
}

func fetchLastReplyBody(targetId int64) string {
	mr := socialmodels.NewMessageReply()
	mr.MessageId = targetId
	query := socialmodels.NewQuery()
	query.Limit = 1
	messages, err := mr.List(query)
	if err != nil {
		return ""
	}

	if len(messages) == 0 {
		return ""
	}

	return messages[0].Body
}

func fetchRepliedMessage(replyId int64) *socialmodels.ChannelMessage {
	mr := socialmodels.NewMessageReply()
	mr.ReplyId = replyId

	parent, err := mr.FetchRepliedMessage()
	if err != nil {
		parent = socialmodels.NewChannelMessage()
	}

	return parent
}

func (n *Controller) SendMail(uc *UserContact, body, subject string) error {
	es := n.settings
	sg := sendgrid.NewSendGridClient(es.Username, es.Password)
	fullname := fmt.Sprintf("%s %s", uc.FirstName, uc.LastName)

	message := sendgrid.NewMail()
	message.AddTo(uc.Email)
	message.AddToName(fullname)
	message.SetSubject(subject)
	message.SetHTML(body)
	message.SetFrom(es.FromMail)
	message.SetFromName(es.FromName)

	if err := sg.Send(message); err != nil {
		return fmt.Errorf("an error occurred while sending notification email to %s", uc.Username)
	}
	n.log.Info("%s notified by email", uc.Username)

	return nil
}
<commit_msg>Notification: checkMailSettings saves daily mail information for user when it is enabled<commit_after>package emailnotifier

import (
	"errors"
	"fmt"
	"koding/db/mongodb/modelhelper"
	socialmodels "socialapi/models"
	"socialapi/workers/notification/models"

	"github.com/koding/logging"
	"github.com/koding/rabbitmq"
	"github.com/koding/worker"
	"github.com/robfig/cron"
	"github.com/sendgrid/sendgrid-go"
	"github.com/streadway/amqp"
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

const SCHEDULE = "0 0 0 * * *"

var cronJob *cron.Cron

var emailConfig = map[string]string{
	models.NotificationContent_TYPE_COMMENT: "comment",
	models.NotificationContent_TYPE_LIKE:    "likeActivities",
	models.NotificationContent_TYPE_FOLLOW:  "followActions",
	models.NotificationContent_TYPE_JOIN:    "groupJoined",
	models.NotificationContent_TYPE_LEAVE:   "groupLeft",
	models.NotificationContent_TYPE_MENTION: "mention",
}

type Action func(*Controller, []byte) error

type Controller struct {
	routes   map[string]Action
	log      logging.Logger
	rmqConn  *amqp.Connection
	settings *EmailSettings
}

type EmailSettings struct {
	Username string
	Password string
	FromName string
	FromMail string
}

func (n *Controller) DefaultErrHandler(delivery amqp.Delivery, err error) bool {
	n.log.Error("an error occured: %s", err)
	delivery.Ack(false)

	return false
}

func (n *Controller) HandleEvent(event string, data []byte) error {
	n.log.Debug("New Event Received %s", event)
	handler, ok := n.routes[event]
	if !ok {
		return worker.HandlerNotFoundErr
	}

	return handler(n, data)
}

func New(rmq *rabbitmq.RabbitMQ, log logging.Logger, es *EmailSettings) (*Controller, error) {
	rmqConn, err := rmq.Connect("NewEmailNotifierWorkerController")
	if err != nil {
		return nil, err
	}

	nwc := &Controller{
		log:      log,
		rmqConn:  rmqConn.Conn(),
		settings: es,
	}

	routes := map[string]Action{
		"notification.notification_created": (*Controller).SendInstantEmail,
		"notification.notification_updated": (*Controller).SendInstantEmail,
	}

	nwc.routes = routes

	nwc.initDailyEmailCron()

	return nwc, nil
}

func (n *EmailNotifierWorkerController) initDailyEmailCron() {

	cronJob = cron.New()
	cronJob.AddFunc(SCHEDULE, n.sendDailyMails)
	cronJob.Start()
}

func (n *Controller) SendInstantEmail(data []byte) error {
	channel, err := n.rmqConn.Channel()
	if err != nil {
		return errors.New("channel connection error")
	}
	defer channel.Close()

	notification := models.NewNotification()
	if err := notification.MapMessage(data); err != nil {
		return err
	}

	// fetch latest activity for checking actor
	activity, nc, err := notification.FetchLastActivity()
	if err != nil {
		return err
	}

	if !validNotification(activity, notification) {
		return nil
	}

	uc, err := fetchUserContact(notification.AccountId)
	if err != nil {
		return fmt.Errorf("an error occurred while fetching user contact: %s", err)
	}

	if !n.checkMailSettings(uc, activity, nc) {
		return nil
	}

	container, err := buildContainer(activity, nc, notification)
	if err != nil {
		return err
	}

	body, err := renderTemplate(uc, container)
	if err != nil {
		return fmt.Errorf("an error occurred while preparing notification email: %s", err)
	}
	subject := prepareSubject(container)

	if err := createToken(uc, nc, container.Token); err != nil {
		return err
	}

	return n.SendMail(uc, body, subject)
}

type UserContact struct {
	AccountId     int64
	UserOldId     bson.ObjectId
	Email         string
	FirstName     string
	LastName      string
	Username      string
	Hash          string
	Token         string
	EmailSettings map[string]bool
}

func validNotification(a *models.NotificationActivity, n *models.Notification) bool {
	// do not notify actor for her own action
	if a.ActorId == n.AccountId {
		return false
	}

	// do not notify user when notification is not yet activated
	return !n.ActivatedAt.IsZero()
}

func (n *EmailNotifierWorkerController) checkMailSettings(uc *UserContact,
	a *models.NotificationActivity, nc *models.NotificationContent) bool {
	// notifications are disabled
	if val := uc.EmailSettings["global"]; !val {
		return false
	}

	notificationEnabled := uc.EmailSettings[emailConfig[nc.TypeConstant]]
	// daily notifications are enabled
	if val := uc.EmailSettings["daily"]; val {
		if notificationEnabled {
			go n.saveDailyMail(uc.AccountId, a.Id)
		}

		return false
	}

	// get config
	return notificationEnabled
}

func buildContainer(accountId int64, a *models.NotificationActivity,
	nc *models.NotificationContent) (*NotificationContainer, error) {

	// if content type not valid return
	contentType, err := nc.GetContentType()
	if err != nil {
		return nil, err
	}

	container := &NotificationContainer{
		Activity:  a,
		Content:   nc,
		AccountId: accountId,
	}

	// if notification target is related with an object (comment/status update)
	if containsObject(nc) {
		target := socialmodels.NewChannelMessage()
		if err := target.ById(nc.TargetId); err != nil {
			return nil, fmt.Errorf("target message not found")
		}

		prepareGroup(container, target)
		prepareSlug(container, target)
		prepareObjectType(container, target)
		container.Message = fetchContentBody(nc, target)
		contentType.SetActorId(target.AccountId)
		contentType.SetListerId(accountId)
	}

	container.ActivityMessage = contentType.GetActivity()

	return container, nil
}

func prepareGroup(container *NotificationContainer, cm *socialmodels.ChannelMessage) {
	c := socialmodels.NewChannel()
	if err := c.ById(cm.InitialChannelId); err != nil {
		return
	}
	// TODO fix these Slug and Name
	container.Group = GroupContent{
		Slug: c.GroupName,
		Name: c.GroupName,
	}
}

func prepareSlug(container *NotificationContainer, cm *socialmodels.ChannelMessage) {
	switch cm.TypeConstant {
	case socialmodels.ChannelMessage_TYPE_POST:
		container.Slug = cm.Slug
	case socialmodels.ChannelMessage_TYPE_REPLY:
		// TODO we need append something like comment id to parent message slug
		container.Slug = fetchRepliedMessage(cm.Id).Slug
	}
}

func prepareObjectType(container *NotificationContainer, cm *socialmodels.ChannelMessage) {
	switch cm.TypeConstant {
	case socialmodels.ChannelMessage_TYPE_POST:
		container.ObjectType = "status update"
	case socialmodels.ChannelMessage_TYPE_REPLY:
		container.ObjectType = "comment"
	}
}

// fetchUserContact gets user and account details with given account id
func fetchUserContact(accountId int64) (*UserContact, error) {
	a := socialmodels.NewAccount()
	if err := a.ById(accountId); err != nil {
		return nil, err
	}

	account, err := modelhelper.GetAccountById(a.OldId)
	if err != nil {
		if err == mgo.ErrNotFound {
			return nil, errors.New("old account not found")
		}

		return nil, err
	}

	user, err := modelhelper.GetUser(account.Profile.Nickname)
	if err != nil {
		if err == mgo.ErrNotFound {
			return nil, errors.New("user not found")
		}

		return nil, err
	}

	token, err := generateToken()
	if err != nil {
		return nil, err
	}

	uc := &UserContact{
		AccountId:     accountId,
		UserOldId:     user.ObjectId,
		Email:         user.Email,
		FirstName:     account.Profile.FirstName,
		LastName:      account.Profile.LastName,
		Username:      account.Profile.Nickname,
		Hash:          account.Profile.Hash,
		EmailSettings: user.EmailFrequency,
		Token:         token,
	}

	return uc, nil
}

func containsObject(nc *models.NotificationContent) bool {
	return nc.TypeConstant == models.NotificationContent_TYPE_LIKE ||
		nc.TypeConstant == models.NotificationContent_TYPE_MENTION ||
		nc.TypeConstant == models.NotificationContent_TYPE_COMMENT
}

func fetchContentBody(nc *models.NotificationContent, cm *socialmodels.ChannelMessage) string {

	switch nc.TypeConstant {
	case models.NotificationContent_TYPE_LIKE:
		return cm.Body
	case models.NotificationContent_TYPE_MENTION:
		return cm.Body
	case models.NotificationContent_TYPE_COMMENT:
		return fetchLastReplyBody(cm.Id)
	}

	return ""
}

func fetchLastReplyBody(targetId int64) string {
	mr := socialmodels.NewMessageReply()
	mr.MessageId = targetId
	query := socialmodels.NewQuery()
	query.Limit = 1
	messages, err := mr.List(query)
	if err != nil {
		return ""
	}

	if len(messages) == 0 {
		return ""
	}

	return messages[0].Body
}

func fetchRepliedMessage(replyId int64) *socialmodels.ChannelMessage {
	mr := socialmodels.NewMessageReply()
	mr.ReplyId = replyId

	parent, err := mr.FetchRepliedMessage()
	if err != nil {
		parent = socialmodels.NewChannelMessage()
	}

	return parent
}

func (n *Controller) SendMail(uc *UserContact, body, subject string) error {
	es := n.settings
	sg := sendgrid.NewSendGridClient(es.Username, es.Password)
	fullname := fmt.Sprintf("%s %s", uc.FirstName, uc.LastName)

	message := sendgrid.NewMail()
	message.AddTo(uc.Email)
	message.AddToName(fullname)
	message.SetSubject(subject)
	message.SetHTML(body)
	message.SetFrom(es.FromMail)
	message.SetFromName(es.FromName)

	if err := sg.Send(message); err != nil {
		return fmt.Errorf("an error occurred while sending notification email to %s", uc.Username)
	}
	n.log.Info("%s notified by email", uc.Username)

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

var (
	noTime     time.Time
	timeFormat = time.RFC3339
)

var duration, _ = time.ParseDuration("25m")

var usage = `tmux-pomodoro
github.com/justincampbell/tmux-pomodoro

  pomodoro start   Start a timer for 25 minutes
  pomodoro status  Show the remaining time, or an exclamation point if done
  pomodoro clear   Clear the timer
`

type State struct {
	endTime time.Time
	now     time.Time
}

type Output struct {
	text       string
	returnCode int
}

func init() {
	flag.Parse()
}

func main() {
	state := State{
		endTime: readExistingTime(),
		now:     time.Now(),
	}

	args := flag.Args()
	var command string
	if len(args) == 0 {
		command = ""
	} else {
		command = args[0]
	}

	newState, output := parseCommand(state, command)

	if newState.endTime != state.endTime {
		writeTime(newState.endTime)
	}

	fmt.Println(output.text)

	if output.returnCode != 0 {
		os.Exit(output.returnCode)
	}
}

func refreshTmux() {
	_ = exec.Command("tmux", "refresh-client", "-S").Start()
}

func parseCommand(state State, command string) (newState State, output Output) {
	newState = state

	switch command {
	case "start":
		newState.endTime = state.now.Add(duration)
		output.text = "Timer started, 25 minutes remaining"
		killRunningBeepers()
		startBeeper()
		refreshTmux()
	case "status":
		if state.endTime == noTime {
			return
		}
		output.text = formatRemainingTime(state.endTime, state.now) + " 🍅 "
	case "clear":
		newState.endTime = noTime
		output.text = "Pomodoro cleared!"
		killRunningBeepers()
		refreshTmux()
	case "beep":
		<-time.NewTicker(duration).C
		_ = exec.Command("tmux", "display-message", "Pomodoro done, take a break!").Run()
		refreshTmux()
	case "":
		output.text = usage
	default:
		output.text = usage
		output.returnCode = 1
	}

	return
}

func startBeeper() {
	command := exec.Command("pomodoro", "beep")
	err := command.Start()
	if err != nil {
		log.Println(err)
	}

	bytes := []byte(strconv.Itoa(command.Process.Pid))
	err = ioutil.WriteFile(pidFilePath(), bytes, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

func killRunningBeepers() {
	bytes, err := ioutil.ReadFile(pidFilePath())
	if err != nil {
		return
	}
	pid, err := strconv.Atoi(string(bytes[:]))
	if err != nil {
		return
	}
	process, err := os.FindProcess(pid)
	if err != nil {
		return
	}
	_ = process.Kill()
}

func formatRemainingTime(existingTime time.Time, now time.Time) string {
	remaining := existingTime.Sub(now)
	remainingMinutes := remaining.Minutes()

	if remainingMinutes >= 0 {
		return strconv.FormatFloat(remainingMinutes, 'f', 0, 64)
	} else {
		return "❗"
	}
}

func writeTime(t time.Time) {
	var bytes []byte
	if t != noTime {
		bytes = []byte(t.Format(timeFormat))
	}
	err := ioutil.WriteFile(filePath(), bytes, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

func readExistingTime() time.Time {
	bytes, err := ioutil.ReadFile(filePath())
	if err != nil {
		return noTime
	}

	contents := string(bytes[:])
	contents = strings.TrimSpace(contents)

	result, err := time.Parse(timeFormat, contents)
	if err != nil {
		return noTime
	}

	return result
}

func filePath() string {
	return homeDir() + "/.pomodoro"
}

func pidFilePath() string {
	return homeDir() + "/.pomodoro.pid"
}

func homeDir() string {
	return os.Getenv("HOME")
}
<commit_msg>Use const for timeFormat<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

const timeFormat = time.RFC3339

var duration, _ = time.ParseDuration("25m")
var noTime time.Time
var usage = `tmux-pomodoro
github.com/justincampbell/tmux-pomodoro

  pomodoro start   Start a timer for 25 minutes
  pomodoro status  Show the remaining time, or an exclamation point if done
  pomodoro clear   Clear the timer
`

type State struct {
	endTime time.Time
	now     time.Time
}

type Output struct {
	text       string
	returnCode int
}

func init() {
	flag.Parse()
}

func main() {
	state := State{
		endTime: readExistingTime(),
		now:     time.Now(),
	}

	args := flag.Args()
	var command string
	if len(args) == 0 {
		command = ""
	} else {
		command = args[0]
	}

	newState, output := parseCommand(state, command)

	if newState.endTime != state.endTime {
		writeTime(newState.endTime)
	}

	fmt.Println(output.text)

	if output.returnCode != 0 {
		os.Exit(output.returnCode)
	}
}

func refreshTmux() {
	_ = exec.Command("tmux", "refresh-client", "-S").Start()
}

func parseCommand(state State, command string) (newState State, output Output) {
	newState = state

	switch command {
	case "start":
		newState.endTime = state.now.Add(duration)
		output.text = "Timer started, 25 minutes remaining"
		killRunningBeepers()
		startBeeper()
		refreshTmux()
	case "status":
		if state.endTime == noTime {
			return
		}
		output.text = formatRemainingTime(state.endTime, state.now) + " 🍅 "
	case "clear":
		newState.endTime = noTime
		output.text = "Pomodoro cleared!"
		killRunningBeepers()
		refreshTmux()
	case "beep":
		<-time.NewTicker(duration).C
		_ = exec.Command("tmux", "display-message", "Pomodoro done, take a break!").Run()
		refreshTmux()
	case "":
		output.text = usage
	default:
		output.text = usage
		output.returnCode = 1
	}

	return
}

func startBeeper() {
	command := exec.Command("pomodoro", "beep")
	err := command.Start()
	if err != nil {
		log.Println(err)
	}

	bytes := []byte(strconv.Itoa(command.Process.Pid))
	err = ioutil.WriteFile(pidFilePath(), bytes, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

func killRunningBeepers() {
	bytes, err := ioutil.ReadFile(pidFilePath())
	if err != nil {
		return
	}
	pid, err := strconv.Atoi(string(bytes[:]))
	if err != nil {
		return
	}
	process, err := os.FindProcess(pid)
	if err != nil {
		return
	}
	_ = process.Kill()
}

func formatRemainingTime(existingTime time.Time, now time.Time) string {
	remaining := existingTime.Sub(now)
	remainingMinutes := remaining.Minutes()

	if remainingMinutes >= 0 {
		return strconv.FormatFloat(remainingMinutes, 'f', 0, 64)
	} else {
		return "❗"
	}
}

func writeTime(t time.Time) {
	var bytes []byte
	if t != noTime {
		bytes = []byte(t.Format(timeFormat))
	}
	err := ioutil.WriteFile(filePath(), bytes, 0644)
	if err != nil {
		log.Fatal(err)
	}
}

func readExistingTime() time.Time {
	bytes, err := ioutil.ReadFile(filePath())
	if err != nil {
		return noTime
	}

	contents := string(bytes[:])
	contents = strings.TrimSpace(contents)

	result, err := time.Parse(timeFormat, contents)
	if err != nil {
		return noTime
	}

	return result
}

func filePath() string {
	return homeDir() + "/.pomodoro"
}

func pidFilePath() string {
	return homeDir() + "/.pomodoro.pid"
}

func homeDir() string {
	return os.Getenv("HOME")
}
<|endoftext|>