<commit_before>package app

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"log"
	randMath "math/rand"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/DanielRenne/GoCore/core/dbServices"
	"github.com/DanielRenne/GoCore/core/fileCache"
	"github.com/DanielRenne/GoCore/core/ginServer"
	"github.com/DanielRenne/GoCore/core/logger"
	"github.com/DanielRenne/GoCore/core/serverSettings"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

type WebSocketRemoval func(conn *WebSocketConnection)

type WebSocketConnection struct {
	sync.RWMutex
	Id            string
	Connection    *websocket.Conn
	Req           *http.Request
	Context       interface{}
	ContextString string
	ContextType   string
}

type WebSocketConnectionCollection struct {
	sync.RWMutex
	Connections []*WebSocketConnection
}

type WebSocketCallbackSync struct {
	sync.RWMutex
	callbacks []WebSocketCallback
}

type WebSocketPubSubPayload struct {
	Key     string      `json:"Key"`
	Content interface{} `json:"Content"`
}

type WebSocketCallback func(conn *WebSocketConnection, c *gin.Context, messageType int, data []byte)

var upgrader = websocket.Upgrader{
	CheckOrigin:     func(r *http.Request) bool { return true },
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

var WebSocketConnections WebSocketConnectionCollection
var WebSocketCallbacks WebSocketCallbackSync
var WebSocketRemovalCallback WebSocketRemoval

func Initialize(path string, cookieDomain string) (err error) {
	err = serverSettings.Initialize(path, "webConfig.json")
	if err != nil {
		return
	}

	serverSettings.WebConfigMutex.RLock()
	inRelease := serverSettings.WebConfig.Application.ReleaseMode == "release"
	serverSettings.WebConfigMutex.RUnlock()

	if inRelease {
		ginServer.Initialize(gin.ReleaseMode, cookieDomain)
	} else {
		ginServer.Initialize(gin.DebugMode, cookieDomain)
	}
	fileCache.Initialize()

	err = dbServices.Initialize()
	if err != nil {
		return
	}
	return
}

func InitializeLite() (err error) {
	ginServer.InitializeLite(gin.ReleaseMode)
	fileCache.Initialize()
	return
}

func RunLite(port int) {
	defer func() {
		if r := recover(); r != nil {
			log.Println("Panic Recovered at RunLite():  ", r)
			time.Sleep(time.Millisecond * 3000)
			RunLite(port)
			return
		}
	}()

	ginServer.Router.GET("/ws", func(c *gin.Context) {
		webSocketHandler(c.Writer, c.Request, c)
	})

	log.Println("GoCore Application Started")

	ginServer.Router.Run(":" + strconv.Itoa(port))

}

func Run() {

	defer func() {
		if r := recover(); r != nil {
			log.Println("Panic Recovered at Run():  ", r)
			time.Sleep(time.Millisecond * 3000)
			Run()
			return
		}
	}()

	if serverSettings.WebConfig.Application.WebServiceOnly == false {

		loadHTMLTemplates()

		ginServer.Router.Static("/web", serverSettings.APP_LOCATION+"/web")

		ginServer.Router.GET("/ws", func(c *gin.Context) {
			webSocketHandler(c.Writer, c.Request, c)
		})
	}

	initializeStaticRoutes()

	go ginServer.Router.RunTLS(":"+strconv.Itoa(serverSettings.WebConfig.Application.HttpsPort), serverSettings.APP_LOCATION+"/keys/cert.pem", serverSettings.APP_LOCATION+"/keys/key.pem")

	log.Println("GoCore Application Started")

	ginServer.Router.Run(":" + strconv.Itoa(serverSettings.WebConfig.Application.HttpPort))

	// go ginServer.Router.GET("/", func(c *gin.Context) {
	// 	c.Redirect(http.StatusMovedPermanently, "https://"+serverSettings.WebConfig.Application.Domain+":"+strconv.Itoa(serverSettings.WebConfig.Application.HttpsPort))
	// })

}

func webSocketHandler(w http.ResponseWriter, r *http.Request, c *gin.Context) {

	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at webSocketHandler():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			webSocketHandler(w, r, c)
			return
		}
	}()
	//log.Println("Web Socket Connection")
	conn, err := upgrader.Upgrade(w, r, nil)

	if err != nil {
		log.Println("Failed to upgrade http connection to websocket:  " + err.Error())
		return
	}

	//Start the Reader, listen for Close Message, and Add to the Connection Array.

	wsConn := new(WebSocketConnection)
	wsConn.Connection = conn
	wsConn.Req = r
	uuid, err := newUUID()
	if err == nil {
		wsConn.Id = uuid
	} else {
		uuid = randomString(20)
		wsConn.Id = uuid
	}

	// log.Println("Upgrading Websocket")
	// log.Printf("%+v\n", r)

	//Reader
	go logger.GoRoutineLogger(func() {
		for {
			messageType, p, err := conn.ReadMessage()
			if err == nil {
				go logger.GoRoutineLogger(func() {
					WebSocketCallbacks.RLock()
					for _, callback := range WebSocketCallbacks.callbacks {
						if callback != nil {
							callback(wsConn, c, messageType, p)
						}
					}
					WebSocketCallbacks.RUnlock()
				}, "GoCore/app.go->webSocketHandler[Callback calls]")
			} else {
				deleteWebSocket(wsConn)
				return
			}
		}
	}, "GoCore/app.go->webSocketHandler[Reader]")

	WebSocketConnections.Lock()
	WebSocketConnections.Connections = append(WebSocketConnections.Connections, wsConn)
	WebSocketConnections.Unlock()
}

func loadHTMLTemplates() {

	if serverSettings.WebConfig.Application.HtmlTemplates.Enabled {

		levels := "/*"
		dirLevel := ""

		switch serverSettings.WebConfig.Application.HtmlTemplates.DirectoryLevels {
		case 0:
			levels = "/*"
			dirLevel = ""
		case 1:
			levels = "/**/*"
			dirLevel = "root/"
		case 2:
			levels = "/**/**/*"
			dirLevel = "root/root/"
		}

		ginServer.Router.LoadHTMLGlob(serverSettings.APP_LOCATION + "/web/" + serverSettings.WebConfig.Application.HtmlTemplates.Directory + levels)

		ginServer.Router.GET("", func(c *gin.Context) {
			c.HTML(http.StatusOK, dirLevel+"index.tmpl", gin.H{})
		})
	} else {

		if serverSettings.WebConfig.Application.DisableRootIndex {
			return
		}

		ginServer.Router.GET("", func(c *gin.Context) {
			if serverSettings.WebConfig.Application.RootIndexPath == "" {
				ginServer.ReadHTMLFile(serverSettings.APP_LOCATION+"/web/index.htm", c)
			} else {
				ginServer.ReadHTMLFile(serverSettings.APP_LOCATION+"/web/"+serverSettings.WebConfig.Application.RootIndexPath, c)
			}
		})
	}
}

func initializeStaticRoutes() {

	ginServer.Router.GET("/swagger", func(c *gin.Context) {
		// c.Redirect(http.StatusMovedPermanently, "https://"+serverSettings.WebConfig.Application.Domain+":"+strconv.Itoa(serverSettings.WebConfig.Application.HttpsPort)+"/web/swagger/dist/index.html")

		ginServer.ReadHTMLFile(serverSettings.APP_LOCATION+"/web/swagger/dist/index.html", c)
	})
}

func RegisterWebSocketDataCallback(callback WebSocketCallback) {
	WebSocketCallbacks.Lock()
	WebSocketCallbacks.callbacks = append(WebSocketCallbacks.callbacks, callback)
	WebSocketCallbacks.Unlock()
}

func ReplyToWebSocket(conn *WebSocketConnection, data []byte) {
	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at ReplyToWebSocket():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			ReplyToWebSocket(conn, data)
			return
		}

		//clear out locks when this is done
		WebSocketConnections.RUnlock()
	}()
	WebSocketConnections.RLock()
	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		if ws.Id == conn.Id {
			go logger.GoRoutineLogger(func() {

				ws.Lock()
				ws.Connection.WriteMessage(websocket.BinaryMessage, data)
				ws.Unlock()
			}, "GoCore/app.go->ReplyToWebSocket[WriteMessage]")
			return
		}
	}
}

func ReplyToWebSocketJSON(conn *WebSocketConnection, v interface{}) {

	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at ReplyToWebSocketJSON():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			ReplyToWebSocketJSON(conn, v)
			return
		}
		WebSocketConnections.RUnlock()
	}()
	WebSocketConnections.RLock()
	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		if ws.Id == conn.Id {
			go logger.GoRoutineLogger(func() {
				ws.Lock()
				ws.Connection.SetWriteDeadline(time.Now().Add(time.Duration(10000) * time.Millisecond))
				ws.Connection.WriteJSON(v)
				ws.Unlock()
			}, "GoCore/app.go->ReplyToWebSocketJSON[WriteJSON]")
			return
		}
	}
}

func ReplyToWebSocketPubSub(conn *WebSocketConnection, key string, v interface{}) {

	WebSocketConnections.RLock()
	defer WebSocketConnections.RUnlock()

	var payload WebSocketPubSubPayload
	payload.Key = key
	payload.Content = v

	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		if ws.Id == conn.Id {
			ws.Lock()
			ws.Connection.SetWriteDeadline(time.Now().Add(time.Duration(10000) * time.Millisecond))
			ws.Connection.WriteJSON(payload)
			ws.Unlock()
			return
		}
	}
}

func BroadcastWebSocketData(data []byte) {

	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at WebSocketConnections():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			BroadcastWebSocketData(data)
			return
		}
	}()

	WebSocketConnections.RLock()
	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		go logger.GoRoutineLogger(func() {

			ws.Lock()
			ws.Connection.WriteMessage(websocket.BinaryMessage, data)
			ws.Unlock()
		}, "GoCore/app.go->BroadcastWebSocketData[WriteMessage]")
	}
	WebSocketConnections.RUnlock()
}

func BroadcastWebSocketJSON(v interface{}) {
	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at BroadcastWebSocketJSON():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			BroadcastWebSocketJSON(v)
			return
		}
	}()
	WebSocketConnections.RLock()
	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		go logger.GoRoutineLogger(func() {
			ws.Lock()
			ws.Connection.SetWriteDeadline(time.Now().Add(time.Duration(10000) * time.Millisecond))
			ws.Connection.WriteJSON(v)
			ws.Unlock()
		}, "GoCore/app.go->BroadcastWebSocketData[WriteJSON]")
	}
	WebSocketConnections.RUnlock()
}

func PublishWebSocketJSON(key string, v interface{}) {
	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at PublishWebSocketJSON():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			PublishWebSocketJSON(key, v)
			return
		}
	}()
	var payload WebSocketPubSubPayload
	payload.Key = key
	payload.Content = v

	//Serialize and Deserialize to prevent Race Conditions from caller.
	data, _ := json.Marshal(payload)
	json.Unmarshal(data, &payload)

	WebSocketConnections.RLock()
	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		go logger.GoRoutineLogger(func() {
			ws.Lock()
			ws.Connection.SetWriteDeadline(time.Now().Add(time.Duration(10000) * time.Millisecond))
			ws.Connection.WriteJSON(payload)
			ws.Unlock()
		}, "GoCore/app.go->WriteJSON")
	}
	WebSocketConnections.RUnlock()
}

func deleteWebSocket(c *WebSocketConnection) {
	WebSocketConnections.Lock()

	for i := 0; i < len(WebSocketConnections.Connections); i++ {
		wsConn := WebSocketConnections.Connections[i]
		if wsConn.Id == c.Id {
			log.Println("Deleting Web Socket from client:  " + wsConn.Connection.RemoteAddr().String())
			WebSocketConnections.Connections = removeWebSocket(WebSocketConnections.Connections, i)
			if WebSocketRemovalCallback != nil {
				go func(c *WebSocketConnection) {
					defer func() {
						if recover := recover(); recover != nil {
							log.Println("Panic Recovered at deleteWebSocket():  ", recover)
							return
						}
					}()
					WebSocketRemovalCallback(c)
				}(wsConn)
			}
			i--
		}
	}

	WebSocketConnections.Unlock()
}

func removeWebSocket(s []*WebSocketConnection, i int) []*WebSocketConnection {
	s[len(s)-1], s[i] = s[i], s[len(s)-1]
	return s[:len(s)-1]
}

// newUUID generates a random UUID according to RFC 4122
func newUUID() (string, error) {
	uuid := make([]byte, 16)
	n, err := io.ReadFull(rand.Reader, uuid)
	if n != len(uuid) || err != nil {
		return "", err
	}
	// variant bits; see section 4.1.1
	uuid[8] = uuid[8]&^0xc0 | 0x80
	// version 4 (pseudo-random); see section 4.1.3
	uuid[6] = uuid[6]&^0xf0 | 0x40
	return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
}

func randomString(strlen int) string {
	randMath.Seed(time.Now().UTC().UnixNano())
	const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
	result := make([]byte, strlen)
	for i := 0; i < strlen; i++ {
		result[i] = chars[randMath.Intn(len(chars))]
	}
	return string(result)
}
<commit_msg>Change to customize webConfig.json as a parameter.<commit_after>package app

import (
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"log"
	randMath "math/rand"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/DanielRenne/GoCore/core/dbServices"
	"github.com/DanielRenne/GoCore/core/fileCache"
	"github.com/DanielRenne/GoCore/core/ginServer"
	"github.com/DanielRenne/GoCore/core/logger"
	"github.com/DanielRenne/GoCore/core/serverSettings"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

type WebSocketRemoval func(conn *WebSocketConnection)

type WebSocketConnection struct {
	sync.RWMutex
	Id            string
	Connection    *websocket.Conn
	Req           *http.Request
	Context       interface{}
	ContextString string
	ContextType   string
}

type WebSocketConnectionCollection struct {
	sync.RWMutex
	Connections []*WebSocketConnection
}

type WebSocketCallbackSync struct {
	sync.RWMutex
	callbacks []WebSocketCallback
}

type WebSocketPubSubPayload struct {
	Key     string      `json:"Key"`
	Content interface{} `json:"Content"`
}

type WebSocketCallback func(conn *WebSocketConnection, c *gin.Context, messageType int, data []byte)

var upgrader = websocket.Upgrader{
	CheckOrigin:     func(r *http.Request) bool { return true },
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

var WebSocketConnections WebSocketConnectionCollection
var WebSocketCallbacks WebSocketCallbackSync
var WebSocketRemovalCallback WebSocketRemoval

func Initialize(path string, config string, cookieDomain string) (err error) {
	err = serverSettings.Initialize(path, config)
	if err != nil {
		return
	}

	serverSettings.WebConfigMutex.RLock()
	inRelease := serverSettings.WebConfig.Application.ReleaseMode == "release"
	serverSettings.WebConfigMutex.RUnlock()

	if inRelease {
		ginServer.Initialize(gin.ReleaseMode, cookieDomain)
	} else {
		ginServer.Initialize(gin.DebugMode, cookieDomain)
	}
	fileCache.Initialize()

	err = dbServices.Initialize()
	if err != nil {
		return
	}
	return
}

func InitializeLite() (err error) {
	ginServer.InitializeLite(gin.ReleaseMode)
	fileCache.Initialize()
	return
}

func RunLite(port int) {
	defer func() {
		if r := recover(); r != nil {
			log.Println("Panic Recovered at RunLite():  ", r)
			time.Sleep(time.Millisecond * 3000)
			RunLite(port)
			return
		}
	}()

	ginServer.Router.GET("/ws", func(c *gin.Context) {
		webSocketHandler(c.Writer, c.Request, c)
	})

	log.Println("GoCore Application Started")

	ginServer.Router.Run(":" + strconv.Itoa(port))

}

func Run() {

	defer func() {
		if r := recover(); r != nil {
			log.Println("Panic Recovered at Run():  ", r)
			time.Sleep(time.Millisecond * 3000)
			Run()
			return
		}
	}()

	if serverSettings.WebConfig.Application.WebServiceOnly == false {

		loadHTMLTemplates()

		ginServer.Router.Static("/web", serverSettings.APP_LOCATION+"/web")

		ginServer.Router.GET("/ws", func(c *gin.Context) {
			webSocketHandler(c.Writer, c.Request, c)
		})
	}

	initializeStaticRoutes()

	go ginServer.Router.RunTLS(":"+strconv.Itoa(serverSettings.WebConfig.Application.HttpsPort), serverSettings.APP_LOCATION+"/keys/cert.pem", serverSettings.APP_LOCATION+"/keys/key.pem")

	log.Println("GoCore Application Started")

	ginServer.Router.Run(":" + strconv.Itoa(serverSettings.WebConfig.Application.HttpPort))

	// go ginServer.Router.GET("/", func(c *gin.Context) {
	// 	c.Redirect(http.StatusMovedPermanently, "https://"+serverSettings.WebConfig.Application.Domain+":"+strconv.Itoa(serverSettings.WebConfig.Application.HttpsPort))
	// })

}

func webSocketHandler(w http.ResponseWriter, r *http.Request, c *gin.Context) {

	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at webSocketHandler():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			webSocketHandler(w, r, c)
			return
		}
	}()
	//log.Println("Web Socket Connection")
	conn, err := upgrader.Upgrade(w, r, nil)

	if err != nil {
		log.Println("Failed to upgrade http connection to websocket:  " + err.Error())
		return
	}

	//Start the Reader, listen for Close Message, and Add to the Connection Array.

	wsConn := new(WebSocketConnection)
	wsConn.Connection = conn
	wsConn.Req = r
	uuid, err := newUUID()
	if err == nil {
		wsConn.Id = uuid
	} else {
		uuid = randomString(20)
		wsConn.Id = uuid
	}

	// log.Println("Upgrading Websocket")
	// log.Printf("%+v\n", r)

	//Reader
	go logger.GoRoutineLogger(func() {
		for {
			messageType, p, err := conn.ReadMessage()
			if err == nil {
				go logger.GoRoutineLogger(func() {
					WebSocketCallbacks.RLock()
					for _, callback := range WebSocketCallbacks.callbacks {
						if callback != nil {
							callback(wsConn, c, messageType, p)
						}
					}
					WebSocketCallbacks.RUnlock()
				}, "GoCore/app.go->webSocketHandler[Callback calls]")
			} else {
				deleteWebSocket(wsConn)
				return
			}
		}
	}, "GoCore/app.go->webSocketHandler[Reader]")

	WebSocketConnections.Lock()
	WebSocketConnections.Connections = append(WebSocketConnections.Connections, wsConn)
	WebSocketConnections.Unlock()
}

func loadHTMLTemplates() {

	if serverSettings.WebConfig.Application.HtmlTemplates.Enabled {

		levels := "/*"
		dirLevel := ""

		switch serverSettings.WebConfig.Application.HtmlTemplates.DirectoryLevels {
		case 0:
			levels = "/*"
			dirLevel = ""
		case 1:
			levels = "/**/*"
			dirLevel = "root/"
		case 2:
			levels = "/**/**/*"
			dirLevel = "root/root/"
		}

		ginServer.Router.LoadHTMLGlob(serverSettings.APP_LOCATION + "/web/" + serverSettings.WebConfig.Application.HtmlTemplates.Directory + levels)

		ginServer.Router.GET("", func(c *gin.Context) {
			c.HTML(http.StatusOK, dirLevel+"index.tmpl", gin.H{})
		})
	} else {

		if serverSettings.WebConfig.Application.DisableRootIndex {
			return
		}

		ginServer.Router.GET("", func(c *gin.Context) {
			if serverSettings.WebConfig.Application.RootIndexPath == "" {
				ginServer.ReadHTMLFile(serverSettings.APP_LOCATION+"/web/index.htm", c)
			} else {
				ginServer.ReadHTMLFile(serverSettings.APP_LOCATION+"/web/"+serverSettings.WebConfig.Application.RootIndexPath, c)
			}
		})
	}
}

func initializeStaticRoutes() {

	ginServer.Router.GET("/swagger", func(c *gin.Context) {
		// c.Redirect(http.StatusMovedPermanently, "https://"+serverSettings.WebConfig.Application.Domain+":"+strconv.Itoa(serverSettings.WebConfig.Application.HttpsPort)+"/web/swagger/dist/index.html")

		ginServer.ReadHTMLFile(serverSettings.APP_LOCATION+"/web/swagger/dist/index.html", c)
	})
}

func RegisterWebSocketDataCallback(callback WebSocketCallback) {
	WebSocketCallbacks.Lock()
	WebSocketCallbacks.callbacks = append(WebSocketCallbacks.callbacks, callback)
	WebSocketCallbacks.Unlock()
}

func ReplyToWebSocket(conn *WebSocketConnection, data []byte) {
	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at ReplyToWebSocket():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			ReplyToWebSocket(conn, data)
			return
		}

		//clear out locks when this is done
		WebSocketConnections.RUnlock()
	}()
	WebSocketConnections.RLock()
	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		if ws.Id == conn.Id {
			go logger.GoRoutineLogger(func() {

				ws.Lock()
				ws.Connection.WriteMessage(websocket.BinaryMessage, data)
				ws.Unlock()
			}, "GoCore/app.go->ReplyToWebSocket[WriteMessage]")
			return
		}
	}
}

func ReplyToWebSocketJSON(conn *WebSocketConnection, v interface{}) {

	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at ReplyToWebSocketJSON():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			ReplyToWebSocketJSON(conn, v)
			return
		}
		WebSocketConnections.RUnlock()
	}()
	WebSocketConnections.RLock()
	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		if ws.Id == conn.Id {
			go logger.GoRoutineLogger(func() {
				ws.Lock()
				ws.Connection.SetWriteDeadline(time.Now().Add(time.Duration(10000) * time.Millisecond))
				ws.Connection.WriteJSON(v)
				ws.Unlock()
			}, "GoCore/app.go->ReplyToWebSocketJSON[WriteJSON]")
			return
		}
	}
}

func ReplyToWebSocketPubSub(conn *WebSocketConnection, key string, v interface{}) {

	WebSocketConnections.RLock()
	defer WebSocketConnections.RUnlock()

	var payload WebSocketPubSubPayload
	payload.Key = key
	payload.Content = v

	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		if ws.Id == conn.Id {
			ws.Lock()
			ws.Connection.SetWriteDeadline(time.Now().Add(time.Duration(10000) * time.Millisecond))
			ws.Connection.WriteJSON(payload)
			ws.Unlock()
			return
		}
	}
}

func BroadcastWebSocketData(data []byte) {

	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at WebSocketConnections():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			BroadcastWebSocketData(data)
			return
		}
	}()

	WebSocketConnections.RLock()
	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		go logger.GoRoutineLogger(func() {

			ws.Lock()
			ws.Connection.WriteMessage(websocket.BinaryMessage, data)
			ws.Unlock()
		}, "GoCore/app.go->BroadcastWebSocketData[WriteMessage]")
	}
	WebSocketConnections.RUnlock()
}

func BroadcastWebSocketJSON(v interface{}) {
	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at BroadcastWebSocketJSON():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			BroadcastWebSocketJSON(v)
			return
		}
	}()
	WebSocketConnections.RLock()
	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		go logger.GoRoutineLogger(func() {
			ws.Lock()
			ws.Connection.SetWriteDeadline(time.Now().Add(time.Duration(10000) * time.Millisecond))
			ws.Connection.WriteJSON(v)
			ws.Unlock()
		}, "GoCore/app.go->BroadcastWebSocketData[WriteJSON]")
	}
	WebSocketConnections.RUnlock()
}

func PublishWebSocketJSON(key string, v interface{}) {
	defer func() {
		if recover := recover(); recover != nil {
			log.Println("Panic Recovered at PublishWebSocketJSON():  ", recover)
			time.Sleep(time.Millisecond * 3000)
			PublishWebSocketJSON(key, v)
			return
		}
	}()
	var payload WebSocketPubSubPayload
	payload.Key = key
	payload.Content = v

	//Serialize and Deserialize to prevent Race Conditions from caller.
	data, _ := json.Marshal(payload)
	json.Unmarshal(data, &payload)

	WebSocketConnections.RLock()
	for _, wsConn := range WebSocketConnections.Connections {
		ws := wsConn
		go logger.GoRoutineLogger(func() {
			ws.Lock()
			ws.Connection.SetWriteDeadline(time.Now().Add(time.Duration(10000) * time.Millisecond))
			ws.Connection.WriteJSON(payload)
			ws.Unlock()
		}, "GoCore/app.go->WriteJSON")
	}
	WebSocketConnections.RUnlock()
}

func deleteWebSocket(c *WebSocketConnection) {
	WebSocketConnections.Lock()

	for i := 0; i < len(WebSocketConnections.Connections); i++ {
		wsConn := WebSocketConnections.Connections[i]
		if wsConn.Id == c.Id {
			log.Println("Deleting Web Socket from client:  " + wsConn.Connection.RemoteAddr().String())
			WebSocketConnections.Connections = removeWebSocket(WebSocketConnections.Connections, i)
			if WebSocketRemovalCallback != nil {
				go func(c *WebSocketConnection) {
					defer func() {
						if recover := recover(); recover != nil {
							log.Println("Panic Recovered at deleteWebSocket():  ", recover)
							return
						}
					}()
					WebSocketRemovalCallback(c)
				}(wsConn)
			}
			i--
		}
	}

	WebSocketConnections.Unlock()
}

func removeWebSocket(s []*WebSocketConnection, i int) []*WebSocketConnection {
	s[len(s)-1], s[i] = s[i], s[len(s)-1]
	return s[:len(s)-1]
}

// newUUID generates a random UUID according to RFC 4122
func newUUID() (string, error) {
	uuid := make([]byte, 16)
	n, err := io.ReadFull(rand.Reader, uuid)
	if n != len(uuid) || err != nil {
		return "", err
	}
	// variant bits; see section 4.1.1
	uuid[8] = uuid[8]&^0xc0 | 0x80
	// version 4 (pseudo-random); see section 4.1.3
	uuid[6] = uuid[6]&^0xf0 | 0x40
	return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
}

func randomString(strlen int) string {
	randMath.Seed(time.Now().UTC().UnixNano())
	const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
	result := make([]byte, strlen)
	for i := 0; i < strlen; i++ {
		result[i] = chars[randMath.Intn(len(chars))]
	}
	return string(result)
}
<|endoftext|>


<commit_before>package core

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"os"
	"syscall"
	"time"

	bserv "github.com/ipfs/go-ipfs/blockservice"
	filestore "github.com/ipfs/go-ipfs/filestore"
	dag "github.com/ipfs/go-ipfs/merkledag"
	resolver "github.com/ipfs/go-ipfs/path/resolver"
	pin "github.com/ipfs/go-ipfs/pin"
	repo "github.com/ipfs/go-ipfs/repo"
	cfg "github.com/ipfs/go-ipfs/repo/config"
	"github.com/ipfs/go-ipfs/thirdparty/verifbs"
	uio "github.com/ipfs/go-ipfs/unixfs/io"

	offline "gx/ipfs/QmPf114DXfa6TqGKYhBGR7EtXRho4rCJgwyA1xkuMY5vwF/go-ipfs-exchange-offline"
	p2phost "gx/ipfs/QmQQGtcp6nVUrQjNsnU53YWV1q8fK1Kd9S7FEkYbRZzxry/go-libp2p-host"
	metrics "gx/ipfs/QmRg1gKTHzc3CZXSKzem8aR4E3TubFhbgXwfVuWnSK5CC5/go-metrics-interface"
	goprocessctx "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess/context"
	libp2p "gx/ipfs/QmUEAR2pS7fP1GPseS3i8MWFyENs7oDp4CZrgn8FCjbsBu/go-libp2p"
	peer "gx/ipfs/QmVf8hTAsLLFtn4WPCRNdnaF2Eag2qTBS6uR8AiHPZARXy/go-libp2p-peer"
	pstore "gx/ipfs/QmZhsmorLpD9kmQ4ynbAu4vbKv2goMUnXazwGA4gnWHDjB/go-libp2p-peerstore"
	bstore "gx/ipfs/QmbaPGg81pvQiC5vTXtC9Jo8rdrWUjRaugH71WYNsgi6Ev/go-ipfs-blockstore"
	ci "gx/ipfs/Qme1knMqwt1hKZbc1BmQFmnm9f36nyQGwXxPGVpVJ9rMK5/go-libp2p-crypto"
	ds "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore"
	retry "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore/retrystore"
	dsync "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore/sync"
)

type BuildCfg struct {
	// If online is set, the node will have networking enabled
	Online bool

	// ExtraOpts is a map of extra options used to configure the ipfs nodes creation
	ExtraOpts map[string]bool

	// If permanent then node should run more expensive processes
	// that will improve performance in long run
	Permanent bool

	// DisableEncryptedConnections disables connection encryption *entirely*.
	// DO NOT SET THIS UNLESS YOU'RE TESTING.
	DisableEncryptedConnections bool

	// If NilRepo is set, a repo backed by a nil datastore will be constructed
	NilRepo bool

	Routing RoutingOption
	Host    HostOption
	Repo    repo.Repo
}

func (cfg *BuildCfg) getOpt(key string) bool {
	if cfg.ExtraOpts == nil {
		return false
	}

	return cfg.ExtraOpts[key]
}

func (cfg *BuildCfg) fillDefaults() error {
	if cfg.Repo != nil && cfg.NilRepo {
		return errors.New("cannot set a repo and specify nilrepo at the same time")
	}

	if cfg.Repo == nil {
		var d ds.Datastore
		d = ds.NewMapDatastore()

		if cfg.NilRepo {
			d = ds.NewNullDatastore()
		}
		r, err := defaultRepo(dsync.MutexWrap(d))
		if err != nil {
			return err
		}
		cfg.Repo = r
	}

	if cfg.Routing == nil {
		cfg.Routing = DHTOption
	}

	if cfg.Host == nil {
		cfg.Host = DefaultHostOption
	}

	return nil
}

func defaultRepo(dstore repo.Datastore) (repo.Repo, error) {
	c := cfg.Config{}
	priv, pub, err := ci.GenerateKeyPairWithReader(ci.RSA, 1024, rand.Reader)
	if err != nil {
		return nil, err
	}

	pid, err := peer.IDFromPublicKey(pub)
	if err != nil {
		return nil, err
	}

	privkeyb, err := priv.Bytes()
	if err != nil {
		return nil, err
	}

	c.Bootstrap = cfg.DefaultBootstrapAddresses
	c.Addresses.Swarm = []string{"/ip4/0.0.0.0/tcp/4001"}
	c.Identity.PeerID = pid.Pretty()
	c.Identity.PrivKey = base64.StdEncoding.EncodeToString(privkeyb)

	return &repo.Mock{
		D: dstore,
		C: c,
	}, nil
}

// NewNode constructs and returns an IpfsNode using the given cfg.
func NewNode(ctx context.Context, cfg *BuildCfg) (*IpfsNode, error) {
	if cfg == nil {
		cfg = new(BuildCfg)
	}

	err := cfg.fillDefaults()
	if err != nil {
		return nil, err
	}

	ctx = metrics.CtxScope(ctx, "ipfs")

	n := &IpfsNode{
		mode:      offlineMode,
		Repo:      cfg.Repo,
		ctx:       ctx,
		Peerstore: pstore.NewPeerstore(),
	}
	if cfg.Online {
		n.mode = onlineMode
	}

	// TODO: this is a weird circular-ish dependency, rework it
	n.proc = goprocessctx.WithContextAndTeardown(ctx, n.teardown)

	if err := setupNode(ctx, n, cfg); err != nil {
		n.Close()
		return nil, err
	}

	return n, nil
}

func isTooManyFDError(err error) bool {
	perr, ok := err.(*os.PathError)
	if ok && perr.Err == syscall.EMFILE {
		return true
	}

	return false
}

func setupNode(ctx context.Context, n *IpfsNode, cfg *BuildCfg) error {
	// setup local peer ID (private key is loaded in online setup)
	if err := n.loadID(); err != nil {
		return err
	}

	rds := &retry.Datastore{
		Batching:    n.Repo.Datastore(),
		Delay:       time.Millisecond * 200,
		Retries:     6,
		TempErrFunc: isTooManyFDError,
	}

	// hash security
	bs := bstore.NewBlockstore(rds)
	bs = &verifbs.VerifBS{Blockstore: bs}

	opts := bstore.DefaultCacheOpts()
	conf, err := n.Repo.Config()
	if err != nil {
		return err
	}

	// TEMP: setting global sharding switch here
	uio.UseHAMTSharding = conf.Experimental.ShardingEnabled

	opts.HasBloomFilterSize = conf.Datastore.BloomFilterSize
	if !cfg.Permanent {
		opts.HasBloomFilterSize = 0
	}

	cbs, err := bstore.CachedBlockstore(ctx, bs, opts)
	if err != nil {
		return err
	}

	n.BaseBlocks = cbs
	n.GCLocker = bstore.NewGCLocker()
	n.Blockstore = bstore.NewGCBlockstore(cbs, n.GCLocker)

	if conf.Experimental.FilestoreEnabled {
		// hash security
		n.Filestore = filestore.NewFilestore(bs, n.Repo.FileManager())
		n.Blockstore = bstore.NewGCBlockstore(n.Filestore, n.GCLocker)
		n.Blockstore = &verifbs.VerifBSGC{GCBlockstore: n.Blockstore}
	}

	rcfg, err := n.Repo.Config()
	if err != nil {
		return err
	}

	if rcfg.Datastore.HashOnRead {
		bs.HashOnRead(true)
	}

	hostOption := cfg.Host
	if cfg.DisableEncryptedConnections {
		innerHostOption := hostOption
		hostOption = func(ctx context.Context, id peer.ID, ps pstore.Peerstore, options ...libp2p.Option) (p2phost.Host, error) {
			return innerHostOption(ctx, id, ps, append(options, libp2p.NoSecurity)...)
		}
		log.Warningf(`Your IPFS node has been configured to run WITHOUT ENCRYPTED CONNECTIONS.
		You will not be able to connect to any nodes configured to use encrypted connections`)
	}

	if cfg.Online {
		do := setupDiscoveryOption(rcfg.Discovery)
		if err := n.startOnlineServices(ctx, cfg.Routing, hostOption, do, cfg.getOpt("pubsub"), cfg.getOpt("ipnsps"), cfg.getOpt("mplex")); err != nil {
			return err
		}
	} else {
		n.Exchange = offline.Exchange(n.Blockstore)
	}

	n.Blocks = bserv.New(n.Blockstore, n.Exchange)
	n.DAG = dag.NewDAGService(n.Blocks)

	internalDag := dag.NewDAGService(bserv.New(n.Blockstore, offline.Exchange(n.Blockstore)))
	n.Pinning, err = pin.LoadPinner(n.Repo.Datastore(), n.DAG, internalDag)
	if err != nil {
		// TODO: we should move towards only running 'NewPinner' explicitly on
		// node init instead of implicitly here as a result of the pinner keys
		// not being found in the datastore.
		// this is kinda sketchy and could cause data loss
		n.Pinning = pin.NewPinner(n.Repo.Datastore(), n.DAG, internalDag)
	}
	n.Resolver = resolver.NewBasicResolver(n.DAG)

	if cfg.Online {
		if err := n.startLateOnlineServices(ctx); err != nil {
			return err
		}
	}

	return n.loadFilesRoot()
}
<commit_msg>Fix how filestore is hooked up.<commit_after>package core

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"os"
	"syscall"
	"time"

	bserv "github.com/ipfs/go-ipfs/blockservice"
	filestore "github.com/ipfs/go-ipfs/filestore"
	dag "github.com/ipfs/go-ipfs/merkledag"
	resolver "github.com/ipfs/go-ipfs/path/resolver"
	pin "github.com/ipfs/go-ipfs/pin"
	repo "github.com/ipfs/go-ipfs/repo"
	cfg "github.com/ipfs/go-ipfs/repo/config"
	"github.com/ipfs/go-ipfs/thirdparty/verifbs"
	uio "github.com/ipfs/go-ipfs/unixfs/io"

	offline "gx/ipfs/QmPf114DXfa6TqGKYhBGR7EtXRho4rCJgwyA1xkuMY5vwF/go-ipfs-exchange-offline"
	p2phost "gx/ipfs/QmQQGtcp6nVUrQjNsnU53YWV1q8fK1Kd9S7FEkYbRZzxry/go-libp2p-host"
	metrics "gx/ipfs/QmRg1gKTHzc3CZXSKzem8aR4E3TubFhbgXwfVuWnSK5CC5/go-metrics-interface"
	goprocessctx "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess/context"
	libp2p "gx/ipfs/QmUEAR2pS7fP1GPseS3i8MWFyENs7oDp4CZrgn8FCjbsBu/go-libp2p"
	peer "gx/ipfs/QmVf8hTAsLLFtn4WPCRNdnaF2Eag2qTBS6uR8AiHPZARXy/go-libp2p-peer"
	pstore "gx/ipfs/QmZhsmorLpD9kmQ4ynbAu4vbKv2goMUnXazwGA4gnWHDjB/go-libp2p-peerstore"
	bstore "gx/ipfs/QmbaPGg81pvQiC5vTXtC9Jo8rdrWUjRaugH71WYNsgi6Ev/go-ipfs-blockstore"
	ci "gx/ipfs/Qme1knMqwt1hKZbc1BmQFmnm9f36nyQGwXxPGVpVJ9rMK5/go-libp2p-crypto"
	ds "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore"
	retry "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore/retrystore"
	dsync "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore/sync"
)

type BuildCfg struct {
	// If online is set, the node will have networking enabled
	Online bool

	// ExtraOpts is a map of extra options used to configure the ipfs nodes creation
	ExtraOpts map[string]bool

	// If permanent then node should run more expensive processes
	// that will improve performance in long run
	Permanent bool

	// DisableEncryptedConnections disables connection encryption *entirely*.
	// DO NOT SET THIS UNLESS YOU'RE TESTING.
	DisableEncryptedConnections bool

	// If NilRepo is set, a repo backed by a nil datastore will be constructed
	NilRepo bool

	Routing RoutingOption
	Host    HostOption
	Repo    repo.Repo
}

func (cfg *BuildCfg) getOpt(key string) bool {
	if cfg.ExtraOpts == nil {
		return false
	}

	return cfg.ExtraOpts[key]
}

func (cfg *BuildCfg) fillDefaults() error {
	if cfg.Repo != nil && cfg.NilRepo {
		return errors.New("cannot set a repo and specify nilrepo at the same time")
	}

	if cfg.Repo == nil {
		var d ds.Datastore
		d = ds.NewMapDatastore()

		if cfg.NilRepo {
			d = ds.NewNullDatastore()
		}
		r, err := defaultRepo(dsync.MutexWrap(d))
		if err != nil {
			return err
		}
		cfg.Repo = r
	}

	if cfg.Routing == nil {
		cfg.Routing = DHTOption
	}

	if cfg.Host == nil {
		cfg.Host = DefaultHostOption
	}

	return nil
}

func defaultRepo(dstore repo.Datastore) (repo.Repo, error) {
	c := cfg.Config{}
	priv, pub, err := ci.GenerateKeyPairWithReader(ci.RSA, 1024, rand.Reader)
	if err != nil {
		return nil, err
	}

	pid, err := peer.IDFromPublicKey(pub)
	if err != nil {
		return nil, err
	}

	privkeyb, err := priv.Bytes()
	if err != nil {
		return nil, err
	}

	c.Bootstrap = cfg.DefaultBootstrapAddresses
	c.Addresses.Swarm = []string{"/ip4/0.0.0.0/tcp/4001"}
	c.Identity.PeerID = pid.Pretty()
	c.Identity.PrivKey = base64.StdEncoding.EncodeToString(privkeyb)

	return &repo.Mock{
		D: dstore,
		C: c,
	}, nil
}

// NewNode constructs and returns an IpfsNode using the given cfg.
func NewNode(ctx context.Context, cfg *BuildCfg) (*IpfsNode, error) {
	if cfg == nil {
		cfg = new(BuildCfg)
	}

	err := cfg.fillDefaults()
	if err != nil {
		return nil, err
	}

	ctx = metrics.CtxScope(ctx, "ipfs")

	n := &IpfsNode{
		mode:      offlineMode,
		Repo:      cfg.Repo,
		ctx:       ctx,
		Peerstore: pstore.NewPeerstore(),
	}
	if cfg.Online {
		n.mode = onlineMode
	}

	// TODO: this is a weird circular-ish dependency, rework it
	n.proc = goprocessctx.WithContextAndTeardown(ctx, n.teardown)

	if err := setupNode(ctx, n, cfg); err != nil {
		n.Close()
		return nil, err
	}

	return n, nil
}

func isTooManyFDError(err error) bool {
	perr, ok := err.(*os.PathError)
	if ok && perr.Err == syscall.EMFILE {
		return true
	}

	return false
}

func setupNode(ctx context.Context, n *IpfsNode, cfg *BuildCfg) error {
	// setup local peer ID (private key is loaded in online setup)
	if err := n.loadID(); err != nil {
		return err
	}

	rds := &retry.Datastore{
		Batching:    n.Repo.Datastore(),
		Delay:       time.Millisecond * 200,
		Retries:     6,
		TempErrFunc: isTooManyFDError,
	}

	// hash security
	bs := bstore.NewBlockstore(rds)
	bs = &verifbs.VerifBS{Blockstore: bs}

	opts := bstore.DefaultCacheOpts()
	conf, err := n.Repo.Config()
	if err != nil {
		return err
	}

	// TEMP: setting global sharding switch here
	uio.UseHAMTSharding = conf.Experimental.ShardingEnabled

	opts.HasBloomFilterSize = conf.Datastore.BloomFilterSize
	if !cfg.Permanent {
		opts.HasBloomFilterSize = 0
	}

	cbs, err := bstore.CachedBlockstore(ctx, bs, opts)
	if err != nil {
		return err
	}

	n.BaseBlocks = cbs
	n.GCLocker = bstore.NewGCLocker()
	n.Blockstore = bstore.NewGCBlockstore(cbs, n.GCLocker)

	if conf.Experimental.FilestoreEnabled {
		// hash security
		n.Filestore = filestore.NewFilestore(cbs, n.Repo.FileManager())
		n.Blockstore = bstore.NewGCBlockstore(n.Filestore, n.GCLocker)
		n.Blockstore = &verifbs.VerifBSGC{GCBlockstore: n.Blockstore}
	}

	rcfg, err := n.Repo.Config()
	if err != nil {
		return err
	}

	if rcfg.Datastore.HashOnRead {
		bs.HashOnRead(true)
	}

	hostOption := cfg.Host
	if cfg.DisableEncryptedConnections {
		innerHostOption := hostOption
		hostOption = func(ctx context.Context, id peer.ID, ps pstore.Peerstore, options ...libp2p.Option) (p2phost.Host, error) {
			return innerHostOption(ctx, id, ps, append(options, libp2p.NoSecurity)...)
		}
		log.Warningf(`Your IPFS node has been configured to run WITHOUT ENCRYPTED CONNECTIONS.
		You will not be able to connect to any nodes configured to use encrypted connections`)
	}

	if cfg.Online {
		do := setupDiscoveryOption(rcfg.Discovery)
		if err := n.startOnlineServices(ctx, cfg.Routing, hostOption, do, cfg.getOpt("pubsub"), cfg.getOpt("ipnsps"), cfg.getOpt("mplex")); err != nil {
			return err
		}
	} else {
		n.Exchange = offline.Exchange(n.Blockstore)
	}

	n.Blocks = bserv.New(n.Blockstore, n.Exchange)
	n.DAG = dag.NewDAGService(n.Blocks)

	internalDag := dag.NewDAGService(bserv.New(n.Blockstore, offline.Exchange(n.Blockstore)))
	n.Pinning, err = pin.LoadPinner(n.Repo.Datastore(), n.DAG, internalDag)
	if err != nil {
		// TODO: we should move towards only running 'NewPinner' explicitly on
		// node init instead of implicitly here as a result of the pinner keys
		// not being found in the datastore.
		// this is kinda sketchy and could cause data loss
		n.Pinning = pin.NewPinner(n.Repo.Datastore(), n.DAG, internalDag)
	}
	n.Resolver = resolver.NewBasicResolver(n.DAG)

	if cfg.Online {
		if err := n.startLateOnlineServices(ctx); err != nil {
			return err
		}
	}

	return n.loadFilesRoot()
}
<|endoftext|>


<commit_before><commit_msg>Refresh session periodically<commit_after><|endoftext|>


<commit_before>package core

import (
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/hexbotio/hex-plugin"
	"github.com/hexbotio/hex/models"
	"github.com/hexbotio/hex/parse"
	"github.com/mohae/deepcopy"
)

// Matcher function
func Matcher(inputMsgs <-chan models.Message, outputMsgs chan<- models.Message, plugins *map[string]models.Plugin, rules *map[string]models.Rule, config models.Config) {
	state := make(map[string]bool)
	for _, rule := range *rules {
		state[rule.Id] = true
	}
	for {
		message := <-inputMsgs
		match := false
		config.Logger.Debug("Matcher - Eval of Message ID:" + message.Attributes["hex.id"])
		config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
		if parse.EitherMember(config.ACL, message.Attributes["hex.user"], message.Attributes["hex.channel"]) {
			Commands(message, outputMsgs, rules, config)
		}
		for _, rule := range *rules {

			// match for input
			if rule.Active && rule.Match != "" && parse.Match(rule.Match, message.Attributes["hex.input"]) {
				if parse.EitherMember(config.ACL, message.Attributes["hex.user"], message.Attributes["hex.channel"]) {
					if parse.EitherMember(rule.ACL, message.Attributes["hex.user"], message.Attributes["hex.channel"]) {
						match = true
						config.Logger.Debug("Matcher - Matched Rule '" + rule.Name + "' with input '" + message.Attributes["hex.input"] + "' on ID:" + message.Attributes["hex.id"])
						config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
						msg := deepcopy.Copy(message).(models.Message)
						go runRule(rule, msg, outputMsgs, state, *plugins, config)
					}
				}
			}

			// match for schedule
			if rule.Active && rule.Schedule != "" && rule.Schedule == message.Attributes["hex.schedule"] {
				match = true
				config.Logger.Debug("Matcher - Matched Rule '" + rule.Name + "' with schedule '" + message.Attributes["hex.schedule"] + "' on ID:" + message.Attributes["hex.id"])
				config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
				msg := deepcopy.Copy(message).(models.Message)
				go runRule(rule, msg, outputMsgs, state, *plugins, config)
			}

			// match for webhook
			if rule.Active && rule.URL != "" && parse.Match(rule.URL, message.Attributes["hex.url"]) {
				match = true
				config.Logger.Debug("Matcher - Matched Rule '" + rule.Name + "' with url '" + message.Attributes["hex.url"] + "' on ID:" + message.Attributes["hex.id"])
				config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
				msg := deepcopy.Copy(message).(models.Message)
				go runRule(rule, msg, outputMsgs, state, *plugins, config)
			}

		}
		if !match && message.Attributes["hex.service"] == "command" {
			StopPlugins(*plugins, config)
			os.Exit(0)

		}
	}
}

func runRule(rule models.Rule, message models.Message, outputMsgs chan<- models.Message, state map[string]bool, plugins map[string]models.Plugin, config models.Config) {
	config.Logger.Debug("Matcher - Running Rule " + rule.Name + " for ID:" + message.Attributes["hex.id"])
	config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
	message.Attributes["hex.rule.runid"] = models.MessageID()
	message.Attributes["hex.rule.name"] = rule.Name
	message.Attributes["hex.rule.format"] = strconv.FormatBool(rule.Format)
	message.Attributes["hex.rule.channel"] = rule.Channel
	message.Attributes["hex.rule.threaded"] = strconv.FormatBool(rule.Threaded)
	for key, value := range config.Vars {
		message.Attributes["hex.var."+key] = value
	}
	ruleResult := true
	lastAction := true
	lastConfig := rule.Actions[0].Config
	for actionCounter, action := range rule.Actions {
		config.Logger.Debug("Matcher - Evaluating Action " + rule.Name + "." + action.Type + " [" + strconv.Itoa(actionCounter) + "] for ID:" + message.Attributes["hex.id"])
		config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
		if lastAction || action.RunOnFail {
			if _, exists := plugins[action.Type]; exists {
				startTime := models.MessageTimestamp()
				attrName := "hex.output." + strconv.Itoa(actionCounter)
				if action.LastConfig {
					action.Config = lastConfig
				}
				for key, _ := range action.Config {
					action.Config[key] = parse.Substitute(action.Config[key], message.Attributes)
				}
				cmd := parse.Substitute(action.Command, message.Attributes)
				args := hexplugin.Arguments{
					Debug:   rule.Debug || config.Debug,
					Command: cmd,
					Config:  action.Config,
				}
				resp := plugins[action.Type].Action.Perform(args)
				if !resp.Success {
					ruleResult = false
				}
				lastAction = resp.Success
				lastConfig = action.Config
				message.Attributes[attrName+".duration"] = strconv.FormatInt(models.MessageTimestamp()-startTime, 10)
				if action.OutputToVar {
					message.Attributes[attrName+".response"] = strings.TrimSpace(resp.Output)
				} else if !action.HideOutput {
					if !action.OutputFailOnly || (action.OutputFailOnly && !resp.Success) {
						if !rule.GroupOutput {
							message.Outputs = []models.Output{models.Output{
								Rule:     rule.Name,
								Response: resp.Output,
								Success:  resp.Success,
								Command:  cmd,
							}}
						} else {
							message.Outputs = append(message.Outputs, models.Output{
								Rule:     rule.Name,
								Response: resp.Output,
								Success:  resp.Success,
								Command:  cmd,
							})
						}
					}
				}
			} else {
				config.Logger.Error("Matcher - Missing Plugin " + action.Type)
			}
		}
		if (rule.GroupOutput && actionCounter+1 == len(rule.Actions)) || (!rule.GroupOutput) {
			message.EndTime = models.MessageTimestamp()
			if !rule.OutputOnChange && (!rule.OutputFailOnly || !ruleResult) {
				config.Logger.Debug("Matcher - Output ID:" + message.Attributes["hex.id"])
				config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
				outputMsgs <- message
			} else if rule.OutputOnChange && ruleResult != state[rule.Id] {
				config.Logger.Debug("Matcher - Output Change ID:" + message.Attributes["hex.id"])
				config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
				outputMsgs <- message
			} else {
				config.Logger.Debug("Matcher - Discarding ID:" + message.Attributes["hex.id"])
				config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
			}
		}
	}
	state[rule.Id] = ruleResult
}
<commit_msg>fixing an output bug around the group_output<commit_after>package core

import (
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/hexbotio/hex-plugin"
	"github.com/hexbotio/hex/models"
	"github.com/hexbotio/hex/parse"
	"github.com/mohae/deepcopy"
)

// Matcher function
func Matcher(inputMsgs <-chan models.Message, outputMsgs chan<- models.Message, plugins *map[string]models.Plugin, rules *map[string]models.Rule, config models.Config) {
	state := make(map[string]bool)
	for _, rule := range *rules {
		state[rule.Id] = true
	}
	for {
		message := <-inputMsgs
		match := false
		config.Logger.Debug("Matcher - Eval of Message ID:" + message.Attributes["hex.id"])
		config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
		if parse.EitherMember(config.ACL, message.Attributes["hex.user"], message.Attributes["hex.channel"]) {
			Commands(message, outputMsgs, rules, config)
		}
		for _, rule := range *rules {

			// match for input
			if rule.Active && rule.Match != "" && parse.Match(rule.Match, message.Attributes["hex.input"]) {
				if parse.EitherMember(config.ACL, message.Attributes["hex.user"], message.Attributes["hex.channel"]) {
					if parse.EitherMember(rule.ACL, message.Attributes["hex.user"], message.Attributes["hex.channel"]) {
						match = true
						config.Logger.Debug("Matcher - Matched Rule '" + rule.Name + "' with input '" + message.Attributes["hex.input"] + "' on ID:" + message.Attributes["hex.id"])
						config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
						msg := deepcopy.Copy(message).(models.Message)
						go runRule(rule, msg, outputMsgs, state, *plugins, config)
					}
				}
			}

			// match for schedule
			if rule.Active && rule.Schedule != "" && rule.Schedule == message.Attributes["hex.schedule"] {
				match = true
				config.Logger.Debug("Matcher - Matched Rule '" + rule.Name + "' with schedule '" + message.Attributes["hex.schedule"] + "' on ID:" + message.Attributes["hex.id"])
				config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
				msg := deepcopy.Copy(message).(models.Message)
				go runRule(rule, msg, outputMsgs, state, *plugins, config)
			}

			// match for webhook
			if rule.Active && rule.URL != "" && parse.Match(rule.URL, message.Attributes["hex.url"]) {
				match = true
				config.Logger.Debug("Matcher - Matched Rule '" + rule.Name + "' with url '" + message.Attributes["hex.url"] + "' on ID:" + message.Attributes["hex.id"])
				config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
				msg := deepcopy.Copy(message).(models.Message)
				go runRule(rule, msg, outputMsgs, state, *plugins, config)
			}

		}
		if !match && message.Attributes["hex.service"] == "command" {
			StopPlugins(*plugins, config)
			os.Exit(0)

		}
	}
}

func runRule(rule models.Rule, message models.Message, outputMsgs chan<- models.Message, state map[string]bool, plugins map[string]models.Plugin, config models.Config) {
	config.Logger.Debug("Matcher - Running Rule " + rule.Name + " for ID:" + message.Attributes["hex.id"])
	config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
	message.Attributes["hex.rule.runid"] = models.MessageID()
	message.Attributes["hex.rule.name"] = rule.Name
	message.Attributes["hex.rule.format"] = strconv.FormatBool(rule.Format)
	message.Attributes["hex.rule.channel"] = rule.Channel
	message.Attributes["hex.rule.threaded"] = strconv.FormatBool(rule.Threaded)
	for key, value := range config.Vars {
		message.Attributes["hex.var."+key] = value
	}
	ruleResult := true
	lastAction := true
	lastConfig := rule.Actions[0].Config
	for actionCounter, action := range rule.Actions {
		config.Logger.Debug("Matcher - Evaluating Action " + rule.Name + "." +
			action.Type + " [" + strconv.Itoa(actionCounter) + "] for ID:" + message.Attributes["hex.id"])
		config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
		if lastAction || action.RunOnFail {
			if _, exists := plugins[action.Type]; exists {
				startTime := models.MessageTimestamp()
				attrName := "hex.output." + strconv.Itoa(actionCounter)
				if action.LastConfig {
					action.Config = lastConfig
				}
				for key, _ := range action.Config {
					action.Config[key] = parse.Substitute(action.Config[key], message.Attributes)
				}
				cmd := parse.Substitute(action.Command, message.Attributes)
				args := hexplugin.Arguments{
					Debug:   rule.Debug || config.Debug,
					Command: cmd,
					Config:  action.Config,
				}
				resp := plugins[action.Type].Action.Perform(args)
				if !resp.Success {
					ruleResult = false
				}
				lastAction = resp.Success
				lastConfig = action.Config
				message.Attributes[attrName+".duration"] = strconv.FormatInt(models.MessageTimestamp()-startTime, 10)
				if action.OutputToVar {
					message.Attributes[attrName+".response"] = strings.TrimSpace(resp.Output)
				} else if !action.HideOutput {
					if !action.OutputFailOnly || (action.OutputFailOnly && !resp.Success) {
						if !rule.GroupOutput {
							message.Outputs = []models.Output{models.Output{
								Rule:     rule.Name,
								Response: resp.Output,
								Success:  resp.Success,
								Command:  cmd,
							}}
							message.EndTime = models.MessageTimestamp()
							if !rule.OutputOnChange && (!rule.OutputFailOnly || !ruleResult) {
								outputMsgs <- message
							} else if rule.OutputOnChange && ruleResult != state[rule.Id] {
								outputMsgs <- message
							}
						} else {
							message.Outputs = append(message.Outputs, models.Output{
								Rule:     rule.Name,
								Response: resp.Output,
								Success:  resp.Success,
								Command:  cmd,
							})
						}
					}
				}
			} else {
				config.Logger.Error("Matcher - Missing Plugin " + action.Type)
			}
		}
	}
	if rule.GroupOutput {
		message.EndTime = models.MessageTimestamp()
		if !rule.OutputOnChange && (!rule.OutputFailOnly || !ruleResult) {
			outputMsgs <- message
		} else if rule.OutputOnChange && ruleResult != state[rule.Id] {
			outputMsgs <- message
		}
	}
	config.Logger.Debug("Matcher - Output ID:" + message.Attributes["hex.id"])
	config.Logger.Trace(fmt.Sprintf("Message: %+v", message))
	state[rule.Id] = ruleResult
}
<|endoftext|>


<commit_before>package po

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"net/textproto"
	"sort"
	"strings"
)

// File represents a PO file.
type File struct {
	Header    textproto.MIMEHeader
	Messages  []Message
	Pluralize PluralSelector
}

// Message stores a gettext message.
type Message struct {
	Comment           // message comments from PO file
	Ctxt     string   // msgctxt: message context, if any
	Id       string   // msgid: untranslated singular string
	IdPlural string   // msgid_plural: untranslated plural string
	Str      []string // msgstr or msgstr[n]: translated strings
}

// Comment stores meta-data from a gettext message.
type Comment struct {
	TranslatorComments []string
	ExtractedComments  []string
	References         []string
	Flags              []string
	PrevCtxt           string
	PrevId             string
	PrevIdPlural       string
}

// Parse reads the content of a PO file and returns the list of messages.
func Parse(r io.Reader) (File, error) {
	var msgs []Message
	var scan = newScanner(r)
	for scan.nextmsg() {
		// NOTE: the source code order of these fields is important.
		var msg = Message{
			Comment: Comment{
				TranslatorComments: scan.mul("# "),
				ExtractedComments:  scan.mul("#."),
				References:         scan.spc("#:"),
				Flags:              scan.spc("#,"),
				PrevCtxt:           scan.one("#| msgctxt"),
				PrevId:             scan.one("#| msgid"),
				PrevIdPlural:       scan.one("#| msgid_plural"),
			},
			Ctxt:     scan.quo("msgctxt"),
			Id:       scan.quo("msgid"),
			IdPlural: scan.quo("msgid_plural"),
			Str:      scan.msgstr(),
		}
		msgs = append(msgs, msg)
	}
	if scan.Err() != nil {
		return File{}, scan.Err()
	}

	var header textproto.MIMEHeader
	if msgs[0].Id == "" && len(msgs[0].Str) == 1 {
		var err error
		header, err = textproto.NewReader(bufio.NewReader(strings.NewReader(msgs[0].Str[0]))).
			ReadMIMEHeader()
		if err != nil && err != io.EOF {
			return File{}, err
		}
		msgs = msgs[1:]
	}

	var pluralize PluralSelector
	if pluralForms := header.Get("Plural-Forms"); pluralForms != "" {
		pluralize = lookupPluralSelector(pluralForms)
		if pluralize == nil {
			return File{}, fmt.Errorf("unrecognized plural form selector: %v", pluralForms)
		}
	}
	if pluralize == nil {
		pluralize = PluralSelectorForLanguage(header.Get("Language"))
	}

	return File{header, msgs, pluralize}, nil
}

// Write the PO file to a destination writer.
func (f File) WriteTo(w io.Writer) (n int64, err error) {
	var wr = newWriter()
	// TODO: Probably better to make a type for the header and implement WriterTo
	if len(f.Header) > 0 {
		wr.quo("msgid ", "")
		var keys []string
		for k := range f.Header {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		var buf bytes.Buffer
		for _, k := range keys {
			buf.WriteString(k + ": " + f.Header.Get(k) + "\n")
		}
		wr.quo("msgstr ", buf.String())
		wr.newline()
	}
	for _, msg := range f.Messages {
		wr.from(msg)
		wr.newline()
	}
	return wr.to(w)
}

// Write the PO Message to a destination writer.
func (m Message) WriteTo(w io.Writer) (n int64, err error) {
	var wr = newWriter()
	wr.from(m.Comment)
	wr.opt("msgctxt ", m.Ctxt)
	wr.quo("msgid ", m.Id)
	wr.opt("msgid_plural ", m.IdPlural)
	if len(m.IdPlural) == 0 {
		wr.msgstr(m.Str)
	} else {
		wr.plural(m.Str)
	}

	return wr.to(w)
}

// Write the comment to the given writer.
func (c Comment) WriteTo(w io.Writer) (n int64, err error) {
	var wr = newWriter()
	wr.mul("#  ", c.TranslatorComments)
	wr.mul("#. ", c.ExtractedComments)
	wr.spc("#: ", c.References)
	wr.spc("#, ", c.Flags)
	wr.one("#| msgctxt ", c.PrevCtxt)
	wr.one("#| msgid ", c.PrevId)
	wr.one("#| msgid_plural ", c.PrevIdPlural)
	return wr.to(w)
}
<commit_msg>gettext: add empty/nil handling for po files. R=telliott, robfig<commit_after>package po

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"net/textproto"
	"sort"
	"strings"
)

// File represents a PO file.
type File struct {
	Header    textproto.MIMEHeader
	Messages  []Message
	Pluralize PluralSelector
}

// Message stores a gettext message.
type Message struct {
	Comment           // message comments from PO file
	Ctxt     string   // msgctxt: message context, if any
	Id       string   // msgid: untranslated singular string
	IdPlural string   // msgid_plural: untranslated plural string
	Str      []string // msgstr or msgstr[n]: translated strings
}

// Comment stores meta-data from a gettext message.
type Comment struct {
	TranslatorComments []string
	ExtractedComments  []string
	References         []string
	Flags              []string
	PrevCtxt           string
	PrevId             string
	PrevIdPlural       string
}

// Parse reads the content of a PO file and returns the list of messages.
func Parse(r io.Reader) (File, error) {
	var msgs []Message
	var scan = newScanner(r)
	for scan.nextmsg() {
		// NOTE: the source code order of these fields is important.
		var msg = Message{
			Comment: Comment{
				TranslatorComments: scan.mul("# "),
				ExtractedComments:  scan.mul("#."),
				References:         scan.spc("#:"),
				Flags:              scan.spc("#,"),
				PrevCtxt:           scan.one("#| msgctxt"),
				PrevId:             scan.one("#| msgid"),
				PrevIdPlural:       scan.one("#| msgid_plural"),
			},
			Ctxt:     scan.quo("msgctxt"),
			Id:       scan.quo("msgid"),
			IdPlural: scan.quo("msgid_plural"),
			Str:      scan.msgstr(),
		}
		msgs = append(msgs, msg)
	}
	if scan.Err() != nil {
		return File{}, scan.Err()
	}

	if len(msgs) == 0 {
		return File{}, nil
	}

	var header textproto.MIMEHeader
	if msgs[0].Id == "" && len(msgs[0].Str) == 1 {
		var err error
		header, err = textproto.NewReader(bufio.NewReader(strings.NewReader(msgs[0].Str[0]))).
			ReadMIMEHeader()
		if err != nil && err != io.EOF {
			return File{}, err
		}
		msgs = msgs[1:]
	}

	var pluralize PluralSelector
	if pluralForms := header.Get("Plural-Forms"); pluralForms != "" {
		pluralize = lookupPluralSelector(pluralForms)
		if pluralize == nil {
			return File{}, fmt.Errorf("unrecognized plural form selector: %v", pluralForms)
		}
	}
	if pluralize == nil {
		pluralize = PluralSelectorForLanguage(header.Get("Language"))
	}

	return File{header, msgs, pluralize}, nil
}

// Write the PO file to a destination writer.
func (f File) WriteTo(w io.Writer) (n int64, err error) {
	var wr = newWriter()
	// TODO: Probably better to make a type for the header and implement WriterTo
	if len(f.Header) > 0 {
		wr.quo("msgid ", "")
		var keys []string
		for k := range f.Header {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		var buf bytes.Buffer
		for _, k := range keys {
			buf.WriteString(k + ": " + f.Header.Get(k) + "\n")
		}
		wr.quo("msgstr ", buf.String())
		wr.newline()
	}
	for _, msg := range f.Messages {
		wr.from(msg)
		wr.newline()
	}
	return wr.to(w)
}

// Write the PO Message to a destination writer.
func (m Message) WriteTo(w io.Writer) (n int64, err error) {
	var wr = newWriter()
	wr.from(m.Comment)
	wr.opt("msgctxt ", m.Ctxt)
	wr.quo("msgid ", m.Id)
	wr.opt("msgid_plural ", m.IdPlural)
	if len(m.IdPlural) == 0 {
		wr.msgstr(m.Str)
	} else {
		wr.plural(m.Str)
	}

	return wr.to(w)
}

// Write the comment to the given writer.
func (c Comment) WriteTo(w io.Writer) (n int64, err error) {
	var wr = newWriter()
	wr.mul("#  ", c.TranslatorComments)
	wr.mul("#. ", c.ExtractedComments)
	wr.spc("#: ", c.References)
	wr.spc("#, ", c.Flags)
	wr.one("#| msgctxt ", c.PrevCtxt)
	wr.one("#| msgid ", c.PrevId)
	wr.one("#| msgid_plural ", c.PrevIdPlural)
	return wr.to(w)
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	. "github.com/arkenio/goarken"
	"github.com/codegangsta/cli"
	"github.com/coreos/go-etcd/etcd"
	"os"
	"strings"
	"text/tabwriter"
	"text/template"
)

type ServiceCommand struct {
	Watcher *Watcher
	Client  *etcd.Client
	Cli     *cli.Context
}

func (sc *ServiceCommand) getServiceCluster() (*ServiceCluster, error) {
	if len(sc.Cli.Args()) > 0 {
		serviceName := sc.Cli.Args()[0]
		if cluster, ok := sc.Watcher.Services[serviceName]; ok {
			return cluster, nil
		} else {
			return nil, errors.New("Service not foudn")
		}

	} else {
		return nil, errors.New("You must pass the service name as an argument")
	}

}

func (sc *ServiceCommand) List(stop chan interface{}) error {

	statusFilter := sc.Cli.String("status")

	tpl := sc.Cli.String("template")

	if tpl == "" {
		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 0, 8, 2, '\t', 0)
		fmt.Fprintln(w, "Name\tIndex\tDomain\tStatus\tLastAccess")
		fmt.Fprintln(w, "----\t-----\t------\t------\t----------")
		for _, cluster := range sc.Watcher.Services {
			for _, service := range cluster.GetInstances() {

				if statusFilter == "" || statusFilter == service.Status.Compute() {
					fmt.Fprintln(w, strings.Join([]string{
						service.Name,
						service.Index,
						service.Domain,
						service.Status.Compute(),
						fmt.Sprintf("%s", service.LastAccess),
					}, "\t"))
				}
			}
		}
		fmt.Fprintln(w)
		w.Flush()
	} else {
		t := template.Must(template.New("service").Parse(tpl))
		for _, cluster := range sc.Watcher.Services {
			for _, service := range cluster.GetInstances() {
				if statusFilter == "" || statusFilter == service.Status.Compute() {
					t.Execute(os.Stdout, service)
					fmt.Fprintln(os.Stdout, "")
				}
			}
		}
	}

	return nil
}

func (sc *ServiceCommand) Cat(stop chan interface{}) error {
	cluster, err := sc.getServiceCluster()
	if err != nil {
		return err
	} else {
		sc.renderService(cluster)
	}
	return nil
}

func (sc *ServiceCommand) Start(stop chan interface{}) error {
	cluster, err := sc.getServiceCluster()
	if err != nil {
		return err
	}

	for _, service := range cluster.GetInstances() {
		err = service.Start()
		if err != nil {
			break
		}
	}

	return err
}

func (sc *ServiceCommand) Stop(stop chan interface{}) error {
	cluster, err := sc.getServiceCluster()
	if err != nil {
		return err
	}

	for _, service := range cluster.GetInstances() {
		err = service.Stop()
		if err != nil {
			break
		}
	}

	return err
}

func (sc *ServiceCommand) Passivate(stop chan interface{}) error {
	cluster, err := sc.getServiceCluster()
	if err != nil {
		return err
	}

	for _, service := range cluster.GetInstances() {
		err = service.Passivate()
		if err != nil {
			break
		}
	}

	return err
}

func (sc *ServiceCommand) renderService(cluster *ServiceCluster) {
	tpl := sc.Cli.String("template")

	if tpl == "" {

		tpl = `{{range $index, $service := .GetInstances }}===========================================
    Node index : {{.Index}}
    Name : {{.Name}}
    UnitName : {{.UnitName }}
    Etcd key : {{.NodeKey }}
    Domain name : {{.Domain}}
    Location : {{.Location.Host}}:{{.Location.Port}}
    LastAccess: {{.LastAccess}}
    Status: {{.Status.Compute}}
      * expected : {{.Status.Expected}}
      * current : {{.Status.Current}}
      * alive : {{.Status.Alive}}
    {{end}}
`
	}

	t := template.Must(template.New("service").Parse(tpl))
	t.Execute(os.Stdout, cluster)
}
<commit_msg>NXIO-345 align with goarken lib change<commit_after>package main

import (
	"errors"
	"fmt"
	. "github.com/arkenio/goarken"
	"github.com/codegangsta/cli"
	"github.com/coreos/go-etcd/etcd"
	"os"
	"strings"
	"text/tabwriter"
	"text/template"
)

type ServiceCommand struct {
	Watcher *Watcher
	Client  *etcd.Client
	Cli     *cli.Context
}

func (sc *ServiceCommand) getServiceCluster() (*ServiceCluster, error) {
	if len(sc.Cli.Args()) > 0 {
		serviceName := sc.Cli.Args()[0]
		return GetServiceClusterFromPath("/service/"+serviceName, sc.Client)
	} else {
		return nil, errors.New("You must pass the service name as an argument")
	}

}

func (sc *ServiceCommand) List(stop chan interface{}) error {

	statusFilter := sc.Cli.String("status")

	tpl := sc.Cli.String("template")

	if tpl == "" {
		w := new(tabwriter.Writer)
		w.Init(os.Stdout, 0, 8, 2, '\t', 0)
		fmt.Fprintln(w, "Name\tIndex\tDomain\tStatus\tLastAccess")
		fmt.Fprintln(w, "----\t-----\t------\t------\t----------")
		for _, cluster := range sc.Watcher.Services {
			for _, service := range cluster.GetInstances() {

				if statusFilter == "" || statusFilter == service.Status.Compute() {
					fmt.Fprintln(w, strings.Join([]string{
						service.Name,
						service.Index,
						service.Domain,
						service.Status.Compute(),
						fmt.Sprintf("%s", service.LastAccess),
					}, "\t"))
				}
			}
		}
		fmt.Fprintln(w)
		w.Flush()
	} else {
		t := template.Must(template.New("service").Parse(tpl))
		for _, cluster := range sc.Watcher.Services {
			for _, service := range cluster.GetInstances() {
				if statusFilter == "" || statusFilter == service.Status.Compute() {
					t.Execute(os.Stdout, service)
					fmt.Fprintln(os.Stdout, "")
				}
			}
		}
	}

	return nil
}

func (sc *ServiceCommand) Cat(stop chan interface{}) error {
	cluster, err := sc.getServiceCluster()
	if err != nil {
		return err
	} else {
		sc.renderService(cluster)
	}
	return nil
}

func (sc *ServiceCommand) Start(stop chan interface{}) error {
	cluster, err := sc.getServiceCluster()
	if err != nil {
		return err
	}

	for _, service := range cluster.GetInstances() {
		err = service.Start(sc.Client)
		if err != nil {
			break
		}
	}

	return err
}

func (sc *ServiceCommand) Stop(stop chan interface{}) error {
	cluster, err := sc.getServiceCluster()
	if err != nil {
		return err
	}

	for _, service := range cluster.GetInstances() {
		err = service.Stop(sc.Client)
		if err != nil {
			break
		}
	}

	return err
}

func (sc *ServiceCommand) Passivate(stop chan interface{}) error {
	cluster, err := sc.getServiceCluster()
	if err != nil {
		return err
	}

	for _, service := range cluster.GetInstances() {
		err = service.Passivate(sc.Client)
		if err != nil {
			break
		}
	}

	return err
}

func (sc *ServiceCommand) renderService(cluster *ServiceCluster) {
	tpl := sc.Cli.String("template")

	if tpl == "" {

		tpl = `{{range $index, $service := .GetInstances }}===========================================
    Node index : {{.Index}}
    Name : {{.Name}}
    UnitName : {{.UnitName }}
    Etcd key : {{.NodeKey }}
    Domain name : {{.Domain}}
    Location : {{.Location.Host}}:{{.Location.Port}}
    LastAccess: {{.LastAccess}}
    Status: {{.Status.Compute}}
      * expected : {{.Status.Expected}}
      * current : {{.Status.Current}}
      * alive : {{.Status.Alive}}
    {{end}}
`
	}

	t := template.Must(template.New("service").Parse(tpl))
	t.Execute(os.Stdout, cluster)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"os"
)

var newline = []uint8("\n")

func main() {
	bufIn := bufio.NewReader(fileFromArguments())
	lastLine := []uint8("")

	arr := make([]byte, 0, 1024*1024)
	buf := bytes.NewBuffer(arr)
	lineNum := int64(1)
	for {
		line, isPrefix, err := bufIn.ReadLine()
		if err != nil && err != io.EOF {
			genericError(err)
		}

		lastLine = append(lastLine, line...)
		if !isPrefix && len(lastLine) != 0 {
			indentAndPrint(buf, lastLine, lineNum)
			lineNum += 1
			lastLine = []uint8("")
		}

		if err == io.EOF {
			os.Exit(0)
		}
	}
}

func indentAndPrint(buf *bytes.Buffer, js []uint8, lineNum int64) {
	jsErr := json.Indent(buf, js, "", "  ")
	if jsErr != nil {
		malformedJSON(jsErr, js, lineNum)
	}
	os.Stdout.Write(buf.Bytes())
	os.Stdout.Write(newline)
	buf.Reset()
}

func malformedJSON(jsErr error, js []uint8, lineNum int64) {
	os.Stdout.Sync()

	synErr, isSynError := (jsErr).(*json.SyntaxError)

	fmt.Fprintf(os.Stderr, "ERROR: Broken json on line %d: %s\n", lineNum, jsErr)

	if isSynError {
		prefix := ""
		suffix := ""

		begin := 0
		if synErr.Offset > 15 {
			begin = int(synErr.Offset - 15)
			prefix = "..."
		}

		end := begin + 30
		if end > len(js) {
			end = len(js)
		} else {
			suffix = "..."
		}

		fmt.Fprintf(os.Stderr, "  Context: %s%s%s\n", prefix, js[begin:end], suffix)
	}

	os.Exit(1)
}

func fileFromArguments() *os.File {
	flag.Parse()
	args := flag.Args()
	if len(args) > 1 {
		msg := fmt.Sprintf("only specify 0 or 1 files in the arguments, not %d\n", len(args))
		genericError(errors.New(msg))
	}
	if len(args) == 0 {
		return os.Stdin
	}

	file, err := os.OpenFile(args[0], os.O_RDONLY, 0)
	if err != nil {
		genericError(err)
	}
	return file
}

func genericError(err error) {
	os.Stdout.Sync()
	fmt.Fprintf(os.Stderr, "ERROR: %s", err)
	os.Exit(2)
}
<commit_msg>override -help to be helpful<commit_after>package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"os"
)

var newline = []uint8("\n")

func main() {

	var help = flag.Bool("help", false, "help")
	flag.Parse()
	if *help {
		cmd := os.Args[0]
		if cmd[0:2] == "./" {
			cmd = cmd[2:]
		}
		fmt.Fprintf(os.Stderr, "Usage: "+cmd+" [file]"+"\n")
		fmt.Fprintf(os.Stderr, "   or: $COMMAND | "+cmd+"\n")
		os.Exit(0)
	}

	bufIn := bufio.NewReader(fileFromArguments())
	lastLine := []uint8("")

	arr := make([]byte, 0, 1024*1024)
	buf := bytes.NewBuffer(arr)
	lineNum := int64(1)
	for {
		line, isPrefix, err := bufIn.ReadLine()
		if err != nil && err != io.EOF {
			genericError(err)
		}

		lastLine = append(lastLine, line...)
		if !isPrefix && len(lastLine) != 0 {
			indentAndPrint(buf, lastLine, lineNum)
			lineNum += 1
			lastLine = []uint8("")
		}

		if err == io.EOF {
			os.Exit(0)
		}
	}
}

func indentAndPrint(buf *bytes.Buffer, js []uint8, lineNum int64) {
	jsErr := json.Indent(buf, js, "", "  ")
	if jsErr != nil {
		malformedJSON(jsErr, js, lineNum)
	}
	os.Stdout.Write(buf.Bytes())
	os.Stdout.Write(newline)
	buf.Reset()
}

func malformedJSON(jsErr error, js []uint8, lineNum int64) {
	os.Stdout.Sync()

	synErr, isSynError := (jsErr).(*json.SyntaxError)

	fmt.Fprintf(os.Stderr, "ERROR: Broken json on line %d: %s\n", lineNum, jsErr)

	if isSynError {
		prefix := ""
		suffix := ""

		begin := 0
		if synErr.Offset > 15 {
			begin = int(synErr.Offset - 15)
			prefix = "..."
		}

		end := begin + 30
		if end > len(js) {
			end = len(js)
		} else {
			suffix = "..."
		}

		fmt.Fprintf(os.Stderr, "  Context: %s%s%s\n", prefix, js[begin:end], suffix)
	}

	os.Exit(1)
}

func fileFromArguments() *os.File {
	flag.Parse()
	args := flag.Args()
	if len(args) > 1 {
		msg := fmt.Sprintf("only specify 0 or 1 files in the arguments, not %d\n", len(args))
		genericError(errors.New(msg))
	}
	if len(args) == 0 {
		return os.Stdin
	}

	file, err := os.OpenFile(args[0], os.O_RDONLY, 0)
	if err != nil {
		genericError(err)
	}
	return file
}

func genericError(err error) {
	os.Stdout.Sync()
	fmt.Fprintf(os.Stderr, "ERROR: %s", err)
	os.Exit(2)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"os/signal"
)

type LogplexPrint struct{}

func (*LogplexPrint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Printf("Request: %#v", *r)
	w.WriteHeader(http.StatusNoContent)
}

func main() {
	s := httptest.NewTLSServer(&LogplexPrint{})
	fmt.Println(s.URL)

	// Signal handling:
	sigch := make(chan os.Signal)
	signal.Notify(sigch, os.Interrupt, os.Kill)
	for sig := range sigch {
		log.Printf("got signal %v", sig)
		if sig == os.Kill {
			os.Exit(2)
		} else if sig == os.Interrupt {
			os.Exit(0)
		}
	}
}
<commit_msg>Use httputil DumpRequest instead of just %v formatting<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"os"
	"os/signal"
)

type LogplexPrint struct{}

func (*LogplexPrint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	dump, err := httputil.DumpRequest(r, true)
	if err != nil {
		log.Printf("Could not dump request: %#v", err)
	}

	log.Printf("%s", dump)

	// Respond saying everything is OK.
	w.WriteHeader(http.StatusNoContent)

}

func main() {
	s := httptest.NewTLSServer(&LogplexPrint{})
	fmt.Println(s.URL)

	// Signal handling:
	sigch := make(chan os.Signal)
	signal.Notify(sigch, os.Interrupt, os.Kill)
	for sig := range sigch {
		log.Printf("got signal %v", sig)
		if sig == os.Kill {
			os.Exit(2)
		} else if sig == os.Interrupt {
			os.Exit(0)
		}
	}
}
<|endoftext|>


<commit_before>package kdniao

import (
	"net/http"
	"net/url"
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"io"
	"strings"
	"fmt"
	"errors"
	"github.com/ridewindx/mel"
	"github.com/ridewindx/mel/binding"
	"github.com/ridewindx/melware"
	"go.uber.org/zap"
)

const (
	BASE_URL      = "http://api.kdniao.cc/api"
	TEST_BASE_URL = "http://testapi.kdniao.cc:8081/api"
)

type Client struct {
	EBusinessID string
	AppKey      string
	*http.Client
}

func NewClient(eid, appKey string, httpClient ...*http.Client) *Client {
	var c *http.Client
	if len(httpClient) > 0 {
		c = httpClient[0]
	} else {
		c = http.DefaultClient
	}
	return &Client{
		EBusinessID: eid,
		AppKey:      appKey,
		Client:      c,
	}
}

type Receiver struct {
	Company      string `json:"Company,omitempty"` // 收件人公司
	Name         string `json:"Name"`              // 收件人
	Tel          string `json:"Tel,omitempty"`     // 电话与手机，必填一个
	Mobile       string `json:"Mobile,omitempty"`
	PostCode     string `json:"PostCode,omitempty"`    // 收件人邮编
	ProvinceName string `json:"ProvinceName"`          // 收件省（如广东省，不要缺少“省”）
	CityName     string `json:"CityName"`              // 收件市（如深圳市，不要缺少“市”）
	ExpAreaName  string `json:"ExpAreaName,omitempty"` // 收件区（如福田区，不要缺少“区”或“县”）
	Address      string `json:"Address"`               // 收件人详细地址
}

type Sender struct {
	Company      string `json:"Company,omitempty"` // 发件人公司
	Name         string `json:"Name"`              // 发件人
	Tel          string `json:"Tel,omitempty"`     // 电话与手机，必填一个
	Mobile       string `json:"Mobile,omitempty"`
	PostCode     string `json:"PostCode,omitempty"`    //	发件人邮编
	ProvinceName string `json:"ProvinceName"`          // 发件省（如广东省，不要缺少“省”）
	CityName     string `json:"CityName"`              // 发件市（如深圳市，不要缺少“市”）
	ExpAreaName  string `json:"ExpAreaName,omitempty"` // 发件区（如福田区，不要缺少“区”或“县”）
	Address      string `json:"Address"`               // 发件详细地址
}

type AddService struct {
	Name       string `json:"Name,omitempty"`       // 增值服务名称
	Value      string `json:"Value,omitempty"`      // 增值服务值
	CustomerID string `json:"CustomerID,omitempty"` // 客户标识(选填)
}

type Commodity struct {
	GoodsName     string  `json:"GoodsName"`               // 商品名称
	GoodsCode     string  `json:"GoodsCode,omitempty"`     // 商品编码
	Goodsquantity int     `json:"Goodsquantity,omitempty"` // 件数
	GoodsPrice    float64 `json:"GoodsPrice,omitempty"`    // 商品价格
	GoodsWeight   float64 `json:"GoodsWeight,omitempty"`   // 商品重量kg
	GoodsDesc     string  `json:"GoodsDesc,omitempty"`     // 商品描述
	GoodsVol      float64 `json:"GoodsVol,omitempty"`      // 商品体积m3
}

type EOrderReq struct {
	CallBack              string     `json:"Callback,omitempty"`     // 用户自定义回调信息
	MemberID              string     `json:"MemberID,omitempty"`     // 会员标识，平台方与快递鸟统一用户标识的商家ID
	CustomerName          string     `json:"CustomerName,omitempty"` // 电子面单客户账号，（与快递网点申请或通过快递鸟官网申请或通过申请电子面单客户号申请）
	CustomerPwd           string     `json:"CustomerPwd,omitempty"`  // 电子面单密码
	SendSite              string     `json:"SendSite,omitempty"`     // 收件网点标识
	ShipperCode           string     `json:"ShipperCode"`            // 快递公司编码
	LogisticCode          string     `json:"LogisticCode,omitempty"` // 快递单号
	ThrOrderCode          string     `json:"ThrOrderCode,omitempty"` // 第三方订单号
	OrderCode             string     `json:"OrderCode"`              // 订单编号
	MonthCode             string     `json:"MonthCode,omitempty"`    // 月结编码
	PayType               int        `json:"PayType"`                // 邮费支付方式: 1-现付，2-到付，3-月结，4-第三方支付
	ExpType               string     `json:"ExpType"`                // 快递类型：1-标准快件
	IsNotice              int        `json:"IsNotice,omitempty"`     // 是否通知快递员上门揽件：0-通知；1-不通知；不填则默认为1
	Cost                  float64    `json:"Cost,omitempty"`         // 寄件费（运费）
	OtherCost             float64    `json:"OtherCost,omitempty"`    // 其他费用
	Receiver              Receiver   `json:"Receiver"`
	Sender                Sender     `json:"Sender"`
	StartDate             string     `json:"StartDate,omitempty"` // 上门取货时间段: "yyyy-MM-dd HH:mm:ss"格式化，本文中所有时间格式相同
	EndDate               string     `json:"EndDate,omitempty"`
	Weight                float64    `json:"Weight,omitempty"`   // 物品总重量kg
	Quantity              int        `json:"Quantity,omitempty"` // 件数/包裹数
	Volume                string     `json:"Volume,omitempty"`   // 物品总体积m3
	Remark                string     `json:"Remark,omitempty"`   // 备注
	AddService            AddService `json:"AddService"`
	Commodity             Commodity  `json:"Commodity"`
	IsReturnPrintTemplate string     `json:"IsReturnPrintTemplate,omitempty"` // 返回电子面单模板：0-不需要；1-需要
	IsSendMessage         int        `json:"IsSendMessage,omitempty"`         // 是否订阅短信：0-不需要；1-需要
	TemplateSize          string     `json:"TemplateSize,omitempty"`          // 模板尺寸
}

type EOrderRep struct {
	EBusinessID string `json:"EBusinessID"` // 电商用户ID
	Order struct {
		OrderCode       string `json:"OrderCode"`                 // 订单编号
		ShipperCode     string `json:"ShipperCode"`               // 快递公司编码
		LogisticCode    string `json:"LogisticCode"`              // 快递单号
		MarkDestination string `json:"MarkDestination,omitempty"` // 大头笔
		OriginCode      string `json:"OriginCode,omitempty"`      // 始发地区域编码
		OriginName      string `json:"OriginName,omitempty"`      // 始发地/始发网点
		DestinatioCode  string `json:"DestinatioCode,omitempty"`  // 目的地区域编码
		DestinatioName  string `json:"DestinatioName,omitempty"`  // 目的地/到达网点
		SortingCode     string `json:"SortingCode,omitempty"`     // 分拣编码
		PackageCode     string `json:"PackageCode,omitempty"`     // 集包编码
	} `json:"Order"`
	Success               bool   `json:"Success"`                         // 成功与否
	ResultCode            string `json:"ResultCode"`                      // 错误编码
	Reason                string `json:"Reason,omitempty"`                // 失败原因
	UniquerRequestNumber  string `json:"UniquerRequestNumber"`            // 唯一标识
	PrintTemplate         string `json:"PrintTemplate,omitempty"`         // 面单打印模板
	EstimatedDeliveryTime string `json:"EstimatedDeliveryTime,omitempty"` // 订单预计到货时间yyyy-mm-dd
	Callback              string `json:"Callback,omitempty"`              // 用户自定义回调信息
	SubCount              int    `json:"SubCount,omitempty"`              // 子单数量
	SubOrders             string `json:"SubOrders,omitempty"`             // 子单号
	SubPrintTemplates     string `json:"SubPrintTemplates,omitempty"`     // 子单模板
	ReceiverSafePhone     string `json:"ReceiverSafePhone,omitempty"`     // 收件人安全电话
	SenderSafePhone       string `json:"SenderSafePhone,omitempty"`       // 寄件人安全电话
	DialPage              string `json:"DialPage"`                        // 拨号页面网址（转换成二维码可扫描拨号）
}

func (c *Client) CreateEOrder(order *EOrderReq) (*EOrderRep, error) {
	data, err := json.Marshal(order)
	if err != nil {
		return nil, err
	}

	req := c.makeReq(ReqCreateEOder, string(data))

	var result EOrderRep
	err = c.post("/eorderservice", req, &result)
	if err != nil {
		return nil, err
	}
	if result.ResultCode != ErrSuccess {
		return nil, fmt.Errorf("code: %s, reason: %s", result.ResultCode, result.Reason)
	}
	return &result, nil
}

type SubscribeTracingReq struct {
	ShipperCode  string `json:"ShipperCode"`  // 快递公司编码
	LogisticCode string `json:"LogisticCode"` // 快递单号
}

func (c *Client) SubscribeTracing(sub *SubscribeTracingReq) error {
	data, err := json.Marshal(sub)
	if err != nil {
		return err
	}

	req := c.makeReq(ReqSubscribeTracing, string(data))

	var rep struct {
		EBusinessID           string `json:"EBusinessID"`
		UpdateTime            string `json:"UpdateTime "`
		Success               bool   `json:"Success"`
		Reason                string `json:"Reason"`
		EstimatedDeliveryTime string `json:"EstimatedDeliveryTime"`
	}

	c.post("/dist", req, &rep)
	if !rep.Success {
		return errors.New(rep.Reason)
	}
	return nil
}

func (c *Client) makeReq(reqType, reqData string) url.Values {
	vals := make(url.Values)
	vals.Set("EBusinessID", c.EBusinessID)
	vals.Set("RequestType", reqType)
	vals.Set("DataSign", c.dataSign(reqData))
	vals.Set("RequestType", url.QueryEscape(string(reqData)))
	return vals
}

func (c *Client) dataSign(data string) string {
	m := md5.New()
	io.WriteString(m, string(data))
	io.WriteString(m, c.AppKey)
	return url.QueryEscape(base64.StdEncoding.EncodeToString(m.Sum(nil)))
}

func (c *Client) post(relativeURL string, req url.Values, rep interface{}) error {
	r, err := c.Post(BASE_URL+relativeURL, "application/x-www-form-urlencoded;charset=utf-8", strings.NewReader(req.Encode()))
	if err != nil {
		return err
	}
	defer r.Body.Close()

	return json.NewDecoder(r.Body).Decode(&rep)
}

type TracingData struct {
	EBusinessID  string `json:"EBusinessID,omitemtpy"`                    // 电商用户ID
	OrderCode    string `json:"OrderCode,omitempty"`                      // 订单编号
	ShipperCode  string `json:"ShipperCode"`                              // 快递公司编码
	LogisticCode string `json:"LogisticCode"`                             // 物流运单号
	Success      bool   `json:"Success"`                                  // 成功与否
	Reason       string `json:"Reason,omitempty"`                         // 失败原因
	State        string `json:"State"`                                    // 物流状态: 0-无轨迹 2-在途中 3-签收 4-问题件
	CallBack     string `json:"CallBack,omitempty"`                       // 订阅接口的Bk值
	Traces struct {
		AcceptTime    string `json:"AcceptTime"`       // 时间
		AcceptStation string `json:"AcceptStation"`    // 描述
		Remark        string `json:"Remark,omitempty"` // 备注
	} `json:"Traces"`                                                     // 物流轨迹详情
	EstimatedDeliveryTime string `json:"EstimatedDeliveryTime,omitempty"` // 预计到达时间yyyy-mm-dd
	PickerInfo struct {
		PersonName     string `json:"PersonName,omitempty"`     // 快递员姓名
		PersonTel      string `json:"PersonTel,omitempty"`      // 快递员电话
		PersonCode     string `json:"PersonCode,omitempty"`     // 快递员工号
		StationName    string `json:"StationName,omitempty"`    // 网点名称
		StationAddress string `json:"StationAddress,omitempty"` // 网点地址
		StationTel     string `json:"StationTel,omitempty"`     // 网点电话
	} `json:"PickerInfo,omitempty"`                                       // 收件员信息
	SenderInfo struct {
		PersonName     string `json:"PersonName,omitempty"`     // 派件员姓名
		PersonTel      string `json:"PersonTel,omitempty"`      // 派件员快递员电话
		PersonCode     string `json:"PersonCode,omitempty"`     // 派件员快递员工号
		StationName    string `json:"StationName,omitempty"`    // 派件员网点名称
		StationAddress string `json:"StationAddress,omitempty"` // 派件员网点地址
		StationTel     string `json:"StationTel,omitempty"`     // 派件员网点电话
	} `json:"SenderInfo,omitempty"`                                       // 派件员信息
}

func PushHandler(c *mel.Context, tracingHandler func(*TracingData)) {
	var req struct {
		RequestType string
		DataSign    string
		RequestData string
	}
	err := c.BindWith(&req, binding.FormPost)
	if err != nil {
		c.AbortWithError(400, err).Type = mel.ErrorTypeBind
		return
	}

	switch req.RequestType {
	case PushTracing:
		data, err := url.QueryUnescape(req.RequestData)
		var tracing struct{
			EBusinessID string `json:"EBusinessID"`
			PushTime string `json:"PushTime"`
			Count int `json:"Count"`
			Data TracingData `json:"Data"`
		}
		if err == nil {
			err = json.Unmarshal([]byte(data), &tracing)
		}
		if err != nil {
			c.AbortWithError(400, err).Type = mel.ErrorTypePrivate
			return
		}
		tracingHandler(&tracing.Data)
		rep := struct{
			EBusinessID string `json:"EBusinessID"`
			UpdateTime	string `json:"UpdateTime"`
			Success	bool `json:"Success"`
		}{
			EBusinessID: tracing.EBusinessID,
			UpdateTime: tracing.PushTime,
			Success: true,
		}
		c.JSON(200, &rep)
	}
}

type Server struct {
	*mel.Mel
}

func NewServer(pushURL string, tracingHandler func(*TracingData), logger *zap.SugaredLogger) *Server {
	s := &Server{
		Mel: mel.New(),
	}

	s.Use(melware.Zap(logger))

	s.Post(pushURL, func(c *mel.Context) {
		PushHandler(c, tracingHandler)
	})

	return s
}
<commit_msg>Update files<commit_after>package kdniao

import (
	"net/http"
	"net/url"
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"io"
	"strings"
	"fmt"
	"errors"
	"github.com/ridewindx/mel"
	"github.com/ridewindx/mel/binding"
	"github.com/ridewindx/melware"
	"go.uber.org/zap"
)

const (
	BASE_URL      = "http://api.kdniao.cc/api"
	TEST_BASE_URL = "http://testapi.kdniao.cc:8081/api"
)

type Client struct {
	EBusinessID string
	AppKey      string
	*http.Client
}

func NewClient(eid, appKey string, httpClient ...*http.Client) *Client {
	var c *http.Client
	if len(httpClient) > 0 {
		c = httpClient[0]
	} else {
		c = http.DefaultClient
	}
	return &Client{
		EBusinessID: eid,
		AppKey:      appKey,
		Client:      c,
	}
}

type Receiver struct {
	Company      string `json:"Company,omitempty"` // 收件人公司
	Name         string `json:"Name"`              // 收件人
	Tel          string `json:"Tel,omitempty"`     // 电话与手机，必填一个
	Mobile       string `json:"Mobile,omitempty"`
	PostCode     string `json:"PostCode,omitempty"`    // 收件人邮编
	ProvinceName string `json:"ProvinceName"`          // 收件省（如广东省，不要缺少“省”）
	CityName     string `json:"CityName"`              // 收件市（如深圳市，不要缺少“市”）
	ExpAreaName  string `json:"ExpAreaName,omitempty"` // 收件区（如福田区，不要缺少“区”或“县”）
	Address      string `json:"Address"`               // 收件人详细地址
}

type Sender struct {
	Company      string `json:"Company,omitempty"` // 发件人公司
	Name         string `json:"Name"`              // 发件人
	Tel          string `json:"Tel,omitempty"`     // 电话与手机，必填一个
	Mobile       string `json:"Mobile,omitempty"`
	PostCode     string `json:"PostCode,omitempty"`    //	发件人邮编
	ProvinceName string `json:"ProvinceName"`          // 发件省（如广东省，不要缺少“省”）
	CityName     string `json:"CityName"`              // 发件市（如深圳市，不要缺少“市”）
	ExpAreaName  string `json:"ExpAreaName,omitempty"` // 发件区（如福田区，不要缺少“区”或“县”）
	Address      string `json:"Address"`               // 发件详细地址
}

type AddService struct {
	Name       string `json:"Name,omitempty"`       // 增值服务名称
	Value      string `json:"Value,omitempty"`      // 增值服务值
	CustomerID string `json:"CustomerID,omitempty"` // 客户标识(选填)
}

type Commodity struct {
	GoodsName     string  `json:"GoodsName"`               // 商品名称
	GoodsCode     string  `json:"GoodsCode,omitempty"`     // 商品编码
	Goodsquantity int     `json:"Goodsquantity,omitempty"` // 件数
	GoodsPrice    float64 `json:"GoodsPrice,omitempty"`    // 商品价格
	GoodsWeight   float64 `json:"GoodsWeight,omitempty"`   // 商品重量kg
	GoodsDesc     string  `json:"GoodsDesc,omitempty"`     // 商品描述
	GoodsVol      float64 `json:"GoodsVol,omitempty"`      // 商品体积m3
}

type EOrderReq struct {
	CallBack              string     `json:"Callback,omitempty"`     // 用户自定义回调信息
	MemberID              string     `json:"MemberID,omitempty"`     // 会员标识，平台方与快递鸟统一用户标识的商家ID
	CustomerName          string     `json:"CustomerName,omitempty"` // 电子面单客户账号，（与快递网点申请或通过快递鸟官网申请或通过申请电子面单客户号申请）
	CustomerPwd           string     `json:"CustomerPwd,omitempty"`  // 电子面单密码
	SendSite              string     `json:"SendSite,omitempty"`     // 收件网点标识
	ShipperCode           string     `json:"ShipperCode"`            // 快递公司编码
	LogisticCode          string     `json:"LogisticCode,omitempty"` // 快递单号
	ThrOrderCode          string     `json:"ThrOrderCode,omitempty"` // 第三方订单号
	OrderCode             string     `json:"OrderCode"`              // 订单编号
	MonthCode             string     `json:"MonthCode,omitempty"`    // 月结编码
	PayType               int        `json:"PayType"`                // 邮费支付方式: 1-现付，2-到付，3-月结，4-第三方支付
	ExpType               string     `json:"ExpType"`                // 快递类型：1-标准快件
	IsNotice              int        `json:"IsNotice,omitempty"`     // 是否通知快递员上门揽件：0-通知；1-不通知；不填则默认为1
	Cost                  float64    `json:"Cost,omitempty"`         // 寄件费（运费）
	OtherCost             float64    `json:"OtherCost,omitempty"`    // 其他费用
	Receiver              Receiver   `json:"Receiver"`
	Sender                Sender     `json:"Sender"`
	StartDate             string     `json:"StartDate,omitempty"` // 上门取货时间段: "yyyy-MM-dd HH:mm:ss"格式化，本文中所有时间格式相同
	EndDate               string     `json:"EndDate,omitempty"`
	Weight                float64    `json:"Weight,omitempty"`   // 物品总重量kg
	Quantity              int        `json:"Quantity,omitempty"` // 件数/包裹数
	Volume                string     `json:"Volume,omitempty"`   // 物品总体积m3
	Remark                string     `json:"Remark,omitempty"`   // 备注
	AddService            AddService `json:"AddService"`
	Commodity             Commodity  `json:"Commodity"`
	IsReturnPrintTemplate string     `json:"IsReturnPrintTemplate,omitempty"` // 返回电子面单模板：0-不需要；1-需要
	IsSendMessage         int        `json:"IsSendMessage,omitempty"`         // 是否订阅短信：0-不需要；1-需要
	TemplateSize          string     `json:"TemplateSize,omitempty"`          // 模板尺寸
}

type EOrderRep struct {
	EBusinessID string `json:"EBusinessID"` // 电商用户ID
	Order struct {
		OrderCode       string `json:"OrderCode"`                 // 订单编号
		ShipperCode     string `json:"ShipperCode"`               // 快递公司编码
		LogisticCode    string `json:"LogisticCode"`              // 快递单号
		MarkDestination string `json:"MarkDestination,omitempty"` // 大头笔
		OriginCode      string `json:"OriginCode,omitempty"`      // 始发地区域编码
		OriginName      string `json:"OriginName,omitempty"`      // 始发地/始发网点
		DestinatioCode  string `json:"DestinatioCode,omitempty"`  // 目的地区域编码
		DestinatioName  string `json:"DestinatioName,omitempty"`  // 目的地/到达网点
		SortingCode     string `json:"SortingCode,omitempty"`     // 分拣编码
		PackageCode     string `json:"PackageCode,omitempty"`     // 集包编码
	} `json:"Order"`
	Success               bool   `json:"Success"`                         // 成功与否
	ResultCode            string `json:"ResultCode"`                      // 错误编码
	Reason                string `json:"Reason,omitempty"`                // 失败原因
	UniquerRequestNumber  string `json:"UniquerRequestNumber"`            // 唯一标识
	PrintTemplate         string `json:"PrintTemplate,omitempty"`         // 面单打印模板
	EstimatedDeliveryTime string `json:"EstimatedDeliveryTime,omitempty"` // 订单预计到货时间yyyy-mm-dd
	Callback              string `json:"Callback,omitempty"`              // 用户自定义回调信息
	SubCount              int    `json:"SubCount,omitempty"`              // 子单数量
	SubOrders             string `json:"SubOrders,omitempty"`             // 子单号
	SubPrintTemplates     string `json:"SubPrintTemplates,omitempty"`     // 子单模板
	ReceiverSafePhone     string `json:"ReceiverSafePhone,omitempty"`     // 收件人安全电话
	SenderSafePhone       string `json:"SenderSafePhone,omitempty"`       // 寄件人安全电话
	DialPage              string `json:"DialPage"`                        // 拨号页面网址（转换成二维码可扫描拨号）
}

func (c *Client) CreateEOrder(order *EOrderReq) (*EOrderRep, error) {
	data, err := json.Marshal(order)
	if err != nil {
		return nil, err
	}

	req := c.makeReq(ReqCreateEOder, string(data))

	var result EOrderRep
	err = c.post("/eorderservice", req, &result)
	if err != nil {
		return nil, err
	}
	if result.ResultCode != ErrSuccess {
		return nil, fmt.Errorf("code: %s, reason: %s", result.ResultCode, result.Reason)
	}
	return &result, nil
}

type SubscribeTracingReq struct {
	ShipperCode  string `json:"ShipperCode"`  // 快递公司编码
	LogisticCode string `json:"LogisticCode"` // 快递单号
}

func (c *Client) SubscribeTracing(sub *SubscribeTracingReq) error {
	data, err := json.Marshal(sub)
	if err != nil {
		return err
	}

	req := c.makeReq(ReqSubscribeTracing, string(data))

	var rep struct {
		EBusinessID           string `json:"EBusinessID"`
		UpdateTime            string `json:"UpdateTime "`
		Success               bool   `json:"Success"`
		Reason                string `json:"Reason"`
		EstimatedDeliveryTime string `json:"EstimatedDeliveryTime"`
	}

	c.post("/dist", req, &rep)
	if !rep.Success {
		return errors.New(rep.Reason)
	}
	return nil
}

func (c *Client) makeReq(reqType, reqData string) url.Values {
	vals := make(url.Values)
	vals.Set("EBusinessID", c.EBusinessID)
	vals.Set("RequestType", reqType)
	vals.Set("DataSign", c.dataSign(reqData))
	vals.Set("RequestType", url.QueryEscape(string(reqData)))
	return vals
}

func (c *Client) dataSign(data string) string {
	m := md5.New()
	io.WriteString(m, string(data))
	io.WriteString(m, c.AppKey)
	return url.QueryEscape(base64.StdEncoding.EncodeToString(m.Sum(nil)))
}

func (c *Client) post(relativeURL string, req url.Values, rep interface{}) error {
	r, err := c.Post(BASE_URL+relativeURL, "application/x-www-form-urlencoded;charset=utf-8", strings.NewReader(req.Encode()))
	if err != nil {
		return err
	}
	defer r.Body.Close()

	return json.NewDecoder(r.Body).Decode(&rep)
}

type TracingData struct {
	EBusinessID  string `json:"EBusinessID,omitemtpy"`                    // 电商用户ID
	OrderCode    string `json:"OrderCode,omitempty"`                      // 订单编号
	ShipperCode  string `json:"ShipperCode"`                              // 快递公司编码
	LogisticCode string `json:"LogisticCode"`                             // 物流运单号
	Success      bool   `json:"Success"`                                  // 成功与否
	Reason       string `json:"Reason,omitempty"`                         // 失败原因
	State        string `json:"State"`                                    // 物流状态: 0-无轨迹 2-在途中 3-签收 4-问题件
	CallBack     string `json:"CallBack,omitempty"`                       // 订阅接口的Bk值
	Traces struct {
		AcceptTime    string `json:"AcceptTime"`       // 时间
		AcceptStation string `json:"AcceptStation"`    // 描述
		Remark        string `json:"Remark,omitempty"` // 备注
	} `json:"Traces"`                                                     // 物流轨迹详情
	EstimatedDeliveryTime string `json:"EstimatedDeliveryTime,omitempty"` // 预计到达时间yyyy-mm-dd
	PickerInfo struct {
		PersonName     string `json:"PersonName,omitempty"`     // 快递员姓名
		PersonTel      string `json:"PersonTel,omitempty"`      // 快递员电话
		PersonCode     string `json:"PersonCode,omitempty"`     // 快递员工号
		StationName    string `json:"StationName,omitempty"`    // 网点名称
		StationAddress string `json:"StationAddress,omitempty"` // 网点地址
		StationTel     string `json:"StationTel,omitempty"`     // 网点电话
	} `json:"PickerInfo,omitempty"`                                       // 收件员信息
	SenderInfo struct {
		PersonName     string `json:"PersonName,omitempty"`     // 派件员姓名
		PersonTel      string `json:"PersonTel,omitempty"`      // 派件员快递员电话
		PersonCode     string `json:"PersonCode,omitempty"`     // 派件员快递员工号
		StationName    string `json:"StationName,omitempty"`    // 派件员网点名称
		StationAddress string `json:"StationAddress,omitempty"` // 派件员网点地址
		StationTel     string `json:"StationTel,omitempty"`     // 派件员网点电话
	} `json:"SenderInfo,omitempty"`                                       // 派件员信息
}

func PushHandler(c *mel.Context, tracingHandler func([]TracingData)) {
	var req struct {
		RequestType string
		DataSign    string
		RequestData string
	}
	err := c.BindWith(&req, binding.FormPost)
	if err != nil {
		c.AbortWithError(400, err).Type = mel.ErrorTypeBind
		return
	}

	switch req.RequestType {
	case PushTracing:
		data, err := url.QueryUnescape(req.RequestData)
		var tracing struct{
			EBusinessID string `json:"EBusinessID"`
			PushTime string `json:"PushTime"`
			Count int `json:"Count"`
			Data []TracingData `json:"Data"`
		}
		if err == nil {
			err = json.Unmarshal([]byte(data), &tracing)
		}
		if err != nil {
			c.AbortWithError(400, err).Type = mel.ErrorTypePrivate
			return
		}
		tracingHandler(tracing.Data)
		rep := struct{
			EBusinessID string `json:"EBusinessID"`
			UpdateTime	string `json:"UpdateTime"`
			Success	bool `json:"Success"`
		}{
			EBusinessID: tracing.EBusinessID,
			UpdateTime: tracing.PushTime,
			Success: true,
		}
		c.JSON(200, &rep)
	}
}

type Server struct {
	*mel.Mel
}

func NewServer(pushURL string, tracingHandler func([]TracingData), logger *zap.SugaredLogger) *Server {
	s := &Server{
		Mel: mel.New(),
	}

	s.Use(melware.Zap(logger))

	s.Post(pushURL, func(c *mel.Context) {
		PushHandler(c, tracingHandler)
	})

	return s
}
<|endoftext|>