<commit_before>package api

import (
	"context"
	"encoding/json"
	"errors"
	log "github.com/Sirupsen/logrus"
	"github.com/gin-gonic/contrib/static"
	"github.com/gin-gonic/gin"
	"github.com/loadimpact/speedboat/lib"
	"github.com/manyminds/api2go/jsonapi"
	"gopkg.in/tylerb/graceful.v1"
	"io/ioutil"
	"net/http"
	// "strconv"
	"time"
)

var contentType = "application/vnd.api+json"

type Server struct {
	Engine *lib.Engine
	Cancel context.CancelFunc

	Info lib.Info
}

// Run runs the API server.
// I'm not sure how idiomatic this is, probably not particularly...
func (s *Server) Run(ctx context.Context, addr string) {
	router := gin.New()

	router.Use(gin.Recovery())
	router.Use(s.logRequestsMiddleware)
	router.Use(s.jsonErrorsMiddleware)

	router.Use(static.Serve("/", static.LocalFile("web/dist", true)))
	router.GET("/ping", func(c *gin.Context) {
		c.Data(http.StatusNoContent, "", nil)
	})
	v1 := router.Group("/v1")
	{
		v1.GET("/info", func(c *gin.Context) {
			data, err := jsonapi.Marshal(s.Info)
			if err != nil {
				c.AbortWithError(500, err)
				return
			}
			c.Data(200, contentType, data)
		})
		v1.GET("/error", func(c *gin.Context) {
			c.AbortWithError(500, errors.New("This is an error"))
		})
		v1.GET("/status", func(c *gin.Context) {
			data, err := jsonapi.Marshal(s.Engine.Status)
			if err != nil {
				c.AbortWithError(500, err)
				return
			}
			c.Data(200, contentType, data)
		})
		v1.GET("/metrics", func(c *gin.Context) {
			metrics := make([]interface{}, 0, len(s.Engine.Metrics))
			for metric, sink := range s.Engine.Metrics {
				metric.Sample = sink.Format()
				metrics = append(metrics, metric)
			}
			data, err := jsonapi.Marshal(metrics)
			if err != nil {
				c.AbortWithError(500, err)
				return
			}
			c.Data(200, contentType, data)
		})
		v1.GET("/metrics/:id", func(c *gin.Context) {
			id := c.Param("id")
			for metric, sink := range s.Engine.Metrics {
				if metric.Name != id {
					continue
				}
				metric.Sample = sink.Format()
				data, err := jsonapi.Marshal(metric)
				if err != nil {
					c.AbortWithError(500, err)
					return
				}
				c.Data(200, contentType, data)
				return
			}
			c.AbortWithError(404, errors.New("Metric not found"))
		})
		// v1.POST("/abort", func(c *gin.Context) {
		// 	s.Cancel()
		// 	c.JSON(202, gin.H{"success": true})
		// })
		// v1.POST("/scale", func(c *gin.Context) {
		// 	vus, err := strconv.ParseInt(c.Query("vus"), 10, 64)
		// 	if err != nil {
		// 		c.AbortWithError(http.StatusBadRequest, err)
		// 		return
		// 	}

		// 	if err := s.Engine.Scale(vus); err != nil {
		// 		c.AbortWithError(http.StatusInternalServerError, err)
		// 		return
		// 	}

		// 	c.JSON(202, gin.H{"success": true})
		// })
	}
	router.NoRoute(func(c *gin.Context) {
		c.JSON(404, gin.H{"error": "Not Found"})
	})

	srv := graceful.Server{NoSignalHandling: true, Server: &http.Server{Addr: addr, Handler: router}}
	go srv.ListenAndServe()

	<-ctx.Done()
	srv.Stop(10 * time.Second)
	<-srv.StopChan()
}

func (s *Server) logRequestsMiddleware(c *gin.Context) {
	path := c.Request.URL.Path
	c.Next()
	log.WithField("status", c.Writer.Status()).Debugf("%s %s", c.Request.Method, path)
}

func (s *Server) jsonErrorsMiddleware(c *gin.Context) {
	c.Header("Content-Type", contentType)
	c.Next()
	if len(c.Errors) > 0 {
		var errors ErrorResponse
		for _, err := range c.Errors {
			errors.Errors = append(errors.Errors, Error{
				Title: err.Error(),
			})
		}
		data, _ := json.Marshal(errors)
		c.Data(c.Writer.Status(), contentType, data)
	}
}
<commit_msg>[feat] HTTP PATCH for /status<commit_after>package api

import (
	"context"
	"encoding/json"
	"errors"
	log "github.com/Sirupsen/logrus"
	"github.com/gin-gonic/contrib/static"
	"github.com/gin-gonic/gin"
	"github.com/loadimpact/speedboat/lib"
	"github.com/manyminds/api2go/jsonapi"
	"gopkg.in/tylerb/graceful.v1"
	"io/ioutil"
	"net/http"
	// "strconv"
	"time"
)

var contentType = "application/vnd.api+json"

type Server struct {
	Engine *lib.Engine
	Cancel context.CancelFunc

	Info lib.Info
}

// Run runs the API server.
// I'm not sure how idiomatic this is, probably not particularly...
func (s *Server) Run(ctx context.Context, addr string) {
	router := gin.New()

	router.Use(gin.Recovery())
	router.Use(s.logRequestsMiddleware)
	router.Use(s.jsonErrorsMiddleware)

	router.Use(static.Serve("/", static.LocalFile("web/dist", true)))
	router.GET("/ping", func(c *gin.Context) {
		c.Data(http.StatusNoContent, "", nil)
	})
	v1 := router.Group("/v1")
	{
		v1.GET("/info", func(c *gin.Context) {
			data, err := jsonapi.Marshal(s.Info)
			if err != nil {
				c.AbortWithError(500, err)
				return
			}
			c.Data(200, contentType, data)
		})
		v1.GET("/error", func(c *gin.Context) {
			c.AbortWithError(500, errors.New("This is an error"))
		})
		v1.GET("/status", func(c *gin.Context) {
			data, err := jsonapi.Marshal(s.Engine.Status)
			if err != nil {
				c.AbortWithError(500, err)
				return
			}
			c.Data(200, contentType, data)
		})
		v1.PATCH("/status", func(c *gin.Context) {
			// TODO: Allow full control of running/active/inactive VUs; stopping a test shouldn't
			// be final, and shouldn't implicitly affect anything else.
			if !s.Engine.Status.Running {
				c.AbortWithError(http.StatusBadRequest, errors.New("Test is stopped"))
				return
			}

			status := s.Engine.Status
			data, _ := ioutil.ReadAll(c.Request.Body)
			if err := jsonapi.Unmarshal(data, &status); err != nil {
				c.AbortWithError(http.StatusBadRequest, err)
				return
			}

			if status.ActiveVUs != s.Engine.Status.ActiveVUs {
				s.Engine.Scale(status.ActiveVUs)
			}
			if !s.Engine.Status.Running {
				s.Cancel()
			}
			s.Engine.Status = status

			data, err := jsonapi.Marshal(s.Engine.Status)
			if err != nil {
				c.AbortWithError(http.StatusInternalServerError, err)
				return
			}
			c.Data(200, contentType, data)
		})
		v1.GET("/metrics", func(c *gin.Context) {
			metrics := make([]interface{}, 0, len(s.Engine.Metrics))
			for metric, sink := range s.Engine.Metrics {
				metric.Sample = sink.Format()
				metrics = append(metrics, metric)
			}
			data, err := jsonapi.Marshal(metrics)
			if err != nil {
				c.AbortWithError(500, err)
				return
			}
			c.Data(200, contentType, data)
		})
		v1.GET("/metrics/:id", func(c *gin.Context) {
			id := c.Param("id")
			for metric, sink := range s.Engine.Metrics {
				if metric.Name != id {
					continue
				}
				metric.Sample = sink.Format()
				data, err := jsonapi.Marshal(metric)
				if err != nil {
					c.AbortWithError(500, err)
					return
				}
				c.Data(200, contentType, data)
				return
			}
			c.AbortWithError(404, errors.New("Metric not found"))
		})
	}
	router.NoRoute(func(c *gin.Context) {
		c.JSON(404, gin.H{"error": "Not Found"})
	})

	srv := graceful.Server{NoSignalHandling: true, Server: &http.Server{Addr: addr, Handler: router}}
	go srv.ListenAndServe()

	<-ctx.Done()
	srv.Stop(10 * time.Second)
	<-srv.StopChan()
}

func (s *Server) logRequestsMiddleware(c *gin.Context) {
	path := c.Request.URL.Path
	c.Next()
	log.WithField("status", c.Writer.Status()).Debugf("%s %s", c.Request.Method, path)
}

func (s *Server) jsonErrorsMiddleware(c *gin.Context) {
	c.Header("Content-Type", contentType)
	c.Next()
	if len(c.Errors) > 0 {
		var errors ErrorResponse
		for _, err := range c.Errors {
			errors.Errors = append(errors.Errors, Error{
				Title: err.Error(),
			})
		}
		data, _ := json.Marshal(errors)
		c.Data(c.Writer.Status(), contentType, data)
	}
}
<|endoftext|>


<commit_before>package lfs

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

type CallbackReader struct {
	C         CopyCallback
	TotalSize int64
	ReadSize  int64
	io.Reader
}

type Platform int

const (
	PlatformWindows      = Platform(iota)
	PlatformLinux        = Platform(iota)
	PlatformOSX          = Platform(iota)
	PlatformOther        = Platform(iota) // most likely a *nix variant e.g. freebsd
	PlatformUndetermined = Platform(iota)
)

var currentPlatform = PlatformUndetermined

type CopyCallback func(totalSize int64, readSoFar int64, readSinceLast int) error

func (w *CallbackReader) Read(p []byte) (int, error) {
	n, err := w.Reader.Read(p)

	if n > 0 {
		w.ReadSize += int64(n)
	}

	if err == nil && w.C != nil {
		err = w.C(w.TotalSize, w.ReadSize, n)
	}

	return n, err
}

func CopyWithCallback(writer io.Writer, reader io.Reader, totalSize int64, cb CopyCallback) (int64, error) {
	if cb == nil {
		return io.Copy(writer, reader)
	}

	cbReader := &CallbackReader{
		C:         cb,
		TotalSize: totalSize,
		Reader:    reader,
	}
	return io.Copy(writer, cbReader)
}

func CopyCallbackFile(event, filename string, index, totalFiles int) (CopyCallback, *os.File, error) {
	logPath := Config.Getenv("GIT_LFS_PROGRESS")
	if len(logPath) == 0 || len(filename) == 0 || len(event) == 0 {
		return nil, nil, nil
	}

	if !filepath.IsAbs(logPath) {
		return nil, nil, fmt.Errorf("GIT_LFS_PROGRESS must be an absolute path")
	}

	cbDir := filepath.Dir(logPath)
	if err := os.MkdirAll(cbDir, 0755); err != nil {
		return nil, nil, wrapProgressError(err, event, logPath)
	}

	file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return nil, file, wrapProgressError(err, event, logPath)
	}

	var prevWritten int64

	cb := CopyCallback(func(total int64, written int64, current int) error {
		if written != prevWritten {
			_, err := file.Write([]byte(fmt.Sprintf("%s %d/%d %d/%d %s\n", event, index, totalFiles, written, total, filename)))
			file.Sync()
			prevWritten = written
			return wrapProgressError(err, event, logPath)
		}

		return nil
	})

	return cb, file, nil
}

func wrapProgressError(err error, event, filename string) error {
	if err != nil {
		return fmt.Errorf("Error writing Git LFS %s progress to %s: %s", event, filename, err.Error())
	}

	return nil
}

// Return whether a given filename passes the include / exclude path filters
// Only paths that are in includePaths and outside excludePaths are passed
// If includePaths is empty that filter always passes and the same with excludePaths
// Both path lists support wildcard matches
func FilenamePassesIncludeExcludeFilter(filename string, includePaths, excludePaths []string) bool {
	if len(includePaths) == 0 && len(excludePaths) == 0 {
		return true
	}

	matchLocalDir := map[string]struct{}{
		".":   {},
		"./":  {},
		".\\": {},
	}
	// For Win32, because git reports files with / separators
	cleanfilename := filepath.Clean(filename)
	if len(includePaths) > 0 {
		matched := false
		for _, inc := range includePaths {
			// Special case local dir, matches all (inc subpaths)
			if _, local := matchLocalDir[inc]; local {
				matched = true
				break
			}
			matched, _ = filepath.Match(inc, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(inc, cleanfilename)
			}
			if !matched {
				// Also support matching a parent directory without a wildcard
				if strings.HasPrefix(cleanfilename, inc+string(filepath.Separator)) {
					matched = true
				}
			}
			if matched {
				break
			}

		}
		if !matched {
			return false
		}
	}

	if len(excludePaths) > 0 {
		for _, ex := range excludePaths {
			// Special case local dir, matches all (inc subpaths)
			if _, local := matchLocalDir[ex]; local {
				return false
			}
			matched, _ := filepath.Match(ex, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(ex, cleanfilename)
			}
			if matched {
				return false
			}
			// Also support matching a parent directory without a wildcard
			if strings.HasPrefix(cleanfilename, ex+string(filepath.Separator)) {
				return false
			}

		}
	}

	return true
}

func GetPlatform() Platform {
	if currentPlatform == PlatformUndetermined {
		switch runtime.GOOS {
		case "windows":
			currentPlatform = PlatformWindows
		case "linux":
			currentPlatform = PlatformLinux
		case "darwin":
			currentPlatform = PlatformOSX
		default:
			currentPlatform = PlatformOther
		}
	}
	return currentPlatform
}

// Convert filenames expressed relative to the root of the repo relative to the
// current working dir. Useful when needing to calling git with results from a rooted command,
// but the user is in a subdir of their repo
// Pass in a channel which you will fill with relative files & receive a channel which will get results
func ConvertRepoFilesRelativeToCwd(repochan <-chan string) (<-chan string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("Unable to get working dir: %v", err)
	}

	// Early-out if working dir is root dir, same result
	passthrough := false
	if LocalWorkingDir == wd {
		passthrough = true
	}

	outchan := make(chan string, 1)

	go func() {
		for f := range repochan {
			if passthrough {
				outchan <- f
				continue
			}
			abs := filepath.Join(LocalWorkingDir, f)
			rel, err := filepath.Rel(wd, abs)
			if err != nil {
				// Use absolute file instead
				outchan <- abs
			} else {
				outchan <- rel
			}
		}
		close(outchan)
	}()

	return outchan, nil
}

// Convert filenames expressed relative to the current directory to be
// relative to the repo root. Useful when calling git with arguments that requires them
// to be rooted but the user is in a subdir of their repo & expects to use relative args
// Pass in a channel which you will fill with relative files & receive a channel which will get results
func ConvertCwdFilesRelativeToRepo(cwdchan <-chan string) (<-chan string, error) {
	curdir, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("Could not retrieve current directory: %v", err)
	}

	// Early-out if working dir is root dir, same result
	passthrough := false
	if LocalWorkingDir == curdir {
		passthrough = true
	}

	outchan := make(chan string, 1)
	go func() {
		for p := range cwdchan {
			if passthrough {
				outchan <- p
				continue
			}
			var abs string
			if filepath.IsAbs(p) {
				abs = p
			} else {
				abs = filepath.Join(curdir, p)
			}
			reltoroot, err := filepath.Rel(LocalWorkingDir, abs)
			if err != nil {
				// Can't do this, use absolute as best fallback
				outchan <- abs
			} else {
				outchan <- reltoroot
			}
		}
		close(outchan)
	}()

	return outchan, nil

}

// Are we running on Windows? Need to handle some extra path shenanigans
func IsWindows() bool {
	return GetPlatform() == PlatformWindows
}
<commit_msg>ンンー ンンンン ンーンン<commit_after>package lfs

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

type CallbackReader struct {
	C         CopyCallback
	TotalSize int64
	ReadSize  int64
	io.Reader
}

type Platform int

const (
	PlatformWindows      = Platform(iota)
	PlatformLinux        = Platform(iota)
	PlatformOSX          = Platform(iota)
	PlatformOther        = Platform(iota) // most likely a *nix variant e.g. freebsd
	PlatformUndetermined = Platform(iota)
)

var currentPlatform = PlatformUndetermined

type CopyCallback func(totalSize int64, readSoFar int64, readSinceLast int) error

func (w *CallbackReader) Read(p []byte) (int, error) {
	n, err := w.Reader.Read(p)

	if n > 0 {
		w.ReadSize += int64(n)
	}

	if err == nil && w.C != nil {
		err = w.C(w.TotalSize, w.ReadSize, n)
	}

	return n, err
}

func CopyWithCallback(writer io.Writer, reader io.Reader, totalSize int64, cb CopyCallback) (int64, error) {
	if cb == nil {
		return io.Copy(writer, reader)
	}

	cbReader := &CallbackReader{
		C:         cb,
		TotalSize: totalSize,
		Reader:    reader,
	}
	return io.Copy(writer, cbReader)
}

func CopyCallbackFile(event, filename string, index, totalFiles int) (CopyCallback, *os.File, error) {
	logPath := Config.Getenv("GIT_LFS_PROGRESS")
	if len(logPath) == 0 || len(filename) == 0 || len(event) == 0 {
		return nil, nil, nil
	}

	if !filepath.IsAbs(logPath) {
		return nil, nil, fmt.Errorf("GIT_LFS_PROGRESS must be an absolute path")
	}

	cbDir := filepath.Dir(logPath)
	if err := os.MkdirAll(cbDir, 0755); err != nil {
		return nil, nil, wrapProgressError(err, event, logPath)
	}

	file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return nil, file, wrapProgressError(err, event, logPath)
	}

	var prevWritten int64

	cb := CopyCallback(func(total int64, written int64, current int) error {
		if written != prevWritten {
			_, err := file.Write([]byte(fmt.Sprintf("%s %d/%d %d/%d %s\n", event, index, totalFiles, written, total, filename)))
			file.Sync()
			prevWritten = written
			return wrapProgressError(err, event, logPath)
		}

		return nil
	})

	return cb, file, nil
}

func wrapProgressError(err error, event, filename string) error {
	if err != nil {
		return fmt.Errorf("Error writing Git LFS %s progress to %s: %s", event, filename, err.Error())
	}

	return nil
}

var localDirSet = map[string]struct{}{
	".":   {},
	"./":  {},
	".\\": {},
}

// Return whether a given filename passes the include / exclude path filters
// Only paths that are in includePaths and outside excludePaths are passed
// If includePaths is empty that filter always passes and the same with excludePaths
// Both path lists support wildcard matches
func FilenamePassesIncludeExcludeFilter(filename string, includePaths, excludePaths []string) bool {
	if len(includePaths) == 0 && len(excludePaths) == 0 {
		return true
	}

	// For Win32, because git reports files with / separators
	cleanfilename := filepath.Clean(filename)
	if len(includePaths) > 0 {
		matched := false
		for _, inc := range includePaths {
			// Special case local dir, matches all (inc subpaths)
			if _, local := localDirSet[inc]; local {
				matched = true
				break
			}
			matched, _ = filepath.Match(inc, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(inc, cleanfilename)
			}
			if !matched {
				// Also support matching a parent directory without a wildcard
				if strings.HasPrefix(cleanfilename, inc+string(filepath.Separator)) {
					matched = true
				}
			}
			if matched {
				break
			}

		}
		if !matched {
			return false
		}
	}

	if len(excludePaths) > 0 {
		for _, ex := range excludePaths {
			// Special case local dir, matches all (inc subpaths)
			if _, local := localDirSet[ex]; local {
				return false
			}
			matched, _ := filepath.Match(ex, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(ex, cleanfilename)
			}
			if matched {
				return false
			}
			// Also support matching a parent directory without a wildcard
			if strings.HasPrefix(cleanfilename, ex+string(filepath.Separator)) {
				return false
			}

		}
	}

	return true
}

func GetPlatform() Platform {
	if currentPlatform == PlatformUndetermined {
		switch runtime.GOOS {
		case "windows":
			currentPlatform = PlatformWindows
		case "linux":
			currentPlatform = PlatformLinux
		case "darwin":
			currentPlatform = PlatformOSX
		default:
			currentPlatform = PlatformOther
		}
	}
	return currentPlatform
}

// Convert filenames expressed relative to the root of the repo relative to the
// current working dir. Useful when needing to calling git with results from a rooted command,
// but the user is in a subdir of their repo
// Pass in a channel which you will fill with relative files & receive a channel which will get results
func ConvertRepoFilesRelativeToCwd(repochan <-chan string) (<-chan string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("Unable to get working dir: %v", err)
	}

	// Early-out if working dir is root dir, same result
	passthrough := false
	if LocalWorkingDir == wd {
		passthrough = true
	}

	outchan := make(chan string, 1)

	go func() {
		for f := range repochan {
			if passthrough {
				outchan <- f
				continue
			}
			abs := filepath.Join(LocalWorkingDir, f)
			rel, err := filepath.Rel(wd, abs)
			if err != nil {
				// Use absolute file instead
				outchan <- abs
			} else {
				outchan <- rel
			}
		}
		close(outchan)
	}()

	return outchan, nil
}

// Convert filenames expressed relative to the current directory to be
// relative to the repo root. Useful when calling git with arguments that requires them
// to be rooted but the user is in a subdir of their repo & expects to use relative args
// Pass in a channel which you will fill with relative files & receive a channel which will get results
func ConvertCwdFilesRelativeToRepo(cwdchan <-chan string) (<-chan string, error) {
	curdir, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("Could not retrieve current directory: %v", err)
	}

	// Early-out if working dir is root dir, same result
	passthrough := false
	if LocalWorkingDir == curdir {
		passthrough = true
	}

	outchan := make(chan string, 1)
	go func() {
		for p := range cwdchan {
			if passthrough {
				outchan <- p
				continue
			}
			var abs string
			if filepath.IsAbs(p) {
				abs = p
			} else {
				abs = filepath.Join(curdir, p)
			}
			reltoroot, err := filepath.Rel(LocalWorkingDir, abs)
			if err != nil {
				// Can't do this, use absolute as best fallback
				outchan <- abs
			} else {
				outchan <- reltoroot
			}
		}
		close(outchan)
	}()

	return outchan, nil

}

// Are we running on Windows? Need to handle some extra path shenanigans
func IsWindows() bool {
	return GetPlatform() == PlatformWindows
}
<|endoftext|>


<commit_before>package lfs

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

type CallbackReader struct {
	C         CopyCallback
	TotalSize int64
	ReadSize  int64
	io.Reader
}

type CopyCallback func(totalSize int64, readSoFar int64, readSinceLast int) error

func (w *CallbackReader) Read(p []byte) (int, error) {
	n, err := w.Reader.Read(p)

	if n > 0 {
		w.ReadSize += int64(n)
	}

	if err == nil && w.C != nil {
		err = w.C(w.TotalSize, w.ReadSize, n)
	}

	return n, err
}

func CopyWithCallback(writer io.Writer, reader io.Reader, totalSize int64, cb CopyCallback) (int64, error) {
	if cb == nil {
		return io.Copy(writer, reader)
	}

	cbReader := &CallbackReader{
		C:         cb,
		TotalSize: totalSize,
		Reader:    reader,
	}
	return io.Copy(writer, cbReader)
}

func CopyCallbackFile(event, filename string, index, totalFiles int) (CopyCallback, *os.File, error) {
	logPath := Config.Getenv("GIT_LFS_PROGRESS")
	if len(logPath) == 0 || len(filename) == 0 || len(event) == 0 {
		return nil, nil, nil
	}

	if !filepath.IsAbs(logPath) {
		return nil, nil, fmt.Errorf("GIT_LFS_PROGRESS must be an absolute path")
	}

	cbDir := filepath.Dir(logPath)
	if err := os.MkdirAll(cbDir, 0755); err != nil {
		return nil, nil, wrapProgressError(err, event, logPath)
	}

	file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return nil, file, wrapProgressError(err, event, logPath)
	}

	var prevWritten int64

	cb := CopyCallback(func(total int64, written int64, current int) error {
		if written != prevWritten {
			_, err := file.Write([]byte(fmt.Sprintf("%s %d/%d %d/%d %s\n", event, index, totalFiles, written, total, filename)))
			file.Sync()
			prevWritten = written
			return wrapProgressError(err, event, logPath)
		}

		return nil
	})

	return cb, file, nil
}

func wrapProgressError(err error, event, filename string) error {
	if err != nil {
		return fmt.Errorf("Error writing Git LFS %s progress to %s: %s", event, filename, err.Error())
	}

	return nil
}

// Return whether a given filename passes the include / exclude path filters
// Only paths that are in includePaths and outside excludePaths are passed
// If includePaths is empty that filter always passes and the same with excludePaths
// Both path lists support wildcard matches
func FilenamePassesIncludeExcludeFilter(filename string, includePaths, excludePaths []string) bool {
	if len(includePaths) == 0 && len(excludePaths) == 0 {
		return true
	}

	// For Win32, becuase git reports files with / separators
	cleanfilename := filepath.Clean(filename)
	if len(includePaths) > 0 {
		matched := false
		for _, inc := range includePaths {
			matched, _ = filepath.Match(inc, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(inc, cleanfilename)
			}
			if !matched {
				// Also support matching a parent directory without a wildcard
				if strings.HasPrefix(cleanfilename, inc+string(filepath.Separator)) {
					matched = true
				}
			}
			if matched {
				break
			}

		}
		if !matched {
			return false
		}
	}

	if len(excludePaths) > 0 {
		for _, ex := range excludePaths {
			matched, _ := filepath.Match(ex, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(ex, cleanfilename)
			}
			if matched {
				return false
			}
			// Also support matching a parent directory without a wildcard
			if strings.HasPrefix(cleanfilename, ex+string(filepath.Separator)) {
				return false
			}

		}
	}

	return true
}

// Are we running on Windows? Need to handle some extra path shenanigans
func IsWindows() bool {
	return runtime.GOOS == "windows"
}
<commit_msg>ララララー ラララー ララ<commit_after>package lfs

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

type CallbackReader struct {
	C         CopyCallback
	TotalSize int64
	ReadSize  int64
	io.Reader
}

type CopyCallback func(totalSize int64, readSoFar int64, readSinceLast int) error

func (w *CallbackReader) Read(p []byte) (int, error) {
	n, err := w.Reader.Read(p)

	if n > 0 {
		w.ReadSize += int64(n)
	}

	if err == nil && w.C != nil {
		err = w.C(w.TotalSize, w.ReadSize, n)
	}

	return n, err
}

func CopyWithCallback(writer io.Writer, reader io.Reader, totalSize int64, cb CopyCallback) (int64, error) {
	if cb == nil {
		return io.Copy(writer, reader)
	}

	cbReader := &CallbackReader{
		C:         cb,
		TotalSize: totalSize,
		Reader:    reader,
	}
	return io.Copy(writer, cbReader)
}

func CopyCallbackFile(event, filename string, index, totalFiles int) (CopyCallback, *os.File, error) {
	logPath := Config.Getenv("GIT_LFS_PROGRESS")
	if len(logPath) == 0 || len(filename) == 0 || len(event) == 0 {
		return nil, nil, nil
	}

	if !filepath.IsAbs(logPath) {
		return nil, nil, fmt.Errorf("GIT_LFS_PROGRESS must be an absolute path")
	}

	cbDir := filepath.Dir(logPath)
	if err := os.MkdirAll(cbDir, 0755); err != nil {
		return nil, nil, wrapProgressError(err, event, logPath)
	}

	file, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return nil, file, wrapProgressError(err, event, logPath)
	}

	var prevWritten int64

	cb := CopyCallback(func(total int64, written int64, current int) error {
		if written != prevWritten {
			_, err := file.Write([]byte(fmt.Sprintf("%s %d/%d %d/%d %s\n", event, index, totalFiles, written, total, filename)))
			file.Sync()
			prevWritten = written
			return wrapProgressError(err, event, logPath)
		}

		return nil
	})

	return cb, file, nil
}

func wrapProgressError(err error, event, filename string) error {
	if err != nil {
		return fmt.Errorf("Error writing Git LFS %s progress to %s: %s", event, filename, err.Error())
	}

	return nil
}

// Return whether a given filename passes the include / exclude path filters
// Only paths that are in includePaths and outside excludePaths are passed
// If includePaths is empty that filter always passes and the same with excludePaths
// Both path lists support wildcard matches
func FilenamePassesIncludeExcludeFilter(filename string, includePaths, excludePaths []string) bool {
	if len(includePaths) == 0 && len(excludePaths) == 0 {
		return true
	}

	// For Win32, because git reports files with / separators
	cleanfilename := filepath.Clean(filename)
	if len(includePaths) > 0 {
		matched := false
		for _, inc := range includePaths {
			matched, _ = filepath.Match(inc, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(inc, cleanfilename)
			}
			if !matched {
				// Also support matching a parent directory without a wildcard
				if strings.HasPrefix(cleanfilename, inc+string(filepath.Separator)) {
					matched = true
				}
			}
			if matched {
				break
			}

		}
		if !matched {
			return false
		}
	}

	if len(excludePaths) > 0 {
		for _, ex := range excludePaths {
			matched, _ := filepath.Match(ex, filename)
			if !matched && IsWindows() {
				// Also Win32 match
				matched, _ = filepath.Match(ex, cleanfilename)
			}
			if matched {
				return false
			}
			// Also support matching a parent directory without a wildcard
			if strings.HasPrefix(cleanfilename, ex+string(filepath.Separator)) {
				return false
			}

		}
	}

	return true
}

// Are we running on Windows? Need to handle some extra path shenanigans
func IsWindows() bool {
	return runtime.GOOS == "windows"
}
<|endoftext|>


<commit_before>package git

/*
#include <git2.h>
#include <git2/errors.h>

extern git_merge_head** _go_git_make_merge_head_array(size_t len);
extern void _go_git_merge_head_array_set(git_merge_head** array, git_merge_head* ptr, size_t n);
extern git_merge_head* _go_git_merge_head_array_get(git_merge_head** array, size_t n);

*/
import "C"
import (
	"runtime"
	"unsafe"
)

type MergeHead struct {
	ptr *C.git_merge_head
}

func newMergeHeadFromC(c *C.git_merge_head) *MergeHead {
	mh := &MergeHead{ptr: c}
	runtime.SetFinalizer(mh, (*MergeHead).Free)
	return mh
}

func (mh *MergeHead) Free() {
	C.git_merge_head_free(mh.ptr)
	runtime.SetFinalizer(mh, nil)
}

func (r *Repository) MergeHeadFromFetchHead(branchName string, remoteURL string, oid *Oid) (*MergeHead, error) {
	mh := &MergeHead{}

	cbranchName := C.CString(branchName)
	defer C.free(unsafe.Pointer(cbranchName))

	cremoteURL := C.CString(remoteURL)
	defer C.free(unsafe.Pointer(cremoteURL))

	ret := C.git_merge_head_from_fetchhead(&mh.ptr, r.ptr, cbranchName, cremoteURL, oid.toC())
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	runtime.SetFinalizer(mh, (*MergeHead).Free)
	return mh, nil
}

func (r *Repository) MergeHeadFromId(oid *Oid) (*MergeHead, error) {
	mh := &MergeHead{}

	ret := C.git_merge_head_from_id(&mh.ptr, r.ptr, oid.toC())
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	runtime.SetFinalizer(mh, (*MergeHead).Free)
	return mh, nil
}

func (r *Repository) MergeHeadFromRef(ref *Reference) (*MergeHead, error) {
	mh := &MergeHead{}

	ret := C.git_merge_head_from_ref(&mh.ptr, r.ptr, ref.ptr)
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	runtime.SetFinalizer(mh, (*MergeHead).Free)
	return mh, nil
}

type MergeFlag int

const (
	MergeFlagDefault MergeFlag = iota
	MergeNoFastForward
	MergeFastForwardOnly
)

type MergeOptions struct {
	Version uint
	Flags   MergeFlag

	TreeOptions MergeTreeOptions
	//TODO: CheckoutOptions CheckoutOptions
}

func (mo *MergeOptions) toC() *C.git_merge_opts {
	return &C.git_merge_opts{
		version:         C.uint(mo.Version),
		merge_flags:     C.git_merge_flags_t(mo.Flags),
		merge_tree_opts: *mo.TreeOptions.toC(),
	}
}

type MergeTreeFlag int

const (
	MergeTreeFindRenames MergeTreeFlag = 1 << iota
)

type MergeFileFavorType int

const (
	MergeFileFavorNormal MergeFileFavorType = iota
	MergeFileFavorOurs
	MergeFileFavorTheirs
	MergeFileFavorUnion
)

type MergeTreeOptions struct {
	Version         uint
	Flags           MergeTreeFlag
	RenameThreshold uint
	TargetLimit     uint
	//TODO: SimilarityMetric *DiffSimilarityMetric
	FileFavor MergeFileFavorType
}

func (mo *MergeTreeOptions) toC() *C.git_merge_tree_opts {
	return &C.git_merge_tree_opts{
		version:          C.uint(mo.Version),
		flags:            C.git_merge_tree_flag_t(mo.Flags),
		rename_threshold: C.uint(mo.RenameThreshold),
		target_limit:     C.uint(mo.TargetLimit),
		file_favor:       C.git_merge_file_favor_t(mo.FileFavor),
	}
}

type MergeResult struct {
	ptr *C.git_merge_result
}

func newMergeResultFromC(c *C.git_merge_result) *MergeResult {
	mr := &MergeResult{ptr: c}
	runtime.SetFinalizer(mr, (*MergeResult).Free)
	return mr
}

func (mr *MergeResult) Free() {
	runtime.SetFinalizer(mr, nil)
	C.git_merge_result_free(mr.ptr)
}

func (mr *MergeResult) IsFastForward() bool {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	ret := C.git_merge_result_is_fastforward(mr.ptr)
	return ret != 0
}

func (mr *MergeResult) IsUpToDate() bool {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	ret := C.git_merge_result_is_uptodate(mr.ptr)
	return ret != 0
}

func (mr *MergeResult) FastForwardId() (*Oid, error) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	var oid C.git_oid
	ret := C.git_merge_result_fastforward_id(&oid, mr.ptr)
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	return newOidFromC(&oid), nil
}

func (r *Repository) Merge(theirHeads []MergeHead, options MergeOptions) (*MergeResult, error) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	var result *C.git_merge_result

	copts := options.toC()

	cmerge_head_array := C._go_git_make_merge_head_array(C.size_t(len(theirHeads)))
	defer C.free(unsafe.Pointer(cmerge_head_array))

	for i, _ := range theirHeads {
		C._go_git_merge_head_array_set(cmerge_head_array, theirHeads[i].ptr, C.size_t(i))
	}

	err := C.git_merge(&result, r.ptr, cmerge_head_array, C.size_t(len(theirHeads)), copts)
	if err < 0 {
		return nil, MakeGitError(err)
	}
	return newMergeResultFromC(result), nil
}

func (r *Repository) MergeCommits(ours *Commit, theirs *Commit, options MergeTreeOptions) (*Index, error) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	copts := options.toC()

	idx := &Index{}

	ret := C.git_merge_commits(&idx.ptr, r.ptr, ours.ptr, theirs.ptr, copts)
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	runtime.SetFinalizer(idx, (*Index).Free)
	return idx, nil
}

func (r *Repository) MergeTrees(ancestor *Tree, ours *Tree, theirs *Tree, options MergeTreeOptions) (*Index, error) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	copts := options.toC()

	idx := &Index{}

	ret := C.git_merge_trees(&idx.ptr, r.ptr, ancestor.ptr, ours.ptr, theirs.ptr, copts)
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	runtime.SetFinalizer(idx, (*Index).Free)
	return idx, nil
}

func (r *Repository) MergeBase(one *Oid, two *Oid) (*Oid, error) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	var oid C.git_oid
	ret := C.git_merge_base(&oid, r.ptr, one.toC(), two.toC())
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	return newOidFromC(&oid), nil
}

//TODO: int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]);
<commit_msg>switch from iota to explicit def<commit_after>package git

/*
#include <git2.h>
#include <git2/errors.h>

extern git_merge_head** _go_git_make_merge_head_array(size_t len);
extern void _go_git_merge_head_array_set(git_merge_head** array, git_merge_head* ptr, size_t n);
extern git_merge_head* _go_git_merge_head_array_get(git_merge_head** array, size_t n);

*/
import "C"
import (
	"runtime"
	"unsafe"
)

type MergeHead struct {
	ptr *C.git_merge_head
}

func newMergeHeadFromC(c *C.git_merge_head) *MergeHead {
	mh := &MergeHead{ptr: c}
	runtime.SetFinalizer(mh, (*MergeHead).Free)
	return mh
}

func (mh *MergeHead) Free() {
	C.git_merge_head_free(mh.ptr)
	runtime.SetFinalizer(mh, nil)
}

func (r *Repository) MergeHeadFromFetchHead(branchName string, remoteURL string, oid *Oid) (*MergeHead, error) {
	mh := &MergeHead{}

	cbranchName := C.CString(branchName)
	defer C.free(unsafe.Pointer(cbranchName))

	cremoteURL := C.CString(remoteURL)
	defer C.free(unsafe.Pointer(cremoteURL))

	ret := C.git_merge_head_from_fetchhead(&mh.ptr, r.ptr, cbranchName, cremoteURL, oid.toC())
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	runtime.SetFinalizer(mh, (*MergeHead).Free)
	return mh, nil
}

func (r *Repository) MergeHeadFromId(oid *Oid) (*MergeHead, error) {
	mh := &MergeHead{}

	ret := C.git_merge_head_from_id(&mh.ptr, r.ptr, oid.toC())
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	runtime.SetFinalizer(mh, (*MergeHead).Free)
	return mh, nil
}

func (r *Repository) MergeHeadFromRef(ref *Reference) (*MergeHead, error) {
	mh := &MergeHead{}

	ret := C.git_merge_head_from_ref(&mh.ptr, r.ptr, ref.ptr)
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	runtime.SetFinalizer(mh, (*MergeHead).Free)
	return mh, nil
}

type MergeFlag int

const (
	MergeFlagDefault     MergeFlag = 0
	MergeNoFastForward             = 1
	MergeFastForwardOnly           = 2
)

type MergeOptions struct {
	Version uint
	Flags   MergeFlag

	TreeOptions MergeTreeOptions
	//TODO: CheckoutOptions CheckoutOptions
}

func (mo *MergeOptions) toC() *C.git_merge_opts {
	return &C.git_merge_opts{
		version:         C.uint(mo.Version),
		merge_flags:     C.git_merge_flags_t(mo.Flags),
		merge_tree_opts: *mo.TreeOptions.toC(),
	}
}

type MergeTreeFlag int

const (
	MergeTreeFindRenames MergeTreeFlag = 1 << 0
)

type MergeFileFavorType int

const (
	MergeFileFavorNormal MergeFileFavorType = 0
	MergeFileFavorOurs                      = 1
	MergeFileFavorTheirs                    = 2
	MergeFileFavorUnion                     = 3
)

type MergeTreeOptions struct {
	Version         uint
	Flags           MergeTreeFlag
	RenameThreshold uint
	TargetLimit     uint
	//TODO: SimilarityMetric *DiffSimilarityMetric
	FileFavor MergeFileFavorType
}

func (mo *MergeTreeOptions) toC() *C.git_merge_tree_opts {
	return &C.git_merge_tree_opts{
		version:          C.uint(mo.Version),
		flags:            C.git_merge_tree_flag_t(mo.Flags),
		rename_threshold: C.uint(mo.RenameThreshold),
		target_limit:     C.uint(mo.TargetLimit),
		file_favor:       C.git_merge_file_favor_t(mo.FileFavor),
	}
}

type MergeResult struct {
	ptr *C.git_merge_result
}

func newMergeResultFromC(c *C.git_merge_result) *MergeResult {
	mr := &MergeResult{ptr: c}
	runtime.SetFinalizer(mr, (*MergeResult).Free)
	return mr
}

func (mr *MergeResult) Free() {
	runtime.SetFinalizer(mr, nil)
	C.git_merge_result_free(mr.ptr)
}

func (mr *MergeResult) IsFastForward() bool {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	ret := C.git_merge_result_is_fastforward(mr.ptr)
	return ret != 0
}

func (mr *MergeResult) IsUpToDate() bool {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	ret := C.git_merge_result_is_uptodate(mr.ptr)
	return ret != 0
}

func (mr *MergeResult) FastForwardId() (*Oid, error) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	var oid C.git_oid
	ret := C.git_merge_result_fastforward_id(&oid, mr.ptr)
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	return newOidFromC(&oid), nil
}

func (r *Repository) Merge(theirHeads []MergeHead, options MergeOptions) (*MergeResult, error) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	var result *C.git_merge_result

	copts := options.toC()

	cmerge_head_array := C._go_git_make_merge_head_array(C.size_t(len(theirHeads)))
	defer C.free(unsafe.Pointer(cmerge_head_array))

	for i, _ := range theirHeads {
		C._go_git_merge_head_array_set(cmerge_head_array, theirHeads[i].ptr, C.size_t(i))
	}

	err := C.git_merge(&result, r.ptr, cmerge_head_array, C.size_t(len(theirHeads)), copts)
	if err < 0 {
		return nil, MakeGitError(err)
	}
	return newMergeResultFromC(result), nil
}

func (r *Repository) MergeCommits(ours *Commit, theirs *Commit, options MergeTreeOptions) (*Index, error) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	copts := options.toC()

	idx := &Index{}

	ret := C.git_merge_commits(&idx.ptr, r.ptr, ours.ptr, theirs.ptr, copts)
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	runtime.SetFinalizer(idx, (*Index).Free)
	return idx, nil
}

func (r *Repository) MergeTrees(ancestor *Tree, ours *Tree, theirs *Tree, options MergeTreeOptions) (*Index, error) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	copts := options.toC()

	idx := &Index{}

	ret := C.git_merge_trees(&idx.ptr, r.ptr, ancestor.ptr, ours.ptr, theirs.ptr, copts)
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	runtime.SetFinalizer(idx, (*Index).Free)
	return idx, nil
}

func (r *Repository) MergeBase(one *Oid, two *Oid) (*Oid, error) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	var oid C.git_oid
	ret := C.git_merge_base(&oid, r.ptr, one.toC(), two.toC())
	if ret < 0 {
		return nil, MakeGitError(ret)
	}
	return newOidFromC(&oid), nil
}

//TODO: int git_merge_base_many(git_oid *out, git_repository *repo, size_t length, const git_oid input_array[]);
<|endoftext|>


<commit_before>package static

import (
	"net/http"

	"github.com/coffeehc/web"
)

func RegisterStaticFilter(server *web._Server, uriPattern string, staticDir string) http.Handler {
	lastChar := uriPattern[len(uriPattern)-1]
	if lastChar != '*' {
		if lastChar != '/' {
			uriPattern += "/"
		}
		uriPattern = uriPattern + "*"
	}
	handler := http.StripPrefix(string(uriPattern[:len(uriPattern)-1]), http.FileServer(http.Dir(staticDir)))
	server.AddLastFilter(uriPattern, func(request *http.Request, reply web.Reply, chain web.FilterChain) {
		reply.AdapterHttpHandler(true)
		handler.ServeHTTP(reply.GetResponseWriter(), request)
	})
	return handler
}
<commit_msg>fix static_filter<commit_after>package static

import (
	"net/http"

	"github.com/coffeehc/web"
)

func RegisterStaticFilter(server web.HttpServer, uriPattern string, staticDir string) http.Handler {
	lastChar := uriPattern[len(uriPattern)-1]
	if lastChar != '*' {
		if lastChar != '/' {
			uriPattern += "/"
		}
		uriPattern = uriPattern + "*"
	}
	handler := http.StripPrefix(string(uriPattern[:len(uriPattern)-1]), http.FileServer(http.Dir(staticDir)))
	server.AddLastFilter(uriPattern, func(reply web.Reply, chain web.FilterChain) {
		reply.AdapterHttpHandler(true)
		handler.ServeHTTP(reply.GetResponseWriter(), reply.GetRequest())
	})
	return handler
}
<|endoftext|>


<commit_before>package average

import (
	"fmt"

	"github.com/catorpilor/leetcode/utils"
)

func MaxAverage(nums []int, k int) float64 {
	if len(nums) < k || k == 0 {
		return float64(0)
	}
	var prev, cur, maxSofar int
	for i := 0; i < k; i++ {
		prev += nums[i]
	}
	maxSofar = prev
	for i := 1; i < len(nums)-k+1; i++ {
		cur = prev - nums[i-1] + nums[i+k-1]
		maxSofar = utils.Max(cur, maxSofar)
		prev = cur
	}
	fmt.Println(maxSofar)
	return float64(maxSofar) / float64(k)
}

func MaxAverage2(nums []int, k int) float64 {
	// cumulative sum array
	// sums[i] stores the sum of the elements of the
	// given nums array from 0 to ith index
	if len(nums) < k || k == 0 {
		return float64(0)
	}
	sums := make([]int, len(nums))
	sums[0] = nums[0]
	for i := 1; i < len(sums); i++ {
		sums[i] = sums[i-1] + nums[i]
	}
	ret := sums[k-1]
	for i := k; i < len(sums); i++ {
		ret = utils.Max(ret, sums[i]-sums[i-k])
	}
	return float64(ret) / float64(k)
}

func MaxAverage3(nums []int, k int) float64 {
	// sliding window
	// same as MaxAverage
	if len(nums) < k || k == 0 {
		return float64(0)
	}
	var cur, maxSofar int
	for i := 0; i < k; i++ {
		cur += nums[i]
	}
	maxSofar = cur
	for i := k; i < len(nums); i++ {
		cur += nums[i] - nums[i-k]
		maxSofar = utils.Max(cur, maxSofar)
	}
	return float64(maxSofar) / float64(k)
}
<commit_msg>some modifications<commit_after>package average

import (
	"github.com/catorpilor/leetcode/utils"
)

func MaxAverage(nums []int, k int) float64 {
	if len(nums) < k || k == 0 {
		return float64(0)
	}
	var prev, cur, maxSofar int
	for i := 0; i < k; i++ {
		prev += nums[i]
	}
	maxSofar = prev
	for i := 1; i < len(nums)-k+1; i++ {
		cur = prev - nums[i-1] + nums[i+k-1]
		maxSofar = utils.Max(cur, maxSofar)
		prev = cur
	}
	return float64(maxSofar) / float64(k)
}

func MaxAverage2(nums []int, k int) float64 {
	// cumulative sum array
	// sums[i] stores the sum of the elements of the
	// given nums array from 0 to ith index
	if len(nums) < k || k == 0 {
		return float64(0)
	}
	sums := make([]int, len(nums))
	sums[0] = nums[0]
	for i := 1; i < len(sums); i++ {
		sums[i] = sums[i-1] + nums[i]
	}
	ret := sums[k-1]
	for i := k; i < len(sums); i++ {
		ret = utils.Max(ret, sums[i]-sums[i-k])
	}
	return float64(ret) / float64(k)
}

func MaxAverage3(nums []int, k int) float64 {
	// sliding window
	// same as MaxAverage
	if len(nums) < k || k == 0 {
		return float64(0)
	}
	var cur, maxSofar int
	for i := 0; i < k; i++ {
		cur += nums[i]
	}
	maxSofar = cur
	for i := k; i < len(nums); i++ {
		cur += nums[i] - nums[i-k]
		maxSofar = utils.Max(cur, maxSofar)
	}
	return float64(maxSofar) / float64(k)
}
<|endoftext|>


<commit_before>package api

import (
	"log"
	"net"
	"net/rpc"
	"time"

	"github.com/open-falcon/graph/g"
)

var Close_chan, Close_done_chan chan int

func init() {
	Close_chan = make(chan int, 1)
	Close_done_chan = make(chan int, 1)
}

func Start() {
	if !g.Config().Rpc.Enabled {
		return
	}
	addr := g.Config().Rpc.Listen
	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		log.Fatalf("net.ResolveTCPAddr fail: %s", err)
	}

	listener, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		log.Fatalf("listen %s fail: %s", addr, err)
	} else {
		log.Println("rpc listening", addr)
	}

	rpc.Register(new(Graph))

	go func() {
		var tempDelay time.Duration // how long to sleep on accept failure
		for {
			conn, err := listener.Accept()
			if err != nil {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				time.Sleep(tempDelay)
				continue
			}
			tempDelay = 0
			go rpc.ServeConn(conn)
		}
	}()

	select {
	case <-Close_chan:
		log.Println("api recv sigout and exit...")
		listener.Close()
		Close_done_chan <- 1
		return
	}

}
<commit_msg> close established connect after recv SIGQUIT<commit_after>package api

import (
	"container/list"
	"log"
	"net"
	"net/rpc"
	"sync"
	"time"

	"github.com/open-falcon/graph/g"
)

type conn_list struct {
	sync.RWMutex
	list *list.List
}

func (l *conn_list) insert(c net.Conn) *list.Element {
	l.Lock()
	defer l.Unlock()
	return l.list.PushBack(c)
}
func (l *conn_list) remove(e *list.Element) net.Conn {
	l.Lock()
	defer l.Unlock()
	return l.list.Remove(e).(net.Conn)
}

var Close_chan, Close_done_chan chan int
var connects conn_list

func init() {
	Close_chan = make(chan int, 1)
	Close_done_chan = make(chan int, 1)
	connects = conn_list{list: list.New()}
}

func Start() {
	if !g.Config().Rpc.Enabled {
		return
	}
	addr := g.Config().Rpc.Listen
	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		log.Fatalf("net.ResolveTCPAddr fail: %s", err)
	}

	listener, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		log.Fatalf("listen %s fail: %s", addr, err)
	} else {
		log.Println("rpc listening", addr)
	}

	rpc.Register(new(Graph))

	go func() {
		var tempDelay time.Duration // how long to sleep on accept failure
		for {
			conn, err := listener.Accept()
			if err != nil {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}
				time.Sleep(tempDelay)
				continue
			}
			tempDelay = 0
			go func() {
				e := connects.insert(conn)
				defer connects.remove(e)
				rpc.ServeConn(conn)
			}()
		}
	}()

	select {
	case <-Close_chan:
		log.Println("api recv sigout and exit...")
		listener.Close()
		Close_done_chan <- 1

		connects.Lock()
		for e := connects.list.Front(); e != nil; e = e.Next() {
			e.Value.(net.Conn).Close()
		}
		connects.Unlock()

		return
	}

}
<|endoftext|>


<commit_before>// Copyright 2017 Vector Creations Ltd
//
// 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 queue

import (
	"context"
	"crypto/ed25519"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/matrix-org/dendrite/federationsender/statistics"
	"github.com/matrix-org/dendrite/federationsender/storage"
	"github.com/matrix-org/dendrite/federationsender/storage/shared"
	"github.com/matrix-org/dendrite/roomserver/api"
	"github.com/matrix-org/dendrite/setup/process"
	"github.com/matrix-org/gomatrixserverlib"
	"github.com/prometheus/client_golang/prometheus"
	log "github.com/sirupsen/logrus"
	"github.com/tidwall/gjson"
)

// OutgoingQueues is a collection of queues for sending transactions to other
// matrix servers
type OutgoingQueues struct {
	db          storage.Database
	process     *process.ProcessContext
	disabled    bool
	rsAPI       api.RoomserverInternalAPI
	origin      gomatrixserverlib.ServerName
	client      *gomatrixserverlib.FederationClient
	statistics  *statistics.Statistics
	signing     *SigningInfo
	queuesMutex sync.Mutex // protects the below
	queues      map[gomatrixserverlib.ServerName]*destinationQueue
}

func init() {
	prometheus.MustRegister(
		destinationQueueTotal, destinationQueueRunning,
		destinationQueueBackingOff,
	)
}

var destinationQueueTotal = prometheus.NewGauge(
	prometheus.GaugeOpts{
		Namespace: "dendrite",
		Subsystem: "federationsender",
		Name:      "destination_queues_total",
	},
)

var destinationQueueRunning = prometheus.NewGauge(
	prometheus.GaugeOpts{
		Namespace: "dendrite",
		Subsystem: "federationsender",
		Name:      "destination_queues_running",
	},
)

var destinationQueueBackingOff = prometheus.NewGauge(
	prometheus.GaugeOpts{
		Namespace: "dendrite",
		Subsystem: "federationsender",
		Name:      "destination_queues_backing_off",
	},
)

// NewOutgoingQueues makes a new OutgoingQueues
func NewOutgoingQueues(
	db storage.Database,
	process *process.ProcessContext,
	disabled bool,
	origin gomatrixserverlib.ServerName,
	client *gomatrixserverlib.FederationClient,
	rsAPI api.RoomserverInternalAPI,
	statistics *statistics.Statistics,
	signing *SigningInfo,
) *OutgoingQueues {
	queues := &OutgoingQueues{
		disabled:   disabled,
		process:    process,
		db:         db,
		rsAPI:      rsAPI,
		origin:     origin,
		client:     client,
		statistics: statistics,
		signing:    signing,
		queues:     map[gomatrixserverlib.ServerName]*destinationQueue{},
	}
	// Look up which servers we have pending items for and then rehydrate those queues.
	if !disabled {
		time.AfterFunc(time.Second*5, func() {
			serverNames := map[gomatrixserverlib.ServerName]struct{}{}
			if names, err := db.GetPendingPDUServerNames(context.Background()); err == nil {
				for _, serverName := range names {
					serverNames[serverName] = struct{}{}
				}
			} else {
				log.WithError(err).Error("Failed to get PDU server names for destination queue hydration")
			}
			if names, err := db.GetPendingEDUServerNames(context.Background()); err == nil {
				for _, serverName := range names {
					serverNames[serverName] = struct{}{}
				}
			} else {
				log.WithError(err).Error("Failed to get EDU server names for destination queue hydration")
			}
			for serverName := range serverNames {
				if queue := queues.getQueue(serverName); queue != nil {
					queue.wakeQueueIfNeeded()
				}
			}
		})
	}
	return queues
}

// TODO: Move this somewhere useful for other components as we often need to ferry these 3 variables
// around together
type SigningInfo struct {
	ServerName gomatrixserverlib.ServerName
	KeyID      gomatrixserverlib.KeyID
	PrivateKey ed25519.PrivateKey
}

type queuedPDU struct {
	receipt *shared.Receipt
	pdu     *gomatrixserverlib.HeaderedEvent
}

type queuedEDU struct {
	receipt *shared.Receipt
	edu     *gomatrixserverlib.EDU
}

func (oqs *OutgoingQueues) getQueue(destination gomatrixserverlib.ServerName) *destinationQueue {
	if oqs.statistics.ForServer(destination).Blacklisted() {
		return nil
	}
	oqs.queuesMutex.Lock()
	defer oqs.queuesMutex.Unlock()
	oq, ok := oqs.queues[destination]
	if !ok {
		destinationQueueTotal.Inc()
		oq = &destinationQueue{
			queues:           oqs,
			db:               oqs.db,
			process:          oqs.process,
			rsAPI:            oqs.rsAPI,
			origin:           oqs.origin,
			destination:      destination,
			client:           oqs.client,
			statistics:       oqs.statistics.ForServer(destination),
			notify:           make(chan struct{}, 1),
			interruptBackoff: make(chan bool),
			signing:          oqs.signing,
		}
		oqs.queues[destination] = oq
	}
	return oq
}

func (oqs *OutgoingQueues) clearQueue(oq *destinationQueue) {
	oqs.queuesMutex.Lock()
	defer oqs.queuesMutex.Unlock()

	close(oq.notify)
	close(oq.interruptBackoff)
	delete(oqs.queues, oq.destination)
	destinationQueueTotal.Dec()
}

type ErrorFederationDisabled struct {
	Message string
}

func (e *ErrorFederationDisabled) Error() string {
	return e.Message
}

// SendEvent sends an event to the destinations
func (oqs *OutgoingQueues) SendEvent(
	ev *gomatrixserverlib.HeaderedEvent, origin gomatrixserverlib.ServerName,
	destinations []gomatrixserverlib.ServerName,
) error {
	if oqs.disabled {
		return &ErrorFederationDisabled{
			Message: "Federation disabled",
		}
	}
	if origin != oqs.origin {
		// TODO: Support virtual hosting; gh issue #577.
		return fmt.Errorf(
			"sendevent: unexpected server to send as: got %q expected %q",
			origin, oqs.origin,
		)
	}

	// Deduplicate destinations and remove the origin from the list of
	// destinations just to be sure.
	destmap := map[gomatrixserverlib.ServerName]struct{}{}
	for _, d := range destinations {
		destmap[d] = struct{}{}
	}
	delete(destmap, oqs.origin)

	// Check if any of the destinations are prohibited by server ACLs.
	for destination := range destmap {
		if api.IsServerBannedFromRoom(
			context.TODO(),
			oqs.rsAPI,
			ev.RoomID(),
			destination,
		) {
			delete(destmap, destination)
		}
	}

	// If there are no remaining destinations then give up.
	if len(destmap) == 0 {
		return nil
	}

	log.WithFields(log.Fields{
		"destinations": len(destmap), "event": ev.EventID(),
	}).Infof("Sending event")

	headeredJSON, err := json.Marshal(ev)
	if err != nil {
		return fmt.Errorf("json.Marshal: %w", err)
	}

	nid, err := oqs.db.StoreJSON(context.TODO(), string(headeredJSON))
	if err != nil {
		return fmt.Errorf("sendevent: oqs.db.StoreJSON: %w", err)
	}

	for destination := range destmap {
		if queue := oqs.getQueue(destination); queue != nil {
			queue.sendEvent(ev, nid)
		}
	}

	return nil
}

// SendEDU sends an EDU event to the destinations.
func (oqs *OutgoingQueues) SendEDU(
	e *gomatrixserverlib.EDU, origin gomatrixserverlib.ServerName,
	destinations []gomatrixserverlib.ServerName,
) error {
	if oqs.disabled {
		return &ErrorFederationDisabled{
			Message: "Federation disabled",
		}
	}
	if origin != oqs.origin {
		// TODO: Support virtual hosting; gh issue #577.
		return fmt.Errorf(
			"sendevent: unexpected server to send as: got %q expected %q",
			origin, oqs.origin,
		)
	}

	// Deduplicate destinations and remove the origin from the list of
	// destinations just to be sure.
	destmap := map[gomatrixserverlib.ServerName]struct{}{}
	for _, d := range destinations {
		destmap[d] = struct{}{}
	}
	delete(destmap, oqs.origin)

	// There is absolutely no guarantee that the EDU will have a room_id
	// field, as it is not required by the spec. However, if it *does*
	// (e.g. typing notifications) then we should try to make sure we don't
	// bother sending them to servers that are prohibited by the server
	// ACLs.
	if result := gjson.GetBytes(e.Content, "room_id"); result.Exists() {
		for destination := range destmap {
			if api.IsServerBannedFromRoom(
				context.TODO(),
				oqs.rsAPI,
				result.Str,
				destination,
			) {
				delete(destmap, destination)
			}
		}
	}

	// If there are no remaining destinations then give up.
	if len(destmap) == 0 {
		return nil
	}

	log.WithFields(log.Fields{
		"destinations": len(destmap), "edu_type": e.Type,
	}).Info("Sending EDU event")

	ephemeralJSON, err := json.Marshal(e)
	if err != nil {
		return fmt.Errorf("json.Marshal: %w", err)
	}

	nid, err := oqs.db.StoreJSON(context.TODO(), string(ephemeralJSON))
	if err != nil {
		return fmt.Errorf("sendevent: oqs.db.StoreJSON: %w", err)
	}

	for destination := range destmap {
		if queue := oqs.getQueue(destination); queue != nil {
			queue.sendEDU(e, nid)
		}
	}

	return nil
}

// RetryServer attempts to resend events to the given server if we had given up.
func (oqs *OutgoingQueues) RetryServer(srv gomatrixserverlib.ServerName) {
	if oqs.disabled {
		return
	}
	if queue := oqs.getQueue(srv); queue != nil {
		queue.wakeQueueIfNeeded()
	}
}
<commit_msg>Don't close channels when clearing queue (we might race and panic, when the GC will still clean it up for us anyway)<commit_after>// Copyright 2017 Vector Creations Ltd
//
// 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 queue

import (
	"context"
	"crypto/ed25519"
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/matrix-org/dendrite/federationsender/statistics"
	"github.com/matrix-org/dendrite/federationsender/storage"
	"github.com/matrix-org/dendrite/federationsender/storage/shared"
	"github.com/matrix-org/dendrite/roomserver/api"
	"github.com/matrix-org/dendrite/setup/process"
	"github.com/matrix-org/gomatrixserverlib"
	"github.com/prometheus/client_golang/prometheus"
	log "github.com/sirupsen/logrus"
	"github.com/tidwall/gjson"
)

// OutgoingQueues is a collection of queues for sending transactions to other
// matrix servers
type OutgoingQueues struct {
	db          storage.Database
	process     *process.ProcessContext
	disabled    bool
	rsAPI       api.RoomserverInternalAPI
	origin      gomatrixserverlib.ServerName
	client      *gomatrixserverlib.FederationClient
	statistics  *statistics.Statistics
	signing     *SigningInfo
	queuesMutex sync.Mutex // protects the below
	queues      map[gomatrixserverlib.ServerName]*destinationQueue
}

func init() {
	prometheus.MustRegister(
		destinationQueueTotal, destinationQueueRunning,
		destinationQueueBackingOff,
	)
}

var destinationQueueTotal = prometheus.NewGauge(
	prometheus.GaugeOpts{
		Namespace: "dendrite",
		Subsystem: "federationsender",
		Name:      "destination_queues_total",
	},
)

var destinationQueueRunning = prometheus.NewGauge(
	prometheus.GaugeOpts{
		Namespace: "dendrite",
		Subsystem: "federationsender",
		Name:      "destination_queues_running",
	},
)

var destinationQueueBackingOff = prometheus.NewGauge(
	prometheus.GaugeOpts{
		Namespace: "dendrite",
		Subsystem: "federationsender",
		Name:      "destination_queues_backing_off",
	},
)

// NewOutgoingQueues makes a new OutgoingQueues
func NewOutgoingQueues(
	db storage.Database,
	process *process.ProcessContext,
	disabled bool,
	origin gomatrixserverlib.ServerName,
	client *gomatrixserverlib.FederationClient,
	rsAPI api.RoomserverInternalAPI,
	statistics *statistics.Statistics,
	signing *SigningInfo,
) *OutgoingQueues {
	queues := &OutgoingQueues{
		disabled:   disabled,
		process:    process,
		db:         db,
		rsAPI:      rsAPI,
		origin:     origin,
		client:     client,
		statistics: statistics,
		signing:    signing,
		queues:     map[gomatrixserverlib.ServerName]*destinationQueue{},
	}
	// Look up which servers we have pending items for and then rehydrate those queues.
	if !disabled {
		time.AfterFunc(time.Second*5, func() {
			serverNames := map[gomatrixserverlib.ServerName]struct{}{}
			if names, err := db.GetPendingPDUServerNames(context.Background()); err == nil {
				for _, serverName := range names {
					serverNames[serverName] = struct{}{}
				}
			} else {
				log.WithError(err).Error("Failed to get PDU server names for destination queue hydration")
			}
			if names, err := db.GetPendingEDUServerNames(context.Background()); err == nil {
				for _, serverName := range names {
					serverNames[serverName] = struct{}{}
				}
			} else {
				log.WithError(err).Error("Failed to get EDU server names for destination queue hydration")
			}
			for serverName := range serverNames {
				if queue := queues.getQueue(serverName); queue != nil {
					queue.wakeQueueIfNeeded()
				}
			}
		})
	}
	return queues
}

// TODO: Move this somewhere useful for other components as we often need to ferry these 3 variables
// around together
type SigningInfo struct {
	ServerName gomatrixserverlib.ServerName
	KeyID      gomatrixserverlib.KeyID
	PrivateKey ed25519.PrivateKey
}

type queuedPDU struct {
	receipt *shared.Receipt
	pdu     *gomatrixserverlib.HeaderedEvent
}

type queuedEDU struct {
	receipt *shared.Receipt
	edu     *gomatrixserverlib.EDU
}

func (oqs *OutgoingQueues) getQueue(destination gomatrixserverlib.ServerName) *destinationQueue {
	if oqs.statistics.ForServer(destination).Blacklisted() {
		return nil
	}
	oqs.queuesMutex.Lock()
	defer oqs.queuesMutex.Unlock()
	oq, ok := oqs.queues[destination]
	if !ok && oq != nil {
		destinationQueueTotal.Inc()
		oq = &destinationQueue{
			queues:           oqs,
			db:               oqs.db,
			process:          oqs.process,
			rsAPI:            oqs.rsAPI,
			origin:           oqs.origin,
			destination:      destination,
			client:           oqs.client,
			statistics:       oqs.statistics.ForServer(destination),
			notify:           make(chan struct{}, 1),
			interruptBackoff: make(chan bool),
			signing:          oqs.signing,
		}
		oqs.queues[destination] = oq
	}
	return oq
}

func (oqs *OutgoingQueues) clearQueue(oq *destinationQueue) {
	oqs.queuesMutex.Lock()
	defer oqs.queuesMutex.Unlock()

	delete(oqs.queues, oq.destination)
	destinationQueueTotal.Dec()
}

type ErrorFederationDisabled struct {
	Message string
}

func (e *ErrorFederationDisabled) Error() string {
	return e.Message
}

// SendEvent sends an event to the destinations
func (oqs *OutgoingQueues) SendEvent(
	ev *gomatrixserverlib.HeaderedEvent, origin gomatrixserverlib.ServerName,
	destinations []gomatrixserverlib.ServerName,
) error {
	if oqs.disabled {
		return &ErrorFederationDisabled{
			Message: "Federation disabled",
		}
	}
	if origin != oqs.origin {
		// TODO: Support virtual hosting; gh issue #577.
		return fmt.Errorf(
			"sendevent: unexpected server to send as: got %q expected %q",
			origin, oqs.origin,
		)
	}

	// Deduplicate destinations and remove the origin from the list of
	// destinations just to be sure.
	destmap := map[gomatrixserverlib.ServerName]struct{}{}
	for _, d := range destinations {
		destmap[d] = struct{}{}
	}
	delete(destmap, oqs.origin)

	// Check if any of the destinations are prohibited by server ACLs.
	for destination := range destmap {
		if api.IsServerBannedFromRoom(
			context.TODO(),
			oqs.rsAPI,
			ev.RoomID(),
			destination,
		) {
			delete(destmap, destination)
		}
	}

	// If there are no remaining destinations then give up.
	if len(destmap) == 0 {
		return nil
	}

	log.WithFields(log.Fields{
		"destinations": len(destmap), "event": ev.EventID(),
	}).Infof("Sending event")

	headeredJSON, err := json.Marshal(ev)
	if err != nil {
		return fmt.Errorf("json.Marshal: %w", err)
	}

	nid, err := oqs.db.StoreJSON(context.TODO(), string(headeredJSON))
	if err != nil {
		return fmt.Errorf("sendevent: oqs.db.StoreJSON: %w", err)
	}

	for destination := range destmap {
		if queue := oqs.getQueue(destination); queue != nil {
			queue.sendEvent(ev, nid)
		}
	}

	return nil
}

// SendEDU sends an EDU event to the destinations.
func (oqs *OutgoingQueues) SendEDU(
	e *gomatrixserverlib.EDU, origin gomatrixserverlib.ServerName,
	destinations []gomatrixserverlib.ServerName,
) error {
	if oqs.disabled {
		return &ErrorFederationDisabled{
			Message: "Federation disabled",
		}
	}
	if origin != oqs.origin {
		// TODO: Support virtual hosting; gh issue #577.
		return fmt.Errorf(
			"sendevent: unexpected server to send as: got %q expected %q",
			origin, oqs.origin,
		)
	}

	// Deduplicate destinations and remove the origin from the list of
	// destinations just to be sure.
	destmap := map[gomatrixserverlib.ServerName]struct{}{}
	for _, d := range destinations {
		destmap[d] = struct{}{}
	}
	delete(destmap, oqs.origin)

	// There is absolutely no guarantee that the EDU will have a room_id
	// field, as it is not required by the spec. However, if it *does*
	// (e.g. typing notifications) then we should try to make sure we don't
	// bother sending them to servers that are prohibited by the server
	// ACLs.
	if result := gjson.GetBytes(e.Content, "room_id"); result.Exists() {
		for destination := range destmap {
			if api.IsServerBannedFromRoom(
				context.TODO(),
				oqs.rsAPI,
				result.Str,
				destination,
			) {
				delete(destmap, destination)
			}
		}
	}

	// If there are no remaining destinations then give up.
	if len(destmap) == 0 {
		return nil
	}

	log.WithFields(log.Fields{
		"destinations": len(destmap), "edu_type": e.Type,
	}).Info("Sending EDU event")

	ephemeralJSON, err := json.Marshal(e)
	if err != nil {
		return fmt.Errorf("json.Marshal: %w", err)
	}

	nid, err := oqs.db.StoreJSON(context.TODO(), string(ephemeralJSON))
	if err != nil {
		return fmt.Errorf("sendevent: oqs.db.StoreJSON: %w", err)
	}

	for destination := range destmap {
		if queue := oqs.getQueue(destination); queue != nil {
			queue.sendEDU(e, nid)
		}
	}

	return nil
}

// RetryServer attempts to resend events to the given server if we had given up.
func (oqs *OutgoingQueues) RetryServer(srv gomatrixserverlib.ServerName) {
	if oqs.disabled {
		return
	}
	if queue := oqs.getQueue(srv); queue != nil {
		queue.wakeQueueIfNeeded()
	}
}
<|endoftext|>


<commit_before>// Copyright 2016 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// Package app 核心处理模块，包括路由函数和页面渲染等。
// 会调用 github.com/issue9/logs 包的内容，调用之前需要初始化该包。
package app

import (
	"html/template"
	"net/http"
	"path/filepath"
	"strings"
	"time"

	"github.com/caixw/typing/client"
	"github.com/caixw/typing/vars"
	"github.com/issue9/logs"
	"github.com/issue9/mux"
)

type app struct {
	path     *vars.Path
	mux      *mux.Mux
	conf     *config // 配置内容
	updated  int64   // 更新时间，一般为重新加载数据的时间
	client   *client.Client
	adminTpl *template.Template // 后台管理的模板页面。
}

// 重新加载数据
func (a *app) reload() error {
	if a.client != nil {
		a.client.Free()
	}

	c, err := client.New(a.path, a.mux)
	if err != nil {
		return err
	}
	a.client = c

	a.updated = time.Now().Unix()

	return nil
}

// Run 运行程序
func Run(path *vars.Path) error {
	logs.Info("程序工作路径为:", path.Root)

	conf, err := loadConfig(filepath.Join(path.ConfDir, "app.json"))
	if err != nil {
		return err
	}

	a := &app{
		path:    path,
		mux:     mux.New(false, false, nil, nil),
		updated: time.Now().Unix(),
		conf:    conf,
	}

	// 初始化 webhooks
	a.mux.PostFunc(a.conf.WebhooksURL, a.postWebhooks)

	// 初始化控制台相关操作
	if err := a.initAdmin(); err != nil {
		return err
	}

	// 加载数据
	if err = a.reload(); err != nil {
		logs.Error(err)
	}

	if !a.conf.HTTPS {
		return http.ListenAndServe(a.conf.Port, a.mux)
	}

	go func() { // 对 80 端口的处理方式
		serveHTTP(a)
	}()
	return http.ListenAndServeTLS(a.conf.Port, a.conf.CertFile, a.conf.KeyFile, a.mux)
}

func serveHTTP(a *app) {
	switch a.conf.HTTPState {
	case "default":
		logs.Error(http.ListenAndServe(":80", a.mux))
	case "redirect":
		logs.Error(http.ListenAndServe(":80", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// 构建跳转链接
			url := r.URL
			url.Scheme = "HTTPS"
			url.Host = strings.Split(r.Host, ":")[0] + a.conf.Port

			http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
		})))
	case "disable":
		return
	}
}
<commit_msg>添加对配置文件中 Pprof 和 Headers 的支持<commit_after>// Copyright 2016 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// Package app 核心处理模块，包括路由函数和页面渲染等。
// 会调用 github.com/issue9/logs 包的内容，调用之前需要初始化该包。
package app

import (
	"html/template"
	"net/http"
	"net/http/pprof"
	"path/filepath"
	"strings"
	"time"

	"github.com/caixw/typing/client"
	"github.com/caixw/typing/vars"
	"github.com/issue9/logs"
	"github.com/issue9/mux"
)

type app struct {
	path     *vars.Path
	mux      *mux.Mux
	conf     *config // 配置内容
	updated  int64   // 更新时间，一般为重新加载数据的时间
	client   *client.Client
	adminTpl *template.Template // 后台管理的模板页面。
}

// 重新加载数据
func (a *app) reload() error {
	if a.client != nil {
		a.client.Free()
	}

	c, err := client.New(a.path, a.mux)
	if err != nil {
		return err
	}
	a.client = c

	a.updated = time.Now().Unix()

	return nil
}

// Run 运行程序
func Run(path *vars.Path) error {
	logs.Info("程序工作路径为:", path.Root)

	conf, err := loadConfig(filepath.Join(path.ConfDir, "app.json"))
	if err != nil {
		return err
	}

	a := &app{
		path:    path,
		mux:     mux.New(false, false, nil, nil),
		updated: time.Now().Unix(),
		conf:    conf,
	}

	// 初始化 webhooks
	a.mux.PostFunc(a.conf.WebhooksURL, a.postWebhooks)

	// 初始化控制台相关操作
	if err := a.initAdmin(); err != nil {
		return err
	}

	// 加载数据
	if err = a.reload(); err != nil {
		logs.Error(err)
	}

	h := a.buildHeader(a.buildPprof(a.mux))

	if !a.conf.HTTPS {
		return http.ListenAndServe(a.conf.Port, h)
	}

	go func() { // 对 80 端口的处理方式
		serveHTTP(a)
	}()
	return http.ListenAndServeTLS(a.conf.Port, a.conf.CertFile, a.conf.KeyFile, h)
}

func (a *app) buildHeader(h http.Handler) http.Handler {
	if len(a.conf.Headers) == 0 {
		return h
	}

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		for k, v := range a.conf.Headers {
			w.Header().Set(k, v)
		}
		h.ServeHTTP(w, r)
	})
}

// 根据 Config.Pprof 决定是否包装调试地址，调用前请确认是否已经开启 Pprof 选项
func (a *app) buildPprof(h http.Handler) http.Handler {
	logs.Debug("开启了调试功能，地址为：", a.conf.Pprof)

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.HasPrefix(r.URL.Path, a.conf.Pprof) {
			h.ServeHTTP(w, r)
			return
		}

		path := r.URL.Path[len(a.conf.Pprof):]
		switch path {
		case "cmdline":
			pprof.Cmdline(w, r)
		case "profile":
			pprof.Profile(w, r)
		case "symbol":
			pprof.Symbol(w, r)
		case "trace":
			pprof.Trace(w, r)
		default:
			pprof.Index(w, r)
		}
	}) // end return http.HandlerFunc
}

// 对 80 端口的处理方式
func serveHTTP(a *app) {
	switch a.conf.HTTPState {
	case "default":
		logs.Error(http.ListenAndServe(":80", a.mux))
	case "redirect":
		logs.Error(http.ListenAndServe(":80", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// 构建跳转链接
			url := r.URL
			url.Scheme = "HTTPS"
			url.Host = strings.Split(r.Host, ":")[0] + a.conf.Port

			http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
		})))
	case "disable":
		return
	}
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/juhovuori/builder/build"
	"github.com/juhovuori/builder/exec"
	"github.com/juhovuori/builder/project"
	"github.com/juhovuori/builder/repository"
	"github.com/juhovuori/builder/version"
)

// App is the container for the whole builder application. This is used by
// frontends such as HTTP server or command line interface
type App interface {
	Config() Config
	Projects() []string
	Project(project string) (project.Project, error)
	Builds() []string
	Build(build string) (build.Build, error)
	TriggerBuild(projectID string) (build.Build, error)
	AddStage(buildID string, stage build.Stage) error
	Version() version.Info
	Shutdown() (<-chan bool, error)
}

type defaultApp struct {
	projects     project.Container
	repositories repository.Container
	builds       build.Container
	cfg          Config
}

func (a defaultApp) Config() Config {
	return a.cfg
}

func (a defaultApp) Projects() []string {
	return a.projects.Projects()
}

func (a defaultApp) Project(project string) (project.Project, error) {
	return a.projects.Project(project)
}

func (a defaultApp) Builds() []string {
	return a.builds.Builds()
}

func (a defaultApp) Build(build string) (build.Build, error) {
	return a.builds.Build(build)
}

func (a defaultApp) TriggerBuild(projectID string) (build.Build, error) {
	p, err := a.Project(projectID)
	if err != nil {
		return nil, err
	}
	repository, err := a.repositories.Repository(repository.Type(p.VCS()), p.URL())
	if err != nil {
		return nil, err
	}
	script, err := repository.File(p.Script())
	if err != nil {
		return nil, err
	}
	b, err := a.builds.New(p)
	if err != nil {
		return nil, err
	}
	env := []string{
		fmt.Sprintf("BUILD_ID=%s", b.ID()),
		fmt.Sprintf("URL=%s", a.cfg.URL),
	}
	e, err := exec.NewWithEnvironment(b, append(os.Environ(), env...))
	if err != nil {
		return nil, err
	}
	if err = a.builds.AddStage(b.ID(), build.StartStage()); err != nil {
		return nil, err
	}

	stdout := make(chan []byte)
	go func() {
		for data := range stdout {
			a.builds.Output(b.ID(), data)
		}
	}()
	go func() {
		err := e.Run(script, stdout)
		exitStatus := exec.AsUnixStatusCode(err)
		log.Printf("Exit %d\n", exitStatus)
		if !b.Completed() {
			stage := build.SuccessStage()
			if exitStatus != 0 {
				stage = build.FailureStage()
			}
			err := a.builds.AddStage(b.ID(), stage)
			if err != nil {
				log.Printf("Could not add final stage.%v\n", err)
			}
		}
	}()

	return b, nil
}

//AddStage adds a build stage
func (a defaultApp) AddStage(buildID string, stage build.Stage) error {
	stage.Timestamp = time.Now().UnixNano()
	return a.builds.AddStage(buildID, stage)
}

// Shutdown initiates a graceful shutdown
func (a defaultApp) Shutdown() (<-chan bool, error) {
	// TODO: stop creating builds
	// TODO: wait for builds to finnish instead of sleep
	log.Println("Initiating shutdown")
	ch := make(chan bool)
	go func() {
		<-time.After(time.Second * 2)
		ch <- true
	}()
	return ch, nil
}

// Version returns app version information
func (a defaultApp) Version() version.Info {
	return version.Version()
}

func (a defaultApp) addProject(pc projectConfig) {
	repository, err := a.repositories.Ensure(repository.Type(pc.Type), pc.Repository)
	if err != nil {
		log.Printf("Cannot add repository %v\n", err)
	}
	if err = repository.Init(); err != nil {
		log.Printf("Cannot initialize repository %v\n", err)
	}
	if err = repository.Update(); err != nil {
		log.Printf("Error updating repository %v\n", err)
	}
	config, err := repository.File(pc.Config)
	if err != nil {
		log.Printf("Cannot read configuration %v\n", err)
	}
	p, err := project.New(string(repository.Type()), repository.URL(), repository.ID(), pc.Config, config)
	if err != nil {
		log.Printf("Cannot create project: %v\n", err)
		return
	}
	err = a.projects.Add(p)
	if err != nil {
		log.Printf("Cannot add project to container %v\n", err)
	}
	log.Printf("Added project: %s - %s - %s\n", pc.Type, pc.Repository, pc.Config)
}

// New creates a new App from configuration
func New(cfg Config) (App, error) {
	builds, err := build.NewContainer(cfg.Store)
	if err != nil {
		return nil, err
	}

	repositories := repository.NewContainer()

	projects := project.NewContainer()

	newApp := defaultApp{
		projects,
		repositories,
		builds,
		cfg,
	}

	for _, p := range cfg.Projects {
		// TODO: add concurrently
		newApp.addProject(p)
	}

	return newApp, nil
}

// NewFromURL creates a new App from configuration filename
func NewFromURL(filename string) (App, error) {
	cfg, err := NewConfig(filename)
	if err != nil {
		return nil, err
	}
	return New(cfg)
}
<commit_msg>small app.go cleanup<commit_after>package app

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/juhovuori/builder/build"
	"github.com/juhovuori/builder/exec"
	"github.com/juhovuori/builder/project"
	"github.com/juhovuori/builder/repository"
	"github.com/juhovuori/builder/version"
)

// App is the container for the whole builder application. This is used by
// frontends such as HTTP server or command line interface
type App interface {
	Config() Config
	Projects() []string
	Project(project string) (project.Project, error)
	Builds() []string
	Build(build string) (build.Build, error)
	TriggerBuild(projectID string) (build.Build, error)
	AddStage(buildID string, stage build.Stage) error
	Version() version.Info
	Shutdown() (<-chan bool, error)
}

type defaultApp struct {
	projects     project.Container
	repositories repository.Container
	builds       build.Container
	cfg          Config
}

func (a defaultApp) Config() Config {
	return a.cfg
}

func (a defaultApp) Projects() []string {
	return a.projects.Projects()
}

func (a defaultApp) Project(project string) (project.Project, error) {
	return a.projects.Project(project)
}

func (a defaultApp) Builds() []string {
	return a.builds.Builds()
}

func (a defaultApp) Build(build string) (build.Build, error) {
	return a.builds.Build(build)
}

func (a defaultApp) TriggerBuild(projectID string) (build.Build, error) {
	p, err := a.Project(projectID)
	if err != nil {
		return nil, err
	}
	repository, err := a.repositories.Repository(repository.Type(p.VCS()), p.URL())
	if err != nil {
		return nil, err
	}
	script, err := repository.File(p.Script())
	if err != nil {
		return nil, err
	}
	b, err := a.builds.New(p)
	if err != nil {
		return nil, err
	}
	env := []string{
		fmt.Sprintf("BUILD_ID=%s", b.ID()),
		fmt.Sprintf("URL=%s", a.cfg.URL),
	}
	e, err := exec.NewWithEnvironment(b, append(os.Environ(), env...))
	if err != nil {
		return nil, err
	}
	if err = a.builds.AddStage(b.ID(), build.StartStage()); err != nil {
		return nil, err
	}

	stdout := make(chan []byte)
	go func() {
		for data := range stdout {
			a.builds.Output(b.ID(), data)
		}
	}()
	go func() {
		err := e.Run(script, stdout)
		exitStatus := exec.AsUnixStatusCode(err)
		log.Printf("Exit %d\n", exitStatus)
		if !b.Completed() {
			stage := build.SuccessStage()
			if exitStatus != 0 {
				stage = build.FailureStage()
			}
			err := a.builds.AddStage(b.ID(), stage)
			if err != nil {
				log.Printf("Could not add final stage.%v\n", err)
			}
		}
	}()

	return b, nil
}

//AddStage adds a build stage
func (a defaultApp) AddStage(buildID string, stage build.Stage) error {
	stage.Timestamp = time.Now().UnixNano()
	return a.builds.AddStage(buildID, stage)
}

// Shutdown initiates a graceful shutdown
func (a defaultApp) Shutdown() (<-chan bool, error) {
	// TODO: stop creating builds
	// TODO: wait for builds to finnish instead of sleep
	log.Println("Initiating shutdown")
	ch := make(chan bool)
	go func() {
		<-time.After(time.Second * 2)
		ch <- true
	}()
	return ch, nil
}

// Version returns app version information
func (a defaultApp) Version() version.Info {
	return version.Version()
}

func (a defaultApp) addProject(pc projectConfig) {
	repository, err := a.repositories.Ensure(repository.Type(pc.Type), pc.Repository)
	if err != nil {
		log.Printf("Cannot add repository %v\n", err)
	}
	if err = repository.Init(); err != nil {
		log.Printf("Cannot initialize repository %v\n", err)
	}
	if err = repository.Update(); err != nil {
		log.Printf("Error updating repository %v\n", err)
	}
	config, err := repository.File(pc.Config)
	if err != nil {
		log.Printf("Cannot read configuration %v\n", err)
	}
	p, err := project.New(string(repository.Type()), repository.URL(), repository.ID(), pc.Config, config)
	if err != nil {
		log.Printf("Cannot create project: %v\n", err)
		return
	}
	if err = a.projects.Add(p); err != nil {
		log.Printf("Cannot add project to container %v\n", err)
	}
	log.Printf("Added project: %s - %s - %s\n", pc.Type, pc.Repository, pc.Config)
}

// New creates a new App from configuration
func New(cfg Config) (App, error) {
	builds, err := build.NewContainer(cfg.Store)
	if err != nil {
		return nil, err
	}

	repositories := repository.NewContainer()

	projects := project.NewContainer()

	newApp := defaultApp{
		projects,
		repositories,
		builds,
		cfg,
	}

	for _, p := range cfg.Projects {
		// TODO: add concurrently
		newApp.addProject(p)
	}

	return newApp, nil
}

// NewFromURL creates a new App from configuration filename
func NewFromURL(filename string) (App, error) {
	cfg, err := NewConfig(filename)
	if err != nil {
		return nil, err
	}
	return New(cfg)
}
<|endoftext|>


<commit_before>package task_bbs

import (
	"github.com/cloudfoundry-incubator/runtime-schema/bbs/shared"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	"github.com/cloudfoundry/storeadapter"
	"github.com/pivotal-golang/lager"
)

func (bbs *TaskBBS) GetAllTasks() ([]models.Task, error) {
	node, err := bbs.store.ListRecursively(shared.TaskSchemaRoot)
	if err == storeadapter.ErrorKeyNotFound {
		return []models.Task{}, nil
	}

	if err != nil {
		return []models.Task{}, err
	}

	tasks := []models.Task{}
	for _, node := range node.ChildNodes {
		task, err := models.NewTaskFromJSON(node.Value)
		if err != nil {
			bbs.logger.Error("failed-to-unmarshal-task", err, lager.Data{
				"key":   node.Key,
				"value": node.Value,
			})
		} else {
			tasks = append(tasks, task)
		}
	}

	return tasks, nil
}

func (bbs *TaskBBS) GetTaskByGuid(guid string) (models.Task, error) {
	task, _, err := bbs.getTask(guid)
	return task, err
}

func (bbs *TaskBBS) GetAllPendingTasks() ([]models.Task, error) {
	all, err := bbs.GetAllTasks()
	return filterTasks(all, models.TaskStatePending), err
}

func (bbs *TaskBBS) GetAllClaimedTasks() ([]models.Task, error) {
	all, err := bbs.GetAllTasks()
	return filterTasks(all, models.TaskStateClaimed), err
}

func (bbs *TaskBBS) GetAllRunningTasks() ([]models.Task, error) {
	all, err := bbs.GetAllTasks()
	return filterTasks(all, models.TaskStateRunning), err
}

func (bbs *TaskBBS) GetAllCompletedTasks() ([]models.Task, error) {
	all, err := bbs.GetAllTasks()
	return filterTasks(all, models.TaskStateCompleted), err
}

func (bbs *TaskBBS) GetAllResolvingTasks() ([]models.Task, error) {
	all, err := bbs.GetAllTasks()
	return filterTasks(all, models.TaskStateResolving), err
}

func filterTasks(tasks []models.Task, state models.TaskState) []models.Task {
	result := make([]models.Task, 0)
	for _, model := range tasks {
		if model.State == state {
			result = append(result, model)
		}
	}
	return result
}

func (bbs *TaskBBS) getTask(taskGuid string) (models.Task, uint64, error) {
	var node storeadapter.StoreNode
	err := shared.RetryIndefinitelyOnStoreTimeout(func() error {
		var err error
		node, err = bbs.store.Get(shared.TaskSchemaPath(taskGuid))
		return err
	})

	if err != nil {
		return models.Task{}, 0, err
	}

	task, err := models.NewTaskFromJSON(node.Value)

	return task, node.Index, err
}
<commit_msg>Refactor task filtering to be more generic<commit_after>package task_bbs

import (
	"github.com/cloudfoundry-incubator/runtime-schema/bbs/shared"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	"github.com/cloudfoundry/storeadapter"
	"github.com/pivotal-golang/lager"
)

func (bbs *TaskBBS) GetAllTasks() ([]models.Task, error) {
	node, err := bbs.store.ListRecursively(shared.TaskSchemaRoot)
	if err == storeadapter.ErrorKeyNotFound {
		return []models.Task{}, nil
	}

	if err != nil {
		return []models.Task{}, err
	}

	tasks := []models.Task{}
	for _, node := range node.ChildNodes {
		task, err := models.NewTaskFromJSON(node.Value)
		if err != nil {
			bbs.logger.Error("failed-to-unmarshal-task", err, lager.Data{
				"key":   node.Key,
				"value": node.Value,
			})
		} else {
			tasks = append(tasks, task)
		}
	}

	return tasks, nil
}

func (bbs *TaskBBS) GetTaskByGuid(guid string) (models.Task, error) {
	task, _, err := bbs.getTask(guid)
	return task, err
}

func (bbs *TaskBBS) GetAllPendingTasks() ([]models.Task, error) {
	all, err := bbs.GetAllTasks()
	return filterTasksByState(all, models.TaskStatePending), err
}

func (bbs *TaskBBS) GetAllClaimedTasks() ([]models.Task, error) {
	all, err := bbs.GetAllTasks()
	return filterTasksByState(all, models.TaskStateClaimed), err
}

func (bbs *TaskBBS) GetAllRunningTasks() ([]models.Task, error) {
	all, err := bbs.GetAllTasks()
	return filterTasksByState(all, models.TaskStateRunning), err
}

func (bbs *TaskBBS) GetAllCompletedTasks() ([]models.Task, error) {
	all, err := bbs.GetAllTasks()
	return filterTasksByState(all, models.TaskStateCompleted), err
}

func (bbs *TaskBBS) GetAllResolvingTasks() ([]models.Task, error) {
	all, err := bbs.GetAllTasks()
	return filterTasksByState(all, models.TaskStateResolving), err
}

func filterTasks(tasks []models.Task, filterFunc func(models.Task) bool) []models.Task {
	result := make([]models.Task, 0)
	for _, task := range tasks {
		if filterFunc(task) {
			result = append(result, task)
		}
	}
	return result
}

func filterTasksByState(tasks []models.Task, state models.TaskState) []models.Task {
	return filterTasks(tasks, func(task models.Task) bool {
		return task.State == state
	})
}

func (bbs *TaskBBS) getTask(taskGuid string) (models.Task, uint64, error) {
	var node storeadapter.StoreNode
	err := shared.RetryIndefinitelyOnStoreTimeout(func() error {
		var err error
		node, err = bbs.store.Get(shared.TaskSchemaPath(taskGuid))
		return err
	})

	if err != nil {
		return models.Task{}, 0, err
	}

	task, err := models.NewTaskFromJSON(node.Value)

	return task, node.Index, err
}
<|endoftext|>


<commit_before>package tgbotapi

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

// APIResponse is a response from the Telegram API with the result stored raw.
type APIResponse struct {
	Ok          bool            `json:"ok"`
	Result      json.RawMessage `json:"result"`
	ErrorCode   int             `json:"error_code"`
	Description string          `json:"description"`
}

// Update is an update response, from GetUpdates.
type Update struct {
	UpdateID int     `json:"update_id"`
	Message  Message `json:"message"`
}

// User is a user, contained in Message and returned by GetSelf.
type User struct {
	ID        int    `json:"id"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	UserName  string `json:"username"`
}

// String displays a simple text version of a user.
// It is normally a user's username,
// but falls back to a first/last name as available.
func (u *User) String() string {
	if u.UserName != "" {
		return u.UserName
	}

	name := u.FirstName
	if u.LastName != "" {
		name += " " + u.LastName
	}

	return name
}

// GroupChat is a group chat, and not currently in use.
type GroupChat struct {
	ID    int    `json:"id"`
	Title string `json:"title"`
}

// UserOrGroupChat is returned in Message, because it's not clear which it is.
type UserOrGroupChat struct {
	ID        int    `json:"id"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	UserName  string `json:"username"`
	Title     string `json:"title"`
}

// Message is returned by almost every request, and contains data about almost anything.
type Message struct {
	MessageID           int             `json:"message_id"`
	From                User            `json:"from"`
	Date                int             `json:"date"`
	Chat                UserOrGroupChat `json:"chat"`
	ForwardFrom         User            `json:"forward_from"`
	ForwardDate         int             `json:"forward_date"`
	ReplyToMessage      *Message        `json:"reply_to_message"`
	Text                string          `json:"text"`
	Audio               Audio           `json:"audio"`
	Document            Document        `json:"document"`
	Photo               []PhotoSize     `json:"photo"`
	Sticker             Sticker         `json:"sticker"`
	Video               Video           `json:"video"`
	Voice               Voice           `json:"voice"`
	Caption             string          `json:"caption"`
	Contact             Contact         `json:"contact"`
	Location            Location        `json:"location"`
	NewChatParticipant  User            `json:"new_chat_participant"`
	LeftChatParticipant User            `json:"left_chat_participant"`
	NewChatTitle        string          `json:"new_chat_title"`
	NewChatPhoto        []PhotoSize     `json:"new_chat_photo"`
	DeleteChatPhoto     bool            `json:"delete_chat_photo"`
	GroupChatCreated    bool            `json:"group_chat_created"`
}

// Time converts the message timestamp into a Time.
func (m *Message) Time() time.Time {
	return time.Unix(int64(m.Date), 0)
}

// IsGroup returns if the message was sent to a group.
func (m *Message) IsGroup() bool {
	return m.From.ID != m.Chat.ID
}

// PhotoSize contains information about photos, including ID and Width and Height.
type PhotoSize struct {
	FileID   string `json:"file_id"`
	Width    int    `json:"width"`
	Height   int    `json:"height"`
	FileSize int    `json:"file_size"`
}

// Audio contains information about audio,
// including ID, Duration, Performer and Title.
type Audio struct {
	FileID    string `json:"file_id"`
	Duration  int    `json:"duration"`
	Performer string `json:"performer"`
	Title     string `json:"title"`
	MimeType  string `json:"mime_type"`
	FileSize  int    `json:"file_size"`
}

// Document contains information about a document, including ID and a Thumbnail.
type Document struct {
	FileID    string    `json:"file_id"`
	Thumbnail PhotoSize `json:"thumb"`
	FileName  string    `json:"file_name"`
	MimeType  string    `json:"mime_type"`
	FileSize  int       `json:"file_size"`
}

// Sticker contains information about a sticker, including ID and Thumbnail.
type Sticker struct {
	FileID    string    `json:"file_id"`
	Width     int       `json:"width"`
	Height    int       `json:"height"`
	Thumbnail PhotoSize `json:"thumb"`
	FileSize  int       `json:"file_size"`
}

// Video contains information about a video, including ID and duration and Thumbnail.
type Video struct {
	FileID    string    `json:"file_id"`
	Width     int       `json:"width"`
	Height    int       `json:"height"`
	Duration  int       `json:"duration"`
	Thumbnail PhotoSize `json:"thumb"`
	MimeType  string    `json:"mime_type"`
	FileSize  int       `json:"file_size"`
}

// Voice contains information about a voice, including ID and duration.
type Voice struct {
	FileID   string `json:"file_id"`
	Duration int    `json:"duration"`
	MimeType string `json:"mime_type"`
	FileSize int    `json:"file_size"`
}

// Contact contains information about a contact, such as PhoneNumber and UserId.
type Contact struct {
	PhoneNumber string `json:"phone_number"`
	FirstName   string `json:"first_name"`
	LastName    string `json:"last_name"`
	UserID      int    `json:"user_id"`
}

// Location contains information about a place, such as Longitude and Latitude.
type Location struct {
	Longitude float32 `json:"longitude"`
	Latitude  float32 `json:"latitude"`
}

// UserProfilePhotos contains information a set of user profile photos.
type UserProfilePhotos struct {
	TotalCount int         `json:"total_count"`
	Photos     []PhotoSize `json:"photos"`
}

// File contains information about a file to download from Telegram
type File struct {
	FileID   string `json:"file_id"`
	FileSize int    `json:"file_size"`
	FilePath string `json:"file_path"`
}

// Link returns a full path to the download URL for a File.
//
// It requires the Bot Token to create the link.
func (f *File) Link(token string) string {
	return fmt.Sprintf(FileEndpoint, token, f.FilePath)
}

// ReplyKeyboardMarkup allows the Bot to set a custom keyboard.
type ReplyKeyboardMarkup struct {
	Keyboard        [][]string `json:"keyboard"`
	ResizeKeyboard  bool       `json:"resize_keyboard"`
	OneTimeKeyboard bool       `json:"one_time_keyboard"`
	Selective       bool       `json:"selective"`
}

// ReplyKeyboardHide allows the Bot to hide a custom keyboard.
type ReplyKeyboardHide struct {
	HideKeyboard bool `json:"hide_keyboard"`
	Selective    bool `json:"selective"`
}

// ForceReply allows the Bot to have users directly reply to it without additional interaction.
type ForceReply struct {
	ForceReply bool `json:"force_reply"`
	Selective  bool `json:"selective"`
}
<commit_msg>New Telegram Chat type, instead of previous UserOrGroupChat type<commit_after>package tgbotapi

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

// APIResponse is a response from the Telegram API with the result stored raw.
type APIResponse struct {
	Ok          bool            `json:"ok"`
	Result      json.RawMessage `json:"result"`
	ErrorCode   int             `json:"error_code"`
	Description string          `json:"description"`
}

// Update is an update response, from GetUpdates.
type Update struct {
	UpdateID int     `json:"update_id"`
	Message  Message `json:"message"`
}

// User is a user, contained in Message and returned by GetSelf.
type User struct {
	ID        int    `json:"id"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	UserName  string `json:"username"`
}

// String displays a simple text version of a user.
// It is normally a user's username,
// but falls back to a first/last name as available.
func (u *User) String() string {
	if u.UserName != "" {
		return u.UserName
	}

	name := u.FirstName
	if u.LastName != "" {
		name += " " + u.LastName
	}

	return name
}

// GroupChat is a group chat, and not currently in use.
type GroupChat struct {
	ID    int    `json:"id"`
	Title string `json:"title"`
}

// Chat is returned in Message, it contains information about the Chat a message was sent in.
type Chat struct {
	ID        int    `json:"id"`
	Type      string `json:"type"`
	Title     string `json:"title"`
	UserName  string `json:"username"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

// Message is returned by almost every request, and contains data about almost anything.
type Message struct {
	MessageID           int         `json:"message_id"`
	From                User        `json:"from"`
	Date                int         `json:"date"`
	Chat                Chat        `json:"chat"`
	ForwardFrom         User        `json:"forward_from"`
	ForwardDate         int         `json:"forward_date"`
	ReplyToMessage      *Message    `json:"reply_to_message"`
	Text                string      `json:"text"`
	Audio               Audio       `json:"audio"`
	Document            Document    `json:"document"`
	Photo               []PhotoSize `json:"photo"`
	Sticker             Sticker     `json:"sticker"`
	Video               Video       `json:"video"`
	Voice               Voice       `json:"voice"`
	Caption             string      `json:"caption"`
	Contact             Contact     `json:"contact"`
	Location            Location    `json:"location"`
	NewChatParticipant  User        `json:"new_chat_participant"`
	LeftChatParticipant User        `json:"left_chat_participant"`
	NewChatTitle        string      `json:"new_chat_title"`
	NewChatPhoto        []PhotoSize `json:"new_chat_photo"`
	DeleteChatPhoto     bool        `json:"delete_chat_photo"`
	GroupChatCreated    bool        `json:"group_chat_created"`
}

// Time converts the message timestamp into a Time.
func (m *Message) Time() time.Time {
	return time.Unix(int64(m.Date), 0)
}

// IsGroup returns if the message was sent to a group.
func (m *Message) IsGroup() bool {
	return m.From.ID != m.Chat.ID
}

// PhotoSize contains information about photos, including ID and Width and Height.
type PhotoSize struct {
	FileID   string `json:"file_id"`
	Width    int    `json:"width"`
	Height   int    `json:"height"`
	FileSize int    `json:"file_size"`
}

// Audio contains information about audio,
// including ID, Duration, Performer and Title.
type Audio struct {
	FileID    string `json:"file_id"`
	Duration  int    `json:"duration"`
	Performer string `json:"performer"`
	Title     string `json:"title"`
	MimeType  string `json:"mime_type"`
	FileSize  int    `json:"file_size"`
}

// Document contains information about a document, including ID and a Thumbnail.
type Document struct {
	FileID    string    `json:"file_id"`
	Thumbnail PhotoSize `json:"thumb"`
	FileName  string    `json:"file_name"`
	MimeType  string    `json:"mime_type"`
	FileSize  int       `json:"file_size"`
}

// Sticker contains information about a sticker, including ID and Thumbnail.
type Sticker struct {
	FileID    string    `json:"file_id"`
	Width     int       `json:"width"`
	Height    int       `json:"height"`
	Thumbnail PhotoSize `json:"thumb"`
	FileSize  int       `json:"file_size"`
}

// Video contains information about a video, including ID and duration and Thumbnail.
type Video struct {
	FileID    string    `json:"file_id"`
	Width     int       `json:"width"`
	Height    int       `json:"height"`
	Duration  int       `json:"duration"`
	Thumbnail PhotoSize `json:"thumb"`
	MimeType  string    `json:"mime_type"`
	FileSize  int       `json:"file_size"`
}

// Voice contains information about a voice, including ID and duration.
type Voice struct {
	FileID   string `json:"file_id"`
	Duration int    `json:"duration"`
	MimeType string `json:"mime_type"`
	FileSize int    `json:"file_size"`
}

// Contact contains information about a contact, such as PhoneNumber and UserId.
type Contact struct {
	PhoneNumber string `json:"phone_number"`
	FirstName   string `json:"first_name"`
	LastName    string `json:"last_name"`
	UserID      int    `json:"user_id"`
}

// Location contains information about a place, such as Longitude and Latitude.
type Location struct {
	Longitude float32 `json:"longitude"`
	Latitude  float32 `json:"latitude"`
}

// UserProfilePhotos contains information a set of user profile photos.
type UserProfilePhotos struct {
	TotalCount int         `json:"total_count"`
	Photos     []PhotoSize `json:"photos"`
}

// File contains information about a file to download from Telegram
type File struct {
	FileID   string `json:"file_id"`
	FileSize int    `json:"file_size"`
	FilePath string `json:"file_path"`
}

// Link returns a full path to the download URL for a File.
//
// It requires the Bot Token to create the link.
func (f *File) Link(token string) string {
	return fmt.Sprintf(FileEndpoint, token, f.FilePath)
}

// ReplyKeyboardMarkup allows the Bot to set a custom keyboard.
type ReplyKeyboardMarkup struct {
	Keyboard        [][]string `json:"keyboard"`
	ResizeKeyboard  bool       `json:"resize_keyboard"`
	OneTimeKeyboard bool       `json:"one_time_keyboard"`
	Selective       bool       `json:"selective"`
}

// ReplyKeyboardHide allows the Bot to hide a custom keyboard.
type ReplyKeyboardHide struct {
	HideKeyboard bool `json:"hide_keyboard"`
	Selective    bool `json:"selective"`
}

// ForceReply allows the Bot to have users directly reply to it without additional interaction.
type ForceReply struct {
	ForceReply bool `json:"force_reply"`
	Selective  bool `json:"selective"`
}
<|endoftext|>