<commit_before>package api

import (
	"log"
	"net/http"

	"github.com/labstack/echo"
)

type Application struct {
	ID int `json:"id"`
	Name string `json:"name"`
	AuthorName string `json:"authorName"`
	CallbackURL string `json:"callbackUrl"`
}
type ApplicationAuthorization struct {
	ID int `json:"id"`
	ApplicationID int `json:"applicationId"`
	Name string `json:"name"`
	AuthorName string `json:"authorName"`
}

type ApplicationTokenResponse struct {
	Status string `json:"status"`
	Token string `json:"token"`
}
type ApplicationAuthorizationsResponse struct {
	Status string `json:"status"`
	Authorizations []ApplicationAuthorization `json:"authorizations"`
}
type SingleApplicationResponse struct {
	Status string `json:"status"`
	Application Application `json:"application"`
}

func InitApplicationAPI(e *echo.Echo) {
	e.POST("/application/completeAuth", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}

		// get the application
		applicationRows, err := DB.Query("SELECT id, name, authorName, callbackUrl FROM applications WHERE clientId = ?", c.FormValue("clientId"))
		if err != nil {
			log.Println("Error while completing application auth: ")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		defer applicationRows.Close()

		if !applicationRows.Next() {
			return c.JSON(http.StatusNotFound, ErrorResponse{"error", "not_found"})
		}

		application := Application{-1, "", "", ""}
		applicationRows.Scan(&application.ID, &application.Name, &application.AuthorName, &application.CallbackURL)

		// check if we've already authorized this application
		tokenCheckRows, err := DB.Query("SELECT token FROM application_authorizations WHERE applicationId = ? AND userId = ?", application.ID, GetSessionUserID(&c))
		if err != nil {
			log.Println("Error while completing application auth: ")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		defer tokenCheckRows.Close()

		if tokenCheckRows.Next() {
			// if we have, just return that token
			token := ""
			tokenCheckRows.Scan(&token)
			return c.JSON(http.StatusOK, ApplicationTokenResponse{"ok", token})
		}

		// add the new authorization
		token, err := Util_GenerateRandomString(56)
		if err != nil {
			log.Println("Error while generating application token:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}

		stmt, err := DB.Prepare("INSERT INTO application_authorizations(applicationId, userId, token) VALUES(?, ?, ?)")
		if err != nil {
			log.Println("Error while authorizing application:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		_, err = stmt.Exec(application.ID, GetSessionUserID(&c), token)
		if err != nil {
			log.Println("Error while authorizing application:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}

		return c.JSON(http.StatusOK, ApplicationTokenResponse{"ok", token})
	})
	e.GET("/application/get/:id", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			jsonResp := ErrorResponse{"error", "logged_out"}
			return c.JSON(http.StatusUnauthorized, jsonResp)
		}

		rows, err := DB.Query("SELECT id, name, authorName, callbackUrl FROM applications WHERE clientId = ?", c.Param("id"))
		if err != nil {
			log.Println("Error while getting application information: ")
			log.Println(err)
			jsonResp := StatusResponse{"error"}
			return c.JSON(http.StatusInternalServerError, jsonResp)
		}
		defer rows.Close()

		if !rows.Next() {
			jsonResp := ErrorResponse{"error", "not_found"}
			return c.JSON(http.StatusNotFound, jsonResp)
		}

		resp := Application{-1, "", "", ""}
		rows.Scan(&resp.ID, &resp.Name, &resp.AuthorName, &resp.CallbackURL)

		jsonResp := SingleApplicationResponse{"ok", resp}
		return c.JSON(http.StatusOK, jsonResp)
	})
	e.GET("/application/getAuthorizations", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}
		rows, err := DB.Query("SELECT application_authorizations.id, applications.id, applications.name, applications.authorName FROM application_authorizations INNER JOIN applications ON application_authorizations.applicationId = applications.id WHERE application_authorizations.userId = ?", GetSessionUserID(&c))
		if err != nil {
			log.Println("Error while getting authorizations: ")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		defer rows.Close()

		authorizations := []ApplicationAuthorization{}
		for rows.Next() {
			resp := ApplicationAuthorization{-1, -1, "", ""}
			rows.Scan(&resp.ID, &resp.ApplicationID, &resp.Name, &resp.AuthorName)
			authorizations = append(authorizations, resp)
		}
		return c.JSON(http.StatusOK, ApplicationAuthorizationsResponse{"ok", authorizations})
	})
	e.GET("/application/requestAuth/:id", func(c echo.Context) error {
		return c.Redirect(http.StatusFound, AuthURLBase + "?id=" + c.Param("id"))
	})
	e.POST("/application/revokeAuth", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}

		// find the authorization
		rows, err := DB.Query("SELECT userId FROM application_authorizations WHERE id = ?", c.FormValue("id"))
		if err != nil {
			log.Println("Error while revoking authorization:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		defer rows.Close()

		if !rows.Next() {
			return c.JSON(http.StatusForbidden, ErrorResponse{"error", "forbidden"})
		}

		userId := -1
		rows.Scan(&userId)

		if GetSessionUserID(&c) != userId {
			return c.JSON(http.StatusForbidden, ErrorResponse{"error", "forbidden"})
		}

		// delete the authorization
		deleteStmt, err := DB.Prepare("DELETE FROM application_authorizations WHERE id = ?")
		if err != nil {
			log.Println("Error while revoking authorization:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		_, err = deleteStmt.Exec(c.FormValue("id"))
		if err != nil {
			log.Println("Error while revoking authorization:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}

		return c.JSON(http.StatusOK, StatusResponse{"ok"})
	})
}<commit_msg>add endpoints to create/update/delete applications<commit_after>package api

import (
	"log"
	"net/http"

	"github.com/labstack/echo"
)

type Application struct {
	ID int `json:"id"`
	Name string `json:"name"`
	AuthorName string `json:"authorName"`
	ClientID string `json:"clientId"`
	CallbackURL string `json:"callbackUrl"`
}
type ApplicationAuthorization struct {
	ID int `json:"id"`
	ApplicationID int `json:"applicationId"`
	Name string `json:"name"`
	AuthorName string `json:"authorName"`
}

type ApplicationTokenResponse struct {
	Status string `json:"status"`
	Token string `json:"token"`
}
type ApplicationAuthorizationsResponse struct {
	Status string `json:"status"`
	Authorizations []ApplicationAuthorization `json:"authorizations"`
}
type SingleApplicationResponse struct {
	Status string `json:"status"`
	Application Application `json:"application"`
}
type MultipleApplicationsResponse struct {
	Status string `json:"status"`
	Applications []Application `json:"applications"`
}

func InitApplicationAPI(e *echo.Echo) {
	e.POST("/application/completeAuth", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}

		// get the application
		applicationRows, err := DB.Query("SELECT id, name, authorName, callbackUrl FROM applications WHERE clientId = ?", c.FormValue("clientId"))
		if err != nil {
			log.Println("Error while completing application auth: ")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		defer applicationRows.Close()

		if !applicationRows.Next() {
			return c.JSON(http.StatusNotFound, ErrorResponse{"error", "not_found"})
		}

		application := Application{-1, "", "", "", ""}
		applicationRows.Scan(&application.ID, &application.Name, &application.AuthorName, &application.CallbackURL)

		// check if we've already authorized this application
		tokenCheckRows, err := DB.Query("SELECT token FROM application_authorizations WHERE applicationId = ? AND userId = ?", application.ID, GetSessionUserID(&c))
		if err != nil {
			log.Println("Error while completing application auth: ")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		defer tokenCheckRows.Close()

		if tokenCheckRows.Next() {
			// if we have, just return that token
			token := ""
			tokenCheckRows.Scan(&token)
			return c.JSON(http.StatusOK, ApplicationTokenResponse{"ok", token})
		}

		// add the new authorization
		token, err := Util_GenerateRandomString(56)
		if err != nil {
			log.Println("Error while generating application token:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}

		stmt, err := DB.Prepare("INSERT INTO application_authorizations(applicationId, userId, token) VALUES(?, ?, ?)")
		if err != nil {
			log.Println("Error while authorizing application:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		_, err = stmt.Exec(application.ID, GetSessionUserID(&c), token)
		if err != nil {
			log.Println("Error while authorizing application:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}

		return c.JSON(http.StatusOK, ApplicationTokenResponse{"ok", token})
	})
	e.GET("/application/get/:id", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			jsonResp := ErrorResponse{"error", "logged_out"}
			return c.JSON(http.StatusUnauthorized, jsonResp)
		}

		rows, err := DB.Query("SELECT id, name, authorName, callbackUrl FROM applications WHERE clientId = ?", c.Param("id"))
		if err != nil {
			log.Println("Error while getting application information: ")
			log.Println(err)
			jsonResp := StatusResponse{"error"}
			return c.JSON(http.StatusInternalServerError, jsonResp)
		}
		defer rows.Close()

		if !rows.Next() {
			jsonResp := ErrorResponse{"error", "not_found"}
			return c.JSON(http.StatusNotFound, jsonResp)
		}

		resp := Application{-1, "", "", "", ""}
		rows.Scan(&resp.ID, &resp.Name, &resp.AuthorName, &resp.CallbackURL)

		jsonResp := SingleApplicationResponse{"ok", resp}
		return c.JSON(http.StatusOK, jsonResp)
	})
	e.GET("/application/getAuthorizations", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}
		rows, err := DB.Query("SELECT application_authorizations.id, applications.id, applications.name, applications.authorName FROM application_authorizations INNER JOIN applications ON application_authorizations.applicationId = applications.id WHERE application_authorizations.userId = ?", GetSessionUserID(&c))
		if err != nil {
			log.Println("Error while getting authorizations: ")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		defer rows.Close()

		authorizations := []ApplicationAuthorization{}
		for rows.Next() {
			resp := ApplicationAuthorization{-1, -1, "", ""}
			rows.Scan(&resp.ID, &resp.ApplicationID, &resp.Name, &resp.AuthorName)
			authorizations = append(authorizations, resp)
		}
		return c.JSON(http.StatusOK, ApplicationAuthorizationsResponse{"ok", authorizations})
	})
	e.GET("/application/requestAuth/:id", func(c echo.Context) error {
		return c.Redirect(http.StatusFound, AuthURLBase + "?id=" + c.Param("id"))
	})
	e.POST("/application/revokeAuth", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}

		// find the authorization
		rows, err := DB.Query("SELECT userId FROM application_authorizations WHERE id = ?", c.FormValue("id"))
		if err != nil {
			log.Println("Error while revoking authorization:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		defer rows.Close()

		if !rows.Next() {
			return c.JSON(http.StatusForbidden, ErrorResponse{"error", "forbidden"})
		}

		userId := -1
		rows.Scan(&userId)

		if GetSessionUserID(&c) != userId {
			return c.JSON(http.StatusForbidden, ErrorResponse{"error", "forbidden"})
		}

		// delete the authorization
		deleteStmt, err := DB.Prepare("DELETE FROM application_authorizations WHERE id = ?")
		if err != nil {
			log.Println("Error while revoking authorization:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		_, err = deleteStmt.Exec(c.FormValue("id"))
		if err != nil {
			log.Println("Error while revoking authorization:")
			log.Println(err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}

		return c.JSON(http.StatusOK, StatusResponse{"ok"})
	})

	e.POST("/application/manage/create", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}

		// generate client id
		clientId, err := Util_GenerateRandomString(42)
		if err != nil {
			log.Println("Error while creating application: ", err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}

		// get author name
		rows, err := DB.Query("SELECT name FROM users WHERE id = ?", GetSessionUserID(&c))
		if err != nil {
			log.Println("Error while creating application: ", err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		defer rows.Close()
		if !rows.Next() {
			log.Println("Error while creating application: ", err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		authorName := ""
		rows.Scan(&authorName)

		// actually create the application
		stmt, err := DB.Prepare("INSERT INTO applications(name, userId, authorName, clientId, callbackUrl) VALUES('New application', ?, ?, ?, '')")
		if err != nil {
			log.Println("Error while creating application: ", err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		_, err = stmt.Exec(GetSessionUserID(&c), authorName, clientId)
		if err != nil {
			log.Println("Error while creating application: ", err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}

		return c.JSON(http.StatusOK, StatusResponse{"ok"})
	})
	e.GET("/application/manage/getAll", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}

		rows, err := DB.Query("SELECT id, name, authorName, clientId, callbackUrl FROM applications WHERE userId = ?", GetSessionUserID(&c))
		if err != nil {
			log.Println("Error while getting user applications: ", err)
			return c.JSON(http.StatusInternalServerError, StatusResponse{"error"})
		}
		defer rows.Close()

		apps := []Application{}
		for rows.Next() {
			resp := Application{-1, "", "", "", ""}
			rows.Scan(&resp.ID, &resp.Name, &resp.AuthorName, &resp.ClientID, &resp.CallbackURL)
			apps = append(apps, resp)
		}

		return c.JSON(http.StatusOK, MultipleApplicationsResponse{"ok", apps})
	})
	e.POST("/application/manage/update", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}

		if c.FormValue("id") == "" || c.FormValue("name") == "" || c.FormValue("callbackUrl") == "" {
			return c.JSON(http.StatusBadRequest, ErrorResponse{"error", "missing_params"})
		}

		// check that you can actually edit the application
		rows, err := DB.Query("SELECT id FROM applications WHERE userId = ? AND id = ?", GetSessionUserID(&c), c.FormValue("id"))
		if err != nil {
			log.Println("Error while updating application: ", err)
			return c.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		}
		defer rows.Close()
		if !rows.Next() {
			return c.JSON(http.StatusForbidden, ErrorResponse{"error", "forbidden"})
		}

		// update the application
		stmt, err := DB.Prepare("UPDATE applications SET name = ?, callbackUrl = ? WHERE id = ?")
		if err != nil {
			log.Println("Error while updating application: ", err)
			return c.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		}
		_, err = stmt.Exec(c.FormValue("name"), c.FormValue("callbackUrl"), c.FormValue("id"))
		if err != nil {
			log.Println("Error while updating application: ", err)
			return c.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		}

		return c.JSON(http.StatusOK, StatusResponse{"ok"})
	})
	e.POST("/application/manage/delete", func(c echo.Context) error {
		if GetSessionUserID(&c) == -1 {
			return c.JSON(http.StatusUnauthorized, ErrorResponse{"error", "logged_out"})
		}

		if c.FormValue("id") == "" {
			return c.JSON(http.StatusBadRequest, ErrorResponse{"error", "missing_params"})
		}

		// check that you can actually edit the application
		rows, err := DB.Query("SELECT id FROM applications WHERE userId = ? AND id = ?", GetSessionUserID(&c), c.FormValue("id"))
		if err != nil {
			log.Println("Error while deleting application: ", err)
			return c.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		}
		defer rows.Close()
		if !rows.Next() {
			return c.JSON(http.StatusForbidden, ErrorResponse{"error", "forbidden"})
		}

		tx, err := DB.Begin()

		// delete authorizations
		authStmt, err := tx.Prepare("DELETE FROM application_authorizations WHERE applicationId = ?")
		if err != nil {
			log.Println("Error while deleting application: ", err)
			return c.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		}
		defer authStmt.Close()
		_, err = authStmt.Exec(c.FormValue("id"))
		if err != nil {
			log.Println("Error while deleting application: ", err)
			return c.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		}

		// delete applications
		appStmt, err := tx.Prepare("DELETE FROM applications WHERE id = ?")
		if err != nil {
			log.Println("Error while deleting application: ", err)
			return c.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		}
		defer appStmt.Close()
		_, err = appStmt.Exec(c.FormValue("id"))
		if err != nil {
			log.Println("Error while deleting application: ", err)
			return c.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		}

		// go!
		err = tx.Commit()
		if err != nil {
			log.Println("Error while deleting application: ", err)
			return c.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		}

		return c.JSON(http.StatusOK, StatusResponse{"ok"})
	})
}<|endoftext|>


<commit_before>package api

import (
	"errors"
	"time"

	"bitbucket.org/mundipagg/boletoapi/config"
	"bitbucket.org/mundipagg/boletoapi/log"
	"bitbucket.org/mundipagg/boletoapi/models"
	"bitbucket.org/mundipagg/boletoapi/util"
	gin "gopkg.in/gin-gonic/gin.v1"
)

// ReturnHeaders 'seta' os headers padrões de resposta
func ReturnHeaders() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Header("Content-Type", "application/json")
		c.Next()
	}
}

func executionController() gin.HandlerFunc {
	return func(c *gin.Context) {
		if config.IsRunning() {
			c.AbortWithError(500, errors.New("A aplicação está sendo finalizada"))
			return
		}
	}
}

//ParseBoleto trata a entrada de boleto em todos os requests
func ParseBoleto() gin.HandlerFunc {
	return func(c *gin.Context) {
		boleto := models.BoletoRequest{}
		errBind := c.BindJSON(&boleto)
		if errBind != nil {
			e := models.NewFormatError(errBind.Error())
			checkError(c, e, log.CreateLog())
			return
		}
		d, errFmt := time.Parse("2006-01-02", boleto.Title.ExpireDate)
		boleto.Title.ExpireDateTime = d
		if errFmt != nil {
			e := models.NewFormatError(errFmt.Error())
			checkError(c, e, log.CreateLog())
			return
		}
		l := log.CreateLog()
		l.NossoNumero = boleto.Title.OurNumber
		l.Operation = "RegisterBoleto"
		l.Recipient = "BoletoApi"
		l.Request(boleto, c.Request.URL.RequestURI(), util.HeaderToMap(c.Request.Header))
		c.Set("boleto", boleto)
		c.Next()
		_boletoResponse, _ := c.Get("boletoResponse")
		l.Response(_boletoResponse, c.Request.URL.RequestURI())
	}
}
<commit_msg>:facepunch: faz o merge com a dev<commit_after>package api

import (
	"errors"
	"time"

	"bitbucket.org/mundipagg/boletoapi/config"
	"bitbucket.org/mundipagg/boletoapi/log"
	"bitbucket.org/mundipagg/boletoapi/models"
	"bitbucket.org/mundipagg/boletoapi/util"
	gin "gopkg.in/gin-gonic/gin.v1"
)

// ReturnHeaders 'seta' os headers padrões de resposta
func ReturnHeaders() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Header("Content-Type", "application/json")
		c.Next()
	}
}

func executionController() gin.HandlerFunc {
	return func(c *gin.Context) {
		if config.IsRunning() {
			c.AbortWithError(500, errors.New("A aplicação está sendo finalizada"))
			return
		}
	}
}

//ParseBoleto trata a entrada de boleto em todos os requests
func ParseBoleto() gin.HandlerFunc {
	return func(c *gin.Context) {
		boleto := models.BoletoRequest{}
		errBind := c.BindJSON(&boleto)
		if errBind != nil {
			e := models.NewFormatError(errBind.Error())
			checkError(c, e, log.CreateLog())
			return
		}
		d, errFmt := time.Parse("2006-01-02", boleto.Title.ExpireDate)
		boleto.Title.ExpireDateTime = d
		if errFmt != nil {
			e := models.NewFormatError(errFmt.Error())
			checkError(c, e, log.CreateLog())
			return
		}
		l := log.CreateLog()
		l.NossoNumero = boleto.Title.OurNumber
		l.Operation = "RegisterBoleto"
		l.Recipient = "BoletoApi"
		l.Request(boleto, c.Request.URL.RequestURI(), util.HeaderToMap(c.Request.Header))
		c.Set("boleto", boleto)
		c.Next()
		resp, _ := c.Get("boletoResponse")
		l.Response(resp, c.Request.URL.RequestURI())
	}
}
<|endoftext|>


<commit_before>package sshd

import (
	"errors"
	"fmt"
	"net"
	"sync"
	"time"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

var keepaliveInterval = time.Second * 30
var keepaliveRequest = "keepalive@ssh-chat"

var ErrNoSessionChannel = errors.New("no session channel")
var ErrNotSessionChannel = errors.New("terminal requires session channel")

// Connection is an interface with fields necessary to operate an sshd host.
type Connection interface {
	PublicKey() ssh.PublicKey
	RemoteAddr() net.Addr
	Name() string
	ClientVersion() []byte
	Close() error
}

type sshConn struct {
	*ssh.ServerConn
}

func (c sshConn) PublicKey() ssh.PublicKey {
	if c.Permissions == nil {
		return nil
	}

	s, ok := c.Permissions.Extensions["pubkey"]
	if !ok {
		return nil
	}

	key, err := ssh.ParsePublicKey([]byte(s))
	if err != nil {
		return nil
	}

	return key
}

func (c sshConn) Name() string {
	return c.User()
}

// Extending ssh/terminal to include a closer interface
type Terminal struct {
	terminal.Terminal
	Conn    Connection
	Channel ssh.Channel

	done      chan struct{}
	closeOnce sync.Once
}

// Make new terminal from a session channel
func NewTerminal(conn *ssh.ServerConn, ch ssh.NewChannel) (*Terminal, error) {
	if ch.ChannelType() != "session" {
		return nil, ErrNotSessionChannel
	}
	channel, requests, err := ch.Accept()
	if err != nil {
		return nil, err
	}
	term := Terminal{
		Terminal: *terminal.NewTerminal(channel, "Connecting..."),
		Conn:     sshConn{conn},
		Channel:  channel,

		done: make(chan struct{}),
	}

	go term.listen(requests)

	go func() {
		// Keep-Alive Ticker
		ticker := time.Tick(keepaliveInterval)
		for {
			select {
			case <-ticker:
				_, err := channel.SendRequest(keepaliveRequest, true, nil)
				if err != nil {
					// Connection is gone
					term.Close()
					return
				}
			case <-term.done:
				return
			}
		}
	}()

	return &term, nil
}

// Find session channel and make a Terminal from it
func NewSession(conn *ssh.ServerConn, channels <-chan ssh.NewChannel) (*Terminal, error) {
	// Make a terminal from the first session found
	for ch := range channels {
		if t := ch.ChannelType(); t != "session" {
			ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
			continue
		}

		return NewTerminal(conn, ch)
	}

	return nil, ErrNoSessionChannel
}

// Close terminal and ssh connection
func (t *Terminal) Close() error {
	var err error
	t.closeOnce.Do(func() {
		close(t.done)
		t.Channel.Close()
		err = t.Conn.Close()
	})
	return err
}

// Negotiate terminal type and settings
func (t *Terminal) listen(requests <-chan *ssh.Request) {
	hasShell := false

	for req := range requests {
		var width, height int
		var ok bool

		switch req.Type {
		case "shell":
			if !hasShell {
				ok = true
				hasShell = true
			}
		case "pty-req":
			width, height, ok = parsePtyRequest(req.Payload)
			if ok {
				// TODO: Hardcode width to 100000?
				err := t.SetSize(width, height)
				ok = err == nil
			}
		case "window-change":
			width, height, ok = parseWinchRequest(req.Payload)
			if ok {
				// TODO: Hardcode width to 100000?
				err := t.SetSize(width, height)
				ok = err == nil
			}
		}

		if req.WantReply {
			req.Reply(ok, nil)
		}
	}
}
<commit_msg>logging: More information about why connections get dropped<commit_after>package sshd

import (
	"errors"
	"fmt"
	"net"
	"sync"
	"time"

	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/terminal"
)

var keepaliveInterval = time.Second * 30
var keepaliveRequest = "keepalive@ssh-chat"

var ErrNoSessionChannel = errors.New("no session channel")
var ErrNotSessionChannel = errors.New("terminal requires session channel")

// Connection is an interface with fields necessary to operate an sshd host.
type Connection interface {
	PublicKey() ssh.PublicKey
	RemoteAddr() net.Addr
	Name() string
	ClientVersion() []byte
	Close() error
}

type sshConn struct {
	*ssh.ServerConn
}

func (c sshConn) PublicKey() ssh.PublicKey {
	if c.Permissions == nil {
		return nil
	}

	s, ok := c.Permissions.Extensions["pubkey"]
	if !ok {
		return nil
	}

	key, err := ssh.ParsePublicKey([]byte(s))
	if err != nil {
		return nil
	}

	return key
}

func (c sshConn) Name() string {
	return c.User()
}

// Extending ssh/terminal to include a closer interface
type Terminal struct {
	terminal.Terminal
	Conn    Connection
	Channel ssh.Channel

	done      chan struct{}
	closeOnce sync.Once
}

// Make new terminal from a session channel
func NewTerminal(conn *ssh.ServerConn, ch ssh.NewChannel) (*Terminal, error) {
	if ch.ChannelType() != "session" {
		return nil, ErrNotSessionChannel
	}
	channel, requests, err := ch.Accept()
	if err != nil {
		return nil, err
	}
	term := Terminal{
		Terminal: *terminal.NewTerminal(channel, "Connecting..."),
		Conn:     sshConn{conn},
		Channel:  channel,

		done: make(chan struct{}),
	}

	go term.listen(requests)

	go func() {
		// Keep-Alive Ticker
		ticker := time.Tick(keepaliveInterval)
		for {
			select {
			case <-ticker:
				_, err := channel.SendRequest(keepaliveRequest, true, nil)
				if err != nil {
					// Connection is gone
					logger.Printf("[%s] Keepalive failed, closing terminal: %s", term.Conn.RemoteAddr(), err)
					term.Close()
					return
				}
			case <-term.done:
				return
			}
		}
	}()

	return &term, nil
}

// Find session channel and make a Terminal from it
func NewSession(conn *ssh.ServerConn, channels <-chan ssh.NewChannel) (*Terminal, error) {
	// Make a terminal from the first session found
	for ch := range channels {
		if t := ch.ChannelType(); t != "session" {
			logger.Printf("[%s] Ignored channel type: %s", conn.RemoteAddr(), t)
			ch.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
			continue
		}

		return NewTerminal(conn, ch)
	}

	return nil, ErrNoSessionChannel
}

// Close terminal and ssh connection
func (t *Terminal) Close() error {
	var err error
	t.closeOnce.Do(func() {
		close(t.done)
		t.Channel.Close()
		err = t.Conn.Close()
	})
	return err
}

// Negotiate terminal type and settings
func (t *Terminal) listen(requests <-chan *ssh.Request) {
	hasShell := false

	for req := range requests {
		var width, height int
		var ok bool

		switch req.Type {
		case "shell":
			if !hasShell {
				ok = true
				hasShell = true
			}
		case "pty-req":
			width, height, ok = parsePtyRequest(req.Payload)
			if ok {
				// TODO: Hardcode width to 100000?
				err := t.SetSize(width, height)
				ok = err == nil
			}
		case "window-change":
			width, height, ok = parseWinchRequest(req.Payload)
			if ok {
				// TODO: Hardcode width to 100000?
				err := t.SetSize(width, height)
				ok = err == nil
			}
		}

		if req.WantReply {
			req.Reply(ok, nil)
		}
	}
}
<|endoftext|>


<commit_before>/*
   Copyright The containerd Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package nvidia

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"

	"github.com/containerd/containerd/containers"
	"github.com/containerd/containerd/oci"
	specs "github.com/opencontainers/runtime-spec/specs-go"
)

const nvidiaCLI = "nvidia-container-cli"

// Capability specifies capabilities for the gpu inside the container
// Detailed explaination of options can be found:
// https://github.com/nvidia/nvidia-container-runtime#supported-driver-capabilities
type Capability int

const (
	// Compute capability
	Compute Capability = iota + 1
	// Compat32 capability
	Compat32
	// Graphics capability
	Graphics
	// Utility capability
	Utility
	// Video capability
	Video
	// Display capability
	Display
)

// WithGPUs adds NVIDIA gpu support to a container
func WithGPUs(opts ...Opts) oci.SpecOpts {
	return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
		c := &config{}
		for _, o := range opts {
			if err := o(c); err != nil {
				return err
			}
		}
		path, err := exec.LookPath("containerd")
		if err != nil {
			return err
		}
		nvidiaPath, err := exec.LookPath(nvidiaCLI)
		if err != nil {
			return err
		}
		if s.Hooks == nil {
			s.Hooks = &specs.Hooks{}
		}
		s.Hooks.Prestart = append(s.Hooks.Prestart, specs.Hook{
			Path: path,
			Args: append([]string{
				"containerd",
				"oci-hook",
				"--",
				nvidiaPath,
			}, c.args()...),
			Env: os.Environ(),
		})
		return nil
	}
}

type config struct {
	Devices      []string
	Capabilities []Capability
	LoadKmods    bool
	LDCache      string
	LDConfig     string
	Requirements []string
}

func (c *config) args() []string {
	var args []string

	if c.LoadKmods {
		args = append(args, "--load-kmods")
	}
	if c.LDCache != "" {
		args = append(args, fmt.Sprintf("--ldcache=%s", c.LDCache))
	}
	args = append(args,
		"configure",
	)
	if len(c.Devices) > 0 {
		args = append(args, fmt.Sprintf("--device=%s", strings.Join(c.Devices, ",")))
	}
	for _, c := range c.Capabilities {
		args = append(args, fmt.Sprintf("--%s", capFlags[c]))
	}
	if c.LDConfig != "" {
		args = append(args, fmt.Sprintf("--ldconfig=%s", c.LDConfig))
	}
	for _, r := range c.Requirements {
		args = append(args, fmt.Sprintf("--require=%s", r))
	}
	args = append(args, "--pid={{pid}}", "{{rootfs}}")
	return args
}

var capFlags = map[Capability]string{
	Compute:  "compute",
	Compat32: "compat32",
	Graphics: "graphics",
	Utility:  "utility",
	Video:    "video",
	Display:  "display",
}

// Opts are options for configuring gpu support
type Opts func(*config) error

// WithDevices adds the provided device indexes to the container
func WithDevices(ids ...int) Opts {
	return func(c *config) error {
		for _, i := range ids {
			c.Devices = append(c.Devices, strconv.Itoa(i))
		}
		return nil
	}
}

// WithDeviceUUIDs adds the specific device UUID to the container
func WithDeviceUUIDs(uuids ...string) Opts {
	return func(c *config) error {
		c.Devices = append(c.Devices, uuids...)
		return nil
	}
}

// WithAllDevices adds all gpus to the container
func WithAllDevices(c *config) error {
	c.Devices = []string{"all"}
	return nil
}

// WithAllCapabilities adds all capabilities to the container for the gpus
func WithAllCapabilities(c *config) error {
	for k := range capFlags {
		c.Capabilities = append(c.Capabilities, k)
	}
	return nil
}

// WithCapabilities adds the specified capabilities to the container for the gpus
func WithCapabilities(caps ...Capability) Opts {
	return func(c *config) error {
		c.Capabilities = append(c.Capabilities, caps...)
		return nil
	}
}

// WithRequiredCUDAVersion sets the required cuda version
func WithRequiredCUDAVersion(major, minor int) Opts {
	return func(c *config) error {
		c.Requirements = append(c.Requirements, fmt.Sprintf("cuda>=%d.%d", major, minor))
		return nil
	}
}
<commit_msg>Add nvidia Opts to lookup containerd binary or hook path<commit_after>/*
   Copyright The containerd Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package nvidia

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"

	"github.com/containerd/containerd/containers"
	"github.com/containerd/containerd/oci"
	specs "github.com/opencontainers/runtime-spec/specs-go"
)

const nvidiaCLI = "nvidia-container-cli"

// Capability specifies capabilities for the gpu inside the container
// Detailed explaination of options can be found:
// https://github.com/nvidia/nvidia-container-runtime#supported-driver-capabilities
type Capability int

const (
	// Compute capability
	Compute Capability = iota + 1
	// Compat32 capability
	Compat32
	// Graphics capability
	Graphics
	// Utility capability
	Utility
	// Video capability
	Video
	// Display capability
	Display
)

// WithGPUs adds NVIDIA gpu support to a container
func WithGPUs(opts ...Opts) oci.SpecOpts {
	return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
		c := &config{}
		for _, o := range opts {
			if err := o(c); err != nil {
				return err
			}
		}
		if c.OCIHookPath == "" {
			path, err := exec.LookPath("containerd")
			if err != nil {
				return err
			}
			c.OCIHookPath = path
		}
		nvidiaPath, err := exec.LookPath(nvidiaCLI)
		if err != nil {
			return err
		}
		if s.Hooks == nil {
			s.Hooks = &specs.Hooks{}
		}
		s.Hooks.Prestart = append(s.Hooks.Prestart, specs.Hook{
			Path: c.OCIHookPath,
			Args: append([]string{
				"containerd",
				"oci-hook",
				"--",
				nvidiaPath,
			}, c.args()...),
			Env: os.Environ(),
		})
		return nil
	}
}

type config struct {
	Devices      []string
	Capabilities []Capability
	LoadKmods    bool
	LDCache      string
	LDConfig     string
	Requirements []string
	OCIHookPath  string
}

func (c *config) args() []string {
	var args []string

	if c.LoadKmods {
		args = append(args, "--load-kmods")
	}
	if c.LDCache != "" {
		args = append(args, fmt.Sprintf("--ldcache=%s", c.LDCache))
	}
	args = append(args,
		"configure",
	)
	if len(c.Devices) > 0 {
		args = append(args, fmt.Sprintf("--device=%s", strings.Join(c.Devices, ",")))
	}
	for _, c := range c.Capabilities {
		args = append(args, fmt.Sprintf("--%s", capFlags[c]))
	}
	if c.LDConfig != "" {
		args = append(args, fmt.Sprintf("--ldconfig=%s", c.LDConfig))
	}
	for _, r := range c.Requirements {
		args = append(args, fmt.Sprintf("--require=%s", r))
	}
	args = append(args, "--pid={{pid}}", "{{rootfs}}")
	return args
}

var capFlags = map[Capability]string{
	Compute:  "compute",
	Compat32: "compat32",
	Graphics: "graphics",
	Utility:  "utility",
	Video:    "video",
	Display:  "display",
}

// Opts are options for configuring gpu support
type Opts func(*config) error

// WithDevices adds the provided device indexes to the container
func WithDevices(ids ...int) Opts {
	return func(c *config) error {
		for _, i := range ids {
			c.Devices = append(c.Devices, strconv.Itoa(i))
		}
		return nil
	}
}

// WithDeviceUUIDs adds the specific device UUID to the container
func WithDeviceUUIDs(uuids ...string) Opts {
	return func(c *config) error {
		c.Devices = append(c.Devices, uuids...)
		return nil
	}
}

// WithAllDevices adds all gpus to the container
func WithAllDevices(c *config) error {
	c.Devices = []string{"all"}
	return nil
}

// WithAllCapabilities adds all capabilities to the container for the gpus
func WithAllCapabilities(c *config) error {
	for k := range capFlags {
		c.Capabilities = append(c.Capabilities, k)
	}
	return nil
}

// WithCapabilities adds the specified capabilities to the container for the gpus
func WithCapabilities(caps ...Capability) Opts {
	return func(c *config) error {
		c.Capabilities = append(c.Capabilities, caps...)
		return nil
	}
}

// WithRequiredCUDAVersion sets the required cuda version
func WithRequiredCUDAVersion(major, minor int) Opts {
	return func(c *config) error {
		c.Requirements = append(c.Requirements, fmt.Sprintf("cuda>=%d.%d", major, minor))
		return nil
	}
}

// WithOCIHookPath sets the hook path for the binary
func WithOCIHookPath(path string) Opts {
	return func(c *config) error {
		c.OCIHookPath = path
		return nil
	}
}

// WithLookupOCIHookPath sets the hook path for the binary via a binary name
func WithLookupOCIHookPath(name string) Opts {
	return func(c *config) error {
		path, err := exec.LookPath(name)
		if err != nil {
			return err
		}
		c.OCIHookPath = path
		return nil
	}
}
<|endoftext|>


<commit_before><commit_msg>Use the test server at http://oauthbin.appspot.com<commit_after><|endoftext|>


<commit_before>package client

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/docker/engine-api/client/transport/cancellable"
	"github.com/docker/engine-api/types"
	"golang.org/x/net/context"
)

// serverResponse is a wrapper for http API responses.
type serverResponse struct {
	body       io.ReadCloser
	header     http.Header
	statusCode int
}

// head sends an http request to the docker API using the method HEAD.
func (cli *Client) head(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
	return cli.sendRequest(ctx, "HEAD", path, query, nil, headers)
}

// getWithContext sends an http request to the docker API using the method GET with a specific go context.
func (cli *Client) get(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
	return cli.sendRequest(ctx, "GET", path, query, nil, headers)
}

// postWithContext sends an http request to the docker API using the method POST with a specific go context.
func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) {
	return cli.sendRequest(ctx, "POST", path, query, obj, headers)
}

func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
	return cli.sendClientRequest(ctx, "POST", path, query, body, headers)
}

// put sends an http request to the docker API using the method PUT.
func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) {
	return cli.sendRequest(ctx, "PUT", path, query, obj, headers)
}

// put sends an http request to the docker API using the method PUT.
func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
	return cli.sendClientRequest(ctx, "PUT", path, query, body, headers)
}

// delete sends an http request to the docker API using the method DELETE.
func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
	return cli.sendRequest(ctx, "DELETE", path, query, nil, headers)
}

func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) {
	var body io.Reader

	if obj != nil {
		var err error
		body, err = encodeData(obj)
		if err != nil {
			return nil, err
		}
		if headers == nil {
			headers = make(map[string][]string)
		}
		headers["Content-Type"] = []string{"application/json"}
	}

	return cli.sendClientRequest(ctx, method, path, query, body, headers)
}

func (cli *Client) sendClientRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
	serverResp := &serverResponse{
		body:       nil,
		statusCode: -1,
	}

	expectedPayload := (method == "POST" || method == "PUT")
	if expectedPayload && body == nil {
		body = bytes.NewReader([]byte{})
	}

	req, err := cli.newRequest(method, path, query, body, headers)
	if err != nil {
		return serverResp, err
	}

	if cli.proto == "unix" || cli.proto == "npipe" {
		// For local communications, it doesn't matter what the host is. We just
		// need a valid and meaningful host name. (See #189)
		req.Host = "docker"
	}
	req.URL.Host = cli.addr
	req.URL.Scheme = cli.transport.Scheme()

	if expectedPayload && req.Header.Get("Content-Type") == "" {
		req.Header.Set("Content-Type", "text/plain")
	}

	resp, err := cancellable.Do(ctx, cli.transport, req)
	if err != nil {
		if isTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
			return serverResp, ErrConnectionFailed
		}

		if !cli.transport.Secure() && strings.Contains(err.Error(), "malformed HTTP response") {
			return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
		}

		if cli.transport.Secure() && strings.Contains(err.Error(), "remote error: bad certificate") {
			return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
		}

		return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
	}

	if resp != nil {
		serverResp.statusCode = resp.StatusCode
	}

	if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return serverResp, err
		}
		if len(body) == 0 {
			return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
		}

		var errorMessage string
		if resp.Header.Get("Content-Type") == "application/json" {
			var errorResponse types.ErrorResponse
			if err := json.Unmarshal(body, &errorResponse); err != nil {
				return serverResp, fmt.Errorf("Error reading JSON: %v", err)
			}
			errorMessage = errorResponse.Message
		} else {
			errorMessage = string(body)
		}

		return serverResp, fmt.Errorf("Error response from daemon: %s", strings.TrimSpace(errorMessage))
	}

	serverResp.body = resp.Body
	serverResp.header = resp.Header
	return serverResp, nil
}

func (cli *Client) newRequest(method, path string, query url.Values, body io.Reader, headers map[string][]string) (*http.Request, error) {
	apiPath := cli.getAPIPath(path, query)
	req, err := http.NewRequest(method, apiPath, body)
	if err != nil {
		return nil, err
	}

	// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
	// then the user can't change OUR headers
	for k, v := range cli.customHTTPHeaders {
		req.Header.Set(k, v)
	}

	if headers != nil {
		for k, v := range headers {
			req.Header[k] = v
		}
	}

	return req, nil
}

func encodeData(data interface{}) (*bytes.Buffer, error) {
	params := bytes.NewBuffer(nil)
	if data != nil {
		if err := json.NewEncoder(params).Encode(data); err != nil {
			return nil, err
		}
	}
	return params, nil
}

func ensureReaderClosed(response *serverResponse) {
	if response != nil && response.body != nil {
		// Drain up to 512 bytes and close the body to let the Transport reuse the connection
		io.CopyN(ioutil.Discard, response.body, 512)
		response.body.Close()
	}
}

func isTimeout(err error) bool {
	type timeout interface {
		Timeout() bool
	}
	e := err
	switch urlErr := err.(type) {
	case *url.Error:
		e = urlErr.Err
	}
	t, ok := e.(timeout)
	return ok && t.Timeout()
}
<commit_msg>client: request: check only 'bad certificate'<commit_after>package client

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/docker/engine-api/client/transport/cancellable"
	"github.com/docker/engine-api/types"
	"golang.org/x/net/context"
)

// serverResponse is a wrapper for http API responses.
type serverResponse struct {
	body       io.ReadCloser
	header     http.Header
	statusCode int
}

// head sends an http request to the docker API using the method HEAD.
func (cli *Client) head(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
	return cli.sendRequest(ctx, "HEAD", path, query, nil, headers)
}

// getWithContext sends an http request to the docker API using the method GET with a specific go context.
func (cli *Client) get(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
	return cli.sendRequest(ctx, "GET", path, query, nil, headers)
}

// postWithContext sends an http request to the docker API using the method POST with a specific go context.
func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) {
	return cli.sendRequest(ctx, "POST", path, query, obj, headers)
}

func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
	return cli.sendClientRequest(ctx, "POST", path, query, body, headers)
}

// put sends an http request to the docker API using the method PUT.
func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) {
	return cli.sendRequest(ctx, "PUT", path, query, obj, headers)
}

// put sends an http request to the docker API using the method PUT.
func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
	return cli.sendClientRequest(ctx, "PUT", path, query, body, headers)
}

// delete sends an http request to the docker API using the method DELETE.
func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) {
	return cli.sendRequest(ctx, "DELETE", path, query, nil, headers)
}

func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) {
	var body io.Reader

	if obj != nil {
		var err error
		body, err = encodeData(obj)
		if err != nil {
			return nil, err
		}
		if headers == nil {
			headers = make(map[string][]string)
		}
		headers["Content-Type"] = []string{"application/json"}
	}

	return cli.sendClientRequest(ctx, method, path, query, body, headers)
}

func (cli *Client) sendClientRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) {
	serverResp := &serverResponse{
		body:       nil,
		statusCode: -1,
	}

	expectedPayload := (method == "POST" || method == "PUT")
	if expectedPayload && body == nil {
		body = bytes.NewReader([]byte{})
	}

	req, err := cli.newRequest(method, path, query, body, headers)
	if err != nil {
		return serverResp, err
	}

	if cli.proto == "unix" || cli.proto == "npipe" {
		// For local communications, it doesn't matter what the host is. We just
		// need a valid and meaningful host name. (See #189)
		req.Host = "docker"
	}
	req.URL.Host = cli.addr
	req.URL.Scheme = cli.transport.Scheme()

	if expectedPayload && req.Header.Get("Content-Type") == "" {
		req.Header.Set("Content-Type", "text/plain")
	}

	resp, err := cancellable.Do(ctx, cli.transport, req)
	if err != nil {
		if isTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
			return serverResp, ErrConnectionFailed
		}

		if !cli.transport.Secure() && strings.Contains(err.Error(), "malformed HTTP response") {
			return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
		}

		if cli.transport.Secure() && strings.Contains(err.Error(), "bad certificate") {
			return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
		}

		return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err)
	}

	if resp != nil {
		serverResp.statusCode = resp.StatusCode
	}

	if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return serverResp, err
		}
		if len(body) == 0 {
			return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
		}

		var errorMessage string
		if resp.Header.Get("Content-Type") == "application/json" {
			var errorResponse types.ErrorResponse
			if err := json.Unmarshal(body, &errorResponse); err != nil {
				return serverResp, fmt.Errorf("Error reading JSON: %v", err)
			}
			errorMessage = errorResponse.Message
		} else {
			errorMessage = string(body)
		}

		return serverResp, fmt.Errorf("Error response from daemon: %s", strings.TrimSpace(errorMessage))
	}

	serverResp.body = resp.Body
	serverResp.header = resp.Header
	return serverResp, nil
}

func (cli *Client) newRequest(method, path string, query url.Values, body io.Reader, headers map[string][]string) (*http.Request, error) {
	apiPath := cli.getAPIPath(path, query)
	req, err := http.NewRequest(method, apiPath, body)
	if err != nil {
		return nil, err
	}

	// Add CLI Config's HTTP Headers BEFORE we set the Docker headers
	// then the user can't change OUR headers
	for k, v := range cli.customHTTPHeaders {
		req.Header.Set(k, v)
	}

	if headers != nil {
		for k, v := range headers {
			req.Header[k] = v
		}
	}

	return req, nil
}

func encodeData(data interface{}) (*bytes.Buffer, error) {
	params := bytes.NewBuffer(nil)
	if data != nil {
		if err := json.NewEncoder(params).Encode(data); err != nil {
			return nil, err
		}
	}
	return params, nil
}

func ensureReaderClosed(response *serverResponse) {
	if response != nil && response.body != nil {
		// Drain up to 512 bytes and close the body to let the Transport reuse the connection
		io.CopyN(ioutil.Discard, response.body, 512)
		response.body.Close()
	}
}

func isTimeout(err error) bool {
	type timeout interface {
		Timeout() bool
	}
	e := err
	switch urlErr := err.(type) {
	case *url.Error:
		e = urlErr.Err
	}
	t, ok := e.(timeout)
	return ok && t.Timeout()
}
<|endoftext|>


<commit_before>package s3

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/dynport/gocloud/aws"
)

var b64 = base64.StdEncoding

const (
	DEFAULT_ENDPOINT_HOST         = "s3.amazonaws.com"
	HEADER_CONTENT_MD5            = "Content-Md5"
	HEADER_CONTENT_TYPE           = "Content-Type"
	HEADER_DATE                   = "Date"
	HEADER_AUTHORIZATION          = "Authorization"
	AMZ_ACL_PUBLIC                = "public-read"
	DEFAULT_CONTENT_TYPE          = "application/octet-stream"
	HEADER_AMZ_ACL                = "x-amz-acl"
	HEADER_SERVER_SIDE_ENCRUPTION = "x-amz-server-side-encryption"
	AES256                        = "AES256"
)

type Client struct {
	*aws.Client
	CustomEndpointHost string
	UseSsl             bool
}

func NewFromEnv() *Client {
	return &Client{
		Client: aws.NewFromEnv(),
	}
}

type Bucket struct {
	Name         string    `xml:"Name"`
	CreationDate time.Time `xml:"CreationDate"`
}

type ListAllMyBucketsResult struct {
	XMLName          xml.Name `xml:"ListAllMyBucketsResult"`
	OwnerID          string   `xml:"Owner>ID"`
	OwnerDisplayName string   `xml:"Owner>DisplayName"`

	Buckets []*Bucket `xml:"Buckets>Bucket"`
}

func (client *Client) EndpointHost() string {
	if client.CustomEndpointHost != "" {
		return client.CustomEndpointHost
	}
	return DEFAULT_ENDPOINT_HOST
}

func (client *Client) Endpoint() string {
	if client.UseSsl {
		return "https://" + client.EndpointHost()
	} else {
		return "http://" + client.EndpointHost()
	}
}

type PutOptions struct {
	ContentType          string
	ContentLength        int
	AmzAcl               string
	ServerSideEncryption bool
}

func NewPublicPut() *PutOptions {
	return &PutOptions{
		AmzAcl: AMZ_ACL_PUBLIC,
	}
}

type Content struct {
	Key              string    `xml:"Key"`
	LastModified     time.Time `xml:"LastModified"`
	Etag             string    `xml:"ETag"`
	Size             int64     `xml:"Size"`
	StorageClass     string    `xml:"StorageClass"`
	OwnerID          string    `xml:"Owner>ID"`
	OwnerDisplayName string    `xml:"Owner>DisplayName"`
}

type ListBucketResult struct {
	XMLName     xml.Name `xml:"ListBucketResult"`
	Name        string   `xml:"Name"`
	Prefix      string   `xml:"Prefix"`
	Marker      string   `xml:"Marker"`
	MaxKeys     int      `xml:"MaxKeys"`
	IsTruncated bool     `xml:"IsTruncated"`

	Contents []*Content `xml:"Contents"`
}

type ApiError struct {
	Message      string
	Request      *http.Request
	Response     *http.Response
	ResponseBody []byte
}

func NewApiError(message string, req *http.Request, rsp *http.Response, body []byte) *ApiError {
	return &ApiError{
		Message:      message,
		Request:      req,
		Response:     rsp,
		ResponseBody: body,
	}
}

func (e ApiError) Error() string {
	return fmt.Sprintf("%s: status=%s", e.Message, e.Response.Status)
}

func (client *Client) Service() (r *ListAllMyBucketsResult, e error) {
	req, e := http.NewRequest("GET", client.Endpoint()+"/", nil)
	if e != nil {
		return r, e
	}
	rsp, body, e := client.signAndDoRequest("", req)
	if e != nil {
		return nil, e
	}
	r = &ListAllMyBucketsResult{}
	e = xml.Unmarshal(body, r)
	if e != nil {
		return nil, NewApiError("Unmarshalling ListAllMyBucketsResult", req, rsp, body)
	}
	return r, e
}

func (client *Client) Head(bucket, key string) (*http.Response, error) {
	return client.readRequest("HEAD", bucket, key)
}

func (client *Client) Get(bucket, key string) (*http.Response, error) {
	return client.readRequest("GET", bucket, key)
}

func (client *Client) readRequest(method, bucket, key string) (*http.Response, error) {
	theUrl := client.keyUrl(bucket, key)
	req, e := http.NewRequest(method, theUrl, nil)
	if e != nil {
		return nil, e
	}
	client.SignS3Request(req, bucket)
	return http.DefaultClient.Do(req)
}

func (client *Client) keyUrl(bucket, key string) string {
	if client.UseSsl {
		return "https://" + client.EndpointHost() + "/" + bucket + "/" + key
	}
	return "http://" + bucket + "." + client.EndpointHost() + "/" + key
}

func (client *Client) PutStream(bucket, key string, r io.Reader, options *PutOptions) error {
	if options == nil {
		options = &PutOptions{ContentType: DEFAULT_CONTENT_TYPE}
	}
	if options.ContentLength == 0 {
		return fmt.Errorf("Content-Length must be set")
	}

	theUrl := client.keyUrl(bucket, key)
	req, e := http.NewRequest("PUT", theUrl, r)
	if e != nil {
		return e
	}

	req.Header.Add("Host", bucket+"."+client.EndpointHost())
	req.Header.Add("Content-Length", strconv.Itoa(options.ContentLength))

	contentType := options.ContentType
	if contentType == "" {
		contentType = DEFAULT_CONTENT_TYPE
	}
	req.Header.Add(HEADER_CONTENT_TYPE, contentType)

	if options.AmzAcl != "" {
		req.Header.Add(HEADER_AMZ_ACL, options.AmzAcl)
	}

	if options.ServerSideEncryption {
		req.Header.Add(HEADER_SERVER_SIDE_ENCRUPTION, AES256)
	}

	client.SignS3Request(req, bucket)
	rsp, e := http.DefaultClient.Do(req)
	if e != nil {
		return e
	}
	defer rsp.Body.Close()
	b, e := ioutil.ReadAll(rsp.Body)
	if e != nil {
		return e
	}
	if rsp.StatusCode != 200 {
		return fmt.Errorf("error uploading key: %s - %s", rsp.Status, string(b))
	}
	return nil
}

func (client *Client) Put(bucket, key string, data []byte, options *PutOptions) error {
	if options == nil {
		options = &PutOptions{ContentType: DEFAULT_CONTENT_TYPE}
	}

	buf := bytes.NewBuffer(data)
	theUrl := client.keyUrl(bucket, key)
	req, e := http.NewRequest("PUT", theUrl, buf)
	if e != nil {
		return e
	}

	req.Header.Add("Host", bucket+"."+client.EndpointHost())

	contentType := options.ContentType
	if contentType == "" {
		contentType = DEFAULT_CONTENT_TYPE
	}
	req.Header.Add(HEADER_CONTENT_TYPE, contentType)

	if options.AmzAcl != "" {
		req.Header.Add(HEADER_AMZ_ACL, options.AmzAcl)
	}

	if options.ServerSideEncryption {
		req.Header.Add(HEADER_SERVER_SIDE_ENCRUPTION, AES256)
	}

	b64md5, e := contentMd5(string(data))
	if e != nil {
		return e
	}
	req.Header.Add(HEADER_CONTENT_MD5, b64md5)

	client.SignS3Request(req, bucket)
	rsp, e := http.DefaultClient.Do(req)
	if e != nil {
		return e
	}
	defer rsp.Body.Close()
	b, e := ioutil.ReadAll(rsp.Body)
	if e != nil {
		return e
	}
	if rsp.StatusCode != 200 {
		return fmt.Errorf("error uploading key: %s - %s", rsp.Status, string(b))
	}
	return nil
}

// stolen from goamz
var s3ParamsToSign = map[string]bool{
	"acl":                          true,
	"location":                     true,
	"logging":                      true,
	"notification":                 true,
	"partNumber":                   true,
	"policy":                       true,
	"requestPayment":               true,
	"torrent":                      true,
	"uploadId":                     true,
	"uploads":                      true,
	"versionId":                    true,
	"versioning":                   true,
	"versions":                     true,
	"response-content-type":        true,
	"response-content-language":    true,
	"response-expires":             true,
	"response-cache-control":       true,
	"response-content-disposition": true,
	"response-content-encoding":    true,
}

func (client *Client) SignS3Request(req *http.Request, bucket string) {
	t := time.Now().UTC()
	date := t.Format(http.TimeFormat)
	payloadParts := []string{
		req.Method,
		req.Header.Get(HEADER_CONTENT_MD5),
		req.Header.Get(HEADER_CONTENT_TYPE),
		date,
	}
	amzHeaders := []string{}
	for k, v := range req.Header {
		value := strings.ToLower(k) + ":" + strings.Join(v, ",")
		if strings.HasPrefix(value, "x-amz") {
			amzHeaders = append(amzHeaders, value)
		}
	}
	sort.Strings(amzHeaders)
	payloadParts = append(payloadParts, amzHeaders...)
	path := req.URL.Path
	query := normalizeParams(req.URL)
	if query != "" {
		path += "?" + query
	}
	if !client.UseSsl && bucket != "" {
		path = "/" + bucket + path
	}
	payloadParts = append(payloadParts, path)
	payload := strings.Join(payloadParts, "\n")
	req.Header.Add(HEADER_DATE, date)
	req.Header.Add(HEADER_AUTHORIZATION, "AWS "+client.Key+":"+signPayload(payload, client.newSha1Hash(client.Secret)))
}

func normalizeParams(url *url.URL) string {
	params := []string{}
	for _, part := range strings.Split(url.RawQuery, "&") {
		parts := strings.SplitN(part, "=", 2)
		if _, ok := s3ParamsToSign[parts[0]]; ok {
			params = append(params, part)
		}
	}
	sort.Strings(params)
	if len(params) > 0 {
		return strings.Join(params, "&")
	}
	return ""
}

func (client *Client) newSha1Hash(secret string) hash.Hash {
	return hmac.New(sha1.New, []byte(client.Secret))
}

func signPayload(payload string, hash hash.Hash) string {
	hash.Write([]byte(payload))
	signature := make([]byte, b64.EncodedLen(hash.Size()))
	b64.Encode(signature, hash.Sum(nil))
	return string(signature)
}

func (client *Client) signAndDoRequest(bucket string, req *http.Request) (rsp *http.Response, body []byte, e error) {
	client.SignS3Request(req, bucket)
	rsp, e = http.DefaultClient.Do(req)
	if e != nil {
		return rsp, nil, e
	}
	defer rsp.Body.Close()
	b, e := ioutil.ReadAll(rsp.Body)
	return rsp, b, e
}
<commit_msg>automatically use multipart upload when content-length > 5MB<commit_after>package s3

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

	"github.com/dynport/gocloud/aws"
)

var b64 = base64.StdEncoding

const (
	DEFAULT_ENDPOINT_HOST         = "s3.amazonaws.com"
	HEADER_CONTENT_MD5            = "Content-Md5"
	HEADER_CONTENT_TYPE           = "Content-Type"
	HEADER_DATE                   = "Date"
	HEADER_AUTHORIZATION          = "Authorization"
	AMZ_ACL_PUBLIC                = "public-read"
	DEFAULT_CONTENT_TYPE          = "application/octet-stream"
	HEADER_AMZ_ACL                = "x-amz-acl"
	HEADER_SERVER_SIDE_ENCRUPTION = "x-amz-server-side-encryption"
	AES256                        = "AES256"
)

type Client struct {
	*aws.Client
	CustomEndpointHost string
	UseSsl             bool
}

func NewFromEnv() *Client {
	return &Client{
		Client: aws.NewFromEnv(),
	}
}

type Bucket struct {
	Name         string    `xml:"Name"`
	CreationDate time.Time `xml:"CreationDate"`
}

type ListAllMyBucketsResult struct {
	XMLName          xml.Name `xml:"ListAllMyBucketsResult"`
	OwnerID          string   `xml:"Owner>ID"`
	OwnerDisplayName string   `xml:"Owner>DisplayName"`

	Buckets []*Bucket `xml:"Buckets>Bucket"`
}

func (client *Client) EndpointHost() string {
	if client.CustomEndpointHost != "" {
		return client.CustomEndpointHost
	}
	return DEFAULT_ENDPOINT_HOST
}

func (client *Client) Endpoint() string {
	if client.UseSsl {
		return "https://" + client.EndpointHost()
	} else {
		return "http://" + client.EndpointHost()
	}
}

type PutOptions struct {
	ContentType          string
	ContentLength        int
	AmzAcl               string
	ServerSideEncryption bool
}

func NewPublicPut() *PutOptions {
	return &PutOptions{
		AmzAcl: AMZ_ACL_PUBLIC,
	}
}

type Content struct {
	Key              string    `xml:"Key"`
	LastModified     time.Time `xml:"LastModified"`
	Etag             string    `xml:"ETag"`
	Size             int64     `xml:"Size"`
	StorageClass     string    `xml:"StorageClass"`
	OwnerID          string    `xml:"Owner>ID"`
	OwnerDisplayName string    `xml:"Owner>DisplayName"`
}

type ListBucketResult struct {
	XMLName     xml.Name `xml:"ListBucketResult"`
	Name        string   `xml:"Name"`
	Prefix      string   `xml:"Prefix"`
	Marker      string   `xml:"Marker"`
	MaxKeys     int      `xml:"MaxKeys"`
	IsTruncated bool     `xml:"IsTruncated"`

	Contents []*Content `xml:"Contents"`
}

type ApiError struct {
	Message      string
	Request      *http.Request
	Response     *http.Response
	ResponseBody []byte
}

func NewApiError(message string, req *http.Request, rsp *http.Response, body []byte) *ApiError {
	return &ApiError{
		Message:      message,
		Request:      req,
		Response:     rsp,
		ResponseBody: body,
	}
}

func (e ApiError) Error() string {
	return fmt.Sprintf("%s: status=%s", e.Message, e.Response.Status)
}

func (client *Client) Service() (r *ListAllMyBucketsResult, e error) {
	req, e := http.NewRequest("GET", client.Endpoint()+"/", nil)
	if e != nil {
		return r, e
	}
	rsp, body, e := client.signAndDoRequest("", req)
	if e != nil {
		return nil, e
	}
	r = &ListAllMyBucketsResult{}
	e = xml.Unmarshal(body, r)
	if e != nil {
		return nil, NewApiError("Unmarshalling ListAllMyBucketsResult", req, rsp, body)
	}
	return r, e
}

func (client *Client) Head(bucket, key string) (*http.Response, error) {
	return client.readRequest("HEAD", bucket, key)
}

func (client *Client) Get(bucket, key string) (*http.Response, error) {
	return client.readRequest("GET", bucket, key)
}

func (client *Client) readRequest(method, bucket, key string) (*http.Response, error) {
	theUrl := client.keyUrl(bucket, key)
	req, e := http.NewRequest(method, theUrl, nil)
	if e != nil {
		return nil, e
	}
	client.SignS3Request(req, bucket)
	return http.DefaultClient.Do(req)
}

func (client *Client) keyUrl(bucket, key string) string {
	if client.UseSsl {
		return "https://" + client.EndpointHost() + "/" + bucket + "/" + key
	}
	return "http://" + bucket + "." + client.EndpointHost() + "/" + key
}

func (client *Client) PutStream(bucket, key string, r io.Reader, options *PutOptions) error {
	if options == nil {
		options = &PutOptions{ContentType: DEFAULT_CONTENT_TYPE}
	}
	theUrl := client.keyUrl(bucket, key)
	req, e := http.NewRequest("PUT", theUrl, r)
	if e != nil {
		return e
	}

	req.Header.Add("Host", bucket+"."+client.EndpointHost())

	contentType := options.ContentType
	if contentType == "" {
		contentType = DEFAULT_CONTENT_TYPE
	}
	req.Header.Add(HEADER_CONTENT_TYPE, contentType)

	if options.AmzAcl != "" {
		req.Header.Add(HEADER_AMZ_ACL, options.AmzAcl)
	}

	if options.ServerSideEncryption {
		req.Header.Add(HEADER_SERVER_SIDE_ENCRUPTION, AES256)
	}

	buf := bytes.NewBuffer(make([]byte, 0, MinPartSize))
	_, e = io.CopyN(buf, r, MinPartSize)
	if e == io.EOF {
		// less than min multipart size => direct upload
		return client.Put(bucket, key, buf.Bytes(), options)
	} else if e != nil {
		return e
	}
	mr := io.MultiReader(buf, r)

	mo := &MultipartOptions{
		PartSize: 5 * 1024 * 1024,
		Callback: func(res *UploadPartResult) {
			if res.Error != nil {
				logger.Print("ERROR: " + e.Error())
			} else if res.Part != nil {
				logger.Printf("uploaded: %03d (%s) %d", res.Part.PartNumber, res.Part.ETag, res.CurrentSize)
			}
		},
		PutOptions: options,
	}
	_, e = client.PutMultipart(bucket, key, mr, mo)
	return e
}

func (client *Client) Put(bucket, key string, data []byte, options *PutOptions) error {
	if options == nil {
		options = &PutOptions{ContentType: DEFAULT_CONTENT_TYPE}
	}

	buf := bytes.NewBuffer(data)
	theUrl := client.keyUrl(bucket, key)
	req, e := http.NewRequest("PUT", theUrl, buf)
	if e != nil {
		return e
	}

	req.Header.Add("Host", bucket+"."+client.EndpointHost())

	contentType := options.ContentType
	if contentType == "" {
		contentType = DEFAULT_CONTENT_TYPE
	}
	req.Header.Add(HEADER_CONTENT_TYPE, contentType)

	if options.AmzAcl != "" {
		req.Header.Add(HEADER_AMZ_ACL, options.AmzAcl)
	}

	if options.ServerSideEncryption {
		req.Header.Add(HEADER_SERVER_SIDE_ENCRUPTION, AES256)
	}

	b64md5, e := contentMd5(string(data))
	if e != nil {
		return e
	}
	req.Header.Add(HEADER_CONTENT_MD5, b64md5)

	client.SignS3Request(req, bucket)
	rsp, e := http.DefaultClient.Do(req)
	if e != nil {
		return e
	}
	defer rsp.Body.Close()
	b, e := ioutil.ReadAll(rsp.Body)
	if e != nil {
		return e
	}
	if rsp.StatusCode != 200 {
		return fmt.Errorf("error uploading key: %s - %s", rsp.Status, string(b))
	}
	return nil
}

// stolen from goamz
var s3ParamsToSign = map[string]bool{
	"acl":                          true,
	"location":                     true,
	"logging":                      true,
	"notification":                 true,
	"partNumber":                   true,
	"policy":                       true,
	"requestPayment":               true,
	"torrent":                      true,
	"uploadId":                     true,
	"uploads":                      true,
	"versionId":                    true,
	"versioning":                   true,
	"versions":                     true,
	"response-content-type":        true,
	"response-content-language":    true,
	"response-expires":             true,
	"response-cache-control":       true,
	"response-content-disposition": true,
	"response-content-encoding":    true,
}

func (client *Client) SignS3Request(req *http.Request, bucket string) {
	t := time.Now().UTC()
	date := t.Format(http.TimeFormat)
	payloadParts := []string{
		req.Method,
		req.Header.Get(HEADER_CONTENT_MD5),
		req.Header.Get(HEADER_CONTENT_TYPE),
		date,
	}
	amzHeaders := []string{}
	for k, v := range req.Header {
		value := strings.ToLower(k) + ":" + strings.Join(v, ",")
		if strings.HasPrefix(value, "x-amz") {
			amzHeaders = append(amzHeaders, value)
		}
	}
	sort.Strings(amzHeaders)
	payloadParts = append(payloadParts, amzHeaders...)
	path := req.URL.Path
	query := normalizeParams(req.URL)
	if query != "" {
		path += "?" + query
	}
	if !client.UseSsl && bucket != "" {
		path = "/" + bucket + path
	}
	payloadParts = append(payloadParts, path)
	payload := strings.Join(payloadParts, "\n")
	req.Header.Add(HEADER_DATE, date)
	req.Header.Add(HEADER_AUTHORIZATION, "AWS "+client.Key+":"+signPayload(payload, client.newSha1Hash(client.Secret)))
}

func normalizeParams(url *url.URL) string {
	params := []string{}
	for _, part := range strings.Split(url.RawQuery, "&") {
		parts := strings.SplitN(part, "=", 2)
		if _, ok := s3ParamsToSign[parts[0]]; ok {
			params = append(params, part)
		}
	}
	sort.Strings(params)
	if len(params) > 0 {
		return strings.Join(params, "&")
	}
	return ""
}

func (client *Client) newSha1Hash(secret string) hash.Hash {
	return hmac.New(sha1.New, []byte(client.Secret))
}

func signPayload(payload string, hash hash.Hash) string {
	hash.Write([]byte(payload))
	signature := make([]byte, b64.EncodedLen(hash.Size()))
	b64.Encode(signature, hash.Sum(nil))
	return string(signature)
}

func (client *Client) signAndDoRequest(bucket string, req *http.Request) (rsp *http.Response, body []byte, e error) {
	client.SignS3Request(req, bucket)
	rsp, e = http.DefaultClient.Do(req)
	if e != nil {
		return rsp, nil, e
	}
	defer rsp.Body.Close()
	b, e := ioutil.ReadAll(rsp.Body)
	return rsp, b, e
}
<|endoftext|>


<commit_before>// Copyright 2014 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package orchestrator

import (
	"github.com/andreaskoch/allmark2/common/config"
	"github.com/andreaskoch/allmark2/common/logger"
	"github.com/andreaskoch/allmark2/common/paths"
	"github.com/andreaskoch/allmark2/common/route"
	"github.com/andreaskoch/allmark2/dataaccess"
	"github.com/andreaskoch/allmark2/model"
	"github.com/andreaskoch/allmark2/services/converter"
	"github.com/andreaskoch/allmark2/services/parser"
	"github.com/andreaskoch/allmark2/web/view/viewmodel"
	"github.com/andreaskoch/allmark2/web/webpaths"
	"sort"
	"strings"
	"time"
)

type CacheType int

const (
	CacheTypeItems CacheType = iota
	CacheTypeItemsByAlias
	CacheTypeLeafesByRoute
)

type CacheState int

const (
	CacheStateStale CacheState = iota
	CacheStatePrimed
)

type CacheStatusMap map[CacheType]CacheState

func newBaseOrchestrator(logger logger.Logger, config config.Config, repository dataaccess.Repository, parser parser.Parser, converter converter.Converter, webPathProvider webpaths.WebPathProvider) *Orchestrator {
	return &Orchestrator{
		logger: logger,

		config:     config,
		repository: repository,
		parser:     parser,
		converter:  converter,

		webPathProvider: webPathProvider,
	}
}

type Orchestrator struct {
	logger logger.Logger

	config     config.Config
	repository dataaccess.Repository
	parser     parser.Parser
	converter  converter.Converter

	webPathProvider webpaths.WebPathProvider

	// cache control
	cacheStatusMap CacheStatusMap
	cachePrimerMap map[CacheType]func()

	// caches
	items         []*model.Item
	itemsByAlias  map[string]*model.Item
	leafesByRoute map[string][]route.Route
}

// Reset all Caches
func (orchestrator *Orchestrator) ResetCache() {

	// mark all caches as stale
	for cacheType := range orchestrator.cacheStatusMap {
		orchestrator.cacheStatusMap[cacheType] = CacheStateStale
	}

	// prime all caches asynchronously
	go func() {
		orchestrator.primeCaches()
	}()

}

func (orchestrator *Orchestrator) setCache(cacheType CacheType, primer func()) {

	// initialize the primer map on first use
	if orchestrator.cachePrimerMap == nil {
		orchestrator.cachePrimerMap = make(map[CacheType]func())
	}

	// initialize the status map on first use
	if orchestrator.cacheStatusMap == nil {
		orchestrator.cacheStatusMap = make(map[CacheType]CacheState)
	}

	// store the primer
	orchestrator.cachePrimerMap[cacheType] = primer

	// fill the cache
	primer()

	// mark the cache type as primed
	orchestrator.cacheStatusMap[cacheType] = CacheStatePrimed
}

func (orchestrator *Orchestrator) isCacheStale(cacheType CacheType) bool {
	if status, exists := orchestrator.cacheStatusMap[cacheType]; exists {
		return status == CacheStateStale
	}

	// if there is no status it is definitly stale
	return true
}

// Prime all caches
func (orchestrator *Orchestrator) primeCaches() {
	for cacheType := range orchestrator.cacheStatusMap {
		orchestrator.primeCache(cacheType)
	}
}

// Prime a particular cache
func (orchestrator *Orchestrator) primeCache(cacheType CacheType) {
	primerFunc := orchestrator.cachePrimerMap[cacheType]
	primerFunc()
}

func (orchestrator *Orchestrator) ItemExists(route route.Route) bool {
	_, exists := orchestrator.repository.Item(route)
	return exists
}

func (orchestrator *Orchestrator) absolutePather(prefix string) paths.Pather {
	return orchestrator.webPathProvider.AbsolutePather(prefix)
}

func (orchestrator *Orchestrator) itemPather() paths.Pather {
	return orchestrator.webPathProvider.ItemPather()
}

func (orchestrator *Orchestrator) tagPather() paths.Pather {
	return orchestrator.webPathProvider.TagPather()
}

func (orchestrator *Orchestrator) relativePather(baseRoute route.Route) paths.Pather {
	return orchestrator.webPathProvider.RelativePather(baseRoute)
}

func (orchestrator *Orchestrator) parseItem(item *dataaccess.Item) *model.Item {
	parsedItem, err := orchestrator.parser.ParseItem(item)
	if err != nil {
		orchestrator.logger.Warn(err.Error())
		return nil
	}

	return parsedItem
}

func (orchestrator *Orchestrator) parseFile(file *dataaccess.File) *model.File {
	parsedFile, err := orchestrator.parser.ParseFile(file)
	if err != nil {
		orchestrator.logger.Warn(err.Error())
		return nil
	}

	return parsedFile
}

func (orchestrator *Orchestrator) rootItem() *model.Item {
	return orchestrator.parseItem(orchestrator.repository.Root())
}

func (orchestrator *Orchestrator) getItem(route route.Route) *model.Item {
	item, exists := orchestrator.repository.Item(route)
	if !exists {
		return nil
	}

	return orchestrator.parseItem(item)
}

func (orchestrator *Orchestrator) getLatestRoutesByPage(parentRoute route.Route, pageSize, page int) (routes []route.Route, found bool) {

	latestRoutes, found := orchestrator.getLatestRoutes(parentRoute)
	if !found {
		return []route.Route{}, false
	}

	// determine the start index
	startIndex := pageSize * (page - 1)
	if startIndex >= len(latestRoutes) {
		return []route.Route{}, false
	}

	// determine the end index
	endIndex := startIndex + pageSize
	if endIndex > len(latestRoutes) {
		endIndex = len(latestRoutes)
	}

	return latestRoutes[startIndex:endIndex], true
}

func (orchestrator *Orchestrator) getLatestRoutes(parentRoute route.Route) (routes []route.Route, found bool) {

	leafes := orchestrator.getAllLeafes(parentRoute)

	// collect the creation dates for all leafes
	routesAndDates := make([]routeAndDate, 0, len(leafes))
	for _, leaf := range leafes {
		creationDate, found := orchestrator.getCreationDate(leaf)
		if !found {
			// todo: log info
			continue
		}

		routesAndDates = append(routesAndDates, routeAndDate{leaf, creationDate})
	}

	// sort the leafes by date
	SortItemRoutesAndDatesBy(sortRoutesAndDatesDescending).Sort(routesAndDates)

	routes = make([]route.Route, 0)
	for _, routeAndDate := range routesAndDates {
		routes = append(routes, routeAndDate.route)
	}

	return routes, true
}

func (orchestrator *Orchestrator) getAllLeafes(parentRoute route.Route) []route.Route {

	// initialize the leafes map on first use
	if orchestrator.leafesByRoute == nil {
		orchestrator.leafesByRoute = make(map[string][]route.Route)
	}

	// cache lookup
	key := parentRoute.Value()
	if leafes, isset := orchestrator.leafesByRoute[key]; isset {
		return leafes
	}

	childRoutes := make([]route.Route, 0)

	childItems := orchestrator.getChilds(parentRoute)
	if hasNoMoreChilds := len(childItems) == 0; hasNoMoreChilds {
		return []route.Route{parentRoute}
	}

	// recurse
	for _, childItem := range childItems {

		// skip locations
		if childItem.Type == model.TypeLocation {
			continue
		}

		childRoutes = append(childRoutes, orchestrator.getAllLeafes(childItem.Route())...)
	}

	// store the value
	orchestrator.leafesByRoute[key] = childRoutes

	return childRoutes

}

func (orchestrator *Orchestrator) getAllItems() []*model.Item {

	// load from cache
	if orchestrator.items != nil {

		// re-prime the cache if it is stale
		if orchestrator.isCacheStale(CacheTypeItems) {
			go orchestrator.primeCache(CacheTypeItems)
		}

		return orchestrator.items
	}

	orchestrator.setCache(CacheTypeItems, func() {

		allItems := make([]*model.Item, 0)

		for _, repositoryItem := range orchestrator.repository.Items() {
			item := orchestrator.parseItem(repositoryItem)
			if item == nil {
				continue
			}

			allItems = append(allItems, item)
		}

		model.SortItemsBy(sortItemsByDate).Sort(allItems)

		// store to cache
		orchestrator.items = allItems
	})

	return orchestrator.items
}

func (orchestrator *Orchestrator) getItems(pageSize, page int) []*model.Item {

	allItems := orchestrator.getAllItems()

	// determine the start index
	startIndex := pageSize * (page - 1)
	if startIndex >= len(allItems) {
		return []*model.Item{}
	}

	// determine the end index
	endIndex := startIndex + pageSize
	if endIndex > len(allItems) {
		endIndex = len(allItems)
	}

	return allItems[startIndex:endIndex]
}

func (orchestrator *Orchestrator) getCreationDate(itemRoute route.Route) (creationDate time.Time, found bool) {

	item := orchestrator.getItem(itemRoute)
	if item == nil {
		return time.Time{}, false
	}

	return item.MetaData.CreationDate, true
}

func (orchestrator *Orchestrator) getFile(route route.Route) *model.File {
	file, exists := orchestrator.repository.File(route)
	if !exists {
		return nil
	}

	return orchestrator.parseFile(file)
}

func (orchestrator *Orchestrator) getParent(route route.Route) *model.Item {
	parent := orchestrator.repository.Parent(route)
	if parent == nil {
		return nil
	}

	return orchestrator.parseItem(parent)
}

func (orchestrator *Orchestrator) getPrevious(currentRoute route.Route) *model.Item {

	latestRoutes, found := orchestrator.getLatestRoutes(route.New())
	if !found {
		return nil
	}

	// determine the position of the supplied route
	matchingIndex := -1
	for index, route := range latestRoutes {
		if route.Value() == currentRoute.Value() {
			matchingIndex = index
			break
		}
	}

	// abort if the route was not found at all
	if noMatchFound := (matchingIndex == -1); noMatchFound {
		return nil
	}

	// abort if there is no next item
	nextIndex := matchingIndex + 1
	if nextIndex >= len(latestRoutes) {
		return nil
	}

	// determine the next route
	nextRoute := latestRoutes[nextIndex]

	return orchestrator.getItem(nextRoute)
}

func (orchestrator *Orchestrator) getNext(currentRoute route.Route) *model.Item {

	latestRoutes, found := orchestrator.getLatestRoutes(route.New())
	if !found {
		return nil
	}

	// determine the position of the supplied route
	matchingIndex := -1
	for index, route := range latestRoutes {
		if route.Value() == currentRoute.Value() {
			matchingIndex = index
			break
		}
	}

	// abort if the route was not found at all
	if noMatchFound := (matchingIndex == -1); noMatchFound {
		return nil
	}

	// abort if there is no previous item
	previousIndex := matchingIndex - 1
	if noPreviousItem := (previousIndex < 0); noPreviousItem {
		return nil
	}

	// determine the previous route
	previousRoute := latestRoutes[previousIndex]

	return orchestrator.getItem(previousRoute)
}

func (orchestrator *Orchestrator) getChilds(route route.Route) (childs []*model.Item) {

	childs = make([]*model.Item, 0)

	for _, child := range orchestrator.repository.Childs(route) {
		parsed := orchestrator.parseItem(child)
		if parsed == nil {
			continue
		}

		childs = append(childs, parsed)
	}

	return childs
}

// Get the item that has the specified alias. Returns nil if there is no matching item.
func (orchestrator *Orchestrator) getItemByAlias(alias string) *model.Item {

	alias = strings.TrimSpace(strings.ToLower(alias))

	// load from cache
	if orchestrator.itemsByAlias != nil {

		// re-prime the cache if it is stale
		if orchestrator.isCacheStale(CacheTypeItemsByAlias) {
			go orchestrator.primeCache(CacheTypeItemsByAlias)
		}

		return orchestrator.itemsByAlias[alias]
	}

	orchestrator.setCache(CacheTypeItemsByAlias, func() {

		itemsByAlias := make(map[string]*model.Item)

		for _, item := range orchestrator.getAllItems() {

			// continue items without an alias
			if item.MetaData.Alias == "" {
				continue
			}

			itemAlias := strings.TrimSpace(strings.ToLower(item.MetaData.Alias))
			itemsByAlias[itemAlias] = item
		}

		orchestrator.itemsByAlias = itemsByAlias
	})

	return orchestrator.itemsByAlias[alias]
}

func (orchestrator *Orchestrator) getAnalyticsSettings() viewmodel.Analytics {
	return viewmodel.Analytics{
		Enabled: orchestrator.config.Analytics.Enabled,
		GoogleAnalytics: viewmodel.GoogleAnalytics{
			Enabled:    orchestrator.config.Analytics.GoogleAnalytics.Enabled,
			TrackingId: orchestrator.config.Analytics.GoogleAnalytics.TrackingId,
		},
	}
}

// sort the models by date and name
func sortItemsByDate(model1, model2 *model.Item) bool {

	return model1.MetaData.CreationDate.Before(model2.MetaData.CreationDate)

}

func sortRoutesAndDatesDescending(itemRoute1, itemRoute2 routeAndDate) bool {
	return itemRoute1.date.After(itemRoute2.date)
}

type routeAndDate struct {
	route route.Route
	date  time.Time
}

type SortItemRoutesAndDatesBy func(itemRoute1, itemRoute2 routeAndDate) bool

func (by SortItemRoutesAndDatesBy) Sort(routesAndDates []routeAndDate) {
	sorter := &routeAndDateSorter{
		routesAndDates: routesAndDates,
		by:             by,
	}

	sort.Sort(sorter)
}

type routeAndDateSorter struct {
	routesAndDates []routeAndDate
	by             SortItemRoutesAndDatesBy
}

func (sorter *routeAndDateSorter) Len() int {
	return len(sorter.routesAndDates)
}

func (sorter *routeAndDateSorter) Swap(i, j int) {
	sorter.routesAndDates[i], sorter.routesAndDates[j] = sorter.routesAndDates[j], sorter.routesAndDates[i]
}

func (sorter *routeAndDateSorter) Less(i, j int) bool {
	return sorter.by(sorter.routesAndDates[i], sorter.routesAndDates[j])
}
<commit_msg>Got rid of the cache type struct -> this was overkill<commit_after>// Copyright 2014 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package orchestrator

import (
	"github.com/andreaskoch/allmark2/common/config"
	"github.com/andreaskoch/allmark2/common/logger"
	"github.com/andreaskoch/allmark2/common/paths"
	"github.com/andreaskoch/allmark2/common/route"
	"github.com/andreaskoch/allmark2/dataaccess"
	"github.com/andreaskoch/allmark2/model"
	"github.com/andreaskoch/allmark2/services/converter"
	"github.com/andreaskoch/allmark2/services/parser"
	"github.com/andreaskoch/allmark2/web/view/viewmodel"
	"github.com/andreaskoch/allmark2/web/webpaths"
	"sort"
	"strings"
	"time"
)

type CacheState int

const (
	CacheStateStale CacheState = iota
	CacheStatePrimed
)

func newBaseOrchestrator(logger logger.Logger, config config.Config, repository dataaccess.Repository, parser parser.Parser, converter converter.Converter, webPathProvider webpaths.WebPathProvider) *Orchestrator {
	return &Orchestrator{
		logger: logger,

		config:     config,
		repository: repository,
		parser:     parser,
		converter:  converter,

		webPathProvider: webPathProvider,
	}
}

type Orchestrator struct {
	logger logger.Logger

	config     config.Config
	repository dataaccess.Repository
	parser     parser.Parser
	converter  converter.Converter

	webPathProvider webpaths.WebPathProvider

	// cache control
	cacheStatusMap map[string]CacheState
	cachePrimerMap map[string]func()

	// caches
	items         []*model.Item
	itemsByAlias  map[string]*model.Item
	leafesByRoute map[string][]route.Route
}

// Reset all Caches
func (orchestrator *Orchestrator) ResetCache() {

	// mark all caches as stale
	for cacheType := range orchestrator.cacheStatusMap {
		orchestrator.cacheStatusMap[cacheType] = CacheStateStale
	}

	// prime all caches asynchronously
	go func() {
		orchestrator.primeCaches()
	}()

}

func (orchestrator *Orchestrator) setCache(cacheType string, primer func()) {

	// initialize the primer map on first use
	if orchestrator.cachePrimerMap == nil {
		orchestrator.cachePrimerMap = make(map[string]func())
	}

	// initialize the status map on first use
	if orchestrator.cacheStatusMap == nil {
		orchestrator.cacheStatusMap = make(map[string]CacheState)
	}

	// store the primer
	orchestrator.cachePrimerMap[cacheType] = primer

	// fill the cache
	primer()

	// mark the cache type as primed
	orchestrator.cacheStatusMap[cacheType] = CacheStatePrimed
}

func (orchestrator *Orchestrator) isCacheStale(cacheType string) bool {
	if status, exists := orchestrator.cacheStatusMap[cacheType]; exists {
		return status == CacheStateStale
	}

	// if there is no status it is definitly stale
	return true
}

// Prime all caches
func (orchestrator *Orchestrator) primeCaches() {
	for cacheType := range orchestrator.cacheStatusMap {
		orchestrator.primeCache(cacheType)
	}
}

// Prime a particular cache
func (orchestrator *Orchestrator) primeCache(cacheType string) {
	primerFunc := orchestrator.cachePrimerMap[cacheType]
	primerFunc()
}

func (orchestrator *Orchestrator) ItemExists(route route.Route) bool {
	_, exists := orchestrator.repository.Item(route)
	return exists
}

func (orchestrator *Orchestrator) absolutePather(prefix string) paths.Pather {
	return orchestrator.webPathProvider.AbsolutePather(prefix)
}

func (orchestrator *Orchestrator) itemPather() paths.Pather {
	return orchestrator.webPathProvider.ItemPather()
}

func (orchestrator *Orchestrator) tagPather() paths.Pather {
	return orchestrator.webPathProvider.TagPather()
}

func (orchestrator *Orchestrator) relativePather(baseRoute route.Route) paths.Pather {
	return orchestrator.webPathProvider.RelativePather(baseRoute)
}

func (orchestrator *Orchestrator) parseItem(item *dataaccess.Item) *model.Item {
	parsedItem, err := orchestrator.parser.ParseItem(item)
	if err != nil {
		orchestrator.logger.Warn(err.Error())
		return nil
	}

	return parsedItem
}

func (orchestrator *Orchestrator) parseFile(file *dataaccess.File) *model.File {
	parsedFile, err := orchestrator.parser.ParseFile(file)
	if err != nil {
		orchestrator.logger.Warn(err.Error())
		return nil
	}

	return parsedFile
}

func (orchestrator *Orchestrator) rootItem() *model.Item {
	return orchestrator.parseItem(orchestrator.repository.Root())
}

func (orchestrator *Orchestrator) getItem(route route.Route) *model.Item {
	item, exists := orchestrator.repository.Item(route)
	if !exists {
		return nil
	}

	return orchestrator.parseItem(item)
}

func (orchestrator *Orchestrator) getLatestRoutesByPage(parentRoute route.Route, pageSize, page int) (routes []route.Route, found bool) {

	latestRoutes, found := orchestrator.getLatestRoutes(parentRoute)
	if !found {
		return []route.Route{}, false
	}

	// determine the start index
	startIndex := pageSize * (page - 1)
	if startIndex >= len(latestRoutes) {
		return []route.Route{}, false
	}

	// determine the end index
	endIndex := startIndex + pageSize
	if endIndex > len(latestRoutes) {
		endIndex = len(latestRoutes)
	}

	return latestRoutes[startIndex:endIndex], true
}

func (orchestrator *Orchestrator) getLatestRoutes(parentRoute route.Route) (routes []route.Route, found bool) {

	leafes := orchestrator.getAllLeafes(parentRoute)

	// collect the creation dates for all leafes
	routesAndDates := make([]routeAndDate, 0, len(leafes))
	for _, leaf := range leafes {
		creationDate, found := orchestrator.getCreationDate(leaf)
		if !found {
			// todo: log info
			continue
		}

		routesAndDates = append(routesAndDates, routeAndDate{leaf, creationDate})
	}

	// sort the leafes by date
	SortItemRoutesAndDatesBy(sortRoutesAndDatesDescending).Sort(routesAndDates)

	routes = make([]route.Route, 0)
	for _, routeAndDate := range routesAndDates {
		routes = append(routes, routeAndDate.route)
	}

	return routes, true
}

func (orchestrator *Orchestrator) getAllLeafes(parentRoute route.Route) []route.Route {

	// initialize the leafes map on first use
	if orchestrator.leafesByRoute == nil {
		orchestrator.leafesByRoute = make(map[string][]route.Route)
	}

	// cache lookup
	key := parentRoute.Value()
	if leafes, isset := orchestrator.leafesByRoute[key]; isset {
		return leafes
	}

	childRoutes := make([]route.Route, 0)

	childItems := orchestrator.getChilds(parentRoute)
	if hasNoMoreChilds := len(childItems) == 0; hasNoMoreChilds {
		return []route.Route{parentRoute}
	}

	// recurse
	for _, childItem := range childItems {

		// skip locations
		if childItem.Type == model.TypeLocation {
			continue
		}

		childRoutes = append(childRoutes, orchestrator.getAllLeafes(childItem.Route())...)
	}

	// store the value
	orchestrator.leafesByRoute[key] = childRoutes

	return childRoutes

}

func (orchestrator *Orchestrator) getAllItems() []*model.Item {

	cacheType := "allItems"

	// load from cache
	if orchestrator.items != nil {

		// re-prime the cache if it is stale
		if orchestrator.isCacheStale(cacheType) {
			go orchestrator.primeCache(cacheType)
		}

		return orchestrator.items
	}

	orchestrator.setCache(cacheType, func() {

		allItems := make([]*model.Item, 0)

		for _, repositoryItem := range orchestrator.repository.Items() {
			item := orchestrator.parseItem(repositoryItem)
			if item == nil {
				continue
			}

			allItems = append(allItems, item)
		}

		model.SortItemsBy(sortItemsByDate).Sort(allItems)

		// store to cache
		orchestrator.items = allItems
	})

	return orchestrator.items
}

func (orchestrator *Orchestrator) getItems(pageSize, page int) []*model.Item {

	allItems := orchestrator.getAllItems()

	// determine the start index
	startIndex := pageSize * (page - 1)
	if startIndex >= len(allItems) {
		return []*model.Item{}
	}

	// determine the end index
	endIndex := startIndex + pageSize
	if endIndex > len(allItems) {
		endIndex = len(allItems)
	}

	return allItems[startIndex:endIndex]
}

func (orchestrator *Orchestrator) getCreationDate(itemRoute route.Route) (creationDate time.Time, found bool) {

	item := orchestrator.getItem(itemRoute)
	if item == nil {
		return time.Time{}, false
	}

	return item.MetaData.CreationDate, true
}

func (orchestrator *Orchestrator) getFile(route route.Route) *model.File {
	file, exists := orchestrator.repository.File(route)
	if !exists {
		return nil
	}

	return orchestrator.parseFile(file)
}

func (orchestrator *Orchestrator) getParent(route route.Route) *model.Item {
	parent := orchestrator.repository.Parent(route)
	if parent == nil {
		return nil
	}

	return orchestrator.parseItem(parent)
}

func (orchestrator *Orchestrator) getPrevious(currentRoute route.Route) *model.Item {

	latestRoutes, found := orchestrator.getLatestRoutes(route.New())
	if !found {
		return nil
	}

	// determine the position of the supplied route
	matchingIndex := -1
	for index, route := range latestRoutes {
		if route.Value() == currentRoute.Value() {
			matchingIndex = index
			break
		}
	}

	// abort if the route was not found at all
	if noMatchFound := (matchingIndex == -1); noMatchFound {
		return nil
	}

	// abort if there is no next item
	nextIndex := matchingIndex + 1
	if nextIndex >= len(latestRoutes) {
		return nil
	}

	// determine the next route
	nextRoute := latestRoutes[nextIndex]

	return orchestrator.getItem(nextRoute)
}

func (orchestrator *Orchestrator) getNext(currentRoute route.Route) *model.Item {

	latestRoutes, found := orchestrator.getLatestRoutes(route.New())
	if !found {
		return nil
	}

	// determine the position of the supplied route
	matchingIndex := -1
	for index, route := range latestRoutes {
		if route.Value() == currentRoute.Value() {
			matchingIndex = index
			break
		}
	}

	// abort if the route was not found at all
	if noMatchFound := (matchingIndex == -1); noMatchFound {
		return nil
	}

	// abort if there is no previous item
	previousIndex := matchingIndex - 1
	if noPreviousItem := (previousIndex < 0); noPreviousItem {
		return nil
	}

	// determine the previous route
	previousRoute := latestRoutes[previousIndex]

	return orchestrator.getItem(previousRoute)
}

func (orchestrator *Orchestrator) getChilds(route route.Route) (childs []*model.Item) {

	childs = make([]*model.Item, 0)

	for _, child := range orchestrator.repository.Childs(route) {
		parsed := orchestrator.parseItem(child)
		if parsed == nil {
			continue
		}

		childs = append(childs, parsed)
	}

	return childs
}

// Get the item that has the specified alias. Returns nil if there is no matching item.
func (orchestrator *Orchestrator) getItemByAlias(alias string) *model.Item {

	cacheType := "itembyalias"
	alias = strings.TrimSpace(strings.ToLower(alias))

	// load from cache
	if orchestrator.itemsByAlias != nil {

		// re-prime the cache if it is stale
		if orchestrator.isCacheStale(cacheType) {
			go orchestrator.primeCache(cacheType)
		}

		return orchestrator.itemsByAlias[alias]
	}

	orchestrator.setCache(cacheType, func() {

		itemsByAlias := make(map[string]*model.Item)

		for _, item := range orchestrator.getAllItems() {

			// continue items without an alias
			if item.MetaData.Alias == "" {
				continue
			}

			itemAlias := strings.TrimSpace(strings.ToLower(item.MetaData.Alias))
			itemsByAlias[itemAlias] = item
		}

		orchestrator.itemsByAlias = itemsByAlias
	})

	return orchestrator.itemsByAlias[alias]
}

func (orchestrator *Orchestrator) getAnalyticsSettings() viewmodel.Analytics {
	return viewmodel.Analytics{
		Enabled: orchestrator.config.Analytics.Enabled,
		GoogleAnalytics: viewmodel.GoogleAnalytics{
			Enabled:    orchestrator.config.Analytics.GoogleAnalytics.Enabled,
			TrackingId: orchestrator.config.Analytics.GoogleAnalytics.TrackingId,
		},
	}
}

// sort the models by date and name
func sortItemsByDate(model1, model2 *model.Item) bool {

	return model1.MetaData.CreationDate.Before(model2.MetaData.CreationDate)

}

func sortRoutesAndDatesDescending(itemRoute1, itemRoute2 routeAndDate) bool {
	return itemRoute1.date.After(itemRoute2.date)
}

type routeAndDate struct {
	route route.Route
	date  time.Time
}

type SortItemRoutesAndDatesBy func(itemRoute1, itemRoute2 routeAndDate) bool

func (by SortItemRoutesAndDatesBy) Sort(routesAndDates []routeAndDate) {
	sorter := &routeAndDateSorter{
		routesAndDates: routesAndDates,
		by:             by,
	}

	sort.Sort(sorter)
}

type routeAndDateSorter struct {
	routesAndDates []routeAndDate
	by             SortItemRoutesAndDatesBy
}

func (sorter *routeAndDateSorter) Len() int {
	return len(sorter.routesAndDates)
}

func (sorter *routeAndDateSorter) Swap(i, j int) {
	sorter.routesAndDates[i], sorter.routesAndDates[j] = sorter.routesAndDates[j], sorter.routesAndDates[i]
}

func (sorter *routeAndDateSorter) Less(i, j int) bool {
	return sorter.by(sorter.routesAndDates[i], sorter.routesAndDates[j])
}
<|endoftext|>