<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/minio/minio-go"
	"github.com/minio/minio-go/pkg/policy"
	"github.com/pborman/uuid"
	"github.com/rcrowley/go-metrics"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path"
	"syscall"
)

var (
	rootDir    string
	listenAddr string
	s3         S3
)

func init() {
	flag.StringVar(&rootDir, "root", "", "http root directory (default: .)")
	flag.StringVar(&listenAddr, "listen", "", "address to listen to (default: :8080)")
	flag.StringVar(&s3.endpoint, "s3-endpoint", "", "s3 endpoint")
	flag.StringVar(&s3.region, "s3-region", "", "s3 region")
	flag.StringVar(&s3.bucket, "s3-bucket", "", "s3 bucket")
	flag.StringVar(&s3.accessKey, "s3-access-key", "", "s3 access key")
	flag.StringVar(&s3.secretKey, "s3-secret-key", "", "s3 secret key")
}

func main() {
	flag.Parse()

	if len(rootDir) == 0 {
		listenAddr = os.Getenv("PUT_ROOT")
	}

	if len(rootDir) == 0 {
		rootDir = "."
	}

	if len(listenAddr) == 0 {
		listenAddr = os.Getenv("PUT_LISTEN")
	}

	if len(listenAddr) == 0 {
		listenAddr = ":8080"
	}

	if len(s3.endpoint) == 0 {
		s3.endpoint = os.Getenv("PUT_S3_ENDPOINT")
	}

	if len(s3.region) == 0 {
		s3.region = os.Getenv("PUT_S3_REGION")
	}

	if len(s3.bucket) == 0 {
		s3.bucket = os.Getenv("PUT_S3_BUCKET")
	}

	if len(s3.accessKey) == 0 {
		s3.accessKey = os.Getenv("PUT_S3_ACCESS_KEY")
	}

	if len(s3.secretKey) == 0 {
		s3.secretKey = os.Getenv("PUT_S3_SECRET_KEY")
	}
	l, err := listen(listenAddr)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	defer l.Close()

	metrics := NewMetrics()

	mux := http.NewServeMux()
	mux.Handle("/", NewRequestHandler(rootDir, &s3, metrics))
	mux.Handle("/health", NewHealthHandler(&s3))
	mux.Handle("/metrics", NewMetricsHandler(metrics))

	go http.Serve(l, mux)

	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
	<-sig
}

func listen(addr string) (net.Listener, error) {
	if len(addr) > 0 && addr[0] != '/' {
		return net.Listen("tcp", addr)
	}
	return net.Listen("unix", addr)
}

type S3 struct {
	minio     *minio.Client
	url       *url.URL
	endpoint  string
	bucket    string
	region    string
	secretKey string
	accessKey string
}

func (s3 *S3) Put(name, content_type string, body io.Reader) (string, error) {
	client, err := s3.client()

	if err != nil {
		return "", err
	}

	if _, err := client.PutObject(s3.bucket, name, body, content_type); err != nil {
		return "", err
	}
	s3.url.Path = path.Join(s3.bucket, name)
	return s3.url.String(), nil
}

func (s3 *S3) HealthCheck() error {
	client, err := s3.client()

	if err != nil {
		return err
	}
	ok, err := client.BucketExists(s3.bucket)

	if err != nil {
		return err
	}

	if !ok {
		fmt.Errorf("bucket unavailable: %s", s3.bucket)
	}
	return nil
}

func (s3 *S3) client() (client *minio.Client, err error) {
	if s3.minio != nil {
		return s3.minio, nil
	}
	s3.url, err = url.Parse(s3.endpoint)

	if err != nil {
		return nil, err
	}
	client, err = minio.New(s3.url.Host, s3.accessKey, s3.secretKey, s3.url.Scheme == "https")

	if err != nil {
		return nil, err
	}
	ok, err := client.BucketExists(s3.bucket)

	if err != nil {
		return nil, err
	}

	if !ok {
		if err := client.MakeBucket(s3.bucket, s3.region); err != nil {
			return nil, err
		}
	}

	if err := client.SetBucketPolicy(s3.bucket, "", policy.BucketPolicyReadOnly); err != nil {
		return nil, err
	}

	s3.minio = client
	return client, nil
}

type RequestHandler struct {
	s3         *S3
	metrics    Metrics
	fileServer http.Handler
}

func NewRequestHandler(rootDir string, s3 *S3, metrics Metrics) *RequestHandler {
	return &RequestHandler{
		s3:         s3,
		metrics:    metrics,
		fileServer: http.FileServer(http.Dir(rootDir)),
	}
}

func (rh *RequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		rh.fileServer.ServeHTTP(w, r)
		return
	}
	rh.metrics.Total.Inc(1)

	if r.Method != "POST" && r.Method != "PUT" {
		rh.metrics.Invalid.Inc(1)
		w.Header().Set("Allow", "GET, POST, PUT")
		http.Error(w, "method not allowed", 405)
		return
	}
	objectUrl, err := rh.s3.Put(uuid.New(), r.Header.Get("Content-Type"), r.Body)

	if err != nil {
		rh.metrics.Failed.Inc(1)
		fmt.Println("could not store object:", err)
		http.Error(w, "internal server error", 500)
		return
	}

	if _, err := fmt.Fprintln(w, objectUrl); err != nil {
		rh.metrics.Failed.Inc(1)
		fmt.Println("could respond with object url:", err)
		return
	}
	rh.metrics.Success.Inc(1)
}

type HealthHandler struct {
	s3 *S3
}

type HealthPayload struct {
	Status string `json:"status"`
}

func NewHealthHandler(s3 *S3) *HealthHandler {
	return &HealthHandler{s3: s3}
}

func (hh *HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	payload := &HealthPayload{Status: "ok"}

	if err := hh.s3.HealthCheck(); err != nil {
		payload.Status = fmt.Sprintf("critical: %s", err)
	}
	w.Header().Set("Content-Type", "application/json")

	if err := json.NewEncoder(w).Encode(payload); err != nil {
		fmt.Println("could not respond with health payload:", err)
	}
}

type Counter struct {
	metrics.Counter
}

func NewCounter() Counter {
	return Counter{metrics.NewCounter()}
}

func (c Counter) MarshalJSON() ([]byte, error) {
	return json.Marshal(c.Count())
}

type Metrics struct {
	Total   Counter `json:"total"`
	Invalid Counter `json:"invalid"`
	Failed  Counter `json:"failed"`
	Success Counter `json:"success"`
}

func NewMetrics() Metrics {
	return Metrics{
		Total:   NewCounter(),
		Invalid: NewCounter(),
		Failed:  NewCounter(),
		Success: NewCounter(),
	}
}

type MetricsHandler struct {
	metrics Metrics
}

func NewMetricsHandler(metrics Metrics) *MetricsHandler {
	return &MetricsHandler{metrics: metrics}
}

func (mh *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	if err := json.NewEncoder(w).Encode(mh.metrics); err != nil {
		fmt.Println("could not respond with metrics payload:", err)
	}
}
<commit_msg>add s3 metrics<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/minio/minio-go"
	"github.com/minio/minio-go/pkg/policy"
	"github.com/pborman/uuid"
	"github.com/rcrowley/go-metrics"
	"io"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path"
	"syscall"
)

var (
	rootDir    string
	listenAddr string
	s3         S3
)

func init() {
	flag.StringVar(&rootDir, "root", "", "http root directory (default: .)")
	flag.StringVar(&listenAddr, "listen", "", "address to listen to (default: :8080)")
	flag.StringVar(&s3.endpoint, "s3-endpoint", "", "s3 endpoint")
	flag.StringVar(&s3.region, "s3-region", "", "s3 region")
	flag.StringVar(&s3.bucket, "s3-bucket", "", "s3 bucket")
	flag.StringVar(&s3.accessKey, "s3-access-key", "", "s3 access key")
	flag.StringVar(&s3.secretKey, "s3-secret-key", "", "s3 secret key")
}

func main() {
	flag.Parse()

	if len(rootDir) == 0 {
		listenAddr = os.Getenv("PUT_ROOT")
	}

	if len(rootDir) == 0 {
		rootDir = "."
	}

	if len(listenAddr) == 0 {
		listenAddr = os.Getenv("PUT_LISTEN")
	}

	if len(listenAddr) == 0 {
		listenAddr = ":8080"
	}

	if len(s3.endpoint) == 0 {
		s3.endpoint = os.Getenv("PUT_S3_ENDPOINT")
	}

	if len(s3.region) == 0 {
		s3.region = os.Getenv("PUT_S3_REGION")
	}

	if len(s3.bucket) == 0 {
		s3.bucket = os.Getenv("PUT_S3_BUCKET")
	}

	if len(s3.accessKey) == 0 {
		s3.accessKey = os.Getenv("PUT_S3_ACCESS_KEY")
	}

	if len(s3.secretKey) == 0 {
		s3.secretKey = os.Getenv("PUT_S3_SECRET_KEY")
	}
	l, err := listen(listenAddr)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	defer l.Close()

	metrics := NewMetrics()

	mux := http.NewServeMux()
	mux.Handle("/", NewRequestHandler(rootDir, &s3, metrics))
	mux.Handle("/health", NewHealthHandler(&s3))
	mux.Handle("/metrics", NewMetricsHandler(metrics, &s3))

	go http.Serve(l, mux)

	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
	<-sig
}

func listen(addr string) (net.Listener, error) {
	if len(addr) > 0 && addr[0] != '/' {
		return net.Listen("tcp", addr)
	}
	return net.Listen("unix", addr)
}

type S3 struct {
	minio     *minio.Client
	url       *url.URL
	endpoint  string
	bucket    string
	region    string
	secretKey string
	accessKey string
}

func (s3 *S3) Put(name, content_type string, body io.Reader) (string, error) {
	client, err := s3.client()

	if err != nil {
		return "", err
	}

	if _, err := client.PutObject(s3.bucket, name, body, content_type); err != nil {
		return "", err
	}
	s3.url.Path = path.Join(s3.bucket, name)
	return s3.url.String(), nil
}

func (s3 *S3) HealthCheck() error {
	client, err := s3.client()

	if err != nil {
		return err
	}
	ok, err := client.BucketExists(s3.bucket)

	if err != nil {
		return err
	}

	if !ok {
		fmt.Errorf("bucket unavailable: %s", s3.bucket)
	}
	return nil
}

func (s3 *S3) Metrics() (count int64, size int64, err error) {
	client, err := s3.client()

	if err != nil {
		return count, size, err
	}

	for obj := range client.ListObjects(s3.bucket, "", false, make(chan struct{})) {
		count += 1
		size += obj.Size
	}
	return count, size, err
}

func (s3 *S3) client() (client *minio.Client, err error) {
	if s3.minio != nil {
		return s3.minio, nil
	}
	s3.url, err = url.Parse(s3.endpoint)

	if err != nil {
		return nil, err
	}
	client, err = minio.New(s3.url.Host, s3.accessKey, s3.secretKey, s3.url.Scheme == "https")

	if err != nil {
		return nil, err
	}
	ok, err := client.BucketExists(s3.bucket)

	if err != nil {
		return nil, err
	}

	if !ok {
		if err := client.MakeBucket(s3.bucket, s3.region); err != nil {
			return nil, err
		}
	}

	if err := client.SetBucketPolicy(s3.bucket, "", policy.BucketPolicyReadOnly); err != nil {
		return nil, err
	}

	s3.minio = client
	return client, nil
}

type RequestHandler struct {
	s3         *S3
	metrics    Metrics
	fileServer http.Handler
}

func NewRequestHandler(rootDir string, s3 *S3, metrics Metrics) *RequestHandler {
	return &RequestHandler{
		s3:         s3,
		metrics:    metrics,
		fileServer: http.FileServer(http.Dir(rootDir)),
	}
}

func (rh *RequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		rh.fileServer.ServeHTTP(w, r)
		return
	}
	rh.metrics.Total.Inc(1)

	if r.Method != "POST" && r.Method != "PUT" {
		rh.metrics.Invalid.Inc(1)
		w.Header().Set("Allow", "GET, POST, PUT")
		http.Error(w, "method not allowed", 405)
		return
	}
	objectUrl, err := rh.s3.Put(uuid.New(), r.Header.Get("Content-Type"), r.Body)

	if err != nil {
		rh.metrics.Failed.Inc(1)
		fmt.Println("could not store object:", err)
		http.Error(w, "internal server error", 500)
		return
	}

	if _, err := fmt.Fprintln(w, objectUrl); err != nil {
		rh.metrics.Failed.Inc(1)
		fmt.Println("could respond with object url:", err)
		return
	}
	rh.metrics.Success.Inc(1)
}

type HealthHandler struct {
	s3 *S3
}

type HealthPayload struct {
	Status string `json:"status"`
}

func NewHealthHandler(s3 *S3) *HealthHandler {
	return &HealthHandler{s3: s3}
}

func (hh *HealthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	payload := &HealthPayload{Status: "ok"}

	if err := hh.s3.HealthCheck(); err != nil {
		payload.Status = fmt.Sprintf("critical: %s", err)
	}
	w.Header().Set("Content-Type", "application/json")

	if err := json.NewEncoder(w).Encode(payload); err != nil {
		fmt.Println("could not respond with health payload:", err)
	}
}

type Metrics struct {
	Total   metrics.Counter
	Invalid metrics.Counter
	Failed  metrics.Counter
	Success metrics.Counter
}

func NewMetrics() Metrics {
	return Metrics{
		Total:   metrics.NewCounter(),
		Invalid: metrics.NewCounter(),
		Failed:  metrics.NewCounter(),
		Success: metrics.NewCounter(),
	}
}

type MetricsHandler struct {
	metrics Metrics
	s3      *S3
}

type MetricsPayload struct {
	RequestsTotal   int64 `json:"requests_total"`
	RequestsInvalid int64 `json:"requests_invalid"`
	RequestsFailed  int64 `json:"requests_failed"`
	RequestsSuccess int64 `json:"requests_success"`
	S3Objects       int64 `json:"s3_objects"`
	S3Usage         int64 `json:"s3_usage"`
}

func NewMetricsHandler(metrics Metrics, s3 *S3) *MetricsHandler {
	return &MetricsHandler{metrics: metrics, s3: s3}
}

func (mh *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s3Objects, s3Usage, _ := mh.s3.Metrics()

	payload := MetricsPayload{
		RequestsTotal:   mh.metrics.Total.Count(),
		RequestsInvalid: mh.metrics.Invalid.Count(),
		RequestsFailed:  mh.metrics.Failed.Count(),
		RequestsSuccess: mh.metrics.Success.Count(),
		S3Objects:       s3Objects,
		S3Usage:         s3Usage,
	}
	w.Header().Set("Content-Type", "application/json")

	if err := json.NewEncoder(w).Encode(payload); err != nil {
		fmt.Println("could not respond with metrics payload:", err)
	}
}
<|endoftext|>


<commit_before>package stats

func rank(data OrderInterface, r []int, stable bool) []int {
    idx := make([]int, data.Len())
    if stable {
        StableOrder_(data, idx)
    } else {
        Order_(data, idx)
    }

    if r == nil { r = make([]int, data.Len()) }
    order_to_rank(idx, r)
    return r
}

func order_to_rank(in, out []int) {
    for i := range in {
        out[in[i]] = i
    }
}

// Rank_ returns the sample ranks of the elements in the collection data.
// For example, conceptually, Rank_([]int{3, 5, 2, 6}) returns []int{1, 2, 0, 3}.
// Note that the elements of data are re-ordered in this function.
func Rank_(data OrderInterface, r []int) []int {
    return rank(data, r, false)
}

func StableRank_(data OrderInterface, r []int) []int {
    return rank(data, r, true)
}

func Rank(x []float64, r []int) []int {
    return Rank_(make_float64_index_slice(x), r)
}

func StableRank(x []float64, r []int) []int {
    return StableRank_(make_float64_index_slice(x), r)
}

func IntRank(x []int, r []int) []int {
    return Rank_(make_int_index_slice(x), r)
}

func IntStableRank(x []int, r []int) []int {
    return StableRank_(make_int_index_slice(x), r)
}

func StringRank(x []string, r []int) []int {
    return Rank_(make_string_index_slice(x), r)
}

func StringStableRank(x []string, r []int) []int {
    return StableRank_(make_string_index_slice(x), r)
}

<commit_msg>rank: minor tweak<commit_after>package stats

func rank(data OrderInterface, r []int, stable bool) []int {
    n := data.Len()

    idx := make([]int, n)
    if stable {
        StableOrder_(data, idx)
    } else {
        Order_(data, idx)
    }

    if r == nil {
        r = make([]int, n)
    }
    order_to_rank(idx, r)

    return r[0:n]
}

func order_to_rank(in, out []int) {
    for i := range in {
        out[in[i]] = i
    }
}

// Rank_ returns the sample ranks of the elements in the collection data.
// For example, conceptually, Rank_([]int{3, 5, 2, 6}) returns []int{1, 2, 0, 3}.
// Note that the elements of data are re-ordered in this function.
func Rank_(data OrderInterface, r []int) []int {
    return rank(data, r, false)
}

func StableRank_(data OrderInterface, r []int) []int {
    return rank(data, r, true)
}

func Rank(x []float64, r []int) []int {
    return Rank_(make_float64_index_slice(x), r)
}

func StableRank(x []float64, r []int) []int {
    return StableRank_(make_float64_index_slice(x), r)
}

func IntRank(x []int, r []int) []int {
    return Rank_(make_int_index_slice(x), r)
}

func IntStableRank(x []int, r []int) []int {
    return StableRank_(make_int_index_slice(x), r)
}

func StringRank(x []string, r []int) []int {
    return Rank_(make_string_index_slice(x), r)
}

func StringStableRank(x []string, r []int) []int {
    return StableRank_(make_string_index_slice(x), r)
}

<|endoftext|>


<commit_before>// ratt operates on a Debian .changes file of a just-built package, identifies
// all reverse-build-dependencies and rebuilds them with the .debs from the
// .changes file.
//
// The intended use-case is, for example, to package a new snapshot of a Go
// library and verify that the new version does not break any other Go
// libraries/binaries.
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strings"

	"pault.ag/go/debian/control"
	"pault.ag/go/debian/version"
)

type buildResult struct {
	src            string
	version        *version.Version
	err            error
	recheckErr     error
	logFile        string
	recheckLogFile string
}

var (
	logDir = flag.String("log_dir",
		"buildlogs",
		"Directory in which sbuild(1) logs for all reverse-build-dependencies are stored")

	dryRun = flag.Bool("dry_run",
		false,
		"Print sbuild command lines, but do not actually build the reverse-build-dependencies")

	sbuildDist = flag.String("sbuild_dist",
		"",
		"sbuild --dist= value (e.g. \"sid\"). Defaults to the Distribution: entry from the specified .changes file")

	dist = flag.String("dist",
		"",
		"Distribution to look up reverse-build-dependencies from. Defaults to the Distribution: entry from the specified .changes file")

	recheck = flag.Bool("recheck",
		false,
		"Rebuild without new changes to check if the failures are really related")

	listsPrefixRe = regexp.MustCompile(`/([^/]*_dists_.*)_InRelease$`)
)

func dependsOn(src control.SourceIndex, binaries map[string]bool) bool {
	buildDepends := src.GetBuildDepends()
	for _, possibility := range buildDepends.GetAllPossibilities() {
		if binaries[possibility.Name] {
			return true
		}
	}
	return false
}

func addReverseBuildDeps(sourcesPath string, binaries map[string]bool, rebuild map[string][]version.Version) error {
	log.Printf("Loading sources index %q\n", sourcesPath)
	catFile := exec.Command("/usr/lib/apt/apt-helper",
		"cat-file",
		sourcesPath)
	var s *bufio.Reader
	if lines, err := catFile.Output(); err == nil {
		s = bufio.NewReader(bytes.NewReader(lines))
	} else {
		// Fallback for older versions of apt-get. See
		// <20160111171230.GA17291@debian.org> for context.
		o, err := os.Open(sourcesPath)
		if err != nil {
			return err
		}
		defer o.Close()
		s = bufio.NewReader(o)
	}
	idx, err := control.ParseSourceIndex(s)
	if err != nil && err != io.EOF {
		return err
	}

	for _, src := range idx {
		if dependsOn(src, binaries) {
			rebuild[src.Package] = append(rebuild[src.Package], src.Version)
		}
	}

	return nil
}

func fallback(sourcesPaths []string, binaries []string) (map[string][]version.Version, error) {
	bins := make(map[string]bool)
	for _, bin := range binaries {
		bins[bin] = true
	}

	rebuild := make(map[string][]version.Version)
	for _, sourcesPath := range sourcesPaths {
		if err := addReverseBuildDeps(sourcesPath, bins, rebuild); err != nil {
			return nil, err
		}
	}
	return rebuild, nil
}

func reverseBuildDeps(packagesPaths, sourcesPaths []string, binaries []string) (map[string][]version.Version, error) {
	if _, err := exec.LookPath("dose-ceve"); err != nil {
		log.Printf("dose-ceve(1) not found. Please install the dose-extra package for more accurate results. Falling back to interpreting Sources directly")
		return fallback(sourcesPaths, binaries)
	}

	archOut, err := exec.Command("dpkg-architecture", "--query=DEB_BUILD_ARCH").Output()
	if err != nil {
		log.Fatal(err)
	}
	arch := strings.TrimSpace(string(archOut))

	// TODO: Cache this output based on the .changes file. dose-ceve takes quite a while.
	ceve := exec.Command(
		"dose-ceve",
		"--deb-native-arch="+arch,
		"-T", "debsrc",
		"-r", strings.Join(binaries, ","),
		"-G", "pkg")
	for _, packagesPath := range packagesPaths {
		ceve.Args = append(ceve.Args, "deb://"+packagesPath)
	}
	for _, sourcesPath := range sourcesPaths {
		ceve.Args = append(ceve.Args, "debsrc://"+sourcesPath)
	}
	ceve.Stderr = os.Stderr

	log.Printf("Figuring out reverse build dependencies using dose-ceve(1). This might take a while")
	out, err := ceve.Output()
	if err != nil {
		log.Printf("dose-ceve(1) failed (%v), falling back to interpreting Sources directly", err)
		return fallback(sourcesPaths, binaries)
	}
	var doseCeves []struct {
		Package string
		Version version.Version
	}
	r := bufio.NewReader(bytes.NewReader(out))
	if err := control.Unmarshal(&doseCeves, r); err != nil {
		return nil, err
	}
	rebuild := make(map[string][]version.Version)
	for _, doseCeve := range doseCeves {
		rebuild[doseCeve.Package] = append(rebuild[doseCeve.Package], doseCeve.Version)
	}
	return rebuild, nil
}

func main() {
	flag.Parse()

	if flag.NArg() == 0 {
		log.Fatalf("Usage: %s [options] <path-to-changes-file>...\n", os.Args[0])
	}

	var debs []string
	var binaries []string
	var changesDist string
	for i, changesPath := range flag.Args() {
		log.Printf("Loading changes file %q\n", changesPath)
		c, err := os.Open(changesPath)
		if err != nil {
			log.Fatal(err)
		}
		defer c.Close()
		changes, err := control.ParseChanges(bufio.NewReader(c), changesPath)
		if err != nil && err != io.EOF {
			log.Fatal(err)
		}

		log.Printf(" - %d binary packages: %s\n", len(changes.Binaries), strings.Join(changes.Binaries, " "))

		for _, file := range changes.Files {
			if filepath.Ext(file.Filename) == ".deb" {
				debs = append(debs, file.Filename)
			}
		}
		binaries = append(binaries, changes.Binaries...)

		if i == 0 {
			changesDist = changes.Distribution
		} else if changesDist != changes.Distribution {
			log.Printf("%s has different distrution, but we will only consider %s\n", changes.Filename, changesDist)
		}
	}

	log.Printf("Corresponding .debs (will be injected when building):\n")
	for _, deb := range debs {
		log.Printf("    %s\n", deb)
	}

	if strings.TrimSpace(*dist) == "" {
		*dist = changesDist
		// Rewrite unstable to sid, which apt-get indextargets (below) requires.
		if *dist == "unstable" {
			*dist = "sid"
		}
		log.Printf("Setting -dist=%s (from .changes file)\n", *dist)
	}

	var sourcesPaths []string
	var packagesPaths []string
	indexTargets := exec.Command("apt-get",
		"indextargets",
		"--format",
		"$(FILENAME)",
		"Codename: "+*dist,
		"ShortDesc: Sources")
	if lines, err := indexTargets.Output(); err == nil {
		for _, line := range strings.Split(string(lines), "\n") {
			trimmed := strings.TrimSpace(line)
			if trimmed != "" {
				sourcesPaths = append(sourcesPaths, line)
			}
		}
		binaryIndexTargets := exec.Command(
			"apt-get",
			"indextargets",
			"--format",
			"$(FILENAME)",
			"Codename: "+*dist,
			"ShortDesc: Packages")
		lines, err = binaryIndexTargets.Output()
		if err != nil {
			log.Fatal("Could not get packages files using %+v: %v", binaryIndexTargets.Args, err)
		}
		for _, line := range strings.Split(string(lines), "\n") {
			trimmed := strings.TrimSpace(line)
			if trimmed != "" {
				packagesPaths = append(packagesPaths, line)
			}
		}
	} else {
		// Fallback for older versions of apt-get. See
		// https://bugs.debian.org/801594 for context.
		releaseMatches, err := filepath.Glob("/var/lib/apt/lists/*_InRelease")
		if err != nil {
			log.Fatal(err)
		}
		for _, releasepath := range releaseMatches {
			r, err := os.Open(releasepath)
			if err != nil {
				log.Fatal(err)
			}
			defer r.Close()
			var inRelease struct {
				Suite string
			}
			if err := control.Unmarshal(&inRelease, bufio.NewReader(r)); err != nil {
				log.Fatal(err)
			}

			listsPrefix := listsPrefixRe.FindStringSubmatch(releasepath)
			if len(listsPrefix) != 2 {
				log.Fatalf("release file path %q does not match regexp %q\n", releasepath, listsPrefixRe)
			}
			sourceMatches, err := filepath.Glob(fmt.Sprintf("/var/lib/apt/lists/%s_*_Sources", listsPrefix[1]))
			if err != nil {
				log.Fatal(err)
			}
			sourcesPaths = append(sourcesPaths, sourceMatches...)
			packagesMatches, err := filepath.Glob(fmt.Sprintf("/var/lib/apt/lists/%s_*_Packages", listsPrefix[1]))
			if err != nil {
				log.Fatal(err)
			}
			packagesPaths = append(packagesPaths, packagesMatches...)
		}
	}

	if len(sourcesPaths) == 0 {
		log.Fatal("Could not find InRelease file for " + *dist + " . Are you missing " + *dist + " in your /etc/apt/sources.list?")
	}

	rebuild, err := reverseBuildDeps(packagesPaths, sourcesPaths, binaries)
	if err != nil {
		log.Fatal(err)
	}

	// TODO: add -recursive flag to also cover dependencies which are not DIRECT dependencies. use http://godoc.org/pault.ag/go/debian/control#OrderDSCForBuild (topsort) to build dependencies in the right order (saving CPU time).

	// TODO: what’s a good integration method for doing this in more setups, e.g. on a cloud provider or something? mapreri from #debian-qa says jenkins.debian.net is suitable.

	if strings.TrimSpace(*sbuildDist) == "" {
		*sbuildDist = changesDist
		log.Printf("Setting -sbuild_dist=%s (from .changes file)\n", *sbuildDist)
	}

	if err := os.MkdirAll(*logDir, 0755); err != nil {
		log.Fatal(err)
	}

	builder := &sbuild{
		dist:      *sbuildDist,
		logDir:    *logDir,
		dryRun:    *dryRun,
		extraDebs: debs,
	}
	cnt := 1
	buildresults := make(map[string](*buildResult))
	for src, versions := range rebuild {
		sort.Sort(sort.Reverse(version.Slice(versions)))
		newest := versions[0]
		log.Printf("Building package %d of %d: %s \n", cnt, len(rebuild), src)
		cnt++
		result := builder.build(src, &newest)
		if result.err != nil {
			log.Printf("building %s failed: %v\n", src, result.err)
		}
		buildresults[src] = result
	}

	if *dryRun {
		return
	}

	if *recheck {
		log.Printf("Begin to rebuild all failed packages without new changes\n")
		recheckBuilder := &sbuild{
			dist:   *sbuildDist,
			logDir: *logDir + "_recheck",
			dryRun: false,
		}
		if err := os.MkdirAll(recheckBuilder.logDir, 0755); err != nil {
			log.Fatal(err)
		}
		for src, result := range buildresults {
			if result.err == nil {
				continue
			}
			recheckResult := recheckBuilder.build(src, result.version)
			result.recheckErr = recheckResult.err
			result.recheckLogFile = recheckResult.logFile
			if recheckResult.err != nil {
				log.Printf("rebuilding %s without new packages failed: %v\n", src, recheckResult.err)
			}
		}
	}

	log.Printf("Build results:\n")
	// Print all successful builds first (not as interesting), then failed ones.
	for src, result := range buildresults {
		if result.err == nil {
			log.Printf("PASSED: %s\n", src)
		}
	}

	for src, result := range buildresults {
		if result.err != nil && result.recheckErr != nil {
			log.Printf("FAILED: %s, but maybe unrelated to new changes (see %s and %s)\n",
				src, result.logFile, result.recheckLogFile)
		}
	}
	for src, result := range buildresults {
		if result.err != nil && result.recheckErr == nil {
			log.Printf("FAILED: %s (see %s)\n", src, result.logFile)
		}
	}
}
<commit_msg>Bugfix: correctly pass relative paths to .deb files<commit_after>// ratt operates on a Debian .changes file of a just-built package, identifies
// all reverse-build-dependencies and rebuilds them with the .debs from the
// .changes file.
//
// The intended use-case is, for example, to package a new snapshot of a Go
// library and verify that the new version does not break any other Go
// libraries/binaries.
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strings"

	"pault.ag/go/debian/control"
	"pault.ag/go/debian/version"
)

type buildResult struct {
	src            string
	version        *version.Version
	err            error
	recheckErr     error
	logFile        string
	recheckLogFile string
}

var (
	logDir = flag.String("log_dir",
		"buildlogs",
		"Directory in which sbuild(1) logs for all reverse-build-dependencies are stored")

	dryRun = flag.Bool("dry_run",
		false,
		"Print sbuild command lines, but do not actually build the reverse-build-dependencies")

	sbuildDist = flag.String("sbuild_dist",
		"",
		"sbuild --dist= value (e.g. \"sid\"). Defaults to the Distribution: entry from the specified .changes file")

	dist = flag.String("dist",
		"",
		"Distribution to look up reverse-build-dependencies from. Defaults to the Distribution: entry from the specified .changes file")

	recheck = flag.Bool("recheck",
		false,
		"Rebuild without new changes to check if the failures are really related")

	listsPrefixRe = regexp.MustCompile(`/([^/]*_dists_.*)_InRelease$`)
)

func dependsOn(src control.SourceIndex, binaries map[string]bool) bool {
	buildDepends := src.GetBuildDepends()
	for _, possibility := range buildDepends.GetAllPossibilities() {
		if binaries[possibility.Name] {
			return true
		}
	}
	return false
}

func addReverseBuildDeps(sourcesPath string, binaries map[string]bool, rebuild map[string][]version.Version) error {
	log.Printf("Loading sources index %q\n", sourcesPath)
	catFile := exec.Command("/usr/lib/apt/apt-helper",
		"cat-file",
		sourcesPath)
	var s *bufio.Reader
	if lines, err := catFile.Output(); err == nil {
		s = bufio.NewReader(bytes.NewReader(lines))
	} else {
		// Fallback for older versions of apt-get. See
		// <20160111171230.GA17291@debian.org> for context.
		o, err := os.Open(sourcesPath)
		if err != nil {
			return err
		}
		defer o.Close()
		s = bufio.NewReader(o)
	}
	idx, err := control.ParseSourceIndex(s)
	if err != nil && err != io.EOF {
		return err
	}

	for _, src := range idx {
		if dependsOn(src, binaries) {
			rebuild[src.Package] = append(rebuild[src.Package], src.Version)
		}
	}

	return nil
}

func fallback(sourcesPaths []string, binaries []string) (map[string][]version.Version, error) {
	bins := make(map[string]bool)
	for _, bin := range binaries {
		bins[bin] = true
	}

	rebuild := make(map[string][]version.Version)
	for _, sourcesPath := range sourcesPaths {
		if err := addReverseBuildDeps(sourcesPath, bins, rebuild); err != nil {
			return nil, err
		}
	}
	return rebuild, nil
}

func reverseBuildDeps(packagesPaths, sourcesPaths []string, binaries []string) (map[string][]version.Version, error) {
	if _, err := exec.LookPath("dose-ceve"); err != nil {
		log.Printf("dose-ceve(1) not found. Please install the dose-extra package for more accurate results. Falling back to interpreting Sources directly")
		return fallback(sourcesPaths, binaries)
	}

	archOut, err := exec.Command("dpkg-architecture", "--query=DEB_BUILD_ARCH").Output()
	if err != nil {
		log.Fatal(err)
	}
	arch := strings.TrimSpace(string(archOut))

	// TODO: Cache this output based on the .changes file. dose-ceve takes quite a while.
	ceve := exec.Command(
		"dose-ceve",
		"--deb-native-arch="+arch,
		"-T", "debsrc",
		"-r", strings.Join(binaries, ","),
		"-G", "pkg")
	for _, packagesPath := range packagesPaths {
		ceve.Args = append(ceve.Args, "deb://"+packagesPath)
	}
	for _, sourcesPath := range sourcesPaths {
		ceve.Args = append(ceve.Args, "debsrc://"+sourcesPath)
	}
	ceve.Stderr = os.Stderr

	log.Printf("Figuring out reverse build dependencies using dose-ceve(1). This might take a while")
	out, err := ceve.Output()
	if err != nil {
		log.Printf("dose-ceve(1) failed (%v), falling back to interpreting Sources directly", err)
		return fallback(sourcesPaths, binaries)
	}
	var doseCeves []struct {
		Package string
		Version version.Version
	}
	r := bufio.NewReader(bytes.NewReader(out))
	if err := control.Unmarshal(&doseCeves, r); err != nil {
		return nil, err
	}
	rebuild := make(map[string][]version.Version)
	for _, doseCeve := range doseCeves {
		rebuild[doseCeve.Package] = append(rebuild[doseCeve.Package], doseCeve.Version)
	}
	return rebuild, nil
}

func main() {
	flag.Parse()

	if flag.NArg() == 0 {
		log.Fatalf("Usage: %s [options] <path-to-changes-file>...\n", os.Args[0])
	}

	var debs []string
	var binaries []string
	var changesDist string
	for i, changesPath := range flag.Args() {
		log.Printf("Loading changes file %q\n", changesPath)
		c, err := os.Open(changesPath)
		if err != nil {
			log.Fatal(err)
		}
		defer c.Close()
		changes, err := control.ParseChanges(bufio.NewReader(c), changesPath)
		if err != nil && err != io.EOF {
			log.Fatal(err)
		}

		log.Printf(" - %d binary packages: %s\n", len(changes.Binaries), strings.Join(changes.Binaries, " "))

		for _, file := range changes.Files {
			if filepath.Ext(file.Filename) == ".deb" {
				debs = append(debs, filepath.Join(filepath.Dir(changesPath), file.Filename))
			}
		}
		binaries = append(binaries, changes.Binaries...)

		if i == 0 {
			changesDist = changes.Distribution
		} else if changesDist != changes.Distribution {
			log.Printf("%s has different distrution, but we will only consider %s\n", changes.Filename, changesDist)
		}
	}

	log.Printf("Corresponding .debs (will be injected when building):\n")
	for _, deb := range debs {
		log.Printf("    %s\n", deb)
	}

	if strings.TrimSpace(*dist) == "" {
		*dist = changesDist
		// Rewrite unstable to sid, which apt-get indextargets (below) requires.
		if *dist == "unstable" {
			*dist = "sid"
		}
		log.Printf("Setting -dist=%s (from .changes file)\n", *dist)
	}

	var sourcesPaths []string
	var packagesPaths []string
	indexTargets := exec.Command("apt-get",
		"indextargets",
		"--format",
		"$(FILENAME)",
		"Codename: "+*dist,
		"ShortDesc: Sources")
	if lines, err := indexTargets.Output(); err == nil {
		for _, line := range strings.Split(string(lines), "\n") {
			trimmed := strings.TrimSpace(line)
			if trimmed != "" {
				sourcesPaths = append(sourcesPaths, line)
			}
		}
		binaryIndexTargets := exec.Command(
			"apt-get",
			"indextargets",
			"--format",
			"$(FILENAME)",
			"Codename: "+*dist,
			"ShortDesc: Packages")
		lines, err = binaryIndexTargets.Output()
		if err != nil {
			log.Fatal("Could not get packages files using %+v: %v", binaryIndexTargets.Args, err)
		}
		for _, line := range strings.Split(string(lines), "\n") {
			trimmed := strings.TrimSpace(line)
			if trimmed != "" {
				packagesPaths = append(packagesPaths, line)
			}
		}
	} else {
		// Fallback for older versions of apt-get. See
		// https://bugs.debian.org/801594 for context.
		releaseMatches, err := filepath.Glob("/var/lib/apt/lists/*_InRelease")
		if err != nil {
			log.Fatal(err)
		}
		for _, releasepath := range releaseMatches {
			r, err := os.Open(releasepath)
			if err != nil {
				log.Fatal(err)
			}
			defer r.Close()
			var inRelease struct {
				Suite string
			}
			if err := control.Unmarshal(&inRelease, bufio.NewReader(r)); err != nil {
				log.Fatal(err)
			}

			listsPrefix := listsPrefixRe.FindStringSubmatch(releasepath)
			if len(listsPrefix) != 2 {
				log.Fatalf("release file path %q does not match regexp %q\n", releasepath, listsPrefixRe)
			}
			sourceMatches, err := filepath.Glob(fmt.Sprintf("/var/lib/apt/lists/%s_*_Sources", listsPrefix[1]))
			if err != nil {
				log.Fatal(err)
			}
			sourcesPaths = append(sourcesPaths, sourceMatches...)
			packagesMatches, err := filepath.Glob(fmt.Sprintf("/var/lib/apt/lists/%s_*_Packages", listsPrefix[1]))
			if err != nil {
				log.Fatal(err)
			}
			packagesPaths = append(packagesPaths, packagesMatches...)
		}
	}

	if len(sourcesPaths) == 0 {
		log.Fatal("Could not find InRelease file for " + *dist + " . Are you missing " + *dist + " in your /etc/apt/sources.list?")
	}

	rebuild, err := reverseBuildDeps(packagesPaths, sourcesPaths, binaries)
	if err != nil {
		log.Fatal(err)
	}

	// TODO: add -recursive flag to also cover dependencies which are not DIRECT dependencies. use http://godoc.org/pault.ag/go/debian/control#OrderDSCForBuild (topsort) to build dependencies in the right order (saving CPU time).

	// TODO: what’s a good integration method for doing this in more setups, e.g. on a cloud provider or something? mapreri from #debian-qa says jenkins.debian.net is suitable.

	if strings.TrimSpace(*sbuildDist) == "" {
		*sbuildDist = changesDist
		log.Printf("Setting -sbuild_dist=%s (from .changes file)\n", *sbuildDist)
	}

	if err := os.MkdirAll(*logDir, 0755); err != nil {
		log.Fatal(err)
	}

	builder := &sbuild{
		dist:      *sbuildDist,
		logDir:    *logDir,
		dryRun:    *dryRun,
		extraDebs: debs,
	}
	cnt := 1
	buildresults := make(map[string](*buildResult))
	for src, versions := range rebuild {
		sort.Sort(sort.Reverse(version.Slice(versions)))
		newest := versions[0]
		log.Printf("Building package %d of %d: %s \n", cnt, len(rebuild), src)
		cnt++
		result := builder.build(src, &newest)
		if result.err != nil {
			log.Printf("building %s failed: %v\n", src, result.err)
		}
		buildresults[src] = result
	}

	if *dryRun {
		return
	}

	if *recheck {
		log.Printf("Begin to rebuild all failed packages without new changes\n")
		recheckBuilder := &sbuild{
			dist:   *sbuildDist,
			logDir: *logDir + "_recheck",
			dryRun: false,
		}
		if err := os.MkdirAll(recheckBuilder.logDir, 0755); err != nil {
			log.Fatal(err)
		}
		for src, result := range buildresults {
			if result.err == nil {
				continue
			}
			recheckResult := recheckBuilder.build(src, result.version)
			result.recheckErr = recheckResult.err
			result.recheckLogFile = recheckResult.logFile
			if recheckResult.err != nil {
				log.Printf("rebuilding %s without new packages failed: %v\n", src, recheckResult.err)
			}
		}
	}

	log.Printf("Build results:\n")
	// Print all successful builds first (not as interesting), then failed ones.
	for src, result := range buildresults {
		if result.err == nil {
			log.Printf("PASSED: %s\n", src)
		}
	}

	for src, result := range buildresults {
		if result.err != nil && result.recheckErr != nil {
			log.Printf("FAILED: %s, but maybe unrelated to new changes (see %s and %s)\n",
				src, result.logFile, result.recheckLogFile)
		}
	}
	for src, result := range buildresults {
		if result.err != nil && result.recheckErr == nil {
			log.Printf("FAILED: %s (see %s)\n", src, result.logFile)
		}
	}
}
<|endoftext|>


<commit_before>// vim: tabstop=2 shiftwidth=2

package main

import (
	"fmt"
	"os"
	"strings"
	"path"
	"io/ioutil"
	"net/mail"
	"crypto/sha256"
	"encoding/hex"
	//"github.com/codahale/blake2"
	"github.com/crooks/yamn/idlog"
	"github.com/crooks/yamn/keymgr"
	"github.com/luksen/maildir"
)

// poolRead returns a dynamic Mix of filenames from the outbound pool
func poolRead() (selectedPoolFiles []string, err error) {
	poolFiles, err := readDir(cfg.Files.Pooldir, "m")
	if err != nil {
		Warn.Printf("Unable to access pool: %s", err)
		return
	}
	poolSize := len(poolFiles)
	Trace.Printf("Pool contains %d messages.\n", poolSize)
	if poolSize < cfg.Pool.Size {
		// Pool isn't sufficiently populated
		Trace.Println("Pool insufficiently populated to trigger sending.",
			fmt.Sprintf("Require=%d, Got=%d", cfg.Pool.Size, poolSize))
		return
	}
	keys := randInts(len(poolFiles))
	numToSend := int((float32(poolSize) / 100.0) * float32(cfg.Pool.Rate))
	Trace.Printf("Processing %d pool messages.\n", poolSize)
	for n := 0; n < numToSend; n++ {
		mykey := keys[n]
		selectedPoolFiles = append(selectedPoolFiles, poolFiles[mykey])
	}
	return
}

// Delete a given file from the pool
func poolDelete(filename string) (err error) {
	// Delete a pool file
	err = os.Remove(path.Join(cfg.Files.Pooldir, filename))
	if err != nil {
		Error.Printf("Failed to remove %s from %s\n", filename, cfg.Files.Pooldir)
	}
	return
}

// processMail reads the Remailer's Maildir and processes the content
func processMail(public *keymgr.Pubring, secret *keymgr.Secring, id idlog.IDLog) (err error) {
	dir := maildir.Dir(cfg.Files.Maildir)
	// Get a list of Maildir keys from the directory
	keys, err := dir.Unseen()
	if err != nil {
		return
	}
	if len(keys) > 0 {
		Trace.Printf("Reading %d messages from %s\n", len(keys), cfg.Files.Maildir)
	}
	// Fetch headers for each Maildir key
	var head mail.Header
	for _, key := range keys {
		head, err = dir.Header(key)
		if err != nil {
			Warn.Printf("%s: Getting headers failed with: %s", key, err)
			continue
		}
		// The Subject determines if the message needs remailer-foo handling
		subject := strings.TrimSpace(strings.ToLower(head.Get("Subject")))
		if strings.HasPrefix(subject, "remailer-") {
			// It's a remailer-foo request
			err = remailerFoo(subject, head.Get("From"))
			if err != nil {
				Info.Println(err)
			}
		} else {
			// It's not a remailer-foo request so assume a remailer message
			var mailMsg *mail.Message
			mailMsg, err := dir.Message(key)
			if err != nil {
				Warn.Printf("%s: Reading message failed with: %s", key, err)
				continue
			}
			var msg []byte
			// Convert the armored Yamn message to its byte components
			msg, err = stripArmor(mailMsg.Body)
			if err != nil {
				Info.Println(err)
			}
			if msg == nil {
				Warn.Println("Dearmor returned zero bytes")
				continue
			}
			err = decodeMsg(msg, secret, id)
			if err != nil {
				Info.Println(err)
			}
			// Decide if we want to inject a dummy
			if randomInt(20) < 3 {
				err = dummy(public)
				if err != nil {
					Warn.Printf("Sending dummy failed: %s", err)
				}
			}
		} // End of remailer-foo or remailer message
		err = dir.Purge(key)
		if err != nil {
			Warn.Printf("Cannot delete mail: %s", err)
		}
	} // Maildir keys loop
	return
}

// processInpool is similar to processMail but reads the Inbound Pool
func processInpool(prefix string, secret *keymgr.Secring, id idlog.IDLog) {
	poolFiles, err := readDir(cfg.Files.Pooldir, prefix)
	if err != nil {
		Warn.Printf("Unable to access inbound pool: %s", err)
		return
	}
	poolSize := len(poolFiles)
	}
	processed := 0
	for _, f := range poolFiles {
		filename := path.Join(cfg.Files.Pooldir, f)
		msg := make([]byte, messageBytes)
		msg, err = ioutil.ReadFile(filename)
		if err != nil {
			Warn.Printf("Failed to read %s from pool: %s", f, err)
			continue
		}
		err = decodeMsg(msg, secret, id)
		if err != nil {
			Warn.Println(err)
		}
		poolDelete(f)
		processed++
	}
	if poolSize > 0 {
		Trace.Printf("Inbound pool processing complete. Read=%d, Decoded=%d",
			poolSize, processed)
	}
}

// inPoolWrite writes a raw Byte Yamn message to the Inbound pool
func inPoolWrite(yamnMsg []byte) (err error) {
	outfileName := randPoolFilename("i")
	err = ioutil.WriteFile(outfileName, yamnMsg, 0600)
	if err != nil {
	Warn.Printf("Failed to write raw message to inbound pool: %s", err)
		return
	}
	return
}

// outPoolWrite writes a raw Byte Yamn message to the Outbound pool.
// Oubound message files are prefixed with the recipient address.
func outPoolWrite(yamnMsg []byte, sendTo string) (err error) {
	digest := sha256.New()
	digest.Write([]byte(sendTo))
	digest.Write(yamnMsg)
	poolFileName := "m" + hex.EncodeToString(digest.Sum(nil))[:14]
	var f *os.File
	f, err = os.Create(path.Join(cfg.Files.Pooldir, poolFileName))
	if err != nil {
		Error.Printf("Unable to create pool file: %s", err)
		return
	}
	defer f.Close()
	// Write recipient
	paddedSendTo := []byte(sendTo + strings.Repeat("\x00", 80 - len(sendTo)))
	var numBytes int
	numBytes, err = f.Write(paddedSendTo)
	if err != nil {
		Error.Printf("Failed to write recipient to pool file: %s", err)
		return
	}
	if numBytes != 80 {
		Error.Println("Wrong byte count writing recipient to pool.",
			fmt.Sprintf("Expected=80, Got=%d", numBytes))
		return
	}
	_, err = f.Write(yamnMsg)
	if err != nil {
		Error.Printf("Failed to write payload to pool file: %s", err)
		return
	}
	return
}
<commit_msg>Remove an unwanted curly brace<commit_after>// vim: tabstop=2 shiftwidth=2

package main

import (
	"fmt"
	"os"
	"strings"
	"path"
	"io/ioutil"
	"net/mail"
	"crypto/sha256"
	"encoding/hex"
	//"github.com/codahale/blake2"
	"github.com/crooks/yamn/idlog"
	"github.com/crooks/yamn/keymgr"
	"github.com/luksen/maildir"
)

// poolRead returns a dynamic Mix of filenames from the outbound pool
func poolRead() (selectedPoolFiles []string, err error) {
	poolFiles, err := readDir(cfg.Files.Pooldir, "m")
	if err != nil {
		Warn.Printf("Unable to access pool: %s", err)
		return
	}
	poolSize := len(poolFiles)
	Trace.Printf("Pool contains %d messages.\n", poolSize)
	if poolSize < cfg.Pool.Size {
		// Pool isn't sufficiently populated
		Trace.Println("Pool insufficiently populated to trigger sending.",
			fmt.Sprintf("Require=%d, Got=%d", cfg.Pool.Size, poolSize))
		return
	}
	keys := randInts(len(poolFiles))
	numToSend := int((float32(poolSize) / 100.0) * float32(cfg.Pool.Rate))
	Trace.Printf("Processing %d pool messages.\n", poolSize)
	for n := 0; n < numToSend; n++ {
		mykey := keys[n]
		selectedPoolFiles = append(selectedPoolFiles, poolFiles[mykey])
	}
	return
}

// Delete a given file from the pool
func poolDelete(filename string) (err error) {
	// Delete a pool file
	err = os.Remove(path.Join(cfg.Files.Pooldir, filename))
	if err != nil {
		Error.Printf("Failed to remove %s from %s\n", filename, cfg.Files.Pooldir)
	}
	return
}

// processMail reads the Remailer's Maildir and processes the content
func processMail(public *keymgr.Pubring, secret *keymgr.Secring, id idlog.IDLog) (err error) {
	dir := maildir.Dir(cfg.Files.Maildir)
	// Get a list of Maildir keys from the directory
	keys, err := dir.Unseen()
	if err != nil {
		return
	}
	if len(keys) > 0 {
		Trace.Printf("Reading %d messages from %s\n", len(keys), cfg.Files.Maildir)
	}
	// Fetch headers for each Maildir key
	var head mail.Header
	for _, key := range keys {
		head, err = dir.Header(key)
		if err != nil {
			Warn.Printf("%s: Getting headers failed with: %s", key, err)
			continue
		}
		// The Subject determines if the message needs remailer-foo handling
		subject := strings.TrimSpace(strings.ToLower(head.Get("Subject")))
		if strings.HasPrefix(subject, "remailer-") {
			// It's a remailer-foo request
			err = remailerFoo(subject, head.Get("From"))
			if err != nil {
				Info.Println(err)
			}
		} else {
			// It's not a remailer-foo request so assume a remailer message
			var mailMsg *mail.Message
			mailMsg, err := dir.Message(key)
			if err != nil {
				Warn.Printf("%s: Reading message failed with: %s", key, err)
				continue
			}
			var msg []byte
			// Convert the armored Yamn message to its byte components
			msg, err = stripArmor(mailMsg.Body)
			if err != nil {
				Info.Println(err)
			}
			if msg == nil {
				Warn.Println("Dearmor returned zero bytes")
				continue
			}
			err = decodeMsg(msg, secret, id)
			if err != nil {
				Info.Println(err)
			}
			// Decide if we want to inject a dummy
			if randomInt(20) < 3 {
				err = dummy(public)
				if err != nil {
					Warn.Printf("Sending dummy failed: %s", err)
				}
			}
		} // End of remailer-foo or remailer message
		err = dir.Purge(key)
		if err != nil {
			Warn.Printf("Cannot delete mail: %s", err)
		}
	} // Maildir keys loop
	return
}

// processInpool is similar to processMail but reads the Inbound Pool
func processInpool(prefix string, secret *keymgr.Secring, id idlog.IDLog) {
	poolFiles, err := readDir(cfg.Files.Pooldir, prefix)
	if err != nil {
		Warn.Printf("Unable to access inbound pool: %s", err)
		return
	}
	poolSize := len(poolFiles)
	processed := 0
	for _, f := range poolFiles {
		filename := path.Join(cfg.Files.Pooldir, f)
		msg := make([]byte, messageBytes)
		msg, err = ioutil.ReadFile(filename)
		if err != nil {
			Warn.Printf("Failed to read %s from pool: %s", f, err)
			continue
		}
		err = decodeMsg(msg, secret, id)
		if err != nil {
			Warn.Println(err)
		}
		poolDelete(f)
		processed++
	}
	if poolSize > 0 {
		Trace.Printf("Inbound pool processing complete. Read=%d, Decoded=%d",
			poolSize, processed)
	}
}

// inPoolWrite writes a raw Byte Yamn message to the Inbound pool
func inPoolWrite(yamnMsg []byte) (err error) {
	outfileName := randPoolFilename("i")
	err = ioutil.WriteFile(outfileName, yamnMsg, 0600)
	if err != nil {
	Warn.Printf("Failed to write raw message to inbound pool: %s", err)
		return
	}
	return
}

// outPoolWrite writes a raw Byte Yamn message to the Outbound pool.
// Oubound message files are prefixed with the recipient address.
func outPoolWrite(yamnMsg []byte, sendTo string) (err error) {
	digest := sha256.New()
	digest.Write([]byte(sendTo))
	digest.Write(yamnMsg)
	poolFileName := "m" + hex.EncodeToString(digest.Sum(nil))[:14]
	var f *os.File
	f, err = os.Create(path.Join(cfg.Files.Pooldir, poolFileName))
	if err != nil {
		Error.Printf("Unable to create pool file: %s", err)
		return
	}
	defer f.Close()
	// Write recipient
	paddedSendTo := []byte(sendTo + strings.Repeat("\x00", 80 - len(sendTo)))
	var numBytes int
	numBytes, err = f.Write(paddedSendTo)
	if err != nil {
		Error.Printf("Failed to write recipient to pool file: %s", err)
		return
	}
	if numBytes != 80 {
		Error.Println("Wrong byte count writing recipient to pool.",
			fmt.Sprintf("Expected=80, Got=%d", numBytes))
		return
	}
	_, err = f.Write(yamnMsg)
	if err != nil {
		Error.Printf("Failed to write payload to pool file: %s", err)
		return
	}
	return
}
<|endoftext|>


<commit_before>package gopool

import (
	"context"

	"log"
	"sync"
	"time"
)

// StdLogger is used to log error messages.
type Logger interface {
	Debug(v ...interface{})
	Info(v ...interface{})
	Crit(v ...interface{})
	Fatal(v ...interface{})
}

var PoolLogger GoPoolLogger

type GoPoolLogger struct {
}

func (gpl *GoPoolLogger) Debug(v ...interface{}) {
	log.Println(v)
}
func (gpl *GoPoolLogger) Info(v ...interface{}) {
	log.Println(v)
}
func (gpl *GoPoolLogger) Crit(v ...interface{}) {
	log.Println(v)
}
func (gpl *GoPoolLogger) Fatal(v ...interface{}) {
	log.Println(v)
}

type goPool struct {
	wg        sync.WaitGroup
	cancelctx context.Context
	canclFunc context.CancelFunc
}

//this is a function which takes a context and exits when the context is done.
type GoPoolFunc func(ctx context.Context, args ...interface{}) error

func NewGoPool(ctx context.Context) *goPool {
	cancelctx, canclFunc := context.WithCancel(ctx)
	return &goPool{
		cancelctx: cancelctx,
		canclFunc: canclFunc,
	}
}

func (gp *goPool) Context() context.Context {
	return gp.cancelctx
}

var (
	PanicHandler = func(err interface{}, method string) {

		switch err.(type) {

		case error:
			PoolLogger.Info("RECOVERED FROM PANIC", "error is ", err.(error).Error(), "details", method)
		case string:
			PoolLogger.Info("RECOVERED FROM PANIC", "error is ", err.(string), "details", method)
		default:
			PoolLogger.Info("RECOVERED FROM PANIC")
		}

	}
)

// waitTimeout waits for the waitgroup for the specified max timeout.
// Returns true if waiting timed out.
func (gp *goPool) waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
	c := make(chan struct{})
	go func() {
		defer close(c)
		wg.Wait()
	}()
	select {
	case <-c:
		return false // completed normally
	case <-time.After(timeout):
		return true // timed out
	}
}

func (gp *goPool) ShutDown(waitforver bool, timeout time.Duration) {
	gp.canclFunc()
	if waitforver {
		gp.wg.Wait()
	} else {
		gp.waitTimeout(&gp.wg, timeout)
	}

}

func (gp *goPool) AddJob(method string, fn GoPoolFunc, args ...interface{}) {
	gp.wg.Add(1)

	go func() {
		defer func() {
			gp.wg.Done()
			handler := PanicHandler
			if handler != nil {
				if err := recover(); err != nil {
					handler(err, method)
				}
			}
		}()
		fn(gp.Context(), args...)
	}()
}
<commit_msg>expose the go pool struct<commit_after>package gopool

import (
	"context"

	"log"
	"sync"
	"time"
)

// StdLogger is used to log error messages.
type Logger interface {
	Debug(v ...interface{})
	Info(v ...interface{})
	Crit(v ...interface{})
	Fatal(v ...interface{})
}

var PoolLogger GoPoolLogger

type GoPoolLogger struct {
}

func (gpl *GoPoolLogger) Debug(v ...interface{}) {
	log.Println(v)
}
func (gpl *GoPoolLogger) Info(v ...interface{}) {
	log.Println(v)
}
func (gpl *GoPoolLogger) Crit(v ...interface{}) {
	log.Println(v)
}
func (gpl *GoPoolLogger) Fatal(v ...interface{}) {
	log.Println(v)
}

type GoPool struct {
	wg        sync.WaitGroup
	cancelctx context.Context
	canclFunc context.CancelFunc
}

//this is a function which takes a context and exits when the context is done.
type GoPoolFunc func(ctx context.Context, args ...interface{}) error

func NewGoPool(ctx context.Context) *GoPool {
	cancelctx, canclFunc := context.WithCancel(ctx)
	return &GoPool{
		cancelctx: cancelctx,
		canclFunc: canclFunc,
	}
}

func (gp *GoPool) Context() context.Context {
	return gp.cancelctx
}

var (
	PanicHandler = func(err interface{}, method string) {

		switch err.(type) {

		case error:
			PoolLogger.Info("RECOVERED FROM PANIC", "error is ", err.(error).Error(), "details", method)
		case string:
			PoolLogger.Info("RECOVERED FROM PANIC", "error is ", err.(string), "details", method)
		default:
			PoolLogger.Info("RECOVERED FROM PANIC")
		}

	}
)

// waitTimeout waits for the waitgroup for the specified max timeout.
// Returns true if waiting timed out.
func (gp *GoPool) waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
	c := make(chan struct{})
	go func() {
		defer close(c)
		wg.Wait()
	}()
	select {
	case <-c:
		return false // completed normally
	case <-time.After(timeout):
		return true // timed out
	}
}

func (gp *GoPool) ShutDown(waitforver bool, timeout time.Duration) {
	gp.canclFunc()
	if waitforver {
		gp.wg.Wait()
	} else {
		gp.waitTimeout(&gp.wg, timeout)
	}

}

func (gp *GoPool) AddJob(method string, fn GoPoolFunc, args ...interface{}) {
	gp.wg.Add(1)

	go func() {
		defer func() {
			gp.wg.Done()
			handler := PanicHandler
			if handler != nil {
				if err := recover(); err != nil {
					handler(err, method)
				}
			}
		}()
		fn(gp.Context(), args...)
	}()
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"strconv"
)

func PostByID(id uint64) (*Post, error) {
	p := new(Post)
	err := Bucket.Get("post@"+strconv.FormatUint(id, 10), p)
	if err != nil {
		return nil, err
	}
	return p, nil
}

func init() {
	http.HandleFunc("/p/", func(w http.ResponseWriter, r *http.Request) {
		id := r.URL.Path[len("/p/"):]
		id_, err := strconv.ParseUint(id, 10, 64)
		if err != nil {
			http.NotFound(w, r)
			return
		}

		post, err := PostByID(id_)
		if err != nil {
			http.NotFound(w, r)
			return
		}
		topicID := strconv.FormatUint(post.Topic, 10)

		var v struct {
			Rows []struct {
				ID string `json:"id"`
			}
		}
		err = Bucket.ViewCustom(DDocName, ViewTopicPost, map[string]interface{}{
			"startkey": []interface{}{topicID},
			"endkey":   []interface{}{topicID, map[string]interface{}{}},
		}, &v)
		if err != nil {
			log.Println(r.URL, err)
			http.Error(w, "database error", http.StatusInternalServerError)
			return
		}
		postID := "post@" + id

		for i, p := range v.Rows {
			if p.ID == postID {
				start := (i / 50) * 50 // TODO: make configurable
				if start == 0 {
					http.Redirect(w, r, "/t/"+topicID+"#"+id, http.StatusMovedPermanently)
				} else {
					http.Redirect(w, r, "/t/"+topicID+"?start="+strconv.Itoa(start)+"#"+id, http.StatusMovedPermanently)
				}
				return
			}
		}

		http.Error(w, "unknown error", http.StatusInternalServerError)
	})
}
<commit_msg>post creation api<commit_after>package main

import (
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
)

func PostByID(id uint64) (*Post, error) {
	p := new(Post)
	err := Bucket.Get("post@"+strconv.FormatUint(id, 10), p)
	if err != nil {
		return nil, err
	}
	return p, nil
}

var postLock sync.Mutex

func NewPost(author *User, replyTo *Post, title, content string, tags []string) (uint64, error) {
	if title == "" {
		title = replyTo.Title
		if !strings.HasPrefix(title, "Re: ") {
			title = "Re: " + title
		}
	}

	// easier and cheaper than trying to do compare-and-set on topics.
	postLock.Lock()
	defer postLock.Unlock()

	topic, err := TopicByID(replyTo.Topic)
	if err != nil {
		return 0, err
	}

	id, err := Bucket.Incr("incr/postID", 1, 1, 0)
	if err != nil {
		return 0, err
	}

	now := time.Now().UTC()

	topic.LastAuthor = author.ID
	topic.LastMod = now
	topic.LastPost = now
	topic.LastTitle = title
	topic.Replies++

	post := &Post{
		ID:      id,
		Topic:   topic.ID,
		ReplyTo: replyTo.ID,
		Author:  author.ID,
		LastMod: now,
		Created: now,
		Title:   title,
		Content: content,
		Tags:    tags,
	}

	err = Bucket.Set("post@"+strconv.FormatUint(id, 10), 0, post)
	if err != nil {
		return 0, err
	}
	err = Bucket.Set("topic@"+strconv.FormatUint(topic.ID, 10), 0, topic)
	if err != nil {
		return 0, err
	}

	return id, nil
}

func NewTopic(author *User, forum *Forum, title, content string, tags []string) (uint64, error) {
	tid, err := Bucket.Incr("incr/topicID", 1, 1, 0)
	if err != nil {
		return 0, err
	}

	pid, err := Bucket.Incr("incr/postID", 1, 1, 0)
	if err != nil {
		return 0, err
	}

	now := time.Now().UTC()

	topic := &Topic{
		ID:          tid,
		Forum:       forum.ID,
		Created:     now,
		LastPost:    now,
		LastMod:     now,
		FirstTitle:  title,
		LastTitle:   title,
		FirstAuthor: author.ID,
		LastAuthor:  author.ID,
	}

	post := &Post{
		ID:      pid,
		Topic:   tid,
		ReplyTo: 0,
		Author:  author.ID,
		LastMod: now,
		Created: now,
		Title:   title,
		Content: content,
		Tags:    tags,
	}

	err = Bucket.Set("post@"+strconv.FormatUint(pid, 10), 0, post)
	if err != nil {
		return 0, err
	}
	err = Bucket.Set("topic@"+strconv.FormatUint(tid, 10), 0, topic)
	if err != nil {
		return 0, err
	}

	return tid, nil
}

func init() {
	http.HandleFunc("/p/", func(w http.ResponseWriter, r *http.Request) {
		id := r.URL.Path[len("/p/"):]
		id_, err := strconv.ParseUint(id, 10, 64)
		if err != nil {
			http.NotFound(w, r)
			return
		}

		post, err := PostByID(id_)
		if err != nil {
			http.NotFound(w, r)
			return
		}
		topicID := strconv.FormatUint(post.Topic, 10)

		var v struct {
			Rows []struct {
				ID string `json:"id"`
			}
		}
		err = Bucket.ViewCustom(DDocName, ViewTopicPost, map[string]interface{}{
			"startkey": []interface{}{topicID},
			"endkey":   []interface{}{topicID, map[string]interface{}{}},
		}, &v)
		if err != nil {
			log.Println(r.URL, err)
			http.Error(w, "database error", http.StatusInternalServerError)
			return
		}
		postID := "post@" + id

		for i, p := range v.Rows {
			if p.ID == postID {
				start := (i / 50) * 50 // TODO: make configurable
				if start == 0 {
					http.Redirect(w, r, "/t/"+topicID+"#"+id, http.StatusMovedPermanently)
				} else {
					http.Redirect(w, r, "/t/"+topicID+"?start="+strconv.Itoa(start)+"#"+id, http.StatusMovedPermanently)
				}
				return
			}
		}

		http.Error(w, "unknown error", http.StatusInternalServerError)
	})
}
<|endoftext|>


<commit_before>package geom

import (
	"fmt"
	"math"
)

type Rect struct {
	X float64
	Y float64
	W float64
	H float64
}

func MakeRect(p Point, s Size) Rect {
	return Rect{
		X: p.X,
		Y: p.Y,
		W: s.W,
		H: s.H,
	}
}

func (r *Rect) SetOrigin(p Point) {
	r.X = p.X
	r.Y = p.Y
}

func (r *Rect) SetSize(s Size) {
	r.W = s.W
	r.H = s.H
}

func (r *Rect) Grow(k float64) {
	r.X -= k
	r.Y -= k

	k += k

	r.W += k
	r.H += k
}

func (r Rect) Origin() Point {
	return Point{
		X: r.X,
		Y: r.Y,
	}
}

func (r Rect) Center() Point {
	return Point{
		X: r.X + (r.W / 2),
		Y: r.Y + (r.H / 2),
	}
}

func (r Rect) Tip() Point {
	return Point{
		X: r.X + r.W,
		Y: r.Y + r.H,
	}
}

func (r Rect) Size() Size {
	return Size{
		W: r.W,
		H: r.H,
	}
}

func (r Rect) Abs() Rect {
	if r.W < 0 {
		r.X += r.W
		r.W = -r.W
	}

	if r.H < 0 {
		r.Y += r.H
		r.H = -r.H
	}

	return r
}

func (r0 Rect) Intersect(r1 Rect) Rect {
	r0 = r0.Abs()
	r1 = r1.Abs()

	x1 := math.Max(r0.X, r1.X)
	y1 := math.Max(r0.Y, r1.Y)

	x2 := math.Min(r0.X+r0.W, r1.X+r1.W)
	y2 := math.Min(r0.Y+r0.H, r1.Y+r1.H)

	if x1 > x2 || y1 > y2 {
		return Rect{}
	}

	return Rect{
		X: x1,
		Y: y1,
		W: x2 - x1,
		H: y2 - y1,
	}
}

func (r0 Rect) Merge(r1 Rect) Rect {
	r0 = r0.Abs()
	r1 = r1.Abs()

	x1 := math.Min(r0.X, r1.X)
	y1 := math.Min(r0.Y, r1.Y)

	x2 := math.Max(r0.X+r0.W, r1.X+r1.W)
	y2 := math.Max(r0.Y+r0.H, r1.Y+r1.H)

	return Rect{
		X: x1,
		Y: y1,
		W: x2 - x1,
		H: y2 - y1,
	}
}

func (r Rect) Zero() bool {
	return r.Origin().Zero() && r.Size().Zero()
}

func (r Rect) Empty() bool {
	return r.Size().Empty()
}

func (r Rect) Area() float64 {
	return r.Size().Area()
}

func (r Rect) ContainsPoint(p Point) bool {
	return r.X <= p.X && (r.X+r.W) > p.X && r.Y <= p.Y && (r.Y+r.H) > p.Y
}

func (r0 Rect) ContainsRect(r1 Rect) bool {
	return r0.X <= r1.X && (r0.X+r0.W) >= (r1.X+r1.W) && r0.Y <= r1.Y && (r0.Y+r0.H) >= (r1.Y+r1.H)
}

func (r Rect) String() string {
	return fmt.Sprintf("{ %.6g, %.6g, %.6g, %.6g }", r.X, r.Y, r.W, r.H)
}

func (r Rect) Path() Path {
	return AppendRect(MakePath(5), r)
}

func MergeRect(list []Rect, rect Rect) []Rect {
	// First we check if one of the rectangles in the slice contains the one we
	// are trying to add. If that's the case there's nothing we need to do, the
	// rectangle is already contained in the slice.
	for _, r := range list {
		if r.ContainsRect(rect) {
			return list
		}
	}

	// Look for rectangles that may intersect with the one we are trying to add,
	// if one is found we merge them and rebuild the slice in case it created
	// new intersections.
	for i, r := range list {
		if !r.Intersect(rect).Empty() {
			s := append(make([]Rect, 0, len(list)), r.Merge(rect))

			for j, r := range list {
				if i != j {
					s = MergeRect(s, r)
				}
			}

			return s
		}
	}

	// TOOD: more clever merging policies?
	// -----------------------------------
	//
	// - don't merge if the resulting area is greater than keeping rectangles
	// - look for the merge that creates the smallest area
	// - optimize memory allocation, only recreate the list if needed
	// - sort the list by areas in descending order to improve the fast path
	//
	// While these merges could greatly reduce the areas being redrawn they
	// could also be expansive to compute for potentially little gain due to
	// the way widgets are laid out (parents contain children).
	// It is also possible that generating more redrawing calls may be slower
	// to render than asking the window system to redraw a bigger area once.

	// If none of the merging policies have matched we simply append it to the
	// slice.
	return append(list, rect)
}
<commit_msg>update rect documentation<commit_after>package geom

import (
	"fmt"
	"math"
)

// The Rect type represents a 2D rectangle made of the coordinates of an origin
// point, and width and height dimensions.
type Rect struct {
	X float64
	Y float64
	W float64
	H float64
}

// MakeRect constructs a Rect value from an origin and dimensions provided as a
// Point and Size value..
func MakeRect(p Point, s Size) Rect {
	return Rect{
		X: p.X,
		Y: p.Y,
		W: s.W,
		H: s.H,
	}
}

// SetOrigin changes the origin of the rectangle instance it is called on to the
// Point passed as argument.
func (r *Rect) SetOrigin(p Point) {
	r.X = p.X
	r.Y = p.Y
}

// SetSize changes the dimensions of the rectangle instance it is called on to
// the Size passed as argument.
func (r *Rect) SetSize(s Size) {
	r.W = s.W
	r.H = s.H
}

// The Grow method modifies the origin and dimensions of the rectangle it is
// called on by the value passed as argument.
func (r *Rect) Grow(k float64) {
	r.X -= k
	r.Y -= k

	k += k

	r.W += k
	r.H += k
}

// Origin returns the current origin of the rectangle as a Point value.
func (r Rect) Origin() Point {
	return Point{
		X: r.X,
		Y: r.Y,
	}
}

// Center returns the current center of the rectangle as a Point value.
func (r Rect) Center() Point {
	return Point{
		X: r.X + (r.W / 2),
		Y: r.Y + (r.H / 2),
	}
}

// Tip returns the 'bottom-right' point of the rectangle as a Point value.
func (r Rect) Tip() Point {
	return Point{
		X: r.X + r.W,
		Y: r.Y + r.H,
	}
}

// Size returns the dimensions of the rectangle as a Size value.
func (r Rect) Size() Size {
	return Size{
		W: r.W,
		H: r.H,
	}
}

// The Abs method transforms returns a rectangle equivalent to the one it is
// called on where negative components of the dimensions have been converted
// to positive values.
func (r Rect) Abs() Rect {
	if r.W < 0 {
		r.X += r.W
		r.W = -r.W
	}

	if r.H < 0 {
		r.Y += r.H
		r.H = -r.H
	}

	return r
}

// The Intersect method computes the intersection of the rectangle it's called
// on with the one passed as argument, returning the result.
//
// The intersection is the area that is covered by both rectangles, which may be
// a zero-value if the two don't overlap.
func (r0 Rect) Intersect(r1 Rect) Rect {
	r0 = r0.Abs()
	r1 = r1.Abs()

	x1 := math.Max(r0.X, r1.X)
	y1 := math.Max(r0.Y, r1.Y)

	x2 := math.Min(r0.X+r0.W, r1.X+r1.W)
	y2 := math.Min(r0.Y+r0.H, r1.Y+r1.H)

	if x1 > x2 || y1 > y2 {
		return Rect{}
	}

	return Rect{
		X: x1,
		Y: y1,
		W: x2 - x1,
		H: y2 - y1,
	}
}

// The Merge method computes and returns the smallest rectangle that includes
// both the one it is called on and the one passed as argument.
func (r0 Rect) Merge(r1 Rect) Rect {
	r0 = r0.Abs()
	r1 = r1.Abs()

	x1 := math.Min(r0.X, r1.X)
	y1 := math.Min(r0.Y, r1.Y)

	x2 := math.Max(r0.X+r0.W, r1.X+r1.W)
	y2 := math.Max(r0.Y+r0.H, r1.Y+r1.H)

	return Rect{
		X: x1,
		Y: y1,
		W: x2 - x1,
		H: y2 - y1,
	}
}

// The Zero method checks whether the rectangle it is called on is the
// zero-value.
func (r Rect) Zero() bool {
	return r.Origin().Zero() && r.Size().Zero()
}

// The Empty method checks whether the rectangle is empty, which means it has a
// zero area.
func (r Rect) Empty() bool {
	return r.Size().Empty()
}

// The Area method computes and returns the area of the rectangle it is called on
// (width x height).
func (r Rect) Area() float64 {
	return r.Size().Area()
}

// ContainsPoint checks whether the point passed as argument is contained in the
// rectangle it's called on, returning true when that's the case, false otherwise.
func (r Rect) ContainsPoint(p Point) bool {
	return r.X <= p.X && (r.X+r.W) > p.X && r.Y <= p.Y && (r.Y+r.H) > p.Y
}

// ContainsRect checks whether the rectangle passed as argument is contained in
// the one it's called on, returning true when that's the case, false otherwise.
func (r0 Rect) ContainsRect(r1 Rect) bool {
	return r0.X <= r1.X && (r0.X+r0.W) >= (r1.X+r1.W) && r0.Y <= r1.Y && (r0.Y+r0.H) >= (r1.Y+r1.H)
}

// The String method returns a human-readable representation of the rectangle.
func (r Rect) String() string {
	return fmt.Sprintf("{ %.6g, %.6g, %.6g, %.6g }", r.X, r.Y, r.W, r.H)
}

// The Path method satisfies the Shape interface, allowing Rect values to be
// used with programs that manipulate shapes.
func (r Rect) Path() Path {
	return AppendRect(MakePath(5), r)
}

// MergeRect merges a rectangle into an existing list of other rectangles,
// returing the potentially modified slice.
//
// Merging doesn't always append the given rectangle to the list, it won't be
// added if another rectangel in the list already contains the one passed as
// argument for example. A program should consider the list of rectangles passed
// to and returned by the function as the optimal list of rectangle that would
// contain all the merged rectangles. This is particulary useful when keeping
// track of 'dirty' areas in a way that would minimize the 'dirty' surface.
func MergeRect(list []Rect, rect Rect) []Rect {
	// First we check if one of the rectangles in the slice contains the one we
	// are trying to add. If that's the case there's nothing we need to do, the
	// rectangle is already contained in the slice.
	for _, r := range list {
		if r.ContainsRect(rect) {
			return list
		}
	}

	// Look for rectangles that may intersect with the one we are trying to add,
	// if one is found we merge them and rebuild the slice in case it created
	// new intersections.
	for i, r := range list {
		if !r.Intersect(rect).Empty() {
			s := append(make([]Rect, 0, len(list)), r.Merge(rect))

			for j, r := range list {
				if i != j {
					s = MergeRect(s, r)
				}
			}

			return s
		}
	}

	// TOOD: more clever merging policies?
	// -----------------------------------
	//
	// - don't merge if the resulting area is greater than keeping rectangles
	// - look for the merge that creates the smallest area
	// - optimize memory allocation, only recreate the list if needed
	// - sort the list by areas in descending order to improve the fast path
	//
	// While these merges could greatly reduce the areas being redrawn they
	// could also be expansive to compute for potentially little gain due to
	// the way widgets are laid out (parents contain children).
	// It is also possible that generating more redrawing calls may be slower
	// to render than asking the window system to redraw a bigger area once.

	// If none of the merging policies have matched we simply append it to the
	// slice.
	return append(list, rect)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/gorilla/mux"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"wayang/wayang"
)

type Config struct {
	HTTPPrefix      string `json:"http_prefix"`
	Database        string `json:"db"`
	DatabaseAddress string `json:"db_addr"`
}

var (
	db     wayang.DataStore
	config Config
)

func main() {

	configPath := flag.String("conf", "./config.json", "Path to configuration file")
	listenAddr := flag.String("laddr", ":8000", "Address to listen on")
	flag.Parse()

	confData, err := ioutil.ReadFile(*configPath)
	if err != nil {
		fmt.Println("Failed to read configuration file:", *configPath)
		return
	}
	config.HTTPPrefix = ""

	err = json.Unmarshal(confData, &config)
	if err != nil {
		fmt.Println("Failed to parse configuration file:", *configPath)
		return
	}

	r := mux.NewRouter()
	r.StrictSlash(true)

	log.Println("Using", config.Database, "@", config.DatabaseAddress)

	if config.Database == "mongodb" {

		db, err = wayang.NewMongoStore(config.DatabaseAddress)
		if err != nil {
			fmt.Println(err)
			return
		}

		r.HandleFunc(config.HTTPPrefix+"/", rootGet).Methods("GET")
		r.HandleFunc(config.HTTPPrefix+"/", rootPost).Methods("POST")
		r.HandleFunc(config.HTTPPrefix+"/", rootOptions).Methods("OPTIONS")
		r.HandleFunc(config.HTTPPrefix+"/{id:[0-9a-z]+}", optionsHandlerRoot).Methods("OPTIONS")
		r.HandleFunc(config.HTTPPrefix+"/{id:[0-9a-z]+}/{endpoint:[a-zA-Z0-9/_-]+}", optionsHandler).Methods("OPTIONS")
		r.HandleFunc(config.HTTPPrefix+"/{id:[0-9a-z]+}", mockRespondRoot).Methods("GET", "POST", "PUT", "PATCH", "DELETE")
		r.HandleFunc(config.HTTPPrefix+"/{id:[0-9a-z]+}/{endpoint:[a-zA-Z0-9/_-]+}", mockRespond).Methods("GET", "POST", "PUT", "PATCH", "DELETE")

	} else if config.Database == "static" {

		mock := wayang.Mock{}
		mockData, err := ioutil.ReadFile(config.DatabaseAddress)
		if err != nil {
			fmt.Println("Failed to read static configuration file:", config.DatabaseAddress)
			return
		}
		err = json.Unmarshal(mockData, &mock)
		if err != nil {
			fmt.Println("Failed to parse static configuration file:", config.DatabaseAddress)
			return
		}

		db, err = wayang.NewStaticStore(mock)
		if err != nil {
			fmt.Println(err)
			return
		}

		r.HandleFunc(config.HTTPPrefix+"/__config__", staticConfigManagement).Methods("GET", "PUT", "PATCH", "DELETE")
		r.HandleFunc(config.HTTPPrefix+"/", optionsHandlerRoot).Methods("OPTIONS")
		r.HandleFunc(config.HTTPPrefix+"/{endpoint:[a-zA-Z0-9/_-]+}", optionsHandler).Methods("OPTIONS")
		r.HandleFunc(config.HTTPPrefix+"/", mockRespondRoot).Methods("GET", "POST", "PUT", "PATCH", "DELETE")
		r.HandleFunc(config.HTTPPrefix+"/{endpoint:[a-zA-Z0-9/_-]+}", mockRespond).Methods("GET", "POST", "PUT", "PATCH", "DELETE")

	}
	defer db.Close()

	http.Handle("/", r)
	http.ListenAndServe(*listenAddr, nil)

}

func accessControlAllow(rw http.ResponseWriter, req *http.Request) {
	origin := req.Header.Get("Origin")
	if origin == "" {
		rw.Header().Set("Access-Control-Allow-Origin", "*")
		log.Println(req.Method, req.RequestURI)
	} else {
		rw.Header().Set("Access-Control-Allow-Origin", origin)
		log.Println(req.Method, req.RequestURI, "[Origin:", req.Header.Get("Origin")+"]")
	}
}

func rootOptions(rw http.ResponseWriter, req *http.Request) {

	defer req.Body.Close()
	accessControlAllow(rw, req)

	csv := "OPTIONS,GET,POST"
	rw.Header().Set("Allow", csv)
	rw.Header().Set("Access-Control-Allow-Methods", csv)

}

func rootGet(rw http.ResponseWriter, req *http.Request) {
	accessControlAllow(rw, req)
	rw.Header().Set("Content-Type", "text/plain")
	helpMessage := `Hi!
To create an endpoint, do a POST request to the current URL.
The request should contain JSON of the following format:
{
	"/": {
		"GET": {
			"some_key": "some_val"
		},
		"POST": {
			"some_key": {
				"some_other_key": "some_val"
			}
		}
	},
	"/other_endpoint": {
		"DELETE": {
			"key": "value"
		}
	}
}`
	rw.Write([]byte(helpMessage))
}

type NewMockResponse struct {
	Status string `json:"status"`
	Detail string `json:"detail,omitempty"`
	URL    string `json:"url,omitempty"`
}

func rootPost(rw http.ResponseWriter, req *http.Request) {

	defer req.Body.Close()
	accessControlAllow(rw, req)
	rw.Header().Set("Content-Type", "application/json")

	resp := NewMockResponse{}

	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		resp.URL = ""
		resp.Status = http.StatusText(http.StatusInternalServerError)
		resp.Detail = err.Error()
		rv, _ := json.MarshalIndent(resp, "", "	")
		http.Error(rw, string(rv), http.StatusInternalServerError)
		return
	}
	mock := unmarshalAndCleanup(rw, body)

	id, err := db.NewMock(mock)
	if err != nil {
		resp.URL = ""
		resp.Status = http.StatusText(http.StatusInternalServerError)
		resp.Detail = err.Error()
		rv, _ := json.MarshalIndent(resp, "", "	")
		http.Error(rw, string(rv), http.StatusInternalServerError)
		return
	} else {
		resp.Status = "200 OK"
	}
	resp.URL = "http://" + req.Host + "/" + id

	rv, _ := json.MarshalIndent(resp, "", "	")

	rw.Write(rv)
}

func optionsHandlerRoot(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]
	endpoint := "/"

	processOptionsResponse(rw, req, id, endpoint)
}

func optionsHandler(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]
	endpoint := "/" + vars["endpoint"]

	processOptionsResponse(rw, req, id, endpoint)
}

func processOptionsResponse(rw http.ResponseWriter, req *http.Request, id string, endpoint string) {

	defer req.Body.Close()
	accessControlAllow(rw, req)

	ep, err := db.GetEndpoint(id, endpoint)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	var allowedMethods []string
	allowedMethods = append(allowedMethods, "OPTIONS")
	for k, _ := range ep {
		allowedMethods = append(allowedMethods, k)
	}

	csv := strings.Join(allowedMethods, ",")
	rw.Header().Set("Allow", csv)
	rw.Header().Set("Access-Control-Allow-Methods", csv)

}

func mockRespondRoot(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]
	endpoint := "/"

	processEndpointResponse(rw, req, id, endpoint)
}

func mockRespond(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]
	endpoint := "/" + vars["endpoint"]

	processEndpointResponse(rw, req, id, endpoint)
}

func processEndpointResponse(rw http.ResponseWriter, req *http.Request, id string, endpoint string) {

	defer req.Body.Close()
	accessControlAllow(rw, req)
	rw.Header().Set("Content-Type", "application/json")

	ep, err := db.GetEndpoint(id, endpoint)
	if err != nil {
		http.Error(rw, http.StatusText(http.StatusNotFound), http.StatusNotFound)
		return
	}

	mockResponse, ok := ep[req.Method]
	if !ok {
		http.Error(rw, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
		return
	}

	rv, _ := json.MarshalIndent(mockResponse, "", "	")
	rw.Write([]byte(rv))

}

func staticConfigManagement(rw http.ResponseWriter, req *http.Request) {

	defer req.Body.Close()

	accessControlAllow(rw, req)
	rw.Header().Set("Content-Type", "application/json")

	static := db.(*wayang.StaticStore)
	resp := NewMockResponse{}

	if req.Method == "GET" {

		rv, _ := json.MarshalIndent(static.StaticData, "", "	")
		rw.Write([]byte(rv))

	} else if req.Method == "PUT" {

		body, err := ioutil.ReadAll(req.Body)
		if err != nil {
			resp.Status = strconv.Itoa(http.StatusInternalServerError) + " " + http.StatusText(http.StatusInternalServerError)
			resp.Detail = err.Error()
			rv, _ := json.MarshalIndent(resp, "", "	")
			http.Error(rw, string(rv), http.StatusInternalServerError)
			return
		}

		mock := unmarshalAndCleanup(rw, body)

		static.StaticData = mock

		go syncToFile()

	} else if req.Method == "PATCH" {

		body, err := ioutil.ReadAll(req.Body)
		if err != nil {
			resp.Status = strconv.Itoa(http.StatusInternalServerError) + " " + http.StatusText(http.StatusInternalServerError)
			resp.Detail = err.Error()
			rv, _ := json.MarshalIndent(resp, "", "	")
			http.Error(rw, string(rv), http.StatusInternalServerError)
			return
		}

		partMock := unmarshalAndCleanup(rw, body)

		for k, v := range partMock {
			static.StaticData[k] = v
		}

		go syncToFile()

	} else if req.Method == "DELETE" {

		static.StaticData = wayang.Mock{}

		go syncToFile()

	}

}

func unmarshalAndCleanup(rw http.ResponseWriter, data []byte) (mock wayang.Mock) {

	resp := NewMockResponse{}

	err := json.Unmarshal(data, &mock)
	if err != nil {
		resp.Status = strconv.Itoa(http.StatusBadRequest) + " " + http.StatusText(http.StatusBadRequest)
		resp.Detail = err.Error()
		rv, _ := json.MarshalIndent(resp, "", "	")
		http.Error(rw, string(rv), http.StatusBadRequest)
		return
	}

	// Ensure that all routes begin with a /
	for k, v := range mock {
		if k[:1] != "/" {
			mock["/"+k] = v
			delete(mock, k)
		}
	}

	// Ensure that all requests are in the right format
	for k, v := range mock {
		for kk, vv := range v {
			KK := strings.ToUpper(kk)
			if KK != "GET" &&
				KK != "POST" &&
				KK != "PUT" &&
				KK != "PATCH" &&
				KK != "DELETE" {
				resp.Status = strconv.Itoa(http.StatusBadRequest) + " " + http.StatusText(http.StatusBadRequest)
				resp.Detail = "Invalid HTTP Method"
				rv, _ := json.MarshalIndent(resp, "", "	")
				http.Error(rw, string(rv), http.StatusBadRequest)
				return
			}
			if kk != KK {
				mock[k][KK] = vv
				delete(mock[k], kk)
			}
		}
	}

	return

}

func syncToFile() {
	static := db.(*wayang.StaticStore)
	data, _ := json.MarshalIndent(static.StaticData, "", "	")
	ioutil.WriteFile(config.DatabaseAddress, data, 0644)
}
<commit_msg>Set Access-Control-Allow-Headers.<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/gorilla/mux"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"strings"
	"wayang/wayang"
)

type Config struct {
	HTTPPrefix      string `json:"http_prefix"`
	Database        string `json:"db"`
	DatabaseAddress string `json:"db_addr"`
}

var (
	db     wayang.DataStore
	config Config
)

func main() {

	configPath := flag.String("conf", "./config.json", "Path to configuration file")
	listenAddr := flag.String("laddr", ":8000", "Address to listen on")
	flag.Parse()

	confData, err := ioutil.ReadFile(*configPath)
	if err != nil {
		fmt.Println("Failed to read configuration file:", *configPath)
		return
	}
	config.HTTPPrefix = ""

	err = json.Unmarshal(confData, &config)
	if err != nil {
		fmt.Println("Failed to parse configuration file:", *configPath)
		return
	}

	r := mux.NewRouter()
	r.StrictSlash(true)

	log.Println("Using", config.Database, "@", config.DatabaseAddress)

	if config.Database == "mongodb" {

		db, err = wayang.NewMongoStore(config.DatabaseAddress)
		if err != nil {
			fmt.Println(err)
			return
		}

		r.HandleFunc(config.HTTPPrefix+"/", rootGet).Methods("GET")
		r.HandleFunc(config.HTTPPrefix+"/", rootPost).Methods("POST")
		r.HandleFunc(config.HTTPPrefix+"/", rootOptions).Methods("OPTIONS")
		r.HandleFunc(config.HTTPPrefix+"/{id:[0-9a-z]+}", optionsHandlerRoot).Methods("OPTIONS")
		r.HandleFunc(config.HTTPPrefix+"/{id:[0-9a-z]+}/{endpoint:[a-zA-Z0-9/_-]+}", optionsHandler).Methods("OPTIONS")
		r.HandleFunc(config.HTTPPrefix+"/{id:[0-9a-z]+}", mockRespondRoot).Methods("GET", "POST", "PUT", "PATCH", "DELETE")
		r.HandleFunc(config.HTTPPrefix+"/{id:[0-9a-z]+}/{endpoint:[a-zA-Z0-9/_-]+}", mockRespond).Methods("GET", "POST", "PUT", "PATCH", "DELETE")

	} else if config.Database == "static" {

		mock := wayang.Mock{}
		mockData, err := ioutil.ReadFile(config.DatabaseAddress)
		if err != nil {
			fmt.Println("Failed to read static configuration file:", config.DatabaseAddress)
			return
		}
		err = json.Unmarshal(mockData, &mock)
		if err != nil {
			fmt.Println("Failed to parse static configuration file:", config.DatabaseAddress)
			return
		}

		db, err = wayang.NewStaticStore(mock)
		if err != nil {
			fmt.Println(err)
			return
		}

		r.HandleFunc(config.HTTPPrefix+"/__config__", staticConfigManagement).Methods("GET", "PUT", "PATCH", "DELETE")
		r.HandleFunc(config.HTTPPrefix+"/", optionsHandlerRoot).Methods("OPTIONS")
		r.HandleFunc(config.HTTPPrefix+"/{endpoint:[a-zA-Z0-9/_-]+}", optionsHandler).Methods("OPTIONS")
		r.HandleFunc(config.HTTPPrefix+"/", mockRespondRoot).Methods("GET", "POST", "PUT", "PATCH", "DELETE")
		r.HandleFunc(config.HTTPPrefix+"/{endpoint:[a-zA-Z0-9/_-]+}", mockRespond).Methods("GET", "POST", "PUT", "PATCH", "DELETE")

	}
	defer db.Close()

	http.Handle("/", r)
	http.ListenAndServe(*listenAddr, nil)

}

func accessControlAllow(rw http.ResponseWriter, req *http.Request) {
	origin := req.Header.Get("Origin")
	if origin == "" {
		rw.Header().Set("Access-Control-Allow-Origin", "*")
		log.Println(req.Method, req.RequestURI)
	} else {
		rw.Header().Set("Access-Control-Allow-Origin", origin)
		log.Println(req.Method, req.RequestURI, "[Origin:", req.Header.Get("Origin")+"]")
	}
	rw.Header().Set("Access-Control-Allow-Headers", "Content-Type")
}

func rootOptions(rw http.ResponseWriter, req *http.Request) {

	defer req.Body.Close()
	accessControlAllow(rw, req)

	csv := "OPTIONS,GET,POST"
	rw.Header().Set("Allow", csv)
	rw.Header().Set("Access-Control-Allow-Methods", csv)

}

func rootGet(rw http.ResponseWriter, req *http.Request) {
	accessControlAllow(rw, req)
	rw.Header().Set("Content-Type", "text/plain")
	helpMessage := `Hi!
To create an endpoint, do a POST request to the current URL.
The request should contain JSON of the following format:
{
	"/": {
		"GET": {
			"some_key": "some_val"
		},
		"POST": {
			"some_key": {
				"some_other_key": "some_val"
			}
		}
	},
	"/other_endpoint": {
		"DELETE": {
			"key": "value"
		}
	}
}`
	rw.Write([]byte(helpMessage))
}

type NewMockResponse struct {
	Status string `json:"status"`
	Detail string `json:"detail,omitempty"`
	URL    string `json:"url,omitempty"`
}

func rootPost(rw http.ResponseWriter, req *http.Request) {

	defer req.Body.Close()
	accessControlAllow(rw, req)
	rw.Header().Set("Content-Type", "application/json")

	resp := NewMockResponse{}

	body, err := ioutil.ReadAll(req.Body)
	if err != nil {
		resp.URL = ""
		resp.Status = http.StatusText(http.StatusInternalServerError)
		resp.Detail = err.Error()
		rv, _ := json.MarshalIndent(resp, "", "	")
		http.Error(rw, string(rv), http.StatusInternalServerError)
		return
	}
	mock := unmarshalAndCleanup(rw, body)

	id, err := db.NewMock(mock)
	if err != nil {
		resp.URL = ""
		resp.Status = http.StatusText(http.StatusInternalServerError)
		resp.Detail = err.Error()
		rv, _ := json.MarshalIndent(resp, "", "	")
		http.Error(rw, string(rv), http.StatusInternalServerError)
		return
	} else {
		resp.Status = "200 OK"
	}
	resp.URL = "http://" + req.Host + "/" + id

	rv, _ := json.MarshalIndent(resp, "", "	")

	rw.Write(rv)
}

func optionsHandlerRoot(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]
	endpoint := "/"

	processOptionsResponse(rw, req, id, endpoint)
}

func optionsHandler(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]
	endpoint := "/" + vars["endpoint"]

	processOptionsResponse(rw, req, id, endpoint)
}

func processOptionsResponse(rw http.ResponseWriter, req *http.Request, id string, endpoint string) {

	defer req.Body.Close()
	accessControlAllow(rw, req)

	ep, err := db.GetEndpoint(id, endpoint)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	var allowedMethods []string
	allowedMethods = append(allowedMethods, "OPTIONS")
	for k, _ := range ep {
		allowedMethods = append(allowedMethods, k)
	}

	csv := strings.Join(allowedMethods, ",")
	rw.Header().Set("Allow", csv)
	rw.Header().Set("Access-Control-Allow-Methods", csv)

}

func mockRespondRoot(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]
	endpoint := "/"

	processEndpointResponse(rw, req, id, endpoint)
}

func mockRespond(rw http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)
	id := vars["id"]
	endpoint := "/" + vars["endpoint"]

	processEndpointResponse(rw, req, id, endpoint)
}

func processEndpointResponse(rw http.ResponseWriter, req *http.Request, id string, endpoint string) {

	defer req.Body.Close()
	accessControlAllow(rw, req)
	rw.Header().Set("Content-Type", "application/json")

	ep, err := db.GetEndpoint(id, endpoint)
	if err != nil {
		http.Error(rw, http.StatusText(http.StatusNotFound), http.StatusNotFound)
		return
	}

	mockResponse, ok := ep[req.Method]
	if !ok {
		http.Error(rw, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
		return
	}

	rv, _ := json.MarshalIndent(mockResponse, "", "	")
	rw.Write([]byte(rv))

}

func staticConfigManagement(rw http.ResponseWriter, req *http.Request) {

	defer req.Body.Close()

	accessControlAllow(rw, req)
	rw.Header().Set("Content-Type", "application/json")

	static := db.(*wayang.StaticStore)
	resp := NewMockResponse{}

	if req.Method == "GET" {

		rv, _ := json.MarshalIndent(static.StaticData, "", "	")
		rw.Write([]byte(rv))

	} else if req.Method == "PUT" {

		body, err := ioutil.ReadAll(req.Body)
		if err != nil {
			resp.Status = strconv.Itoa(http.StatusInternalServerError) + " " + http.StatusText(http.StatusInternalServerError)
			resp.Detail = err.Error()
			rv, _ := json.MarshalIndent(resp, "", "	")
			http.Error(rw, string(rv), http.StatusInternalServerError)
			return
		}

		mock := unmarshalAndCleanup(rw, body)

		static.StaticData = mock

		go syncToFile()

	} else if req.Method == "PATCH" {

		body, err := ioutil.ReadAll(req.Body)
		if err != nil {
			resp.Status = strconv.Itoa(http.StatusInternalServerError) + " " + http.StatusText(http.StatusInternalServerError)
			resp.Detail = err.Error()
			rv, _ := json.MarshalIndent(resp, "", "	")
			http.Error(rw, string(rv), http.StatusInternalServerError)
			return
		}

		partMock := unmarshalAndCleanup(rw, body)

		for k, v := range partMock {
			static.StaticData[k] = v
		}

		go syncToFile()

	} else if req.Method == "DELETE" {

		static.StaticData = wayang.Mock{}

		go syncToFile()

	}

}

func unmarshalAndCleanup(rw http.ResponseWriter, data []byte) (mock wayang.Mock) {

	resp := NewMockResponse{}

	err := json.Unmarshal(data, &mock)
	if err != nil {
		resp.Status = strconv.Itoa(http.StatusBadRequest) + " " + http.StatusText(http.StatusBadRequest)
		resp.Detail = err.Error()
		rv, _ := json.MarshalIndent(resp, "", "	")
		http.Error(rw, string(rv), http.StatusBadRequest)
		return
	}

	// Ensure that all routes begin with a /
	for k, v := range mock {
		if k[:1] != "/" {
			mock["/"+k] = v
			delete(mock, k)
		}
	}

	// Ensure that all requests are in the right format
	for k, v := range mock {
		for kk, vv := range v {
			KK := strings.ToUpper(kk)
			if KK != "GET" &&
				KK != "POST" &&
				KK != "PUT" &&
				KK != "PATCH" &&
				KK != "DELETE" {
				resp.Status = strconv.Itoa(http.StatusBadRequest) + " " + http.StatusText(http.StatusBadRequest)
				resp.Detail = "Invalid HTTP Method"
				rv, _ := json.MarshalIndent(resp, "", "	")
				http.Error(rw, string(rv), http.StatusBadRequest)
				return
			}
			if kk != KK {
				mock[k][KK] = vv
				delete(mock[k], kk)
			}
		}
	}

	return

}

func syncToFile() {
	static := db.(*wayang.StaticStore)
	data, _ := json.MarshalIndent(static.StaticData, "", "	")
	ioutil.WriteFile(config.DatabaseAddress, data, 0644)
}
<|endoftext|>