<commit_before>package main

import (
	"os"
	"log"
	"net/http"
	"io"
	"sync"
	"strings"
	"os/exec"
	"fmt"
	"encoding/xml"
	"io/ioutil"
	"encoding/json"
	"bytes"
	"flag"
	"path/filepath"
	"mime/multipart"
	"net/url"

	"github.com/gorilla/mux"
)

type Config struct {
	Port       int `xml:"port"`
	Slic3rPath string `xml:"slic3rPath"`
}

var config = Config{}
//Declare flags
var debugFlag = flag.Bool("debug", false, "If set debug output will print")
var portFlag = flag.Int("port", 0, "If set slic3r server will bind to given port and will override config file")
var slic3rPathFlag = flag.String("", "slic3r", "If set slic3r server will use given sli3r path and will override config file")

func main() {
	log.Println("Starting Slic3r Server")
	//Parse flags
	flag.Parse()
	err := SetUp()
	if err != nil {
		panic(err)
	}
	server := NewServer()
	http.Handle("/", server)
	log.Printf("Slic3r Server binding to port: %d\n", config.Port)
	http.ListenAndServe(fmt.Sprintf(":%d", config.Port), nil)
}

func SetUp() error{
	//Generate Directories
	if _, err := os.Stat("stl"); os.IsNotExist(err) {
		if (*debugFlag) {
			log.Println("Making STL Directory")
		}
		os.Mkdir("stl", 0777)
	}
	if _, err := os.Stat("gcode"); os.IsNotExist(err) {
		if *debugFlag {
			log.Println("Making Gcode Directory")
		}
		os.Mkdir("gcode", 0777)
	}
	//Create config file if does not exist
	if _, err := os.Stat("config.xml"); os.IsNotExist(err) {
		if *debugFlag {
			log.Println("Making config")
		}
		config.Port = 7766
		config.Slic3rPath = "slic3r"
		xml, err := xml.MarshalIndent(config, "", "    ")
		if err != nil {
			return err
		}
		err = ioutil.WriteFile("config.xml", xml, 0666)
		if err != nil {
			return err
		}
	} else {
		//Read config file if does exist
		data, err := ioutil.ReadFile("config.xml")
		if err != nil || string(data) == "" {
			return err
		}
		if err = xml.Unmarshal(data, &config); err != nil {
			return err
		}
		if *debugFlag {
			log.Println(config)
		}
	}
	//Override config with flags if set
	if *portFlag != 0 {
		config.Port = *portFlag
	}
	if *slic3rPathFlag != "" {
		config.Slic3rPath = *slic3rPathFlag
	}
	return nil
}

func NewServer() *mux.Router{
	//Start HTTP server
	router := mux.NewRouter()
	router.HandleFunc("/slice", sliceHandler).Methods("POST")
	router.Handle("/gcode/{name}", http.StripPrefix("/gcode/", http.FileServer(http.Dir("gcode")))).Methods("GET")
	router.Handle("/stl/{name}", http.StripPrefix("/stl/", http.FileServer(http.Dir("stl")))).Methods("GET")
	router.HandleFunc("/{stl|gcode}", fileListHandler).Methods("GET")
	router.HandleFunc("/{stl|gcode}", clearFilesHandler).Methods("DELETE")
	router.HandleFunc("/{type:stl|gcode}/{name}", deleteFileHandler).Methods("DELETE")
	return router
}

func fileListHandler(writer http.ResponseWriter, request *http.Request) {
	files, err := ioutil.ReadDir("." + request.URL.String())
	if err != nil {
		http.Error(writer, "Could not get file list", 500)
		log.Println(err)
		return
	}
	var fileList []string
	for _, file := range files {
		fileList = append(fileList, file.Name())
	}
	data, err := json.MarshalIndent(fileList, "", "    ")
	if err != nil {
		http.Error(writer, "Could not get file list", 500)
		log.Println(err)
		return
	}
	writer.Write(data)
}

func deleteFileHandler(writer http.ResponseWriter, request *http.Request) {
	vars := getVars(request)
	fileType := vars["type"]
	fileName := vars["name"]
	if err := os.Remove("./" + fileType + "/" + fileName); err != nil {
		log.Println(err)
		http.Error(writer, "Failed to delete file", 500)
		return
	}
	writer.WriteHeader(204)
}

func clearFilesHandler(writer http.ResponseWriter, request *http.Request) {
	d, err := os.Open("." + request.URL.String())
	if err != nil {
		log.Println(err)
		http.Error(writer, "Failed to delete files", 500)
		return
	}
	defer d.Close()
	names, err := d.Readdirnames(-1)
	if err != nil {
		log.Println(err)
		http.Error(writer, "Failed to delete files", 500)
		return
	}
	for _, name := range names {
		err = os.RemoveAll(filepath.Join("." + request.URL.String(), name))
		if err != nil {
			log.Println(err)
			http.Error(writer, "Failed to delete files", 500)
			return
		}
	}
	writer.WriteHeader(204)
}

func sliceHandler(writer http.ResponseWriter, request *http.Request) {
	//Reject request if it is not a POST request
	if request.Method != "POST" {
		http.Error(writer, "Request is not a POST request", 400)
		return
	}
	//Get form data
	request.ParseMultipartForm(32 << 20)
	var otherArgs, callbackType, callbackURL string
	var wait bool
	for key, value := range request.Form {
		if key == "callback" && len(value) > 0 {
			tmp := strings.Split(value[0], ",")
			callbackType = tmp[0]
			callbackURL = tmp[1]
		} else if key == "wait" && len(value) > 0 {
			if value[0] == "true" {
				wait = true
			}
			if value[0] != "true" && value[0] != "false" {
				http.Error(writer, "Invalid value given for wait", 400)
				return
			}
		} else {
			if (len(value) > 0) {
				otherArgs += fmt.Sprintf(" --%s %s", key, value[0])
			} else {
				otherArgs += fmt.Sprintf(" --%s", key)
			}
		}
	}
	//Check if request is valid
	if (callbackType != "" && callbackType != "url" && callbackType != "file") {
		http.Error(writer, "Invalid callback type", 400)
		return
	}
	if (callbackURL != "") {
		_, err := url.Parse(callbackURL)
		if (err != nil) {
			http.Error(writer, "Invalid callback URL", 400)
			return
		}
	}
	//Get STL file
	tmpFile, header, err := request.FormFile("file")
	if err != nil {
		log.Println(err)
		http.Error(writer, "Could not parse file form request", 400)
		return
	}
	defer tmpFile.Close()
	fileName := header.Filename[:(len(header.Filename) - 4)]
	file, err := os.OpenFile("stl/" + header.Filename, os.O_WRONLY | os.O_CREATE, 0666)
	if err != nil {
		log.Println(err)
		http.Error(writer, "Could not open file: stl/" + header.Filename, 500)
		return
	}
	io.Copy(file, tmpFile)
	file.Close()
	//Run slic3r with STL file and args
	args := fmt.Sprintf(" stl/%s.stl %s --output gcode/%s.gcode", fileName, otherArgs, fileName)
	wg := new(sync.WaitGroup)
	wg.Add(1)
	go exe_cmd(config.Slic3rPath + args, wg)
	gcodeFile := "/gcode/" + fileName + ".gcode"
	//Wait if needed
	if (!wait) {
		writer.Write([]byte(gcodeFile))
	}else if (wait && callbackURL == "") {
		wg.Wait()
		writer.Write([]byte(gcodeFile))
	}
	//Run callback
	if (callbackType == "url" && callbackURL != "") {
		wg.Wait()
		req, err := http.NewRequest("POST", callbackURL, bytes.NewBuffer([]byte(gcodeFile)))
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		client := &http.Client{}
		_, err = client.Do(req)
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		if (wait) {
			writer.Write([]byte(gcodeFile))
		}
	} else if (callbackType == "file" && callbackURL != "") {
		wg.Wait()
		file, err := os.Open(gcodeFile[1:len(gcodeFile)])
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		defer file.Close()
		body := &bytes.Buffer{}
		mpWriter := multipart.NewWriter(body)
		part, err := mpWriter.CreateFormFile("file", filepath.Base(gcodeFile[1:len(gcodeFile)]))
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		_, err = io.Copy(part, file)
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		err = mpWriter.Close()
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		req, err := http.NewRequest("POST", callbackURL, body)
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		req.Header.Set("Content-Type", mpWriter.FormDataContentType())
		client := &http.Client{}
		_, err = client.Do(req)
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		if (wait) {
			writer.Write([]byte(gcodeFile))
		}
	}
}

func exe_cmd(cmd string, wg *sync.WaitGroup) {
	if (*debugFlag) {
		log.Println("executing: ", cmd)
	}
	parts := strings.Fields(cmd)
	head := parts[0]
	parts = parts[1:len(parts)]

	out, err := exec.Command(head, parts...).Output()
	if err != nil {
		log.Printf("%s\n", err)
	}
	if (*debugFlag) {
		log.Printf("%s\n", out)
	}
	wg.Done()
}

var getVars = func(request *http.Request) map[string]string {
	return mux.Vars(request)
}
<commit_msg>Added additional error message to slicerFileHandler<commit_after>package main

import (
	"os"
	"log"
	"net/http"
	"io"
	"sync"
	"strings"
	"os/exec"
	"fmt"
	"encoding/xml"
	"io/ioutil"
	"encoding/json"
	"bytes"
	"flag"
	"path/filepath"
	"mime/multipart"
	"net/url"

	"github.com/gorilla/mux"
)

type Config struct {
	Port       int `xml:"port"`
	Slic3rPath string `xml:"slic3rPath"`
}

var config = Config{}
//Declare flags
var debugFlag = flag.Bool("debug", false, "If set debug output will print")
var portFlag = flag.Int("port", 0, "If set slic3r server will bind to given port and will override config file")
var slic3rPathFlag = flag.String("", "slic3r", "If set slic3r server will use given sli3r path and will override config file")

func main() {
	log.Println("Starting Slic3r Server")
	//Parse flags
	flag.Parse()
	err := SetUp()
	if err != nil {
		panic(err)
	}
	server := NewServer()
	http.Handle("/", server)
	log.Printf("Slic3r Server binding to port: %d\n", config.Port)
	http.ListenAndServe(fmt.Sprintf(":%d", config.Port), nil)
}

func SetUp() error{
	//Generate Directories
	if _, err := os.Stat("stl"); os.IsNotExist(err) {
		if (*debugFlag) {
			log.Println("Making STL Directory")
		}
		os.Mkdir("stl", 0777)
	}
	if _, err := os.Stat("gcode"); os.IsNotExist(err) {
		if *debugFlag {
			log.Println("Making Gcode Directory")
		}
		os.Mkdir("gcode", 0777)
	}
	//Create config file if does not exist
	if _, err := os.Stat("config.xml"); os.IsNotExist(err) {
		if *debugFlag {
			log.Println("Making config")
		}
		config.Port = 7766
		config.Slic3rPath = "slic3r"
		xml, err := xml.MarshalIndent(config, "", "    ")
		if err != nil {
			return err
		}
		err = ioutil.WriteFile("config.xml", xml, 0666)
		if err != nil {
			return err
		}
	} else {
		//Read config file if does exist
		data, err := ioutil.ReadFile("config.xml")
		if err != nil || string(data) == "" {
			return err
		}
		if err = xml.Unmarshal(data, &config); err != nil {
			return err
		}
		if *debugFlag {
			log.Println(config)
		}
	}
	//Override config with flags if set
	if *portFlag != 0 {
		config.Port = *portFlag
	}
	if *slic3rPathFlag != "" {
		config.Slic3rPath = *slic3rPathFlag
	}
	return nil
}

func NewServer() *mux.Router{
	//Start HTTP server
	router := mux.NewRouter()
	router.HandleFunc("/slice", sliceHandler).Methods("POST")
	router.Handle("/gcode/{name}", http.StripPrefix("/gcode/", http.FileServer(http.Dir("gcode")))).Methods("GET")
	router.Handle("/stl/{name}", http.StripPrefix("/stl/", http.FileServer(http.Dir("stl")))).Methods("GET")
	router.HandleFunc("/{stl|gcode}", fileListHandler).Methods("GET")
	router.HandleFunc("/{stl|gcode}", clearFilesHandler).Methods("DELETE")
	router.HandleFunc("/{type:stl|gcode}/{name}", deleteFileHandler).Methods("DELETE")
	return router
}

func fileListHandler(writer http.ResponseWriter, request *http.Request) {
	files, err := ioutil.ReadDir("." + request.URL.String())
	if err != nil {
		http.Error(writer, "Could not get file list", 500)
		log.Println(err)
		return
	}
	var fileList []string
	for _, file := range files {
		fileList = append(fileList, file.Name())
	}
	data, err := json.MarshalIndent(fileList, "", "    ")
	if err != nil {
		http.Error(writer, "Could not get file list", 500)
		log.Println(err)
		return
	}
	writer.Write(data)
}

func deleteFileHandler(writer http.ResponseWriter, request *http.Request) {
	vars := getVars(request)
	fileType := vars["type"]
	fileName := vars["name"]
	if err := os.Remove("./" + fileType + "/" + fileName); err != nil {
		log.Println(err)
		http.Error(writer, "Failed to delete file", 500)
		return
	}
	writer.WriteHeader(204)
}

func clearFilesHandler(writer http.ResponseWriter, request *http.Request) {
	d, err := os.Open("." + request.URL.String())
	if err != nil {
		log.Println(err)
		http.Error(writer, "Failed to delete files", 500)
		return
	}
	defer d.Close()
	names, err := d.Readdirnames(-1)
	if err != nil {
		log.Println(err)
		http.Error(writer, "Failed to delete files", 500)
		return
	}
	for _, name := range names {
		err = os.RemoveAll(filepath.Join("." + request.URL.String(), name))
		if err != nil {
			log.Println(err)
			http.Error(writer, "Failed to delete files", 500)
			return
		}
	}
	writer.WriteHeader(204)
}

func sliceHandler(writer http.ResponseWriter, request *http.Request) {
	//Reject request if it is not a POST request
	if request.Method != "POST" {
		http.Error(writer, "Request is not a POST request", 400)
		return
	}
	//Get form data
	err := request.ParseMultipartForm(32 << 20)
	if err != nil {
		http.Error(writer, "Failed to parse multipart form", 500)
		return
	}
	var otherArgs, callbackType, callbackURL string
	var wait bool
	for key, value := range request.Form {
		if key == "callback" && len(value) > 0 {
			tmp := strings.Split(value[0], ",")
			callbackType = tmp[0]
			callbackURL = tmp[1]
		} else if key == "wait" && len(value) > 0 {
			if value[0] == "true" {
				wait = true
			}
			if value[0] != "true" && value[0] != "false" {
				http.Error(writer, "Invalid value given for wait", 400)
				return
			}
		} else {
			if (len(value) > 0) {
				otherArgs += fmt.Sprintf(" --%s %s", key, value[0])
			} else {
				otherArgs += fmt.Sprintf(" --%s", key)
			}
		}
	}
	//Check if request is valid
	if (callbackType != "" && callbackType != "url" && callbackType != "file") {
		http.Error(writer, "Invalid callback type", 400)
		return
	}
	if (callbackURL != "") {
		_, err := url.Parse(callbackURL)
		if (err != nil) {
			http.Error(writer, "Invalid callback URL", 400)
			return
		}
	}
	//Get STL file
	tmpFile, header, err := request.FormFile("file")
	if err != nil {
		log.Println(err)
		http.Error(writer, "Could not parse file form request", 400)
		return
	}
	defer tmpFile.Close()
	fileName := header.Filename[:(len(header.Filename) - 4)]
	file, err := os.OpenFile("stl/" + header.Filename, os.O_WRONLY | os.O_CREATE, 0666)
	if err != nil {
		log.Println(err)
		http.Error(writer, "Could not open file: stl/" + header.Filename, 500)
		return
	}
	io.Copy(file, tmpFile)
	file.Close()
	//Run slic3r with STL file and args
	args := fmt.Sprintf(" stl/%s.stl %s --output gcode/%s.gcode", fileName, otherArgs, fileName)
	wg := new(sync.WaitGroup)
	wg.Add(1)
	go exe_cmd(config.Slic3rPath + args, wg)
	gcodeFile := "/gcode/" + fileName + ".gcode"
	//Wait if needed
	if (!wait) {
		writer.Write([]byte(gcodeFile))
	}else if (wait && callbackURL == "") {
		wg.Wait()
		writer.Write([]byte(gcodeFile))
	}
	//Run callback
	if (callbackType == "url" && callbackURL != "") {
		wg.Wait()
		req, err := http.NewRequest("POST", callbackURL, bytes.NewBuffer([]byte(gcodeFile)))
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		client := &http.Client{}
		_, err = client.Do(req)
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		if (wait) {
			writer.Write([]byte(gcodeFile))
		}
	} else if (callbackType == "file" && callbackURL != "") {
		wg.Wait()
		file, err := os.Open(gcodeFile[1:len(gcodeFile)])
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		defer file.Close()
		body := &bytes.Buffer{}
		mpWriter := multipart.NewWriter(body)
		part, err := mpWriter.CreateFormFile("file", filepath.Base(gcodeFile[1:len(gcodeFile)]))
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		_, err = io.Copy(part, file)
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		err = mpWriter.Close()
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		req, err := http.NewRequest("POST", callbackURL, body)
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		req.Header.Set("Content-Type", mpWriter.FormDataContentType())
		client := &http.Client{}
		_, err = client.Do(req)
		if (err != nil) {
			log.Println(err)
			if (wait) {
				http.Error(writer, "Callback could not be completed", 500)
			}
			return
		}
		if (wait) {
			writer.Write([]byte(gcodeFile))
		}
	}
}

func exe_cmd(cmd string, wg *sync.WaitGroup) {
	if (*debugFlag) {
		log.Println("executing: ", cmd)
	}
	parts := strings.Fields(cmd)
	head := parts[0]
	parts = parts[1:len(parts)]

	out, err := exec.Command(head, parts...).Output()
	if err != nil {
		log.Printf("%s\n", err)
	}
	if (*debugFlag) {
		log.Printf("%s\n", out)
	}
	wg.Done()
}

var getVars = func(request *http.Request) map[string]string {
	return mux.Vars(request)
}
<|endoftext|>


<commit_before>package rest

import (
	"net/http"
	"net/http/httptest"
	"time"
)

// RecordContext is a implementation of Context that records its mutations for later inspection in tests.
type RecordContext struct {
	Context

	request  *http.Request
	recorder http.ResponseWriter
	renders  []interface{}
}

// NewRecordContext create a RecordContext with a http request and url's parameters.
func NewRecordContext(vars map[string]string, req *http.Request) *RecordContext {
	resp := httptest.NewRecorder()
	return &RecordContext{
		Context: newBaseContext("test", nil, "utf-8", vars, req, resp),

		request:  req,
		recorder: resp,
	}
}

// Render implement Context's Render.
func (ctx *RecordContext) Render(v interface{}) error {
	ctx.renders = append(ctx.renders, v)
	return ctx.Context.Render(v)
}

// Ping implement StreamContext's Ping.
func (ctx *RecordContext) Ping() error {
	return nil
}

// SetWriteDeadline implement StreamContext's SetWriteDeadline.
func (ctx *RecordContext) SetWriteDeadline(t time.Time) error {
	return nil
}
<commit_msg>record context export field<commit_after>package rest

import (
	"net/http"
	"net/http/httptest"
	"time"
)

// RecordContext is a implementation of Context that records its mutations for later inspection in tests.
type RecordContext struct {
	Context

	Req      *http.Request
	Recorder http.ResponseWriter
	Renders  []interface{}
}

// NewRecordContext create a RecordContext with a http request and url's parameters.
func NewRecordContext(vars map[string]string, req *http.Request) *RecordContext {
	resp := httptest.NewRecorder()
	return &RecordContext{
		Context: newBaseContext("test", nil, "utf-8", vars, req, resp),

		Req:      req,
		Recorder: resp,
	}
}

// Render implement Context's Render.
func (ctx *RecordContext) Render(v interface{}) error {
	ctx.Renders = append(ctx.Renders, v)
	return ctx.Context.Render(v)
}

// Ping implement StreamContext's Ping.
func (ctx *RecordContext) Ping() error {
	return nil
}

// SetWriteDeadline implement StreamContext's SetWriteDeadline.
func (ctx *RecordContext) SetWriteDeadline(t time.Time) error {
	return nil
}
<|endoftext|>


<commit_before>package smtp

// http://www.rfc-editor.org/rfc/rfc5321.txt

import (
	"io"
	"log"
	"strings"

	"github.com/ian-kent/Go-MailHog/MailHog-MTA/backend"
	"github.com/ian-kent/Go-MailHog/data"
	"github.com/ian-kent/Go-MailHog/smtp/protocol"
)

// Session represents a SMTP session using net.TCPConn
type Session struct {
	server *Server

	conn          io.ReadWriteCloser
	proto         *protocol.Protocol
	remoteAddress string
	isTLS         bool
	line          string
	identity      *backend.Identity
}

// Accept starts a new SMTP session using io.ReadWriteCloser
func (s *Server) Accept(remoteAddress string, conn io.ReadWriteCloser) {
	proto := protocol.NewProtocol()
	proto.Hostname = s.Hostname

	session := &Session{
		server:        s,
		conn:          conn,
		proto:         proto,
		remoteAddress: remoteAddress,
		isTLS:         false,
		line:          "",
		identity:      nil,
	}

	// FIXME this all feels nasty
	proto.LogHandler = session.logf
	proto.MessageReceivedHandler = session.acceptMessage
	proto.ValidateSenderHandler = session.validateSender
	proto.ValidateRecipientHandler = session.validateRecipient
	proto.ValidateAuthenticationHandler = session.validateAuthentication
	if session.server != nil && session.server.AuthBackend != nil {
		proto.GetAuthenticationMechanismsHandler = session.server.AuthBackend.Mechanisms
	}
	proto.SMTPVerbFilter = session.verbFilter

	session.logf("Starting session")
	session.Write(proto.Start())
	for session.Read() == true {
	}
	session.logf("Session ended")
}

func (c *Session) validateAuthentication(mechanism string, args ...string) (errorReply *protocol.Reply, ok bool) {
	if c.server.AuthBackend == nil {
		return protocol.ReplyInvalidAuth(), false
	}
	i, e, ok := c.server.AuthBackend.Authenticate(mechanism, args...)
	if e != nil || !ok {
		return protocol.ReplyInvalidAuth(), false
	}
	c.identity = i
	return nil, true
}

func (c *Session) validateRecipient(to string) bool {
	maxRecipients := c.server.DeliveryBackend.MaxRecipients(c.identity)
	if maxRecipients > -1 && len(c.proto.Message.To) > maxRecipients {
		return false
	}
	return c.server.DeliveryBackend.WillDeliver(to, c.proto.Message.From, c.identity)
}

func (c *Session) validateSender(from string) bool {
	return true
}

func (c *Session) verbFilter(verb string, args ...string) (errorReply *protocol.Reply) {
	if c.server.PolicySet.RequireAuthentication && c.proto.State == protocol.MAIL && c.identity == nil {
		verb = strings.ToUpper(verb)
		if verb == "RSET" || verb == "QUIT" || verb == "NOOP" ||
			verb == "EHLO" || verb == "HELO" || verb == "AUTH" {
			return nil
		}
		// FIXME more appropriate error
		return protocol.ReplyUnrecognisedCommand()
	}
	return nil
}

func (c *Session) acceptMessage(msg *data.Message) (id string, err error) {
	c.logf("Storing message %s", msg.ID)
	//id, err = c.storage.Store(msg)
	//c.messageChan <- msg
	return
}

func (c *Session) logf(message string, args ...interface{}) {
	message = strings.Join([]string{"[SMTP %s]", message}, " ")
	args = append([]interface{}{c.remoteAddress}, args...)
	log.Printf(message, args...)
}

// Read reads from the underlying net.TCPConn
func (c *Session) Read() bool {
	buf := make([]byte, 1024)
	n, err := io.Reader(c.conn).Read(buf)

	if n == 0 {
		c.logf("Connection closed by remote host\n")
		io.Closer(c.conn).Close() // not sure this is necessary?
		return false
	}

	if err != nil {
		c.logf("Error reading from socket: %s\n", err)
		return false
	}

	text := string(buf[0:n])
	logText := strings.Replace(text, "\n", "\\n", -1)
	logText = strings.Replace(logText, "\r", "\\r", -1)
	c.logf("Received %d bytes: '%s'\n", n, logText)

	c.line += text

	for strings.Contains(c.line, "\n") {
		line, reply := c.proto.Parse(c.line)
		c.line = line

		if reply != nil {
			c.Write(reply)
			if reply.Status == 221 {
				io.Closer(c.conn).Close()
				return false
			}
		}
	}

	return true
}

// Write writes a reply to the underlying net.TCPConn
func (c *Session) Write(reply *protocol.Reply) {
	lines := reply.Lines()
	for _, l := range lines {
		logText := strings.Replace(l, "\n", "\\n", -1)
		logText = strings.Replace(logText, "\r", "\\r", -1)
		c.logf("Sent %d bytes: '%s'", len(l), logText)
		io.Writer(c.conn).Write([]byte(l))
	}
}
<commit_msg>Fix delivery bug<commit_after>package smtp

// http://www.rfc-editor.org/rfc/rfc5321.txt

import (
	"io"
	"log"
	"strings"

	"github.com/ian-kent/Go-MailHog/MailHog-MTA/backend"
	"github.com/ian-kent/Go-MailHog/data"
	"github.com/ian-kent/Go-MailHog/smtp/protocol"
)

// Session represents a SMTP session using net.TCPConn
type Session struct {
	server *Server

	conn          io.ReadWriteCloser
	proto         *protocol.Protocol
	remoteAddress string
	isTLS         bool
	line          string
	identity      *backend.Identity
}

// Accept starts a new SMTP session using io.ReadWriteCloser
func (s *Server) Accept(remoteAddress string, conn io.ReadWriteCloser) {
	proto := protocol.NewProtocol()
	proto.Hostname = s.Hostname

	session := &Session{
		server:        s,
		conn:          conn,
		proto:         proto,
		remoteAddress: remoteAddress,
		isTLS:         false,
		line:          "",
		identity:      nil,
	}

	// FIXME this all feels nasty
	proto.LogHandler = session.logf
	proto.MessageReceivedHandler = session.acceptMessage
	proto.ValidateSenderHandler = session.validateSender
	proto.ValidateRecipientHandler = session.validateRecipient
	proto.ValidateAuthenticationHandler = session.validateAuthentication
	if session.server != nil && session.server.AuthBackend != nil {
		proto.GetAuthenticationMechanismsHandler = session.server.AuthBackend.Mechanisms
	}
	proto.SMTPVerbFilter = session.verbFilter

	session.logf("Starting session")
	session.Write(proto.Start())
	for session.Read() == true {
	}
	session.logf("Session ended")
}

func (c *Session) validateAuthentication(mechanism string, args ...string) (errorReply *protocol.Reply, ok bool) {
	if c.server.AuthBackend == nil {
		return protocol.ReplyInvalidAuth(), false
	}
	i, e, ok := c.server.AuthBackend.Authenticate(mechanism, args...)
	if e != nil || !ok {
		return protocol.ReplyInvalidAuth(), false
	}
	c.identity = i
	return nil, true
}

func (c *Session) validateRecipient(to string) bool {
	if c.server.DeliveryBackend == nil {
		return false
	}
	maxRecipients := c.server.DeliveryBackend.MaxRecipients(c.identity)
	if maxRecipients > -1 && len(c.proto.Message.To) > maxRecipients {
		return false
	}
	return c.server.DeliveryBackend.WillDeliver(to, c.proto.Message.From, c.identity)
}

func (c *Session) validateSender(from string) bool {
	return true
}

func (c *Session) verbFilter(verb string, args ...string) (errorReply *protocol.Reply) {
	if c.server.PolicySet.RequireAuthentication && c.proto.State == protocol.MAIL && c.identity == nil {
		verb = strings.ToUpper(verb)
		if verb == "RSET" || verb == "QUIT" || verb == "NOOP" ||
			verb == "EHLO" || verb == "HELO" || verb == "AUTH" {
			return nil
		}
		// FIXME more appropriate error
		return protocol.ReplyUnrecognisedCommand()
	}
	return nil
}

func (c *Session) acceptMessage(msg *data.Message) (id string, err error) {
	c.logf("Storing message %s", msg.ID)
	//id, err = c.storage.Store(msg)
	//c.messageChan <- msg
	return
}

func (c *Session) logf(message string, args ...interface{}) {
	message = strings.Join([]string{"[SMTP %s]", message}, " ")
	args = append([]interface{}{c.remoteAddress}, args...)
	log.Printf(message, args...)
}

// Read reads from the underlying net.TCPConn
func (c *Session) Read() bool {
	buf := make([]byte, 1024)
	n, err := io.Reader(c.conn).Read(buf)

	if n == 0 {
		c.logf("Connection closed by remote host\n")
		io.Closer(c.conn).Close() // not sure this is necessary?
		return false
	}

	if err != nil {
		c.logf("Error reading from socket: %s\n", err)
		return false
	}

	text := string(buf[0:n])
	logText := strings.Replace(text, "\n", "\\n", -1)
	logText = strings.Replace(logText, "\r", "\\r", -1)
	c.logf("Received %d bytes: '%s'\n", n, logText)

	c.line += text

	for strings.Contains(c.line, "\n") {
		line, reply := c.proto.Parse(c.line)
		c.line = line

		if reply != nil {
			c.Write(reply)
			if reply.Status == 221 {
				io.Closer(c.conn).Close()
				return false
			}
		}
	}

	return true
}

// Write writes a reply to the underlying net.TCPConn
func (c *Session) Write(reply *protocol.Reply) {
	lines := reply.Lines()
	for _, l := range lines {
		logText := strings.Replace(l, "\n", "\\n", -1)
		logText = strings.Replace(logText, "\r", "\\r", -1)
		c.logf("Sent %d bytes: '%s'", len(l), logText)
		io.Writer(c.conn).Write([]byte(l))
	}
}
<|endoftext|>


<commit_before>package pool

import (
	"context"
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
	"sync/atomic"
	"time"
)

var (
	errEmptyTarget = errors.New("error: empty target hostname")
	errNotRun      = errors.New("error: pool is not running")
	errNotWait     = errors.New("error: pool is not waiting tasks")
)

// Task - structure describing a task
type Task struct {
	ID           int64
	WorkerID     int64
	Hostname     string
	Body         []byte
	Proxy        *url.URL
	Response     *http.Response
	ResponceTime time.Duration
	Error        error
}

// Add - add new task to pool
func (p *Pool) Add(hostname string, proxy *url.URL) error {
	if hostname == "" {
		return errEmptyTarget
	}
	if !p.poolIsRunning() {
		return errNotRun
	}
	if !p.poolIsWaitingTasks() {
		return errNotWait
	}
	_, err := url.Parse(hostname)
	if err != nil {
		return err
	}
	task := &Task{
		Hostname: hostname,
		Proxy:    proxy,
	}
	p.inputTaskChan <- task
	return nil
}

func (p *Pool) addTask(task *Task) {
	if p.getFreeWorkers() > 0 {
		p.decWorkers()
		p.workChan <- task
	} else {
		p.queue.put(task)
	}
}

func (p *Pool) tryGetTask() {
	task, ok := p.queue.get()
	if ok {
		p.decWorkers()
		p.workChan <- task
	}
}

// SetTimeout - set http timeout in millisecond
func (p *Pool) SetTimeout(t int64) {
	p.timeout = time.Duration(t) * time.Millisecond
}

// SetQuitTimeout - set timeout to quit after finish all tasks in millisecond
func (p *Pool) SetQuitTimeout(t int64) {
	p.useQuitTimeout = true
	p.quitTimeout = time.Duration(t) * time.Millisecond
	p.timer = time.NewTimer(p.quitTimeout)
	go func() {
		<-p.timer.C
		p.quit <- true
	}()
}

func (p *Pool) crawl(t *Task) *Task {
	startTime := time.Now()
	client := &http.Client{
		Timeout: p.timeout,
	}
	if t.Proxy != nil {
		client.Transport = &http.Transport{
			Proxy:             http.ProxyURL(t.Proxy),
			DisableKeepAlives: true,
		}
	}
	req, err := http.NewRequest("GET", t.Hostname, nil)
	if err != nil {
		t.Error = err
		return t
	}
	ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
	defer cancel()
	req = req.WithContext(ctx)
	resp, err := client.Do(req)
	if err != nil {
		t.Error = err
		return t
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error = err
		return t
	}
	err = resp.Body.Close()
	if err != nil {
		t.Error = err
		return t
	}
	t.Body = body
	t.Response = resp
	t.ResponceTime = time.Since(startTime)
	return t
}

// GetAddedTasks - get num of added tasks
func (p *Pool) GetAddedTasks() int64 {
	return atomic.LoadInt64(&p.addedTasks)
}

func (p *Pool) incAddedTasks() {
	atomic.AddInt64(&p.addedTasks, 1)
}

// GetCompletedTasks - get num of completed tasks
func (p *Pool) GetCompletedTasks() int64 {
	return atomic.LoadInt64(&p.completedTasks)
}

func (p *Pool) incCompletedTasks() {
	atomic.AddInt64(&p.completedTasks, 1)
}

func (p *Pool) isCompleteJobs() bool {
	return p.GetCompletedTasks() > 0 && p.GetCompletedTasks() == p.GetAddedTasks()
}
<commit_msg>use defer<commit_after>package pool

import (
	"context"
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
	"sync/atomic"
	"time"
)

var (
	errEmptyTarget = errors.New("error: empty target hostname")
	errNotRun      = errors.New("error: pool is not running")
	errNotWait     = errors.New("error: pool is not waiting tasks")
)

// Task - structure describing a task
type Task struct {
	ID           int64
	WorkerID     int64
	Hostname     string
	Body         []byte
	Proxy        *url.URL
	Response     *http.Response
	ResponceTime time.Duration
	Error        error
}

// Add - add new task to pool
func (p *Pool) Add(hostname string, proxy *url.URL) error {
	if hostname == "" {
		return errEmptyTarget
	}
	if !p.poolIsRunning() {
		return errNotRun
	}
	if !p.poolIsWaitingTasks() {
		return errNotWait
	}
	_, err := url.Parse(hostname)
	if err != nil {
		return err
	}
	task := &Task{
		Hostname: hostname,
		Proxy:    proxy,
	}
	p.inputTaskChan <- task
	return nil
}

func (p *Pool) addTask(task *Task) {
	if p.getFreeWorkers() > 0 {
		p.decWorkers()
		p.workChan <- task
	} else {
		p.queue.put(task)
	}
}

func (p *Pool) tryGetTask() {
	task, ok := p.queue.get()
	if ok {
		p.decWorkers()
		p.workChan <- task
	}
}

// SetTimeout - set http timeout in millisecond
func (p *Pool) SetTimeout(t int64) {
	p.timeout = time.Duration(t) * time.Millisecond
}

// SetQuitTimeout - set timeout to quit after finish all tasks in millisecond
func (p *Pool) SetQuitTimeout(t int64) {
	p.useQuitTimeout = true
	p.quitTimeout = time.Duration(t) * time.Millisecond
	p.timer = time.NewTimer(p.quitTimeout)
	go func() {
		<-p.timer.C
		p.quit <- true
	}()
}

func (p *Pool) crawl(t *Task) *Task {
	startTime := time.Now()
	client := &http.Client{
		Timeout: p.timeout,
	}
	if t.Proxy != nil {
		client.Transport = &http.Transport{
			Proxy:             http.ProxyURL(t.Proxy),
			DisableKeepAlives: true,
		}
	}
	req, err := http.NewRequest("GET", t.Hostname, nil)
	if err != nil {
		t.Error = err
		return t
	}
	ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
	defer cancel()
	req = req.WithContext(ctx)
	resp, err := client.Do(req)
	if err != nil {
		t.Error = err
		return t
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error = err
		return t
	}
	t.Body = body
	t.Response = resp
	t.ResponceTime = time.Since(startTime)
	return t
}

// GetAddedTasks - get num of added tasks
func (p *Pool) GetAddedTasks() int64 {
	return atomic.LoadInt64(&p.addedTasks)
}

func (p *Pool) incAddedTasks() {
	atomic.AddInt64(&p.addedTasks, 1)
}

// GetCompletedTasks - get num of completed tasks
func (p *Pool) GetCompletedTasks() int64 {
	return atomic.LoadInt64(&p.completedTasks)
}

func (p *Pool) incCompletedTasks() {
	atomic.AddInt64(&p.completedTasks, 1)
}

func (p *Pool) isCompleteJobs() bool {
	return p.GetCompletedTasks() > 0 && p.GetCompletedTasks() == p.GetAddedTasks()
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 Space Monkey, Inc.
//
// 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 monitor

import (
	"sync"

	"golang.org/x/net/context"
)

type taskKey int

const (
	taskGetFunc taskKey = 0
)

type LazyTask func(ctx *context.Context, args ...interface{}) func(*error)

func (f LazyTask) Func() (out *Func) {
	// we're doing crazy things to make a function have methods that do other
	// things with internal state. basically, we have a secret argument we can
	// pass to the function that is only checked if ctx is nil (which it should
	// never be) that controls what other behavior we want.
	// in this case, if arg[0] is taskGetFunc, then f will place the func in the
	// out location.
	// since someone can cast any function of this signature to a lazy task,
	// let's make sure we got roughly expected behavior and panic otherwise
	if f(nil, taskGetFunc, &out) != nil || out == nil {
		panic("Func() called on a non-LazyTask function")
	}
	return out
}

func taskArgs(f *Func, args []interface{}) bool {
	// this function essentially does method dispatch for LazyTasks. returns true
	// if a method got dispatched and normal behavior should be aborted
	if len(args) != 2 {
		return false
	}
	val, ok := args[0].(taskKey)
	if !ok {
		return false
	}
	switch val {
	case taskGetFunc:
		*(args[1].(**Func)) = f
		return true
	}
	return false
}

func (s *Scope) Task() LazyTask {
	var initOnce sync.Once
	var f *Func
	init := func() {
		f = s.FuncNamed(callerFunc(3))
	}
	return LazyTask(func(ctx *context.Context,
		args ...interface{}) func(*error) {
		initOnce.Do(init)
		if ctx == nil && taskArgs(f, args) {
			return nil
		}
		s, exit := newSpan(*ctx, f, args, NewId(), nil)
		*ctx = s
		return exit
	})
}

func (s *Scope) TaskNamed(name string) LazyTask {
	return s.FuncNamed(name).Task
}

func (f *Func) Task(ctx *context.Context, args ...interface{}) func(*error) {
	if ctx == nil && taskArgs(f, args) {
		return nil
	}
	s, exit := newSpan(*ctx, f, args, NewId(), nil)
	*ctx = s
	return exit
}

func (f *Func) RemoteTrace(ctx *context.Context, spanId int64, trace *Trace,
	args ...interface{}) func(*error) {
	if trace != nil {
		f.scope.r.observeTrace(trace)
	}
	s, exit := newSpan(*ctx, f, args, spanId, trace)
	*ctx = s
	return exit
}

func (f *Func) ResetTrace(ctx *context.Context,
	args ...interface{}) func(*error) {
	if ctx == nil && taskArgs(f, args) {
		return nil
	}
	trace := NewTrace(NewId())
	f.scope.r.observeTrace(trace)
	s, exit := newSpan(*ctx, f, args, trace.Id(), trace)
	*ctx = s
	return exit
}
<commit_msg>monv2: support nil contexts<commit_after>// Copyright (C) 2015 Space Monkey, Inc.
//
// 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 monitor

import (
	"sync"
	"time"

	"golang.org/x/net/context"
)

type taskKey int

const taskGetFunc taskKey = 0

type lazyTaskSecretT struct{}

func (*lazyTaskSecretT) Value(key interface{}) interface{} { return nil }
func (*lazyTaskSecretT) Done() <-chan struct{}             { return nil }
func (*lazyTaskSecretT) Err() error                        { return nil }
func (*lazyTaskSecretT) Deadline() (time.Time, bool) {
	return time.Time{}, false
}

var lazyTaskSecret context.Context = &lazyTaskSecretT{}

type LazyTask func(ctx *context.Context, args ...interface{}) func(*error)

func (f LazyTask) Func() (out *Func) {
	// we're doing crazy things to make a function have methods that do other
	// things with internal state. basically, we have a secret argument we can
	// pass to the function that is only checked if ctx is lazyTaskSecret (
	// which it should never be) that controls what other behavior we want.
	// in this case, if arg[0] is taskGetFunc, then f will place the func in the
	// out location.
	// since someone can cast any function of this signature to a lazy task,
	// let's make sure we got roughly expected behavior and panic otherwise
	if f(&lazyTaskSecret, taskGetFunc, &out) != nil || out == nil {
		panic("Func() called on a non-LazyTask function")
	}
	return out
}

func taskArgs(f *Func, args []interface{}) bool {
	// this function essentially does method dispatch for LazyTasks. returns true
	// if a method got dispatched and normal behavior should be aborted
	if len(args) != 2 {
		return false
	}
	val, ok := args[0].(taskKey)
	if !ok {
		return false
	}
	switch val {
	case taskGetFunc:
		*(args[1].(**Func)) = f
		return true
	}
	return false
}

func (s *Scope) Task() LazyTask {
	var initOnce sync.Once
	var f *Func
	init := func() {
		f = s.FuncNamed(callerFunc(3))
	}
	return LazyTask(func(ctx *context.Context,
		args ...interface{}) func(*error) {
		if ctx == nil {
			ctx = emptyCtx()
		} else if ctx == &lazyTaskSecret && taskArgs(f, args) {
			return nil
		}
		initOnce.Do(init)
		s, exit := newSpan(*ctx, f, args, NewId(), nil)
		*ctx = s
		return exit
	})
}

func (s *Scope) TaskNamed(name string) LazyTask {
	return s.FuncNamed(name).Task
}

func (f *Func) Task(ctx *context.Context, args ...interface{}) func(*error) {
	if ctx == nil {
		ctx = emptyCtx()
	} else if ctx == &lazyTaskSecret && taskArgs(f, args) {
		return nil
	}
	s, exit := newSpan(*ctx, f, args, NewId(), nil)
	*ctx = s
	return exit
}

func (f *Func) RemoteTrace(ctx *context.Context, spanId int64, trace *Trace,
	args ...interface{}) func(*error) {
	if ctx == nil {
		ctx = emptyCtx()
	}
	if trace != nil {
		f.scope.r.observeTrace(trace)
	}
	s, exit := newSpan(*ctx, f, args, spanId, trace)
	*ctx = s
	return exit
}

func (f *Func) ResetTrace(ctx *context.Context,
	args ...interface{}) func(*error) {
	if ctx == nil {
		ctx = emptyCtx()
	} else if ctx == &lazyTaskSecret && taskArgs(f, args) {
		return nil
	}
	trace := NewTrace(NewId())
	f.scope.r.observeTrace(trace)
	s, exit := newSpan(*ctx, f, args, trace.Id(), trace)
	*ctx = s
	return exit
}

func emptyCtx() *context.Context {
	// TODO: maybe we should generate some special parent for these unparented
	// spans
	ctx := context.Background()
	return &ctx
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"golang.org/x/net/context"
)

type (
	CommandConfig struct {
		Template []string            `json:"-"`
		Options  map[string][]string `json:"options,omitempty"`
		Dryrun   bool                `json:"dryrun,omitempty"`
	}

	Job struct {
		config *CommandConfig
		// https://godoc.org/google.golang.org/genproto/googleapis/pubsub/v1#ReceivedMessage
		message      *JobMessage
		notification *ProgressNotification
		storage      Storage

		// These are set at at setupWorkspace
		workspace     string
		downloads_dir string
		uploads_dir   string

		// These are set at setupDownloadFiles
		downloadFileMap     map[string]string
		remoteDownloadFiles interface{}
		localDownloadFiles  interface{}

		cmd *exec.Cmd
	}
)

func (job *Job) run(ctx context.Context) error {
	verr := job.message.Validate()
	if verr != nil {
		log.Printf("Invalid Message: MessageId: %v, Message: %v, error: %v\n", job.message.MessageId(), job.message.raw.Message, verr)
		err := job.withNotify(CANCELLING, job.message.Ack)()
		if err != nil {
			return err
		}
		return nil
	}

	go job.message.sendMADPeriodically()
	defer job.message.Done()

	job.notification.notify(PROCESSING, job.message.MessageId(), "info")

	err := job.setupWorkspace()
	if err != nil {
		return err
	}

	defer job.clearWorkspace()

	err = job.withNotify(PREPARING, job.setupDownloadFiles)()
	if err != nil {
		return err
	}

	err := job.build()
	if err != nil {
		log.Fatalf("Command build Error template: %v msg: %v cause of %v\n", job.config.Template, job.message, err)
		return err
	}

	err = job.withNotify(DOWNLOADING, job.downloadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(EXECUTING, job.execute)()
	if err != nil {
		return err
	}

	err = job.withNotify(UPLOADING, job.uploadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(ACKSENDING, job.message.Ack)()
	if err != nil {
		return err
	}

	job.notification.notify(CLEANUP, job.message.MessageId(), "info")
	return err
}

func (job *Job) withNotify(progress int, f func() error) func() error {
	msg_id := job.message.MessageId()
	return func() error {
		job.notification.notify(progress, msg_id, "info")
		err := f()
		if err != nil {
			job.notification.notify(progress+2, msg_id, "error")
			return err
		}
		job.notification.notify(progress+1, msg_id, "info")
		return nil
	}
}

func (job *Job) setupWorkspace() error {
	dir, err := ioutil.TempDir("", "workspace")
	if err != nil {
		log.Fatal(err)
		return err
	}

	subdirs := []string{
		filepath.Join(dir, "downloads"),
		filepath.Join(dir, "uploads"),
	}
	for _, subdir := range subdirs {
		err := os.MkdirAll(subdir, 0700)
		if err != nil {
			return err
		}
	}
	job.workspace = dir
	job.downloads_dir = subdirs[0]
	job.uploads_dir = subdirs[1]
	return nil
}

func (job *Job) clearWorkspace() error {
	return os.RemoveAll(job.workspace)
}

func (job *Job) setupDownloadFiles() error {
	job.downloadFileMap = map[string]string{}
	job.remoteDownloadFiles = job.message.DownloadFiles()
	objects := job.flatten(job.remoteDownloadFiles)
	remoteUrls := []string{}
	for _, obj := range objects {
		switch obj.(type) {
		case string:
			remoteUrls = append(remoteUrls, obj.(string))
		default:
			log.Printf("Invalid download file URL: %v [%T]", obj, obj)
		}
	}
	for _, remote_url := range remoteUrls {
		url, err := url.Parse(remote_url)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remote_url, err)
			return err
		}
		urlstr := fmt.Sprintf("gs://%v%v", url.Host, url.Path)
		destPath := filepath.Join(job.downloads_dir, url.Host, url.Path)
		job.downloadFileMap[urlstr] = destPath
	}
	job.localDownloadFiles = job.copyWithFileMap(job.remoteDownloadFiles)
	return nil
}

func (job *Job) copyWithFileMap(obj interface{}) interface{} {
	switch obj.(type) {
	case map[string]interface{}:
		result := map[string]interface{}{}
		for k, v := range obj.(map[string]interface{}) {
			result[k] = job.copyWithFileMap(v)
		}
		return result
	case []interface{}:
		result := []interface{}{}
		for _, v := range obj.([]interface{}) {
			result = append(result, job.copyWithFileMap(v))
		}
		return result
	case string:
		return job.downloadFileMap[obj.(string)]
	default:
		return obj
	}
}

func (job *Job) buildVariable() *Variable {
	return &Variable{
		data: map[string]interface{}{
			"workspace":             job.workspace,
			"downloads_dir":         job.downloads_dir,
			"uploads_dir":           job.uploads_dir,
			"download_files":        job.localDownloadFiles,
			"local_download_files":  job.localDownloadFiles,
			"remote_download_files": job.remoteDownloadFiles,
			"attrs":                 job.message.raw.Message.Attributes,
			"attributes":            job.message.raw.Message.Attributes,
			"data":                  job.message.raw.Message.Data,
		},
	}
}

func (job *Job) build() error {
	v := job.buildVariable()

	values, err := job.extract(v, job.config.Template)
	if err != nil {
		return err
	}
	if len(job.config.Options) > 0 {
		key := strings.Join(values, " ")
		t := job.config.Options[key]
		if t == nil {
			t = job.config.Options["default"]
		}
		if t != nil {
			values, err = job.extract(v, t)
			if err != nil {
				return err
			}
		}
	}
	job.cmd = exec.Command(values[0], values[1:]...)
	return nil
}

func (job *Job) extract(v *Variable, values []string) ([]string, error) {
	result := []string{}
	for _, src := range values {
		extracted, err := v.expand(src)
		if err != nil {
			return nil, err
		}
		vals := strings.Split(extracted, v.separator)
		for _, val := range vals {
			result = append(result, val)
		}
	}
	return result, nil
}

func (job *Job) downloadFiles() error {
	for remoteURL, destPath := range job.downloadFileMap {
		url, err := url.Parse(remoteURL)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remoteURL, err)
			return err
		}

		dir := path.Dir(destPath)
		err = os.MkdirAll(dir, 0700)
		if err != nil {
			return err
		}

		err = job.storage.Download(url.Host, url.Path[1:], destPath)
		if err != nil {
			return err
		}
	}
	return nil
}

func (job *Job) execute() error {
	var out bytes.Buffer
	job.cmd.Stdout = &out
	job.cmd.Stderr = &out
	log.Printf("EXECUTE running: %v\n", job.cmd)
	err = job.cmd.Run()
	if err != nil {
		log.Printf("Command Error: cmd: %v cause of %v\n%v\n", job.cmd, err, out.String())
		return err
	}
	return nil
}

func (job *Job) uploadFiles() error {
	localPaths, err := job.listFiles(job.uploads_dir)
	if err != nil {
		return err
	}
	for _, localPath := range localPaths {
		relPath, err := filepath.Rel(job.uploads_dir, localPath)
		if err != nil {
			log.Fatalf("Error getting relative path of %v: %v\n", localPath, err)
			return err
		}
		sep := string([]rune{os.PathSeparator})
		parts := strings.Split(relPath, sep)
		bucket := parts[0]
		object := strings.Join(parts[1:], sep)
		err = job.storage.Upload(bucket, object, localPath)
		if err != nil {
			log.Fatalf("Error uploading %v to gs://%v/%v: %v\n", localPath, bucket, object, err)
			return err
		}
	}
	return nil
}

func (job *Job) listFiles(dir string) ([]string, error) {
	result := []string{}
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			result = append(result, path)
		}
		return nil
	})
	if err != nil {
		log.Fatalf("Error listing upload files: %v\n", err)
		return nil, err
	}
	return result, nil
}

func (job *Job) flatten(obj interface{}) []interface{} {
	// Support only unmarshalled object from JSON
	// See https://golang.org/pkg/encoding/json/#Unmarshal also
	switch obj.(type) {
	case []interface{}:
		res := []interface{}{}
		for _, i := range obj.([]interface{}) {
			switch i.(type) {
			case bool, float64, string, nil:
				res = append(res, i)
			default:
				for _, j := range job.flatten(i) {
					res = append(res, j)
				}
			}
		}
		return res
	case map[string]interface{}:
		values := []interface{}{}
		for _, val := range obj.(map[string]interface{}) {
			values = append(values, val)
		}
		return job.flatten(values)
	default:
		return []interface{}{obj}
	}
}
<commit_msg>:+1: Start job.message.sendMADPeriodically before downloadFiles<commit_after>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"golang.org/x/net/context"
)

type (
	CommandConfig struct {
		Template []string            `json:"-"`
		Options  map[string][]string `json:"options,omitempty"`
		Dryrun   bool                `json:"dryrun,omitempty"`
	}

	Job struct {
		config *CommandConfig
		// https://godoc.org/google.golang.org/genproto/googleapis/pubsub/v1#ReceivedMessage
		message      *JobMessage
		notification *ProgressNotification
		storage      Storage

		// These are set at at setupWorkspace
		workspace     string
		downloads_dir string
		uploads_dir   string

		// These are set at setupDownloadFiles
		downloadFileMap     map[string]string
		remoteDownloadFiles interface{}
		localDownloadFiles  interface{}

		cmd *exec.Cmd
	}
)

func (job *Job) run(ctx context.Context) error {
	job.notification.notify(PROCESSING, job.message.MessageId(), "info")

	verr := job.message.Validate()
	if verr != nil {
		log.Printf("Invalid Message: MessageId: %v, Message: %v, error: %v\n", job.message.MessageId(), job.message.raw.Message, verr)
		err := job.withNotify(CANCELLING, job.message.Ack)()
		if err != nil {
			return err
		}
		return nil
	}

	err := job.setupWorkspace()
	if err != nil {
		return err
	}

	defer job.clearWorkspace()

	err = job.withNotify(PREPARING, job.setupDownloadFiles)()
	if err != nil {
		return err
	}

	err = job.build()
	if err != nil {
		log.Fatalf("Command build Error template: %v msg: %v cause of %v\n", job.config.Template, job.message, err)
		return err
	}

	go job.message.sendMADPeriodically()
	defer job.message.Done()

	err = job.withNotify(DOWNLOADING, job.downloadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(EXECUTING, job.execute)()
	if err != nil {
		return err
	}

	err = job.withNotify(UPLOADING, job.uploadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(ACKSENDING, job.message.Ack)()
	if err != nil {
		return err
	}

	job.notification.notify(CLEANUP, job.message.MessageId(), "info")
	return err
}

func (job *Job) withNotify(progress int, f func() error) func() error {
	msg_id := job.message.MessageId()
	return func() error {
		job.notification.notify(progress, msg_id, "info")
		err := f()
		if err != nil {
			job.notification.notify(progress+2, msg_id, "error")
			return err
		}
		job.notification.notify(progress+1, msg_id, "info")
		return nil
	}
}

func (job *Job) setupWorkspace() error {
	dir, err := ioutil.TempDir("", "workspace")
	if err != nil {
		log.Fatal(err)
		return err
	}

	subdirs := []string{
		filepath.Join(dir, "downloads"),
		filepath.Join(dir, "uploads"),
	}
	for _, subdir := range subdirs {
		err := os.MkdirAll(subdir, 0700)
		if err != nil {
			return err
		}
	}
	job.workspace = dir
	job.downloads_dir = subdirs[0]
	job.uploads_dir = subdirs[1]
	return nil
}

func (job *Job) clearWorkspace() error {
	return os.RemoveAll(job.workspace)
}

func (job *Job) setupDownloadFiles() error {
	job.downloadFileMap = map[string]string{}
	job.remoteDownloadFiles = job.message.DownloadFiles()
	objects := job.flatten(job.remoteDownloadFiles)
	remoteUrls := []string{}
	for _, obj := range objects {
		switch obj.(type) {
		case string:
			remoteUrls = append(remoteUrls, obj.(string))
		default:
			log.Printf("Invalid download file URL: %v [%T]", obj, obj)
		}
	}
	for _, remote_url := range remoteUrls {
		url, err := url.Parse(remote_url)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remote_url, err)
			return err
		}
		urlstr := fmt.Sprintf("gs://%v%v", url.Host, url.Path)
		destPath := filepath.Join(job.downloads_dir, url.Host, url.Path)
		job.downloadFileMap[urlstr] = destPath
	}
	job.localDownloadFiles = job.copyWithFileMap(job.remoteDownloadFiles)
	return nil
}

func (job *Job) copyWithFileMap(obj interface{}) interface{} {
	switch obj.(type) {
	case map[string]interface{}:
		result := map[string]interface{}{}
		for k, v := range obj.(map[string]interface{}) {
			result[k] = job.copyWithFileMap(v)
		}
		return result
	case []interface{}:
		result := []interface{}{}
		for _, v := range obj.([]interface{}) {
			result = append(result, job.copyWithFileMap(v))
		}
		return result
	case string:
		return job.downloadFileMap[obj.(string)]
	default:
		return obj
	}
}

func (job *Job) buildVariable() *Variable {
	return &Variable{
		data: map[string]interface{}{
			"workspace":             job.workspace,
			"downloads_dir":         job.downloads_dir,
			"uploads_dir":           job.uploads_dir,
			"download_files":        job.localDownloadFiles,
			"local_download_files":  job.localDownloadFiles,
			"remote_download_files": job.remoteDownloadFiles,
			"attrs":                 job.message.raw.Message.Attributes,
			"attributes":            job.message.raw.Message.Attributes,
			"data":                  job.message.raw.Message.Data,
		},
	}
}

func (job *Job) build() error {
	v := job.buildVariable()

	values, err := job.extract(v, job.config.Template)
	if err != nil {
		return err
	}
	if len(job.config.Options) > 0 {
		key := strings.Join(values, " ")
		t := job.config.Options[key]
		if t == nil {
			t = job.config.Options["default"]
		}
		if t != nil {
			values, err = job.extract(v, t)
			if err != nil {
				return err
			}
		}
	}
	job.cmd = exec.Command(values[0], values[1:]...)
	return nil
}

func (job *Job) extract(v *Variable, values []string) ([]string, error) {
	result := []string{}
	for _, src := range values {
		extracted, err := v.expand(src)
		if err != nil {
			return nil, err
		}
		vals := strings.Split(extracted, v.separator)
		for _, val := range vals {
			result = append(result, val)
		}
	}
	return result, nil
}

func (job *Job) downloadFiles() error {
	for remoteURL, destPath := range job.downloadFileMap {
		url, err := url.Parse(remoteURL)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remoteURL, err)
			return err
		}

		dir := path.Dir(destPath)
		err = os.MkdirAll(dir, 0700)
		if err != nil {
			return err
		}

		err = job.storage.Download(url.Host, url.Path[1:], destPath)
		if err != nil {
			return err
		}
	}
	return nil
}

func (job *Job) execute() error {
	var out bytes.Buffer
	job.cmd.Stdout = &out
	job.cmd.Stderr = &out
	log.Printf("EXECUTE running: %v\n", job.cmd)
	err := job.cmd.Run()
	if err != nil {
		log.Printf("Command Error: cmd: %v cause of %v\n%v\n", job.cmd, err, out.String())
		return err
	}
	return nil
}

func (job *Job) uploadFiles() error {
	localPaths, err := job.listFiles(job.uploads_dir)
	if err != nil {
		return err
	}
	for _, localPath := range localPaths {
		relPath, err := filepath.Rel(job.uploads_dir, localPath)
		if err != nil {
			log.Fatalf("Error getting relative path of %v: %v\n", localPath, err)
			return err
		}
		sep := string([]rune{os.PathSeparator})
		parts := strings.Split(relPath, sep)
		bucket := parts[0]
		object := strings.Join(parts[1:], sep)
		err = job.storage.Upload(bucket, object, localPath)
		if err != nil {
			log.Fatalf("Error uploading %v to gs://%v/%v: %v\n", localPath, bucket, object, err)
			return err
		}
	}
	return nil
}

func (job *Job) listFiles(dir string) ([]string, error) {
	result := []string{}
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			result = append(result, path)
		}
		return nil
	})
	if err != nil {
		log.Fatalf("Error listing upload files: %v\n", err)
		return nil, err
	}
	return result, nil
}

func (job *Job) flatten(obj interface{}) []interface{} {
	// Support only unmarshalled object from JSON
	// See https://golang.org/pkg/encoding/json/#Unmarshal also
	switch obj.(type) {
	case []interface{}:
		res := []interface{}{}
		for _, i := range obj.([]interface{}) {
			switch i.(type) {
			case bool, float64, string, nil:
				res = append(res, i)
			default:
				for _, j := range job.flatten(i) {
					res = append(res, j)
				}
			}
		}
		return res
	case map[string]interface{}:
		values := []interface{}{}
		for _, val := range obj.(map[string]interface{}) {
			values = append(values, val)
		}
		return job.flatten(values)
	default:
		return []interface{}{obj}
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Vadim Kravcenko
//
// 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 gojenkins

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/url"
	"strconv"
	"fmt"
)

type Job struct {
	Raw     *jobResponse
	Jenkins *Jenkins
	Base    string
}

type jobBuild struct {
	Number int64
	URL    string
}

type job struct {
	Name  string `json:"name"`
	Url   string `json:"url"`
	Color string `json:"color"`
}

type parameterDefinition struct {
	DefaultParameterValue struct {
		Name  string `json:"name"`
		Value bool   `json:"value"`
	} `json:"defaultParameterValue"`
	Description string `json:"description"`
	Name        string `json:"name"`
	Type        string `json:"type"`
}

type jobResponse struct {
	Actions            []generalObj
	Buildable          bool `json:"buildable"`
	Builds             []jobBuild
	Color              string      `json:"color"`
	ConcurrentBuild    bool        `json:"concurrentBuild"`
	Description        string      `json:"description"`
	DisplayName        string      `json:"displayName"`
	DisplayNameOrNull  interface{} `json:"displayNameOrNull"`
	DownstreamProjects []job       `json:"downstreamProjects"`
	FirstBuild         jobBuild
	HealthReport       []struct {
		Description   string `json:"description"`
		IconClassName string `json:"iconClassName"`
		IconUrl       string `json:"iconUrl"`
		Score         int64  `json:"score"`
	} `json:"healthReport"`
	InQueue               bool     `json:"inQueue"`
	KeepDependencies      bool     `json:"keepDependencies"`
	LastBuild             jobBuild `json:"lastBuild"`
	LastCompletedBuild    jobBuild `json:"lastCompletedBuild"`
	LastFailedBuild       jobBuild `json:"lastFailedBuild"`
	LastStableBuild       jobBuild `json:"lastStableBuild"`
	LastSuccessfulBuild   jobBuild `json:"lastSuccessfulBuild"`
	LastUnstableBuild     jobBuild `json:"lastUnstableBuild"`
	LastUnsuccessfulBuild jobBuild `json:"lastUnsuccessfulBuild"`
	Name                  string   `json:"name"`
	NextBuildNumber       int64    `json:"nextBuildNumber"`
	Property              []struct {
		ParameterDefinitions []parameterDefinition `json:"parameterDefinitions"`
	} `json:"property"`
	QueueItem        interface{} `json:"queueItem"`
	Scm              struct{}    `json:"scm"`
	UpstreamProjects []job       `json:"upstreamProjects"`
	URL              string      `json:"url"`
}

func (j *Job) GetName() string {
	return j.Raw.Name
}

func (j *Job) GetDescription() string {
	return j.Raw.Description
}

func (j *Job) GetDetails() *jobResponse {
	return j.Raw
}

func (j *Job) GetBuild(id int64) (*Build, error) {
	build := Build{Jenkins: j.Jenkins, Job: j, Raw: new(buildResponse), Depth: 1, Base: "/job/" + j.GetName() + "/" + strconv.FormatInt(id, 10)}
	status, err := build.Poll()
	if err != nil {
		return nil, err
	}
	if status == 200 {
		return &build, nil
	}
	return nil, errors.New(strconv.Itoa(status))
}

func (j *Job) getBuildByType(buildType string) (*Build, error) {
	allowed := map[string]jobBuild{
		"lastStableBuild":     j.Raw.LastStableBuild,
		"lastSuccessfulBuild": j.Raw.LastSuccessfulBuild,
		"lastBuild":           j.Raw.LastBuild,
		"lastCompletedBuild":  j.Raw.LastCompletedBuild,
		"firstBuild":          j.Raw.FirstBuild,
		"lastFailedBuild":     j.Raw.LastFailedBuild,
	}
	number := ""
	if val, ok := allowed[buildType]; ok {
		number = strconv.FormatInt(val.Number, 10)
	} else {
		panic("No Such Build")
	}
	build := Build{
		Jenkins: j.Jenkins,
		Depth:   1,
		Job:     j,
		Raw:     new(buildResponse),
		Base:    "/job/" + j.GetName() + "/" + number}
	status, err := build.Poll()
	if err != nil {
		return nil, err
	}
	if status == 200 {
		return &build, nil
	}
	return nil, errors.New(strconv.Itoa(status))
}

func (j *Job) GetLastSuccessfulBuild() (*Build, error) {
	return j.getBuildByType("lastSuccessfulBuild")
}

func (j *Job) GetFirstBuild() (*Build, error) {
	return j.getBuildByType("firstBuild")
}

func (j *Job) GetLastBuild() (*Build, error) {
	return j.getBuildByType("lastBuild")
}

func (j *Job) GetLastStableBuild() (*Build, error) {
	return j.getBuildByType("lastStableBuild")
}

func (j *Job) GetLastFailedBuild() (*Build, error) {
	return j.getBuildByType("lastFailedBuild")
}

func (j *Job) GetLastCompletedBuild() (*Build, error) {
	return j.getBuildByType("lastCompletedBuild")
}

// Returns All Builds with Number and URL
func (j *Job) GetAllBuildIds() ([]jobBuild, error) {
	var buildsResp struct {
		Builds []jobBuild `json:"allBuilds"`
	}
	_, err := j.Jenkins.Requester.GetJSON(j.Base, &buildsResp, map[string]string{"tree": "allBuilds[number,url]"})
	if err != nil {
		return nil, err
	}
	return buildsResp.Builds, nil
}

func (j *Job) GetUpstreamJobsMetadata() []job {
	return j.Raw.UpstreamProjects
}

func (j *Job) GetDownstreamJobsMetadata() []job {
	return j.Raw.DownstreamProjects
}

func (j *Job) GetUpstreamJobs() ([]*Job, error) {
	jobs := make([]*Job, len(j.Raw.UpstreamProjects))
	for i, job := range j.Raw.UpstreamProjects {
		ji, err := j.Jenkins.GetJob(job.Name)
		if err != nil {
			return nil, err
		}
		jobs[i] = ji
	}
	return jobs, nil
}

func (j *Job) GetDownstreamJobs() ([]*Job, error) {
	jobs := make([]*Job, len(j.Raw.DownstreamProjects))
	for i, job := range j.Raw.DownstreamProjects {
		ji, err := j.Jenkins.GetJob(job.Name)
		if err != nil {
			return nil, err
		}
		jobs[i] = ji
	}
	return jobs, nil
}

func (j *Job) Enable() (bool, error) {
	resp, err := j.Jenkins.Requester.Post(j.Base+"/enable", nil, nil, nil)
	if err != nil {
		return false, err
	}
	if resp.StatusCode != 200 {
		return false, errors.New(strconv.Itoa(resp.StatusCode))
	}
	return true, nil
}

func (j *Job) Disable() (bool, error) {
	resp, err := j.Jenkins.Requester.Post(j.Base+"/disable", nil, nil, nil)
	if err != nil {
		return false, err
	}
	if resp.StatusCode != 200 {
		return false, errors.New(strconv.Itoa(resp.StatusCode))
	}
	return true, nil
}

func (j *Job) Delete() (bool, error) {
	resp, err := j.Jenkins.Requester.Post(j.Base+"/doDelete", nil, nil, nil)
	if err != nil {
		return false, err
	}
	if resp.StatusCode != 200 {
		return false, errors.New(strconv.Itoa(resp.StatusCode))
	}
	return true, nil
}

func (j *Job) Rename(name string) (bool, error) {
	data := url.Values{}
	data.Set("newName", name)
	_, err := j.Jenkins.Requester.Post(j.Base+"/doRename", bytes.NewBufferString(data.Encode()), nil, nil)
	if err != nil {
		return false, err
	}
	return true, nil
}

func (j *Job) Create(config string, qr ...interface{}) (*Job, error) {
	var querystring map[string]string
	if len(qr) > 0 {
		querystring = qr[0].(map[string]string)
	}
	resp, err := j.Jenkins.Requester.PostXML("/createItem", config, j.Raw, querystring)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == 200 {
		j.Poll()
		return j, nil
	}
	return nil, errors.New(strconv.Itoa(resp.StatusCode))
}

func (j *Job) Copy(from string, newName string) (*Job, error) {
	qr := map[string]string{"name": newName, "from": from, "mode": "copy"}
	resp, err := j.Jenkins.Requester.Post("/createItem", nil, nil, qr)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == 200 {
		return j, nil
	}
	return nil, errors.New(strconv.Itoa(resp.StatusCode))
}

func (j *Job) GetConfig() (string, error) {
	var data string
	_, err := j.Jenkins.Requester.GetXML(j.Base+"/config.xml", &data, nil)
	if err != nil {
		return "", err
	}
	return data, nil
}

func (j *Job) GetParameters() ([]parameterDefinition, error) {
	_, err := j.Poll()
	if err != nil {
		return nil, err
	}
	var parameters []parameterDefinition
	for _, property := range j.Raw.Property {
		for _, param := range property.ParameterDefinitions {
			parameters = append(parameters, param)
		}
	}
	return parameters, nil
}

func (j *Job) IsQueued() (bool, error) {
	if _, err := j.Poll(); err != nil {
		return false, err
	}
	return j.Raw.InQueue, nil
}

func (j *Job) IsRunning() (bool, error) {
	if _, err := j.Poll(); err != nil {
		return false, err
	}
	lastBuild, err := j.GetLastBuild()
	if err != nil {
		return false, err
	}
	return lastBuild.IsRunning(), nil
}

func (j *Job) IsEnabled() (bool, error) {
	if _, err := j.Poll(); err != nil {
		return false, err
	}
	return j.Raw.Color != "disabled", nil
}

func (j *Job) HasQueuedBuild() {
	panic("Not Implemented yet")
}

func (j *Job) InvokeSimple(params map[string]string) (bool, error) {
	isQueued, err := j.IsQueued()
	if err != nil {
		return false, err
	}
	if isQueued {
		Error.Printf("%s is already running", j.GetName())
		return false, nil
	}

	endpoint := "/build"
	parameters, err := j.GetParameters()
	if err != nil {
		return false, err
	}
	if len(parameters) > 0 {
		endpoint = "/buildWithParameters"
	}
	data := url.Values{}
	for k, v := range params {
		data.Set(k, v)
	}
	resp, err := j.Jenkins.Requester.Post(j.Base+endpoint, bytes.NewBufferString(data.Encode()), nil, nil)
	if err != nil {
		return false, err
	}
	if resp.StatusCode != 200 && resp.StatusCode != 201 {
		return false, errors.New("Could not invoke job " + j.GetName())
	}
	return true, nil
}

func (j *Job) Invoke(files []string, skipIfRunning bool, params map[string]string, cause string, securityToken string) (bool, error) {
	isQueued, err := j.IsQueued()
	if err != nil {
		return false, err
	}
	if isQueued {
		Error.Printf("%s is already running", j.GetName())
		return false, nil
	}
	isRunning, err := j.IsRunning()
	if err != nil {
		return false, err
	}
	if isRunning && skipIfRunning {
		return false, errors.New(fmt.Sprintf("%s Will not request new build because %s is already running", j.GetName()))
	}

	base := "/build"

	// If parameters are specified - url is /builWithParameters
	if params != nil {
		base = "/buildWithParameters"
	} else {
		params = make(map[string]string)
	}

	// If files are specified - url is /build
	if files != nil {
		base = "/build"
	}
	reqParams := map[string]string{}
	buildParams := map[string]string{}
	if securityToken != "" {
		reqParams["token"] = securityToken
	}

	buildParams["json"] = string(makeJson(params))
	b, _ := json.Marshal(buildParams)
	resp, err := j.Jenkins.Requester.PostFiles(j.Base+base, bytes.NewBuffer(b), nil, reqParams, files)
	if err != nil {
		return false, err
	}
	if resp.StatusCode == 200 || resp.StatusCode == 201 {
		return true, nil
	}
	return false, errors.New(strconv.Itoa(resp.StatusCode))
}

func (j *Job) Poll() (int, error) {
	_, err := j.Jenkins.Requester.GetJSON(j.Base, j.Raw, nil)
	if err != nil {
		return 0, err
	}
	return j.Jenkins.Requester.LastResponse.StatusCode, nil
}
<commit_msg>fix(job.go): change method to PostXML on Enable, Disable, Delete<commit_after>// Copyright 2015 Vadim Kravcenko
//
// 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 gojenkins

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/url"
	"strconv"
	"fmt"
)

type Job struct {
	Raw     *jobResponse
	Jenkins *Jenkins
	Base    string
}

type jobBuild struct {
	Number int64
	URL    string
}

type job struct {
	Name  string `json:"name"`
	Url   string `json:"url"`
	Color string `json:"color"`
}

type parameterDefinition struct {
	DefaultParameterValue struct {
		Name  string `json:"name"`
		Value bool   `json:"value"`
	} `json:"defaultParameterValue"`
	Description string `json:"description"`
	Name        string `json:"name"`
	Type        string `json:"type"`
}

type jobResponse struct {
	Actions            []generalObj
	Buildable          bool `json:"buildable"`
	Builds             []jobBuild
	Color              string      `json:"color"`
	ConcurrentBuild    bool        `json:"concurrentBuild"`
	Description        string      `json:"description"`
	DisplayName        string      `json:"displayName"`
	DisplayNameOrNull  interface{} `json:"displayNameOrNull"`
	DownstreamProjects []job       `json:"downstreamProjects"`
	FirstBuild         jobBuild
	HealthReport       []struct {
		Description   string `json:"description"`
		IconClassName string `json:"iconClassName"`
		IconUrl       string `json:"iconUrl"`
		Score         int64  `json:"score"`
	} `json:"healthReport"`
	InQueue               bool     `json:"inQueue"`
	KeepDependencies      bool     `json:"keepDependencies"`
	LastBuild             jobBuild `json:"lastBuild"`
	LastCompletedBuild    jobBuild `json:"lastCompletedBuild"`
	LastFailedBuild       jobBuild `json:"lastFailedBuild"`
	LastStableBuild       jobBuild `json:"lastStableBuild"`
	LastSuccessfulBuild   jobBuild `json:"lastSuccessfulBuild"`
	LastUnstableBuild     jobBuild `json:"lastUnstableBuild"`
	LastUnsuccessfulBuild jobBuild `json:"lastUnsuccessfulBuild"`
	Name                  string   `json:"name"`
	NextBuildNumber       int64    `json:"nextBuildNumber"`
	Property              []struct {
		ParameterDefinitions []parameterDefinition `json:"parameterDefinitions"`
	} `json:"property"`
	QueueItem        interface{} `json:"queueItem"`
	Scm              struct{}    `json:"scm"`
	UpstreamProjects []job       `json:"upstreamProjects"`
	URL              string      `json:"url"`
}

func (j *Job) GetName() string {
	return j.Raw.Name
}

func (j *Job) GetDescription() string {
	return j.Raw.Description
}

func (j *Job) GetDetails() *jobResponse {
	return j.Raw
}

func (j *Job) GetBuild(id int64) (*Build, error) {
	build := Build{Jenkins: j.Jenkins, Job: j, Raw: new(buildResponse), Depth: 1, Base: "/job/" + j.GetName() + "/" + strconv.FormatInt(id, 10)}
	status, err := build.Poll()
	if err != nil {
		return nil, err
	}
	if status == 200 {
		return &build, nil
	}
	return nil, errors.New(strconv.Itoa(status))
}

func (j *Job) getBuildByType(buildType string) (*Build, error) {
	allowed := map[string]jobBuild{
		"lastStableBuild":     j.Raw.LastStableBuild,
		"lastSuccessfulBuild": j.Raw.LastSuccessfulBuild,
		"lastBuild":           j.Raw.LastBuild,
		"lastCompletedBuild":  j.Raw.LastCompletedBuild,
		"firstBuild":          j.Raw.FirstBuild,
		"lastFailedBuild":     j.Raw.LastFailedBuild,
	}
	number := ""
	if val, ok := allowed[buildType]; ok {
		number = strconv.FormatInt(val.Number, 10)
	} else {
		panic("No Such Build")
	}
	build := Build{
		Jenkins: j.Jenkins,
		Depth:   1,
		Job:     j,
		Raw:     new(buildResponse),
		Base:    "/job/" + j.GetName() + "/" + number}
	status, err := build.Poll()
	if err != nil {
		return nil, err
	}
	if status == 200 {
		return &build, nil
	}
	return nil, errors.New(strconv.Itoa(status))
}

func (j *Job) GetLastSuccessfulBuild() (*Build, error) {
	return j.getBuildByType("lastSuccessfulBuild")
}

func (j *Job) GetFirstBuild() (*Build, error) {
	return j.getBuildByType("firstBuild")
}

func (j *Job) GetLastBuild() (*Build, error) {
	return j.getBuildByType("lastBuild")
}

func (j *Job) GetLastStableBuild() (*Build, error) {
	return j.getBuildByType("lastStableBuild")
}

func (j *Job) GetLastFailedBuild() (*Build, error) {
	return j.getBuildByType("lastFailedBuild")
}

func (j *Job) GetLastCompletedBuild() (*Build, error) {
	return j.getBuildByType("lastCompletedBuild")
}

// Returns All Builds with Number and URL
func (j *Job) GetAllBuildIds() ([]jobBuild, error) {
	var buildsResp struct {
		Builds []jobBuild `json:"allBuilds"`
	}
	_, err := j.Jenkins.Requester.GetJSON(j.Base, &buildsResp, map[string]string{"tree": "allBuilds[number,url]"})
	if err != nil {
		return nil, err
	}
	return buildsResp.Builds, nil
}

func (j *Job) GetUpstreamJobsMetadata() []job {
	return j.Raw.UpstreamProjects
}

func (j *Job) GetDownstreamJobsMetadata() []job {
	return j.Raw.DownstreamProjects
}

func (j *Job) GetUpstreamJobs() ([]*Job, error) {
	jobs := make([]*Job, len(j.Raw.UpstreamProjects))
	for i, job := range j.Raw.UpstreamProjects {
		ji, err := j.Jenkins.GetJob(job.Name)
		if err != nil {
			return nil, err
		}
		jobs[i] = ji
	}
	return jobs, nil
}

func (j *Job) GetDownstreamJobs() ([]*Job, error) {
	jobs := make([]*Job, len(j.Raw.DownstreamProjects))
	for i, job := range j.Raw.DownstreamProjects {
		ji, err := j.Jenkins.GetJob(job.Name)
		if err != nil {
			return nil, err
		}
		jobs[i] = ji
	}
	return jobs, nil
}

func (j *Job) Enable() (bool, error) {
	resp, err := j.Jenkins.Requester.PostXML(j.Base+"/enable", nil, nil, nil)
	if err != nil {
		return false, err
	}
	if resp.StatusCode != 200 {
		return false, errors.New(strconv.Itoa(resp.StatusCode))
	}
	return true, nil
}

func (j *Job) Disable() (bool, error) {
	resp, err := j.Jenkins.Requester.PostXML(j.Base+"/disable", nil, nil, nil)
	if err != nil {
		return false, err
	}
	if resp.StatusCode != 200 {
		return false, errors.New(strconv.Itoa(resp.StatusCode))
	}
	return true, nil
}

func (j *Job) Delete() (bool, error) {
	resp, err := j.Jenkins.Requester.PostXML(j.Base+"/doDelete", nil, nil, nil)
	if err != nil {
		return false, err
	}
	if resp.StatusCode != 200 {
		return false, errors.New(strconv.Itoa(resp.StatusCode))
	}
	return true, nil
}

func (j *Job) Rename(name string) (bool, error) {
	data := url.Values{}
	data.Set("newName", name)
	_, err := j.Jenkins.Requester.Post(j.Base+"/doRename", bytes.NewBufferString(data.Encode()), nil, nil)
	if err != nil {
		return false, err
	}
	return true, nil
}

func (j *Job) Create(config string, qr ...interface{}) (*Job, error) {
	var querystring map[string]string
	if len(qr) > 0 {
		querystring = qr[0].(map[string]string)
	}
	resp, err := j.Jenkins.Requester.PostXML("/createItem", config, j.Raw, querystring)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == 200 {
		j.Poll()
		return j, nil
	}
	return nil, errors.New(strconv.Itoa(resp.StatusCode))
}

func (j *Job) Copy(from string, newName string) (*Job, error) {
	qr := map[string]string{"name": newName, "from": from, "mode": "copy"}
	resp, err := j.Jenkins.Requester.Post("/createItem", nil, nil, qr)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode == 200 {
		return j, nil
	}
	return nil, errors.New(strconv.Itoa(resp.StatusCode))
}

func (j *Job) GetConfig() (string, error) {
	var data string
	_, err := j.Jenkins.Requester.GetXML(j.Base+"/config.xml", &data, nil)
	if err != nil {
		return "", err
	}
	return data, nil
}

func (j *Job) GetParameters() ([]parameterDefinition, error) {
	_, err := j.Poll()
	if err != nil {
		return nil, err
	}
	var parameters []parameterDefinition
	for _, property := range j.Raw.Property {
		for _, param := range property.ParameterDefinitions {
			parameters = append(parameters, param)
		}
	}
	return parameters, nil
}

func (j *Job) IsQueued() (bool, error) {
	if _, err := j.Poll(); err != nil {
		return false, err
	}
	return j.Raw.InQueue, nil
}

func (j *Job) IsRunning() (bool, error) {
	if _, err := j.Poll(); err != nil {
		return false, err
	}
	lastBuild, err := j.GetLastBuild()
	if err != nil {
		return false, err
	}
	return lastBuild.IsRunning(), nil
}

func (j *Job) IsEnabled() (bool, error) {
	if _, err := j.Poll(); err != nil {
		return false, err
	}
	return j.Raw.Color != "disabled", nil
}

func (j *Job) HasQueuedBuild() {
	panic("Not Implemented yet")
}

func (j *Job) InvokeSimple(params map[string]string) (bool, error) {
	isQueued, err := j.IsQueued()
	if err != nil {
		return false, err
	}
	if isQueued {
		Error.Printf("%s is already running", j.GetName())
		return false, nil
	}

	endpoint := "/build"
	parameters, err := j.GetParameters()
	if err != nil {
		return false, err
	}
	if len(parameters) > 0 {
		endpoint = "/buildWithParameters"
	}
	data := url.Values{}
	for k, v := range params {
		data.Set(k, v)
	}
	resp, err := j.Jenkins.Requester.Post(j.Base+endpoint, bytes.NewBufferString(data.Encode()), nil, nil)
	if err != nil {
		return false, err
	}
	if resp.StatusCode != 200 && resp.StatusCode != 201 {
		return false, errors.New("Could not invoke job " + j.GetName())
	}
	return true, nil
}

func (j *Job) Invoke(files []string, skipIfRunning bool, params map[string]string, cause string, securityToken string) (bool, error) {
	isQueued, err := j.IsQueued()
	if err != nil {
		return false, err
	}
	if isQueued {
		Error.Printf("%s is already running", j.GetName())
		return false, nil
	}
	isRunning, err := j.IsRunning()
	if err != nil {
		return false, err
	}
	if isRunning && skipIfRunning {
		return false, errors.New(fmt.Sprintf("%s Will not request new build because %s is already running", j.GetName()))
	}

	base := "/build"

	// If parameters are specified - url is /builWithParameters
	if params != nil {
		base = "/buildWithParameters"
	} else {
		params = make(map[string]string)
	}

	// If files are specified - url is /build
	if files != nil {
		base = "/build"
	}
	reqParams := map[string]string{}
	buildParams := map[string]string{}
	if securityToken != "" {
		reqParams["token"] = securityToken
	}

	buildParams["json"] = string(makeJson(params))
	b, _ := json.Marshal(buildParams)
	resp, err := j.Jenkins.Requester.PostFiles(j.Base+base, bytes.NewBuffer(b), nil, reqParams, files)
	if err != nil {
		return false, err
	}
	if resp.StatusCode == 200 || resp.StatusCode == 201 {
		return true, nil
	}
	return false, errors.New(strconv.Itoa(resp.StatusCode))
}

func (j *Job) Poll() (int, error) {
	_, err := j.Jenkins.Requester.GetJSON(j.Base, j.Raw, nil)
	if err != nil {
		return 0, err
	}
	return j.Jenkins.Requester.LastResponse.StatusCode, nil
}
<|endoftext|>


<commit_before>package main

import (
    "github.com/nsf/termbox-go"
    "os"
    "log"
    "strconv"
    "unicode"
    "errors"
)

var colors map[rune]termbox.Attribute = map[rune]termbox.Attribute{
    'd': termbox.ColorDefault,
    'k': termbox.ColorBlack,    'K': termbox.ColorBlack | termbox.AttrBold,
    'r': termbox.ColorRed,      'R': termbox.ColorRed | termbox.AttrBold,
    'g': termbox.ColorGreen,    'G': termbox.ColorGreen | termbox.AttrBold,
    'y': termbox.ColorYellow,   'Y': termbox.ColorYellow | termbox.AttrBold,
    'b': termbox.ColorBlue,     'B': termbox.ColorBlue | termbox.AttrBold,
    'm': termbox.ColorMagenta,  'M': termbox.ColorMagenta | termbox.AttrBold,
    'c': termbox.ColorCyan,     'C': termbox.ColorCyan | termbox.AttrBold,
    'w': termbox.ColorWhite,    'W': termbox.ColorWhite | termbox.AttrBold,
}

type Tabber interface {
    Name() string
    Status() string
    HandleKeyEvent(*termbox.Event) bool
    Draw()
    Query(string)
}

type Apollo struct {
    running bool
    width int
    height int
    events chan termbox.Event
    c *Configuration
    d *Database
    currentTab int
    tabs []Tabber
    input []rune
    inputCursor int
    inputActive bool
}

func newApollo() *Apollo {
    err := os.Mkdir(os.Getenv("HOME") + "/.config/apollo", 0755)
    if err != nil {
        log.Print(err)
    }

    width, height := termbox.Size()
    var tabs []Tabber

    a := &Apollo{
        running: true,
        width: width,
        height: height,
        events: make(chan termbox.Event, 20),
        c: newConfiguration(),
        d: newDatabase(),
        tabs: tabs,
    }

    a.tabs = append(a.tabs, Tabber(newStatusTab(a)))

    if a.c.get("movies_tab") == "true" {
        a.tabs = append(a.tabs, Tabber(newMoviesTab(a)))
    }

    if a.c.get("series_tab") == "true" {
        a.tabs = append(a.tabs, Tabber(newSeriesTab(a)))
    }

    if a.c.get("anime_tab") == "true" {
        a.tabs = append(a.tabs, Tabber(newAnimeTab(a)))
    }

    if a.c.get("games_tab") == "true" {
        a.tabs = append(a.tabs, Tabber(newGamesTab(a)))
    }

    if a.c.get("books_tab") == "true" {
        a.tabs = append(a.tabs, Tabber(newBooksTab(a)))
    }

    a.printWelcome()

    return a
}

func (a *Apollo) handleEvent(ev *termbox.Event) error {
    switch ev.Type {
    case termbox.EventKey:
        a.handleKeyEvent(ev)
    case termbox.EventResize:
        a.width, a.height = termbox.Size()
    case termbox.EventError:
        return ev.Err
    }

    return nil
}

func (a *Apollo) handleKeyEvent(ev *termbox.Event) {
    if !a.inputActive && ev.Mod != termbox.ModAlt {
        handled := a.tabs[a.currentTab].HandleKeyEvent(ev)
        if handled {
            return
        }
    }

    switch ev.Key {
    case termbox.KeyCtrlC:
        a.running = false
    case termbox.KeyEnter:
        if len(a.input) > 0 {
            if a.input[0] == '/' {
                a.handleCommand()
            } else if a.currentTab != 0 {
                a.tabs[a.currentTab].Query(string(a.input))
            }
            a.input = a.input[:0]
            a.inputCursor = 0
        } else {
            a.inputActive = !a.inputActive
        }
    default:
        if ev.Mod == termbox.ModAlt {
            indexes := map[rune]int{'1': 1, '2': 2, '3': 3,
                                    '4': 4, '5': 5, '6': 6,
                                    '7': 7, '8': 8, '9': 9,}
            if i, exist := indexes[ev.Ch]; exist {
                if len(a.tabs) > i - 1 {
                    a.currentTab = i - 1
                }
            }
        } else {
            if unicode.IsPrint(ev.Ch) && a.inputActive {
                a.input = append(a.input, ' ')
                copy(a.input[a.inputCursor+1:], a.input[a.inputCursor:])
                a.input[a.inputCursor] = ev.Ch
                a.inputCursor++
            }
        }
    }

    if a.inputActive {
        switch ev.Key {
        case termbox.KeyBackspace, termbox.KeyBackspace2:
            if a.inputCursor > 0 {
                a.input = append(a.input[:a.inputCursor-1], a.input[a.inputCursor:]...)
                a.inputCursor--
            }
        case termbox.KeySpace:
            a.input = append(a.input, ' ')
            copy(a.input[a.inputCursor+1:], a.input[a.inputCursor:])
            a.input[a.inputCursor] = ' '
            a.inputCursor++
        case termbox.KeyArrowLeft:
            a.inputCursor--
            if a.inputCursor < 0 {
                a.inputCursor = 0
            }
        case termbox.KeyArrowRight:
            a.inputCursor++
            if a.inputCursor > len(a.input) {
                a.inputCursor = len(a.input)
            }
        }
    }
}

func (a *Apollo) drawStatusBars() {
    for i := 0; i < a.width; i++ {
        termbox.SetCell(i, 0, ' ', colors['d'], colors['k'])
        termbox.SetCell(i, a.height - 2, ' ', colors['d'], colors['k'])
    }
}

func (a *Apollo) drawTopStatus() {
    runes := []rune(version + " - " + a.tabs[a.currentTab].Status())
    for i := 0; i < len(runes); i++ {
        termbox.SetCell(i, 0, runes[i], colors['W'], colors['k'])
    }
}

func (a *Apollo) drawBottomStatus() {
    var str string
    for i := range a.tabs {
        if i == a.currentTab {
            str += "{" + strconv.Itoa(i+1) + "." + a.tabs[i].Name() + "} "
        } else {
            str += strconv.Itoa(i+1) + "." + a.tabs[i].Name() + " "
        }
    }

    fg := colors['w']
    x := 0
    runes := []rune(str)
    for i := 0; i < len(runes); i++ {
        if runes[i] == '{' {
            fg = colors['W']
            i++
        } else if runes[i] == '}' {
            fg = colors['w']
            i++
        }
        termbox.SetCell(x, a.height - 2, runes[i], fg, colors['k'])
        x++
    }
}

func (a *Apollo) drawInput() {
    if len(a.input) < a.width {
        for i := 0; i < len(a.input); i++ {
            termbox.SetCell(i, a.height - 1, a.input[i], colors['w'], colors['d'])
        }
    } else {
        offset := len(a.input) - a.width + 1
        for i := 0; i < a.width - 1; i++ {
            termbox.SetCell(i, a.height - 1, a.input[i + offset], colors['w'], colors['d'])
        }
    }

    if a.inputActive {
        termbox.SetCursor(a.inputCursor, a.height - 1)
    } else {
        termbox.HideCursor()
    }
}

func (a *Apollo) draw() {
    termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)

    a.tabs[a.currentTab].Draw()

    a.drawStatusBars()
    a.drawTopStatus()
    a.drawBottomStatus()
    a.drawInput()

    termbox.Flush()
}

func (a *Apollo) log(str string) {
    a.tabs[0].Query(str)
}

func (a *Apollo) logError(str string) {
    a.log("{r}│ ERROR: {d}" + str)
}

func (a *Apollo) logDebug(str string) {
    if a.c.get("debug") == "true" {
        log.Print(str)
    }
}

func (a *Apollo) openTab(name string) error {
    for i := range a.tabs {
        if a.tabs[i].Name() == name {
            a.currentTab = i
            return nil
        }
    }

    switch name {
    case "movies":
        a.tabs = append(a.tabs, Tabber(newMoviesTab(a)))
    case "series":
        a.tabs = append(a.tabs, Tabber(newSeriesTab(a)))
    case "anime":
        a.tabs = append(a.tabs, Tabber(newAnimeTab(a)))
    case "games":
        a.tabs = append(a.tabs, Tabber(newGamesTab(a)))
    case "books":
        a.tabs = append(a.tabs, Tabber(newBooksTab(a)))
    default:
        return errors.New("term: tab does not exist")
    }

    a.currentTab = len(a.tabs) - 1
    return nil
}

func (a *Apollo) closeCurrentTab() error {
    if a.tabs[a.currentTab].Name() == "(status)" {
        return errors.New("term: cannot close status tab")
    }

    a.tabs = append(a.tabs[:a.currentTab], a.tabs[a.currentTab+1:]...)
    a.currentTab--
    return nil
}
<commit_msg>Errors now gets properly logged<commit_after>package main

import (
    "github.com/nsf/termbox-go"
    "os"
    "log"
    "strconv"
    "unicode"
    "errors"
)

var colors map[rune]termbox.Attribute = map[rune]termbox.Attribute{
    'd': termbox.ColorDefault,
    'k': termbox.ColorBlack,    'K': termbox.ColorBlack | termbox.AttrBold,
    'r': termbox.ColorRed,      'R': termbox.ColorRed | termbox.AttrBold,
    'g': termbox.ColorGreen,    'G': termbox.ColorGreen | termbox.AttrBold,
    'y': termbox.ColorYellow,   'Y': termbox.ColorYellow | termbox.AttrBold,
    'b': termbox.ColorBlue,     'B': termbox.ColorBlue | termbox.AttrBold,
    'm': termbox.ColorMagenta,  'M': termbox.ColorMagenta | termbox.AttrBold,
    'c': termbox.ColorCyan,     'C': termbox.ColorCyan | termbox.AttrBold,
    'w': termbox.ColorWhite,    'W': termbox.ColorWhite | termbox.AttrBold,
}

type Tabber interface {
    Name() string
    Status() string
    HandleKeyEvent(*termbox.Event) bool
    Draw()
    Query(string)
}

type Apollo struct {
    running bool
    width int
    height int
    events chan termbox.Event
    c *Configuration
    d *Database
    currentTab int
    tabs []Tabber
    input []rune
    inputCursor int
    inputActive bool
}

func newApollo() *Apollo {
    err := os.Mkdir(os.Getenv("HOME") + "/.config/apollo", 0755)
    if err != nil {
        log.Print(err)
    }

    width, height := termbox.Size()
    var tabs []Tabber

    a := &Apollo{
        running: true,
        width: width,
        height: height,
        events: make(chan termbox.Event, 20),
        c: newConfiguration(),
        d: newDatabase(),
        tabs: tabs,
    }

    a.tabs = append(a.tabs, Tabber(newStatusTab(a)))

    if a.c.get("movies_tab") == "true" {
        a.tabs = append(a.tabs, Tabber(newMoviesTab(a)))
    }

    if a.c.get("series_tab") == "true" {
        a.tabs = append(a.tabs, Tabber(newSeriesTab(a)))
    }

    if a.c.get("anime_tab") == "true" {
        a.tabs = append(a.tabs, Tabber(newAnimeTab(a)))
    }

    if a.c.get("games_tab") == "true" {
        a.tabs = append(a.tabs, Tabber(newGamesTab(a)))
    }

    if a.c.get("books_tab") == "true" {
        a.tabs = append(a.tabs, Tabber(newBooksTab(a)))
    }

    a.printWelcome()

    return a
}

func (a *Apollo) handleEvent(ev *termbox.Event) error {
    switch ev.Type {
    case termbox.EventKey:
        a.handleKeyEvent(ev)
    case termbox.EventResize:
        a.width, a.height = termbox.Size()
    case termbox.EventError:
        return ev.Err
    }

    return nil
}

func (a *Apollo) handleKeyEvent(ev *termbox.Event) {
    if !a.inputActive && ev.Mod != termbox.ModAlt {
        handled := a.tabs[a.currentTab].HandleKeyEvent(ev)
        if handled {
            return
        }
    }

    switch ev.Key {
    case termbox.KeyCtrlC:
        a.running = false
    case termbox.KeyEnter:
        if len(a.input) > 0 {
            if a.input[0] == '/' {
                a.handleCommand()
            } else if a.currentTab != 0 {
                a.tabs[a.currentTab].Query(string(a.input))
            }
            a.input = a.input[:0]
            a.inputCursor = 0
        } else {
            a.inputActive = !a.inputActive
        }
    default:
        if ev.Mod == termbox.ModAlt {
            indexes := map[rune]int{'1': 1, '2': 2, '3': 3,
                                    '4': 4, '5': 5, '6': 6,
                                    '7': 7, '8': 8, '9': 9,}
            if i, exist := indexes[ev.Ch]; exist {
                if len(a.tabs) > i - 1 {
                    a.currentTab = i - 1
                }
            }
        } else {
            if unicode.IsPrint(ev.Ch) && a.inputActive {
                a.input = append(a.input, ' ')
                copy(a.input[a.inputCursor+1:], a.input[a.inputCursor:])
                a.input[a.inputCursor] = ev.Ch
                a.inputCursor++
            }
        }
    }

    if a.inputActive {
        switch ev.Key {
        case termbox.KeyBackspace, termbox.KeyBackspace2:
            if a.inputCursor > 0 {
                a.input = append(a.input[:a.inputCursor-1], a.input[a.inputCursor:]...)
                a.inputCursor--
            }
        case termbox.KeySpace:
            a.input = append(a.input, ' ')
            copy(a.input[a.inputCursor+1:], a.input[a.inputCursor:])
            a.input[a.inputCursor] = ' '
            a.inputCursor++
        case termbox.KeyArrowLeft:
            a.inputCursor--
            if a.inputCursor < 0 {
                a.inputCursor = 0
            }
        case termbox.KeyArrowRight:
            a.inputCursor++
            if a.inputCursor > len(a.input) {
                a.inputCursor = len(a.input)
            }
        }
    }
}

func (a *Apollo) drawStatusBars() {
    for i := 0; i < a.width; i++ {
        termbox.SetCell(i, 0, ' ', colors['d'], colors['k'])
        termbox.SetCell(i, a.height - 2, ' ', colors['d'], colors['k'])
    }
}

func (a *Apollo) drawTopStatus() {
    runes := []rune(version + " - " + a.tabs[a.currentTab].Status())
    for i := 0; i < len(runes); i++ {
        termbox.SetCell(i, 0, runes[i], colors['W'], colors['k'])
    }
}

func (a *Apollo) drawBottomStatus() {
    var str string
    for i := range a.tabs {
        if i == a.currentTab {
            str += "{" + strconv.Itoa(i+1) + "." + a.tabs[i].Name() + "} "
        } else {
            str += strconv.Itoa(i+1) + "." + a.tabs[i].Name() + " "
        }
    }

    fg := colors['w']
    x := 0
    runes := []rune(str)
    for i := 0; i < len(runes); i++ {
        if runes[i] == '{' {
            fg = colors['W']
            i++
        } else if runes[i] == '}' {
            fg = colors['w']
            i++
        }
        termbox.SetCell(x, a.height - 2, runes[i], fg, colors['k'])
        x++
    }
}

func (a *Apollo) drawInput() {
    if len(a.input) < a.width {
        for i := 0; i < len(a.input); i++ {
            termbox.SetCell(i, a.height - 1, a.input[i], colors['w'], colors['d'])
        }
    } else {
        offset := len(a.input) - a.width + 1
        for i := 0; i < a.width - 1; i++ {
            termbox.SetCell(i, a.height - 1, a.input[i + offset], colors['w'], colors['d'])
        }
    }

    if a.inputActive {
        termbox.SetCursor(a.inputCursor, a.height - 1)
    } else {
        termbox.HideCursor()
    }
}

func (a *Apollo) draw() {
    termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)

    a.tabs[a.currentTab].Draw()

    a.drawStatusBars()
    a.drawTopStatus()
    a.drawBottomStatus()
    a.drawInput()

    termbox.Flush()
}

func (a *Apollo) log(str string) {
    a.tabs[0].Query(str)
}

func (a *Apollo) logError(str string) {
    a.log("{r}│ ERROR: {d}" + str)
    log.Print(str)
}

func (a *Apollo) logDebug(str string) {
    if a.c.get("debug") == "true" {
        log.Print(str)
    }
}

func (a *Apollo) openTab(name string) error {
    for i := range a.tabs {
        if a.tabs[i].Name() == name {
            a.currentTab = i
            return nil
        }
    }

    switch name {
    case "movies":
        a.tabs = append(a.tabs, Tabber(newMoviesTab(a)))
    case "series":
        a.tabs = append(a.tabs, Tabber(newSeriesTab(a)))
    case "anime":
        a.tabs = append(a.tabs, Tabber(newAnimeTab(a)))
    case "games":
        a.tabs = append(a.tabs, Tabber(newGamesTab(a)))
    case "books":
        a.tabs = append(a.tabs, Tabber(newBooksTab(a)))
    default:
        return errors.New("term: tab does not exist")
    }

    a.currentTab = len(a.tabs) - 1
    return nil
}

func (a *Apollo) closeCurrentTab() error {
    if a.tabs[a.currentTab].Name() == "(status)" {
        return errors.New("term: cannot close status tab")
    }

    a.tabs = append(a.tabs[:a.currentTab], a.tabs[a.currentTab+1:]...)
    a.currentTab--
    return nil
}
<|endoftext|>


<commit_before>package main

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

const TWSEURL string = "http://mis.tse.com.tw/"

//STOCKPATH = '/stock/api/getStockInfo.jsp?ex_ch=%(exchange)s_%(no)s.tw_%(date)s&json=1&delay=%(delay)s&_=%(timestamp)s'

type StockOption struct {
	no        string
	timestamp int64
	date      time.Time
}

func (stock StockOption) GenStockUrl() string {
	return fmt.Sprintf(
		"%sstock/api/getStockInfo.jsp?ex_ch=%s_%s.tw_%s&json=1&delay=0&_=%d",
		TWSEURL,
		"tse",
		stock.no,
		fmt.Sprintf(
			"%d%02d%02d",
			stock.date.Year(),
			int(stock.date.Month()),
			stock.date.Day(),
		),
		stock.timestamp,
	)
}

func (stock StockOption) GetData() (value stockBlob) {
	url := stock.GenStockUrl()
	resp, _ := http.Get(url)
	defer resp.Body.Close()
	json.NewDecoder(resp.Body).Decode(&value)
	return
}

type jsonBlob struct {
	Args    map[string]interface{}
	Headers map[string]interface{}
}

type QueryTimeBlob struct {
	sysTime           string
	sessionLatestTime int
	sysDate           string
}

type stockBlob struct {
	Rtcode    string
	UserDelay int
	Rtmessage string
	Referer   string
	MsgArray  []map[string]string
	QueryTime map[string]interface{}
}
<commit_msg>Tiny changed.<commit_after>package main

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

const TWSEURL string = "http://mis.tse.com.tw/"

//STOCKPATH = '/stock/api/getStockInfo.jsp?ex_ch=%(exchange)s_%(no)s.tw_%(date)s&json=1&delay=%(delay)s&_=%(timestamp)s'

type StockOption struct {
	no        string
	timestamp int64
	date      time.Time
}

type stockBlob struct {
	Rtcode    string
	UserDelay int
	Rtmessage string
	Referer   string
	MsgArray  []map[string]string
	QueryTime map[string]interface{}
}

func (stock StockOption) GenStockUrl() string {
	return fmt.Sprintf(
		"%sstock/api/getStockInfo.jsp?ex_ch=%s_%s.tw_%s&json=1&delay=0&_=%d",
		TWSEURL,
		"tse",
		stock.no,
		fmt.Sprintf(
			"%d%02d%02d",
			stock.date.Year(),
			int(stock.date.Month()),
			stock.date.Day(),
		),
		stock.timestamp,
	)
}

func (stock StockOption) GetData() (value stockBlob) {
	url := stock.GenStockUrl()
	resp, _ := http.Get(url)
	defer resp.Body.Close()
	json.NewDecoder(resp.Body).Decode(&value)
	return
}
<|endoftext|>