<commit_before>package main

import (
	"errors"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/shazow/rateio"
	"github.com/shazow/ssh-chat/chat"
	"github.com/shazow/ssh-chat/sshd"
)

const maxInputLength int = 1024

// GetPrompt will render the terminal prompt string based on the user.
func GetPrompt(user *chat.User) string {
	name := user.Name()
	if user.Config.Theme != nil {
		name = user.Config.Theme.ColorName(user)
	}
	return fmt.Sprintf("[%s] ", name)
}

// Host is the bridge between sshd and chat modules
// TODO: Should be easy to add support for multiple rooms, if we want.
type Host struct {
	*chat.Room
	listener *sshd.SSHListener
	commands chat.Commands

	motd  string
	auth  *Auth
	count int

	// Default theme
	theme *chat.Theme
}

// NewHost creates a Host on top of an existing listener.
func NewHost(listener *sshd.SSHListener) *Host {
	room := chat.NewRoom()
	h := Host{
		Room:     room,
		listener: listener,
		commands: chat.Commands{},
	}

	// Make our own commands registry instance.
	chat.InitCommands(&h.commands)
	h.InitCommands(&h.commands)
	room.SetCommands(h.commands)

	go room.Serve()
	return &h
}

// SetMotd sets the host's message of the day.
func (h *Host) SetMotd(motd string) {
	h.motd = motd
}

func (h Host) isOp(conn sshd.Connection) bool {
	key := conn.PublicKey()
	if key == nil {
		return false
	}
	return h.auth.IsOp(key)
}

// Connect a specific Terminal to this host and its room.
func (h *Host) Connect(term *sshd.Terminal) {
	id := NewIdentity(term.Conn)
	user := chat.NewUserScreen(id, term)
	user.Config.Theme = h.theme
	go func() {
		// Close term once user is closed.
		user.Wait()
		term.Close()
	}()
	defer user.Close()

	// Send MOTD
	if h.motd != "" {
		user.Send(chat.NewAnnounceMsg(h.motd))
	}

	member, err := h.Join(user)
	if err != nil {
		// Try again...
		id.SetName(fmt.Sprintf("Guest%d", h.count))
		member, err = h.Join(user)
	}
	if err != nil {
		logger.Errorf("Failed to join: %s", err)
		return
	}

	// Successfully joined.
	term.SetPrompt(GetPrompt(user))
	term.AutoCompleteCallback = h.AutoCompleteFunction(user)
	user.SetHighlight(user.Name())
	h.count++

	// Should the user be op'd on join?
	member.Op = h.isOp(term.Conn)
	ratelimit := rateio.NewSimpleLimiter(3, time.Second*3)

	for {
		line, err := term.ReadLine()
		if err == io.EOF {
			// Closed
			break
		} else if err != nil {
			logger.Errorf("Terminal reading error: %s", err)
			break
		}

		err = ratelimit.Count(1)
		if err != nil {
			user.Send(chat.NewSystemMsg("Message rejected: Rate limiting is in effect.", user))
			continue
		}
		if len(line) > maxInputLength {
			user.Send(chat.NewSystemMsg("Message rejected: Input too long.", user))
			continue
		}
		if line == "" {
			// Silently ignore empty lines.
			continue
		}

		m := chat.ParseInput(line, user)

		// FIXME: Any reason to use h.room.Send(m) instead?
		h.HandleMsg(m)

		cmd := m.Command()
		if cmd == "/nick" || cmd == "/theme" {
			// Hijack /nick command to update terminal synchronously. Wouldn't
			// work if we use h.room.Send(m) above.
			//
			// FIXME: This is hacky, how do we improve the API to allow for
			// this? Chat module shouldn't know about terminals.
			term.SetPrompt(GetPrompt(user))
			user.SetHighlight(user.Name())
		}
	}

	err = h.Leave(user)
	if err != nil {
		logger.Errorf("Failed to leave: %s", err)
		return
	}
}

// Serve our chat room onto the listener
func (h *Host) Serve() {
	terminals := h.listener.ServeTerminal()

	for term := range terminals {
		go h.Connect(term)
	}
}

func (h Host) completeName(partial string) string {
	names := h.NamesPrefix(partial)
	if len(names) == 0 {
		// Didn't find anything
		return ""
	}

	return names[len(names)-1]
}

func (h Host) completeCommand(partial string) string {
	for cmd, _ := range h.commands {
		if strings.HasPrefix(cmd, partial) {
			return cmd
		}
	}
	return ""
}

// AutoCompleteFunction returns a callback for terminal autocompletion
func (h *Host) AutoCompleteFunction(u *chat.User) func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
	return func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
		if key != 9 {
			return
		}

		if strings.HasSuffix(line[:pos], " ") {
			// Don't autocomplete spaces.
			return
		}

		fields := strings.Fields(line[:pos])
		isFirst := len(fields) < 2
		partial := fields[len(fields)-1]
		posPartial := pos - len(partial)

		var completed string
		if isFirst && strings.HasPrefix(partial, "/") {
			// Command
			completed = h.completeCommand(partial)
			if completed == "/reply" {
				replyTo := u.ReplyTo()
				if replyTo != nil {
					completed = "/msg " + replyTo.Name()
				}
			}
		} else {
			// Name
			completed = h.completeName(partial)
			if completed == "" {
				return
			}
			if isFirst {
				completed += ":"
			}
		}
		completed += " "

		// Reposition the cursor
		newLine = strings.Replace(line[posPartial:], partial, completed, 1)
		newLine = line[:posPartial] + newLine
		newPos = pos + (len(completed) - len(partial))
		ok = true
		return
	}
}

// GetUser returns a chat.User based on a name.
func (h *Host) GetUser(name string) (*chat.User, bool) {
	m, ok := h.MemberById(name)
	if !ok {
		return nil, false
	}
	return m.User, true
}

// InitCommands adds host-specific commands to a Commands container. These will
// override any existing commands.
func (h *Host) InitCommands(c *chat.Commands) {
	c.Add(chat.Command{
		Prefix:     "/msg",
		PrefixHelp: "USER MESSAGE",
		Help:       "Send MESSAGE to USER.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			args := msg.Args()
			switch len(args) {
			case 0:
				return errors.New("must specify user")
			case 1:
				return errors.New("must specify message")
			}

			target, ok := h.GetUser(args[0])
			if !ok {
				return errors.New("user not found")
			}

			m := chat.NewPrivateMsg(strings.Join(args[1:], " "), msg.From(), target)
			room.Send(m)
			return nil
		},
	})

	c.Add(chat.Command{
		Prefix:     "/reply",
		PrefixHelp: "MESSAGE",
		Help:       "Reply with MESSAGE to the previous private message.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			args := msg.Args()
			switch len(args) {
			case 0:
				return errors.New("must specify message")
			}

			target := msg.From().ReplyTo()
			if target == nil {
				return errors.New("no message to reply to")
			}

			m := chat.NewPrivateMsg(strings.Join(args, " "), msg.From(), target)
			room.Send(m)
			return nil
		},
	})

	c.Add(chat.Command{
		Prefix:     "/whois",
		PrefixHelp: "USER",
		Help:       "Information about USER.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			args := msg.Args()
			if len(args) == 0 {
				return errors.New("must specify user")
			}

			target, ok := h.GetUser(args[0])
			if !ok {
				return errors.New("user not found")
			}

			id := target.Identifier.(*Identity)
			room.Send(chat.NewSystemMsg(id.Whois(), msg.From()))

			return nil
		},
	})

	// Op commands
	c.Add(chat.Command{
		Op:         true,
		Prefix:     "/kick",
		PrefixHelp: "USER",
		Help:       "Kick USER from the server.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			if !room.IsOp(msg.From()) {
				return errors.New("must be op")
			}

			args := msg.Args()
			if len(args) == 0 {
				return errors.New("must specify user")
			}

			target, ok := h.GetUser(args[0])
			if !ok {
				return errors.New("user not found")
			}

			body := fmt.Sprintf("%s was kicked by %s.", target.Name(), msg.From().Name())
			room.Send(chat.NewAnnounceMsg(body))
			target.Close()
			return nil
		},
	})

	c.Add(chat.Command{
		Op:         true,
		Prefix:     "/ban",
		PrefixHelp: "USER [DURATION]",
		Help:       "Ban USER from the server.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			// TODO: Would be nice to specify what to ban. Key? Ip? etc.
			if !room.IsOp(msg.From()) {
				return errors.New("must be op")
			}

			args := msg.Args()
			if len(args) == 0 {
				return errors.New("must specify user")
			}

			target, ok := h.GetUser(args[0])
			if !ok {
				return errors.New("user not found")
			}

			var until time.Duration = 0
			if len(args) > 1 {
				until, _ = time.ParseDuration(args[1])
			}

			id := target.Identifier.(*Identity)
			h.auth.Ban(id.PublicKey(), until)
			h.auth.BanAddr(id.RemoteAddr(), until)

			body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name())
			room.Send(chat.NewAnnounceMsg(body))
			target.Close()

			logger.Debugf("Banned: \n-> %s", id.Whois())

			return nil
		},
	})

	c.Add(chat.Command{
		Op:         true,
		Prefix:     "/motd",
		PrefixHelp: "MESSAGE",
		Help:       "Set the MESSAGE of the day.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			if !room.IsOp(msg.From()) {
				return errors.New("must be op")
			}

			motd := ""
			args := msg.Args()
			if len(args) > 0 {
				motd = strings.Join(args, " ")
			}

			h.motd = motd
			body := fmt.Sprintf("New message of the day set by %s:", msg.From().Name())
			room.Send(chat.NewAnnounceMsg(body))
			if motd != "" {
				room.Send(chat.NewAnnounceMsg(motd))
			}

			return nil
		},
	})

	c.Add(chat.Command{
		Op:         true,
		Prefix:     "/op",
		PrefixHelp: "USER [DURATION]",
		Help:       "Set USER as admin.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			if !room.IsOp(msg.From()) {
				return errors.New("must be op")
			}

			args := msg.Args()
			if len(args) == 0 {
				return errors.New("must specify user")
			}

			var until time.Duration = 0
			if len(args) > 1 {
				until, _ = time.ParseDuration(args[1])
			}

			member, ok := room.MemberById(args[0])
			if !ok {
				return errors.New("user not found")
			}
			member.Op = true
			id := member.Identifier.(*Identity)
			h.auth.Op(id.PublicKey(), until)

			body := fmt.Sprintf("Made op by %s.", msg.From().Name())
			room.Send(chat.NewSystemMsg(body, member.User))

			return nil
		},
	})
}
<commit_msg>/version, /uptime<commit_after>package main

import (
	"errors"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/shazow/rateio"
	"github.com/shazow/ssh-chat/chat"
	"github.com/shazow/ssh-chat/sshd"
)

const maxInputLength int = 1024

// GetPrompt will render the terminal prompt string based on the user.
func GetPrompt(user *chat.User) string {
	name := user.Name()
	if user.Config.Theme != nil {
		name = user.Config.Theme.ColorName(user)
	}
	return fmt.Sprintf("[%s] ", name)
}

// Host is the bridge between sshd and chat modules
// TODO: Should be easy to add support for multiple rooms, if we want.
type Host struct {
	*chat.Room
	listener *sshd.SSHListener
	commands chat.Commands

	motd  string
	auth  *Auth
	count int

	// Default theme
	theme *chat.Theme
}

// NewHost creates a Host on top of an existing listener.
func NewHost(listener *sshd.SSHListener) *Host {
	room := chat.NewRoom()
	h := Host{
		Room:     room,
		listener: listener,
		commands: chat.Commands{},
	}

	// Make our own commands registry instance.
	chat.InitCommands(&h.commands)
	h.InitCommands(&h.commands)
	room.SetCommands(h.commands)

	go room.Serve()
	return &h
}

// SetMotd sets the host's message of the day.
func (h *Host) SetMotd(motd string) {
	h.motd = motd
}

func (h Host) isOp(conn sshd.Connection) bool {
	key := conn.PublicKey()
	if key == nil {
		return false
	}
	return h.auth.IsOp(key)
}

// Connect a specific Terminal to this host and its room.
func (h *Host) Connect(term *sshd.Terminal) {
	id := NewIdentity(term.Conn)
	user := chat.NewUserScreen(id, term)
	user.Config.Theme = h.theme
	go func() {
		// Close term once user is closed.
		user.Wait()
		term.Close()
	}()
	defer user.Close()

	// Send MOTD
	if h.motd != "" {
		user.Send(chat.NewAnnounceMsg(h.motd))
	}

	member, err := h.Join(user)
	if err != nil {
		// Try again...
		id.SetName(fmt.Sprintf("Guest%d", h.count))
		member, err = h.Join(user)
	}
	if err != nil {
		logger.Errorf("Failed to join: %s", err)
		return
	}

	// Successfully joined.
	term.SetPrompt(GetPrompt(user))
	term.AutoCompleteCallback = h.AutoCompleteFunction(user)
	user.SetHighlight(user.Name())
	h.count++

	// Should the user be op'd on join?
	member.Op = h.isOp(term.Conn)
	ratelimit := rateio.NewSimpleLimiter(3, time.Second*3)

	for {
		line, err := term.ReadLine()
		if err == io.EOF {
			// Closed
			break
		} else if err != nil {
			logger.Errorf("Terminal reading error: %s", err)
			break
		}

		err = ratelimit.Count(1)
		if err != nil {
			user.Send(chat.NewSystemMsg("Message rejected: Rate limiting is in effect.", user))
			continue
		}
		if len(line) > maxInputLength {
			user.Send(chat.NewSystemMsg("Message rejected: Input too long.", user))
			continue
		}
		if line == "" {
			// Silently ignore empty lines.
			continue
		}

		m := chat.ParseInput(line, user)

		// FIXME: Any reason to use h.room.Send(m) instead?
		h.HandleMsg(m)

		cmd := m.Command()
		if cmd == "/nick" || cmd == "/theme" {
			// Hijack /nick command to update terminal synchronously. Wouldn't
			// work if we use h.room.Send(m) above.
			//
			// FIXME: This is hacky, how do we improve the API to allow for
			// this? Chat module shouldn't know about terminals.
			term.SetPrompt(GetPrompt(user))
			user.SetHighlight(user.Name())
		}
	}

	err = h.Leave(user)
	if err != nil {
		logger.Errorf("Failed to leave: %s", err)
		return
	}
}

// Serve our chat room onto the listener
func (h *Host) Serve() {
	terminals := h.listener.ServeTerminal()

	for term := range terminals {
		go h.Connect(term)
	}
}

func (h Host) completeName(partial string) string {
	names := h.NamesPrefix(partial)
	if len(names) == 0 {
		// Didn't find anything
		return ""
	}

	return names[len(names)-1]
}

func (h Host) completeCommand(partial string) string {
	for cmd, _ := range h.commands {
		if strings.HasPrefix(cmd, partial) {
			return cmd
		}
	}
	return ""
}

// AutoCompleteFunction returns a callback for terminal autocompletion
func (h *Host) AutoCompleteFunction(u *chat.User) func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
	return func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
		if key != 9 {
			return
		}

		if strings.HasSuffix(line[:pos], " ") {
			// Don't autocomplete spaces.
			return
		}

		fields := strings.Fields(line[:pos])
		isFirst := len(fields) < 2
		partial := fields[len(fields)-1]
		posPartial := pos - len(partial)

		var completed string
		if isFirst && strings.HasPrefix(partial, "/") {
			// Command
			completed = h.completeCommand(partial)
			if completed == "/reply" {
				replyTo := u.ReplyTo()
				if replyTo != nil {
					completed = "/msg " + replyTo.Name()
				}
			}
		} else {
			// Name
			completed = h.completeName(partial)
			if completed == "" {
				return
			}
			if isFirst {
				completed += ":"
			}
		}
		completed += " "

		// Reposition the cursor
		newLine = strings.Replace(line[posPartial:], partial, completed, 1)
		newLine = line[:posPartial] + newLine
		newPos = pos + (len(completed) - len(partial))
		ok = true
		return
	}
}

// GetUser returns a chat.User based on a name.
func (h *Host) GetUser(name string) (*chat.User, bool) {
	m, ok := h.MemberById(name)
	if !ok {
		return nil, false
	}
	return m.User, true
}

// InitCommands adds host-specific commands to a Commands container. These will
// override any existing commands.
func (h *Host) InitCommands(c *chat.Commands) {
	c.Add(chat.Command{
		Prefix:     "/msg",
		PrefixHelp: "USER MESSAGE",
		Help:       "Send MESSAGE to USER.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			args := msg.Args()
			switch len(args) {
			case 0:
				return errors.New("must specify user")
			case 1:
				return errors.New("must specify message")
			}

			target, ok := h.GetUser(args[0])
			if !ok {
				return errors.New("user not found")
			}

			m := chat.NewPrivateMsg(strings.Join(args[1:], " "), msg.From(), target)
			room.Send(m)
			return nil
		},
	})

	c.Add(chat.Command{
		Prefix:     "/reply",
		PrefixHelp: "MESSAGE",
		Help:       "Reply with MESSAGE to the previous private message.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			args := msg.Args()
			switch len(args) {
			case 0:
				return errors.New("must specify message")
			}

			target := msg.From().ReplyTo()
			if target == nil {
				return errors.New("no message to reply to")
			}

			m := chat.NewPrivateMsg(strings.Join(args, " "), msg.From(), target)
			room.Send(m)
			return nil
		},
	})

	c.Add(chat.Command{
		Prefix:     "/whois",
		PrefixHelp: "USER",
		Help:       "Information about USER.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			args := msg.Args()
			if len(args) == 0 {
				return errors.New("must specify user")
			}

			target, ok := h.GetUser(args[0])
			if !ok {
				return errors.New("user not found")
			}

			id := target.Identifier.(*Identity)
			room.Send(chat.NewSystemMsg(id.Whois(), msg.From()))

			return nil
		},
	})

	// Hidden commands
	c.Add(chat.Command{
		Prefix: "/version",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			room.Send(chat.NewSystemMsg(buildCommit, msg.From()))
			return nil
		},
	})

	timeStarted := time.Now()
	c.Add(chat.Command{
		Prefix: "/uptime",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			room.Send(chat.NewSystemMsg(time.Now().Sub(timeStarted).String(), msg.From()))
			return nil
		},
	})

	// Op commands
	c.Add(chat.Command{
		Op:         true,
		Prefix:     "/kick",
		PrefixHelp: "USER",
		Help:       "Kick USER from the server.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			if !room.IsOp(msg.From()) {
				return errors.New("must be op")
			}

			args := msg.Args()
			if len(args) == 0 {
				return errors.New("must specify user")
			}

			target, ok := h.GetUser(args[0])
			if !ok {
				return errors.New("user not found")
			}

			body := fmt.Sprintf("%s was kicked by %s.", target.Name(), msg.From().Name())
			room.Send(chat.NewAnnounceMsg(body))
			target.Close()
			return nil
		},
	})

	c.Add(chat.Command{
		Op:         true,
		Prefix:     "/ban",
		PrefixHelp: "USER [DURATION]",
		Help:       "Ban USER from the server.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			// TODO: Would be nice to specify what to ban. Key? Ip? etc.
			if !room.IsOp(msg.From()) {
				return errors.New("must be op")
			}

			args := msg.Args()
			if len(args) == 0 {
				return errors.New("must specify user")
			}

			target, ok := h.GetUser(args[0])
			if !ok {
				return errors.New("user not found")
			}

			var until time.Duration = 0
			if len(args) > 1 {
				until, _ = time.ParseDuration(args[1])
			}

			id := target.Identifier.(*Identity)
			h.auth.Ban(id.PublicKey(), until)
			h.auth.BanAddr(id.RemoteAddr(), until)

			body := fmt.Sprintf("%s was banned by %s.", target.Name(), msg.From().Name())
			room.Send(chat.NewAnnounceMsg(body))
			target.Close()

			logger.Debugf("Banned: \n-> %s", id.Whois())

			return nil
		},
	})

	c.Add(chat.Command{
		Op:         true,
		Prefix:     "/motd",
		PrefixHelp: "MESSAGE",
		Help:       "Set the MESSAGE of the day.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			if !room.IsOp(msg.From()) {
				return errors.New("must be op")
			}

			motd := ""
			args := msg.Args()
			if len(args) > 0 {
				motd = strings.Join(args, " ")
			}

			h.motd = motd
			body := fmt.Sprintf("New message of the day set by %s:", msg.From().Name())
			room.Send(chat.NewAnnounceMsg(body))
			if motd != "" {
				room.Send(chat.NewAnnounceMsg(motd))
			}

			return nil
		},
	})

	c.Add(chat.Command{
		Op:         true,
		Prefix:     "/op",
		PrefixHelp: "USER [DURATION]",
		Help:       "Set USER as admin.",
		Handler: func(room *chat.Room, msg chat.CommandMsg) error {
			if !room.IsOp(msg.From()) {
				return errors.New("must be op")
			}

			args := msg.Args()
			if len(args) == 0 {
				return errors.New("must specify user")
			}

			var until time.Duration = 0
			if len(args) > 1 {
				until, _ = time.ParseDuration(args[1])
			}

			member, ok := room.MemberById(args[0])
			if !ok {
				return errors.New("user not found")
			}
			member.Op = true
			id := member.Identifier.(*Identity)
			h.auth.Op(id.PublicKey(), until)

			body := fmt.Sprintf("Made op by %s.", msg.From().Name())
			room.Send(chat.NewSystemMsg(body, member.User))

			return nil
		},
	})
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"os"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/client"
	"github.com/kataras/iris"
	"github.com/iris-contrib/middleware/cors"
)

type swarmistService struct {
	swarm.Service
	ReplicasRunning int
}

func main() {
	cli, err := client.NewEnvClient()
	if err != nil {
		panic(err)
	}

	iris.Use(cors.Default())

	iris.Get("/", func(ctx *iris.Context) {
		requestpath := "client/build/index.html"
		ctx.ServeFile(requestpath, true)
	})

	iris.Get("/static/*file", func(ctx *iris.Context) {
		requestpath := "client/build/static" + ctx.Param("file")
		ctx.ServeFile(requestpath, true)
	})

	iris.Get("/api/info", func(ctx *iris.Context) {
		info, err := cli.Info(context.Background())
		if err != nil {
			fmt.Println(err)
			ctx.EmitError(iris.StatusInternalServerError)
			return
		}

		ctx.JSON(iris.StatusOK, info)
	})

	iris.Get("/api/services", func(ctx *iris.Context) {
		services, err := cli.ServiceList(context.Background(), types.ServiceListOptions{})
		if err != nil {
			fmt.Println(err)
			ctx.EmitError(iris.StatusInternalServerError)
			return
		}

		tasks, err := cli.TaskList(context.Background(), types.TaskListOptions{})
		if err != nil {
			fmt.Println(err)
			ctx.EmitError(iris.StatusInternalServerError)
			return
		}

		swarmistServices := make([]swarmistService, len(services))
		for i, service := range services {
			swarmistService := swarmistService{service, 0}

			replicasRunning := 0
			for _, task := range tasks {
				if task.ServiceID == service.ID && task.Status.State == "running" {
					replicasRunning++
				}
			}
			swarmistService.ReplicasRunning = replicasRunning
			swarmistServices[i] = swarmistService
		}

		ctx.JSON(iris.StatusOK, swarmistServices)
	})

	iris.Get("/api/tasks", func(ctx *iris.Context) {
		tasks, err := cli.TaskList(context.Background(), types.TaskListOptions{})
		if err != nil {
			ctx.EmitError(iris.StatusInternalServerError)
			return
		}

		ctx.JSON(iris.StatusOK, tasks)
	})

	listenTo := ":3000"
	if os.Getenv("PORT") != "" {
		listenTo = ":" + os.Getenv("PORT")
	}
	iris.Listen(listenTo)
}
<commit_msg>fix: Add canonical import path to golang server<commit_after>package main // import "github.com/jsalonen/swarmist/server"

import (
	"context"
	"fmt"
	"os"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/swarm"
	"github.com/docker/docker/client"
	"github.com/kataras/iris"
	"github.com/iris-contrib/middleware/cors"
)

type swarmistService struct {
	swarm.Service
	ReplicasRunning int
}

func main() {
	cli, err := client.NewEnvClient()
	if err != nil {
		panic(err)
	}

	iris.Use(cors.Default())

	iris.Get("/", func(ctx *iris.Context) {
		requestpath := "client/build/index.html"
		ctx.ServeFile(requestpath, true)
	})

	iris.Get("/static/*file", func(ctx *iris.Context) {
		requestpath := "client/build/static" + ctx.Param("file")
		ctx.ServeFile(requestpath, true)
	})

	iris.Get("/api/info", func(ctx *iris.Context) {
		info, err := cli.Info(context.Background())
		if err != nil {
			fmt.Println(err)
			ctx.EmitError(iris.StatusInternalServerError)
			return
		}

		ctx.JSON(iris.StatusOK, info)
	})

	iris.Get("/api/services", func(ctx *iris.Context) {
		services, err := cli.ServiceList(context.Background(), types.ServiceListOptions{})
		if err != nil {
			fmt.Println(err)
			ctx.EmitError(iris.StatusInternalServerError)
			return
		}

		tasks, err := cli.TaskList(context.Background(), types.TaskListOptions{})
		if err != nil {
			fmt.Println(err)
			ctx.EmitError(iris.StatusInternalServerError)
			return
		}

		swarmistServices := make([]swarmistService, len(services))
		for i, service := range services {
			swarmistService := swarmistService{service, 0}

			replicasRunning := 0
			for _, task := range tasks {
				if task.ServiceID == service.ID && task.Status.State == "running" {
					replicasRunning++
				}
			}
			swarmistService.ReplicasRunning = replicasRunning
			swarmistServices[i] = swarmistService
		}

		ctx.JSON(iris.StatusOK, swarmistServices)
	})

	iris.Get("/api/tasks", func(ctx *iris.Context) {
		tasks, err := cli.TaskList(context.Background(), types.TaskListOptions{})
		if err != nil {
			ctx.EmitError(iris.StatusInternalServerError)
			return
		}

		ctx.JSON(iris.StatusOK, tasks)
	})

	listenTo := ":3000"
	if os.Getenv("PORT") != "" {
		listenTo = ":" + os.Getenv("PORT")
	}
	iris.Listen(listenTo)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	_ "net/http/pprof"
	"sort"
	"strings"
	"time"

	"github.com/Nitro/memberlist"
	"github.com/Nitro/sidecar/catalog"
	"github.com/Nitro/sidecar/output"
	"github.com/Nitro/sidecar/service"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
)

type ApiServer struct {
	Name         string
	LastUpdated  time.Time
	ServiceCount int
}

type ApiServices struct {
	Services       map[string][]*service.Service
	ClusterMembers map[string]*ApiServer
	ClusterName    string
}

// A ServicesState.Listener that we use for the /watch endpoint
type HttpListener struct {
	eventChan chan catalog.ChangeEvent
	name      string
}

func NewHttpListener() *HttpListener {
	return &HttpListener{
		// This should be fine enough granularity for practical purposes
		name: fmt.Sprintf("httpListener-%d", time.Now().UTC().UnixNano()),
		// Listeners must have buffered channels. We'll use a
		// somewhat larger buffer here because of the slow link
		// problem with http
		eventChan: make(chan catalog.ChangeEvent, 50),
	}
}

func (h *HttpListener) Chan() chan catalog.ChangeEvent {
	return h.eventChan
}

func (h *HttpListener) Name() string {
	return h.name
}

func makeHandler(fn func(http.ResponseWriter, *http.Request,
	*memberlist.Memberlist, *catalog.ServicesState),
	list *memberlist.Memberlist, state *catalog.ServicesState) http.HandlerFunc {

	return func(response http.ResponseWriter, req *http.Request) {
		fn(response, req, list, state)
	}
}

func watchHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	defer req.Body.Close()

	response.Header().Set("Content-Type", "application/json")

	var jsonBytes []byte
	var err error

	listener := NewHttpListener()

	// Find out when the http connection closed so we can stop
	notify := response.(http.CloseNotifier).CloseNotify()

	// Let's subscribe to state change events
	state.AddListener(listener)
	defer state.RemoveListener(listener.Name())

	for {
		select {
		case <-notify:
			break

		case <-listener.Chan():
			jsonBytes, err = json.Marshal(state.ByService())
			if err != nil {
				log.Errorf("Error marshaling state in watchHandler: %s", err.Error())
				return
			}
			// In order to flush immediately, we have to cast to a Flusher.
			// The normal HTTP library supports this but not all do, so we
			// check just in case.
			response.Write(jsonBytes)
			if f, ok := response.(http.Flusher); ok {
				f.Flush()
			}
		}
	}
}

func servicesHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	params := mux.Vars(req)
	defer req.Body.Close()

	if params["extension"] == ".json" {
		response.Header().Set("Content-Type", "application/json")
		response.Header().Set("Access-Control-Allow-Origin", "*")
		response.Header().Set("Access-Control-Allow-Methods", "GET")

		listMembers := list.Members()
		sort.Sort(listByName(listMembers))
		members := make(map[string]*ApiServer, len(listMembers))

		var jsonBytes []byte
		var err error

		func() { // Wrap critical section
			state.RLock()
			defer state.RUnlock()

			for _, member := range listMembers {
				if state.HasServer(member.Name) {
					members[member.Name] = &ApiServer{
						Name:         member.Name,
						LastUpdated:  state.Servers[member.Name].LastUpdated,
						ServiceCount: len(state.Servers[member.Name].Services),
					}
				} else {
					members[member.Name] = &ApiServer{
						Name:         member.Name,
						LastUpdated:  time.Unix(0, 0),
						ServiceCount: 0,
					}
				}
			}

			result := ApiServices{
				Services:       state.ByService(),
				ClusterMembers: members,
				ClusterName:    list.ClusterName(),
			}

			jsonBytes, err = json.MarshalIndent(&result, "", "  ")
		}()

		if err != nil {
			log.Errorf("Error marshaling state in servicesHandler: %s", err.Error())
		}

		response.Write(jsonBytes)
		return
	}
}

func serversHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	defer req.Body.Close()

	response.Header().Set("Content-Type", "text/html")
	state.RLock()
	defer state.RUnlock()

	response.Write(
		[]byte(`

 			<meta http-equiv="refresh" content="4">

` + state.Format(list) + "
"))
}

func stateHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	params := mux.Vars(req)
	defer req.Body.Close()

	state.RLock()
	defer state.RUnlock()

	if params["extension"] == ".json" {
		response.Header().Set("Content-Type", "application/json")
		response.Header().Set("Access-Control-Allow-Origin", "*")
		response.Header().Set("Access-Control-Allow-Methods", "GET")
		response.Write(state.Encode())
		return
	}
}

func optionsHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	response.Header().Set("Access-Control-Allow-Origin", "*")
	response.Header().Set("Access-Control-Allow-Methods", "GET")
	return
}

func statusStr(status int) string {
	switch status {
	case 0:
		return "Alive"
	case 1:
		return "Tombstone"
	case 2:
		return "Unhealthy"
	default:
		return "Unknown"
	}
}

func portsStr(svcPorts []service.Port) string {
	var ports []string

	for _, port := range svcPorts {
		if port.ServicePort != 0 {
			ports = append(ports, fmt.Sprintf("%v->%v", port.ServicePort, port.Port))
		} else {
			ports = append(ports, fmt.Sprintf("%v", port.Port))
		}
	}

	return strings.Join(ports, ", ")
}

type listByName []*memberlist.Node

func (a listByName) Len() int           { return len(a) }
func (a listByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a listByName) Less(i, j int) bool { return a[i].Name < a[j].Name }

type Member struct {
	Node    *memberlist.Node
	Updated time.Time
}

func lineWrapMembers(cols int, fields []*Member) [][]*Member {
	if len(fields) < cols {
		return [][]*Member{fields}
	}

	retval := make([][]*Member, len(fields)/cols+1)
	for i := 0; i < len(fields); i++ {
		row := i / cols
		retval[row] = append(retval[row], fields[i])
	}

	return retval
}

func viewHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	timeAgo := func(when time.Time) string { return output.TimeAgo(when, time.Now().UTC()) }

	funcMap := template.FuncMap{
		"statusStr":   statusStr,
		"timeAgo":     timeAgo,
		"portsStr":    portsStr,
		"clusterName": func() string { return list.ClusterName() },
	}

	t, err := template.New("services").Funcs(funcMap).ParseFiles("views/services.html")
	if err != nil {
		log.Errorf("Error parsing template: %s", err.Error())
	}

	members := list.Members()
	sort.Sort(listByName(members))

	state.RLock()
	defer state.RUnlock()

	compiledMembers := make([]*Member, len(members))
	for i, member := range members {
		if state.HasServer(member.Name) {
			compiledMembers[i] = &Member{member, state.Servers[member.Name].LastUpdated}
		} else {
			compiledMembers[i] = &Member{Node: member}
			log.Debug("No updated time for " + member.Name)
		}
	}

	wrappedMembers := lineWrapMembers(5, compiledMembers)

	viewData := struct {
		Services map[string][]*service.Service
		Members  [][]*Member
	}{
		Services: state.ByService(),
		Members:  wrappedMembers,
	}

	t.ExecuteTemplate(response, "services.html", viewData)
}

func uiRedirectHandler(response http.ResponseWriter, req *http.Request) {
	http.Redirect(response, req, "/ui/", 301)
}

func serveHttp(list *memberlist.Memberlist, state *catalog.ServicesState) {
	router := mux.NewRouter()

	router.HandleFunc("/", uiRedirectHandler).Methods("GET")

	router.HandleFunc(
		"/services{extension}", makeHandler(servicesHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/servers", makeHandler(serversHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/services", makeHandler(viewHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/state{extension}", makeHandler(stateHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/watch", makeHandler(watchHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/{path}", makeHandler(optionsHandler, list, state),
	).Methods("OPTIONS")

	staticFs := http.FileServer(http.Dir("views/static"))
	router.PathPrefix("/static").Handler(http.StripPrefix("/static", staticFs))

	uiFs := http.FileServer(http.Dir("ui/app"))
	router.PathPrefix("/ui").Handler(http.StripPrefix("/ui", uiFs))

	http.Handle("/", router)

	err := http.ListenAndServe("0.0.0.0:7777", nil)
	exitWithError(err, "Can't start HTTP server")
}
<commit_msg>explicit return from watchHandler<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	_ "net/http/pprof"
	"sort"
	"strings"
	"time"

	"github.com/Nitro/memberlist"
	"github.com/Nitro/sidecar/catalog"
	"github.com/Nitro/sidecar/output"
	"github.com/Nitro/sidecar/service"
	log "github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
)

type ApiServer struct {
	Name         string
	LastUpdated  time.Time
	ServiceCount int
}

type ApiServices struct {
	Services       map[string][]*service.Service
	ClusterMembers map[string]*ApiServer
	ClusterName    string
}

// A ServicesState.Listener that we use for the /watch endpoint
type HttpListener struct {
	eventChan chan catalog.ChangeEvent
	name      string
}

func NewHttpListener() *HttpListener {
	return &HttpListener{
		// This should be fine enough granularity for practical purposes
		name: fmt.Sprintf("httpListener-%d", time.Now().UTC().UnixNano()),
		// Listeners must have buffered channels. We'll use a
		// somewhat larger buffer here because of the slow link
		// problem with http
		eventChan: make(chan catalog.ChangeEvent, 50),
	}
}

func (h *HttpListener) Chan() chan catalog.ChangeEvent {
	return h.eventChan
}

func (h *HttpListener) Name() string {
	return h.name
}

func makeHandler(fn func(http.ResponseWriter, *http.Request,
	*memberlist.Memberlist, *catalog.ServicesState),
	list *memberlist.Memberlist, state *catalog.ServicesState) http.HandlerFunc {

	return func(response http.ResponseWriter, req *http.Request) {
		fn(response, req, list, state)
	}
}

func watchHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	defer req.Body.Close()

	response.Header().Set("Content-Type", "application/json")

	var jsonBytes []byte
	var err error

	listener := NewHttpListener()

	// Find out when the http connection closed so we can stop
	notify := response.(http.CloseNotifier).CloseNotify()

	// Let's subscribe to state change events
	state.AddListener(listener)
	defer state.RemoveListener(listener.Name())

	for {
		select {
		case <-notify:
			return

		case <-listener.Chan():
			jsonBytes, err = json.Marshal(state.ByService())
			if err != nil {
				log.Errorf("Error marshaling state in watchHandler: %s", err.Error())
				return
			}
			// In order to flush immediately, we have to cast to a Flusher.
			// The normal HTTP library supports this but not all do, so we
			// check just in case.
			response.Write(jsonBytes)
			if f, ok := response.(http.Flusher); ok {
				f.Flush()
			}
		}
	}
}

func servicesHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	params := mux.Vars(req)
	defer req.Body.Close()

	if params["extension"] == ".json" {
		response.Header().Set("Content-Type", "application/json")
		response.Header().Set("Access-Control-Allow-Origin", "*")
		response.Header().Set("Access-Control-Allow-Methods", "GET")

		listMembers := list.Members()
		sort.Sort(listByName(listMembers))
		members := make(map[string]*ApiServer, len(listMembers))

		var jsonBytes []byte
		var err error

		func() { // Wrap critical section
			state.RLock()
			defer state.RUnlock()

			for _, member := range listMembers {
				if state.HasServer(member.Name) {
					members[member.Name] = &ApiServer{
						Name:         member.Name,
						LastUpdated:  state.Servers[member.Name].LastUpdated,
						ServiceCount: len(state.Servers[member.Name].Services),
					}
				} else {
					members[member.Name] = &ApiServer{
						Name:         member.Name,
						LastUpdated:  time.Unix(0, 0),
						ServiceCount: 0,
					}
				}
			}

			result := ApiServices{
				Services:       state.ByService(),
				ClusterMembers: members,
				ClusterName:    list.ClusterName(),
			}

			jsonBytes, err = json.MarshalIndent(&result, "", "  ")
		}()

		if err != nil {
			log.Errorf("Error marshaling state in servicesHandler: %s", err.Error())
		}

		response.Write(jsonBytes)
		return
	}
}

func serversHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	defer req.Body.Close()

	response.Header().Set("Content-Type", "text/html")
	state.RLock()
	defer state.RUnlock()

	response.Write(
		[]byte(`

 			<meta http-equiv="refresh" content="4">

` + state.Format(list) + "
"))
}

func stateHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	params := mux.Vars(req)
	defer req.Body.Close()

	state.RLock()
	defer state.RUnlock()

	if params["extension"] == ".json" {
		response.Header().Set("Content-Type", "application/json")
		response.Header().Set("Access-Control-Allow-Origin", "*")
		response.Header().Set("Access-Control-Allow-Methods", "GET")
		response.Write(state.Encode())
		return
	}
}

func optionsHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	response.Header().Set("Access-Control-Allow-Origin", "*")
	response.Header().Set("Access-Control-Allow-Methods", "GET")
	return
}

func statusStr(status int) string {
	switch status {
	case 0:
		return "Alive"
	case 1:
		return "Tombstone"
	case 2:
		return "Unhealthy"
	default:
		return "Unknown"
	}
}

func portsStr(svcPorts []service.Port) string {
	var ports []string

	for _, port := range svcPorts {
		if port.ServicePort != 0 {
			ports = append(ports, fmt.Sprintf("%v->%v", port.ServicePort, port.Port))
		} else {
			ports = append(ports, fmt.Sprintf("%v", port.Port))
		}
	}

	return strings.Join(ports, ", ")
}

type listByName []*memberlist.Node

func (a listByName) Len() int           { return len(a) }
func (a listByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a listByName) Less(i, j int) bool { return a[i].Name < a[j].Name }

type Member struct {
	Node    *memberlist.Node
	Updated time.Time
}

func lineWrapMembers(cols int, fields []*Member) [][]*Member {
	if len(fields) < cols {
		return [][]*Member{fields}
	}

	retval := make([][]*Member, len(fields)/cols+1)
	for i := 0; i < len(fields); i++ {
		row := i / cols
		retval[row] = append(retval[row], fields[i])
	}

	return retval
}

func viewHandler(response http.ResponseWriter, req *http.Request, list *memberlist.Memberlist, state *catalog.ServicesState) {
	timeAgo := func(when time.Time) string { return output.TimeAgo(when, time.Now().UTC()) }

	funcMap := template.FuncMap{
		"statusStr":   statusStr,
		"timeAgo":     timeAgo,
		"portsStr":    portsStr,
		"clusterName": func() string { return list.ClusterName() },
	}

	t, err := template.New("services").Funcs(funcMap).ParseFiles("views/services.html")
	if err != nil {
		log.Errorf("Error parsing template: %s", err.Error())
	}

	members := list.Members()
	sort.Sort(listByName(members))

	state.RLock()
	defer state.RUnlock()

	compiledMembers := make([]*Member, len(members))
	for i, member := range members {
		if state.HasServer(member.Name) {
			compiledMembers[i] = &Member{member, state.Servers[member.Name].LastUpdated}
		} else {
			compiledMembers[i] = &Member{Node: member}
			log.Debug("No updated time for " + member.Name)
		}
	}

	wrappedMembers := lineWrapMembers(5, compiledMembers)

	viewData := struct {
		Services map[string][]*service.Service
		Members  [][]*Member
	}{
		Services: state.ByService(),
		Members:  wrappedMembers,
	}

	t.ExecuteTemplate(response, "services.html", viewData)
}

func uiRedirectHandler(response http.ResponseWriter, req *http.Request) {
	http.Redirect(response, req, "/ui/", 301)
}

func serveHttp(list *memberlist.Memberlist, state *catalog.ServicesState) {
	router := mux.NewRouter()

	router.HandleFunc("/", uiRedirectHandler).Methods("GET")

	router.HandleFunc(
		"/services{extension}", makeHandler(servicesHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/servers", makeHandler(serversHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/services", makeHandler(viewHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/state{extension}", makeHandler(stateHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/watch", makeHandler(watchHandler, list, state),
	).Methods("GET")

	router.HandleFunc(
		"/{path}", makeHandler(optionsHandler, list, state),
	).Methods("OPTIONS")

	staticFs := http.FileServer(http.Dir("views/static"))
	router.PathPrefix("/static").Handler(http.StripPrefix("/static", staticFs))

	uiFs := http.FileServer(http.Dir("ui/app"))
	router.PathPrefix("/ui").Handler(http.StripPrefix("/ui", uiFs))

	http.Handle("/", router)

	err := http.ListenAndServe("0.0.0.0:7777", nil)
	exitWithError(err, "Can't start HTTP server")
}
<|endoftext|>


<commit_before>package main

import (
    "log"
    "strconv"
    "net/http"
    "huabot-sched/db"
    "github.com/go-martini/martini"
    "github.com/martini-contrib/render"
    "github.com/martini-contrib/binding"
)

const API = ""

func StartHttpServer(addr string, sched *Sched) {
    mart := martini.Classic()
    mart.Use(render.Renderer(render.Options{
        Directory: "templates",
        Layout: "layout",
        Extensions: []string{".tmpl", ".html"},
        Charset: "UTF-8",
        IndentJSON: true,
        IndentXML: true,
        HTMLContentType: "application/xhtml+xml",
    }))

    api(mart, sched)

    mart.RunOnAddr(addr)
}

type JobForm struct {
    Name    string `form:"name" binding:"required"`
    Func    string `form:"func" binding:"required"`
    Args    string `form:"workload"`
    Timeout int64  `form:"timeout"`
    SchedAt int64  `form:"sched_at" binding:"required"`
}

func api(mart *martini.ClassicMartini, sched *Sched) {

    mart.Post(API + "/jobs/", binding.Bind(JobForm{}), func(j JobForm, r render.Render) {
        job := db.Job{
            Name: j.Name,
            Func: j.Func,
            Args: j.Args,
            Timeout: j.Timeout,
            SchedAt: j.SchedAt,
            Status: "ready",
        }
        jobId, _ := db.GetIndex("job:" + job.Func + ":name", job.Name)
        if jobId > 0 {
            job.Id = jobId
        }
        err := job.Save()
        if err != nil {
            r.JSON(http.StatusInternalServerError, map[string]interface{}{"err": err.Error()})
            return
        }
        sched.Notify()
        r.JSON(http.StatusOK, map[string]db.Job{"job": job})
    })

    mart.Get(API + "/jobs/(?P<func>[^/]+)/(?P<status>ready|doing)/",
            func(params martini.Params, req *http.Request, r render.Render) {
        Func := params["func"]
        status := params["status"]
        qs := req.URL.Query()
        var start, limit, stop int
        var err error
        if start, err = strconv.Atoi(qs.Get("start")); err != nil {
            start = 0
        }
        if limit, err = strconv.Atoi(qs.Get("limit")); err != nil {
            limit = 10
        }
        stop = start + limit
        var jobs []db.Job
        var count int64
        jobs, err = db.RangeSchedJob(Func, status, start, stop)
        count, _ = db.CountSchedJob(Func, status)
        if err != nil {
            log.Printf("Error: RangeSchedJob error %s\n", err)
            r.JSON(http.StatusOK, map[string]interface{}{"jobs": "[]", "total": count, "current": start})
            return
        }
        r.JSON(http.StatusOK, map[string]interface{}{"jobs": jobs, "total": count, "current": start})
    })

    mart.Get(API + "/jobs/(?P<job_id>[0-9a-zA-Z]+)",
            func(params martini.Params, req *http.Request, r render.Render) {
        qs := req.URL.Query()
        id := params["job_id"]
        var jobId int64
        if qs.Get("id_type") == "name" {
            Func := qs.Get("func")
            jobId, _ = db.GetIndex("job:" + Func + ":name", id)
        } else {
            jobId, _ = strconv.ParseInt(id, 10 ,0)
        }
        job, err := db.GetJob(jobId)
        if err != nil {
            r.JSON(http.StatusNotFound, map[string]interface{}{"err": err.Error()})
            return
        }
        r.JSON(http.StatusOK, map[string]db.Job{"job": job})
    })

    mart.Get(API + "/jobs/", func(req *http.Request, r render.Render) {
        qs := req.URL.Query()
        var start, limit, stop int
        var err error
        if start, err = strconv.Atoi(qs.Get("start")); err != nil {
            start = 0
        }
        if limit, err = strconv.Atoi(qs.Get("limit")); err != nil {
            limit = 10
        }
        stop = start + limit
        var jobs []db.Job
        var count int64
        jobs, err = db.RangeJob(start, stop)
        count, _ = db.CountJob()
        if err != nil {
            log.Printf("Error: RangeJob error %s\n", err)
            r.JSON(http.StatusOK, map[string]interface{}{"jobs": "[]", "total": count, "current": start})
            return
        }
        r.JSON(http.StatusOK, map[string]interface{}{"jobs": jobs, "total": count, "current": start})
    })

    mart.Delete(API + "/jobs/(?P<job_id>[0-9a-zA-Z]+)",
            func(params martini.Params, req *http.Request, r render.Render) {
        qs := req.URL.Query()
        id := params["job_id"]
        var jobId int64
        if qs.Get("id_type") == "name" {
            Func := qs.Get("func")
            jobId, _ = db.GetIndex("job:" + Func + ":name", id)
        } else {
            jobId, _ = strconv.ParseInt(id, 10, 0)
        }
        err := db.DelJob(jobId)
        if err != nil {
            log.Printf("Error: DelJob error %s\n", err)
            r.JSON(http.StatusOK, map[string]interface{}{"err": err.Error()})
            return
        }
        sched.Notify()
        r.JSON(http.StatusOK, map[string]interface{}{})
    })

    mart.Post(API + "/notify", func(r render.Render) {
        sched.Notify()
        r.JSON(http.StatusOK, map[string]interface{}{})
    })

    mart.Get(API + "/(?P<func>[^/]+)/status", func(params martini.Params, r render.Render) {
        var status = make(map[string]int64)
        Func := params["func"]
        var count int64
        count, _ = db.CountJob()
        status["total"] = count
        count, _ = db.CountSchedJob(Func, "ready")
        status["ready"] = count
        count, _ = db.CountSchedJob(Func, "doing")
        status["doing"] = count
        status["worker_count"] = int64(sched.TotalWorkerCount)
        r.JSON(http.StatusOK, status)
    })
}
<commit_msg>Add global status<commit_after>package main

import (
    "log"
    "strconv"
    "net/http"
    "huabot-sched/db"
    "github.com/go-martini/martini"
    "github.com/martini-contrib/render"
    "github.com/martini-contrib/binding"
)

const API = ""

func StartHttpServer(addr string, sched *Sched) {
    mart := martini.Classic()
    mart.Use(render.Renderer(render.Options{
        Directory: "templates",
        Layout: "layout",
        Extensions: []string{".tmpl", ".html"},
        Charset: "UTF-8",
        IndentJSON: true,
        IndentXML: true,
        HTMLContentType: "application/xhtml+xml",
    }))

    api(mart, sched)

    mart.RunOnAddr(addr)
}

type JobForm struct {
    Name    string `form:"name" binding:"required"`
    Func    string `form:"func" binding:"required"`
    Args    string `form:"workload"`
    Timeout int64  `form:"timeout"`
    SchedAt int64  `form:"sched_at" binding:"required"`
}

func api(mart *martini.ClassicMartini, sched *Sched) {

    mart.Post(API + "/jobs/", binding.Bind(JobForm{}), func(j JobForm, r render.Render) {
        job := db.Job{
            Name: j.Name,
            Func: j.Func,
            Args: j.Args,
            Timeout: j.Timeout,
            SchedAt: j.SchedAt,
            Status: "ready",
        }
        jobId, _ := db.GetIndex("job:" + job.Func + ":name", job.Name)
        if jobId > 0 {
            job.Id = jobId
        }
        err := job.Save()
        if err != nil {
            r.JSON(http.StatusInternalServerError, map[string]interface{}{"err": err.Error()})
            return
        }
        sched.Notify()
        r.JSON(http.StatusOK, map[string]db.Job{"job": job})
    })

    mart.Get(API + "/jobs/(?P<func>[^/]+)/(?P<status>ready|doing)/",
            func(params martini.Params, req *http.Request, r render.Render) {
        Func := params["func"]
        status := params["status"]
        qs := req.URL.Query()
        var start, limit, stop int
        var err error
        if start, err = strconv.Atoi(qs.Get("start")); err != nil {
            start = 0
        }
        if limit, err = strconv.Atoi(qs.Get("limit")); err != nil {
            limit = 10
        }
        stop = start + limit
        var jobs []db.Job
        var count int64
        jobs, err = db.RangeSchedJob(Func, status, start, stop)
        count, _ = db.CountSchedJob(Func, status)
        if err != nil {
            log.Printf("Error: RangeSchedJob error %s\n", err)
            r.JSON(http.StatusOK, map[string]interface{}{"jobs": "[]", "total": count, "current": start})
            return
        }
        r.JSON(http.StatusOK, map[string]interface{}{"jobs": jobs, "total": count, "current": start})
    })

    mart.Get(API + "/jobs/(?P<job_id>[0-9a-zA-Z]+)",
            func(params martini.Params, req *http.Request, r render.Render) {
        qs := req.URL.Query()
        id := params["job_id"]
        var jobId int64
        if qs.Get("id_type") == "name" {
            Func := qs.Get("func")
            jobId, _ = db.GetIndex("job:" + Func + ":name", id)
        } else {
            jobId, _ = strconv.ParseInt(id, 10 ,0)
        }
        job, err := db.GetJob(jobId)
        if err != nil {
            r.JSON(http.StatusNotFound, map[string]interface{}{"err": err.Error()})
            return
        }
        r.JSON(http.StatusOK, map[string]db.Job{"job": job})
    })

    mart.Get(API + "/jobs/", func(req *http.Request, r render.Render) {
        qs := req.URL.Query()
        var start, limit, stop int
        var err error
        if start, err = strconv.Atoi(qs.Get("start")); err != nil {
            start = 0
        }
        if limit, err = strconv.Atoi(qs.Get("limit")); err != nil {
            limit = 10
        }
        stop = start + limit
        var jobs []db.Job
        var count int64
        jobs, err = db.RangeJob(start, stop)
        count, _ = db.CountJob()
        if err != nil {
            log.Printf("Error: RangeJob error %s\n", err)
            r.JSON(http.StatusOK, map[string]interface{}{"jobs": "[]", "total": count, "current": start})
            return
        }
        r.JSON(http.StatusOK, map[string]interface{}{"jobs": jobs, "total": count, "current": start})
    })

    mart.Delete(API + "/jobs/(?P<job_id>[0-9a-zA-Z]+)",
            func(params martini.Params, req *http.Request, r render.Render) {
        qs := req.URL.Query()
        id := params["job_id"]
        var jobId int64
        if qs.Get("id_type") == "name" {
            Func := qs.Get("func")
            jobId, _ = db.GetIndex("job:" + Func + ":name", id)
        } else {
            jobId, _ = strconv.ParseInt(id, 10, 0)
        }
        err := db.DelJob(jobId)
        if err != nil {
            log.Printf("Error: DelJob error %s\n", err)
            r.JSON(http.StatusOK, map[string]interface{}{"err": err.Error()})
            return
        }
        sched.Notify()
        r.JSON(http.StatusOK, map[string]interface{}{})
    })

    mart.Post(API + "/notify", func(r render.Render) {
        sched.Notify()
        r.JSON(http.StatusOK, map[string]interface{}{})
    })

    mart.Get(API + "/(?P<func>[^/]+)/status", func(params martini.Params, r render.Render) {
        var status = make(map[string]int64)
        Func := params["func"]
        var count int64
        count, _ = db.CountJob()
        status["total"] = count
        count, _ = db.CountSchedJob(Func, "ready")
        status["ready"] = count
        count, _ = db.CountSchedJob(Func, "doing")
        status["doing"] = count
        status["worker_count"] = int64(sched.TotalWorkerCount)
        r.JSON(http.StatusOK, status)
    })

    mart.Get(API + "/status", func(params martini.Params, r render.Render) {
        r.JSON(http.StatusOK, sched.Funcs)
    })
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"fmt"
	"github.com/fatih/color"
	pb "gopkg.in/cheggaaa/pb.v1"
	"io"
	"net"
	"net/http"
	stdurl "net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
)

var (
	tr = &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client = &http.Client{Transport: tr}
)

var (
	acceptRangeHeader   = "Accept-Ranges"
	contentLengthHeader = "Content-Length"
)

type HttpDownloader struct {
	url       string
	file      string
	par       int64
	len       int64
	ips       []string
	skipTls   bool
	parts     []Part
	resumable bool
}

func NewHttpDownloader(url string, par int, skipTls bool) *HttpDownloader {
	var resumable = true

	parsed, err := stdurl.Parse(url)
	FatalCheck(err)

	ips, err := net.LookupIP(parsed.Host)
	FatalCheck(err)

	ipstr := FilterIPV4(ips)
	Printf("Resolve ip: %s\n", strings.Join(ipstr, " | "))

	req, err := http.NewRequest("GET", url, nil)
	FatalCheck(err)

	resp, err := client.Do(req)
	FatalCheck(err)

	if resp.Header.Get(acceptRangeHeader) == "" {
		Warnf("Target url is not supported range download\n")
		//fallback to par = 1
		par = 1
	}

	//get download range
	clen := resp.Header.Get(contentLengthHeader)
	if clen == "" {
		Warnf("Target url not contain Content-Length header\n")
		clen = "1" //set 1 because of progress bar not accept 0 length
		resumable = false
	}

	len, err := strconv.ParseInt(clen, 10, 64)
	FatalCheck(err)

	sizeInMb := float64(len) / (1024 * 1024)

	if clen == "1" {
		Printf("Dowload size: not specified\n")
	} else if sizeInMb < 1024 {
		Printf("Download target size: %.1f MB\n", sizeInMb)
	} else {
		Printf("Download target size: %.1f GB\n", sizeInMb/1024)
	}

	file := filepath.Base(url)
	ret := new(HttpDownloader)
	ret.url = url
	ret.file = file
	ret.par = int64(par)
	ret.len = len
	ret.ips = ipstr
	ret.skipTls = skipTls
	ret.parts = partCalculate(int64(par), len, url)
	ret.resumable = resumable

	return ret
}

func partCalculate(par int64, len int64, url string) []Part {
	ret := make([]Part, 0)
	for j := int64(0); j < par; j++ {
		from := (len / par) * j
		var to int64
		if j < par-1 {
			to = (len/par)*(j+1) - 1
		} else {
			to = len
		}

		file := filepath.Base(url)
		folder := FolderOf(url)
		if err := MkdirIfNotExist(folder); err != nil {
			Errorf("%v", err)
			os.Exit(1)
		}

		fname := fmt.Sprintf("%s.part%d", file, j)
		path := filepath.Join(folder, fname) // ~/.hget/download-file-name/part-name
		ret = append(ret, Part{Url: url, Path: path, RangeFrom: from, RangeTo: to})
	}
	return ret
}

func (d *HttpDownloader) Do(doneChan chan bool, fileChan chan string, errorChan chan error, interruptChan chan bool, stateSaveChan chan Part) {
	var ws sync.WaitGroup

	bars := make([]*pb.ProgressBar, 0)
	for i, part := range d.parts {
		newbar := pb.New64(part.RangeTo - part.RangeFrom).SetUnits(pb.U_BYTES).Prefix(color.YellowString(fmt.Sprintf("%s-%d", d.file, i)))
		bars = append(bars, newbar)
	}

	barpool, err := pb.StartPool(bars...)
	FatalCheck(err)

	for i, p := range d.parts {
		ws.Add(1)
		go func(d *HttpDownloader, loop int64, part Part) {
			defer ws.Done()
			var bar = bars[loop]

			var ranges string
			if part.RangeTo != d.len {
				ranges = fmt.Sprintf("bytes=%d-%d", part.RangeFrom, part.RangeTo)
			} else {
				ranges = fmt.Sprintf("bytes=%d-", part.RangeFrom) //get all
			}

			//send request
			req, err := http.NewRequest("GET", d.url, nil)
			if err != nil {
				errorChan <- err
				return
			}

			if d.par > 1 { //support range download just in case parallel factor is over 1
				req.Header.Add("Range", ranges)
				if err != nil {
					errorChan <- err
					return
				}
			}

			//write to file
			resp, err := client.Do(req)
			if err != nil {
				errorChan <- err
				return
			}
			defer resp.Body.Close()
			f, err := os.OpenFile(part.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)

			defer f.Close()
			if err != nil {
				Errorf("%v\n", err)
				errorChan <- err
				return
			}

			writer := io.MultiWriter(f, bar)

			//make copy interruptable by copy 100 bytes each loop
			current := int64(0)
			for {
				select {
				case <-interruptChan:
					stateSaveChan <- Part{Url: d.url, Path: part.Path, RangeFrom: current + part.RangeFrom, RangeTo: part.RangeTo}
					return
				default:
					written, err := io.CopyN(writer, resp.Body, 100)
					current += written
					if err != nil {
						if err != io.EOF {
							errorChan <- err
						}
						bar.Finish()
						fileChan <- part.Path
						return
					}
				}
			}
		}(d, int64(i), p)
	}

	ws.Wait()
	doneChan <- true
	barpool.Stop()
}
<commit_msg>Fallback to par 1 when size not specify<commit_after>package main

import (
	"crypto/tls"
	"fmt"
	"github.com/fatih/color"
	pb "gopkg.in/cheggaaa/pb.v1"
	"io"
	"net"
	"net/http"
	stdurl "net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
)

var (
	tr = &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client = &http.Client{Transport: tr}
)

var (
	acceptRangeHeader   = "Accept-Ranges"
	contentLengthHeader = "Content-Length"
)

type HttpDownloader struct {
	url       string
	file      string
	par       int64
	len       int64
	ips       []string
	skipTls   bool
	parts     []Part
	resumable bool
}

func NewHttpDownloader(url string, par int, skipTls bool) *HttpDownloader {
	var resumable = true

	parsed, err := stdurl.Parse(url)
	FatalCheck(err)

	ips, err := net.LookupIP(parsed.Host)
	FatalCheck(err)

	ipstr := FilterIPV4(ips)
	Printf("Resolve ip: %s\n", strings.Join(ipstr, " | "))

	req, err := http.NewRequest("GET", url, nil)
	FatalCheck(err)

	resp, err := client.Do(req)
	FatalCheck(err)

	if resp.Header.Get(acceptRangeHeader) == "" {
		Printf("Target url is not supported range download, fallback to parallel 1\n")
		//fallback to par = 1
		par = 1
	}

	//get download range
	clen := resp.Header.Get(contentLengthHeader)
	if clen == "" {
		Printf("Target url not contain Content-Length header, fallback to parallel 1\n")
		clen = "1" //set 1 because of progress bar not accept 0 length
		par = 1
		resumable = false
	}

	Printf("Start download with %d connections \n", par)

	len, err := strconv.ParseInt(clen, 10, 64)
	FatalCheck(err)

	sizeInMb := float64(len) / (1024 * 1024)

	if clen == "1" {
		Printf("Download size: not specified\n")
	} else if sizeInMb < 1024 {
		Printf("Download target size: %.1f MB\n", sizeInMb)
	} else {
		Printf("Download target size: %.1f GB\n", sizeInMb/1024)
	}

	file := filepath.Base(url)
	ret := new(HttpDownloader)
	ret.url = url
	ret.file = file
	ret.par = int64(par)
	ret.len = len
	ret.ips = ipstr
	ret.skipTls = skipTls
	ret.parts = partCalculate(int64(par), len, url)
	ret.resumable = resumable

	return ret
}

func partCalculate(par int64, len int64, url string) []Part {
	ret := make([]Part, 0)
	for j := int64(0); j < par; j++ {
		from := (len / par) * j
		var to int64
		if j < par-1 {
			to = (len/par)*(j+1) - 1
		} else {
			to = len
		}

		file := filepath.Base(url)
		folder := FolderOf(url)
		if err := MkdirIfNotExist(folder); err != nil {
			Errorf("%v", err)
			os.Exit(1)
		}

		fname := fmt.Sprintf("%s.part%d", file, j)
		path := filepath.Join(folder, fname) // ~/.hget/download-file-name/part-name
		ret = append(ret, Part{Url: url, Path: path, RangeFrom: from, RangeTo: to})
	}
	return ret
}

func (d *HttpDownloader) Do(doneChan chan bool, fileChan chan string, errorChan chan error, interruptChan chan bool, stateSaveChan chan Part) {
	var ws sync.WaitGroup

	bars := make([]*pb.ProgressBar, 0)
	for i, part := range d.parts {
		newbar := pb.New64(part.RangeTo - part.RangeFrom).SetUnits(pb.U_BYTES).Prefix(color.YellowString(fmt.Sprintf("%s-%d", d.file, i)))
		bars = append(bars, newbar)
	}

	barpool, err := pb.StartPool(bars...)
	FatalCheck(err)

	for i, p := range d.parts {
		ws.Add(1)
		go func(d *HttpDownloader, loop int64, part Part) {
			defer ws.Done()
			var bar = bars[loop]

			var ranges string
			if part.RangeTo != d.len {
				ranges = fmt.Sprintf("bytes=%d-%d", part.RangeFrom, part.RangeTo)
			} else {
				ranges = fmt.Sprintf("bytes=%d-", part.RangeFrom) //get all
			}

			//send request
			req, err := http.NewRequest("GET", d.url, nil)
			if err != nil {
				errorChan <- err
				return
			}

			if d.par > 1 { //support range download just in case parallel factor is over 1
				req.Header.Add("Range", ranges)
				if err != nil {
					errorChan <- err
					return
				}
			}

			//write to file
			resp, err := client.Do(req)
			if err != nil {
				errorChan <- err
				return
			}
			defer resp.Body.Close()
			f, err := os.OpenFile(part.Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)

			defer f.Close()
			if err != nil {
				Errorf("%v\n", err)
				errorChan <- err
				return
			}

			writer := io.MultiWriter(f, bar)

			//make copy interruptable by copy 100 bytes each loop
			current := int64(0)
			for {
				select {
				case <-interruptChan:
					stateSaveChan <- Part{Url: d.url, Path: part.Path, RangeFrom: current + part.RangeFrom, RangeTo: part.RangeTo}
					return
				default:
					written, err := io.CopyN(writer, resp.Body, 100)
					current += written
					if err != nil {
						if err != io.EOF {
							errorChan <- err
						}
						bar.Finish()
						fileChan <- part.Path
						return
					}
				}
			}
		}(d, int64(i), p)
	}

	ws.Wait()
	doneChan <- true
	barpool.Stop()
}
<|endoftext|>


<commit_before>/*
Author: Aosen
Data: 2016-01-11
QQ: 316052486
Desc: http中间件，采用接口思想，restfull，开发者只要继承基类，即可处理http请求
*/

package utils

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

func PutError(err error) {
	if err != nil {
		log.Fatal(err.Error())
	}
}

type HandleInterface interface {
	Prepare(w http.ResponseWriter, r *http.Request, g *G)
	Get(w http.ResponseWriter, r *http.Request, g *G)
	Put(w http.ResponseWriter, r *http.Request, g *G)
	Post(w http.ResponseWriter, r *http.Request, g *G)
	Options(w http.ResponseWriter, r *http.Request, g *G)
	Head(w http.ResponseWriter, r *http.Request, g *G)
	Delete(w http.ResponseWriter, r *http.Request, g *G)
	Connect(w http.ResponseWriter, r *http.Request, g *G)
	Finish(w http.ResponseWriter, r *http.Request, g *G)
}

/*全局控制对象*/
type Global struct {
	//settings 配置信息
	Settings map[string]interface{}
}

func (self *Global) Go(handler HandleInterface) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		//为了保证程序不会异常退出，增加recover
		debug, err := GetSetting(self.Settings, "DEBUG")
		PutError(err)
		if debug != "True" {
			defer func() {
				if x := recover(); x != nil {
					log.Printf("[%v] caught panic: %v", r.RemoteAddr, x)
				}
			}()
		}
		//无论什么方法 都预先调用prepare方法
		handler.Prepare(w, r, self)
		//相应http方法关联处理
		switch r.Method {
		case "GET":
			handler.Get(w, r, self)
		case "PUT":
			handler.Put(w, r, self)
		case "POST":
			handler.Post(w, r, self)
		case "OPTIONS":
			handler.Options(w, r, self)
		case "HEAD":
			handler.Head(w, r, self)
		case "DELETE":
			handler.Delete(w, r, self)
		case "CONNECT":
			handler.Connect(w, r, self)
		}
		//无论什么方法 结束后都调用finish方法
		handler.Finish(w, r, self)
	}
}

//所有http处理类都继承此类
type Web struct {
}

func (self *Web) Prepare(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Get(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Put(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Post(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Options(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Head(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Delete(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Connect(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Finish(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}
<commit_msg>修复bug<commit_after>/*
Author: Aosen
Data: 2016-01-11
QQ: 316052486
Desc: http中间件，采用接口思想，restfull，开发者只要继承基类，即可处理http请求
*/

package utils

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

func PutError(err error) {
	if err != nil {
		log.Fatal(err.Error())
	}
}

type HandleInterface interface {
	Prepare(w http.ResponseWriter, r *http.Request, g *Global)
	Get(w http.ResponseWriter, r *http.Request, g *Global)
	Put(w http.ResponseWriter, r *http.Request, g *Global)
	Post(w http.ResponseWriter, r *http.Request, g *Global)
	Options(w http.ResponseWriter, r *http.Request, g *Global)
	Head(w http.ResponseWriter, r *http.Request, g *Global)
	Delete(w http.ResponseWriter, r *http.Request, g *Global)
	Connect(w http.ResponseWriter, r *http.Request, g *Global)
	Finish(w http.ResponseWriter, r *http.Request, g *Global)
}

/*全局控制对象*/
type Global struct {
	//settings 配置信息
	Settings map[string]interface{}
}

func (self *Global) Go(handler HandleInterface) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		//为了保证程序不会异常退出，增加recover
		debug, err := GetSetting(self.Settings, "DEBUG")
		PutError(err)
		if debug != "True" {
			defer func() {
				if x := recover(); x != nil {
					log.Printf("[%v] caught panic: %v", r.RemoteAddr, x)
				}
			}()
		}
		//无论什么方法 都预先调用prepare方法
		handler.Prepare(w, r, self)
		//相应http方法关联处理
		switch r.Method {
		case "GET":
			handler.Get(w, r, self)
		case "PUT":
			handler.Put(w, r, self)
		case "POST":
			handler.Post(w, r, self)
		case "OPTIONS":
			handler.Options(w, r, self)
		case "HEAD":
			handler.Head(w, r, self)
		case "DELETE":
			handler.Delete(w, r, self)
		case "CONNECT":
			handler.Connect(w, r, self)
		}
		//无论什么方法 结束后都调用finish方法
		handler.Finish(w, r, self)
	}
}

//所有http处理类都继承此类
type Web struct {
}

func (self *Web) Prepare(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Get(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Put(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Post(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Options(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Head(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Delete(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Connect(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}

func (self *Web) Finish(w http.ResponseWriter, r *http.Request, g *G) {
	fmt.Fprintln(w, "sorry 404 not found")
}
<|endoftext|>


<commit_before>// Copyright 2012-2016 Apcera Inc. All rights reserved.

package server

import (
	"time"

	"github.com/nats-io/nuid"
)

// Use nuid.
func genID() string {
	return nuid.Next()
}

// Ascii numbers 0-9
const (
	asciiZero = 48
	asciiNine = 57
)

// parseSize expects decimal positive numbers. We
// return -1 to signal error
func parseSize(d []byte) (n int) {
	if len(d) == 0 {
		return -1
	}
	for _, dec := range d {
		if dec < asciiZero || dec > asciiNine {
			return -1
		}
		n = n*10 + (int(dec) - asciiZero)
	}
	return n
}

// parseInt64 expects decimal positive numbers. We
// return -1 to signal error
func parseInt64(d []byte) (n int64) {
	if len(d) == 0 {
		return -1
	}
	for _, dec := range d {
		if dec < asciiZero || dec > asciiNine {
			return -1
		}
		n = n*10 + (int64(dec) - asciiZero)
	}
	return n
}

// Helper to move from float seconds to time.Duration
func secondsToDuration(seconds float64) time.Duration {
	ttl := seconds * float64(time.Second)
	return time.Duration(ttl)
}
<commit_msg>Remove for loop in parseSize to enable inlining<commit_after>// Copyright 2012-2017 Apcera Inc. All rights reserved.

package server

import (
	"time"

	"github.com/nats-io/nuid"
)

// Use nuid.
func genID() string {
	return nuid.Next()
}

// Ascii numbers 0-9
const (
	asciiZero = 48
	asciiNine = 57
)

// parseSize expects decimal positive numbers. We
// return -1 to signal error.
func parseSize(d []byte) (n int) {
	l := len(d)
	if l == 0 {
		return -1
	}
	var (
		i   int
		dec byte
	)

	// Note: Use `goto` here to avoid for loop in order
	// to have the function be inlined.
	// See: https://github.com/golang/go/issues/14768
loop:
	dec = d[i]
	if dec < asciiZero || dec > asciiNine {
		return -1
	}
	n = n*10 + (int(dec) - asciiZero)

	i++
	if i < l {
		goto loop
	}
	return n
}

// parseInt64 expects decimal positive numbers. We
// return -1 to signal error
func parseInt64(d []byte) (n int64) {
	if len(d) == 0 {
		return -1
	}
	for _, dec := range d {
		if dec < asciiZero || dec > asciiNine {
			return -1
		}
		n = n*10 + (int64(dec) - asciiZero)
	}
	return n
}

// Helper to move from float seconds to time.Duration
func secondsToDuration(seconds float64) time.Duration {
	ttl := seconds * float64(time.Second)
	return time.Duration(ttl)
}
<|endoftext|>


<commit_before>package docker

import (
	"github.com/dotcloud/docker/utils"
	"testing"
	"time"
)

func TestContainerTagImageDelete(t *testing.T) {
	runtime := mkRuntime(t)
	defer nuke(runtime)

	srv := &Server{runtime: runtime}

	initialImages, err := srv.Images(false, "")
	if err != nil {
		t.Fatal(err)
	}

	if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil {
		t.Fatal(err)
	}
	if err := srv.runtime.repositories.Set("utest/docker", "tag2", unitTestImageName, false); err != nil {
		t.Fatal(err)
	}

	images, err := srv.Images(false, "")
	if err != nil {
		t.Fatal(err)
	}

	if len(images) != len(initialImages)+2 {
		t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
	}

	if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil {
		t.Fatal(err)
	}

	images, err = srv.Images(false, "")
	if err != nil {
		t.Fatal(err)
	}

	if len(images) != len(initialImages)+1 {
		t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
	}

	if _, err := srv.ImageDelete("utest:tag1", true); err != nil {
		t.Fatal(err)
	}

	images, err = srv.Images(false, "")
	if err != nil {
		t.Fatal(err)
	}

	if len(images) != len(initialImages) {
		t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
	}
}

func TestCreateRm(t *testing.T) {
	runtime := mkRuntime(t)
	defer nuke(runtime)

	srv := &Server{runtime: runtime}

	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
	if err != nil {
		t.Fatal(err)
	}

	id, err := srv.ContainerCreate(config)
	if err != nil {
		t.Fatal(err)
	}

	if len(runtime.List()) != 1 {
		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
	}

	if err = srv.ContainerDestroy(id, true); err != nil {
		t.Fatal(err)
	}

	if len(runtime.List()) != 0 {
		t.Errorf("Expected 0 container, %v found", len(runtime.List()))
	}

}

func TestCreateStartRestartStopStartKillRm(t *testing.T) {
	runtime := mkRuntime(t)
	defer nuke(runtime)

	srv := &Server{runtime: runtime}

	config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
	if err != nil {
		t.Fatal(err)
	}

	id, err := srv.ContainerCreate(config)
	if err != nil {
		t.Fatal(err)
	}

	if len(runtime.List()) != 1 {
		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
	}

	err = srv.ContainerStart(id, hostConfig)
	if err != nil {
		t.Fatal(err)
	}

	err = srv.ContainerRestart(id, 1)
	if err != nil {
		t.Fatal(err)
	}

	err = srv.ContainerStop(id, 1)
	if err != nil {
		t.Fatal(err)
	}

	err = srv.ContainerStart(id, hostConfig)
	if err != nil {
		t.Fatal(err)
	}

	err = srv.ContainerKill(id)
	if err != nil {
		t.Fatal(err)
	}

	// FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty")
	if err = srv.ContainerDestroy(id, true); err != nil {
		t.Fatal(err)
	}

	if len(runtime.List()) != 0 {
		t.Errorf("Expected 0 container, %v found", len(runtime.List()))
	}

}

func TestRunWithTooLowMemoryLimit(t *testing.T) {
	var err error
	runtime := mkRuntime(t)
	srv := &Server{runtime: runtime}
	defer nuke(runtime)
	// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
	_, err = srv.ContainerCreate(
		&Config{
			Image:     GetTestImage(runtime).ID,
			Memory:    524287,
			CpuShares: 1000,
			Cmd:       []string{"/bin/cat"},
		},
	)
	if err == nil {
		t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
	}

}

func TestContainerTop(t *testing.T) {
	runtime := mkRuntime(t)
	srv := &Server{runtime: runtime}
	defer nuke(runtime)

	c, hostConfig := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t)
	defer runtime.Destroy(c)
	if err := c.Start(hostConfig); err != nil {
		t.Fatal(err)
	}

	// Give some time to the process to start
	c.WaitTimeout(500 * time.Millisecond)

	if !c.State.Running {
		t.Errorf("Container should be running")
	}
	procs, err := srv.ContainerTop(c.ID)
	if err != nil {
		t.Fatal(err)
	}

	if len(procs) != 2 {
		t.Fatalf("Expected 2 processes, found %d.", len(procs))
	}

	if procs[0].Cmd != "sh" && procs[0].Cmd != "busybox" {
		t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[0].Cmd)
	}

	if procs[1].Cmd != "sh" && procs[1].Cmd != "busybox" {
		t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[1].Cmd)
	}
}

func TestPools(t *testing.T) {
	runtime := mkRuntime(t)
	srv := &Server{
		runtime:     runtime,
		pullingPool: make(map[string]struct{}),
		pushingPool: make(map[string]struct{}),
	}
	defer nuke(runtime)

	err := srv.poolAdd("pull", "test1")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolAdd("pull", "test2")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolAdd("push", "test1")
	if err == nil || err.Error() != "pull test1 is already in progress" {
		t.Fatalf("Expected `pull test1 is already in progress`")
	}
	err = srv.poolAdd("pull", "test1")
	if err == nil || err.Error() != "pull test1 is already in progress" {
		t.Fatalf("Expected `pull test1 is already in progress`")
	}
	err = srv.poolAdd("wait", "test3")
	if err == nil || err.Error() != "Unkown pool type" {
		t.Fatalf("Expected `Unkown pool type`")
	}

	err = srv.poolRemove("pull", "test2")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolRemove("pull", "test2")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolRemove("pull", "test1")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolRemove("push", "test1")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolRemove("wait", "test3")
	if err == nil || err.Error() != "Unkown pool type" {
		t.Fatalf("Expected `Unkown pool type`")
	}
}

func TestLogEvent(t *testing.T) {
	runtime := mkRuntime(t)
	srv := &Server{
		runtime:   runtime,
		events:    make([]utils.JSONMessage, 0, 64),
		listeners: make(map[string]chan utils.JSONMessage),
	}

	srv.LogEvent("fakeaction", "fakeid")

	listener := make(chan utils.JSONMessage)
	srv.Lock()
	srv.listeners["test"] = listener
	srv.Unlock()

	srv.LogEvent("fakeaction2", "fakeid")

	if len(srv.events) != 2 {
		t.Fatalf("Expected 2 events, found %d", len(srv.events))
	}
	go func() {
		time.Sleep(200 * time.Millisecond)
		srv.LogEvent("fakeaction3", "fakeid")
		time.Sleep(200 * time.Millisecond)
		srv.LogEvent("fakeaction4", "fakeid")
	}()

	setTimeout(t, "Listening for events timed out", 2*time.Second, func() {
		for i := 2; i < 4; i++ {
			event := <-listener
			if event != srv.events[i] {
				t.Fatalf("Event received it different than expected")
			}
		}
	})
}
<commit_msg>Modify test to accept error from mkContainer<commit_after>package docker

import (
	"github.com/dotcloud/docker/utils"
	"testing"
	"time"
)

func TestContainerTagImageDelete(t *testing.T) {
	runtime := mkRuntime(t)
	defer nuke(runtime)

	srv := &Server{runtime: runtime}

	initialImages, err := srv.Images(false, "")
	if err != nil {
		t.Fatal(err)
	}

	if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil {
		t.Fatal(err)
	}
	if err := srv.runtime.repositories.Set("utest/docker", "tag2", unitTestImageName, false); err != nil {
		t.Fatal(err)
	}

	images, err := srv.Images(false, "")
	if err != nil {
		t.Fatal(err)
	}

	if len(images) != len(initialImages)+2 {
		t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
	}

	if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil {
		t.Fatal(err)
	}

	images, err = srv.Images(false, "")
	if err != nil {
		t.Fatal(err)
	}

	if len(images) != len(initialImages)+1 {
		t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
	}

	if _, err := srv.ImageDelete("utest:tag1", true); err != nil {
		t.Fatal(err)
	}

	images, err = srv.Images(false, "")
	if err != nil {
		t.Fatal(err)
	}

	if len(images) != len(initialImages) {
		t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
	}
}

func TestCreateRm(t *testing.T) {
	runtime := mkRuntime(t)
	defer nuke(runtime)

	srv := &Server{runtime: runtime}

	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
	if err != nil {
		t.Fatal(err)
	}

	id, err := srv.ContainerCreate(config)
	if err != nil {
		t.Fatal(err)
	}

	if len(runtime.List()) != 1 {
		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
	}

	if err = srv.ContainerDestroy(id, true); err != nil {
		t.Fatal(err)
	}

	if len(runtime.List()) != 0 {
		t.Errorf("Expected 0 container, %v found", len(runtime.List()))
	}

}

func TestCreateStartRestartStopStartKillRm(t *testing.T) {
	runtime := mkRuntime(t)
	defer nuke(runtime)

	srv := &Server{runtime: runtime}

	config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
	if err != nil {
		t.Fatal(err)
	}

	id, err := srv.ContainerCreate(config)
	if err != nil {
		t.Fatal(err)
	}

	if len(runtime.List()) != 1 {
		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
	}

	err = srv.ContainerStart(id, hostConfig)
	if err != nil {
		t.Fatal(err)
	}

	err = srv.ContainerRestart(id, 1)
	if err != nil {
		t.Fatal(err)
	}

	err = srv.ContainerStop(id, 1)
	if err != nil {
		t.Fatal(err)
	}

	err = srv.ContainerStart(id, hostConfig)
	if err != nil {
		t.Fatal(err)
	}

	err = srv.ContainerKill(id)
	if err != nil {
		t.Fatal(err)
	}

	// FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty")
	if err = srv.ContainerDestroy(id, true); err != nil {
		t.Fatal(err)
	}

	if len(runtime.List()) != 0 {
		t.Errorf("Expected 0 container, %v found", len(runtime.List()))
	}

}

func TestRunWithTooLowMemoryLimit(t *testing.T) {
	var err error
	runtime := mkRuntime(t)
	srv := &Server{runtime: runtime}
	defer nuke(runtime)
	// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
	_, err = srv.ContainerCreate(
		&Config{
			Image:     GetTestImage(runtime).ID,
			Memory:    524287,
			CpuShares: 1000,
			Cmd:       []string{"/bin/cat"},
		},
	)
	if err == nil {
		t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
	}

}

func TestContainerTop(t *testing.T) {
	runtime := mkRuntime(t)
	srv := &Server{runtime: runtime}
	defer nuke(runtime)

	c, hostConfig, err := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "sleep 2"}, t)
	if err != nil {
		t.Fatal(err)
	}
	defer runtime.Destroy(c)
	if err := c.Start(hostConfig); err != nil {
		t.Fatal(err)
	}

	// Give some time to the process to start
	c.WaitTimeout(500 * time.Millisecond)

	if !c.State.Running {
		t.Errorf("Container should be running")
	}
	procs, err := srv.ContainerTop(c.ID)
	if err != nil {
		t.Fatal(err)
	}

	if len(procs) != 2 {
		t.Fatalf("Expected 2 processes, found %d.", len(procs))
	}

	if procs[0].Cmd != "sh" && procs[0].Cmd != "busybox" {
		t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[0].Cmd)
	}

	if procs[1].Cmd != "sh" && procs[1].Cmd != "busybox" {
		t.Fatalf("Expected `busybox` or `sh`, found %s.", procs[1].Cmd)
	}
}

func TestPools(t *testing.T) {
	runtime := mkRuntime(t)
	srv := &Server{
		runtime:     runtime,
		pullingPool: make(map[string]struct{}),
		pushingPool: make(map[string]struct{}),
	}
	defer nuke(runtime)

	err := srv.poolAdd("pull", "test1")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolAdd("pull", "test2")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolAdd("push", "test1")
	if err == nil || err.Error() != "pull test1 is already in progress" {
		t.Fatalf("Expected `pull test1 is already in progress`")
	}
	err = srv.poolAdd("pull", "test1")
	if err == nil || err.Error() != "pull test1 is already in progress" {
		t.Fatalf("Expected `pull test1 is already in progress`")
	}
	err = srv.poolAdd("wait", "test3")
	if err == nil || err.Error() != "Unkown pool type" {
		t.Fatalf("Expected `Unkown pool type`")
	}

	err = srv.poolRemove("pull", "test2")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolRemove("pull", "test2")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolRemove("pull", "test1")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolRemove("push", "test1")
	if err != nil {
		t.Fatal(err)
	}
	err = srv.poolRemove("wait", "test3")
	if err == nil || err.Error() != "Unkown pool type" {
		t.Fatalf("Expected `Unkown pool type`")
	}
}

func TestLogEvent(t *testing.T) {
	runtime := mkRuntime(t)
	srv := &Server{
		runtime:   runtime,
		events:    make([]utils.JSONMessage, 0, 64),
		listeners: make(map[string]chan utils.JSONMessage),
	}

	srv.LogEvent("fakeaction", "fakeid")

	listener := make(chan utils.JSONMessage)
	srv.Lock()
	srv.listeners["test"] = listener
	srv.Unlock()

	srv.LogEvent("fakeaction2", "fakeid")

	if len(srv.events) != 2 {
		t.Fatalf("Expected 2 events, found %d", len(srv.events))
	}
	go func() {
		time.Sleep(200 * time.Millisecond)
		srv.LogEvent("fakeaction3", "fakeid")
		time.Sleep(200 * time.Millisecond)
		srv.LogEvent("fakeaction4", "fakeid")
	}()

	setTimeout(t, "Listening for events timed out", 2*time.Second, func() {
		for i := 2; i < 4; i++ {
			event := <-listener
			if event != srv.events[i] {
				t.Fatalf("Event received it different than expected")
			}
		}
	})
}
<|endoftext|>


<commit_before>package goinfo

import (
	"errors"
	"os"
	"os/signal"
	"strings"
	"syscall"

	"github.com/hanwen/go-fuse/fuse"
	"github.com/hanwen/go-fuse/fuse/nodefs"
	"github.com/hanwen/go-fuse/fuse/pathfs"

	"github.com/betamike/goinfo/sources"
	"github.com/betamike/goinfo/sources/memstats"
	"github.com/betamike/goinfo/sources/stacktrace"
)

type GoMonitorFs struct {
	pathfs.FileSystem
	Sources map[string]sources.DataSource
}

func NewGoMonitorFs() *GoMonitorFs {
	src := make(map[string]sources.DataSource)
	return &GoMonitorFs{pathfs.NewDefaultFileSystem(), src}
}

func (gfs *GoMonitorFs) AddSource(source sources.DataSource) {
	gfs.Sources[source.Name()] = source
}

func (gfs *GoMonitorFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
	parts := strings.Split(name, string(os.PathSeparator))
	if len(parts) == 1 {
		return &fuse.Attr{
			Mode: fuse.S_IFDIR | 0755,
		}, fuse.OK
	}
	if source, ok := gfs.Sources[parts[0]]; ok {
		size, updated, found := source.Metadata(parts[1])
		if !found {
			return nil, fuse.ENOENT
		}
		return &fuse.Attr{
			Mode:  fuse.S_IFREG | 0644,
			Size:  size,
			Mtime: updated,
		}, fuse.OK
	}
	return nil, fuse.ENOENT
}

func (gfs *GoMonitorFs) OpenDir(name string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
	if name == "" {
		listing := make([]fuse.DirEntry, 0, len(gfs.Sources))
		for name, _ := range gfs.Sources {
			listing = append(listing, fuse.DirEntry{Name: name, Mode: fuse.S_IFDIR})
		}
		return listing, fuse.OK
	}

	if source, ok := gfs.Sources[name]; ok {
		items := source.Listing()
		listing := make([]fuse.DirEntry, len(items))
		for i, item := range items {
			listing[i] = fuse.DirEntry{Name: item, Mode: fuse.S_IFREG}
		}
		return listing, fuse.OK
	}
	return nil, fuse.ENOENT
}

func (gfs *GoMonitorFs) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
	if flags&fuse.O_ANYWRITE != 0 {
		return nil, fuse.EPERM
	}
	parts := strings.Split(name, string(os.PathSeparator))
	if source, ok := gfs.Sources[parts[0]]; ok {
		content, found := source.Contents(parts[1])
		if !found {
			return nil, fuse.EPERM
		}
		return nodefs.NewDataFile(content), fuse.OK
	}
	return nil, fuse.EPERM
}

var server *fuse.Server

func Start(mountpoint string) error {
	//create the file system
	gfs := NewGoMonitorFs()
	gfs.AddSource(stacktrace.NewStacktraceSource())
	gfs.AddSource(memstats.NewMemStatsSource())

	nfs := pathfs.NewPathNodeFs(gfs, nil)

	var err error
	server, _, err = nodefs.MountFileSystem(mountpoint, nfs, nil)
	if err != nil {
		return errors.New("Failed to mount monitoring fs at " + mountpoint + ": " + err.Error())
	}

	//unmount when program exits cleanly or uncleanly
	sig := make(chan os.Signal, 1)
	go func() {
		<-sig
		server.Unmount()
		os.Exit(1)
	}()
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	//start handling the fs calls
	go server.Serve()

	return nil
}
<commit_msg>mount file system with allow_others enabled<commit_after>package goinfo

import (
	"errors"
	"os"
	"os/signal"
	"strings"
	"syscall"

	"github.com/hanwen/go-fuse/fuse"
	"github.com/hanwen/go-fuse/fuse/nodefs"
	"github.com/hanwen/go-fuse/fuse/pathfs"

	"github.com/betamike/goinfo/sources"
	"github.com/betamike/goinfo/sources/memstats"
	"github.com/betamike/goinfo/sources/stacktrace"
)

type GoMonitorFs struct {
	pathfs.FileSystem
	Sources map[string]sources.DataSource
}

func NewGoMonitorFs() *GoMonitorFs {
	src := make(map[string]sources.DataSource)
	return &GoMonitorFs{pathfs.NewDefaultFileSystem(), src}
}

func (gfs *GoMonitorFs) AddSource(source sources.DataSource) {
	gfs.Sources[source.Name()] = source
}

func (gfs *GoMonitorFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
	parts := strings.Split(name, string(os.PathSeparator))
	if len(parts) == 1 {
		return &fuse.Attr{
			Mode: fuse.S_IFDIR | 0755,
		}, fuse.OK
	}
	if source, ok := gfs.Sources[parts[0]]; ok {
		size, updated, found := source.Metadata(parts[1])
		if !found {
			return nil, fuse.ENOENT
		}
		return &fuse.Attr{
			Mode:  fuse.S_IFREG | 0644,
			Size:  size,
			Mtime: updated,
		}, fuse.OK
	}
	return nil, fuse.ENOENT
}

func (gfs *GoMonitorFs) OpenDir(name string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
	if name == "" {
		listing := make([]fuse.DirEntry, 0, len(gfs.Sources))
		for name, _ := range gfs.Sources {
			listing = append(listing, fuse.DirEntry{Name: name, Mode: fuse.S_IFDIR})
		}
		return listing, fuse.OK
	}

	if source, ok := gfs.Sources[name]; ok {
		items := source.Listing()
		listing := make([]fuse.DirEntry, len(items))
		for i, item := range items {
			listing[i] = fuse.DirEntry{Name: item, Mode: fuse.S_IFREG}
		}
		return listing, fuse.OK
	}
	return nil, fuse.ENOENT
}

func (gfs *GoMonitorFs) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
	if flags&fuse.O_ANYWRITE != 0 {
		return nil, fuse.EPERM
	}
	parts := strings.Split(name, string(os.PathSeparator))
	if source, ok := gfs.Sources[parts[0]]; ok {
		content, found := source.Contents(parts[1])
		if !found {
			return nil, fuse.EPERM
		}
		return nodefs.NewDataFile(content), fuse.OK
	}
	return nil, fuse.EPERM
}

var server *fuse.Server

func Start(mountpoint string) error {
	//create the file system
	gfs := NewGoMonitorFs()
	gfs.AddSource(stacktrace.NewStacktraceSource())
	gfs.AddSource(memstats.NewMemStatsSource())

	nfs := pathfs.NewPathNodeFs(gfs, nil)

	var err error
	conn := nodefs.NewFileSystemConnector(nfs, nil)
	server, err = fuse.NewServer(conn.RawFS(), mountpoint, &fuse.MountOptions{AllowOther: true})
	if err != nil {
		return errors.New("Failed to mount monitoring fs at " + mountpoint + ": " + err.Error())
	}

	//unmount when program exits cleanly or uncleanly
	sig := make(chan os.Signal, 1)
	go func() {
		<-sig
		server.Unmount()
		os.Exit(1)
	}()
	signal.Notify(sig, syscall.SIGINT, syscall.SIGKILL)

	//start handling the fs calls
	go server.Serve()

	return nil
}
<|endoftext|>


<commit_before>package service

import (
	"archive/tar"
	"compress/gzip"
	"errors"
	"io"
	"io/ioutil"
	"os"

	"github.com/hashicorp/raft"
	"github.com/icexin/raftkv/config"
	"github.com/icexin/raftkv/proto"
	"github.com/syndtr/goleveldb/leveldb"
)

var (
	errBadMethod = errors.New("bad method")
	errBadAction = errors.New("bad action")
)

type FSM struct {
	cfg *config.DB
	*leveldb.DB
}

func NewFSM(cfg *config.DB) (*FSM, error) {
	db, err := leveldb.OpenFile(cfg.Dir, nil)
	if err != nil {
		return nil, err
	}

	return &FSM{
		cfg: cfg,
		DB:  db,
	}, nil
}

func (f *FSM) Apply(l *raft.Log) interface{} {
	req := new(proto.Request)
	err := proto.Unmarshal(l.Data, req)
	if err != nil {
		return err
	}
	switch req.Action {
	case proto.OpWrite:
		return f.Put(req.Key, req.Data, nil)
	case proto.OpDelete:
		return f.Delete(req.Key, nil)
	default:
		return errBadAction
	}
}

func (f *FSM) Snapshot() (raft.FSMSnapshot, error) {
	snapshot, err := f.GetSnapshot()
	if err != nil {
		return nil, err
	}
	return &fsmSnapshot{snapshot}, nil
}

func (f *FSM) Restore(r io.ReadCloser) error {
	defer r.Close()

	zr, err := gzip.NewReader(r)
	if err != nil {
		return err
	}

	oldname := f.cfg.Dir + ".old"
	err = os.Rename(f.cfg.Dir, oldname)
	if err != nil {
		return err
	}
	defer os.RemoveAll(oldname)

	tr := tar.NewReader(zr)
	err = Untar(f.cfg.Dir, tr)
	if err != nil {
		return err
	}

	db, err := leveldb.OpenFile(f.cfg.Dir, nil)
	if err != nil {
		return err
	}

	f.DB = db
	return nil
}

// fsmSnapshot implement FSMSnapshot interface
type fsmSnapshot struct {
	snapshot *leveldb.Snapshot
}

// At first, walk all kvs, write temp leveldb.
// Second, make tar.gz for temp leveldb dir
func (f *fsmSnapshot) Persist(sink raft.SnapshotSink) error {
	// Create a temporary path for the state store
	tmpPath, err := ioutil.TempDir(os.TempDir(), "state")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpPath)

	db, err := leveldb.OpenFile(tmpPath, nil)
	if err != nil {
		return err
	}
	iter := f.snapshot.NewIterator(nil, nil)
	for iter.Next() {
		err = db.Put(iter.Key(), iter.Value(), nil)
		if err != nil {
			db.Close()
			sink.Cancel()
			return err
		}
	}
	iter.Release()
	db.Close()

	// make tar.gz
	w := gzip.NewWriter(sink)
	err = Tar(tmpPath, w)
	if err != nil {
		sink.Cancel()
		return err
	}

	err = w.Close()
	if err != nil {
		sink.Cancel()
		return err
	}

	sink.Close()
	return nil
}

func (f *fsmSnapshot) Release() {
	f.snapshot.Release()
}
<commit_msg>add close to old db<commit_after>package service

import (
	"archive/tar"
	"compress/gzip"
	"errors"
	"io"
	"io/ioutil"
	"os"

	"github.com/hashicorp/raft"
	"github.com/icexin/raftkv/config"
	"github.com/icexin/raftkv/proto"
	"github.com/syndtr/goleveldb/leveldb"
)

var (
	errBadMethod = errors.New("bad method")
	errBadAction = errors.New("bad action")
)

type FSM struct {
	cfg *config.DB
	*leveldb.DB
}

func NewFSM(cfg *config.DB) (*FSM, error) {
	db, err := leveldb.OpenFile(cfg.Dir, nil)
	if err != nil {
		return nil, err
	}

	return &FSM{
		cfg: cfg,
		DB:  db,
	}, nil
}

func (f *FSM) Apply(l *raft.Log) interface{} {
	req := new(proto.Request)
	err := proto.Unmarshal(l.Data, req)
	if err != nil {
		return err
	}
	switch req.Action {
	case proto.OpWrite:
		return f.Put(req.Key, req.Data, nil)
	case proto.OpDelete:
		return f.Delete(req.Key, nil)
	default:
		return errBadAction
	}
}

func (f *FSM) Snapshot() (raft.FSMSnapshot, error) {
	snapshot, err := f.GetSnapshot()
	if err != nil {
		return nil, err
	}
	return &fsmSnapshot{snapshot}, nil
}

func (f *FSM) Restore(r io.ReadCloser) error {
	defer r.Close()

	zr, err := gzip.NewReader(r)
	if err != nil {
		return err
	}

	err = f.DB.Close()
	if err != nil {
		return err
	}

	oldname := f.cfg.Dir + ".old"
	err = os.Rename(f.cfg.Dir, oldname)
	if err != nil {
		return err
	}
	defer os.RemoveAll(oldname)

	tr := tar.NewReader(zr)
	err = Untar(f.cfg.Dir, tr)
	if err != nil {
		return err
	}

	db, err := leveldb.OpenFile(f.cfg.Dir, nil)
	if err != nil {
		return err
	}

	f.DB = db
	return nil
}

// fsmSnapshot implement FSMSnapshot interface
type fsmSnapshot struct {
	snapshot *leveldb.Snapshot
}

// First, walk all kvs, write temp leveldb.
// Second, make tar.gz for temp leveldb dir
func (f *fsmSnapshot) Persist(sink raft.SnapshotSink) error {
	// Create a temporary path for the state store
	tmpPath, err := ioutil.TempDir(os.TempDir(), "state")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpPath)

	db, err := leveldb.OpenFile(tmpPath, nil)
	if err != nil {
		return err
	}
	iter := f.snapshot.NewIterator(nil, nil)
	for iter.Next() {
		err = db.Put(iter.Key(), iter.Value(), nil)
		if err != nil {
			db.Close()
			sink.Cancel()
			return err
		}
	}
	iter.Release()
	db.Close()

	// make tar.gz
	w := gzip.NewWriter(sink)
	err = Tar(tmpPath, w)
	if err != nil {
		sink.Cancel()
		return err
	}

	err = w.Close()
	if err != nil {
		sink.Cancel()
		return err
	}

	sink.Close()
	return nil
}

func (f *fsmSnapshot) Release() {
	f.snapshot.Release()
}
<|endoftext|>


<commit_before>package main

import (
    "flag"
    "strconv"
    "fmt"
    "github.com/prataprc/collatejson"
)

var options struct {
    floatText string
}

func argParse() {
    //flag.BoolVar(&options.ast, "ast", false, "Show the ast of production")
    //flag.IntVar(&options.seed, "s", seed, "Seed value")
    //flag.IntVar(&options.count, "n", 1, "Generate n combinations")
    //flag.StringVar(&options.outfile, "o", "-", "Specify an output file")
    flag.StringVar(&options.floatText, "f", "", "encode floating point number")
    flag.Parse()
}

func main() {
    argParse()
    if options.floatText != "" {
        encodeFloat(options.floatText)
    }
}

func encodeFloat(text string) {
    if f, err := strconv.ParseFloat(text, 64); err != nil {
        panic(err)
    } else {
        ftext := []byte(strconv.FormatFloat(f, 'e', -1, 64))
        fmt.Printf("Encoding %v: %v\n", f, string(collatejson.EncodeFloat(ftext)))
    }
}
<commit_msg>Added integer encoding switch.<commit_after>package main

import (
    "flag"
    "strconv"
    "fmt"
    "github.com/prataprc/collatejson"
)

var options struct {
    floatText string
    intText string
}

func argParse() {
    //flag.BoolVar(&options.ast, "ast", false, "Show the ast of production")
    //flag.IntVar(&options.seed, "s", seed, "Seed value")
    //flag.IntVar(&options.count, "n", 1, "Generate n combinations")
    //flag.StringVar(&options.outfile, "o", "-", "Specify an output file")
    flag.StringVar(&options.floatText, "f", "", "encode floating point number")
    flag.StringVar(&options.intText, "i", "", "encode integer number")
    flag.Parse()
}

func main() {
    argParse()
    if options.floatText != "" {
        encodeFloat(options.floatText)
    } else if options.intText != "" {
        encodeInt(options.intText)
    }
}

func encodeFloat(text string) {
    if f, err := strconv.ParseFloat(text, 64); err != nil {
        panic(err)
    } else {
        ftext := []byte(strconv.FormatFloat(f, 'e', -1, 64))
        fmt.Printf("Encoding %v: %v\n", f, string(collatejson.EncodeFloat(ftext)))
    }
}

func encodeInt(text string) {
    fmt.Println(string(collatejson.EncodeInt([]byte(text))))
}
<|endoftext|>