<commit_before>// 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 main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/lancetw/hcfd-forecast/db"
	"github.com/lancetw/hcfd-forecast/rain"
	"github.com/line/line-bot-sdk-go/linebot"
)

const timeZone = "Asia/Taipei"

var bot *linebot.Client

func main() {
	strID := os.Getenv("ChannelID")
	numID, err := strconv.ParseInt(strID, 10, 64)
	if err != nil {
		log.Fatal("Wrong environment setting about ChannelID")
	}
	bot, err = linebot.NewClient(numID, os.Getenv("ChannelSecret"), os.Getenv("MID"))
	if err != nil {
		log.Println("Bot:", bot, " err:", err)
	}

	http.HandleFunc("/callback", callbackHandler)
	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)
}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	received, err := bot.ParseRequest(r)
	if err != nil {
		if err == linebot.ErrInvalidSignature {
			w.WriteHeader(400)
		} else {
			w.WriteHeader(500)
		}
		return
	}
	for _, result := range received.Results {
		content := result.Content()
		if content != nil && content.IsOperation && content.OpType == linebot.OpTypeAddedAsFriend {
			op, err := content.OperationContent()
			if err != nil {
				log.Println(err)
				return
			}
			from := op.Params[0]

			user, err := bot.GetUserProfile([]string{from})
			if err != nil {
				log.Println(err)
				return
			}
			_, err = bot.SendText([]string{from}, user.Contacts[0].DisplayName+" 您好，目前可用指令為：「加入」「退出」「警報」「貓圖」「狀態」「時間」")
			if err != nil {
				log.Println(err)
			}
		}

		if content != nil && content.IsMessage && content.ContentType == linebot.ContentTypeText {
			user, err := bot.GetUserProfile([]string{content.From})
			if err != nil {
				return
			}
			text, err := content.TextContent()
			if err != nil {
				log.Println(err)
			}
			switch text.Text {
			case "加入":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, addErr := c.Do("SADD", "user", content.From)
				if addErr != nil {
					log.Println("SADD to redis error", addErr, status)
				} else {
					_, err = bot.SendText([]string{content.From}, user.Contacts[0].DisplayName+" 您好，已將您加入傳送對象 ^＿^ ")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "退出":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, setErr := c.Do("SREM", "user", content.From)
				if setErr != nil {
					log.Println("DEL to redis error", setErr, status)
				} else {
					_, err = bot.SendText([]string{content.From}, user.Contacts[0].DisplayName+" 掰掰 Q＿Q")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "狀態":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, getErr := redis.Int(c.Do("SISMEMBER", "user", content.From))
				if getErr != nil || status == 0 {
					_, err = bot.SendText([]string{content.From}, "目前沒有登記您的編號喔！")
					if err != nil {
						log.Println(err)
					}
				} else {
					_, err = bot.SendText([]string{content.From}, "已登記完成，未來將會傳送天氣警報資訊給您 :D")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "時間":
				local := time.Now()
				location, timezoneErr := time.LoadLocation(timeZone)
				if timezoneErr == nil {
					local = local.In(location)
				}
				_, err = bot.SendText([]string{content.From}, local.Format("2006/01/02 15:04:05"))
				if err != nil {
					log.Println(err)
				}

			case "雨量":
				msgs, _ := rain.GetRainingInfo(nil)
				for _, msg := range msgs {
					_, err = bot.SendText([]string{content.From}, msg)
					if err != nil {
						log.Println(err)
					}
				}

			case "警報":
				msgs, _ := rain.GetWarningInfo(nil)
				for _, msg := range msgs {
					_, err = bot.SendText([]string{content.From}, msg)
					if err != nil {
						log.Println(err)
					}
				}

			case "清除":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status0, clearErr0 := c.Do("DEL", "token0")
				if clearErr0 != nil {
					log.Println("DEL to redis error", clearErr0, status0)
				}
				status1, clearErr1 := c.Do("DEL", "token1")
				if clearErr1 != nil {
					log.Println("DEL to redis error", clearErr1, status1)
				}
				if status0 == 0 && status1 == 0 {
					_, err = bot.SendText([]string{content.From}, "清除完成 :D")
					if err != nil {
						log.Println(err)
					}
				}

			case "貓圖":
				type Cat struct {
					File string `json:"file"`
				}
				cat := new(Cat)
				getJSON("http://random.cat/meow", &cat)
				if cat.File != "" {
					_, err = bot.SendImage([]string{content.From}, cat.File, cat.File)
					if err != nil {
						log.Println(err)
					}
				}

			case "正妹":
				type Beauty struct {
					Count   int      `json:"count"`
					PhotoID []string `json:"photoId"`
				}
				beauty := new(Beauty)
				getJSON("", &beauty)
				if beauty.PhotoID[0] != "" {
					_, err = bot.SendImage([]string{content.From}, beauty.PhotoID[0], beauty.PhotoID[0])
					if err != nil {
						log.Println(err)
					}
				}

			default:
				_, err = bot.SendText([]string{content.From}, "指令錯誤，請重試")
				if err != nil {
					log.Println(err)
				}

			}
		}
	}
}

// getJSON func
func getJSON(url string, target interface{}) error {
	r, err := http.Get(url)
	if err != nil {
		return err
	}
	defer r.Body.Close()

	return json.NewDecoder(r.Body).Decode(target)
}
<commit_msg>fixed typo<commit_after>// 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 main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/lancetw/hcfd-forecast/db"
	"github.com/lancetw/hcfd-forecast/rain"
	"github.com/line/line-bot-sdk-go/linebot"
)

const timeZone = "Asia/Taipei"

var bot *linebot.Client

func main() {
	strID := os.Getenv("ChannelID")
	numID, err := strconv.ParseInt(strID, 10, 64)
	if err != nil {
		log.Fatal("Wrong environment setting about ChannelID")
	}
	bot, err = linebot.NewClient(numID, os.Getenv("ChannelSecret"), os.Getenv("MID"))
	if err != nil {
		log.Println("Bot:", bot, " err:", err)
	}

	http.HandleFunc("/callback", callbackHandler)
	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)
}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	received, err := bot.ParseRequest(r)
	if err != nil {
		if err == linebot.ErrInvalidSignature {
			w.WriteHeader(400)
		} else {
			w.WriteHeader(500)
		}
		return
	}
	for _, result := range received.Results {
		content := result.Content()
		if content != nil && content.IsOperation && content.OpType == linebot.OpTypeAddedAsFriend {
			op, err := content.OperationContent()
			if err != nil {
				log.Println(err)
				return
			}
			from := op.Params[0]

			user, err := bot.GetUserProfile([]string{from})
			if err != nil {
				log.Println(err)
				return
			}
			_, err = bot.SendText([]string{from}, user.Contacts[0].DisplayName+" 您好，目前可用指令為：「加入」「退出」「警報」「貓圖」「狀態」「時間」")
			if err != nil {
				log.Println(err)
			}
		}

		if content != nil && content.IsMessage && content.ContentType == linebot.ContentTypeText {
			user, err := bot.GetUserProfile([]string{content.From})
			if err != nil {
				return
			}
			text, err := content.TextContent()
			if err != nil {
				log.Println(err)
			}
			switch text.Text {
			case "加入":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, addErr := c.Do("SADD", "user", content.From)
				if addErr != nil {
					log.Println("SADD to redis error", addErr, status)
				} else {
					_, err = bot.SendText([]string{content.From}, user.Contacts[0].DisplayName+" 您好，已將您加入傳送對象 ^＿^ ")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "退出":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, setErr := c.Do("SREM", "user", content.From)
				if setErr != nil {
					log.Println("DEL to redis error", setErr, status)
				} else {
					_, err = bot.SendText([]string{content.From}, user.Contacts[0].DisplayName+" 掰掰 Q＿Q")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "狀態":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status, getErr := redis.Int(c.Do("SISMEMBER", "user", content.From))
				if getErr != nil || status == 0 {
					_, err = bot.SendText([]string{content.From}, "目前沒有登記您的編號喔！")
					if err != nil {
						log.Println(err)
					}
				} else {
					_, err = bot.SendText([]string{content.From}, "已登記完成，未來將會傳送天氣警報資訊給您 :D")
					if err != nil {
						log.Println(err)
					}
				}
				defer c.Close()

			case "時間":
				local := time.Now()
				location, timezoneErr := time.LoadLocation(timeZone)
				if timezoneErr == nil {
					local = local.In(location)
				}
				_, err = bot.SendText([]string{content.From}, local.Format("2006/01/02 15:04:05"))
				if err != nil {
					log.Println(err)
				}

			case "雨量":
				msgs, _ := rain.GetRainingInfo(nil)
				for _, msg := range msgs {
					_, err = bot.SendText([]string{content.From}, msg)
					if err != nil {
						log.Println(err)
					}
				}

			case "警報":
				msgs, _ := rain.GetWarningInfo(nil)
				for _, msg := range msgs {
					_, err = bot.SendText([]string{content.From}, msg)
					if err != nil {
						log.Println(err)
					}
				}

			case "清除":
				c := db.Connect(os.Getenv("REDISTOGO_URL"))
				status0, clearErr0 := c.Do("DEL", "token0")
				if clearErr0 != nil {
					log.Println("DEL to redis error", clearErr0, status0)
				}
				status1, clearErr1 := c.Do("DEL", "token1")
				if clearErr1 != nil {
					log.Println("DEL to redis error", clearErr1, status1)
				}
				if status0 == 1 && status1 == 1 {
					_, err = bot.SendText([]string{content.From}, "清除完成 :D")
					if err != nil {
						log.Println(err)
					}
				}

			case "貓圖":
				type Cat struct {
					File string `json:"file"`
				}
				cat := new(Cat)
				getJSON("http://random.cat/meow", &cat)
				if cat.File != "" {
					_, err = bot.SendImage([]string{content.From}, cat.File, cat.File)
					if err != nil {
						log.Println(err)
					}
				}

			case "正妹":
				type Beauty struct {
					Count   int      `json:"count"`
					PhotoID []string `json:"photoId"`
				}
				beauty := new(Beauty)
				getJSON("", &beauty)
				if beauty.PhotoID[0] != "" {
					_, err = bot.SendImage([]string{content.From}, beauty.PhotoID[0], beauty.PhotoID[0])
					if err != nil {
						log.Println(err)
					}
				}

			default:
				_, err = bot.SendText([]string{content.From}, "指令錯誤，請重試")
				if err != nil {
					log.Println(err)
				}

			}
		}
	}
}

// getJSON func
func getJSON(url string, target interface{}) error {
	r, err := http.Get(url)
	if err != nil {
		return err
	}
	defer r.Body.Close()

	return json.NewDecoder(r.Body).Decode(target)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	goconf "github.com/akrennmair/goconf"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"strings"
)

type Backend struct {
	Name          string
	ConnectString string
}

type Frontend struct {
	Name         string
	BindString   string
	HTTPS        bool
	AddForwarded bool
	Hosts        []string
	Backends     []string
	//AddHeader    struct { Key string; Value string }
	KeyFile  string
	CertFile string
}

func Copy(dest *bufio.ReadWriter, src *bufio.ReadWriter) {
	buf := make([]byte, 40*1024)
	for {
		n, err := src.Read(buf)
		if err != nil && err != io.EOF {
			log.Printf("Read failed: %v", err)
			return
		}
		if n == 0 {
			return
		}
		dest.Write(buf[0:n])
		dest.Flush()
	}
}

func CopyBidir(conn1 io.ReadWriteCloser, rw1 *bufio.ReadWriter, conn2 io.ReadWriteCloser, rw2 *bufio.ReadWriter) {
	finished := make(chan bool)

	go func() {
		Copy(rw2, rw1)
		conn2.Close()
		finished <- true
	}()
	go func() {
		Copy(rw1, rw2)
		conn1.Close()
		finished <- true
	}()

	<-finished
	<-finished
}

type RequestHandler struct {
	Transport    *http.Transport
	Frontend     *Frontend
	HostBackends map[string]chan *Backend
	Backends     chan *Backend
}

func (h *RequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	//log.Printf("incoming request: %#v", *r)
	r.RequestURI = ""
	r.URL.Scheme = "http"

	if h.Frontend.AddForwarded {
		remote_addr := r.RemoteAddr
		idx := strings.LastIndex(remote_addr, ":")
		if idx != -1 {
			remote_addr = remote_addr[0:idx]
			if remote_addr[0] == '[' && remote_addr[len(remote_addr)-1] == ']' {
				remote_addr = remote_addr[1 : len(remote_addr)-1]
			}
		}
		r.Header.Add("X-Forwarded-For", remote_addr)
	}

	if len(h.Frontend.Hosts) == 0 {
		backend := <-h.Backends
		r.URL.Host = backend.ConnectString
		h.Backends <- backend
	} else {
		backend_list := h.HostBackends[r.Host]
		if backend_list == nil {
			if len(h.Frontend.Backends) == 0 {
				http.Error(w, "no suitable backend found for request", http.StatusServiceUnavailable)
				return
			} else {
				backend := <-h.Backends
				r.URL.Host = backend.ConnectString
				h.Backends <- backend
			}
		} else {
			backend := <-backend_list
			r.URL.Host = backend.ConnectString
			backend_list <- backend
		}
	}

	conn_hdr := ""
	conn_hdrs := r.Header["Connection"]
	log.Printf("Connection headers: %v", conn_hdrs)
	if len(conn_hdrs) > 0 {
		conn_hdr = conn_hdrs[0]
	}

	upgrade_websocket := false
	if strings.ToLower(conn_hdr) == "upgrade" {
		log.Printf("got Connection: Upgrade")
		upgrade_hdrs := r.Header["Upgrade"]
		log.Printf("Upgrade headers: %v", upgrade_hdrs)
		if len(upgrade_hdrs) > 0 {
			upgrade_websocket = (strings.ToLower(upgrade_hdrs[0]) == "websocket")
		}
	}

	if upgrade_websocket {
		hj, ok := w.(http.Hijacker)

		if !ok {
			http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
			return
		}

		conn, bufrw, err := hj.Hijack()
		defer conn.Close()

		conn2, err := net.Dial("tcp", r.URL.Host)
		if err != nil {
			http.Error(w, "couldn't connect to backend server", http.StatusServiceUnavailable)
			return
		}
		defer conn2.Close()

		err = r.Write(conn2)
		if err != nil {
			log.Printf("writing WebSocket request to backend server failed: %v", err)
			return
		}

		CopyBidir(conn, bufrw, conn2, bufio.NewReadWriter(bufio.NewReader(conn2), bufio.NewWriter(conn2)))

	} else {

		resp, err := h.Transport.RoundTrip(r)
		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprintf(w, "Error: %v", err)
			return
		}

		for k, v := range resp.Header {
			for _, vv := range v {
				w.Header().Add(k, vv)
			}
		}

		w.WriteHeader(resp.StatusCode)

		io.Copy(w, resp.Body)
		resp.Body.Close()
	}
}

func usage() {
	fmt.Fprintf(os.Stdout, "usage: %s -config=<configfile>\n", os.Args[0])
	os.Exit(1)
}

func main() {
	var cfgfile *string = flag.String("config", "", "configuration file")

	backends := make(map[string]*Backend)
	hosts := make(map[string][]*Backend)
	frontends := make(map[string]*Frontend)

	flag.Parse()

	if *cfgfile == "" {
		usage()
	}

	cfg, err := goconf.ReadConfigFile(*cfgfile)
	if err != nil {
		log.Printf("opening %s failed: %v", *cfgfile, err)
		os.Exit(1)
	}

	var access_f io.WriteCloser
	accesslog_file, err := cfg.GetString("global", "accesslog")
	if err == nil {
		access_f, err = os.OpenFile(accesslog_file, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
		if err == nil {
			defer access_f.Close()
		} else {
			log.Printf("Opening access log %s failed: %v", accesslog_file, err)
		}
	}

	// first, extract backends
	for _, section := range cfg.GetSections() {
		if strings.HasPrefix(section, "backend ") {
			tokens := strings.Split(section, " ")
			if len(tokens) < 2 {
				log.Printf("backend section has no name, ignoring.")
				continue
			}
			connect_str, _ := cfg.GetString(section, "connect")
			if connect_str == "" {
				log.Printf("empty connect string for backend %s, ignoring.", tokens[1])
				continue
			}
			b := &Backend{Name: tokens[1], ConnectString: connect_str}
			backends[b.Name] = b
		}
	}

	// then extract hosts
	for _, section := range cfg.GetSections() {
		if strings.HasPrefix(section, "host ") {
			tokens := strings.Split(section, " ")
			if len(tokens) < 2 {
				log.Printf("host section has no name, ignoring.")
				continue
			}
			backends_str, _ := cfg.GetString(section, "backends")
			backends_list := strings.Split(backends_str, " ")
			if len(backends_list) == 0 {
				log.Printf("host %s has no backends, ignoring.", tokens[1])
				continue
			}
			for _, host := range tokens[1:] {
				backends_for_host := []*Backend{}
				for _, backend := range backends_list {
					b := backends[backend]
					if b == nil {
						log.Printf("backend %s doesn't exist, ignoring.", backend)
					}
					backends_for_host = append(backends_for_host, b)
				}
				hosts[host] = backends_for_host
			}
		}
	}

	// and finally, extract frontends
	for _, section := range cfg.GetSections() {
		if strings.HasPrefix(section, "frontend ") {
			tokens := strings.Split(section, " ")
			if len(tokens) < 2 {
				log.Printf("frontend section has no name, ignoring.")
				continue
			}

			frontend_name := tokens[1]

			frontend := &Frontend{}
			frontend.Name = frontend_name
			frontend.BindString, err = cfg.GetString(section, "bind")
			if err != nil {
				log.Printf("error while getting [%s]bind: %v, ignoring.", section, err)
				continue
			}
			if frontend.BindString == "" {
				log.Printf("frontend %s has no bind argument, ignoring.", frontend_name)
				continue
			}

			frontend.HTTPS, err = cfg.GetBool(section, "https")
			if err != nil {
				frontend.HTTPS = false
			}

			if frontend.HTTPS {
				frontend.KeyFile, err = cfg.GetString(section, "keyfile")
				if err != nil {
					log.Printf("error while getting[%s]keyfile: %v, ignoring.", section, err)
					continue
				}
				if frontend.KeyFile == "" {
					log.Printf("frontend %s has HTTPS enabled but no keyfile, ignoring.", frontend_name)
					continue
				}

				frontend.CertFile, err = cfg.GetString(section, "certfile")
				if err != nil {
					log.Printf("error while getting[%s]certfile: %v, ignoring.", section, err)
					continue
				}
				if frontend.CertFile == "" {
					log.Printf("frontend %s has HTTPS enabled but no certfile, ignoring.", frontend_name)
					continue
				}
			}

			frontend_hosts, err := cfg.GetString(section, "hosts")
			if err == nil && frontend_hosts != "" {
				frontend.Hosts = strings.Split(frontend_hosts, " ")
			}

			frontend_backends, err := cfg.GetString(section, "backends")
			if err == nil && frontend_backends != "" {
				frontend.Backends = strings.Split(frontend_backends, " ")
			}

			frontend.AddForwarded, _ = cfg.GetBool(section, "add-x-forwarded-for")

			if len(frontend.Backends) == 0 && len(frontend.Hosts) == 0 {
				log.Printf("frontend %s has neither backends nor hosts configured, ignoring.", frontend_name)
				continue
			}

			frontends[frontend_name] = frontend
		}
	}

	count := 0
	exit_chan := make(chan int)
	for name, frontend := range frontends {
		log.Printf("Starting frontend %s...", name)
		go func(fe *Frontend, name string) {
			var accesslogger *log.Logger
			if access_f != nil {
				accesslogger = log.New(access_f, "frontend:"+name+" ", log.Ldate|log.Ltime|log.Lmicroseconds)
			} else {
				log.Printf("Not creating logger for frontend %s", name)
			}
			fe.Start(hosts, backends, accesslogger)
			exit_chan <- 1
		}(frontend, name)
		count++
	}

	// this shouldn't return
	for i := 0; i < count; i++ {
		<-exit_chan
	}
}

func (f *Frontend) Start(hosts map[string][]*Backend, backends map[string]*Backend, logger *log.Logger) {
	mux := http.NewServeMux()

	hosts_chans := make(map[string]chan *Backend)

	for _, h := range f.Hosts {
		host_chan := make(chan *Backend, len(hosts[h]))
		for _, b := range hosts[h] {
			host_chan <- b
		}
		hosts_chans[h] = host_chan
	}

	backends_chan := make(chan *Backend, len(f.Backends))

	for _, b := range f.Backends {
		backends_chan <- backends[b]
	}

	var request_handler http.Handler = &RequestHandler{Transport: &http.Transport{DisableKeepAlives: false, DisableCompression: false}, Frontend: f, HostBackends: hosts_chans, Backends: backends_chan}

	if logger != nil {
		request_handler = NewRequestLogger(request_handler, *logger)
	}

	mux.Handle("/", request_handler)

	srv := &http.Server{Handler: mux, Addr: f.BindString}

	if f.HTTPS {
		if err := srv.ListenAndServeTLS(f.CertFile, f.KeyFile); err != nil {
			log.Printf("Starting HTTPS frontend %s failed: %v", f.Name, err)
		}
	} else {
		if err := srv.ListenAndServe(); err != nil {
			log.Printf("Starting frontend %s failed: %v", f.Name, err)
		}
	}
}
<commit_msg>Fork of drunken-hipster and refactor websocket detection<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	goconf "github.com/akrennmair/goconf"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"strings"
)

type Backend struct {
	Name          string
	ConnectString string
}
type Frontend struct {
	Name         string
	BindString   string
	HTTPS        bool
	AddForwarded bool
	Hosts        []string
	Backends     []string
	//AddHeader    struct { Key string; Value string }
	KeyFile  string
	CertFile string
}

func shouldUpgradeWebsocket(r *http.Request) bool {
	conn_hdr := ""
	conn_hdrs := r.Header["Connection"]
	log.Printf("Connection headers: %v", conn_hdrs)
	if len(conn_hdrs) > 0 {
		conn_hdr = conn_hdrs[0]
	}
	upgrade_websocket := false
	if strings.ToLower(conn_hdr) == "upgrade" {
		log.Printf("got Connection: Upgrade")
		upgrade_hdrs := r.Header["Upgrade"]
		log.Printf("Upgrade headers: %v", upgrade_hdrs)
		if len(upgrade_hdrs) > 0 {
			upgrade_websocket = (strings.ToLower(upgrade_hdrs[0]) == "websocket")
		}
	}
	return upgrade_websocket
}

func Copy(dest *bufio.ReadWriter, src *bufio.ReadWriter) {
	buf := make([]byte, 40*1024)
	for {
		n, err := src.Read(buf)
		if err != nil && err != io.EOF {
			log.Printf("Read failed: %v", err)
			return
		}
		if n == 0 {
			return
		}
		dest.Write(buf[0:n])
		dest.Flush()
	}
}
func CopyBidir(conn1 io.ReadWriteCloser, rw1 *bufio.ReadWriter, conn2 io.ReadWriteCloser, rw2 *bufio.ReadWriter) {
	finished := make(chan bool)
	go func() {
		Copy(rw2, rw1)
		conn2.Close()
		finished <- true
	}()
	go func() {
		Copy(rw1, rw2)
		conn1.Close()
		finished <- true
	}()
	<-finished
	<-finished
}

type RequestHandler struct {
	Transport    *http.Transport
	Frontend     *Frontend
	HostBackends map[string]chan *Backend
	Backends     chan *Backend
}

func (h *RequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	//log.Printf("incoming request: %#v", *r)
	r.RequestURI = ""
	r.URL.Scheme = "http"
	if h.Frontend.AddForwarded {
		remote_addr := r.RemoteAddr
		idx := strings.LastIndex(remote_addr, ":")
		if idx != -1 {
			remote_addr = remote_addr[0:idx]
			if remote_addr[0] == '[' && remote_addr[len(remote_addr)-1] == ']' {
				remote_addr = remote_addr[1 : len(remote_addr)-1]
			}
		}
		r.Header.Add("X-Forwarded-For", remote_addr)
	}
	if len(h.Frontend.Hosts) == 0 {
		backend := <-h.Backends
		r.URL.Host = backend.ConnectString
		h.Backends <- backend
	} else {
		backend_list := h.HostBackends[r.Host]
		if backend_list == nil {
			if len(h.Frontend.Backends) == 0 {
				http.Error(w, "no suitable backend found for request", http.StatusServiceUnavailable)
				return
			} else {
				backend := <-h.Backends
				r.URL.Host = backend.ConnectString
				h.Backends <- backend
			}
		} else {
			backend := <-backend_list
			r.URL.Host = backend.ConnectString
			backend_list <- backend
		}
	}
	upgrade_websocket := shouldUpgradeWebsocket(r)
	if upgrade_websocket {
		hj, ok := w.(http.Hijacker)
		if !ok {
			http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
			return
		}
		conn, bufrw, err := hj.Hijack()
		defer conn.Close()
		conn2, err := net.Dial("tcp", r.URL.Host)
		if err != nil {
			http.Error(w, "couldn't connect to backend server", http.StatusServiceUnavailable)
			return
		}
		defer conn2.Close()
		err = r.Write(conn2)
		if err != nil {
			log.Printf("writing WebSocket request to backend server failed: %v", err)
			return
		}
		CopyBidir(conn, bufrw, conn2, bufio.NewReadWriter(bufio.NewReader(conn2), bufio.NewWriter(conn2)))
	} else {
		resp, err := h.Transport.RoundTrip(r)
		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprintf(w, "Error: %v", err)
			return
		}
		for k, v := range resp.Header {
			for _, vv := range v {
				w.Header().Add(k, vv)
			}
		}
		w.WriteHeader(resp.StatusCode)
		io.Copy(w, resp.Body)
		resp.Body.Close()
	}
}
func usage() {
	fmt.Fprintf(os.Stdout, "usage: %s -config=<configfile>\n", os.Args[0])
	os.Exit(1)
}
func main() {
	var cfgfile *string = flag.String("config", "", "configuration file")
	backends := make(map[string]*Backend)
	hosts := make(map[string][]*Backend)
	frontends := make(map[string]*Frontend)
	flag.Parse()
	if *cfgfile == "" {
		usage()
	}
	cfg, err := goconf.ReadConfigFile(*cfgfile)
	if err != nil {
		log.Printf("opening %s failed: %v", *cfgfile, err)
		os.Exit(1)
	}
	var access_f io.WriteCloser
	accesslog_file, err := cfg.GetString("global", "accesslog")
	if err == nil {
		access_f, err = os.OpenFile(accesslog_file, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
		if err == nil {
			defer access_f.Close()
		} else {
			log.Printf("Opening access log %s failed: %v", accesslog_file, err)
		}
	}
	// first, extract backends
	for _, section := range cfg.GetSections() {
		if strings.HasPrefix(section, "backend ") {
			tokens := strings.Split(section, " ")
			if len(tokens) < 2 {
				log.Printf("backend section has no name, ignoring.")
				continue
			}
			connect_str, _ := cfg.GetString(section, "connect")
			if connect_str == "" {
				log.Printf("empty connect string for backend %s, ignoring.", tokens[1])
				continue
			}
			b := &Backend{Name: tokens[1], ConnectString: connect_str}
			backends[b.Name] = b
		}
	}
	// then extract hosts
	for _, section := range cfg.GetSections() {
		if strings.HasPrefix(section, "host ") {
			tokens := strings.Split(section, " ")
			if len(tokens) < 2 {
				log.Printf("host section has no name, ignoring.")
				continue
			}
			backends_str, _ := cfg.GetString(section, "backends")
			backends_list := strings.Split(backends_str, " ")
			if len(backends_list) == 0 {
				log.Printf("host %s has no backends, ignoring.", tokens[1])
				continue
			}
			for _, host := range tokens[1:] {
				backends_for_host := []*Backend{}
				for _, backend := range backends_list {
					b := backends[backend]
					if b == nil {
						log.Printf("backend %s doesn't exist, ignoring.", backend)
					}
					backends_for_host = append(backends_for_host, b)
				}
				hosts[host] = backends_for_host
			}
		}
	}
	// and finally, extract frontends
	for _, section := range cfg.GetSections() {
		if strings.HasPrefix(section, "frontend ") {
			tokens := strings.Split(section, " ")
			if len(tokens) < 2 {
				log.Printf("frontend section has no name, ignoring.")
				continue
			}
			frontend_name := tokens[1]
			frontend := &Frontend{}
			frontend.Name = frontend_name
			frontend.BindString, err = cfg.GetString(section, "bind")
			if err != nil {
				log.Printf("error while getting [%s]bind: %v, ignoring.", section, err)
				continue
			}
			if frontend.BindString == "" {
				log.Printf("frontend %s has no bind argument, ignoring.", frontend_name)
				continue
			}
			frontend.HTTPS, err = cfg.GetBool(section, "https")
			if err != nil {
				frontend.HTTPS = false
			}
			if frontend.HTTPS {
				frontend.KeyFile, err = cfg.GetString(section, "keyfile")
				if err != nil {
					log.Printf("error while getting[%s]keyfile: %v, ignoring.", section, err)
					continue
				}
				if frontend.KeyFile == "" {
					log.Printf("frontend %s has HTTPS enabled but no keyfile, ignoring.", frontend_name)
					continue
				}
				frontend.CertFile, err = cfg.GetString(section, "certfile")
				if err != nil {
					log.Printf("error while getting[%s]certfile: %v, ignoring.", section, err)
					continue
				}
				if frontend.CertFile == "" {
					log.Printf("frontend %s has HTTPS enabled but no certfile, ignoring.", frontend_name)
					continue
				}
			}
			frontend_hosts, err := cfg.GetString(section, "hosts")
			if err == nil && frontend_hosts != "" {
				frontend.Hosts = strings.Split(frontend_hosts, " ")
			}
			frontend_backends, err := cfg.GetString(section, "backends")
			if err == nil && frontend_backends != "" {
				frontend.Backends = strings.Split(frontend_backends, " ")
			}
			frontend.AddForwarded, _ = cfg.GetBool(section, "add-x-forwarded-for")
			if len(frontend.Backends) == 0 && len(frontend.Hosts) == 0 {
				log.Printf("frontend %s has neither backends nor hosts configured, ignoring.", frontend_name)
				continue
			}
			frontends[frontend_name] = frontend
		}
	}
	count := 0
	exit_chan := make(chan int)
	for name, frontend := range frontends {
		log.Printf("Starting frontend %s...", name)
		go func(fe *Frontend, name string) {
			var accesslogger *log.Logger
			if access_f != nil {
				accesslogger = log.New(access_f, "frontend:"+name+" ", log.Ldate|log.Ltime|log.Lmicroseconds)
			} else {
				log.Printf("Not creating logger for frontend %s", name)
			}
			fe.Start(hosts, backends, accesslogger)
			exit_chan <- 1
		}(frontend, name)
		count++
	}
	// this shouldn't return
	for i := 0; i < count; i++ {
		<-exit_chan
	}
}
func (f *Frontend) Start(hosts map[string][]*Backend, backends map[string]*Backend, logger *log.Logger) {
	mux := http.NewServeMux()
	hosts_chans := make(map[string]chan *Backend)
	for _, h := range f.Hosts {
		host_chan := make(chan *Backend, len(hosts[h]))
		for _, b := range hosts[h] {
			host_chan <- b
		}
		hosts_chans[h] = host_chan
	}
	backends_chan := make(chan *Backend, len(f.Backends))
	for _, b := range f.Backends {
		backends_chan <- backends[b]
	}
	var request_handler http.Handler = &RequestHandler{Transport: &http.Transport{DisableKeepAlives: false, DisableCompression: false}, Frontend: f, HostBackends: hosts_chans, Backends: backends_chan}
	if logger != nil {
		request_handler = NewRequestLogger(request_handler, *logger)
	}
	mux.Handle("/", request_handler)
	srv := &http.Server{Handler: mux, Addr: f.BindString}
	if f.HTTPS {
		if err := srv.ListenAndServeTLS(f.CertFile, f.KeyFile); err != nil {
			log.Printf("Starting HTTPS frontend %s failed: %v", f.Name, err)
		}
	} else {
		if err := srv.ListenAndServe(); err != nil {
			log.Printf("Starting frontend %s failed: %v", f.Name, err)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	"os"
	"os/signal"
	"os/user"
	"syscall"
	"time"

	"github.com/mdlayher/wavepipe/core"
)

// dbFlag points to the location of the wavepipe.db sqlite3 database
var dbFlag = flag.String("db", "", "Location of "+core.App+" sqlite3 database.")

// testFlag invokes wavepipe in "test" mode, where it will start and exit shortly after.  Used for testing.
var testFlag = flag.Bool("test", false, "Starts "+core.App+" in test mode, causing it to exit shortly after starting.")

func main() {
	// Set up logging, parse flags
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
	flag.Parse()

	// Check if wavepipe was invoked as root (which is a really bad idea)
	currUser, err := user.Current()
	if err != nil {
		log.Fatal(core.App, ": could not determine current user, exiting")
	}

	// Check for root
	if currUser.Uid == "0" || currUser.Gid == "0" || currUser.Username == "root" {
		log.Fatal(core.App, ": cannot be run as root, exiting")
	}

	// Check command-line arguments, last one should be the media path to scan
	if len(os.Args) < 3 || (*testFlag && len(os.Args) < 4) {
		log.Fatalf("usage: %s -db=%s.db [-test] /media/path", core.App, core.App)
	}

	// Get the database path
	// TODO: replace with configuration later
	core.DBPath = *dbFlag

	// Get the last argument as the media path
	// TODO: replace with configuration later
	core.MediaFolder = os.Args[len(os.Args)-1]

	// Application entry point
	log.Println(core.App, ": starting...")

	// Invoke the manager, with graceful termination and core.Application exit code channels
	killChan := make(chan struct{})
	exitChan := make(chan int)
	go core.Manager(killChan, exitChan)

	// Gracefully handle termination via UNIX signal
	sigChan := make(chan os.Signal, 1)

	// In test mode, wait for a short time, then invoke a signal shutdown
	if *testFlag {
		go func() {
			// Set an environment variable to enable mocking in other areas of the program
			if err := os.Setenv("WAVEPIPE_TEST", "1"); err != nil {
				log.Println(err)
			}

			// Wait 5 seconds, to allow reasonable startup time
			seconds := 5
			log.Println(core.App, ": started in test mode, stopping in", seconds, "seconds.")
			<-time.After(time.Duration(seconds) * time.Second)

			// Send interrupt
			sigChan <- os.Interrupt
		}()
	}

	// Trigger a shutdown if SIGINT or SIGTERM received
	signal.Notify(sigChan, os.Interrupt)
	signal.Notify(sigChan, syscall.SIGTERM)
	for sig := range sigChan {
		log.Println(core.App, ": caught signal:", sig)
		killChan <- struct{}{}
		break
	}

	// Force terminate if signaled twice
	go func() {
		for sig := range sigChan {
			log.Println(core.App, ": caught signal:", sig, ", force halting now!")
			os.Exit(1)
		}
	}()

	// Graceful exit
	code := <-exitChan
	log.Println(core.App, ": graceful shutdown complete")
	os.Exit(code)
}
<commit_msg>Remove config flags in place of config file<commit_after>package main

import (
	"flag"
	"log"
	"os"
	"os/signal"
	"os/user"
	"syscall"
	"time"

	"github.com/mdlayher/wavepipe/core"
)

// testFlag invokes wavepipe in "test" mode, where it will start and exit shortly after.  Used for testing.
var testFlag = flag.Bool("test", false, "Starts "+core.App+" in test mode, causing it to exit shortly after starting.")

func main() {
	// Set up logging, parse flags
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
	flag.Parse()

	// Check if wavepipe was invoked as root (which is a really bad idea)
	currUser, err := user.Current()
	if err != nil {
		log.Fatal(core.App, ": could not determine current user, exiting")
	}

	// Check for root
	if currUser.Uid == "0" || currUser.Gid == "0" || currUser.Username == "root" {
		log.Fatal(core.App, ": cannot be run as root, exiting")
	}

	// Set configuration path
	core.ConfigPath = currUser.HomeDir + "/.config/wavepipe/wavepipe.json"

	// Application entry point
	log.Println(core.App, ": starting...")

	// Invoke the manager, with graceful termination and core.Application exit code channels
	killChan := make(chan struct{})
	exitChan := make(chan int)
	go core.Manager(killChan, exitChan)

	// Gracefully handle termination via UNIX signal
	sigChan := make(chan os.Signal, 1)

	// In test mode, wait for a short time, then invoke a signal shutdown
	if *testFlag {
		go func() {
			// Set an environment variable to enable mocking in other areas of the program
			if err := os.Setenv("WAVEPIPE_TEST", "1"); err != nil {
				log.Println(err)
			}

			// Wait 5 seconds, to allow reasonable startup time
			seconds := 5
			log.Println(core.App, ": started in test mode, stopping in", seconds, "seconds.")
			<-time.After(time.Duration(seconds) * time.Second)

			// Send interrupt
			sigChan <- os.Interrupt
		}()
	}

	// Trigger a shutdown if SIGINT or SIGTERM received
	signal.Notify(sigChan, os.Interrupt)
	signal.Notify(sigChan, syscall.SIGTERM)
	for sig := range sigChan {
		log.Println(core.App, ": caught signal:", sig)
		killChan <- struct{}{}
		break
	}

	// Force terminate if signaled twice
	go func() {
		for sig := range sigChan {
			log.Println(core.App, ": caught signal:", sig, ", force halting now!")
			os.Exit(1)
		}
	}()

	// Graceful exit
	code := <-exitChan
	log.Println(core.App, ": graceful shutdown complete")
	os.Exit(code)
}
<|endoftext|>


<commit_before>package drip

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

const baseURL = "https://api.getdrip.com/v2/"

// Client represents a Drip API client wrapper
type Client struct {
	apiKey     string
	appID      string
	httpClient http.Client
}

type Subscriber struct {
	Email        string                 `json:"email"`
	CustomFields map[string]interface{} `json:"custom_fields,omitempty"`
	Tags         []string               `json:"tags,omitempty"`

	customFieldLock sync.Mutex
}

func NewSubscriber(email string) Subscriber {
	return Subscriber{
		Email:        email,
		CustomFields: map[string]interface{}{},
	}
}

func (s *Subscriber) AddCustomField(key, value string) {
	s.customFieldLock.Lock()
	defer s.customFieldLock.Unlock()

	s.CustomFields[NormalizeKey(key)] = value
}

type subRoot struct {
	Subscribers []Subscriber `json:"subscribers"`
}

type eventRoot struct {
	Events []eventParams `json:"events"`
}

type eventParams struct {
	Email  string `json:"email"`
	Action string `json:"action"`
}

type tagRoot struct {
	Tags []tagParams `json:"tags"`
}

type tagParams struct {
	Email string `json:"email"`
	Tag   string `json:"tag"`
}

type batchReq struct {
	Batches []subRoot `json:"batches"`
}

// NewClient returns a client instance ready to act with Drip for the given app and API key
func NewClient(apiKey, appID string) *Client {
	client := &Client{
		apiKey: apiKey,
		appID:  appID,
		httpClient: http.Client{
			Timeout: time.Second * 10,
			Transport: &http.Transport{
				Dial: (&net.Dialer{
					Timeout: time.Second * 5,
				}).Dial,
				TLSHandshakeTimeout: time.Second * 5,
			},
		},
	}

	return client
}

// CreateSubscriber creates a new or updates an existing subscriber by email
func (c Client) CreateSubscriber(email string, customFields map[string]interface{}) error {
	bodyData := subRoot{
		Subscribers: []Subscriber{
			{Email: email, CustomFields: NormalizedFields(customFields)},
		},
	}

	return c.authenticatedPost("/subscribers", bodyData)
}

// RecordEvent sends a custom event to Drip
func (c Client) RecordEvent(email, eventName string) error {
	bodyData := eventRoot{
		Events: []eventParams{
			{Email: email, Action: eventName},
		},
	}
	return c.authenticatedPost("/events", bodyData)
}

// TagSubscriber adds a tag to a subscriber
func (c Client) TagSubscriber(email, tag string) error {
	data := tagRoot{
		Tags: []tagParams{
			{Email: email, Tag: tag},
		},
	}
	return c.authenticatedPost("/tags", data)
}

// UntagSubscriber removes a tag from the subscriber email address
func (c Client) UntagSubscriber(email, tagName string) error {
	path := fmt.Sprintf("/subscribers/%s/tags/%s", email, tagName)
	req, err := c.authenticatedRequest("DELETE", path, nil)
	if err != nil {
		err = fmt.Errorf("failed to create request to untag subscriber: %w", err)
		return err
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		err = fmt.Errorf("failed to DELETE Drip tag: %w", err)
		return err
	}

	if resp.StatusCode != 204 {
		body, _ := ioutil.ReadAll(resp.Body)
		_ = resp.Body.Close()
		err = fmt.Errorf("untag subscriber not successful - %s: %w", string(body), err)
		return err
	}

	return nil
}

func (c Client) BatchUpdateSubscribers(subscribers []Subscriber) error {
	return c.authenticatedPost("/subscribers/batches", batchReq{Batches: []subRoot{{Subscribers: subscribers}}})
}

func NormalizedFields(customFields map[string]interface{}) map[string]interface{} {
	dripFields := map[string]interface{}{}
	for key, value := range customFields {
		dripFields[NormalizeKey(key)] = value
	}
	return dripFields
}

func NormalizeKey(key string) string {
	newKey := key
	newKey = strings.Replace(key, "$", "", -1)
	newKey = strings.Replace(newKey, " ", "_", -1)
	newKey = strings.ToLower(newKey)
	return newKey
}

func (c Client) authenticatedPost(path string, body interface{}) error {
	postBody, err := json.Marshal(body)
	if err != nil {
		return err
	}
	req, err := c.authenticatedRequest("POST", path, bytes.NewReader(postBody))
	req.Header.Add("Content-Type", "application/json")
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		body, err := ioutil.ReadAll(resp.Body)
		defer func() { _ = resp.Body.Close() }()
		if err != nil {
			return err
		}
		return fmt.Errorf("Drip API error: %s (%d): %s", baseURL+c.appID+path, resp.StatusCode, string(body))
	}

	return nil
}

func (c Client) authenticatedRequest(method, path string, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequest(method, baseURL+c.appID+path, body)
	req.SetBasicAuth(c.apiKey, "")
	req.Header.Add("Accept", "application/vnd.api+json")

	return req, err
}
<commit_msg>Remove double slash in URLs<commit_after>package drip

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

const baseURL = "https://api.getdrip.com/v2"

// Client represents a Drip API client wrapper
type Client struct {
	apiKey     string
	appID      string
	httpClient http.Client
}

type Subscriber struct {
	Email        string                 `json:"email"`
	CustomFields map[string]interface{} `json:"custom_fields,omitempty"`
	Tags         []string               `json:"tags,omitempty"`

	customFieldLock sync.Mutex
}

func NewSubscriber(email string) Subscriber {
	return Subscriber{
		Email:        email,
		CustomFields: map[string]interface{}{},
	}
}

func (s *Subscriber) AddCustomField(key, value string) {
	s.customFieldLock.Lock()
	defer s.customFieldLock.Unlock()

	s.CustomFields[NormalizeKey(key)] = value
}

type subRoot struct {
	Subscribers []Subscriber `json:"subscribers"`
}

type eventRoot struct {
	Events []eventParams `json:"events"`
}

type eventParams struct {
	Email  string `json:"email"`
	Action string `json:"action"`
}

type tagRoot struct {
	Tags []tagParams `json:"tags"`
}

type tagParams struct {
	Email string `json:"email"`
	Tag   string `json:"tag"`
}

type batchReq struct {
	Batches []subRoot `json:"batches"`
}

// NewClient returns a client instance ready to act with Drip for the given app and API key
func NewClient(apiKey, appID string) *Client {
	client := &Client{
		apiKey: apiKey,
		appID:  appID,
		httpClient: http.Client{
			Timeout: time.Second * 10,
			Transport: &http.Transport{
				Dial: (&net.Dialer{
					Timeout: time.Second * 5,
				}).Dial,
				TLSHandshakeTimeout: time.Second * 5,
			},
		},
	}

	return client
}

// CreateSubscriber creates a new or updates an existing subscriber by email
func (c Client) CreateSubscriber(email string, customFields map[string]interface{}) error {
	bodyData := subRoot{
		Subscribers: []Subscriber{
			{Email: email, CustomFields: NormalizedFields(customFields)},
		},
	}

	return c.authenticatedPost("/subscribers", bodyData)
}

// RecordEvent sends a custom event to Drip
func (c Client) RecordEvent(email, eventName string) error {
	bodyData := eventRoot{
		Events: []eventParams{
			{Email: email, Action: eventName},
		},
	}
	return c.authenticatedPost("/events", bodyData)
}

// TagSubscriber adds a tag to a subscriber
func (c Client) TagSubscriber(email, tag string) error {
	data := tagRoot{
		Tags: []tagParams{
			{Email: email, Tag: tag},
		},
	}
	return c.authenticatedPost("/tags", data)
}

// UntagSubscriber removes a tag from the subscriber email address
func (c Client) UntagSubscriber(email, tagName string) error {
	path := fmt.Sprintf("/subscribers/%s/tags/%s", email, tagName)
	req, err := c.authenticatedRequest("DELETE", path, nil)
	if err != nil {
		err = fmt.Errorf("failed to create request to untag subscriber: %w", err)
		return err
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		err = fmt.Errorf("failed to DELETE Drip tag: %w", err)
		return err
	}

	if resp.StatusCode != 204 {
		body, _ := ioutil.ReadAll(resp.Body)
		_ = resp.Body.Close()
		err = fmt.Errorf("untag subscriber not successful - %s: %w", string(body), err)
		return err
	}

	return nil
}

func (c Client) BatchUpdateSubscribers(subscribers []Subscriber) error {
	return c.authenticatedPost("/subscribers/batches", batchReq{Batches: []subRoot{{Subscribers: subscribers}}})
}

func NormalizedFields(customFields map[string]interface{}) map[string]interface{} {
	dripFields := map[string]interface{}{}
	for key, value := range customFields {
		dripFields[NormalizeKey(key)] = value
	}
	return dripFields
}

func NormalizeKey(key string) string {
	newKey := key
	newKey = strings.Replace(key, "$", "", -1)
	newKey = strings.Replace(newKey, " ", "_", -1)
	newKey = strings.ToLower(newKey)
	return newKey
}

func (c Client) authenticatedPost(path string, body interface{}) error {
	postBody, err := json.Marshal(body)
	if err != nil {
		return err
	}
	req, err := c.authenticatedRequest("POST", path, bytes.NewReader(postBody))
	req.Header.Add("Content-Type", "application/json")
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return err
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		body, err := ioutil.ReadAll(resp.Body)
		defer func() { _ = resp.Body.Close() }()
		if err != nil {
			return err
		}
		return fmt.Errorf("Drip API error: %s (%d): %s", baseURL+c.appID+path, resp.StatusCode, string(body))
	}

	return nil
}

func (c Client) authenticatedRequest(method, path string, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequest(method, baseURL+c.appID+path, body)
	req.SetBasicAuth(c.apiKey, "")
	req.Header.Add("Accept", "application/vnd.api+json")

	return req, err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/crockeo/go-tuner/convert"
	"github.com/crockeo/go-tuner/filestore"
	"github.com/crockeo/go-tuner/server"
	"github.com/crockeo/go-tuner/synth"
	"github.com/crockeo/go-tuner/visualize"
	"os"
)

// Printing out help information for the user.
func printHelp() {
	fmt.Println("Usage:")
	fmt.Println(" go-tuner server")
	fmt.Println(" go-tuner file <file/path>")
	fmt.Println(" go-tuner visualize <file/path>")
	fmt.Println(" go-tuner convert <original/file/path> <new/file/path>")
}

// Handling er
func handleErrors(errChannel chan error) {
	for {
		err := <-errChannel
		if err != nil {
			fmt.Println(err.Error())
		}
	}
}

// The entry point to the application.
func main() {
	if len(os.Args) < 2 || os.Args[1] == "help" {
		printHelp()
		return
	}

	errChannel := make(chan error, 8)
	noteChannel := make(chan synth.DelayedNoteData, 32)

	go handleErrors(errChannel)
	go server.Start(errChannel, noteChannel)

	if os.Args[1] == "server" {
		err := synth.StartSynth(noteChannel)
		if err != nil {
			fmt.Println(err.Error())
		}
	} else if os.Args[1] == "file" {
		if len(os.Args) != 3 {
			printHelp()
			return
		}

		na, err := filestore.LoadNoteArrangement(os.Args[2])
		if err != nil {
			fmt.Println(err.Error())
			return
		}

		if err = synth.StartSynthWith(na, noteChannel, true); err != nil {
			fmt.Println(err.Error())
		}
	} else if os.Args[1] == "visualize" {
		quitChannel := make(chan bool)
		defer close(quitChannel)

		go synth.StartSynthAsync(noteChannel, quitChannel, errChannel)

		if err := visualize.Testing(); err != nil {
			fmt.Println("Visualize error: " + err.Error())
		}

		quitChannel <- true
	} else if os.Args[1] == "convert" {
		if len(os.Args) != 4 {
			printHelp()
			return
		}

		if err := convert.ConvertFiles(os.Args[2], os.Args[3]); err != nil {
			fmt.Println(err.Error())
		}
	} else {
		printHelp()
	}
}
<commit_msg>Optimized out the TCP server unless necessary.<commit_after>package main

import (
	"fmt"
	"github.com/crockeo/go-tuner/convert"
	"github.com/crockeo/go-tuner/filestore"
	"github.com/crockeo/go-tuner/server"
	"github.com/crockeo/go-tuner/synth"
	"github.com/crockeo/go-tuner/visualize"
	"os"
)

// Printing out help information for the user.
func printHelp() {
	fmt.Println("Usage:")
	fmt.Println(" go-tuner server")
	fmt.Println(" go-tuner file <file/path>")
	fmt.Println(" go-tuner visualize <file/path>")
	fmt.Println(" go-tuner convert <original/file/path> <new/file/path>")
}

// Handling er
func handleErrors(errChannel chan error) {
	for {
		err := <-errChannel
		if err != nil {
			fmt.Println(err.Error())
		}
	}
}

// The entry point to the application.
func main() {
	if len(os.Args) < 2 || os.Args[1] == "help" {
		printHelp()
		return
	}

	errChannel := make(chan error, 8)
	defer close(errChannel)
	go handleErrors(errChannel)

	if os.Args[1] == "server" {
		noteChannel := make(chan synth.DelayedNoteData, 32)
		defer close(noteChannel)
		go server.Start(errChannel, noteChannel)

		err := synth.StartSynth(noteChannel)
		if err != nil {
			fmt.Println(err.Error())
		}
	} else if os.Args[1] == "file" {
		if len(os.Args) != 3 {
			printHelp()
			return
		}

		na, err := filestore.LoadNoteArrangement(os.Args[2])
		if err != nil {
			fmt.Println(err.Error())
			return
		}

		if err = synth.StartSynthWith(na, make(chan synth.DelayedNoteData), true); err != nil {
			fmt.Println(err.Error())
		}
	} else if os.Args[1] == "visualize" {
		quitChannel := make(chan bool)
		defer close(quitChannel)

		go synth.StartSynthAsync(make(chan synth.DelayedNoteData), quitChannel, errChannel)

		if err := visualize.Testing(); err != nil {
			fmt.Println("Visualize error: " + err.Error())
		}

		quitChannel <- true
	} else if os.Args[1] == "convert" {
		if len(os.Args) != 4 {
			printHelp()
			return
		}

		if err := convert.ConvertFiles(os.Args[2], os.Args[3]); err != nil {
			fmt.Println(err.Error())
		}
	} else {
		printHelp()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	flags "github.com/jessevdk/go-flags"
	"github.com/nerdalize/nerd/command"
	"github.com/nerdalize/nerd/nerd"
	"github.com/nerdalize/nerd/nerd/conf"

	"github.com/mitchellh/cli"
)

var (
	name    = "nerd"
	version = "build.from.src"
	commit  = "0000000"
)

type ConfOpts struct {
	ConfigFile string `long:"config" default:"" default-mask:"" env:"CONFIG" description:"location of config file"`
}

func init() {
	opts := new(ConfOpts)
	_, err := flags.ParseArgs(opts, os.Args[1:])
	if err == nil {
		conf.SetLocation(opts.ConfigFile)
	}
	nerd.SetupLogging()
}

func main() {
	c := cli.NewCLI(name, fmt.Sprintf("%s (%s)", version, commit))
	c.Args = os.Args[1:]
	c.Commands = map[string]cli.CommandFactory{
		"login":    command.LoginFactory(),
		"upload":   command.UploadFactory(),
		"run":      command.RunFactory(),
		"logs":     command.LogsFactory(),
		"work":     command.WorkFactory(),
		"status":   command.StatusFactory(),
		"download": command.DownloadFactory(),
	}

	status, err := c.Run()
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s: %s", name, err)
	}

	os.Exit(status)
}
<commit_msg>Suppress unknown flag errors<commit_after>package main

import (
	"fmt"
	"os"

	flags "github.com/jessevdk/go-flags"
	"github.com/nerdalize/nerd/command"
	"github.com/nerdalize/nerd/nerd"
	"github.com/nerdalize/nerd/nerd/conf"

	"github.com/mitchellh/cli"
)

var (
	name    = "nerd"
	version = "build.from.src"
	commit  = "0000000"
)

type ConfOpts struct {
	ConfigFile string `long:"config" default:"" default-mask:"" env:"CONFIG" description:"location of config file"`
}

func init() {
	opts := new(ConfOpts)
	_, err := flags.NewParser(opts, flags.IgnoreUnknown).ParseArgs(os.Args[1:])
	if err == nil {
		conf.SetLocation(opts.ConfigFile)
	}
	nerd.SetupLogging()
}

func main() {
	c := cli.NewCLI(name, fmt.Sprintf("%s (%s)", version, commit))
	c.Args = os.Args[1:]
	c.Commands = map[string]cli.CommandFactory{
		"login":    command.LoginFactory(),
		"upload":   command.UploadFactory(),
		"run":      command.RunFactory(),
		"logs":     command.LogsFactory(),
		"work":     command.WorkFactory(),
		"status":   command.StatusFactory(),
		"download": command.DownloadFactory(),
	}

	status, err := c.Run()
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s: %s", name, err)
	}

	os.Exit(status)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/olekukonko/tablewriter"
	"go/ast"
	"go/types"
	"os"
	"sort"

	"golang.org/x/tools/go/loader"
)

func main() {
	var conf loader.Config

	conf.CreateFromFilenames(".", os.Args[1:]...)
	p, err := conf.Load()
	if err != nil {
		fmt.Println(err)
		return
	}

	w := NewWalker(p)

	// work only with top package for now.
	// TODO: work with all recursive sub-packages (optionally?)
	top := p.InitialPackages()[0]

	// find all selectors ('f.x') for imported packages
	selectors := make(map[string]Selector)
	counter := make(map[string]int)
	for _, f := range top.Files {
		ast.Inspect(f, func(n ast.Node) bool {
			switch x := n.(type) {
			case *ast.SelectorExpr:
				var (
					n   string // package name
					obj types.Object
				)

				// handle methods
				s, ok := top.Selections[x]
				if ok {
					// (pkg).pkgvar.Method()
					obj = s.Obj()
					n = s.Obj().Pkg().Name()
				} else {
					// pkg.Func()
					n = pkgName(x)
				}

				// if it's not a selector for external package, skip it
				pkg, ok := w.Packages[n]
				if !ok {
					break
				}

				sel := Selector{
					Pkg:  pkg,
					Name: x.Sel.Name,
				}

				// lookup this object in package
				dp := p.Package(pkg.Path)
				if obj == nil {
					obj = w.FindObject(dp, sel.Name)
					if obj == nil {
						return true
					}
				}
				if _, ok := obj.Type().(*types.Signature); ok {
					sel.Type = "func"

					node := w.FindFnNode(dp, x.Sel.Name)
					if node != nil {
						lines := w.Lines(node)
						_, depth, linesCum, depthInt := w.WalkExternal(node, dp)
						sel.LOC, sel.Depth = lines, depth
						sel.LOCCum, sel.DepthInternal = linesCum, depthInt
					}
				}
				selectors[sel.Name] = sel
				counter[sel.Name]++
			}
			return true
		})
	}

	// Print stats
	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"Name", "LOC", "Type", "Count", "Depth", "LOCCum", "DepthInt"})
	var results [][]string
	for name, count := range counter {
		sel := selectors[name]
		loc := fmt.Sprintf("%d", sel.LOC)
		depth := fmt.Sprintf("%d", sel.Depth)
		locCum := fmt.Sprintf("%d", sel.LOCCum)
		depthInt := fmt.Sprintf("%d", sel.DepthInternal)
		count := fmt.Sprintf("%d", count)
		results = append(results, []string{name, loc, sel.Type, count, depth, locCum, depthInt})
	}
	sort.Sort(ByName(results))
	for _, v := range results {
		table.Append(v)
	}
	table.Render() // Send output
}

func (w *Walker) Lines(node ast.Node) int {
	var (
		lines int
		ok    bool
	)
	if lines, ok = w.CacheLOC[node]; ok {
		return lines
	}
	ast.Inspect(node, func(n ast.Node) bool {
		switch x := n.(type) {
		case *ast.FuncDecl:
			if x.Body == nil {
				break
			}
			start := w.P.Fset.Position(x.Body.Lbrace)
			end := w.P.Fset.Position(x.Body.Rbrace)
			lines = end.Line - start.Line
			if lines == 0 {
				lines = 1
			}
			w.CacheLOC[node] = lines
			return false
		}
		return true
	})
	if lines != 0 {
		return lines
	}
	return 0
}

type ByName [][]string

func (b ByName) Len() int           { return len(b) }
func (b ByName) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
func (b ByName) Less(i, j int) bool { return b[i][0] < b[j][0] }

// pkgName returns qualified package name from SelectorExpr.
func pkgName(x *ast.SelectorExpr) string {
	n, ok := x.X.(*ast.Ident)
	if !ok {
		return ""
	}

	return n.Name
}

type Walker struct {
	P          *loader.Program
	Packages   map[string]Package
	CacheLOC   map[ast.Node]int
	CacheNodes map[string]ast.Node

	Stdlib bool
}

func NewWalker(p *loader.Program) *Walker {
	// work only with top package for now.
	// TODO: work with all recursive sub-packages (optionally?)
	top := p.InitialPackages()[0]

	// prepare map of resolved imports
	packages := make(map[string]Package)
	for _, pkg := range top.Pkg.Imports() {
		if IsStdlib(pkg.Path()) {
			continue
		}
		packages[pkg.Name()] = Package{
			Name: pkg.Name(),
			Path: pkg.Path(),
		}
	}

	return &Walker{
		P:          p,
		Packages:   packages,
		CacheLOC:   make(map[ast.Node]int),
		CacheNodes: make(map[string]ast.Node),

		Stdlib: false,
	}
}

// WalkExternal walks through function body block,
// looking for external dependencies expressions.
func (w *Walker) WalkExternal(topNode ast.Node, parent *loader.PackageInfo) (lines, depth, locCum, depthInt int) {
	ast.Inspect(topNode, func(n ast.Node) bool {
		switch x := n.(type) {
		case *ast.CallExpr:
			expr, ok := x.Fun.(*ast.Ident)
			if !ok {
				break
			}
			name := expr.Name

			// lookup this object in package
			obj := w.FindObject(parent, name)
			if obj == nil {
				return true
			}

			if _, ok := obj.Type().(*types.Signature); ok {
				node := w.FindFnNode(parent, name)
				// skip recursive calls
				if node == topNode {
					return false
				}

				if node != nil {
					depthInt++
					loc := w.Lines(node)
					locCum += loc

					lines1, depth1, lines2, depth2 := w.WalkExternal(node, parent)
					lines = lines1
					depth += depth1
					locCum += lines2
					depthInt += depth2
				}
			}
		case *ast.SelectorExpr:
			n := pkgName(x)
			pkg := w.FindImport(parent, n)
			if pkg == nil {
				return true
			}

			name := x.Sel.Name

			// lookup this object in package
			obj := w.FindObject(pkg, name)

			// skip recursive calls
			if obj == nil {
				return true
			}

			if _, ok := obj.Type().(*types.Signature); ok {

				node := w.FindFnNode(pkg, name)
				if node == topNode {
					return false
				}

				if node != nil {
					depth++
					lines1, depth1, lines2, depth2 := w.WalkExternal(node, pkg)
					lines = lines1
					depth += depth1
					locCum += lines2
					depthInt += depth2
				}
			}
		}
		return true
	})
	return
}

func (w *Walker) FindObject(pkg *loader.PackageInfo, name string) types.Object {
	scope := pkg.Pkg.Scope()
	return scope.Lookup(name)
}

func (w *Walker) FindFnNode(pkg *loader.PackageInfo, fnName string) ast.Node {
	var (
		node ast.Node
		ok   bool
	)
	qName := fmt.Sprintf("%s.%s", pkg.Pkg.Path(), fnName)
	node, ok = w.CacheNodes[qName]
	if ok {
		return node
	}
	for _, f := range pkg.Files {
		ast.Inspect(f, func(n ast.Node) bool {
			switch x := n.(type) {
			case *ast.FuncDecl:
				if x.Name.Name == fnName {
					if x.Body == nil {
						return false
						w.CacheNodes[qName] = nil
					}
					node = n
					w.CacheNodes[qName] = n
					return false
				}
			}
			return true
		})
		if node != nil {
			return node
		}
	}
	return nil
}

func (w *Walker) FindImport(parent *loader.PackageInfo, name string) *loader.PackageInfo {
	for _, p := range parent.Pkg.Imports() {
		if p.Name() == name {
			if !w.Stdlib {
				std := IsStdlib(p.Path())
				if std {
					return nil
				}
			}
			return w.P.Package(p.Path())
		}
	}
	return nil
}
<commit_msg>Fix some cases<commit_after>package main

import (
	"fmt"
	"github.com/olekukonko/tablewriter"
	"go/ast"
	"go/types"
	"os"
	"sort"

	"golang.org/x/tools/go/loader"
)

func main() {
	var conf loader.Config

	conf.CreateFromFilenames(".", os.Args[1:]...)
	p, err := conf.Load()
	if err != nil {
		fmt.Println(err)
		return
	}

	w := NewWalker(p)

	// work only with top package for now.
	// TODO: work with all recursive sub-packages (optionally?)
	top := p.InitialPackages()[0]

	// find all selectors ('f.x') for imported packages
	selectors := make(map[string]Selector)
	counter := make(map[string]int)
	for _, f := range top.Files {
		ast.Inspect(f, func(n ast.Node) bool {
			switch x := n.(type) {
			case *ast.SelectorExpr:
				var (
					n   string // package name
					obj types.Object
				)

				// handle methods
				s, ok := top.Selections[x]
				if ok {
					// (pkg).pkgvar.Method()
					obj = s.Obj()
					if obj.Pkg() == nil {
						return false
					}
					n = s.Obj().Pkg().Name()
				} else {
					// pkg.Func()
					n = pkgName(x)
				}

				// if it's not a selector for external package, skip it
				pkg, ok := w.Packages[n]
				if !ok {
					break
				}

				sel := Selector{
					Pkg:  pkg,
					Name: x.Sel.Name,
				}

				// lookup this object in package
				dp := p.Package(pkg.Path)
				if obj == nil {
					obj = w.FindObject(dp, sel.Name)
					if obj == nil {
						return true
					}
				}
				if _, ok := obj.Type().(*types.Signature); ok {
					sel.Type = "func"

					node := w.FindFnNode(dp, x.Sel.Name)
					if node != nil {
						lines := w.Lines(node)
						_, depth, linesCum, depthInt := w.WalkExternal(node, dp)
						sel.LOC, sel.Depth = lines, depth
						sel.LOCCum, sel.DepthInternal = linesCum, depthInt
					}
				}
				selectors[sel.String()] = sel
				counter[sel.String()]++
			}
			return true
		})
	}

	// Print stats
	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{"Name", "LOC", "Type", "Count", "Depth", "LOCCum", "DepthInt"})
	var results [][]string
	for name, count := range counter {
		sel := selectors[name]
		loc := fmt.Sprintf("%d", sel.LOC)
		depth := fmt.Sprintf("%d", sel.Depth)
		locCum := fmt.Sprintf("%d", sel.LOCCum)
		depthInt := fmt.Sprintf("%d", sel.DepthInternal)
		count := fmt.Sprintf("%d", count)
		results = append(results, []string{name, loc, sel.Type, count, depth, locCum, depthInt})
	}
	sort.Sort(ByName(results))
	for _, v := range results {
		table.Append(v)
	}
	table.Render() // Send output
}

func (w *Walker) Lines(node ast.Node) int {
	var (
		lines int
		ok    bool
	)
	if lines, ok = w.CacheLOC[node]; ok {
		return lines
	}
	ast.Inspect(node, func(n ast.Node) bool {
		switch x := n.(type) {
		case *ast.FuncDecl:
			if x.Body == nil {
				break
			}
			start := w.P.Fset.Position(x.Body.Lbrace)
			end := w.P.Fset.Position(x.Body.Rbrace)
			lines = end.Line - start.Line
			if lines == 0 {
				lines = 1
			}
			w.CacheLOC[node] = lines
			return false
		}
		return true
	})
	if lines != 0 {
		return lines
	}
	return 0
}

type ByName [][]string

func (b ByName) Len() int           { return len(b) }
func (b ByName) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
func (b ByName) Less(i, j int) bool { return b[i][0] < b[j][0] }

// pkgName returns qualified package name from SelectorExpr.
func pkgName(x *ast.SelectorExpr) string {
	n, ok := x.X.(*ast.Ident)
	if !ok {
		return ""
	}

	return n.Name
}

type Walker struct {
	P          *loader.Program
	Packages   map[string]Package
	CacheLOC   map[ast.Node]int
	CacheNodes map[string]ast.Node

	Stdlib bool
}

func NewWalker(p *loader.Program) *Walker {
	// work only with top package for now.
	// TODO: work with all recursive sub-packages (optionally?)
	top := p.InitialPackages()[0]

	// prepare map of resolved imports
	packages := make(map[string]Package)
	for _, pkg := range top.Pkg.Imports() {
		if IsStdlib(pkg.Path()) {
			continue
		}
		packages[pkg.Name()] = Package{
			Name: pkg.Name(),
			Path: pkg.Path(),
		}
	}

	return &Walker{
		P:          p,
		Packages:   packages,
		CacheLOC:   make(map[ast.Node]int),
		CacheNodes: make(map[string]ast.Node),

		Stdlib: false,
	}
}

// WalkExternal walks through function body block,
// looking for external dependencies expressions.
func (w *Walker) WalkExternal(topNode ast.Node, parent *loader.PackageInfo) (lines, depth, locCum, depthInt int) {
	ast.Inspect(topNode, func(n ast.Node) bool {
		switch x := n.(type) {
		case *ast.CallExpr:
			expr, ok := x.Fun.(*ast.Ident)
			if !ok {
				break
			}
			name := expr.Name

			// lookup this object in package
			obj := w.FindObject(parent, name)
			if obj == nil {
				return false
			}

			if _, ok := obj.Type().(*types.Signature); ok {
				node := w.FindFnNode(parent, name)
				// skip recursive calls
				if node == topNode {
					return false
				}

				if node != nil {
					depthInt++
					loc := w.Lines(node)
					locCum += loc

					lines1, depth1, lines2, depth2 := w.WalkExternal(node, parent)
					lines = lines1
					depth += depth1
					locCum += lines2
					depthInt += depth2
				}
			}
		case *ast.SelectorExpr:
			var (
				n   string // package name
				obj types.Object
			)
			s, ok := parent.Selections[x]
			if ok {
				// (pkg).pkgvar.Method()
				obj = s.Obj()
				if obj.Pkg() == nil {
					return false
				}
				n = obj.Pkg().Name()
			} else {
				// pkg.Func()
				n = pkgName(x)
			}

			var pkg *loader.PackageInfo
			if n == parent.Pkg.Name() {
				pkg = parent
			} else {
				pkg = w.FindImport(parent, n)
				if pkg == nil {
					return false
				}
			}

			name := x.Sel.Name

			// lookup this object in package
			if obj == nil {
				obj = w.FindObject(pkg, name)
				if obj == nil {
					return false
				}
			}

			if _, ok := obj.Type().(*types.Signature); ok {
				node := w.FindFnNode(pkg, name)

				// skip recursive calls
				if node == topNode {
					return false
				}

				if node != nil {
					depth++
					lines1, depth1, lines2, depth2 := w.WalkExternal(node, pkg)
					lines = lines1
					depth += depth1
					locCum += lines2
					depthInt += depth2
				}
			}
		}
		return true
	})
	return
}

func (w *Walker) FindObject(pkg *loader.PackageInfo, name string) types.Object {
	scope := pkg.Pkg.Scope()
	return scope.Lookup(name)
}

func (w *Walker) FindFnNode(pkg *loader.PackageInfo, fnName string) ast.Node {
	var (
		node ast.Node
		ok   bool
	)
	qName := fmt.Sprintf("%s.%s", pkg.Pkg.Path(), fnName)
	node, ok = w.CacheNodes[qName]
	if ok {
		return node
	}
	for _, f := range pkg.Files {
		ast.Inspect(f, func(n ast.Node) bool {
			switch x := n.(type) {
			case *ast.FuncDecl:
				if x.Name.Name == fnName {
					if x.Body == nil {
						return false
						w.CacheNodes[qName] = nil
					}
					node = n
					w.CacheNodes[qName] = n
					return false
				}
			}
			return true
		})
		if node != nil {
			return node
		}
	}
	return nil
}

func (w *Walker) FindImport(parent *loader.PackageInfo, name string) *loader.PackageInfo {
	for _, p := range parent.Pkg.Imports() {
		if p.Name() == name {
			if !w.Stdlib {
				std := IsStdlib(p.Path())
				if std {
					return nil
				}
			}
			return w.P.Package(p.Path())
		}
	}
	return nil
}
<|endoftext|>


<commit_before><commit_msg>Seventh step to POC<commit_after><|endoftext|>


<commit_before>package main

import (
	"time"

	"github.com/jcelliott/lumber"
	"github.com/nanopack/mist/core"
	"github.com/nanopack/pulse/api"
	"github.com/nanopack/pulse/plexer"
	"github.com/nanopack/pulse/server"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	"github.com/nanopack/pulse/influx"
)

var configFile string

func main() {
	viper.SetDefault("server_listen_address", "127.0.0.1:3000")
	viper.SetDefault("token", "secret")
	viper.SetDefault("http_listen_address", "127.0.0.1:8080")
	viper.SetDefault("mist_address", "")
	viper.SetDefault("influx_address", "http://127.0.0.1:8086")
	viper.SetDefault("log_level", "INFO")
	viper.SetDefault("poll_interval", 60)
	//

	server := true
	configFile := ""
	command := cobra.Command{
		Use:   "lojack",
		Short: "lojack is a server controller",
		Long:  ``,
		Run: func(ccmd *cobra.Command, args []string) {
			if !server {
				ccmd.HelpFunc()(ccmd, args)
				return
			}
			viper.SetConfigFile(configFile)
			viper.ReadInConfig()
			viper.Set("log", lumber.NewConsoleLogger(lumber.LvlInt(viper.GetString("log_level"))))
			serverStart()
		},
	}
	command.Flags().String("http_listen_address", ":8080", "Http Listen address")
	viper.BindPFlag("http_listen_address", command.Flags().Lookup("http_listen_address"))
	command.Flags().BoolVarP(&server, "server", "s", false, "Run as server")
	command.Flags().StringVarP(&configFile, "configFile", "", "","config file location for server")

	command.Execute()
}

func serverStart() {

	plex := plexer.NewPlexer()

	if viper.GetString("mist_address") != "" {
		mist, err := mist.NewRemoteClient(viper.GetString("mist_address"))
		if err != nil {
			panic(err)
		}
		plex.AddObserver("mist", mist.Publish)
		defer mist.Close()
	}

	plex.AddBatcher("influx", influx.Insert)

	err := server.Listen(viper.GetString("server_listen_address"), plex.Publish)
	if err != nil {
		panic(err)
	}
	// begin polling the connected servers
	pi := viper.GetInt("poll_interval")
	if pi == 0 {
		pi = 60
	}
	go server.StartPolling(nil, nil, time.Duration(pi) * time.Second, nil)

	queries := []string{
		"CREATE DATABASE statistics",
		`CREATE RETENTION POLICY "2.days" ON statistics DURATION 2d REPLICATION 1 DEFAULT`,
		`CREATE RETENTION POLICY "1.week" ON statistics DURATION 1w REPLICATION 1`,
	}

	for _, query := range queries {
		_, err := influx.Query(query)
		if err != nil {
			panic(err)
		}
	}

	err = api.Start()
	if err != nil {
		panic(err)
	}

	go influx.KeepContinuousQueriesUpToDate()
}
<commit_msg>Set default interval to update CQ<commit_after>package main

import (
	"time"

	"github.com/jcelliott/lumber"
	"github.com/nanopack/mist/core"
	"github.com/nanopack/pulse/api"
	"github.com/nanopack/pulse/plexer"
	"github.com/nanopack/pulse/server"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	"github.com/nanopack/pulse/influx"
)

var configFile string

func main() {
	viper.SetDefault("server_listen_address", "127.0.0.1:3000")
	viper.SetDefault("token", "secret")
	viper.SetDefault("http_listen_address", "127.0.0.1:8080")
	viper.SetDefault("mist_address", "")
	viper.SetDefault("influx_address", "http://127.0.0.1:8086")
	viper.SetDefault("log_level", "INFO")
	viper.SetDefault("poll_interval", 60)
	viper.SetDefault("aggregate_interval", 15)

	server := true
	configFile := ""
	command := cobra.Command{
		Use:   "lojack",
		Short: "lojack is a server controller",
		Long:  ``,
		Run: func(ccmd *cobra.Command, args []string) {
			if !server {
				ccmd.HelpFunc()(ccmd, args)
				return
			}
			viper.SetConfigFile(configFile)
			viper.ReadInConfig()
			viper.Set("log", lumber.NewConsoleLogger(lumber.LvlInt(viper.GetString("log_level"))))
			serverStart()
		},
	}
	command.Flags().String("http_listen_address", ":8080", "Http Listen address")
	viper.BindPFlag("http_listen_address", command.Flags().Lookup("http_listen_address"))
	command.Flags().BoolVarP(&server, "server", "s", false, "Run as server")
	command.Flags().StringVarP(&configFile, "configFile", "", "","config file location for server")

	command.Execute()
}

func serverStart() {

	plex := plexer.NewPlexer()

	if viper.GetString("mist_address") != "" {
		mist, err := mist.NewRemoteClient(viper.GetString("mist_address"))
		if err != nil {
			panic(err)
		}
		plex.AddObserver("mist", mist.Publish)
		defer mist.Close()
	}

	plex.AddBatcher("influx", influx.Insert)

	err := server.Listen(viper.GetString("server_listen_address"), plex.Publish)
	if err != nil {
		panic(err)
	}
	// begin polling the connected servers
	pi := viper.GetInt("poll_interval")
	if pi == 0 {
		pi = 60
	}
	go server.StartPolling(nil, nil, time.Duration(pi) * time.Second, nil)

	queries := []string{
		"CREATE DATABASE statistics",
		`CREATE RETENTION POLICY "2.days" ON statistics DURATION 2d REPLICATION 1 DEFAULT`,
		`CREATE RETENTION POLICY "1.week" ON statistics DURATION 1w REPLICATION 1`,
	}

	for _, query := range queries {
		_, err := influx.Query(query)
		if err != nil {
			panic(err)
		}
	}

	err = api.Start()
	if err != nil {
		panic(err)
	}

	go influx.KeepContinuousQueriesUpToDate()
}
<|endoftext|>


<commit_before><commit_msg>test jenkins job type from xml<commit_after><|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"

	"github.com/codegangsta/cli"
	"github.com/gummiboll/forgetful/commands"
	"github.com/gummiboll/forgetful/storage"
)

const version string = "0.9"

func main() {
	// Init
	i := storage.Impl{}
	if err := i.InitDB(); err != nil {
		panic(err)
	}

	i.InitSchema()
	if err := i.RemoveExpiredNotes(); err != nil {
		panic(err)
	}

	app := cli.NewApp()
	app.Name = "forgetful"
	app.Usage = "For your notes/cheat sheets"
	app.Version = version
	app.Commands = []cli.Command{
		{
			Name:    "add",
			Aliases: []string{"a"},
			Usage:   "Add a note",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "t",
					Usage: "Mark as temporary (expires after 24 hours)",
				},
				cli.BoolFlag{
					Name:  "p",
					Usage: "Create note with contents from clipboard",
				},
			},
			Action: func(c *cli.Context) {
				n, err := commands.AddCommand(c, i)
				if err != nil {
					fmt.Println(err)
					return
				}

				fmt.Println(fmt.Sprintf("Added note: %s", n.Name))
			},
		},
		{
			Name:    "delete",
			Aliases: []string{"d"},
			Usage:   "Delete a note",
			Action: func(c *cli.Context) {
				n, err := commands.DeleteCommand(c, i)
				if err != nil {
					fmt.Println(err)
					return
				}

				fmt.Println(fmt.Sprintf("Deleted note: %s", n.Name))
			},
		},
		{
			Name:    "edit",
			Aliases: []string{"e"},
			Usage:   "Edit/read a note",
			Action: func(c *cli.Context) {
				n, err := commands.EditCommand(c, i)
				if err != nil {
					fmt.Println(err)
					return
				}

				fmt.Println(fmt.Sprintf("Updated note: %s", n.Name))
			},
		},
		{
			Name:    "read",
			Aliases: []string{"r"},
			Usage:   "Read a note",
			Action: func(c *cli.Context) {
				if err := commands.ReadCommand(c, i); err != nil {
					fmt.Println(err)
				}
			},
		},
		{
			Name:    "list",
			Aliases: []string{"l"},
			Usage:   "List all notes, filter result if argument i present",
			Action: func(c *cli.Context) {
				notes := commands.ListCommand(c, i)

				if len(notes) > 0 {
					fmt.Println("Matching notes:")
				} else {
					fmt.Println("No matches")
				}

				for _, n := range notes {
					fmt.Println(n)
				}
			},
		},
		{
			Name:    "search",
			Aliases: []string{"s"},
			Usage:   "Search notes for argument",
			Action: func(c *cli.Context) {
				notes, err := commands.SearchCommand(c, i)
				if err != nil {
					fmt.Println(err)
					return
				}

				if len(notes) > 0 {
					fmt.Println("Matching notes:")
				} else {
					fmt.Println("No matches")
				}

				for _, n := range notes {
					fmt.Println(n)
				}
			},
		},
		{
			Name:  "share",
			Usage: "Share a note (publicly) on hastebin.com",
			Action: func(c *cli.Context) {
				n, url, err := commands.ShareCommand(c, i)
				if err != nil {
					fmt.Println(err)
					return
				}

				fmt.Println(fmt.Sprintf("Shared note '%s': %s", n.Name, url))
			},
		},
		{
			Name:    "keep",
			Aliases: []string{"k"},
			Usage:   "Sets a temporary note as permanent",
			Action: func(c *cli.Context) {
				n, err := commands.KeepCommand(c, i)
				if err != nil {
					fmt.Println(err)
				}

				fmt.Println(fmt.Sprintf("Keeping note: %s", n.Name))
			},
		},
	}

	app.Run(os.Args)

}
<commit_msg>Updated ActionFunc signature for codegangsta/cli<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/codegangsta/cli"
	"github.com/gummiboll/forgetful/commands"
	"github.com/gummiboll/forgetful/storage"
)

const version string = "0.9"

func main() {
	// Init
	i := storage.Impl{}
	if err := i.InitDB(); err != nil {
		panic(err)
	}

	i.InitSchema()
	if err := i.RemoveExpiredNotes(); err != nil {
		panic(err)
	}

	app := cli.NewApp()
	app.Name = "forgetful"
	app.Usage = "For your notes/cheat sheets"
	app.Version = version
	app.Commands = []cli.Command{
		{
			Name:    "add",
			Aliases: []string{"a"},
			Usage:   "Add a note",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "t",
					Usage: "Mark as temporary (expires after 24 hours)",
				},
				cli.BoolFlag{
					Name:  "p",
					Usage: "Create note with contents from clipboard",
				},
			},
			Action: func(c *cli.Context) error {
				n, err := commands.AddCommand(c, i)
				if err != nil {
					return err
				}

				fmt.Println(fmt.Sprintf("Added note: %s", n.Name))
				return nil
			},
		},
		{
			Name:    "delete",
			Aliases: []string{"d"},
			Usage:   "Delete a note",
			Action: func(c *cli.Context) error {
				n, err := commands.DeleteCommand(c, i)
				if err != nil {
					return err
				}

				fmt.Println(fmt.Sprintf("Deleted note: %s", n.Name))
				return nil
			},
		},
		{
			Name:    "edit",
			Aliases: []string{"e"},
			Usage:   "Edit/read a note",
			Action: func(c *cli.Context) error {
				n, err := commands.EditCommand(c, i)
				if err != nil {
					return err
				}

				fmt.Println(fmt.Sprintf("Updated note: %s", n.Name))
				return nil
			},
		},
		{
			Name:    "read",
			Aliases: []string{"r"},
			Usage:   "Read a note",
			Action: func(c *cli.Context) error {
				if err := commands.ReadCommand(c, i); err != nil {
					return err
				}
				return nil
			},
		},
		{
			Name:    "list",
			Aliases: []string{"l"},
			Usage:   "List all notes, filter result if argument i present",
			Action: func(c *cli.Context) error {
				notes := commands.ListCommand(c, i)

				if len(notes) > 0 {
					fmt.Println("Matching notes:")
				} else {
					fmt.Println("No matches")
				}

				for _, n := range notes {
					fmt.Println(n)
				}
				return nil
			},
		},
		{
			Name:    "search",
			Aliases: []string{"s"},
			Usage:   "Search notes for argument",
			Action: func(c *cli.Context) error {
				notes, err := commands.SearchCommand(c, i)
				if err != nil {
					return err
				}

				if len(notes) > 0 {
					fmt.Println("Matching notes:")
				} else {
					fmt.Println("No matches")
				}

				for _, n := range notes {
					fmt.Println(n)
				}
				return nil
			},
		},
		{
			Name:  "share",
			Usage: "Share a note (publicly) on hastebin.com",
			Action: func(c *cli.Context) error {
				n, url, err := commands.ShareCommand(c, i)
				if err != nil {
					return err
				}

				fmt.Println(fmt.Sprintf("Shared note '%s': %s", n.Name, url))
				return nil
			},
		},
		{
			Name:    "keep",
			Aliases: []string{"k"},
			Usage:   "Sets a temporary note as permanent",
			Action: func(c *cli.Context) error {
				n, err := commands.KeepCommand(c, i)
				if err != nil {
					return err
				}

				fmt.Println(fmt.Sprintf("Keeping note: %s", n.Name))
				return nil
			},
		},
	}

	app.Run(os.Args)

}
<|endoftext|>


<commit_before>// HTTP/2 web server with built-in support for Lua, Markdown, GCSS, Amber and JSX.
package main

import (
	"errors"
	"fmt"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
	internallog "log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"runtime/pprof"
	"strings"
	"syscall"
	"time"

	"github.com/xyproto/pinterface"
	"github.com/xyproto/unzip"
	"github.com/yuin/gopher-lua"
)

const (
	versionString = "Algernon 0.9"
	description   = "HTTP/2 Web Server"
)

var (
	// For convenience. Set in the main function.
	serverHost      string
	dbName          string
	refreshDuration time.Duration
	fs              *FileStat
)

func main() {
	var err error

	// Will be default in Go 1.5
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Temporary directory that might be used for logging, databases or file extraction
	serverTempDir, err := ioutil.TempDir("", "algernon")
	if err != nil {
		log.Fatalln(err)
	}
	defer os.RemoveAll(serverTempDir)

	// Set several configuration variables, based on the given flags and arguments
	serverHost = handleFlags(serverTempDir)

	// Version
	if showVersion {
		if !quietMode {
			fmt.Println(versionString)
		}
		os.Exit(0)
	}

	// CPU profiling
	if profileCPU != "" {
		f, err := os.Create(profileCPU)
		if err != nil {
			log.Fatal(err)
		}
		go func() {
			log.Info("Profiling CPU usage")
			pprof.StartCPUProfile(f)
		}()
		atShutdown(func() {
			pprof.StopCPUProfile()
			log.Info("Done profiling CPU usage")
		})
	}

	// Memory profiling at server shutdown
	if profileMem != "" {
		atShutdown(func() {
			f, err := os.Create(profileMem)
			defer f.Close()
			if err != nil {
				log.Fatal(err)
			}
			log.Info("Saving heap profile to ", profileMem)
			pprof.WriteHeapProfile(f)
		})
	}

	// Request handlers
	mux := http.NewServeMux()

	// Read mime data from the system, if available
	initializeMime()

	// Create a new FileStat struct, with optional caching (for speed).
	// Clear the cache every 10 minutes.
	fs = NewFileStat(cacheFileStat, time.Minute*10)

	// Check if the given directory really is a directory
	if !fs.isDir(serverDir) {
		// Possibly a file
		filename := serverDir
		// Check if the file exists
		if fs.exists(filename) {
			// Switch based on the lowercase filename extension
			switch strings.ToLower(filepath.Ext(filename)) {
			case ".md", ".markdown":
				// Serve the given Markdown file as a static HTTP server
				serveStaticFile(filename, defaultWebColonPort)
				return
			case ".zip", ".alg":
				// Assume this to be a compressed Algernon application
				err := unzip.Extract(filename, serverTempDir)
				if err != nil {
					log.Fatalln(err)
				}
				// Use the directory where the file was extracted as the server directory
				serverDir = serverTempDir
				// If there is only one directory there, assume it's the
				// directory of the newly extracted ZIP file.
				if filenames := getFilenames(serverDir); len(filenames) == 1 {
					fullPath := filepath.Join(serverDir, filenames[0])
					if fs.isDir(fullPath) {
						// Use this as the server directory instead
						serverDir = fullPath
					}
				}
				// If there are server configuration files in the extracted
				// directory, register them.
				for _, filename := range serverConfigurationFilenames {
					configFilename := filepath.Join(serverDir, filename)
					if fs.exists(configFilename) {
						serverConfigurationFilenames = append(serverConfigurationFilenames, configFilename)
					}
				}
				// Disregard all configuration files from the current directory
				// (filenames without a path separator), since we are serving a
				// ZIP file.
				for i, filename := range serverConfigurationFilenames {
					if strings.Count(filepath.ToSlash(filename), "/") == 0 {
						// Remove the filename from the slice
						serverConfigurationFilenames = append(serverConfigurationFilenames[:i], serverConfigurationFilenames[i+1:]...)
					}
				}
			default:
				singleFileMode = true
			}
		} else {
			fatalExit(errors.New("File does not exist: " + filename))
		}
	}

	// Make a few changes to the defaults if we are serving a single file
	if singleFileMode {
		debugMode = true
		serveJustHTTP = true
	}

	// Console output
	if !quietMode && !singleFileMode {
		fmt.Println(banner())
	}

	// Dividing line between the banner and output from any of the configuration scripts
	if len(serverConfigurationFilenames) > 0 && !quietMode {
		fmt.Println("--------------------------------------- - - · ·")
	}

	// Disable the database backend if the BoltDB filename is /dev/null
	if boltFilename == "/dev/null" {
		useNoDatabase = true
	}

	var perm pinterface.IPermissions // nil by default
	if !useNoDatabase {
		// Connect to a database and retrieve a Permissions struct
		perm, err = aquirePermissions()
		if err != nil {
			log.Fatalln("Could not find a usable database backend.")
		}
	}

	// Lua LState pool
	luapool := &lStatePool{saved: make([]*lua.LState, 0, 4)}
	atShutdown(func() {
		luapool.Shutdown()
	})

	// TODO: save repl history + close luapool + close logs ++ at shutdown

	// Create a cache struct for reading files (contains functions that can
	// be used for reading files, also when caching is disabled).
	// The final argument is for compressing with "fast" instead of "best".
	cache := newFileCache(cacheSize, cacheCompression, cacheMaxEntitySize)

	if singleFileMode && filepath.Ext(serverDir) == ".lua" {
		luaServerFilename = serverDir
		if luaServerFilename == "index.lua" || luaServerFilename == "data.lua" {
			log.Warn("Using " + luaServerFilename + " as a standalone server!\nYou might wish to serve a directory instead.")
		}
		serverDir = filepath.Dir(serverDir)
		singleFileMode = false
	}

	// Log to a file as JSON, if a log file has been specified
	if serverLogFile != "" {
		f, err := os.OpenFile(serverLogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, defaultPermissions)
		if err != nil {
			log.Error("Could not log to", serverLogFile)
			fatalExit(err)
		}
		log.SetFormatter(&log.JSONFormatter{})
		log.SetOutput(f)
	} else if quietMode {
		// If quiet mode is enabled and no log file has been specified, disable logging
		log.SetOutput(ioutil.Discard)
	}

	if quietMode {
		os.Stdout.Close()
		os.Stderr.Close()
	}

	// Read server configuration script, if present.
	// The scripts may change global variables.
	var ranConfigurationFilenames []string
	for _, filename := range serverConfigurationFilenames {
		if fs.exists(filename) {
			if verboseMode {
				fmt.Println("Running configuration file: " + filename)
			}
			if err := runConfiguration(filename, perm, luapool, cache, mux, false); err != nil {
				log.Error("Could not use configuration script: " + filename)
				if perm != nil {
					fatalExit(err)
				} else {
					log.Warn("Ignoring script error since database backend is disabled.")
				}
			}
			ranConfigurationFilenames = append(ranConfigurationFilenames, filename)
		}
	}
	// Only keep the active ones. Used when outputting server information.
	serverConfigurationFilenames = ranConfigurationFilenames

	// Run the standalone Lua server, if specified
	if luaServerFilename != "" {
		// Run the Lua server file and set up handlers
		if verboseMode {
			fmt.Println("Running Lua Server File")
		}
		if err := runConfiguration(luaServerFilename, perm, luapool, cache, mux, true); err != nil {
			log.Error("Error in Lua server script: " + luaServerFilename)
			fatalExit(err)
		}
	} else {
		// Register HTTP handler functions
		registerHandlers(mux, "/", serverDir, perm, luapool, cache, serverAddDomain)
	}

	// Set the values that has not been set by flags nor scripts
	// (and can be set by both)
	ranServerReadyFunction := finalConfiguration(serverHost)

	// If no configuration files were being ran successfully,
	// output basic server information.
	if len(serverConfigurationFilenames) == 0 {
		if !quietMode {
			fmt.Println(serverInfo())
		}
		ranServerReadyFunction = true
	}

	// Dividing line between the banner and output from any of the
	// configuration scripts. Marks the end of the configuration output.
	if ranServerReadyFunction && !quietMode {
		fmt.Println("--------------------------------------- - - · ·")
	}

	// Direct internal logging elsewhere
	internalLogFile, err := os.Open(internalLogFilename)
	defer internalLogFile.Close()

	if err != nil {
		// Could not open the internalLogFilename filename, try using another filename
		internalLogFile, err = os.OpenFile("internal.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, defaultPermissions)
		atShutdown(func() {
			// TODO This one is is special and should be closed after the other shutdown functions.
			//      Set up a "done" channel instead of sleeping.
			time.Sleep(100 * time.Millisecond)
			internalLogFile.Close()
		})
		if err != nil {
			fatalExit(fmt.Errorf("Could not write to %s nor %s.", internalLogFilename, "internal.log"))
		}
	}
	internallog.SetOutput(internalLogFile)

	// Serve filesystem events in the background.
	// Used for reloading pages when the sources change.
	// Can also be used when serving a single file.
	if autoRefreshMode {
		refreshDuration, err = time.ParseDuration(eventRefresh)
		if err != nil {
			log.Warn(fmt.Sprintf("%s is an invalid duration. Using %s instead.", eventRefresh, defaultEventRefresh))
			// Ignore the error, since defaultEventRefresh is a constant and must be parseable
			refreshDuration, _ = time.ParseDuration(defaultEventRefresh)
		}
		if autoRefreshDir != "" {
			// Only watch the autoRefreshDir, recursively
			EventServer(eventAddr, defaultEventPath, autoRefreshDir, refreshDuration, "*")
		} else {
			// Watch everything in the server directory, recursively
			EventServer(eventAddr, defaultEventPath, serverDir, refreshDuration, "*")
		}
	}

	// For communicating to and from the REPL
	ready := make(chan bool) // for when the server is up and running
	done := make(chan bool)  // for when the user wish to quit the server

	// The Lua REPL
	if !serverMode {
		// If the REPL uses readline, the SIGWINCH signal is handled
		go REPL(perm, luapool, cache, ready, done)
	} else {
		// Ignore SIGWINCH if we are not going to use a REPL
		signal.Ignore(syscall.SIGWINCH)
	}

	shutdownTimeout := 10 * time.Second

	conf := &algernonServerConfig{
		productionMode:      productionMode,
		serverHost:          serverHost,
		serverAddr:          serverAddr,
		serverCert:          serverCert,
		serverKey:           serverKey,
		serveJustHTTP:       serveJustHTTP,
		serveJustHTTP2:      serveJustHTTP2,
		shutdownTimeout:     shutdownTimeout,
		internalLogFilename: internalLogFilename,
	}

	// Run the shutdown functions if graceful does not
	defer generateShutdownFunction(nil)()

	// Serve HTTP, HTTP/2 and/or HTTPS
	if err := serve(conf, mux, done, ready); err != nil {
		fatalExit(err)
	}
}
<commit_msg>Don't react to terminal resize signals on Windows<commit_after>// HTTP/2 web server with built-in support for Lua, Markdown, GCSS, Amber and JSX.
package main

import (
	"errors"
	"fmt"
	log "github.com/sirupsen/logrus"
	"io/ioutil"
	internallog "log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"runtime/pprof"
	"strings"
	"syscall"
	"time"

	"github.com/xyproto/pinterface"
	"github.com/xyproto/unzip"
	"github.com/yuin/gopher-lua"
)

const (
	versionString = "Algernon 0.9"
	description   = "HTTP/2 Web Server"
)

var (
	// For convenience. Set in the main function.
	serverHost      string
	dbName          string
	refreshDuration time.Duration
	fs              *FileStat
)

func main() {
	var err error

	// Will be default in Go 1.5
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Temporary directory that might be used for logging, databases or file extraction
	serverTempDir, err := ioutil.TempDir("", "algernon")
	if err != nil {
		log.Fatalln(err)
	}
	defer os.RemoveAll(serverTempDir)

	// Set several configuration variables, based on the given flags and arguments
	serverHost = handleFlags(serverTempDir)

	// Version
	if showVersion {
		if !quietMode {
			fmt.Println(versionString)
		}
		os.Exit(0)
	}

	// CPU profiling
	if profileCPU != "" {
		f, err := os.Create(profileCPU)
		if err != nil {
			log.Fatal(err)
		}
		go func() {
			log.Info("Profiling CPU usage")
			pprof.StartCPUProfile(f)
		}()
		atShutdown(func() {
			pprof.StopCPUProfile()
			log.Info("Done profiling CPU usage")
		})
	}

	// Memory profiling at server shutdown
	if profileMem != "" {
		atShutdown(func() {
			f, err := os.Create(profileMem)
			defer f.Close()
			if err != nil {
				log.Fatal(err)
			}
			log.Info("Saving heap profile to ", profileMem)
			pprof.WriteHeapProfile(f)
		})
	}

	// Request handlers
	mux := http.NewServeMux()

	// Read mime data from the system, if available
	initializeMime()

	// Create a new FileStat struct, with optional caching (for speed).
	// Clear the cache every 10 minutes.
	fs = NewFileStat(cacheFileStat, time.Minute*10)

	// Check if the given directory really is a directory
	if !fs.isDir(serverDir) {
		// Possibly a file
		filename := serverDir
		// Check if the file exists
		if fs.exists(filename) {
			// Switch based on the lowercase filename extension
			switch strings.ToLower(filepath.Ext(filename)) {
			case ".md", ".markdown":
				// Serve the given Markdown file as a static HTTP server
				serveStaticFile(filename, defaultWebColonPort)
				return
			case ".zip", ".alg":
				// Assume this to be a compressed Algernon application
				err := unzip.Extract(filename, serverTempDir)
				if err != nil {
					log.Fatalln(err)
				}
				// Use the directory where the file was extracted as the server directory
				serverDir = serverTempDir
				// If there is only one directory there, assume it's the
				// directory of the newly extracted ZIP file.
				if filenames := getFilenames(serverDir); len(filenames) == 1 {
					fullPath := filepath.Join(serverDir, filenames[0])
					if fs.isDir(fullPath) {
						// Use this as the server directory instead
						serverDir = fullPath
					}
				}
				// If there are server configuration files in the extracted
				// directory, register them.
				for _, filename := range serverConfigurationFilenames {
					configFilename := filepath.Join(serverDir, filename)
					if fs.exists(configFilename) {
						serverConfigurationFilenames = append(serverConfigurationFilenames, configFilename)
					}
				}
				// Disregard all configuration files from the current directory
				// (filenames without a path separator), since we are serving a
				// ZIP file.
				for i, filename := range serverConfigurationFilenames {
					if strings.Count(filepath.ToSlash(filename), "/") == 0 {
						// Remove the filename from the slice
						serverConfigurationFilenames = append(serverConfigurationFilenames[:i], serverConfigurationFilenames[i+1:]...)
					}
				}
			default:
				singleFileMode = true
			}
		} else {
			fatalExit(errors.New("File does not exist: " + filename))
		}
	}

	// Make a few changes to the defaults if we are serving a single file
	if singleFileMode {
		debugMode = true
		serveJustHTTP = true
	}

	// Console output
	if !quietMode && !singleFileMode && !simpleMode {
		fmt.Println(banner())
	}

	// Dividing line between the banner and output from any of the configuration scripts
	if len(serverConfigurationFilenames) > 0 && !quietMode {
		fmt.Println("--------------------------------------- - - · ·")
	}

	// Disable the database backend if the BoltDB filename is /dev/null
	if boltFilename == "/dev/null" {
		useNoDatabase = true
	}

	var perm pinterface.IPermissions // nil by default
	if !useNoDatabase {
		// Connect to a database and retrieve a Permissions struct
		perm, err = aquirePermissions()
		if err != nil {
			log.Fatalln("Could not find a usable database backend.")
		}
	}

	// Lua LState pool
	luapool := &lStatePool{saved: make([]*lua.LState, 0, 4)}
	atShutdown(func() {
		luapool.Shutdown()
	})

	// TODO: save repl history + close luapool + close logs ++ at shutdown

	// Create a cache struct for reading files (contains functions that can
	// be used for reading files, also when caching is disabled).
	// The final argument is for compressing with "fast" instead of "best".
	cache := newFileCache(cacheSize, cacheCompression, cacheMaxEntitySize)

	if singleFileMode && filepath.Ext(serverDir) == ".lua" {
		luaServerFilename = serverDir
		if luaServerFilename == "index.lua" || luaServerFilename == "data.lua" {
			log.Warn("Using " + luaServerFilename + " as a standalone server!\nYou might wish to serve a directory instead.")
		}
		serverDir = filepath.Dir(serverDir)
		singleFileMode = false
	}

	// Log to a file as JSON, if a log file has been specified
	if serverLogFile != "" {
		f, err := os.OpenFile(serverLogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, defaultPermissions)
		if err != nil {
			log.Error("Could not log to", serverLogFile)
			fatalExit(err)
		}
		log.SetFormatter(&log.JSONFormatter{})
		log.SetOutput(f)
	} else if quietMode {
		// If quiet mode is enabled and no log file has been specified, disable logging
		log.SetOutput(ioutil.Discard)
	}

	if quietMode {
		os.Stdout.Close()
		os.Stderr.Close()
	}

	// Read server configuration script, if present.
	// The scripts may change global variables.
	var ranConfigurationFilenames []string
	for _, filename := range serverConfigurationFilenames {
		if fs.exists(filename) {
			if verboseMode {
				fmt.Println("Running configuration file: " + filename)
			}
			if err := runConfiguration(filename, perm, luapool, cache, mux, false); err != nil {
				log.Error("Could not use configuration script: " + filename)
				if perm != nil {
					fatalExit(err)
				} else {
					log.Warn("Ignoring script error since database backend is disabled.")
				}
			}
			ranConfigurationFilenames = append(ranConfigurationFilenames, filename)
		}
	}
	// Only keep the active ones. Used when outputting server information.
	serverConfigurationFilenames = ranConfigurationFilenames

	// Run the standalone Lua server, if specified
	if luaServerFilename != "" {
		// Run the Lua server file and set up handlers
		if verboseMode {
			fmt.Println("Running Lua Server File")
		}
		if err := runConfiguration(luaServerFilename, perm, luapool, cache, mux, true); err != nil {
			log.Error("Error in Lua server script: " + luaServerFilename)
			fatalExit(err)
		}
	} else {
		// Register HTTP handler functions
		registerHandlers(mux, "/", serverDir, perm, luapool, cache, serverAddDomain)
	}

	// Set the values that has not been set by flags nor scripts
	// (and can be set by both)
	ranServerReadyFunction := finalConfiguration(serverHost)

	// If no configuration files were being ran successfully,
	// output basic server information.
	if len(serverConfigurationFilenames) == 0 {
		if !quietMode {
			fmt.Println(serverInfo())
		}
		ranServerReadyFunction = true
	}

	// Dividing line between the banner and output from any of the
	// configuration scripts. Marks the end of the configuration output.
	if ranServerReadyFunction && !quietMode {
		fmt.Println("--------------------------------------- - - · ·")
	}

	// Direct internal logging elsewhere
	internalLogFile, err := os.Open(internalLogFilename)
	defer internalLogFile.Close()

	if err != nil {
		// Could not open the internalLogFilename filename, try using another filename
		internalLogFile, err = os.OpenFile("internal.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, defaultPermissions)
		atShutdown(func() {
			// TODO This one is is special and should be closed after the other shutdown functions.
			//      Set up a "done" channel instead of sleeping.
			time.Sleep(100 * time.Millisecond)
			internalLogFile.Close()
		})
		if err != nil {
			fatalExit(fmt.Errorf("Could not write to %s nor %s.", internalLogFilename, "internal.log"))
		}
	}
	internallog.SetOutput(internalLogFile)

	// Serve filesystem events in the background.
	// Used for reloading pages when the sources change.
	// Can also be used when serving a single file.
	if autoRefreshMode {
		refreshDuration, err = time.ParseDuration(eventRefresh)
		if err != nil {
			log.Warn(fmt.Sprintf("%s is an invalid duration. Using %s instead.", eventRefresh, defaultEventRefresh))
			// Ignore the error, since defaultEventRefresh is a constant and must be parseable
			refreshDuration, _ = time.ParseDuration(defaultEventRefresh)
		}
		if autoRefreshDir != "" {
			// Only watch the autoRefreshDir, recursively
			EventServer(eventAddr, defaultEventPath, autoRefreshDir, refreshDuration, "*")
		} else {
			// Watch everything in the server directory, recursively
			EventServer(eventAddr, defaultEventPath, serverDir, refreshDuration, "*")
		}
	}

	// For communicating to and from the REPL
	ready := make(chan bool) // for when the server is up and running
	done := make(chan bool)  // for when the user wish to quit the server

	// The Lua REPL
	if !serverMode {
		// If the REPL uses readline, the SIGWINCH signal is handled
		go REPL(perm, luapool, cache, ready, done)
	} else {
		// Ignore SIGWINCH if we are not going to use a REPL
		if runtime.GOOS != "windows" {
			signal.Ignore(syscall.SIGWINCH)
		}
	}

	shutdownTimeout := 10 * time.Second

	conf := &algernonServerConfig{
		productionMode:      productionMode,
		serverHost:          serverHost,
		serverAddr:          serverAddr,
		serverCert:          serverCert,
		serverKey:           serverKey,
		serveJustHTTP:       serveJustHTTP,
		serveJustHTTP2:      serveJustHTTP2,
		shutdownTimeout:     shutdownTimeout,
		internalLogFilename: internalLogFilename,
	}

	// Run the shutdown functions if graceful does not
	defer generateShutdownFunction(nil)()

	// Serve HTTP, HTTP/2 and/or HTTPS
	if err := serve(conf, mux, done, ready); err != nil {
		fatalExit(err)
	}
}
<|endoftext|>