<commit_before>package main

import (
	"fmt"
	"github.com/ant0ine/go-json-rest/rest"
	"github.com/pmylund/go-cache"
	"github.com/stacktic/dropbox"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"log"
	"net/http"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

/*
   Stores the config file, read from the drive
*/
type Config struct {
	Api_secret    string
	Api_key       string
	Client_token  string
	Context_root  string
	Server_listen string
	Server_port   string
}

/*
   Represents an archived piece of software
*/
type Archive struct {
	Software string
	Date     string
	Version  string
	Tag      string
}

/*
   Initialize an archive from a path
*/
func (a Archive) Init(path string) *Archive {
	_, filename := filepath.Split(path)
	parts := strings.Split(filename, "-")
	a.Software = parts[0]
	a.Version = strings.Join([]string{parts[1], parts[2]}, " ")
	a.Date = strings.Join([]string{parts[3], parts[4], parts[5]}, "-")
	a.Tag = parts[6]
	return &a
}

/*
   A basic payload struct to return endpoint information to the client
*/
type BasicResult struct {
	Tag  string
	Date string
}

// Allow BasicResults to be sorted
func (slice BasicResults) Len() int {
	return len(slice)
}

func (slice BasicResults) Less(i, j int) bool {
	return (slice[i].Date > slice[j].Date) && slice[i].Tag != "latest"
}

func (slice BasicResults) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

// Declare BasicResults as a type of slice of BasicResult items
type BasicResults []BasicResult

// The struct to store the configuration data
var config Config

// 12 hour caching that cleans up every 15 minutes
var cache_instance *cache.Cache

//The api link to dropbox
var db *dropbox.Dropbox

// slice that stores the list of items to not include in the searches
var do_not_include []string

func main() {
	var err error

	do_not_include = []string{}
	// Ignore all .txt files
	do_not_include = append(do_not_include, ".txt")

	config = Config{}
	cache_instance = cache.New(12*time.Hour, 15*time.Minute)
	db = dropbox.NewDropbox()

	data, read_err := ioutil.ReadFile("config.yml")
	if read_err != nil {
		log.Fatal(read_err)
	}

	yaml_err := yaml.Unmarshal(data, &config)
	if yaml_err != nil {
		log.Fatal("error: %v", yaml_err)
	}
	fmt.Printf("--- config_file dump:\n%s\n\n", data)
	fmt.Printf("--- config dump:\n%s\n\n", config)

	db.SetAppInfo(config.Api_key, config.Api_secret)
	if len(config.Client_token) >= 1 {
		db.SetAccessToken(config.Client_token)
	} else {
		if err = db.Auth(); err != nil {
			fmt.Println(err)
			return
		}
		config.Client_token = db.AccessToken()
		db.SetAccessToken(config.Client_token)
		d, marshal_err := yaml.Marshal(&config)
		if marshal_err != nil {
			log.Fatal("error: %v", marshal_err)
		}
		ioutil.WriteFile("config.yml", []byte(d), 0644)
	}

	// root_paths := get_directories(cache, db, "")
	// fmt.Printf("--- paths dump:\n%s\n\n", root_paths)

	// nightly_files := get_files(cache, db, "ARMv7")
	// fmt.Printf("--- paths dump:\n%s\n\n", nightly_files)

	// setup server to link
	api := rest.NewApi()
	statusMw := &rest.StatusMiddleware{}
	api.Use(statusMw)
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		// Status endpoint for monitoring
		rest.Get("/.status", func(w rest.ResponseWriter, r *rest.Request) {
			w.WriteJson(statusMw.GetStatus())
		}),
		// The JSON endpoints for data about the next endpoint
		rest.Get("/", list_arches),
		rest.Get("/#arch", list_softwares),
		rest.Get("/#arch/#software", list_versions),
		rest.Get("/#arch/#software/#version", list_targets),
		// Endpoint that redirects the client
		rest.Get("/#arch/#software/#version/#target", link_target),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	s := []string{}
	s = append(s, config.Server_listen)
	s = append(s, config.Server_port)
	server_listen := strings.Join(s, ":")
	http.Handle(strings.Join([]string{config.Context_root, "/"}, ""), http.StripPrefix(config.Context_root, api.MakeHandler()))
	log.Fatal(http.ListenAndServe(server_listen, nil))
}

/*
   Return a list of available architectures as reported by
   the top dropbox directories of the app folder
*/
func list_arches(w rest.ResponseWriter, r *rest.Request) {
	// Use caching to reduce calls to the Dropbox API
	cache_path := "arches"
	data, found := cache_instance.Get(cache_path)
	if found {
		if cached, ok := data.([]string); ok {
			w.WriteJson(cached)
			return
		} else {
			log.Println("Error: Unable to retrieve from cache")
		}
	}

	arches := []string{}
	directories := get_directories(cache_instance, db, "/")
	for _, arch := range directories {
		arches = append(arches, strings.Replace(arch.Path, "/", "", -1))
	}
	cache_instance.Set(cache_path, arches, 0)
	w.WriteJson(arches)
}

/*
   Return a list of the software that exists under a particular
   architecture
*/
func list_softwares(w rest.ResponseWriter, r *rest.Request) {
	arch := r.PathParam("arch")

	// Use caching to reduce calls to the Dropbox API
	cache_path := arch
	data, found := cache_instance.Get(cache_path)
	if found {
		if cached, ok := data.([]string); ok {
			w.WriteJson(cached)
			return
		} else {
			log.Println("Error: Unable to retrieve from cache")
		}
	}

	softwares := make(map[string]string)
	files := get_files(cache_instance, db, arch)
	for _, file := range files {
		archive := new(Archive)
		archive = archive.Init(file.Path)
		softwares[archive.Software] = ""
	}
	keys := make([]string, 0, len(softwares))
	for k := range softwares {
		keys = append(keys, k)
	}
	cache_instance.Set(cache_path, keys, 0)
	w.WriteJson(keys)
}

/*
   List available versions for a specific piece of software
*/
func list_versions(w rest.ResponseWriter, r *rest.Request) {
	w.WriteJson([]string{"nightly", "beta", "stable"})
}

/*
   Return a list of available targets for a software and a version.
   These targets represent the possible redirects that are available.
*/
func list_targets(w rest.ResponseWriter, r *rest.Request) {
	arch := r.PathParam("arch")
	software := r.PathParam("software")
	version := r.PathParam("version")

	// Doesn't need to be cached, as its calls are already cached.
	targets := BasicResults{}
	latest_date := time.Time{}
	target_path := get_target_path(arch, version)
	files := get_files(cache_instance, db, target_path)
	for _, file := range files {
		archive := new(Archive)
		archive = archive.Init(file.Path)
		if archive.Software == software {
			parsed_time, err := time.Parse("2006-01-02", archive.Date)
			if err != nil {
				log.Println(err)
				parsed_time = time.Time{}
			}
			if parsed_time.After(latest_date) {
				latest_date = parsed_time
			}
			targets = append(targets, BasicResult{archive.Tag, archive.Date})
		}
	}
	targets = append(targets, BasicResult{"latest", latest_date.Format("2006-01-02")})

	// Sort the targets by date descending.
	sort.Sort(targets)

	w.WriteJson(targets)
}

/*
   Redirect the client to the link, OR return a 404 for an undefined target.
*/
func link_target(w rest.ResponseWriter, r *rest.Request) {
	arch := r.PathParam("arch")
	software := r.PathParam("software")
	version := r.PathParam("version")
	target := r.PathParam("target")

	target_file, found := get_target(arch, software, version, target)
	if found {
		target_link := get_link(cache_instance, db, target_file.Path)
		w.Header().Set("Location", target_link)
		w.WriteHeader(302)
	} else {
		w.WriteHeader(404)
		w.WriteJson(map[string]string{"error": "Target Not Found"})
	}
}

/*
	Get only a slice of the directories at a path
*/
func get_directories(cache *cache.Cache, db *dropbox.Dropbox, path string) []dropbox.Entry {
	return get(cache, db, path, true)
}

/*
	Get only a slice of the directories at a path
*/
func get_files(cache *cache.Cache, db *dropbox.Dropbox, path string) []dropbox.Entry {
	return get(cache, db, path, false)
}

/*
	Actually get a list of directories or files from the dropbox connection
*/
func get(cache *cache.Cache, db *dropbox.Dropbox, path string, directories bool) []dropbox.Entry {
	// Use caching to reduce calls to the Dropbox API
	var cache_descriptor string
	if directories {
		cache_descriptor = "dirs:"
	} else {
		cache_descriptor = "files:"
	}
	s := []string{}
	s = append(s, cache_descriptor)
	s = append(s, path)
	cache_path := strings.Join(s, "")

	data, found := cache.Get(cache_path)
	if found {
		if cached_paths, ok := data.([]dropbox.Entry); ok {
			return (cached_paths)
		} else {
			log.Println("Error: Unable to retrieve from cache")
		}
	}

	entry, err := db.Metadata(path, true, false, "", "", 500)
	if err != nil {
		log.Println(err)
		return []dropbox.Entry{}
	}
	paths := make([]dropbox.Entry, 0)
	for i := 0; i < len(entry.Contents); i++ {
		entry := entry.Contents[i]
		if directories {
			if entry.IsDir {
				paths = append(paths, entry)
			}
		} else {
			if !entry.IsDir {
				include := true
				for _, lookup := range do_not_include {
					if strings.Contains(entry.Path, lookup) {
						include = false
					}
				}
				if include {
					paths = append(paths, entry)
				}
			}
		}
	}
	cache.Set(cache_path, paths, 0)
	return paths
}

/*
	Divine the correct target path from the provided info
*/
func get_target_path(arch string, version string) string {
	var target_path string
	if version == "nightly" {
		target_path = arch
	} else {
		directories := get_directories(cache_instance, db, arch)
		mTime := time.Time(dropbox.DBTime{})
		var latest_directory dropbox.Entry
		for _, dir := range directories {
			if strings.Contains(dir.Path, version) {
				if time.Time(dir.Modified).After(mTime) {
					mTime = time.Time(dir.Modified)
					latest_directory = dir
				}
			}
		}
		target_path = latest_directory.Path
	}
	return target_path
}

/*
	Returns a shared link to dropbox file
*/
func get_link(cache *cache.Cache, db *dropbox.Dropbox, path string) string {

	// Use caching to reduce calls to the Dropbox API
	cache_path := strings.Join([]string{"link", path}, ":")
	data, found := cache.Get(cache_path)
	if found {
		if cached, ok := data.(string); ok {
			return cached
		} else {
			log.Println("Error: Unable to retrieve from cache")
		}
	}

	link, err := db.Shares(path, false)
	if err != nil {
		log.Println(err)
		return ""
	}
	cache.Set(cache_path, link.URL, 0)
	return link.URL
}

/*
   Take a target and return the appropriate Entry item for that target.
   OR, return an empty Entry and a boolean flag that indicates the the
   requested target doesn't exist
*/
func get_target(arch string, software string, version string, target string) (dropbox.Entry, bool) {
	if target == "latest" {
		return get_latest(arch, software, version)
	} else {
		target_path := get_target_path(arch, version)
		files := get_files(cache_instance, db, target_path)
		for _, file := range files {
			archive := new(Archive)
			archive = archive.Init(file.Path)
			if archive.Software == software {
				if archive.Tag == target {
					return file, true
				}
			}
		}
	}
	return dropbox.Entry{}, false
}

/*
	Use the arch, software and version to find the latest
*/
func get_latest(arch string, software string, version string) (dropbox.Entry, bool) {
	target_path := get_target_path(arch, version)

	s := []string{}
	s = append(s, software)
	s = append(s, "-")
	search := strings.Join(s, "")

	mTime := time.Time(dropbox.DBTime{})
	var latest_file dropbox.Entry
	files := get_files(cache_instance, db, target_path)
	for _, file := range files {
		if strings.Contains(file.Path, search) {
			if time.Time(file.Modified).After(mTime) {
				mTime = time.Time(file.Modified)
				latest_file = file
			}
		}
	}
	if latest_file.Path == "" {
		return latest_file, false
	} else {
		return latest_file, true
	}
}
<commit_msg>Reversed the operator<commit_after>package main

import (
	"fmt"
	"github.com/ant0ine/go-json-rest/rest"
	"github.com/pmylund/go-cache"
	"github.com/stacktic/dropbox"
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"log"
	"net/http"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

/*
   Stores the config file, read from the drive
*/
type Config struct {
	Api_secret    string
	Api_key       string
	Client_token  string
	Context_root  string
	Server_listen string
	Server_port   string
}

/*
   Represents an archived piece of software
*/
type Archive struct {
	Software string
	Date     string
	Version  string
	Tag      string
}

/*
   Initialize an archive from a path
*/
func (a Archive) Init(path string) *Archive {
	_, filename := filepath.Split(path)
	parts := strings.Split(filename, "-")
	a.Software = parts[0]
	a.Version = strings.Join([]string{parts[1], parts[2]}, " ")
	a.Date = strings.Join([]string{parts[3], parts[4], parts[5]}, "-")
	a.Tag = parts[6]
	return &a
}

/*
   A basic payload struct to return endpoint information to the client
*/
type BasicResult struct {
	Tag  string
	Date string
}

// Allow BasicResults to be sorted
func (slice BasicResults) Len() int {
	return len(slice)
}

func (slice BasicResults) Less(i, j int) bool {
	return (slice[i].Date > slice[j].Date) && slice[j].Tag != "latest"
}

func (slice BasicResults) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}

// Declare BasicResults as a type of slice of BasicResult items
type BasicResults []BasicResult

// The struct to store the configuration data
var config Config

// 12 hour caching that cleans up every 15 minutes
var cache_instance *cache.Cache

//The api link to dropbox
var db *dropbox.Dropbox

// slice that stores the list of items to not include in the searches
var do_not_include []string

func main() {
	var err error

	do_not_include = []string{}
	// Ignore all .txt files
	do_not_include = append(do_not_include, ".txt")

	config = Config{}
	cache_instance = cache.New(12*time.Hour, 15*time.Minute)
	db = dropbox.NewDropbox()

	data, read_err := ioutil.ReadFile("config.yml")
	if read_err != nil {
		log.Fatal(read_err)
	}

	yaml_err := yaml.Unmarshal(data, &config)
	if yaml_err != nil {
		log.Fatal("error: %v", yaml_err)
	}
	fmt.Printf("--- config_file dump:\n%s\n\n", data)
	fmt.Printf("--- config dump:\n%s\n\n", config)

	db.SetAppInfo(config.Api_key, config.Api_secret)
	if len(config.Client_token) >= 1 {
		db.SetAccessToken(config.Client_token)
	} else {
		if err = db.Auth(); err != nil {
			fmt.Println(err)
			return
		}
		config.Client_token = db.AccessToken()
		db.SetAccessToken(config.Client_token)
		d, marshal_err := yaml.Marshal(&config)
		if marshal_err != nil {
			log.Fatal("error: %v", marshal_err)
		}
		ioutil.WriteFile("config.yml", []byte(d), 0644)
	}

	// root_paths := get_directories(cache, db, "")
	// fmt.Printf("--- paths dump:\n%s\n\n", root_paths)

	// nightly_files := get_files(cache, db, "ARMv7")
	// fmt.Printf("--- paths dump:\n%s\n\n", nightly_files)

	// setup server to link
	api := rest.NewApi()
	statusMw := &rest.StatusMiddleware{}
	api.Use(statusMw)
	api.Use(rest.DefaultDevStack...)
	router, err := rest.MakeRouter(
		// Status endpoint for monitoring
		rest.Get("/.status", func(w rest.ResponseWriter, r *rest.Request) {
			w.WriteJson(statusMw.GetStatus())
		}),
		// The JSON endpoints for data about the next endpoint
		rest.Get("/", list_arches),
		rest.Get("/#arch", list_softwares),
		rest.Get("/#arch/#software", list_versions),
		rest.Get("/#arch/#software/#version", list_targets),
		// Endpoint that redirects the client
		rest.Get("/#arch/#software/#version/#target", link_target),
	)
	if err != nil {
		log.Fatal(err)
	}
	api.SetApp(router)
	s := []string{}
	s = append(s, config.Server_listen)
	s = append(s, config.Server_port)
	server_listen := strings.Join(s, ":")
	http.Handle(strings.Join([]string{config.Context_root, "/"}, ""), http.StripPrefix(config.Context_root, api.MakeHandler()))
	log.Fatal(http.ListenAndServe(server_listen, nil))
}

/*
   Return a list of available architectures as reported by
   the top dropbox directories of the app folder
*/
func list_arches(w rest.ResponseWriter, r *rest.Request) {
	// Use caching to reduce calls to the Dropbox API
	cache_path := "arches"
	data, found := cache_instance.Get(cache_path)
	if found {
		if cached, ok := data.([]string); ok {
			w.WriteJson(cached)
			return
		} else {
			log.Println("Error: Unable to retrieve from cache")
		}
	}

	arches := []string{}
	directories := get_directories(cache_instance, db, "/")
	for _, arch := range directories {
		arches = append(arches, strings.Replace(arch.Path, "/", "", -1))
	}
	cache_instance.Set(cache_path, arches, 0)
	w.WriteJson(arches)
}

/*
   Return a list of the software that exists under a particular
   architecture
*/
func list_softwares(w rest.ResponseWriter, r *rest.Request) {
	arch := r.PathParam("arch")

	// Use caching to reduce calls to the Dropbox API
	cache_path := arch
	data, found := cache_instance.Get(cache_path)
	if found {
		if cached, ok := data.([]string); ok {
			w.WriteJson(cached)
			return
		} else {
			log.Println("Error: Unable to retrieve from cache")
		}
	}

	softwares := make(map[string]string)
	files := get_files(cache_instance, db, arch)
	for _, file := range files {
		archive := new(Archive)
		archive = archive.Init(file.Path)
		softwares[archive.Software] = ""
	}
	keys := make([]string, 0, len(softwares))
	for k := range softwares {
		keys = append(keys, k)
	}
	cache_instance.Set(cache_path, keys, 0)
	w.WriteJson(keys)
}

/*
   List available versions for a specific piece of software
*/
func list_versions(w rest.ResponseWriter, r *rest.Request) {
	w.WriteJson([]string{"nightly", "beta", "stable"})
}

/*
   Return a list of available targets for a software and a version.
   These targets represent the possible redirects that are available.
*/
func list_targets(w rest.ResponseWriter, r *rest.Request) {
	arch := r.PathParam("arch")
	software := r.PathParam("software")
	version := r.PathParam("version")

	// Doesn't need to be cached, as its calls are already cached.
	targets := BasicResults{}
	latest_date := time.Time{}
	target_path := get_target_path(arch, version)
	files := get_files(cache_instance, db, target_path)
	for _, file := range files {
		archive := new(Archive)
		archive = archive.Init(file.Path)
		if archive.Software == software {
			parsed_time, err := time.Parse("2006-01-02", archive.Date)
			if err != nil {
				log.Println(err)
				parsed_time = time.Time{}
			}
			if parsed_time.After(latest_date) {
				latest_date = parsed_time
			}
			targets = append(targets, BasicResult{archive.Tag, archive.Date})
		}
	}
	targets = append(targets, BasicResult{"latest", latest_date.Format("2006-01-02")})

	// Sort the targets by date descending.
	sort.Sort(targets)

	w.WriteJson(targets)
}

/*
   Redirect the client to the link, OR return a 404 for an undefined target.
*/
func link_target(w rest.ResponseWriter, r *rest.Request) {
	arch := r.PathParam("arch")
	software := r.PathParam("software")
	version := r.PathParam("version")
	target := r.PathParam("target")

	target_file, found := get_target(arch, software, version, target)
	if found {
		target_link := get_link(cache_instance, db, target_file.Path)
		w.Header().Set("Location", target_link)
		w.WriteHeader(302)
	} else {
		w.WriteHeader(404)
		w.WriteJson(map[string]string{"error": "Target Not Found"})
	}
}

/*
	Get only a slice of the directories at a path
*/
func get_directories(cache *cache.Cache, db *dropbox.Dropbox, path string) []dropbox.Entry {
	return get(cache, db, path, true)
}

/*
	Get only a slice of the directories at a path
*/
func get_files(cache *cache.Cache, db *dropbox.Dropbox, path string) []dropbox.Entry {
	return get(cache, db, path, false)
}

/*
	Actually get a list of directories or files from the dropbox connection
*/
func get(cache *cache.Cache, db *dropbox.Dropbox, path string, directories bool) []dropbox.Entry {
	// Use caching to reduce calls to the Dropbox API
	var cache_descriptor string
	if directories {
		cache_descriptor = "dirs:"
	} else {
		cache_descriptor = "files:"
	}
	s := []string{}
	s = append(s, cache_descriptor)
	s = append(s, path)
	cache_path := strings.Join(s, "")

	data, found := cache.Get(cache_path)
	if found {
		if cached_paths, ok := data.([]dropbox.Entry); ok {
			return (cached_paths)
		} else {
			log.Println("Error: Unable to retrieve from cache")
		}
	}

	entry, err := db.Metadata(path, true, false, "", "", 500)
	if err != nil {
		log.Println(err)
		return []dropbox.Entry{}
	}
	paths := make([]dropbox.Entry, 0)
	for i := 0; i < len(entry.Contents); i++ {
		entry := entry.Contents[i]
		if directories {
			if entry.IsDir {
				paths = append(paths, entry)
			}
		} else {
			if !entry.IsDir {
				include := true
				for _, lookup := range do_not_include {
					if strings.Contains(entry.Path, lookup) {
						include = false
					}
				}
				if include {
					paths = append(paths, entry)
				}
			}
		}
	}
	cache.Set(cache_path, paths, 0)
	return paths
}

/*
	Divine the correct target path from the provided info
*/
func get_target_path(arch string, version string) string {
	var target_path string
	if version == "nightly" {
		target_path = arch
	} else {
		directories := get_directories(cache_instance, db, arch)
		mTime := time.Time(dropbox.DBTime{})
		var latest_directory dropbox.Entry
		for _, dir := range directories {
			if strings.Contains(dir.Path, version) {
				if time.Time(dir.Modified).After(mTime) {
					mTime = time.Time(dir.Modified)
					latest_directory = dir
				}
			}
		}
		target_path = latest_directory.Path
	}
	return target_path
}

/*
	Returns a shared link to dropbox file
*/
func get_link(cache *cache.Cache, db *dropbox.Dropbox, path string) string {

	// Use caching to reduce calls to the Dropbox API
	cache_path := strings.Join([]string{"link", path}, ":")
	data, found := cache.Get(cache_path)
	if found {
		if cached, ok := data.(string); ok {
			return cached
		} else {
			log.Println("Error: Unable to retrieve from cache")
		}
	}

	link, err := db.Shares(path, false)
	if err != nil {
		log.Println(err)
		return ""
	}
	cache.Set(cache_path, link.URL, 0)
	return link.URL
}

/*
   Take a target and return the appropriate Entry item for that target.
   OR, return an empty Entry and a boolean flag that indicates the the
   requested target doesn't exist
*/
func get_target(arch string, software string, version string, target string) (dropbox.Entry, bool) {
	if target == "latest" {
		return get_latest(arch, software, version)
	} else {
		target_path := get_target_path(arch, version)
		files := get_files(cache_instance, db, target_path)
		for _, file := range files {
			archive := new(Archive)
			archive = archive.Init(file.Path)
			if archive.Software == software {
				if archive.Tag == target {
					return file, true
				}
			}
		}
	}
	return dropbox.Entry{}, false
}

/*
	Use the arch, software and version to find the latest
*/
func get_latest(arch string, software string, version string) (dropbox.Entry, bool) {
	target_path := get_target_path(arch, version)

	s := []string{}
	s = append(s, software)
	s = append(s, "-")
	search := strings.Join(s, "")

	mTime := time.Time(dropbox.DBTime{})
	var latest_file dropbox.Entry
	files := get_files(cache_instance, db, target_path)
	for _, file := range files {
		if strings.Contains(file.Path, search) {
			if time.Time(file.Modified).After(mTime) {
				mTime = time.Time(file.Modified)
				latest_file = file
			}
		}
	}
	if latest_file.Path == "" {
		return latest_file, false
	} else {
		return latest_file, true
	}
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"fmt"
	"log"
	//"net/http"
	"gopkg.in/yaml.v2"
	"github.com/stacktic/dropbox"
	//"github.com/ant0ine/go-json-rest/rest"
)

type Config struct {
	Api_secret string
	Api_key string
	Client_token string
	App_folder string
	Socket_file string
}

func main() {
	var err error
    var db *dropbox.Dropbox

	// The struct to store the configuration data
	config := Config{}

	data, read_err := ioutil.ReadFile("config.yml")
	if read_err != nil {
		log.Fatal(read_err)
	}

	yaml_err := yaml.Unmarshal(data, &config)
	if yaml_err != nil {
    	log.Fatal("error: %v", yaml_err)
    }
    fmt.Printf("--- config_file dump:\n%s\n\n", data)
    fmt.Printf("--- config dump:\n%s\n\n", config)

	db = dropbox.NewDropbox()
	db.SetAppInfo(config.Api_key, config.Api_secret)
	if len(config.Client_token) >= 1 {
		db.SetAccessToken(config.Client_token)
	} else {
		if err = db.Auth(); err != nil {
	        fmt.Println(err)
	        return
	    }
	    config.Client_token = db.AccessToken()
	    db.SetAccessToken(config.Client_token)
	    d, marshal_err := yaml.Marshal(&config)
        if marshal_err != nil {
            log.Fatal("error: %v", marshal_err)
        }
        ioutil.WriteFile("config.yml",[]byte(d), 0644)
	}

}
<commit_msg>Working on developing the web service to do the lookups<commit_after>package main

import (
	"io/ioutil"
	"strings"
	"fmt"
	"log"
    "time"
	"net/http"
	"gopkg.in/yaml.v2"
	"github.com/pmylund/go-cache"
	"github.com/stacktic/dropbox"
	"github.com/ant0ine/go-json-rest/rest"
)

type Config struct {
	Api_secret string
	Api_key string
	Client_token string
	App_folder string
	Socket_file string
}

// The struct to store the configuration data
var config Config
// 12 hour caching that cleans up every 15 minutes
var c *cache.Cache
//Link to dropbox
var db *dropbox.Dropbox

func main() {
	var err error

	config = Config{}
	c = cache.New(12*time.Hour, 15*time.Minute)
	db = dropbox.NewDropbox()

	data, read_err := ioutil.ReadFile("config.yml")
	if read_err != nil {
		log.Fatal(read_err)
	}

	yaml_err := yaml.Unmarshal(data, &config)
	if yaml_err != nil {
    	log.Fatal("error: %v", yaml_err)
    }
    fmt.Printf("--- config_file dump:\n%s\n\n", data)
    fmt.Printf("--- config dump:\n%s\n\n", config)

	db.SetAppInfo(config.Api_key, config.Api_secret)
	if len(config.Client_token) >= 1 {
		db.SetAccessToken(config.Client_token)
	} else {
		if err = db.Auth(); err != nil {
	        fmt.Println(err)
	        return
	    }
	    config.Client_token = db.AccessToken()
	    db.SetAccessToken(config.Client_token)
	    d, marshal_err := yaml.Marshal(&config)
        if marshal_err != nil {
            log.Fatal("error: %v", marshal_err)
        }
        ioutil.WriteFile("config.yml",[]byte(d), 0644)
	}

	// root_paths := get_directories(cache, db, "")
	// fmt.Printf("--- paths dump:\n%s\n\n", root_paths)

	// nightly_files := get_files(cache, db, "ARMv7")
	// fmt.Printf("--- paths dump:\n%s\n\n", nightly_files)

	// setup server to link
	api := rest.NewApi()
    api.Use(rest.DefaultDevStack...)
    router, err := rest.MakeRouter(
        rest.Get("/#arch/#software/#version/#target", lookup_target),
    )
    if err != nil {
        log.Fatal(err)
    }
    api.SetApp(router)
    log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}

func lookup_target(w rest.ResponseWriter, req *rest.Request) {
	arch := req.PathParam("arch")
	software := req.PathParam("software")
	version := req.PathParam("version")
	target := req.PathParam("target")
    w.WriteJson(map[string]string{"arch": arch, "software": software, "version": version, "target": target})
}

/*
	Get only a slice of the directories at a path
*/
func get_directories(cache *cache.Cache, db *dropbox.Dropbox, path string) []dropbox.Entry {
	return get(cache, db, path, true)
}

/*
	Get only a slice of the directories at a path
*/
func get_files(cache *cache.Cache, db *dropbox.Dropbox, path string) []dropbox.Entry {
	return get(cache, db, path, false)
}

func get(cache *cache.Cache, db *dropbox.Dropbox, path string, directories bool) []dropbox.Entry {
	// Use caching to reduce calls to the Dropbox API
	var cache_descriptor string
	if directories {
		cache_descriptor = "dirs:"
	} else {
		cache_descriptor = "files:"
	}
	s := []string{}
	s = append(s, cache_descriptor)
	s = append(s, path)
	cache_path := strings.Join(s,"")

	data, found := cache.Get(cache_path)
    if found {
    	if cached_paths, ok := data.([]dropbox.Entry); ok {
    		fmt.Printf("Loaded from cache")
		    return (cached_paths)
		} else {
			log.Fatal("Unable to retrieve from cache")
		}
    }

	entry, err := db.Metadata(path,true,false,"","",500);
	if err != nil {
		log.Fatal(err)
	}
	paths := make([]dropbox.Entry, 0)
	for i := 0; i < len(entry.Contents); i++ {
		entry := entry.Contents[i]
		if (directories) {
			if entry.IsDir {
				paths = append(paths, entry)
			}
		} else {
			if ! entry.IsDir {
				paths = append(paths, entry)
			}
		}
	}
	cache.Set(cache_path, paths, 0)
	return paths
}

/*
	Returns a shared link to dropbox file
*/
func get_link(cache *cache.Cache, db *dropbox.Dropbox, path string) *dropbox.Link {
	link, err := db.Shares(path, false)
	if err != nil {
		log.Fatal(err)
	}
	return link
}<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/davecgh/go-spew/spew"
	"github.com/nsf/termbox-go"
	"github.com/simulatedsimian/neo"
	"os"
	"path/filepath"
	"unicode/utf8"
)

func printAt(x, y int, s string, fg, bg termbox.Attribute) {
	for len(s) > 0 {
		r, rlen := utf8.DecodeRuneInString(s)
		termbox.SetCell(x, y, r, fg, bg)
		s = s[rlen:]
		x++
	}
}

func printAtDef(x, y int, s string) {
	printAt(x, y, s, termbox.ColorDefault, termbox.ColorDefault)
}

type treeNode struct {
	path     string
	info     os.FileInfo
	children []*treeNode
	parent   *treeNode
	expanded bool
	last     bool
	index    int
}

func createNodes(rootPath string, parent *treeNode) ([]*treeNode, error) {

	var res []*treeNode
	err := filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {

		if err == nil && path != rootPath {
			if len(res) > 0 {
				res[len(res)-1].last = false
			}
			res = append(res, &treeNode{path, info, nil, parent, false, true, len(res)})
			if info.IsDir() {
				return filepath.SkipDir
			}
		}
		return nil
	})

	return res, err
}

func populateChildren(node *treeNode) error {

	if node.info.IsDir() {
		children, err := createNodes(node.path, node)
		neo.PanicOnError(err)
		node.children = children
		node.expanded = true
	}
	return nil
}

func drawNode(node *treeNode) {

	preamble := ""
	for n := node; n.parent != nil; n = n.parent {
		if n.parent.last {
			preamble = "   " + preamble
		} else {
			preamble = "│  " + preamble
		}
	}
	fmt.Print(preamble)

	if node.last {
		fmt.Print("└─")
	} else {
		fmt.Print("├─")
	}

	if node.info.IsDir() {
		if node.expanded {
			fmt.Print("[-]")
		} else {
			fmt.Print("[+]")
		}
	} else {
		fmt.Print("── ")
	}

	fmt.Println(node.info.Name())
}

func drawNodes(nodes []*treeNode) {

	for _, node := range nodes {
		drawNode(node)
		if node.expanded && len(node.children) > 0 {
			drawNodes(node.children)
		}
	}
}

func drawNodesFrom(node *treeNode, count int) {
	siblings := node.parent.children
	for i, max := node.index, len(siblings); i < max; i++ {
		drawNode(siblings[i])
	}
}

func filltree(nodes []*treeNode) {

	for _, node := range nodes {
		populateChildren(node)
		if len(node.children) > 0 {
			filltree(node.children)
		}
	}
}

func test() {
	rootpath := "."
	if len(os.Args) > 1 {
		rootpath = os.Args[1]
	}

	nodes, err := createNodes(rootpath, nil)
	neo.PanicOnError(err)

	filltree(nodes)

	nodes[0].expanded = false

	drawNodes(nodes)

	spew.Dump(err)
	fmt.Println(err)
}

func termtest() {
	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()

	termbox.HideCursor()

	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			printAt(0, 1, fmt.Sprint(ev), termbox.ColorDefault, termbox.ColorDefault)
			termbox.Flush()

			switch ev.Key {
			case termbox.KeyEsc:
				return
			case termbox.KeyArrowUp:
			case termbox.KeyArrowDown:
			}

			termbox.Flush()

		case termbox.EventResize:
			x, y := ev.Width, ev.Height
			printAt(0, 0, fmt.Sprintf("[%d, %d] ", x, y), termbox.ColorDefault, termbox.ColorDefault)
			termbox.Flush()
		}
	}

	termbox.Flush()
}

func main() {
	test()
}
<commit_msg>added root node<commit_after>package main

import (
	"fmt"
	"github.com/davecgh/go-spew/spew"
	"github.com/nsf/termbox-go"
	"github.com/simulatedsimian/neo"
	"os"
	"path/filepath"
	"unicode/utf8"
)

func printAt(x, y int, s string, fg, bg termbox.Attribute) {
	for len(s) > 0 {
		r, rlen := utf8.DecodeRuneInString(s)
		termbox.SetCell(x, y, r, fg, bg)
		s = s[rlen:]
		x++
	}
}

func printAtDef(x, y int, s string) {
	printAt(x, y, s, termbox.ColorDefault, termbox.ColorDefault)
}

type treeNode struct {
	path     string
	info     os.FileInfo
	children []*treeNode
	parent   *treeNode
	expanded bool
	last     bool
	index    int
}

func createNodes(rootPath string, parent *treeNode) ([]*treeNode, error) {

	var res []*treeNode
	err := filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {

		if err == nil && path != rootPath {
			if len(res) > 0 {
				res[len(res)-1].last = false
			}
			res = append(res, &treeNode{path, info, nil, parent, false, true, len(res)})
			if info.IsDir() {
				return filepath.SkipDir
			}
		}
		return nil
	})

	return res, err
}

func populateChildren(node *treeNode) error {

	if node.info.IsDir() {
		children, err := createNodes(node.path, node)
		neo.PanicOnError(err)
		node.children = children
		node.expanded = true
	}
	return nil
}

func drawNode(node *treeNode) {

	preamble := ""
	for n := node; n.parent != nil; n = n.parent {
		if n.parent.last {
			preamble = "   " + preamble
		} else {
			preamble = "│  " + preamble
		}
	}
	fmt.Print(preamble)

	if node.last {
		fmt.Print("└─")
	} else {
		fmt.Print("├─")
	}

	if node.info.IsDir() {
		if node.expanded {
			fmt.Print("[-]")
		} else {
			fmt.Print("[+]")
		}
	} else {
		fmt.Print("── ")
	}

	fmt.Println(node.info.Name())
}

func drawNodes(nodes []*treeNode) {

	for _, node := range nodes {
		drawNode(node)
		if node.expanded && len(node.children) > 0 {
			drawNodes(node.children)
		}
	}
}

func drawNodesFrom(node *treeNode, count int) {
	siblings := node.parent.children
	for i, max := node.index, len(siblings); i < max; i++ {
		drawNode(siblings[i])
	}
}

func filltree(nodes []*treeNode) {

	for _, node := range nodes {
		populateChildren(node)
		if len(node.children) > 0 {
			filltree(node.children)
		}
	}
}

func test() {
	rootpath := "."
	if len(os.Args) > 1 {
		rootpath = os.Args[1]
	}

	rootInfo, err := os.Stat(rootpath)
	neo.PanicOnError(err)

	rootNode := &treeNode{rootpath, rootInfo, nil, nil, true, true, 1}

	nodes, err := createNodes(rootpath, rootNode)
	neo.PanicOnError(err)
	filltree(nodes)
	rootNode.children = nodes

	var root []*treeNode
	root = append(root, rootNode)

	//	nodes[0].expanded = false

	drawNodes(root)

	spew.Dump(err)
	fmt.Println(err)
}

func termtest() {
	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()

	termbox.HideCursor()

	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			printAt(0, 1, fmt.Sprint(ev), termbox.ColorDefault, termbox.ColorDefault)
			termbox.Flush()

			switch ev.Key {
			case termbox.KeyEsc:
				return
			case termbox.KeyArrowUp:
			case termbox.KeyArrowDown:
			}

			termbox.Flush()

		case termbox.EventResize:
			x, y := ev.Width, ev.Height
			printAt(0, 0, fmt.Sprintf("[%d, %d] ", x, y), termbox.ColorDefault, termbox.ColorDefault)
			termbox.Flush()
		}
	}

	termbox.Flush()
}

func main() {
	test()
}
<|endoftext|>


<commit_before>//  Copyright 2016 The goscope 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 gui

import (
	"image"
	"image/color"
	"image/png"
	"os"
	"sort"

	"github.com/zagrodzki/goscope/scope"
)

type aggrPoint struct {
	sumY  int
	sizeY int
}

func (p aggrPoint) add(y int) aggrPoint {
	p.sumY += y
	p.sizeY++
	return p
}

func (p aggrPoint) toPoint(x int) image.Point {
	return image.Point{x, p.sumY / p.sizeY}
}

// ZeroAndScale represents the position of zero and the scale of the plot
type ZeroAndScale struct {
	// the position of Y=0 (0 <= Zero <= 1) given as
	// the fraction of the window height counting from the top
	Zero float64
	// scale of the plot in sample units per pixel
	Scale float64
}

func samplesToPoints(samples []scope.Sample, zeroAndScale ZeroAndScale, start, end image.Point) []image.Point {
	if len(samples) == 0 {
		return nil
	}

	sampleMaxY := zeroAndScale.Zero * zeroAndScale.Scale
	sampleMinY := (zeroAndScale.Zero - 1) * zeroAndScale.Scale
	sampleWidthX := float64(len(samples) - 1)
	sampleWidthY := sampleMaxY - sampleMinY

	pixelStartX := float64(start.X)
	pixelEndY := float64(end.Y)
	pixelWidthX := float64(end.X - start.X)
	pixelWidthY := float64(end.Y - start.Y)

	aggrPoints := make(map[int]aggrPoint)
	for i, y := range samples {
		mapX := int(pixelStartX + float64(i)/sampleWidthX*pixelWidthX)
		mapY := int(pixelEndY - float64(y-scope.Sample(sampleMinY))/sampleWidthY*pixelWidthY)
		aggrPoints[mapX] = aggrPoints[mapX].add(mapY)
	}
	var points []image.Point
	for x, p := range aggrPoints {
		points = append(points, p.toPoint(x))
	}

	return points
}

// Plot represents the entire plotting area.
type Plot struct {
	*image.RGBA
}

// Fill fills the plot with a color.
func (plot Plot) Fill(col color.RGBA) {
	pix := plot.Pix
	for i := 0; i < len(pix); i = i + 4 {
		pix[i] = col.R
		pix[i+1] = col.G
		pix[i+2] = col.B
		pix[i+3] = col.A
	}
}

func isInside(x, y int, start, end image.Point) bool {
	return x >= start.X && x <= end.X && y >= start.Y && y <= end.Y
}

// DrawLine draws a straight line from pixel p1 to p2.
// Only the line fragment inside the image rectangle defined by
// starting (upper left) and ending (lower right) pixel is drawn.
func (plot Plot) DrawLine(p1, p2 image.Point, start, end image.Point, col color.RGBA) {
	if p1.X == p2.X { // vertical line
		for i := min(p1.Y, p2.Y); i <= max(p1.Y, p2.Y); i++ {
			plot.SetRGBA(p1.X, i, col)
		}
		return
	}

	// Calculating the parameters of the equation
	// of the straight line (in the form y=a*x+b)
	// passing through p1 and p2.

	// slope of the line
	a := float64(p1.Y-p2.Y) / float64(p1.X-p2.X)
	// intercept of the line
	b := float64(p1.Y) - float64(p1.X)*a

	// To avoid visual "gaps" between the pixels we switch on,
	// we draw the line in one of two ways.
	if abs(p1.X-p2.X) >= abs(p1.Y-p2.Y) {
		// If the line is more horizontal than vertical,
		// for every pixel column between p1 and p2
		// we find and switch on the pixel closest to y=a*x+b
		for i := min(p1.X, p2.X); i <= max(p1.X, p2.X); i++ {
			y := int(a*float64(i) + b)
			if isInside(i, y, start, end) {
				plot.SetRGBA(i, y, col)
			}
		}
	} else {
		// If the line is more vertical than horizontal,
		// for every pixel row between p1 and p2
		// we find and switch on the pixel closest to y=a*x+b
		for i := min(p1.Y, p2.Y); i <= max(p1.Y, p2.Y); i++ {
			x := int((float64(i) - b) / a)
			if isInside(x, i, start, end) {
				plot.SetRGBA(x, i, col)
			}
		}
	}
}

// DrawSamples draws samples in the image rectangle defined by
// starting (upper left) and ending (lower right) pixel.
func (plot Plot) DrawSamples(samples []scope.Sample, zeroAndScale ZeroAndScale, start, end image.Point, col color.RGBA) {
	points := samplesToPoints(samples, zeroAndScale, start, end)
	sort.Sort(pointsByX(points))
	for i := 1; i < len(points); i++ {
		plot.DrawLine(points[i-1], points[i], start, end, col)
	}
}

// DrawAll draws samples from all the channels into one image.
func (plot Plot) DrawAll(samples map[scope.ChanID][]scope.Sample, zas map[scope.ChanID]ZeroAndScale, cols map[scope.ChanID]color.RGBA) {
	b := plot.Bounds()
	x1 := b.Min.X + 10
	x2 := b.Max.X - 10
	y1 := b.Min.Y + 10
	y2 := b.Min.Y + 10 + int((b.Max.Y-b.Min.Y-10*(len(samples)+1))/len(samples))
	step := y2 - b.Min.Y
	for id, v := range samples {
		plot.DrawSamples(v, zas[id], image.Point{x1, y1}, image.Point{x2, y2}, cols[id])
		y1 = y1 + step
		y2 = y2 + step
	}
}

// CreatePlot plots samples from the device.
func CreatePlot(dev scope.Device, zas map[scope.ChanID]ZeroAndScale) (Plot, error) {
	plot := Plot{image.NewRGBA(image.Rect(0, 0, 800, 600))}

	data, stop, err := dev.StartSampling()
	defer stop()
	if err != nil {
		return plot, err
	}
	samples := (<-data).Samples

	colWhite := color.RGBA{255, 255, 255, 255}
	colRed := color.RGBA{255, 0, 0, 255}
	colGreen := color.RGBA{0, 255, 0, 255}
	colBlue := color.RGBA{0, 0, 255, 255}
	colBlack := color.RGBA{0, 0, 0, 255}
	chanCols := [4]color.RGBA{colRed, colGreen, colBlue, colBlack}

	plot.Fill(colWhite)

	cols := make(map[scope.ChanID]color.RGBA)
	next := 0
	for _, id := range dev.Channels() {
		if _, exists := zas[id]; !exists {
			zas[id] = ZeroAndScale{0.5, 2}
		}
		cols[id] = chanCols[next]
		next = (next + 1) % 4
	}
	plot.DrawAll(samples, zas, cols)
	return plot, nil
}

// PlotToPng creates a plot of the samples from the device
// and saves it as PNG.
func PlotToPng(dev scope.Device, zas map[scope.ChanID]ZeroAndScale, outputFile string) error {
	plot, err := CreatePlot(dev, zas)
	if err != nil {
		return err
	}

	f, err := os.Create(outputFile)
	if err != nil {
		return err
	}
	defer f.Close()
	png.Encode(f, plot)
	return nil
}

type pointsByX []image.Point

func (a pointsByX) Len() int {
	return len(a)
}

func (a pointsByX) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func (a pointsByX) Less(i, j int) bool {
	return a[i].X < a[j].X
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func abs(a int) int {
	if a < 0 {
		return -a
	}
	return a
}
<commit_msg>experiment - add a bgCache to test the efficiency of copy() vs setting pixels one by one. copy() saves about 50% CPU compared to a for loop and setting the colors by hand.<commit_after>//  Copyright 2016 The goscope 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 gui

import (
	"image"
	"image/color"
	"image/png"
	"os"
	"sort"

	"github.com/zagrodzki/goscope/scope"
)

type aggrPoint struct {
	sumY  int
	sizeY int
}

func (p aggrPoint) add(y int) aggrPoint {
	p.sumY += y
	p.sizeY++
	return p
}

func (p aggrPoint) toPoint(x int) image.Point {
	return image.Point{x, p.sumY / p.sizeY}
}

// ZeroAndScale represents the position of zero and the scale of the plot
type ZeroAndScale struct {
	// the position of Y=0 (0 <= Zero <= 1) given as
	// the fraction of the window height counting from the top
	Zero float64
	// scale of the plot in sample units per pixel
	Scale float64
}

func samplesToPoints(samples []scope.Sample, zeroAndScale ZeroAndScale, start, end image.Point) []image.Point {
	if len(samples) == 0 {
		return nil
	}

	sampleMaxY := zeroAndScale.Zero * zeroAndScale.Scale
	sampleMinY := (zeroAndScale.Zero - 1) * zeroAndScale.Scale
	sampleWidthX := float64(len(samples) - 1)
	sampleWidthY := sampleMaxY - sampleMinY

	pixelStartX := float64(start.X)
	pixelEndY := float64(end.Y)
	pixelWidthX := float64(end.X - start.X)
	pixelWidthY := float64(end.Y - start.Y)

	aggrPoints := make(map[int]aggrPoint)
	for i, y := range samples {
		mapX := int(pixelStartX + float64(i)/sampleWidthX*pixelWidthX)
		mapY := int(pixelEndY - float64(y-scope.Sample(sampleMinY))/sampleWidthY*pixelWidthY)
		aggrPoints[mapX] = aggrPoints[mapX].add(mapY)
	}
	var points []image.Point
	for x, p := range aggrPoints {
		points = append(points, p.toPoint(x))
	}

	return points
}

// Plot represents the entire plotting area.
type Plot struct {
	*image.RGBA
}

var (
	bgCache *image.RGBA
	bgColor color.RGBA
)

func background(r image.Rectangle, col color.RGBA) *image.RGBA {
	img := image.NewRGBA(r)
	pix := img.Pix
	for i := 0; i < len(pix); i = i + 4 {
		pix[i] = col.R
		pix[i+1] = col.G
		pix[i+2] = col.B
		pix[i+3] = col.A
	}
	return img
}

// Fill fills the plot with a background image of the same size.
func (plot Plot) Fill(col color.RGBA) {
	if bgCache == nil || bgCache.Bounds() != plot.Bounds() || bgColor != col {
		bgCache = background(plot.Bounds(), col)
		bgColor = col
	}
	copy(plot.Pix, bgCache.Pix)
}

func isInside(x, y int, start, end image.Point) bool {
	return x >= start.X && x <= end.X && y >= start.Y && y <= end.Y
}

// DrawLine draws a straight line from pixel p1 to p2.
// Only the line fragment inside the image rectangle defined by
// starting (upper left) and ending (lower right) pixel is drawn.
func (plot Plot) DrawLine(p1, p2 image.Point, start, end image.Point, col color.RGBA) {
	if p1.X == p2.X { // vertical line
		for i := min(p1.Y, p2.Y); i <= max(p1.Y, p2.Y); i++ {
			plot.SetRGBA(p1.X, i, col)
		}
		return
	}

	// Calculating the parameters of the equation
	// of the straight line (in the form y=a*x+b)
	// passing through p1 and p2.

	// slope of the line
	a := float64(p1.Y-p2.Y) / float64(p1.X-p2.X)
	// intercept of the line
	b := float64(p1.Y) - float64(p1.X)*a

	// To avoid visual "gaps" between the pixels we switch on,
	// we draw the line in one of two ways.
	if abs(p1.X-p2.X) >= abs(p1.Y-p2.Y) {
		// If the line is more horizontal than vertical,
		// for every pixel column between p1 and p2
		// we find and switch on the pixel closest to y=a*x+b
		for i := min(p1.X, p2.X); i <= max(p1.X, p2.X); i++ {
			y := int(a*float64(i) + b)
			if isInside(i, y, start, end) {
				plot.SetRGBA(i, y, col)
			}
		}
	} else {
		// If the line is more vertical than horizontal,
		// for every pixel row between p1 and p2
		// we find and switch on the pixel closest to y=a*x+b
		for i := min(p1.Y, p2.Y); i <= max(p1.Y, p2.Y); i++ {
			x := int((float64(i) - b) / a)
			if isInside(x, i, start, end) {
				plot.SetRGBA(x, i, col)
			}
		}
	}
}

// DrawSamples draws samples in the image rectangle defined by
// starting (upper left) and ending (lower right) pixel.
func (plot Plot) DrawSamples(samples []scope.Sample, zeroAndScale ZeroAndScale, start, end image.Point, col color.RGBA) {
	points := samplesToPoints(samples, zeroAndScale, start, end)
	sort.Sort(pointsByX(points))
	for i := 1; i < len(points); i++ {
		plot.DrawLine(points[i-1], points[i], start, end, col)
	}
}

// DrawAll draws samples from all the channels into one image.
func (plot Plot) DrawAll(samples map[scope.ChanID][]scope.Sample, zas map[scope.ChanID]ZeroAndScale, cols map[scope.ChanID]color.RGBA) {
	b := plot.Bounds()
	x1 := b.Min.X + 10
	x2 := b.Max.X - 10
	y1 := b.Min.Y + 10
	y2 := b.Min.Y + 10 + int((b.Max.Y-b.Min.Y-10*(len(samples)+1))/len(samples))
	step := y2 - b.Min.Y
	for id, v := range samples {
		plot.DrawSamples(v, zas[id], image.Point{x1, y1}, image.Point{x2, y2}, cols[id])
		y1 = y1 + step
		y2 = y2 + step
	}
}

// CreatePlot plots samples from the device.
func CreatePlot(dev scope.Device, zas map[scope.ChanID]ZeroAndScale) (Plot, error) {
	plot := Plot{image.NewRGBA(image.Rect(0, 0, 800, 600))}

	data, stop, err := dev.StartSampling()
	defer stop()
	if err != nil {
		return plot, err
	}
	samples := (<-data).Samples

	colWhite := color.RGBA{255, 255, 255, 255}
	colRed := color.RGBA{255, 0, 0, 255}
	colGreen := color.RGBA{0, 255, 0, 255}
	colBlue := color.RGBA{0, 0, 255, 255}
	colBlack := color.RGBA{0, 0, 0, 255}
	chanCols := [4]color.RGBA{colRed, colGreen, colBlue, colBlack}

	plot.Fill(colWhite)

	cols := make(map[scope.ChanID]color.RGBA)
	next := 0
	for _, id := range dev.Channels() {
		if _, exists := zas[id]; !exists {
			zas[id] = ZeroAndScale{0.5, 2}
		}
		cols[id] = chanCols[next]
		next = (next + 1) % 4
	}
	plot.DrawAll(samples, zas, cols)
	return plot, nil
}

// PlotToPng creates a plot of the samples from the device
// and saves it as PNG.
func PlotToPng(dev scope.Device, zas map[scope.ChanID]ZeroAndScale, outputFile string) error {
	plot, err := CreatePlot(dev, zas)
	if err != nil {
		return err
	}

	f, err := os.Create(outputFile)
	if err != nil {
		return err
	}
	defer f.Close()
	png.Encode(f, plot)
	return nil
}

type pointsByX []image.Point

func (a pointsByX) Len() int {
	return len(a)
}

func (a pointsByX) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func (a pointsByX) Less(i, j int) bool {
	return a[i].X < a[j].X
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func abs(a int) int {
	if a < 0 {
		return -a
	}
	return a
}
<|endoftext|>


<commit_before>package rest

import (
	"crypto/tls"
	"fmt"
	"net/http"
	"os"

	errors "github.com/go-openapi/errors"
	runtime "github.com/go-openapi/runtime"
	middleware "github.com/go-openapi/runtime/middleware"
	"github.com/golang/glog"
	gmiddleware "github.com/gorilla/handlers"
	"github.com/rs/cors"
	graceful "github.com/tylerb/graceful"

	apipkg "github.com/sapcc/kubernikus/pkg/api"
	"github.com/sapcc/kubernikus/pkg/api/handlers"
	"github.com/sapcc/kubernikus/pkg/api/rest/operations"
)

// This file is safe to edit. Once it exists it will not be overwritten

//go:generate swagger generate server --target ../pkg/api --name kubernikus --spec ../swagger.yml --server-package rest --principal models.Principal --exclude-main

func configureFlags(api *operations.KubernikusAPI) {
	// api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{ ... }
}

func configureAPI(api *operations.KubernikusAPI) http.Handler {
	// configure the api here
	api.ServeError = errors.ServeError

	// Set your custom logger if needed. Default one is log.Printf
	// Expected interface func(string, ...interface{})
	//
	// Example:
	api.Logger = func(msg string, args ...interface{}) {
		glog.InfoDepth(2, fmt.Sprintf(msg, args...))
	}

	api.JSONConsumer = runtime.JSONConsumer()

	api.JSONProducer = runtime.JSONProducer()

	// Applies when the "x-auth-token" header is set
	api.KeystoneAuth = keystoneAuth()

	rt := &apipkg.Runtime{Clients: NewKubeClients()}

	api.ListAPIVersionsHandler = handlers.NewListAPIVersions(rt)
	api.ListClustersHandler = handlers.NewListClusters(rt)
	api.CreateClusterHandler = handlers.NewCreateCluster(rt)
	api.ShowClusterHandler = handlers.NewShowCluster(rt)
	api.TerminateClusterHandler = handlers.NewTerminateCluster(rt)
	api.UpdateClusterHandler = handlers.NewUpdateCluster(rt)

	api.ServerShutdown = func() {}

	return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}

// The TLS configuration before HTTPS server starts.
func configureTLS(tlsConfig *tls.Config) {
	// Make all necessary changes to the TLS configuration here.
}

// As soon as server is initialized but not run yet, this function will be called.
// If you need to modify a config, store server instance to stop it individually later, this is the place.
// This function can be called multiple times, depending on the number of serving schemes.
// scheme value will be set accordingly: "http", "https" or "unix"
func configureServer(s *graceful.Server, scheme, addr string) {
}

// The middleware configuration is for the handler executors. These do not apply to the swagger.json document.
// The middleware executes after routing but before authentication, binding and validation
func setupMiddlewares(handler http.Handler) http.Handler {
	return middleware.Redoc(middleware.RedocOpts{}, handler)
}

// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
// So this is a good place to plug in a panic handling middleware, logging and metrics
func setupGlobalMiddleware(handler http.Handler) http.Handler {
	c := cors.New(cors.Options{
		AllowedHeaders: []string{"X-Auth-Token"},
		AllowedMethods: []string{"GET", "HEAD", "POST", "DELETE"},
	})
	return gmiddleware.LoggingHandler(os.Stdout, handlers.RootHandler(c.Handler(handler)))
}
<commit_msg>Allow Content-Type and PUT via CORS<commit_after>package rest

import (
	"crypto/tls"
	"fmt"
	"net/http"
	"os"

	errors "github.com/go-openapi/errors"
	runtime "github.com/go-openapi/runtime"
	middleware "github.com/go-openapi/runtime/middleware"
	"github.com/golang/glog"
	gmiddleware "github.com/gorilla/handlers"
	"github.com/rs/cors"
	graceful "github.com/tylerb/graceful"

	apipkg "github.com/sapcc/kubernikus/pkg/api"
	"github.com/sapcc/kubernikus/pkg/api/handlers"
	"github.com/sapcc/kubernikus/pkg/api/rest/operations"
)

// This file is safe to edit. Once it exists it will not be overwritten

//go:generate swagger generate server --target ../pkg/api --name kubernikus --spec ../swagger.yml --server-package rest --principal models.Principal --exclude-main

func configureFlags(api *operations.KubernikusAPI) {
	// api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{ ... }
}

func configureAPI(api *operations.KubernikusAPI) http.Handler {
	// configure the api here
	api.ServeError = errors.ServeError

	// Set your custom logger if needed. Default one is log.Printf
	// Expected interface func(string, ...interface{})
	//
	// Example:
	api.Logger = func(msg string, args ...interface{}) {
		glog.InfoDepth(2, fmt.Sprintf(msg, args...))
	}

	api.JSONConsumer = runtime.JSONConsumer()

	api.JSONProducer = runtime.JSONProducer()

	// Applies when the "x-auth-token" header is set
	api.KeystoneAuth = keystoneAuth()

	rt := &apipkg.Runtime{Clients: NewKubeClients()}

	api.ListAPIVersionsHandler = handlers.NewListAPIVersions(rt)
	api.ListClustersHandler = handlers.NewListClusters(rt)
	api.CreateClusterHandler = handlers.NewCreateCluster(rt)
	api.ShowClusterHandler = handlers.NewShowCluster(rt)
	api.TerminateClusterHandler = handlers.NewTerminateCluster(rt)
	api.UpdateClusterHandler = handlers.NewUpdateCluster(rt)

	api.ServerShutdown = func() {}

	return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}

// The TLS configuration before HTTPS server starts.
func configureTLS(tlsConfig *tls.Config) {
	// Make all necessary changes to the TLS configuration here.
}

// As soon as server is initialized but not run yet, this function will be called.
// If you need to modify a config, store server instance to stop it individually later, this is the place.
// This function can be called multiple times, depending on the number of serving schemes.
// scheme value will be set accordingly: "http", "https" or "unix"
func configureServer(s *graceful.Server, scheme, addr string) {
}

// The middleware configuration is for the handler executors. These do not apply to the swagger.json document.
// The middleware executes after routing but before authentication, binding and validation
func setupMiddlewares(handler http.Handler) http.Handler {
	return middleware.Redoc(middleware.RedocOpts{}, handler)
}

// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document.
// So this is a good place to plug in a panic handling middleware, logging and metrics
func setupGlobalMiddleware(handler http.Handler) http.Handler {
	c := cors.New(cors.Options{
		AllowedHeaders: []string{"X-Auth-Token", "Content-Type"},
		AllowedMethods: []string{"GET", "HEAD", "POST", "DELETE", "PUT"},
	})
	return gmiddleware.LoggingHandler(os.Stdout, handlers.RootHandler(c.Handler(handler)))
}
<|endoftext|>


<commit_before>package installconfig

import (
	"github.com/pborman/uuid"

	"github.com/openshift/installer/pkg/asset"
)

type clusterID struct{}

var _ asset.Asset = (*clusterID)(nil)

// Dependencies returns no dependencies.
func (a *clusterID) Dependencies() []asset.Asset {
	return []asset.Asset{}
}

// Generate generates a new UUID
func (a *clusterID) Generate(map[asset.Asset]*asset.State) (*asset.State, error) {
	return &asset.State{
		Contents: []asset.Content{
			{Data: []byte(uuid.NewUUID().String())},
		},
	}, nil
}
<commit_msg>uuid: Use random (version 4) uuid for better randomness.<commit_after>package installconfig

import (
	"github.com/pborman/uuid"

	"github.com/openshift/installer/pkg/asset"
)

type clusterID struct{}

var _ asset.Asset = (*clusterID)(nil)

// Dependencies returns no dependencies.
func (a *clusterID) Dependencies() []asset.Asset {
	return []asset.Asset{}
}

// Generate generates a new UUID
func (a *clusterID) Generate(map[asset.Asset]*asset.State) (*asset.State, error) {
	return &asset.State{
		Contents: []asset.Content{
			{Data: []byte(uuid.New())},
		},
	}, nil
}
<|endoftext|>


<commit_before>package daemon

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/datawire/dlib/dexec"
	"github.com/datawire/dlib/dgroup"
	"github.com/datawire/dlib/dlog"
	"github.com/datawire/telepresence2/v2/pkg/client/daemon/dns"
	"github.com/datawire/telepresence2/v2/pkg/subnet"
)

const kubernetesZone = "cluster.local"

type resolveFile struct {
	domain      string
	nameservers []net.IP
	search      []string
}

func readResolveFile(fileName string) (*resolveFile, error) {
	fl, err := os.Open(fileName)
	if err != nil {
		return nil, err
	}
	defer fl.Close()
	sc := bufio.NewScanner(fl)
	rf := resolveFile{}
	line := 0
	for sc.Scan() {
		line++
		txt := strings.TrimSpace(sc.Text())
		if len(txt) == 0 || strings.HasPrefix(txt, "#") {
			continue
		}
		fields := strings.Fields(txt)
		fc := len(fields)
		if fc == 0 {
			continue
		}
		key := fields[0]
		if fc == 1 {
			return nil, fmt.Errorf("%q must have a value at %s line %d", key, fileName, line)
		}
		value := fields[1]
		switch key {
		case "domain":
			if fc != 2 {
				return nil, fmt.Errorf("%q can only have one value at %s line %d", key, fileName, line)
			}
			rf.domain = value
		case "nameserver":
			if fc != 2 {
				return nil, fmt.Errorf("%q can only have one value at %s line %d", key, fileName, line)
			}
			ip := net.ParseIP(value)
			if ip == nil {
				return nil, fmt.Errorf("value %q for %q is not a valid IP at %s line %d", value, key, fileName, line)
			}
			rf.nameservers = append(rf.nameservers, ip)
		case "search":
			rf.search = fields[1:]
		default:
			// This reader doesn't do options just yet
			return nil, fmt.Errorf("%q is not a recognized key at %s line %d", key, fileName, line)
		}
	}
	return &rf, nil
}

func (r *resolveFile) write(fileName string) error {
	buf := bytes.NewBufferString("# Generated by telepresence\n")
	if r.domain != "" {
		fmt.Fprintf(buf, "domain %s\n", r.domain)
	}
	for _, ns := range r.nameservers {
		fmt.Fprintf(buf, "nameserver %s\n", ns)
	}

	if len(r.search) > 0 {
		buf.WriteString("search")
		for _, s := range r.search {
			buf.WriteByte(' ')
			buf.WriteString(s)
		}
		buf.WriteByte('\n')
	}
	return ioutil.WriteFile(fileName, buf.Bytes(), 0644)
}

func (r *resolveFile) setSearchPaths(paths ...string) {
	ps := make([]string, 0, len(paths)+1)
	for _, p := range paths {
		p = strings.TrimSuffix(p, ".")
		if len(p) > 0 && p != r.domain {
			ps = append(ps, p)
		}
	}
	ps = append(ps, r.domain)
	r.search = ps
}

// dnsServerWorker places a file under the /etc/resolver directory so that it is picked up by the
// MacOS resolver. The file is configured with a single nameserver that points to the local IP
// that the Telepresence DNS server listens to. The file is removed, and the DNS is flushed when
// the worker terminates
//
// For more information about /etc/resolver files, please view the man pages available at
//
//   man 5 resolver
//
// or, if not on a Mac, follow this link: https://www.manpagez.com/man/5/resolver/
func (o *outbound) dnsServerWorker(c context.Context, onReady func()) error {
	resolverDirName := filepath.Join("/etc", "resolver")
	resolverFileName := filepath.Join(resolverDirName, "telepresence.local")

	err := os.MkdirAll(resolverDirName, 0755)
	if err != nil {
		return err
	}

	loopBackCIDR, err := subnet.FindAvailableLoopBackClassC()
	if err != nil {
		return err
	}

	// Place the DNS server in the private network at x.x.x.2
	dnsIP := make(net.IP, len(loopBackCIDR.IP))
	copy(dnsIP, loopBackCIDR.IP)
	dnsIP[len(dnsIP)-1] = 2

	rf := resolveFile{
		domain:      kubernetesZone,
		nameservers: []net.IP{dnsIP},
		search:      []string{kubernetesZone},
	}
	if err = rf.write(resolverFileName); err != nil {
		return err
	}
	dlog.Infof(c, "Generated new %s", resolverFileName)

	o.setSearchPathFunc = func(c context.Context, paths []string) {
		dlog.Infof(c, "setting search paths %s", strings.Join(paths, " "))
		rf, err := readResolveFile(resolverFileName)
		if err != nil {
			dlog.Error(c, err)
			return
		}
		rf.setSearchPaths(paths...)

		// Versions prior to Big Sur will not trigger an update unless the resolver file
		// is removed and recreated.
		_ = os.Remove(resolverFileName)

		if err = rf.write(resolverFileName); err != nil {
			dlog.Error(c, err)
			return
		}
	}

	// Up our loopback device
	if err = dexec.CommandContext(c, "ifconfig", "lo0", "alias", dnsIP.String(), "up").Run(); err != nil {
		return err
	}

	defer func() {
		// recover a panic. The DNS must be reset, no matter what
		r := recover()
		// Remove the resolver file
		_ = os.Remove(resolverFileName)

		// Remove loopback device
		_ = exec.Command("ifconfig", "lo0", "-alias", dnsIP.String()).Run()

		dns.Flush()
		if r != nil {
			// Propagate panic, it's of no interest here
			panic(r)
		}
	}()

	// Start local DNS server
	initDone := &sync.WaitGroup{}
	initDone.Add(1)
	g := dgroup.NewGroup(c, dgroup.GroupConfig{})
	g.Go("Server", func(c context.Context) error {
		v := dns.NewServer(c, []*net.UDPAddr{{IP: dnsIP, Port: 53}}, "", func(domain string) string {
			if r := o.resolveNoNS(domain); r != nil {
				return r.Ip
			}
			return ""
		})
		return v.Run(c, initDone)
	})
	initDone.Wait()

	dns.Flush()

	onReady()
	return g.Wait()
}
<commit_msg>daemon: Remove superflous recover/panic<commit_after>package daemon

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"

	"github.com/datawire/dlib/dexec"
	"github.com/datawire/dlib/dgroup"
	"github.com/datawire/dlib/dlog"
	"github.com/datawire/telepresence2/v2/pkg/client/daemon/dns"
	"github.com/datawire/telepresence2/v2/pkg/subnet"
)

const kubernetesZone = "cluster.local"

type resolveFile struct {
	domain      string
	nameservers []net.IP
	search      []string
}

func readResolveFile(fileName string) (*resolveFile, error) {
	fl, err := os.Open(fileName)
	if err != nil {
		return nil, err
	}
	defer fl.Close()
	sc := bufio.NewScanner(fl)
	rf := resolveFile{}
	line := 0
	for sc.Scan() {
		line++
		txt := strings.TrimSpace(sc.Text())
		if len(txt) == 0 || strings.HasPrefix(txt, "#") {
			continue
		}
		fields := strings.Fields(txt)
		fc := len(fields)
		if fc == 0 {
			continue
		}
		key := fields[0]
		if fc == 1 {
			return nil, fmt.Errorf("%q must have a value at %s line %d", key, fileName, line)
		}
		value := fields[1]
		switch key {
		case "domain":
			if fc != 2 {
				return nil, fmt.Errorf("%q can only have one value at %s line %d", key, fileName, line)
			}
			rf.domain = value
		case "nameserver":
			if fc != 2 {
				return nil, fmt.Errorf("%q can only have one value at %s line %d", key, fileName, line)
			}
			ip := net.ParseIP(value)
			if ip == nil {
				return nil, fmt.Errorf("value %q for %q is not a valid IP at %s line %d", value, key, fileName, line)
			}
			rf.nameservers = append(rf.nameservers, ip)
		case "search":
			rf.search = fields[1:]
		default:
			// This reader doesn't do options just yet
			return nil, fmt.Errorf("%q is not a recognized key at %s line %d", key, fileName, line)
		}
	}
	return &rf, nil
}

func (r *resolveFile) write(fileName string) error {
	buf := bytes.NewBufferString("# Generated by telepresence\n")
	if r.domain != "" {
		fmt.Fprintf(buf, "domain %s\n", r.domain)
	}
	for _, ns := range r.nameservers {
		fmt.Fprintf(buf, "nameserver %s\n", ns)
	}

	if len(r.search) > 0 {
		buf.WriteString("search")
		for _, s := range r.search {
			buf.WriteByte(' ')
			buf.WriteString(s)
		}
		buf.WriteByte('\n')
	}
	return ioutil.WriteFile(fileName, buf.Bytes(), 0644)
}

func (r *resolveFile) setSearchPaths(paths ...string) {
	ps := make([]string, 0, len(paths)+1)
	for _, p := range paths {
		p = strings.TrimSuffix(p, ".")
		if len(p) > 0 && p != r.domain {
			ps = append(ps, p)
		}
	}
	ps = append(ps, r.domain)
	r.search = ps
}

// dnsServerWorker places a file under the /etc/resolver directory so that it is picked up by the
// MacOS resolver. The file is configured with a single nameserver that points to the local IP
// that the Telepresence DNS server listens to. The file is removed, and the DNS is flushed when
// the worker terminates
//
// For more information about /etc/resolver files, please view the man pages available at
//
//   man 5 resolver
//
// or, if not on a Mac, follow this link: https://www.manpagez.com/man/5/resolver/
func (o *outbound) dnsServerWorker(c context.Context, onReady func()) error {
	resolverDirName := filepath.Join("/etc", "resolver")
	resolverFileName := filepath.Join(resolverDirName, "telepresence.local")

	err := os.MkdirAll(resolverDirName, 0755)
	if err != nil {
		return err
	}

	loopBackCIDR, err := subnet.FindAvailableLoopBackClassC()
	if err != nil {
		return err
	}

	// Place the DNS server in the private network at x.x.x.2
	dnsIP := make(net.IP, len(loopBackCIDR.IP))
	copy(dnsIP, loopBackCIDR.IP)
	dnsIP[len(dnsIP)-1] = 2

	rf := resolveFile{
		domain:      kubernetesZone,
		nameservers: []net.IP{dnsIP},
		search:      []string{kubernetesZone},
	}
	if err = rf.write(resolverFileName); err != nil {
		return err
	}
	dlog.Infof(c, "Generated new %s", resolverFileName)

	o.setSearchPathFunc = func(c context.Context, paths []string) {
		dlog.Infof(c, "setting search paths %s", strings.Join(paths, " "))
		rf, err := readResolveFile(resolverFileName)
		if err != nil {
			dlog.Error(c, err)
			return
		}
		rf.setSearchPaths(paths...)

		// Versions prior to Big Sur will not trigger an update unless the resolver file
		// is removed and recreated.
		_ = os.Remove(resolverFileName)

		if err = rf.write(resolverFileName); err != nil {
			dlog.Error(c, err)
			return
		}
	}

	// Up our loopback device
	if err = dexec.CommandContext(c, "ifconfig", "lo0", "alias", dnsIP.String(), "up").Run(); err != nil {
		return err
	}

	defer func() {
		// Remove the resolver file
		_ = os.Remove(resolverFileName)

		// Remove loopback device
		_ = exec.Command("ifconfig", "lo0", "-alias", dnsIP.String()).Run()

		dns.Flush()
	}()

	// Start local DNS server
	initDone := &sync.WaitGroup{}
	initDone.Add(1)
	g := dgroup.NewGroup(c, dgroup.GroupConfig{})
	g.Go("Server", func(c context.Context) error {
		v := dns.NewServer(c, []*net.UDPAddr{{IP: dnsIP, Port: 53}}, "", func(domain string) string {
			if r := o.resolveNoNS(domain); r != nil {
				return r.Ip
			}
			return ""
		})
		return v.Run(c, initDone)
	})
	initDone.Wait()

	dns.Flush()

	onReady()
	return g.Wait()
}
<|endoftext|>


<commit_before>package hamster

/*Hamster Server*/

import (
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/adnaan/routes"
	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/sessions"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
)

type Server struct {
	listener   net.Listener
	logger     *log.Logger
	httpServer *http.Server
	route      *routes.RouteMux
	db         *Db
	config     *Config
	cookie     *sessions.CookieStore
	redisConn  func() redis.Conn
}

//dbUrl:"mongodb://adnaan:pass@localhost:27017/hamster"
//serverUrl:fmt.Sprintf("%s:%d", address, port)
//creates a new server, setups logging etc.
func NewServer(port int, dbUrl string) *Server {
	f, err := os.OpenFile("hamster.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println("hamster.log faied to open")

	}
	//log.SetOutput(f)
	//log.SetOutput(os.Stdout)
	//router
	r := routes.New()
	//toml config
	var cfg Config
	if _, err := toml.DecodeFile("hamster.toml", &cfg); err != nil {
		fmt.Println(err)
		return nil
	}
	//cookie store
	ck := sessions.NewCookieStore([]byte(cfg.Servers["local"].CookieSecret))

	//redis
	var getRedis = func() redis.Conn {

		c, err := redis.Dial("tcp", ":6379")
		if err != nil {
			panic(err)
		}

		return c

	}

	//initialize server
	s := &Server{
		httpServer: &http.Server{Addr: fmt.Sprintf(":%d", cfg.Servers["local"].Port), Handler: r},
		route:      r,
		logger:     log.New(f, "", log.LstdFlags),
		db:         &Db{Url: cfg.DB["mongo"].Host},
		config:     &cfg,
		cookie:     ck,
		redisConn:  getRedis,
	}

	s.addHandlers()

	return s

}

//listen and serve a fastcgi server

func (s *Server) ListenAndServe() error {

	listener, err := net.Listen("tcp", s.httpServer.Addr)
	if err != nil {
		s.logger.Printf("error listening: %v \n", err)
		return err
	}
	s.listener = listener

	go s.httpServer.Serve(s.listener)

	s.logger.Print("********Server Startup*********\n")
	s.logger.Print("********++++++++++++++*********\n")
	s.logger.Printf("hamster is now listening on http://localhost%s\n", s.httpServer.Addr)

	//index the collections
	s.IndexDevelopers()

	return nil
}

// stops the server.
func (s *Server) Shutdown() error {

	if s.listener != nil {
		// Then stop the server.
		err := s.listener.Close()
		s.listener = nil
		if err != nil {
			return err
		}
	}

	return nil
}

// no log
func (s *Server) Quiet() {
	s.logger = log.New(ioutil.Discard, "", log.LstdFlags)
}
<commit_msg>now that we have redis, figure out whatto do with cookiestore<commit_after>package hamster

/*Hamster Server*/

import (
	"fmt"
	"github.com/BurntSushi/toml"
	"github.com/adnaan/routes"
	"github.com/garyburd/redigo/redis"
	"github.com/gorilla/sessions"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
)

type Server struct {
	listener   net.Listener
	logger     *log.Logger
	httpServer *http.Server
	route      *routes.RouteMux
	db         *Db
	config     *Config
	cookie     *sessions.CookieStore //unused
	redisConn  func() redis.Conn
}

//dbUrl:"mongodb://adnaan:pass@localhost:27017/hamster"
//serverUrl:fmt.Sprintf("%s:%d", address, port)
//creates a new server, setups logging etc.
func NewServer(port int, dbUrl string) *Server {
	f, err := os.OpenFile("hamster.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println("hamster.log faied to open")

	}
	//log.SetOutput(f)
	//log.SetOutput(os.Stdout)
	//router
	r := routes.New()
	//toml config
	var cfg Config
	if _, err := toml.DecodeFile("hamster.toml", &cfg); err != nil {
		fmt.Println(err)
		return nil
	}
	//cookie store
	ck := sessions.NewCookieStore([]byte(cfg.Servers["local"].CookieSecret))

	//redis
	var getRedis = func() redis.Conn {

		c, err := redis.Dial("tcp", ":6379")
		if err != nil {
			panic(err)
		}

		return c

	}

	//initialize server
	s := &Server{
		httpServer: &http.Server{Addr: fmt.Sprintf(":%d", cfg.Servers["local"].Port), Handler: r},
		route:      r,
		logger:     log.New(f, "", log.LstdFlags),
		db:         &Db{Url: cfg.DB["mongo"].Host},
		config:     &cfg,
		cookie:     ck,
		redisConn:  getRedis,
	}

	s.addHandlers()

	return s

}

//listen and serve a fastcgi server

func (s *Server) ListenAndServe() error {

	listener, err := net.Listen("tcp", s.httpServer.Addr)
	if err != nil {
		s.logger.Printf("error listening: %v \n", err)
		return err
	}
	s.listener = listener

	go s.httpServer.Serve(s.listener)

	s.logger.Print("********Server Startup*********\n")
	s.logger.Print("********++++++++++++++*********\n")
	s.logger.Printf("hamster is now listening on http://localhost%s\n", s.httpServer.Addr)

	//index the collections
	s.IndexDevelopers()

	return nil
}

// stops the server.
func (s *Server) Shutdown() error {

	if s.listener != nil {
		// Then stop the server.
		err := s.listener.Close()
		s.listener = nil
		if err != nil {
			return err
		}
	}

	return nil
}

// no log
func (s *Server) Quiet() {
	s.logger = log.New(ioutil.Discard, "", log.LstdFlags)
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"github.com/auth0/go-jwt-middleware"
	"github.com/dgrijalva/jwt-go"
	"net/http"
)

type KeyCheckHandler struct {
	db *Database
}

type JwtCheckHandler struct {
	db            *Database
	jwtMiddleware *jwtmiddleware.JWTMiddleware
}

func (h *KeyCheckHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

	key := r.Header.Get("X-PYTILT-KEY")
	if key == "" {
		http.Error(w, "key not specified", http.StatusUnauthorized)
		return
	}
	user, err := h.db.getUserForKey(key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusForbidden)
	}
	if err == nil && next != nil {
		ctx := r.Context()
		ctx = context.WithValue(ctx, "user", user)
		r = r.WithContext(ctx)
		next(w, r)
	}
}

func (h *JwtCheckHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	err := h.jwtMiddleware.CheckJWT(w, r)
	if err == nil && next != nil {
		claims := r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)
		if claims["iss"] == "https://securetoken.google.com/pitilt-7a37c" && claims["aud"] == "pitilt-7a37c" {
			userId := claims["user_id"].(string)
			exists, err := h.db.userExists(userId)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			if !exists {
				err = h.db.createUser(userId, claims["email"].(string), claims["name"].(string))
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					return
				}
			}
			ctx := r.Context()
			ctx = context.WithValue(ctx, "user", userId)
			r = r.WithContext(ctx)
			next(w, r)
		} else {
			http.Error(w, "key not valid", http.StatusUnauthorized)
		}
	}
}
<commit_msg>Log errors from db in jwt handler.<commit_after>package main

import (
	"context"
	log "github.com/Sirupsen/logrus"
	"github.com/auth0/go-jwt-middleware"
	"github.com/dgrijalva/jwt-go"
	"net/http"
)

type KeyCheckHandler struct {
	db *Database
}

type JwtCheckHandler struct {
	db            *Database
	jwtMiddleware *jwtmiddleware.JWTMiddleware
}

func (h *KeyCheckHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

	key := r.Header.Get("X-PYTILT-KEY")
	if key == "" {
		http.Error(w, "key not specified", http.StatusUnauthorized)
		return
	}
	user, err := h.db.getUserForKey(key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusForbidden)
	}
	if err == nil && next != nil {
		ctx := r.Context()
		ctx = context.WithValue(ctx, "user", user)
		r = r.WithContext(ctx)
		next(w, r)
	}
}

func (h *JwtCheckHandler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	err := h.jwtMiddleware.CheckJWT(w, r)
	if err == nil && next != nil {
		claims := r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)
		if claims["iss"] == "https://securetoken.google.com/pitilt-7a37c" && claims["aud"] == "pitilt-7a37c" {
			userId := claims["user_id"].(string)
			exists, err := h.db.userExists(userId)
			if err != nil {
				log.WithFields(log.Fields{
					"err":    err,
					"userId": userId,
					"email":  claims["email"].(string),
				}).Error("Unable to check if user exists")
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			if !exists {
				err = h.db.createUser(userId, claims["email"].(string), claims["name"].(string))
				if err != nil {
					log.WithFields(log.Fields{
						"err":    err,
						"userId": userId,
						"email":  claims["email"].(string),
					}).Error("Unable to create user")
					http.Error(w, err.Error(), http.StatusInternalServerError)
					return
				}
			}
			ctx := r.Context()
			ctx = context.WithValue(ctx, "user", userId)
			r = r.WithContext(ctx)
			next(w, r)
		} else {
			http.Error(w, "key not valid", http.StatusUnauthorized)
		}
	}
}
<|endoftext|>


<commit_before>/*
Package eventstore provides memory implementation of domain event store
*/
package eventstore

import (
	"sync"

	"github.com/google/uuid"
	"github.com/vardius/go-api-boilerplate/pkg/domain"
	baseeventstore "github.com/vardius/go-api-boilerplate/pkg/eventstore"
)

type eventStore struct {
	mtx    sync.RWMutex
	events map[string]*domain.Event
}

func (s *eventStore) Store(events []*domain.Event) error {
	if len(events) == 0 {
		return nil
	}

	s.mtx.Lock()
	defer s.mtx.Unlock()

	// @TODO: check event version
	for _, e := range events {
		s.events[e.ID.String()] = e
	}

	return nil
}

func (s *eventStore) Get(id uuid.UUID) (*domain.Event, error) {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	if val, ok := s.events[id.String()]; ok {
		return val, nil
	}
	return nil, ErrEventNotFound
}

func (s *eventStore) FindAll() []*domain.Event {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	es := make([]*domain.Event, 0, len(s.events))
	for _, val := range s.events {
		es = append(es, val)
	}
	return es
}

func (s *eventStore) GetStream(streamID uuid.UUID, streamName string) []*domain.Event {
	s.mtx.RLock()
	defer s.mtx.RUnlock()
	e := make([]*domain.Event, 0, 0)
	for _, val := range s.events {
		if val.Metadata.StreamName == streamName && val.Metadata.StreamID == streamID {
			e = append(e, val)
		}
	}
	return e
}

// New creates in memory event store
func New() baseeventstore.EventStore {
	return &eventStore{
		events: make(map[string]*domain.Event),
	}
}
<commit_msg>Update mutex<commit_after>/*
Package eventstore provides memory implementation of domain event store
*/
package eventstore

import (
	"sync"

	"github.com/google/uuid"
	"github.com/vardius/go-api-boilerplate/pkg/domain"
	baseeventstore "github.com/vardius/go-api-boilerplate/pkg/eventstore"
)

type eventStore struct {
	sync.RWMutex
	events map[string]*domain.Event
}

func (s *eventStore) Store(events []*domain.Event) error {
	if len(events) == 0 {
		return nil
	}

	s.Lock()
	defer s.Unlock()

	// @TODO: check event version
	for _, e := range events {
		s.events[e.ID.String()] = e
	}

	return nil
}

func (s *eventStore) Get(id uuid.UUID) (*domain.Event, error) {
	s.RLock()
	defer s.RUnlock()
	if val, ok := s.events[id.String()]; ok {
		return val, nil
	}
	return nil, ErrEventNotFound
}

func (s *eventStore) FindAll() []*domain.Event {
	s.RLock()
	defer s.RUnlock()
	es := make([]*domain.Event, 0, len(s.events))
	for _, val := range s.events {
		es = append(es, val)
	}
	return es
}

func (s *eventStore) GetStream(streamID uuid.UUID, streamName string) []*domain.Event {
	s.RLock()
	defer s.RUnlock()
	e := make([]*domain.Event, 0, 0)
	for _, val := range s.events {
		if val.Metadata.StreamName == streamName && val.Metadata.StreamID == streamID {
			e = append(e, val)
		}
	}
	return e
}

// New creates in memory event store
func New() baseeventstore.EventStore {
	return &eventStore{
		events: make(map[string]*domain.Event),
	}
}
<|endoftext|>


<commit_before>package log

import (
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/volatile/core"
	"github.com/volatile/core/coreutil"
	"github.com/whitedevops/colors"
)

// Use tells the core to use this handler.
func Use() {
	core.Use(func(c *core.Context) {
		start := time.Now()

		c.Next()

		log.Printf(colors.ResetAll+" %s  %s  %s  %s"+colors.ResetAll, fmtMethod(c), fmtPath(c), fmtStatus(c), fmtDuration(start))
	})
}

func fmtMethod(c *core.Context) string {
	s := colors.ResetAll

	switch strings.ToUpper(c.Request.Method) {
	case "GET":
		s += colors.Green + "GET"
	case "POST":
		s += colors.Cyan + "POST"
	case "PUT":
		s += colors.Blue + "PUT"
	case "PATCH":
		s += colors.Blue + "PATCH"
	case "DELETE":
		s += colors.Red + "DELETE"
	case "TRACE":
		s += colors.Magenta + "TRACE"
	case "OPTIONS":
		s += colors.Magenta + "OPTIONS"
	default:
		s += colors.Red + colors.Blink + "UNKNOWN"
	}

	return s + colors.ResetAll
}

func fmtPath(c *core.Context) string {
	return colors.ResetAll + c.Request.URL.String()
}

func fmtStatus(c *core.Context) string {
	code := coreutil.ResponseStatus(c.ResponseWriter)

	s := colors.ResetAll

	switch {
	case code >= 200 && code <= 299:
		s += colors.Green
	case code >= 300 && code <= 399:
		s += colors.Cyan
	case code >= 400 && code <= 499:
		s += colors.Yellow
	default:
		s += colors.Red + colors.Blink
	}

	return s + strconv.Itoa(code) + colors.ResetAll
}

func fmtDuration(start time.Time) string {
	return colors.ResetAll + colors.Dim + time.Since(start).String() + colors.ResetAll
}
<commit_msg>Add HEAD method support<commit_after>package log

import (
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/volatile/core"
	"github.com/volatile/core/coreutil"
	"github.com/whitedevops/colors"
)

// Use tells the core to use this handler.
func Use() {
	core.Use(func(c *core.Context) {
		start := time.Now()

		c.Next()

		log.Printf(colors.ResetAll+" %s  %s  %s  %s"+colors.ResetAll, fmtMethod(c), fmtPath(c), fmtStatus(c), fmtDuration(start))
	})
}

func fmtMethod(c *core.Context) string {
	s := colors.ResetAll

	switch strings.ToUpper(c.Request.Method) {
	case "GET":
		s += colors.Green + "GET"
	case "POST":
		s += colors.Cyan + "POST"
	case "PUT":
		s += colors.Blue + "PUT"
	case "PATCH":
		s += colors.Blue + "PATCH"
	case "DELETE":
		s += colors.Red + "DELETE"
	case "HEAD":
		s += colors.Magenta + "HEAD"
	case "OPTIONS":
		s += colors.Magenta + "OPTIONS"
	case "TRACE":
		s += colors.Magenta + "TRACE"
	default:
		s += colors.Red + colors.Blink + "UNKNOWN"
	}

	return s + colors.ResetAll
}

func fmtPath(c *core.Context) string {
	return colors.ResetAll + c.Request.URL.String()
}

func fmtStatus(c *core.Context) string {
	code := coreutil.ResponseStatus(c.ResponseWriter)

	s := colors.ResetAll

	switch {
	case code >= 200 && code <= 299:
		s += colors.Green
	case code >= 300 && code <= 399:
		s += colors.Cyan
	case code >= 400 && code <= 499:
		s += colors.Yellow
	default:
		s += colors.Red + colors.Blink
	}

	return s + strconv.Itoa(code) + colors.ResetAll
}

func fmtDuration(start time.Time) string {
	return colors.ResetAll + colors.Dim + time.Since(start).String() + colors.ResetAll
}
<|endoftext|>


<commit_before>package grpczap

import (
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

var _ grpc.UnaryServerInterceptor = UnaryZapHandler
var _ grpc.StreamServerInterceptor = StreamZapHandler

func UnaryZapHandler(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
	logger.With(zap.String("method", info.FullMethod))
	ctx = NewContext(ctx, l)
	return handler(ctx, req)
}

func StreamZapHandler(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) {
	l := logger.With(zap.String("method", info.FullMethod))
	ctx = NewContext(ctx, l)
	return handler(ctx, stream)
}
<commit_msg>fix import<commit_after>package grpczap

import (
	"github.com/uber-go/zap"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

var _ grpc.UnaryServerInterceptor = UnaryZapHandler
var _ grpc.StreamServerInterceptor = StreamZapHandler

func UnaryZapHandler(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
	logger.With(zap.String("method", info.FullMethod))
	ctx = NewContext(ctx, l)
	return handler(ctx, req)
}

func StreamZapHandler(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) {
	l := logger.With(zap.String("method", info.FullMethod))
	ctx = NewContext(ctx, l)
	return handler(ctx, stream)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"github.com/orrc/git-webhook-proxy/hooks"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"reflect"
	"strings"
	"sync"
)

type Handler struct {
	gitPath       string
	mirrorRootDir string
	remoteUrl     string
	proxy         http.Handler
	requests      map[string]*sync.Mutex
}

func NewHandler(gitPath, mirrorRootDir, remoteUrl string) (h *Handler, err error) {
	backendUrl, err := url.Parse(remoteUrl)
	proxy := httputil.NewSingleHostReverseProxy(backendUrl)

	// Ensure we send the correct Host header to the backend
	defaultDirector := proxy.Director
	proxy.Director = func(req *http.Request) {
		defaultDirector(req)
		req.Host = backendUrl.Host
	}

	h = &Handler{
		gitPath:       gitPath,
		mirrorRootDir: mirrorRootDir,
		remoteUrl:     remoteUrl,
		proxy:         proxy,
		requests:      make(map[string]*sync.Mutex),
	}
	return
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Log request
	log.Printf("Incoming webhook from %s %s %s", req.RemoteAddr, req.Method, req.URL)

	// Determine which handler to use
	// TODO: This won't work well for e.g. "/jenkins/git/notifyCommit"
	var hookType hooks.Webhook
	switch req.URL.Path {
	case "/git/notifyCommit":
		hookType = hooks.JenkinsHook{}
	case "/github-webhook/":
		hookType = hooks.GitHubFormHook{}
	default:
		log.Println("No hook handler found!")
		http.NotFound(w, req)
		return
	}

	// Parse the Git repo URI from the webhook request
	repoUri, err := hookType.GetGitRepoUri(req)
	if err != nil {
		msg := fmt.Sprintf("%s returned error: %s", reflect.TypeOf(hookType), err)
		log.Println(msg)
		http.Error(w, msg, http.StatusInternalServerError)
		return
	}
	if repoUri == "" {
		msg := fmt.Sprintf("%s could not determine the repository URL from this request", reflect.TypeOf(hookType))
		log.Println(msg)
		http.Error(w, msg, http.StatusInternalServerError)
		return
	}

	// Check whether we're already working on updating this repo
	// TODO: Coalesce multiple blocked requests
	if _, exists := h.requests[repoUri]; !exists {
		h.requests[repoUri] = &sync.Mutex{}
	}
	lock := h.requests[repoUri]
	lock.Lock()
	defer lock.Unlock()

	// Clone or mirror the repo
	// TODO: Test what happens if the HTTP client disappears in the middle of a long clone
	err = h.updateOrCloneRepoMirror(repoUri)
	if err != nil {
		log.Println(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Proxy the original webhook request to the backend
	log.Printf("Proxying webhook request to %s/%s\n", h.remoteUrl, req.URL)
	h.proxy.ServeHTTP(w, req)
}

func (h *Handler) updateOrCloneRepoMirror(repoUri string) error {
	// Check whether we have cloned this repo already
	repoPath := h.getMirrorPathForRepo(repoUri)
	if _, err := os.Stat(repoPath); os.IsNotExist(err) {
		// TODO: Also need to somehow detect whether a directory has a full clone, or failed...
		err = h.cloneRepo(repoUri)
		if err != nil {
			err = errors.New(fmt.Sprintf("Failed to clone %s: %s", repoUri, err.Error()))
		}
		return err
	}

	// If we already have clone the repo, ensure that it is up-to-date
	log.Printf("Updating mirror at %s", repoPath)
	cmd := exec.Command(h.gitPath, "remote", "update", "-p")
	cmd.Dir = repoPath
	err := cmd.Run()
	if err == nil {
		log.Printf("Successfully updated %s", repoPath)

		// Also run "git gc", if required, to clean up afterwards
		cmd := exec.Command(h.gitPath, "gc", "--aggressive", "--auto")
		cmd.Dir = repoPath

		// But we don't really care about the outcome
		cmd.Run()
	} else {
		err = fmt.Errorf("Failed to update %s: %s", repoPath, err.Error())
	}
	return err
}

func (h *Handler) cloneRepo(repoUri string) error {
	// Ensure the mirror root directory exists
	err := os.MkdirAll(h.mirrorRootDir, 0700)
	if err != nil {
		return err
	}

	// Delete the directory if cloning fails
	defer func() {
		if err != nil {
			os.Remove(h.getMirrorPathForRepo(repoUri))
		}
	}()

	// TODO: We may need to transform incoming repo URIs to add user credentials so they can be cloned
	log.Printf("Cloning %s to %s", repoUri, h.mirrorRootDir)
	cmd := exec.Command(h.gitPath, "clone", "--mirror", repoUri, getDirNameForRepo(repoUri))
	cmd.Dir = h.mirrorRootDir
	err = cmd.Run()
	if err == nil {
		log.Printf("Successfully cloned %s", repoUri)
	}
	return err
}

func (h *Handler) getMirrorPathForRepo(repoUri string) string {
	return fmt.Sprintf("%s/%s", h.mirrorRootDir, getDirNameForRepo(repoUri))
}

func getDirNameForRepo(repoUri string) string {
	repoUri = strings.TrimSpace(repoUri)
	repoUri = strings.TrimSuffix(repoUri, "/")
	repoUri = strings.TrimSuffix(repoUri, ".git")
	repoUri = strings.ToLower(repoUri)

	if strings.Contains(repoUri, "://") {
		uri, _ := url.Parse(repoUri)
		if i := strings.Index(uri.Host, ":"); i != -1 {
			uri.Host = uri.Host[:i]
		}
		return fmt.Sprintf("%s/%s.git", uri.Host, uri.Path[1:])
	}

	if i := strings.Index(repoUri, "@"); i != -1 {
		repoUri = repoUri[i+1:]
	}
	repoUri = strings.Replace(repoUri, ":", "/", 1)
	repoUri = strings.Replace(repoUri, "//", "/", -1)
	return repoUri + ".git"
}
<commit_msg>Prune all loose objects immediately upon repo update.<commit_after>package main

import (
	"errors"
	"fmt"
	"github.com/orrc/git-webhook-proxy/hooks"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"reflect"
	"strings"
	"sync"
)

type Handler struct {
	gitPath       string
	mirrorRootDir string
	remoteUrl     string
	proxy         http.Handler
	requests      map[string]*sync.Mutex
}

func NewHandler(gitPath, mirrorRootDir, remoteUrl string) (h *Handler, err error) {
	backendUrl, err := url.Parse(remoteUrl)
	proxy := httputil.NewSingleHostReverseProxy(backendUrl)

	// Ensure we send the correct Host header to the backend
	defaultDirector := proxy.Director
	proxy.Director = func(req *http.Request) {
		defaultDirector(req)
		req.Host = backendUrl.Host
	}

	h = &Handler{
		gitPath:       gitPath,
		mirrorRootDir: mirrorRootDir,
		remoteUrl:     remoteUrl,
		proxy:         proxy,
		requests:      make(map[string]*sync.Mutex),
	}
	return
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Log request
	log.Printf("Incoming webhook from %s %s %s", req.RemoteAddr, req.Method, req.URL)

	// Determine which handler to use
	// TODO: This won't work well for e.g. "/jenkins/git/notifyCommit"
	var hookType hooks.Webhook
	switch req.URL.Path {
	case "/git/notifyCommit":
		hookType = hooks.JenkinsHook{}
	case "/github-webhook/":
		hookType = hooks.GitHubFormHook{}
	default:
		log.Println("No hook handler found!")
		http.NotFound(w, req)
		return
	}

	// Parse the Git repo URI from the webhook request
	repoUri, err := hookType.GetGitRepoUri(req)
	if err != nil {
		msg := fmt.Sprintf("%s returned error: %s", reflect.TypeOf(hookType), err)
		log.Println(msg)
		http.Error(w, msg, http.StatusInternalServerError)
		return
	}
	if repoUri == "" {
		msg := fmt.Sprintf("%s could not determine the repository URL from this request", reflect.TypeOf(hookType))
		log.Println(msg)
		http.Error(w, msg, http.StatusInternalServerError)
		return
	}

	// Check whether we're already working on updating this repo
	// TODO: Coalesce multiple blocked requests
	if _, exists := h.requests[repoUri]; !exists {
		h.requests[repoUri] = &sync.Mutex{}
	}
	lock := h.requests[repoUri]
	lock.Lock()
	defer lock.Unlock()

	// Clone or mirror the repo
	// TODO: Test what happens if the HTTP client disappears in the middle of a long clone
	err = h.updateOrCloneRepoMirror(repoUri)
	if err != nil {
		log.Println(err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Proxy the original webhook request to the backend
	log.Printf("Proxying webhook request to %s/%s\n", h.remoteUrl, req.URL)
	h.proxy.ServeHTTP(w, req)
}

func (h *Handler) updateOrCloneRepoMirror(repoUri string) error {
	// Check whether we have cloned this repo already
	repoPath := h.getMirrorPathForRepo(repoUri)
	if _, err := os.Stat(repoPath); os.IsNotExist(err) {
		// TODO: Also need to somehow detect whether a directory has a full clone, or failed...
		err = h.cloneRepo(repoUri)
		if err != nil {
			err = errors.New(fmt.Sprintf("Failed to clone %s: %s", repoUri, err.Error()))
		}
		return err
	}

	// If we already have clone the repo, ensure that it is up-to-date
	log.Printf("Updating mirror at %s", repoPath)
	cmd := exec.Command(h.gitPath, "remote", "update", "-p")
	cmd.Dir = repoPath
	err := cmd.Run()
	if err == nil {
		log.Printf("Successfully updated %s", repoPath)

		// Also run "git gc", if required, to clean up afterwards
		cmd := exec.Command(h.gitPath, "gc", "--prune=now", "--aggressive", "--auto")
		cmd.Dir = repoPath

		// But we don't really care about the outcome
		cmd.Run()
	} else {
		err = fmt.Errorf("Failed to update %s: %s", repoPath, err.Error())
	}
	return err
}

func (h *Handler) cloneRepo(repoUri string) error {
	// Ensure the mirror root directory exists
	err := os.MkdirAll(h.mirrorRootDir, 0700)
	if err != nil {
		return err
	}

	// Delete the directory if cloning fails
	defer func() {
		if err != nil {
			os.Remove(h.getMirrorPathForRepo(repoUri))
		}
	}()

	// TODO: We may need to transform incoming repo URIs to add user credentials so they can be cloned
	log.Printf("Cloning %s to %s", repoUri, h.mirrorRootDir)
	cmd := exec.Command(h.gitPath, "clone", "--mirror", repoUri, getDirNameForRepo(repoUri))
	cmd.Dir = h.mirrorRootDir
	err = cmd.Run()
	if err == nil {
		log.Printf("Successfully cloned %s", repoUri)
	}
	return err
}

func (h *Handler) getMirrorPathForRepo(repoUri string) string {
	return fmt.Sprintf("%s/%s", h.mirrorRootDir, getDirNameForRepo(repoUri))
}

func getDirNameForRepo(repoUri string) string {
	repoUri = strings.TrimSpace(repoUri)
	repoUri = strings.TrimSuffix(repoUri, "/")
	repoUri = strings.TrimSuffix(repoUri, ".git")
	repoUri = strings.ToLower(repoUri)

	if strings.Contains(repoUri, "://") {
		uri, _ := url.Parse(repoUri)
		if i := strings.Index(uri.Host, ":"); i != -1 {
			uri.Host = uri.Host[:i]
		}
		return fmt.Sprintf("%s/%s.git", uri.Host, uri.Path[1:])
	}

	if i := strings.Index(repoUri, "@"); i != -1 {
		repoUri = repoUri[i+1:]
	}
	repoUri = strings.Replace(repoUri, ":", "/", 1)
	repoUri = strings.Replace(repoUri, "//", "/", -1)
	return repoUri + ".git"
}
<|endoftext|>