<commit_before>// flashlight is a lightweight chained proxy that can run in client or server mode.
package main

import (
	"flag"
	"fmt"
	"math/rand"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/getlantern/fronted"
	"github.com/getlantern/golog"
	"github.com/getlantern/i18n"
	"github.com/getlantern/profiling"
	"github.com/getlantern/systray"

	"github.com/getlantern/flashlight/analytics"
	"github.com/getlantern/flashlight/autoupdate"
	"github.com/getlantern/flashlight/client"
	"github.com/getlantern/flashlight/config"
	"github.com/getlantern/flashlight/geolookup"
	"github.com/getlantern/flashlight/logging"
	"github.com/getlantern/flashlight/proxiedsites"
	"github.com/getlantern/flashlight/server"
	"github.com/getlantern/flashlight/settings"
	"github.com/getlantern/flashlight/statreporter"
	"github.com/getlantern/flashlight/statserver"
	"github.com/getlantern/flashlight/ui"
)

var (
	version   string
	buildDate string
	log       = golog.LoggerFor("flashlight")

	// Command-line Flags
	help      = flag.Bool("help", false, "Get usage help")
	parentPID = flag.Int("parentpid", 0, "the parent process's PID, used on Windows for killing flashlight when the parent disappears")
	headless  = flag.Bool("headless", false, "if true, lantern will run with no ui")

	configUpdates = make(chan *config.Config)
	exitCh        = make(chan error, 1)

	showui = true
)

func init() {

	if packageVersion != defaultPackageVersion {
		// packageVersion has precedence over GIT revision. This will happen when
		// packing a version intended for release.
		version = packageVersion
	}

	if version == "" {
		version = "development"
	}

	if buildDate == "" {
		buildDate = "now"
	}

	// Passing public key and version to the autoupdate service.
	autoupdate.PublicKey = []byte(packagePublicKey)
	autoupdate.Version = packageVersion

	rand.Seed(time.Now().UnixNano())
}

func main() {
	parseFlags()
	showui = !*headless

	if showui {
		systray.Run(_main)
	} else {
		log.Debug("Running headless")
		_main()
	}
}

func _main() {
	err := doMain()
	if err != nil {
		log.Fatal(err)
	}
	log.Debug("Lantern stopped")
	os.Exit(0)
}

func doMain() error {
	err := logging.Init()
	if err != nil {
		return err
	}

	// Schedule cleanup actions
	defer logging.Close()
	defer pacOff()
	defer systray.Quit()

	i18nInit()
	if showui {
		err = configureSystemTray()
		if err != nil {
			return err
		}
	}
	displayVersion()

	parseFlags()
	configUpdates = make(chan *config.Config)
	cfg, err := config.Init()
	if err != nil {
		return fmt.Errorf("Unable to initialize configuration: %v", err)
	}
	go func() {
		err := config.Run(func(updated *config.Config) {
			configUpdates <- updated
		})
		if err != nil {
			exit(err)
		}
	}()
	if *help || cfg.Addr == "" || (cfg.Role != "server" && cfg.Role != "client") {
		flag.Usage()
		return fmt.Errorf("Wrong arguments")
	}

	finishProfiling := profiling.Start(cfg.CpuProfile, cfg.MemProfile)
	defer finishProfiling()

	// Configure stats initially
	err = statreporter.Configure(cfg.Stats)
	if err != nil {
		return err
	}

	log.Debug("Running proxy")
	if cfg.IsDownstream() {
		runClientProxy(cfg)
	} else {
		runServerProxy(cfg)
	}

	return waitForExit()
}

func i18nInit() {
	i18n.SetMessagesFunc(func(filename string) ([]byte, error) {
		return ui.Translations.Get(filename)
	})
	err := i18n.UseOSLocale()
	if err != nil {
		exit(err)
	}
}

func displayVersion() {
	log.Debugf("---- flashlight version: %s, release: %s, build date: %s ----", version, packageVersion, buildDate)
}

func parseFlags() {
	args := os.Args[1:]
	// On OS X, the first time that the program is run after download it is
	// quarantined.  OS X will ask the user whether or not it's okay to run the
	// program.  If the user says that it's okay, OS X will run the program but
	// pass an extra flag like -psn_0_1122578.  flag.Parse() fails if it sees
	// any flags that haven't been declared, so we remove the extra flag.
	if len(os.Args) == 2 && strings.HasPrefix(os.Args[1], "-psn") {
		log.Debugf("Ignoring extra flag %v", os.Args[1])
		args = []string{}
	}
	// Note - we can ignore the returned error because CommandLine.Parse() will
	// exit if it fails.
	flag.CommandLine.Parse(args)
}

// Runs the client-side proxy
func runClientProxy(cfg *config.Config) {
	setProxyAddr(cfg.Addr)
	err := setUpPacTool()
	if err != nil {
		exit(err)
	}
	client := &client.Client{
		Addr:         cfg.Addr,
		ReadTimeout:  0, // don't timeout
		WriteTimeout: 0,
	}

	hqfd := client.Configure(cfg.Client)

	if cfg.UIAddr != "" {
		err := ui.Start(cfg.UIAddr)
		if err != nil {
			exit(fmt.Errorf("Unable to start UI: %v", err))
			return
		}
		if showui {
			ui.Show()
		}
	}

	autoupdate.Configure(cfg)
	logging.Configure(cfg, version, buildDate)
	settings.Configure(cfg, version, buildDate)
	proxiedsites.Configure(cfg.ProxiedSites)

	if hqfd == nil {
		log.Errorf("No fronted dialer available, not enabling geolocation, stats or analytics")
	} else {
		hqfdc := hqfd.DirectHttpClient()
		geolookup.Configure(hqfdc)
		statserver.Configure(hqfdc)
		// start GA service
		analytics.Configure(cfg, false, hqfdc)
	}

	// Continually poll for config updates and update client accordingly
	go func() {
		for {
			cfg := <-configUpdates

			proxiedsites.Configure(cfg.ProxiedSites)
			// Note - we deliberately ignore the error from statreporter.Configure here
			statreporter.Configure(cfg.Stats)
			hqfd = client.Configure(cfg.Client)
			if hqfd != nil {
				hqfdc := hqfd.DirectHttpClient()
				geolookup.Configure(hqfdc)
				statserver.Configure(hqfdc)
				logging.Configure(cfg, version, buildDate)
				autoupdate.Configure(cfg)
			}
		}
	}()

	watchDirectAddrs()

	go func() {
		exit(client.ListenAndServe(pacOn))
	}()
	log.Debug("Ran goroutine")
}

// Runs the server-side proxy
func runServerProxy(cfg *config.Config) {
	useAllCores()

	pkFile, err := config.InConfigDir("proxypk.pem")
	if err != nil {
		log.Fatal(err)
	}
	certFile, err := config.InConfigDir("servercert.pem")
	if err != nil {
		log.Fatal(err)
	}

	srv := &server.Server{
		Addr:         cfg.Addr,
		ReadTimeout:  0, // don't timeout
		WriteTimeout: 0,
		CertContext: &fronted.CertContext{
			PKFile:         pkFile,
			ServerCertFile: certFile,
		},
		AllowedPorts: []int{80, 443, 8080, 8443, 5222, 5223, 5228},

		// We allow all censored countries plus us, es and mx because we do work
		// and testing from those countries.
		AllowedCountries: []string{"US", "ES", "MX", "CN", "VN", "IN", "IQ", "IR", "CU", "SY", "SA", "BH", "ET", "ER", "UZ", "TM", "PK", "TR", "VE"},
	}

	srv.Configure(cfg.Server)
	analytics.Configure(nil, true, nil)

	// Continually poll for config updates and update server accordingly
	go func() {
		for {
			cfg := <-configUpdates
			statreporter.Configure(cfg.Stats)
			srv.Configure(cfg.Server)
		}
	}()

	err = srv.ListenAndServe(func(update func(*server.ServerConfig) error) {
		err := config.Update(func(cfg *config.Config) error {
			return update(cfg.Server)
		})
		if err != nil {
			log.Errorf("Error while trying to update: %v", err)
		}
	})
	if err != nil {
		log.Fatalf("Unable to run server proxy: %s", err)
	}
}

func useAllCores() {
	numcores := runtime.NumCPU()
	log.Debugf("Using all %d cores on machine", numcores)
	runtime.GOMAXPROCS(numcores)
}

func configureSystemTray() error {
	icon, err := Asset("icons/16on.ico")
	if err != nil {
		return fmt.Errorf("Unable to load icon for system tray: %v", err)
	}
	systray.SetIcon(icon)
	systray.SetTooltip("Lantern")
	show := systray.AddMenuItem(i18n.T("TRAY_SHOW_LANTERN"), i18n.T("SHOW"))
	quit := systray.AddMenuItem(i18n.T("TRAY_QUIT"), i18n.T("QUIT"))
	go func() {
		for {
			select {
			case <-show.ClickedCh:
				ui.Show()
			case <-quit.ClickedCh:
				exit(nil)
				return
			}
		}
	}()

	return nil
}

// exit tells the application to exit, optionally supplying an error that caused
// the exit.
func exit(err error) {
	exitCh <- err
}

// WaitForExit waits for a request to exit the application.
func waitForExit() error {
	return <-exitCh
}
<commit_msg>Logging error instead of exiting, a missing translation should not be a fatal error. Closes #2360.<commit_after>// flashlight is a lightweight chained proxy that can run in client or server mode.
package main

import (
	"flag"
	"fmt"
	"math/rand"
	"os"
	"runtime"
	"strings"
	"time"

	"github.com/getlantern/fronted"
	"github.com/getlantern/golog"
	"github.com/getlantern/i18n"
	"github.com/getlantern/profiling"
	"github.com/getlantern/systray"

	"github.com/getlantern/flashlight/analytics"
	"github.com/getlantern/flashlight/autoupdate"
	"github.com/getlantern/flashlight/client"
	"github.com/getlantern/flashlight/config"
	"github.com/getlantern/flashlight/geolookup"
	"github.com/getlantern/flashlight/logging"
	"github.com/getlantern/flashlight/proxiedsites"
	"github.com/getlantern/flashlight/server"
	"github.com/getlantern/flashlight/settings"
	"github.com/getlantern/flashlight/statreporter"
	"github.com/getlantern/flashlight/statserver"
	"github.com/getlantern/flashlight/ui"
)

var (
	version   string
	buildDate string
	log       = golog.LoggerFor("flashlight")

	// Command-line Flags
	help      = flag.Bool("help", false, "Get usage help")
	parentPID = flag.Int("parentpid", 0, "the parent process's PID, used on Windows for killing flashlight when the parent disappears")
	headless  = flag.Bool("headless", false, "if true, lantern will run with no ui")

	configUpdates = make(chan *config.Config)
	exitCh        = make(chan error, 1)

	showui = true
)

func init() {

	if packageVersion != defaultPackageVersion {
		// packageVersion has precedence over GIT revision. This will happen when
		// packing a version intended for release.
		version = packageVersion
	}

	if version == "" {
		version = "development"
	}

	if buildDate == "" {
		buildDate = "now"
	}

	// Passing public key and version to the autoupdate service.
	autoupdate.PublicKey = []byte(packagePublicKey)
	autoupdate.Version = packageVersion

	rand.Seed(time.Now().UnixNano())
}

func main() {
	parseFlags()
	showui = !*headless

	if showui {
		systray.Run(_main)
	} else {
		log.Debug("Running headless")
		_main()
	}
}

func _main() {
	err := doMain()
	if err != nil {
		log.Fatal(err)
	}
	log.Debug("Lantern stopped")
	os.Exit(0)
}

func doMain() error {
	err := logging.Init()
	if err != nil {
		return err
	}

	// Schedule cleanup actions
	defer logging.Close()
	defer pacOff()
	defer systray.Quit()

	i18nInit()
	if showui {
		err = configureSystemTray()
		if err != nil {
			return err
		}
	}
	displayVersion()

	parseFlags()
	configUpdates = make(chan *config.Config)
	cfg, err := config.Init()
	if err != nil {
		return fmt.Errorf("Unable to initialize configuration: %v", err)
	}
	go func() {
		err := config.Run(func(updated *config.Config) {
			configUpdates <- updated
		})
		if err != nil {
			exit(err)
		}
	}()
	if *help || cfg.Addr == "" || (cfg.Role != "server" && cfg.Role != "client") {
		flag.Usage()
		return fmt.Errorf("Wrong arguments")
	}

	finishProfiling := profiling.Start(cfg.CpuProfile, cfg.MemProfile)
	defer finishProfiling()

	// Configure stats initially
	err = statreporter.Configure(cfg.Stats)
	if err != nil {
		return err
	}

	log.Debug("Running proxy")
	if cfg.IsDownstream() {
		runClientProxy(cfg)
	} else {
		runServerProxy(cfg)
	}

	return waitForExit()
}

func i18nInit() {
	i18n.SetMessagesFunc(func(filename string) ([]byte, error) {
		return ui.Translations.Get(filename)
	})
	err := i18n.UseOSLocale()
	if err != nil {
		log.Debugf("i18n.UseOSLocale: %q", err)
	}
}

func displayVersion() {
	log.Debugf("---- flashlight version: %s, release: %s, build date: %s ----", version, packageVersion, buildDate)
}

func parseFlags() {
	args := os.Args[1:]
	// On OS X, the first time that the program is run after download it is
	// quarantined.  OS X will ask the user whether or not it's okay to run the
	// program.  If the user says that it's okay, OS X will run the program but
	// pass an extra flag like -psn_0_1122578.  flag.Parse() fails if it sees
	// any flags that haven't been declared, so we remove the extra flag.
	if len(os.Args) == 2 && strings.HasPrefix(os.Args[1], "-psn") {
		log.Debugf("Ignoring extra flag %v", os.Args[1])
		args = []string{}
	}
	// Note - we can ignore the returned error because CommandLine.Parse() will
	// exit if it fails.
	flag.CommandLine.Parse(args)
}

// Runs the client-side proxy
func runClientProxy(cfg *config.Config) {
	setProxyAddr(cfg.Addr)
	err := setUpPacTool()
	if err != nil {
		exit(err)
	}
	client := &client.Client{
		Addr:         cfg.Addr,
		ReadTimeout:  0, // don't timeout
		WriteTimeout: 0,
	}

	hqfd := client.Configure(cfg.Client)

	if cfg.UIAddr != "" {
		err := ui.Start(cfg.UIAddr)
		if err != nil {
			exit(fmt.Errorf("Unable to start UI: %v", err))
			return
		}
		if showui {
			ui.Show()
		}
	}

	autoupdate.Configure(cfg)
	logging.Configure(cfg, version, buildDate)
	settings.Configure(cfg, version, buildDate)
	proxiedsites.Configure(cfg.ProxiedSites)

	if hqfd == nil {
		log.Errorf("No fronted dialer available, not enabling geolocation, stats or analytics")
	} else {
		hqfdc := hqfd.DirectHttpClient()
		geolookup.Configure(hqfdc)
		statserver.Configure(hqfdc)
		// start GA service
		analytics.Configure(cfg, false, hqfdc)
	}

	// Continually poll for config updates and update client accordingly
	go func() {
		for {
			cfg := <-configUpdates

			proxiedsites.Configure(cfg.ProxiedSites)
			// Note - we deliberately ignore the error from statreporter.Configure here
			statreporter.Configure(cfg.Stats)
			hqfd = client.Configure(cfg.Client)
			if hqfd != nil {
				hqfdc := hqfd.DirectHttpClient()
				geolookup.Configure(hqfdc)
				statserver.Configure(hqfdc)
				logging.Configure(cfg, version, buildDate)
				autoupdate.Configure(cfg)
			}
		}
	}()

	watchDirectAddrs()

	go func() {
		exit(client.ListenAndServe(pacOn))
	}()
	log.Debug("Ran goroutine")
}

// Runs the server-side proxy
func runServerProxy(cfg *config.Config) {
	useAllCores()

	pkFile, err := config.InConfigDir("proxypk.pem")
	if err != nil {
		log.Fatal(err)
	}
	certFile, err := config.InConfigDir("servercert.pem")
	if err != nil {
		log.Fatal(err)
	}

	srv := &server.Server{
		Addr:         cfg.Addr,
		ReadTimeout:  0, // don't timeout
		WriteTimeout: 0,
		CertContext: &fronted.CertContext{
			PKFile:         pkFile,
			ServerCertFile: certFile,
		},
		AllowedPorts: []int{80, 443, 8080, 8443, 5222, 5223, 5228},

		// We allow all censored countries plus us, es and mx because we do work
		// and testing from those countries.
		AllowedCountries: []string{"US", "ES", "MX", "CN", "VN", "IN", "IQ", "IR", "CU", "SY", "SA", "BH", "ET", "ER", "UZ", "TM", "PK", "TR", "VE"},
	}

	srv.Configure(cfg.Server)
	analytics.Configure(nil, true, nil)

	// Continually poll for config updates and update server accordingly
	go func() {
		for {
			cfg := <-configUpdates
			statreporter.Configure(cfg.Stats)
			srv.Configure(cfg.Server)
		}
	}()

	err = srv.ListenAndServe(func(update func(*server.ServerConfig) error) {
		err := config.Update(func(cfg *config.Config) error {
			return update(cfg.Server)
		})
		if err != nil {
			log.Errorf("Error while trying to update: %v", err)
		}
	})
	if err != nil {
		log.Fatalf("Unable to run server proxy: %s", err)
	}
}

func useAllCores() {
	numcores := runtime.NumCPU()
	log.Debugf("Using all %d cores on machine", numcores)
	runtime.GOMAXPROCS(numcores)
}

func configureSystemTray() error {
	icon, err := Asset("icons/16on.ico")
	if err != nil {
		return fmt.Errorf("Unable to load icon for system tray: %v", err)
	}
	systray.SetIcon(icon)
	systray.SetTooltip("Lantern")
	show := systray.AddMenuItem(i18n.T("TRAY_SHOW_LANTERN"), i18n.T("SHOW"))
	quit := systray.AddMenuItem(i18n.T("TRAY_QUIT"), i18n.T("QUIT"))
	go func() {
		for {
			select {
			case <-show.ClickedCh:
				ui.Show()
			case <-quit.ClickedCh:
				exit(nil)
				return
			}
		}
	}()

	return nil
}

// exit tells the application to exit, optionally supplying an error that caused
// the exit.
func exit(err error) {
	exitCh <- err
}

// WaitForExit waits for a request to exit the application.
func waitForExit() error {
	return <-exitCh
}
<|endoftext|>


<commit_before>// Package gzip implements a gzip compression handler middleware for Negroni.
package gzip

import (
	"compress/gzip"
	"net/http"
	"strings"

	"github.com/codegangsta/negroni"
)

// These compression constants are copied from the compress/gzip package.
const (
	encodingGzip = "gzip"

	headerAcceptEncoding  = "Accept-Encoding"
	headerContentEncoding = "Content-Encoding"
	headerContentLength   = "Content-Length"
	headerContentType     = "Content-Type"
	headerVary            = "Vary"
	headerSecWebSocketKey = "Sec-WebSocket-Key"

	BestCompression    = gzip.BestCompression
	BestSpeed          = gzip.BestSpeed
	DefaultCompression = gzip.DefaultCompression
	NoCompression      = gzip.NoCompression
)

// gzipResponseWriter is the ResponseWriter that negroni.ResponseWriter is
// wrapped in.
type gzipResponseWriter struct {
	w *gzip.Writer
	negroni.ResponseWriter
}

// Write writes bytes to the gzip.Writer. It will also set the Content-Type
// header using the net/http library content type detection if the Content-Type
// header was not set yet.
func (grw gzipResponseWriter) Write(b []byte) (int, error) {
	if len(grw.Header().Get(headerContentType)) == 0 {
		grw.Header().Set(headerContentType, http.DetectContentType(b))
	}
	return grw.w.Write(b)
}

// handler struct contains the ServeHTTP method and the compressionLevel to be
// used.
type handler struct {
	compressionLevel int
}

// Gzip returns a handler which will handle the Gzip compression in ServeHTTP.
// Valid values for level are identical to those in the compress/gzip package.
func Gzip(level int) *handler {
	return &handler{
		compressionLevel: level,
	}
}

// ServeHTTP wraps the http.ResponseWriter with a gzip.Writer.
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	// Skip compression if the client doesn't accept gzip encoding.
	if !strings.Contains(r.Header.Get(headerAcceptEncoding), encodingGzip) {
		next(w, r)
		return
	}

	// Skip compression if client attempt WebSocket connection
	if len(r.Header.Get(headerSecWebSocketKey)) > 0 {
		next(w, r)
		return
	}

	// Skip compression if already comprssed
	if w.Header().Get(headerContentEncoding) == encodingGzip {
		next(w, r)
		return
	}

	// Create new gzip Writer. Skip compression if an invalid compression
	// level was set.
	gz, err := gzip.NewWriterLevel(w, h.compressionLevel)
	if err != nil {
		next(w, r)
		return
	}
	defer gz.Close()

	// Set the appropriate gzip headers.
	headers := w.Header()
	headers.Set(headerContentEncoding, encodingGzip)
	headers.Set(headerVary, headerAcceptEncoding)

	// Wrap the original http.ResponseWriter with negroni.ResponseWriter
	// and create the gzipResponseWriter.
	nrw := negroni.NewResponseWriter(w)
	grw := gzipResponseWriter{
		gz,
		nrw,
	}

	// Call the next handler supplying the gzipResponseWriter instead of
	// the original.
	next(grw, r)

	// Delete the content length after we know we have been written to.
	grw.Header().Del(headerContentLength)
}
<commit_msg>Whitespace clean up and typo fix.<commit_after>// Package gzip implements a gzip compression handler middleware for Negroni.
package gzip

import (
	"compress/gzip"
	"net/http"
	"strings"

	"github.com/codegangsta/negroni"
)

// These compression constants are copied from the compress/gzip package.
const (
	encodingGzip = "gzip"

	headerAcceptEncoding  = "Accept-Encoding"
	headerContentEncoding = "Content-Encoding"
	headerContentLength   = "Content-Length"
	headerContentType     = "Content-Type"
	headerVary            = "Vary"
	headerSecWebSocketKey = "Sec-WebSocket-Key"

	BestCompression    = gzip.BestCompression
	BestSpeed          = gzip.BestSpeed
	DefaultCompression = gzip.DefaultCompression
	NoCompression      = gzip.NoCompression
)

// gzipResponseWriter is the ResponseWriter that negroni.ResponseWriter is
// wrapped in.
type gzipResponseWriter struct {
	w *gzip.Writer
	negroni.ResponseWriter
}

// Write writes bytes to the gzip.Writer. It will also set the Content-Type
// header using the net/http library content type detection if the Content-Type
// header was not set yet.
func (grw gzipResponseWriter) Write(b []byte) (int, error) {
	if len(grw.Header().Get(headerContentType)) == 0 {
		grw.Header().Set(headerContentType, http.DetectContentType(b))
	}
	return grw.w.Write(b)
}

// handler struct contains the ServeHTTP method and the compressionLevel to be
// used.
type handler struct {
	compressionLevel int
}

// Gzip returns a handler which will handle the Gzip compression in ServeHTTP.
// Valid values for level are identical to those in the compress/gzip package.
func Gzip(level int) *handler {
	return &handler{
		compressionLevel: level,
	}
}

// ServeHTTP wraps the http.ResponseWriter with a gzip.Writer.
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	// Skip compression if the client doesn't accept gzip encoding.
	if !strings.Contains(r.Header.Get(headerAcceptEncoding), encodingGzip) {
		next(w, r)
		return
	}

	// Skip compression if client attempt WebSocket connection
	if len(r.Header.Get(headerSecWebSocketKey)) > 0 {
		next(w, r)
		return
	}

	// Skip compression if already compressed
	if w.Header().Get(headerContentEncoding) == encodingGzip {
		next(w, r)
		return
	}

	// Create new gzip Writer. Skip compression if an invalid compression
	// level was set.
	gz, err := gzip.NewWriterLevel(w, h.compressionLevel)
	if err != nil {
		next(w, r)
		return
	}
	defer gz.Close()

	// Set the appropriate gzip headers.
	headers := w.Header()
	headers.Set(headerContentEncoding, encodingGzip)
	headers.Set(headerVary, headerAcceptEncoding)

	// Wrap the original http.ResponseWriter with negroni.ResponseWriter
	// and create the gzipResponseWriter.
	nrw := negroni.NewResponseWriter(w)
	grw := gzipResponseWriter{
		gz,
		nrw,
	}

	// Call the next handler supplying the gzipResponseWriter instead of
	// the original.
	next(grw, r)

	// Delete the content length after we know we have been written to.
	grw.Header().Del(headerContentLength)
}
<|endoftext|>


<commit_before>//go:build integration
// +build integration

/*
Copyright 2021 The Kubernetes Authors All rights reserved.

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 integration

import (
	"context"
	"fmt"
	"os/exec"
	"strings"
	"testing"
)

const (
	mountGID   = "0"
	mountMSize = "6543"
	mountMode  = "0777"
	mountPort  = "46464"
	mountUID   = "0"
)

// TestMountStart tests using the mount command on start
func TestMountStart(t *testing.T) {
	if NoneDriver() {
		t.Skip("skipping: none driver does not support mount")
	}

	type validateFunc func(context.Context, *testing.T, string)
	profile1 := UniqueProfileName("mount-start-1")
	profile2 := UniqueProfileName("mount-start-2")
	ctx, cancel := context.WithTimeout(context.Background(), Minutes(15))
	defer Cleanup(t, profile1, cancel)
	defer Cleanup(t, profile2, cancel)

	// Serial tests
	t.Run("serial", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
			profile   string
		}{
			{"StartWithMountFirst", validateStartWithMount, profile1},
			{"StartWithMountSecond", validateStartWithMount, profile2},
			{"VerifyMountFirst", validateMount, profile1},
			{"VerifyMountSecond", validateMount, profile2},
			{"DeleteFirst", validateDelete, profile1},
			{"VerifyMountPostDelete", validateMount, profile2},
			{"Stop", validateMountStop, profile2},
			{"RestartStopped", validateRestart, profile2},
			{"VerifyMountPostStop", validateMount, profile2},
		}

		for _, test := range tests {
			if ctx.Err() == context.DeadlineExceeded {
				t.Fatalf("Unable to run more tests (deadline exceeded)")
			}

			t.Run(test.name, func(t *testing.T) {
				test.validator(ctx, t, test.profile)
			})
		}
	})
}

// validateStartWithMount starts a cluster with mount enabled
func validateStartWithMount(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"start", "-p", profile, "--memory=2048", "--mount", "--mount-gid", mountGID, "--mount-msize", mountMSize, "--mount-mode", mountMode, "--mount-port", mountPort, "--mount-uid", mountUID}
	args = append(args, StartArgs()...)
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to start minikube with args: %q : %v", rr.Command(), err)
	}
}

// validateMount checks if the cluster has a folder mounted
func validateMount(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	sshArgs := []string{"-p", profile, "ssh", "--"}

	args := sshArgs
	args = append(args, "ls", "/minikube-host")
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("mount failed: %q : %v", rr.Command(), err)
	}

	args = sshArgs
	args = append(args, "stat", "--format", "'%a'", "/minikube-host")
	rr, err = Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to get directory mode: %v", err)
	}

	want := "777"
	if !strings.Contains(rr.Output(), want) {
		t.Errorf("wanted mode to be %q; got: %q", want, rr.Output())
	}

	args = sshArgs
	args = append(args, "mount", "|", "grep", "9p")
	rr, err = Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to get mount information: %v", err)
	}

	flags := []struct {
		key      string
		expected string
	}{
		{"gid", mountGID},
		{"msize", mountMSize},
		{"port", mountPort},
		{"uid", mountUID},
	}

	for _, flag := range flags {
		want := fmt.Sprintf("%s=%s", flag.key, flag.expected)
		if !strings.Contains(rr.Output(), want) {
			t.Errorf("wanted gid to be: %q; got: %q", want, rr.Output())
		}
	}
}

// validateMountStop stops a cluster
func validateMountStop(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"stop", "-p", profile}
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("stop failed: %q : %v", rr.Command(), err)
	}
}

// validateRestart restarts a cluster
func validateRestart(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"start", "-p", profile}
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("restart failed: %q : %v", rr.Command(), err)
	}
}
<commit_msg>skip checking mounting flags with Docker<commit_after>//go:build integration
// +build integration

/*
Copyright 2021 The Kubernetes Authors All rights reserved.

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 integration

import (
	"context"
	"fmt"
	"os/exec"
	"strings"
	"testing"
)

const (
	mountGID   = "0"
	mountMSize = "6543"
	mountMode  = "0777"
	mountPort  = "46464"
	mountUID   = "0"
)

// TestMountStart tests using the mount command on start
func TestMountStart(t *testing.T) {
	if NoneDriver() {
		t.Skip("skipping: none driver does not support mount")
	}

	type validateFunc func(context.Context, *testing.T, string)
	profile1 := UniqueProfileName("mount-start-1")
	profile2 := UniqueProfileName("mount-start-2")
	ctx, cancel := context.WithTimeout(context.Background(), Minutes(15))
	defer Cleanup(t, profile1, cancel)
	defer Cleanup(t, profile2, cancel)

	// Serial tests
	t.Run("serial", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
			profile   string
		}{
			{"StartWithMountFirst", validateStartWithMount, profile1},
			{"StartWithMountSecond", validateStartWithMount, profile2},
			{"VerifyMountFirst", validateMount, profile1},
			{"VerifyMountSecond", validateMount, profile2},
			{"DeleteFirst", validateDelete, profile1},
			{"VerifyMountPostDelete", validateMount, profile2},
			{"Stop", validateMountStop, profile2},
			{"RestartStopped", validateRestart, profile2},
			{"VerifyMountPostStop", validateMount, profile2},
		}

		for _, test := range tests {
			if ctx.Err() == context.DeadlineExceeded {
				t.Fatalf("Unable to run more tests (deadline exceeded)")
			}

			t.Run(test.name, func(t *testing.T) {
				test.validator(ctx, t, test.profile)
			})
		}
	})
}

// validateStartWithMount starts a cluster with mount enabled
func validateStartWithMount(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"start", "-p", profile, "--memory=2048", "--mount", "--mount-gid", mountGID, "--mount-msize", mountMSize, "--mount-mode", mountMode, "--mount-port", mountPort, "--mount-uid", mountUID}
	args = append(args, StartArgs()...)
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to start minikube with args: %q : %v", rr.Command(), err)
	}
}

// validateMount checks if the cluster has a folder mounted
func validateMount(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	sshArgs := []string{"-p", profile, "ssh", "--"}

	args := sshArgs
	args = append(args, "ls", "/minikube-host")
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("mount failed: %q : %v", rr.Command(), err)
	}

	// Docker has it's own mounting method, it doesn't respect the mounting flags
	if DockerDriver() {
		return
	}

	args = sshArgs
	args = append(args, "stat", "--format", "'%a'", "/minikube-host")
	rr, err = Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to get directory mode: %v", err)
	}

	want := "777"
	if !strings.Contains(rr.Output(), want) {
		t.Errorf("wanted mode to be %q; got: %q", want, rr.Output())
	}

	args = sshArgs
	args = append(args, "mount", "|", "grep", "9p")
	rr, err = Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to get mount information: %v", err)
	}

	flags := []struct {
		key      string
		expected string
	}{
		{"gid", mountGID},
		{"msize", mountMSize},
		{"port", mountPort},
		{"uid", mountUID},
	}

	for _, flag := range flags {
		want := fmt.Sprintf("%s=%s", flag.key, flag.expected)
		if !strings.Contains(rr.Output(), want) {
			t.Errorf("wanted gid to be: %q; got: %q", want, rr.Output())
		}
	}
}

// validateMountStop stops a cluster
func validateMountStop(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"stop", "-p", profile}
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("stop failed: %q : %v", rr.Command(), err)
	}
}

// validateRestart restarts a cluster
func validateRestart(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)

	args := []string{"start", "-p", profile}
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("restart failed: %q : %v", rr.Command(), err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/codeskyblue/go-sh"
	"github.com/gorilla/mux"
	"github.com/gorilla/rpc"
	"github.com/gorilla/rpc/json"
)

const frontendTmpl = `
acl is{{.ACL}} hdr_beg(host) {{.Hostname}}
use_backend {{.Backend}} if is{{.ACL}}
`
const backendTmpl = `
backend {{.Backend}}
  server {{.Backend}} {{.Hostname}}:{{.Port}} check inter 10000
`

var confPath = "/usr/local/etc/haproxy/conf"
var haproxyPath = "/usr/local/etc/haproxy"

// Service to be added
type Service struct {
	// service name
	Name string
	// service port
	Port string
	// .example.com
	Domain string
}

// Services ...
type Services struct {
	Services []Service
}

// Haproxy ...
type Haproxy int

// Result ...
type Result int

// Add a frontend and backend
func (h *Haproxy) Add(r *http.Request, services *Services, result *Result) error {

	for _, service := range services.Services {
		sh.Command("rm", "-f", confPath+"/"+service.Name+".backend").Run()
		sh.Command("rm", "-f", confPath+"/"+service.Name+".frontend").Run()

		log.Printf("Add service %s%s:%s", service.Name, service.Domain, service.Port)
		data := struct {
			ACL      string
			Hostname string
			Backend  string
			Port     string
		}{
			strings.Title(service.Name),
			service.Name + service.Domain,
			service.Name,
			service.Port,
		}

		// Generate frontend entry
		tmpl := template.Must(template.New("frontend").Parse(frontendTmpl))
		f, err := os.OpenFile(confPath+"/"+service.Name+".frontend", os.O_CREATE|os.O_RDWR, 0777)
		if err != nil {
			*result = 0
			return err
		}
		// fill in the template
		err = tmpl.Execute(f, data)
		if err != nil {
			*result = 0
			return err
		}

		// Generate backend entry
		tmpl = template.Must(template.New("backend").Parse(backendTmpl))
		f, err = os.OpenFile(confPath+"/"+service.Name+".backend", os.O_CREATE|os.O_RDWR, 0777)
		if err != nil {
			*result = 0
			return err
		}
		err = tmpl.Execute(f, data)
		if err != nil {
			*result = 0
			return err
		}
	}

	//join all the configs
	if err := h.generateCfg(); err != nil {
		*result = 0
		return err
	}

	*result = 1
	return nil

}

// Remove a frontend and backend
func (h *Haproxy) Remove(r *http.Request, services *Services, result *Result) error {
	for _, service := range services.Services {
		log.Printf("Remove service %s.%s:%s", service.Name, service.Domain, service.Port)
		sh.Command("rm", "-f", confPath+"/"+service.Name+".backend").Run()
		sh.Command("rm", "-f", confPath+"/"+service.Name+".frontend").Run()
	}

	if err := h.generateCfg(); err != nil {
		*result = 0
		return err
	}

	*result = 1
	return nil

}

func (h *Haproxy) generateCfg() error {
	// if conf doesn't exist , create from default
	// if _, err := os.Stat(confPath); os.IsNotExist(err) {
	// 	_, err := sh.Command("cp", "-rf", "/usr/local/etc/haproxy/conf", confPath).Output()
	// 	if err != nil {
	// 		log.Println("error:", err.Error())
	// 	}
	// }

	// check if haproxy.cfg already exists and take a backup
	if _, err := os.Stat(haproxyPath + "/haproxy.cfg"); !os.IsNotExist(err) {
		currentTime := string(time.Now().Format("20060102150405"))
		err := os.Rename(haproxyPath+"/haproxy.cfg", haproxyPath+"/haproxy.cfg.BAK."+currentTime)
		if err != nil {
			return err
		}
	}

	var haproxyCfg []byte

	var partFunc = func(part string) {
		// walk all files in the directory
		filepath.Walk(confPath, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if !info.IsDir() && strings.HasSuffix(info.Name(), part) {
				b, err := ioutil.ReadFile(path)
				if err != nil {
					return err
				}
				haproxyCfg = append(haproxyCfg, b...)
			}
			return nil

		})
	}

	//append the configs in the following order
	parts := []string{".globalcfg", ".defaultcfg", ".frontendcfg", ".frontend", ".backend"}
	for i := range parts {
		partFunc(parts[i])
	}

	//write the file
	ioutil.WriteFile(haproxyPath+"/haproxy.cfg", haproxyCfg, 0777)

	// restart haproxy container
	session := sh.NewSession()
	//reload haproxy
	// haproxyName := os.Getenv("HAPROXY_CONTAINER_NAME")
	// out, err := session.Command("docker", "inspect", "-f", "{{.State.Running}}", haproxyName).Output()
	// if err != nil {
	// 	log.Println("error:", err.Error())
	// }
	// log.Println("Haproxy isRunning", string(out))
	// if strings.Contains(string(out), "false") {
	// 	log.Printf("Can't reload. %v is not running", haproxyName)
	// 	return nil
	// }

	// log.Println("Reloading haproxy container....", haproxyName)
	// out, err = session.Command("docker", "kill", "-s", "HUP", haproxyName).Output()
	// if err != nil {
	// 	log.Println("error:", err.Error())
	// }
	// log.Println("isReloaded: ", string(out))
	err := session.Command("/usr/bin/reload.sh").Run()
	if err != nil {
		return err
	}
	return nil

}

// Generate regenerates haproxy config from existing configs
func (h *Haproxy) Generate(r *http.Request, service *Service, result *Result) error {
	if err := h.generateCfg(); err != nil {
		*result = 0
		return err
	}

	*result = 1
	return nil

}

func main() {

	// if os.Getenv("HAPROXY_CONTAINER_NAME") == "" {
	// 	log.Println("Please set env HAPROXY_CONTAINER_NAME")
	// 	return
	// }

	//if running without docker
	if os.Getenv("CONF_PATH") != "" {
		confPath = os.Getenv("CONF_PATH")
	}

	if os.Getenv("HAPROXY_PATH") != "" {
		haproxyPath = os.Getenv("HAPROXY_PATH")
	}

	s := rpc.NewServer()
	s.RegisterCodec(json.NewCodec(), "application/json")
	haproxy := new(Haproxy)
	// generate default haproxy.cfg
	log.Println("Generating default haproxy.cfg")
	haproxy.generateCfg()
	s.RegisterService(haproxy, "")
	r := mux.NewRouter()
	r.Handle("/haproxy", s)
	http.ListenAndServe(":34015", r)

}
<commit_msg>using hostname from incoming hostname var<commit_after>package main

import (
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/codeskyblue/go-sh"
	"github.com/gorilla/mux"
	"github.com/gorilla/rpc"
	"github.com/gorilla/rpc/json"
)

const frontendTmpl = `
acl is{{.ACL}} hdr_beg(host) {{.Hostname}}
use_backend {{.Backend}} if is{{.ACL}}
`
const backendTmpl = `
backend {{.Backend}}
  server {{.Backend}} {{.Hostname}}:{{.Port}} check inter 10000
`

var confPath = "/usr/local/etc/haproxy/conf"
var haproxyPath = "/usr/local/etc/haproxy"

// Service to be added
type Service struct {
	// service name
	Name string
	// service port
	Port string
	// .example.com
	Domain string
	// storefront-services-1
	Hostname string
}

// Services ...
type Services struct {
	Services []Service
}

// Haproxy ...
type Haproxy int

// Result ...
type Result int

// Add a frontend and backend
func (h *Haproxy) Add(r *http.Request, services *Services, result *Result) error {

	for _, service := range services.Services {
		sh.Command("rm", "-f", confPath+"/"+service.Name+".backend").Run()
		sh.Command("rm", "-f", confPath+"/"+service.Name+".frontend").Run()

		log.Printf("Add service %s%s:%s", service.Name, service.Domain, service.Port)
		data := struct {
			ACL      string
			Hostname string
			Backend  string
			Port     string
		}{
			strings.Title(service.Name),
			service.Hostname + ".myntra.com",
			service.Name,
			service.Port,
		}

		// Generate frontend entry
		tmpl := template.Must(template.New("frontend").Parse(frontendTmpl))
		f, err := os.OpenFile(confPath+"/"+service.Name+".frontend", os.O_CREATE|os.O_RDWR, 0777)
		if err != nil {
			*result = 0
			return err
		}
		// fill in the template
		err = tmpl.Execute(f, data)
		if err != nil {
			*result = 0
			return err
		}

		// Generate backend entry
		tmpl = template.Must(template.New("backend").Parse(backendTmpl))
		f, err = os.OpenFile(confPath+"/"+service.Name+".backend", os.O_CREATE|os.O_RDWR, 0777)
		if err != nil {
			*result = 0
			return err
		}
		err = tmpl.Execute(f, data)
		if err != nil {
			*result = 0
			return err
		}
	}

	//join all the configs
	if err := h.generateCfg(); err != nil {
		*result = 0
		return err
	}

	*result = 1
	return nil

}

// Remove a frontend and backend
func (h *Haproxy) Remove(r *http.Request, services *Services, result *Result) error {
	for _, service := range services.Services {
		log.Printf("Remove service %s.%s:%s", service.Name, service.Domain, service.Port)
		sh.Command("rm", "-f", confPath+"/"+service.Name+".backend").Run()
		sh.Command("rm", "-f", confPath+"/"+service.Name+".frontend").Run()
	}

	if err := h.generateCfg(); err != nil {
		*result = 0
		return err
	}

	*result = 1
	return nil

}

func (h *Haproxy) generateCfg() error {
	// if conf doesn't exist , create from default
	// if _, err := os.Stat(confPath); os.IsNotExist(err) {
	// 	_, err := sh.Command("cp", "-rf", "/usr/local/etc/haproxy/conf", confPath).Output()
	// 	if err != nil {
	// 		log.Println("error:", err.Error())
	// 	}
	// }

	// check if haproxy.cfg already exists and take a backup
	if _, err := os.Stat(haproxyPath + "/haproxy.cfg"); !os.IsNotExist(err) {
		currentTime := string(time.Now().Format("20060102150405"))
		err := os.Rename(haproxyPath+"/haproxy.cfg", haproxyPath+"/haproxy.cfg.BAK."+currentTime)
		if err != nil {
			return err
		}
	}

	var haproxyCfg []byte

	var partFunc = func(part string) {
		// walk all files in the directory
		filepath.Walk(confPath, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if !info.IsDir() && strings.HasSuffix(info.Name(), part) {
				b, err := ioutil.ReadFile(path)
				if err != nil {
					return err
				}
				haproxyCfg = append(haproxyCfg, b...)
			}
			return nil

		})
	}

	//append the configs in the following order
	parts := []string{".globalcfg", ".defaultcfg", ".frontendcfg", ".frontend", ".backend"}
	for i := range parts {
		partFunc(parts[i])
	}

	//write the file
	ioutil.WriteFile(haproxyPath+"/haproxy.cfg", haproxyCfg, 0777)

	// restart haproxy container
	session := sh.NewSession()
	//reload haproxy
	// haproxyName := os.Getenv("HAPROXY_CONTAINER_NAME")
	// out, err := session.Command("docker", "inspect", "-f", "{{.State.Running}}", haproxyName).Output()
	// if err != nil {
	// 	log.Println("error:", err.Error())
	// }
	// log.Println("Haproxy isRunning", string(out))
	// if strings.Contains(string(out), "false") {
	// 	log.Printf("Can't reload. %v is not running", haproxyName)
	// 	return nil
	// }

	// log.Println("Reloading haproxy container....", haproxyName)
	// out, err = session.Command("docker", "kill", "-s", "HUP", haproxyName).Output()
	// if err != nil {
	// 	log.Println("error:", err.Error())
	// }
	// log.Println("isReloaded: ", string(out))
	err := session.Command("/usr/bin/reload.sh").Run()
	if err != nil {
		return err
	}
	return nil

}

// Generate regenerates haproxy config from existing configs
func (h *Haproxy) Generate(r *http.Request, service *Service, result *Result) error {
	if err := h.generateCfg(); err != nil {
		*result = 0
		return err
	}

	*result = 1
	return nil

}

func main() {

	// if os.Getenv("HAPROXY_CONTAINER_NAME") == "" {
	// 	log.Println("Please set env HAPROXY_CONTAINER_NAME")
	// 	return
	// }

	//if running without docker
	if os.Getenv("CONF_PATH") != "" {
		confPath = os.Getenv("CONF_PATH")
	}

	if os.Getenv("HAPROXY_PATH") != "" {
		haproxyPath = os.Getenv("HAPROXY_PATH")
	}

	s := rpc.NewServer()
	s.RegisterCodec(json.NewCodec(), "application/json")
	haproxy := new(Haproxy)
	// generate default haproxy.cfg
	log.Println("Generating default haproxy.cfg")
	haproxy.generateCfg()
	s.RegisterService(haproxy, "")
	r := mux.NewRouter()
	r.Handle("/haproxy", s)
	http.ListenAndServe(":34015", r)

}
<|endoftext|>


<commit_before>// Copyright 2015 realglobe, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hash

import (
	"crypto"
	"github.com/realglobe-Inc/go-lib/erro"
)

// related_users に入れるハッシュ値の文字列としての長さを返す。
func StringSize(alg string) (int, error) {
	switch alg {
	case "SHA256":
		return (128 + 5) / 6, nil
	case "SHA384":
		return (192 + 5) / 6, nil
	case "SHA512":
		return (256 + 5) / 6, nil
	default:
		return 0, erro.New("unsupported algorithm " + alg)
	}
}

func HashFunction(alg string) (crypto.Hash, error) {
	switch alg {
	case "SHA256":
		return crypto.SHA256, nil
	case "SHA384":
		return crypto.SHA384, nil
	case "SHA512":
		return crypto.SHA512, nil
	default:
		return 0, erro.New("unsupported algorithm " + alg)
	}
}
<commit_msg>ハッシュ値計算用関数を追加<commit_after>// Copyright 2015 realglobe, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// ハッシュ値計算用関数。
package hash

import (
	"crypto"
	"github.com/realglobe-Inc/go-lib/erro"
	"hash"
)

// related_users に入れるハッシュ値の文字列としての長さを返す。
func StringSize(alg string) (int, error) {
	switch alg {
	case "SHA256":
		return (128 + 5) / 6, nil
	case "SHA384":
		return (192 + 5) / 6, nil
	case "SHA512":
		return (256 + 5) / 6, nil
	default:
		return 0, erro.New("unsupported algorithm " + alg)
	}
}

func HashFunction(alg string) (crypto.Hash, error) {
	switch alg {
	case "SHA256":
		return crypto.SHA256, nil
	case "SHA384":
		return crypto.SHA384, nil
	case "SHA512":
		return crypto.SHA512, nil
	default:
		return 0, erro.New("unsupported algorithm " + alg)
	}
}

// ハッシュ値を計算して返す。
func Hashing(h hash.Hash, data ...[]byte) []byte {
	for _, d := range data {
		h.Write(d)
	}
	return h.Sum(nil)
}
<|endoftext|>


<commit_before>package durationfmt

import (
	"fmt"
	"time"
)

const Day = 24 * time.Hour
const Week = 7 * Day
const Year = 365 * Day

type duration struct {
	Present    bool
	DurDivisor time.Duration
}

//type durationUnits struct {
//	Years   duration
//	Weeks   duration
//	Days    duration
//	Hours   duration
//	Minutes duration
//	Seconds duration
//}

// Format formats the given duration according to the given format string.
// %y - # of years
// %w - # of weeks
// %d - # of days
// %h - # of hours
// %m - # of minutes
// %s - # of seconds
// %% - print a percent sign
func Format(dur time.Duration, fmtStr string) (string, error) {
	var durationUnits = map[string]*duration{
		"y": &duration{
			DurDivisor: Year,
		},
		"w": &duration{
			DurDivisor: Week,
		},
		"d": &duration{
			DurDivisor: Day,
		},
		"h": &duration{
			DurDivisor: time.Hour,
		},
		"m": &duration{
			DurDivisor: time.Minute,
		},
		"s": &duration{
			DurDivisor: time.Second,
		},
	}

	modifier, zeropad := false, false
	sprintfFmt := ""
	durCount := 0
	for _, c := range fmtStr {
		fmtChar := string(c)
		if modifier == false {
			if fmtChar == "%" {
				modifier = true
			} else {
				sprintfFmt += fmtChar
			}
			continue
		}
		if _, ok := durationUnits[fmtChar]; ok {
			durationUnits[fmtChar].Present = true
			durCount++
			if zeropad {
				sprintfFmt += "%02d"
				zeropad = false
			} else {
				sprintfFmt += "%d"
			}
		} else {
			switch fmtChar {
			case "0":
				zeropad = true
				continue
			case "%":
				sprintfFmt += "%%"
			default:
				return "", fmt.Errorf("incorrect duration modifier")
			}
		}
		modifier = false
	}

	remainingDur := dur
	durationArray := make([]interface{}, durCount)
	durCount = 0
	for _, c := range "ywdhms" {
		durChar := string(c)
		if durationUnits[durChar].Present {
			durationArray[durCount] = remainingDur / durationUnits[durChar].DurDivisor
			remainingDur = remainingDur % durationUnits[durChar].DurDivisor
			durCount++
		}
	}

	return fmt.Sprintf(sprintfFmt, durationArray...), nil
}
<commit_msg>code cleanup: modularized into functions, added documentation<commit_after>// durationfmt provides a function to format durations according to a format
// string.
package durationfmt

import (
	"fmt"
	"time"
)

const Day = 24 * time.Hour
const Week = 7 * Day
const Year = 365 * Day

// durationUnit represets a possible duration unit. A durationUnit object
// contains the divisor that the duration unit uses as well as if that duration
// unit is present in the duration format.
type durationUnit struct {
	Present    bool
	DurDivisor time.Duration
}

// Format formats the given duration according to the given format string.
// %y - # of years
// %w - # of weeks
// %d - # of days
// %h - # of hours
// %m - # of minutes
// %s - # of seconds
// %% - print a percent sign
// You can place a 0 before the h, m, and s modifiers to zeropad those values to
// two digits. Zeropadding is undefined for the other modifiers.
func Format(dur time.Duration, fmtStr string) (string, error) {
	var durUnits = map[string]*durationUnit{
		"y": &durationUnit{
			DurDivisor: Year,
		},
		"w": &durationUnit{
			DurDivisor: Week,
		},
		"d": &durationUnit{
			DurDivisor: Day,
		},
		"h": &durationUnit{
			DurDivisor: time.Hour,
		},
		"m": &durationUnit{
			DurDivisor: time.Minute,
		},
		"s": &durationUnit{
			DurDivisor: time.Second,
		},
	}

	sprintfFmt, durCount, err := parseFmtStr(fmtStr, durUnits)
	if err != nil {
		return "", err
	}

	durArray := make([]interface{}, durCount)
	calculateDurUnits(dur, durArray, durUnits)

	return fmt.Sprintf(sprintfFmt, durArray...), nil
}

// calculateDurUnits takes a duration and breaks it up into its constituent
// duration unit values.
func calculateDurUnits(dur time.Duration, durArray []interface{}, durUnits map[string]*durationUnit) {
	remainingDur := dur
	durCount := 0
	for _, c := range "ywdhms" {
		durChar := string(c)
		if durUnits[durChar].Present {
			durArray[durCount] = remainingDur / durUnits[durChar].DurDivisor
			remainingDur = remainingDur % durUnits[durChar].DurDivisor
			durCount++
		}
	}
}

// parseFmtStr parses the given duration format string into its constituent
// units.
// parseFmtStr returns a format string that can be passed to fmt.Sprintf and a
// count of how many duration units are in the format string.
func parseFmtStr(fmtStr string, durUnits map[string]*durationUnit) (string, int, error) {
	modifier, zeropad := false, false
	sprintfFmt := ""
	durCount := 0
	for _, c := range fmtStr {
		fmtChar := string(c)
		if modifier == false {
			if fmtChar == "%" {
				modifier = true
			} else {
				sprintfFmt += fmtChar
			}
			continue
		}
		if _, ok := durUnits[fmtChar]; ok {
			durUnits[fmtChar].Present = true
			durCount++
			if zeropad {
				sprintfFmt += "%02d"
				zeropad = false
			} else {
				sprintfFmt += "%d"
			}
		} else {
			switch fmtChar {
			case "0":
				zeropad = true
				continue
			case "%":
				sprintfFmt += "%%"
			default:
				return "", durCount, fmt.Errorf("incorrect duration modifier")
			}
		}
		modifier = false
	}
	return sprintfFmt, durCount, nil
}
<|endoftext|>


<commit_before>package dockerpty

import (
	"errors"
	"github.com/fgrehm/go-dockerpty/term"
	"github.com/fsouza/go-dockerclient"
	"io"
	"os"
)

func Start(client *docker.Client, container *docker.Container, hostConfig *docker.HostConfig) (err error) {
	var (
		terminalFd uintptr
		oldState   *term.State
		out        io.Writer = os.Stdout
	)

	if file, ok := out.(*os.File); ok {
		terminalFd = file.Fd()
	} else {
		return errors.New("Not a terminal!")
	}

	// Set up the pseudo terminal
	oldState, err = term.SetRawTerminal(terminalFd)
	if err != nil {
		return
	}

	// Start it
	err = client.StartContainer(container.ID, hostConfig)
	if err != nil {
		return err
	}

	// Attach to the container
	attachToContainer(client, container.ID)

	// Clean up after the container has exited
	defer term.RestoreTerminal(terminalFd, oldState)

	return err
}

func attachToContainer(client *docker.Client, containerID string) {
	client.AttachToContainer(docker.AttachToContainerOptions{
		Container:    containerID,
		InputStream:  os.Stdin,
		OutputStream: os.Stdout,
		ErrorStream:  os.Stderr,
		Stdin:        true,
		Stdout:       true,
		Stderr:       true,
		Stream:       true,
		RawTerminal:  true,
	})
}
<commit_msg>Defer terminal restore before starting the container so that it gets cleaned up if an error occurs<commit_after>package dockerpty

import (
	"errors"
	"github.com/fgrehm/go-dockerpty/term"
	"github.com/fsouza/go-dockerclient"
	"io"
	"os"
)

func Start(client *docker.Client, container *docker.Container, hostConfig *docker.HostConfig) (err error) {
	var (
		terminalFd uintptr
		oldState   *term.State
		out        io.Writer = os.Stdout
	)

	if file, ok := out.(*os.File); ok {
		terminalFd = file.Fd()
	} else {
		return errors.New("Not a terminal!")
	}

	// Set up the pseudo terminal
	oldState, err = term.SetRawTerminal(terminalFd)
	if err != nil {
		return
	}

	// Clean up after the container has exited
	defer term.RestoreTerminal(terminalFd, oldState)

	// Start it
	err = client.StartContainer(container.ID, hostConfig)
	if err != nil {
		return err
	}

	// Attach to the container
	attachToContainer(client, container.ID)

	return err
}

func attachToContainer(client *docker.Client, containerID string) {
	client.AttachToContainer(docker.AttachToContainerOptions{
		Container:    containerID,
		InputStream:  os.Stdin,
		OutputStream: os.Stdout,
		ErrorStream:  os.Stderr,
		Stdin:        true,
		Stdout:       true,
		Stderr:       true,
		Stream:       true,
		RawTerminal:  true,
	})
}
<|endoftext|>


<commit_before>package fuse

import (
	"io/ioutil"
	"os"
	"os/signal"
	pathpkg "path"
	"path/filepath"
	"strings"

	"github.com/hanwen/go-fuse/v2/fs"

	"github.com/pachyderm/pachyderm/src/client"
	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
	"github.com/pachyderm/pachyderm/src/server/pkg/progress"
	"github.com/pachyderm/pachyderm/src/server/pkg/uuid"
)

// Mount pfs to target, opts may be left nil.
func Mount(c *client.APIClient, target string, opts *Options) (retErr error) {
	if err := opts.validate(c); err != nil {
		return err
	}
	commits := make(map[string]string)
	for repo, branch := range opts.getBranches() {
		if uuid.IsUUIDWithoutDashes(branch) {
			commits[repo] = branch
		}
	}
	// branches := opts.getBranches()
	rootDir, err := ioutil.TempDir("", "pfs")
	if err != nil {
		return err
	}
	defer func() {
		if err := os.RemoveAll(rootDir); err != nil && retErr == nil {
			retErr = errors.WithStack(err)
		}
	}()
	root, err := newLoopbackRoot(rootDir, target, c, opts)
	if err != nil {
		return err
	}
	server, err := fs.Mount(target, root, opts.getFuse())
	if err != nil {
		return errors.WithStack(err)
	}
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt)
	go func() {
		select {
		case <-sigChan:
		case <-opts.getUnmount():
		}
		server.Unmount()
	}()
	server.Serve()
	pfcs := make(map[string]client.PutFileClient)
	pfc := func(repo string) (client.PutFileClient, error) {
		if pfc, ok := pfcs[repo]; ok {
			return pfc, nil
		}
		pfc, err := c.NewPutFileClient()
		if err != nil {
			return nil, err
		}
		pfcs[repo] = pfc
		return pfc, nil
	}
	defer func() {
		for _, pfc := range pfcs {
			if err := pfc.Close(); err != nil && retErr == nil {
				retErr = err
			}
		}
	}()
	for path, state := range root.files {
		if state != dirty {
			continue
		}
		parts := strings.Split(path, "/")
		pfc, err := pfc(parts[0])
		if err != nil {
			return err
		}
		if err := func() (retErr error) {
			f, err := progress.Open(filepath.Join(root.rootPath, path))
			if err != nil {
				if os.IsNotExist(err) {
					return pfc.DeleteFile(parts[0], root.branch(parts[0]), pathpkg.Join(parts[1:]...))
				}
				return errors.Wrap(err, "os.Open")
			}
			defer func() {
				if err := f.Close(); err != nil && retErr == nil {
					retErr = errors.Wrap(err, "f.Close")
				}
			}()
			if _, err := pfc.PutFileOverwrite(parts[0], root.branch(parts[0]),
				pathpkg.Join(parts[1:]...), f, 0); err != nil {
				return err
			}
			return nil
		}(); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Stackify a few more errors.<commit_after>package fuse

import (
	"io/ioutil"
	"os"
	"os/signal"
	pathpkg "path"
	"path/filepath"
	"strings"

	"github.com/hanwen/go-fuse/v2/fs"

	"github.com/pachyderm/pachyderm/src/client"
	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
	"github.com/pachyderm/pachyderm/src/server/pkg/progress"
	"github.com/pachyderm/pachyderm/src/server/pkg/uuid"
)

// Mount pfs to target, opts may be left nil.
func Mount(c *client.APIClient, target string, opts *Options) (retErr error) {
	if err := opts.validate(c); err != nil {
		return err
	}
	commits := make(map[string]string)
	for repo, branch := range opts.getBranches() {
		if uuid.IsUUIDWithoutDashes(branch) {
			commits[repo] = branch
		}
	}
	// branches := opts.getBranches()
	rootDir, err := ioutil.TempDir("", "pfs")
	if err != nil {
		return errors.WithStack(err)
	}
	defer func() {
		if err := os.RemoveAll(rootDir); err != nil && retErr == nil {
			retErr = errors.WithStack(err)
		}
	}()
	root, err := newLoopbackRoot(rootDir, target, c, opts)
	if err != nil {
		return err
	}
	server, err := fs.Mount(target, root, opts.getFuse())
	if err != nil {
		return errors.WithStack(err)
	}
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt)
	go func() {
		select {
		case <-sigChan:
		case <-opts.getUnmount():
		}
		server.Unmount()
	}()
	server.Serve()
	pfcs := make(map[string]client.PutFileClient)
	pfc := func(repo string) (client.PutFileClient, error) {
		if pfc, ok := pfcs[repo]; ok {
			return pfc, nil
		}
		pfc, err := c.NewPutFileClient()
		if err != nil {
			return nil, err
		}
		pfcs[repo] = pfc
		return pfc, nil
	}
	defer func() {
		for _, pfc := range pfcs {
			if err := pfc.Close(); err != nil && retErr == nil {
				retErr = err
			}
		}
	}()
	for path, state := range root.files {
		if state != dirty {
			continue
		}
		parts := strings.Split(path, "/")
		pfc, err := pfc(parts[0])
		if err != nil {
			return err
		}
		if err := func() (retErr error) {
			f, err := progress.Open(filepath.Join(root.rootPath, path))
			if err != nil {
				if os.IsNotExist(err) {
					return pfc.DeleteFile(parts[0], root.branch(parts[0]), pathpkg.Join(parts[1:]...))
				}
				return errors.WithStack(err)
			}
			defer func() {
				if err := f.Close(); err != nil && retErr == nil {
					retErr = errors.WithStack(err)
				}
			}()
			if _, err := pfc.PutFileOverwrite(parts[0], root.branch(parts[0]),
				pathpkg.Join(parts[1:]...), f, 0); err != nil {
				return err
			}
			return nil
		}(); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package edit

import (
	"fmt"
	"os"
	"time"
)

var (
	// EscSequenceTimeout is the amount of time within which runes that make up
	// an escape sequence are supposed to follow each other. Modern terminal
	// emulators send escape sequences very fast, so 10ms is more than
	// sufficient. SSH connections on a slow link might be problematic though.
	EscSequenceTimeout = 10 * time.Millisecond
)

// Special rune values used in the return value of (*Reader).ReadRune.
const (
	// No rune received before specified time.
	runeTimeout rune = -1 - iota
	// Error occured in AsyncReader. The error is left at the readError field.
	runeReadError
)

// Reader converts a stream of runes into a stream of OneRead's.
type Reader struct {
	ar        *AsyncReader
	keyChan   chan Key
	cprChan   chan pos
	mouseChan chan mouseEvent
	errChan   chan error
	quit      chan struct{}
}

type mouseEvent struct {
	pos
	down   bool
	button int
	mod    Mod
}

// NewReader creates a new Reader on the given terminal file.
func NewReader(f *os.File) *Reader {
	rd := &Reader{
		NewAsyncReader(f),
		make(chan Key),
		make(chan pos),
		make(chan mouseEvent),
		make(chan error),
		nil,
	}
	return rd
}

// KeyChan returns the channel onto which the Reader writes Keys it has read.
func (rd *Reader) KeyChan() <-chan Key {
	return rd.keyChan
}

// CPRChan returns the channel onto which the Reader writes CPRs it has read.
func (rd *Reader) CPRChan() <-chan pos {
	return rd.cprChan
}

// MouseChan returns the channel onto which the Reader writes mouse events it
// has read.
func (rd *Reader) MouseChan() <-chan mouseEvent {
	return rd.mouseChan
}

// ErrorChan returns the channel onto which the Reader writes errors it came
// across during the reading process.
func (rd *Reader) ErrorChan() <-chan error {
	return rd.errChan
}

// Run runs the Reader. It blocks until Quit is called and should be called in
// a separate goroutine.
func (rd *Reader) Run() {
	runes := rd.ar.Chan()
	rd.quit = make(chan struct{})
	go rd.ar.Run()

	for {
		select {
		case r := <-runes:
			rd.readOne(r)
		case <-rd.quit:
			return
		}
	}
}

// Quit terminates the loop of Run.
func (rd *Reader) Quit() {
	rd.ar.Quit()
	close(rd.quit)
}

// Close releases files associated with the Reader. It does not close the file
// used to create it.
func (rd *Reader) Close() {
	rd.ar.Close()
}

// readOne attempts to read one key or CPR, led by a rune already read.
func (rd *Reader) readOne(r rune) {
	var k Key
	var cpr pos
	var mouse mouseEvent
	var err error
	currentSeq := string(r)

	badSeq := func(msg string) {
		err = fmt.Errorf("%s: %q", msg, currentSeq)
	}

	// readRune attempts to read a rune within EscSequenceTimeout. It writes to
	// the err and currentSeq variable in the outer scope.
	readRune :=
		func() rune {
			select {
			case r := <-rd.ar.Chan():
				currentSeq += string(r)
				return r
			case err = <-rd.ar.ErrorChan():
				return runeReadError
			case <-time.After(EscSequenceTimeout):
				return runeTimeout
			}
		}

	defer func() {
		if k != (Key{}) {
			select {
			case rd.keyChan <- k:
			case <-rd.quit:
			}
		} else if cpr != (pos{}) {
			select {
			case rd.cprChan <- cpr:
			case <-rd.quit:
			}
		} else if mouse != (mouseEvent{}) {
			select {
			case rd.mouseChan <- mouse:
			case <-rd.quit:
			}
		}
		if err != nil {
			select {
			case rd.errChan <- err:
			case <-rd.quit:
			}
		}
	}()

	switch r {
	case Tab, Enter, Backspace:
		k = Key{r, 0}
	case 0x0:
		k = Key{'`', Ctrl} // ^@
	case 0x1d:
		k = Key{'6', Ctrl} // ^^
	case 0x1f:
		k = Key{'/', Ctrl} // ^_
	case 0x1b: // ^[ Escape
		r2 := readRune()
		if r2 == runeTimeout || r2 == runeReadError {
			k = Key{'[', Ctrl}
			break
		}
		switch r2 {
		case '[':
			// CSI style function key sequence.
			r = readRune()
			if r == runeTimeout || r == runeReadError {
				k = Key{'[', Alt}
				return
			}

			nums := make([]int, 0, 2)
			var starter rune

			// Read an optional starter.
			switch r {
			case 'M', '<':
				starter = r
				r = readRune()
			}
		CSISeq:
			for {
				switch {
				case r == ';':
					nums = append(nums, 0)
				case '0' <= r && r <= '9':
					if len(nums) == 0 {
						nums = append(nums, 0)
					}
					cur := len(nums) - 1
					nums[cur] = nums[cur]*10 + int(r-'0')
				case r == runeTimeout:
					// Incomplete CSI.
					badSeq("Incomplete CSI")
					return
				case r == runeReadError:
					// TODO Also complain about incomplte CSI.
					return
				default: // Treat as a terminator.
					break CSISeq
				}

				r = readRune()
			}
			if starter == 0 && r == 'R' {
				// Cursor position report.
				if len(nums) != 2 {
					badSeq("bad CPR")
					return
				}
				cpr = pos{nums[0], nums[1]}
			} else if starter == '<' && (r == 'm' || r == 'M') {
				// SGR-style mouse event.
				if len(nums) != 3 {
					badSeq("bad SGR mouse event")
					return
				}
				down := r == 'M'
				n0 := nums[0]
				button := n0 & 3
				mod := Mod(0)
				if n0&4 != 0 {
					mod |= Shift
				}
				if n0&8 != 0 {
					mod |= Alt
				}
				if n0&16 != 0 {
					mod |= Ctrl
				}
				mouse = mouseEvent{pos{nums[2], nums[1]}, down, button, mod}
			} else {
				k = parseCSI(nums, r, currentSeq)
				if k == (Key{}) {
					badSeq("bad CSI")
				}
			}
		case 'O':
			// G3 style function key sequence: read one rune.
			r = readRune()
			if r == runeTimeout || r == runeReadError {
				k = Key{r2, Alt}
				return
			}
			r, ok := g3Seq[r]
			if ok {
				k = Key{r, 0}
			} else {
				badSeq("bad G3")
			}
		default:
			k = Key{r2, Alt}
		}
	default:
		// Regular Ctrl sequences.
		if 0x1 <= r && r <= 0x1d {
			k = Key{r + 0x40, Ctrl}
		} else {
			k = Key{r, 0}
		}
	}
}

// G3-style key sequences: \eO followed by exactly one character. For instance,
// \eOP is F1.
var g3Seq = map[rune]rune{
	// F1-F4: xterm, libvte and tmux
	'P': F1, 'Q': F2,
	'R': F3, 'S': F4,

	// Home and End: libvte
	'H': Home, 'F': End,
}

// Tables for CSI-style key sequences, which are \e[ followed by a list of
// semicolon-delimited numeric arguments, before being concluded by a
// non-numeric, non-semicolon rune.

// CSI-style key sequences that can be identified based on the ending rune. For
// instance, \e[A is Up.
var keyByLast = map[rune]Key{
	'A': Key{Up, 0}, 'B': Key{Down, 0},
	'C': Key{Right, 0}, 'D': Key{Left, 0},
	'H': Key{Home, 0}, 'F': Key{End, 0},
	'Z': Key{Tab, Shift},
}

// CSI-style key sequences ending with '~' and can be identified based on
// the only number argument. For instance, \e[1~ is Home.
var keyByNum0 = map[int]rune{
	1: Home, 2: Insert, 3: Delete, 4: End, 5: PageUp, 6: PageDown,
	11: F1, 12: F2, 13: F3, 14: F4,
	15: F5, 17: F6, 18: F7, 19: F8, 20: F9, 21: F10, 23: F11, 24: F12,
}

// CSI-style key sequences ending with '~', with 27 as the first numeric
// argument. For instance, \e[27;9~ is Tab.
//
// The list is taken blindly from tmux source xterm-keys.c. I don't have a
// keyboard-terminal combination that generate such sequences, but assumably
// some PC keyboard with a numpad can.
var keyByNum2 = map[int]rune{
	9: '\t', 13: '\r',
	33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-',
	46: '.',
	48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7',
	56: '8', 57: '9',
	58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';',
}

// parseCSI parses a CSI-style key sequence.
func parseCSI(nums []int, last rune, seq string) Key {
	if k, ok := keyByLast[last]; ok {
		if len(nums) == 0 {
			// Unmodified: \e[A (Up)
			return k
		} else if len(nums) == 2 && nums[0] == 1 {
			// Modified: \e[1;5A (Ctrl-Up)
			return xtermModify(k, nums[1], seq)
		} else {
			return Key{}
		}
	}

	if last == '~' {
		if len(nums) == 1 || len(nums) == 2 {
			if r, ok := keyByNum0[nums[0]]; ok {
				k := Key{r, 0}
				if len(nums) == 1 {
					// Unmodified: \e[5~ (PageUp)
					return k
				}
				// Modified: \e[5;5~ (Ctrl-PageUp)
				return xtermModify(k, nums[1], seq)
			}
		} else if len(nums) == 3 && nums[0] == 27 {
			if r, ok := keyByNum2[nums[2]]; ok {
				k := Key{r, 0}
				return xtermModify(k, nums[1], seq)
			}
		}
	}

	return Key{}
}

func xtermModify(k Key, mod int, seq string) Key {
	switch mod {
	case 0:
		// do nothing
	case 2:
		k.Mod |= Shift
	case 3:
		k.Mod |= Alt
	case 4:
		k.Mod |= Shift | Alt
	case 5:
		k.Mod |= Ctrl
	case 6:
		k.Mod |= Shift | Ctrl
	case 7:
		k.Mod |= Alt | Ctrl
	case 8:
		k.Mod |= Shift | Alt | Ctrl
	default:
		return Key{}
	}
	return k
}
<commit_msg>edit: read "normal" mouse event as well.<commit_after>package edit

import (
	"fmt"
	"os"
	"time"
)

var (
	// EscSequenceTimeout is the amount of time within which runes that make up
	// an escape sequence are supposed to follow each other. Modern terminal
	// emulators send escape sequences very fast, so 10ms is more than
	// sufficient. SSH connections on a slow link might be problematic though.
	EscSequenceTimeout = 10 * time.Millisecond
)

// Special rune values used in the return value of (*Reader).ReadRune.
const (
	// No rune received before specified time.
	runeTimeout rune = -1 - iota
	// Error occured in AsyncReader. The error is left at the readError field.
	runeReadError
)

// Reader converts a stream of runes into a stream of OneRead's.
type Reader struct {
	ar        *AsyncReader
	keyChan   chan Key
	cprChan   chan pos
	mouseChan chan mouseEvent
	errChan   chan error
	quit      chan struct{}
}

type mouseEvent struct {
	pos
	down bool
	// Number of the button, 0-based. -1 for unknown.
	button int
	mod    Mod
}

// NewReader creates a new Reader on the given terminal file.
func NewReader(f *os.File) *Reader {
	rd := &Reader{
		NewAsyncReader(f),
		make(chan Key),
		make(chan pos),
		make(chan mouseEvent),
		make(chan error),
		nil,
	}
	return rd
}

// KeyChan returns the channel onto which the Reader writes Keys it has read.
func (rd *Reader) KeyChan() <-chan Key {
	return rd.keyChan
}

// CPRChan returns the channel onto which the Reader writes CPRs it has read.
func (rd *Reader) CPRChan() <-chan pos {
	return rd.cprChan
}

// MouseChan returns the channel onto which the Reader writes mouse events it
// has read.
func (rd *Reader) MouseChan() <-chan mouseEvent {
	return rd.mouseChan
}

// ErrorChan returns the channel onto which the Reader writes errors it came
// across during the reading process.
func (rd *Reader) ErrorChan() <-chan error {
	return rd.errChan
}

// Run runs the Reader. It blocks until Quit is called and should be called in
// a separate goroutine.
func (rd *Reader) Run() {
	runes := rd.ar.Chan()
	rd.quit = make(chan struct{})
	go rd.ar.Run()

	for {
		select {
		case r := <-runes:
			rd.readOne(r)
		case <-rd.quit:
			return
		}
	}
}

// Quit terminates the loop of Run.
func (rd *Reader) Quit() {
	rd.ar.Quit()
	close(rd.quit)
}

// Close releases files associated with the Reader. It does not close the file
// used to create it.
func (rd *Reader) Close() {
	rd.ar.Close()
}

// readOne attempts to read one key or CPR, led by a rune already read.
func (rd *Reader) readOne(r rune) {
	var k Key
	var cpr pos
	var mouse mouseEvent
	var err error
	currentSeq := string(r)

	badSeq := func(msg string) {
		err = fmt.Errorf("%s: %q", msg, currentSeq)
	}

	// readRune attempts to read a rune within EscSequenceTimeout. It writes to
	// the err and currentSeq variable in the outer scope.
	readRune :=
		func() rune {
			select {
			case r := <-rd.ar.Chan():
				currentSeq += string(r)
				return r
			case err = <-rd.ar.ErrorChan():
				return runeReadError
			case <-time.After(EscSequenceTimeout):
				return runeTimeout
			}
		}

	defer func() {
		if k != (Key{}) {
			select {
			case rd.keyChan <- k:
			case <-rd.quit:
			}
		} else if cpr != (pos{}) {
			select {
			case rd.cprChan <- cpr:
			case <-rd.quit:
			}
		} else if mouse != (mouseEvent{}) {
			select {
			case rd.mouseChan <- mouse:
			case <-rd.quit:
			}
		}
		if err != nil {
			select {
			case rd.errChan <- err:
			case <-rd.quit:
			}
		}
	}()

	switch r {
	case Tab, Enter, Backspace:
		k = Key{r, 0}
	case 0x0:
		k = Key{'`', Ctrl} // ^@
	case 0x1d:
		k = Key{'6', Ctrl} // ^^
	case 0x1f:
		k = Key{'/', Ctrl} // ^_
	case 0x1b: // ^[ Escape
		r2 := readRune()
		if r2 == runeTimeout || r2 == runeReadError {
			k = Key{'[', Ctrl}
			break
		}
		switch r2 {
		case '[':
			// CSI style function key sequence.
			r = readRune()
			if r == runeTimeout || r == runeReadError {
				k = Key{'[', Alt}
				return
			}

			nums := make([]int, 0, 2)
			var starter rune

			// Read an optional starter.
			switch r {
			case '<':
				starter = r
				r = readRune()
			case 'M':
				// Mouse event.
				cb := readRune()
				if cb == runeTimeout || cb == runeReadError {
					badSeq("Incomplete mouse event")
					return
				}
				cx := readRune()
				if cx == runeTimeout || cx == runeReadError {
					badSeq("Incomplete mouse event")
					return
				}
				cy := readRune()
				if cy == runeTimeout || cy == runeReadError {
					badSeq("Incomplete mouse event")
					return
				}
				down := true
				button := int(cb & 3)
				if button == 3 {
					down = false
					button = -1
				}
				mod := mouseModify(int(cb))
				mouse = mouseEvent{
					pos{int(cy) - 32, int(cx) - 32}, down, button, mod}
				return
			}
		CSISeq:
			for {
				switch {
				case r == ';':
					nums = append(nums, 0)
				case '0' <= r && r <= '9':
					if len(nums) == 0 {
						nums = append(nums, 0)
					}
					cur := len(nums) - 1
					nums[cur] = nums[cur]*10 + int(r-'0')
				case r == runeTimeout:
					// Incomplete CSI.
					badSeq("Incomplete CSI")
					return
				case r == runeReadError:
					// TODO Also complain about incomplte CSI.
					return
				default: // Treat as a terminator.
					break CSISeq
				}

				r = readRune()
			}
			if starter == 0 && r == 'R' {
				// Cursor position report.
				if len(nums) != 2 {
					badSeq("bad CPR")
					return
				}
				cpr = pos{nums[0], nums[1]}
			} else if starter == '<' && (r == 'm' || r == 'M') {
				// SGR-style mouse event.
				if len(nums) != 3 {
					badSeq("bad SGR mouse event")
					return
				}
				down := r == 'M'
				button := nums[0] & 3
				mod := mouseModify(nums[0])
				mouse = mouseEvent{pos{nums[2], nums[1]}, down, button, mod}
			} else {
				k = parseCSI(nums, r, currentSeq)
				if k == (Key{}) {
					badSeq("bad CSI")
				}
			}
		case 'O':
			// G3 style function key sequence: read one rune.
			r = readRune()
			if r == runeTimeout || r == runeReadError {
				k = Key{r2, Alt}
				return
			}
			r, ok := g3Seq[r]
			if ok {
				k = Key{r, 0}
			} else {
				badSeq("bad G3")
			}
		default:
			k = Key{r2, Alt}
		}
	default:
		// Regular Ctrl sequences.
		if 0x1 <= r && r <= 0x1d {
			k = Key{r + 0x40, Ctrl}
		} else {
			k = Key{r, 0}
		}
	}
}

// G3-style key sequences: \eO followed by exactly one character. For instance,
// \eOP is F1.
var g3Seq = map[rune]rune{
	// F1-F4: xterm, libvte and tmux
	'P': F1, 'Q': F2,
	'R': F3, 'S': F4,

	// Home and End: libvte
	'H': Home, 'F': End,
}

// Tables for CSI-style key sequences, which are \e[ followed by a list of
// semicolon-delimited numeric arguments, before being concluded by a
// non-numeric, non-semicolon rune.

// CSI-style key sequences that can be identified based on the ending rune. For
// instance, \e[A is Up.
var keyByLast = map[rune]Key{
	'A': Key{Up, 0}, 'B': Key{Down, 0},
	'C': Key{Right, 0}, 'D': Key{Left, 0},
	'H': Key{Home, 0}, 'F': Key{End, 0},
	'Z': Key{Tab, Shift},
}

// CSI-style key sequences ending with '~' and can be identified based on
// the only number argument. For instance, \e[1~ is Home.
var keyByNum0 = map[int]rune{
	1: Home, 2: Insert, 3: Delete, 4: End, 5: PageUp, 6: PageDown,
	11: F1, 12: F2, 13: F3, 14: F4,
	15: F5, 17: F6, 18: F7, 19: F8, 20: F9, 21: F10, 23: F11, 24: F12,
}

// CSI-style key sequences ending with '~', with 27 as the first numeric
// argument. For instance, \e[27;9~ is Tab.
//
// The list is taken blindly from tmux source xterm-keys.c. I don't have a
// keyboard-terminal combination that generate such sequences, but assumably
// some PC keyboard with a numpad can.
var keyByNum2 = map[int]rune{
	9: '\t', 13: '\r',
	33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-',
	46: '.',
	48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7',
	56: '8', 57: '9',
	58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';',
}

// parseCSI parses a CSI-style key sequence.
func parseCSI(nums []int, last rune, seq string) Key {
	if k, ok := keyByLast[last]; ok {
		if len(nums) == 0 {
			// Unmodified: \e[A (Up)
			return k
		} else if len(nums) == 2 && nums[0] == 1 {
			// Modified: \e[1;5A (Ctrl-Up)
			return xtermModify(k, nums[1], seq)
		} else {
			return Key{}
		}
	}

	if last == '~' {
		if len(nums) == 1 || len(nums) == 2 {
			if r, ok := keyByNum0[nums[0]]; ok {
				k := Key{r, 0}
				if len(nums) == 1 {
					// Unmodified: \e[5~ (PageUp)
					return k
				}
				// Modified: \e[5;5~ (Ctrl-PageUp)
				return xtermModify(k, nums[1], seq)
			}
		} else if len(nums) == 3 && nums[0] == 27 {
			if r, ok := keyByNum2[nums[2]]; ok {
				k := Key{r, 0}
				return xtermModify(k, nums[1], seq)
			}
		}
	}

	return Key{}
}

func xtermModify(k Key, mod int, seq string) Key {
	switch mod {
	case 0:
		// do nothing
	case 2:
		k.Mod |= Shift
	case 3:
		k.Mod |= Alt
	case 4:
		k.Mod |= Shift | Alt
	case 5:
		k.Mod |= Ctrl
	case 6:
		k.Mod |= Shift | Ctrl
	case 7:
		k.Mod |= Alt | Ctrl
	case 8:
		k.Mod |= Shift | Alt | Ctrl
	default:
		return Key{}
	}
	return k
}

func mouseModify(n int) Mod {
	var mod Mod
	if n&4 != 0 {
		mod |= Shift
	}
	if n&8 != 0 {
		mod |= Alt
	}
	if n&16 != 0 {
		mod |= Ctrl
	}
	return mod
}
<|endoftext|>


<commit_before>package editor

import (
	"log"

	"github.com/atotto/clipboard"
	"github.com/gdamore/tcell"
	"github.com/jantb/olive/go_plugin"
	"github.com/jantb/olive/rpc"
	"github.com/jantb/olive/xi"
	"github.com/rivo/tview"
)

// View implements the view editor view.
type View struct {
	dataView map[string]*Dataview
	Lines    [][]Block
	*tview.Box
	*Editor
	offy, offx, height, width int
	findstatus                *rpc.FindStatus
}

type Block struct {
	Rune  rune
	Style tcell.Style
}

// NewView returns a new view view primitive.
func NewView() *View {
	view := View{
		Box:   tview.NewBox().SetBorder(false),
		Lines: [][]Block{},
	}
	return &view
}

// Draw draws this primitive onto the screen.
func (v *View) Draw(screen tcell.Screen) {
	_, bg, _ := defaultStyle.Decompose()
	v.Box.SetBackgroundColor(bg).Draw(screen)
	_, _, width, height := v.Box.GetInnerRect()
	v.height = height
	v.width = width
	v.Editor.mutex.Lock()
	defer v.Editor.mutex.Unlock()
	dataview := v.dataView[v.curViewID]
	if dataview == nil {
		return
	}

	lines := dataview.Lines()
	blocksy := [][]Block{}
	offy := v.offy
	offx := v.offx
	blocksy = getBlocks(lines, offy, height, blocksy, offx, width, v)

	v.Lines = blocksy
	v.drawBlocks(screen)
	v.drawCursors(lines, height, screen)
}

func getBlocks(lines []*xi.Line, offy int, height int, blocksy [][]Block, offx int, width int, m *View) [][]Block {
	if len(lines) < offy {
		//return
	}
	for y, line := range lines[offy : offy+height] {
		if line == nil {
			continue
		}
		var blocks []Block
		blocksy = append(blocksy, blocks)

		for x, r := range line.Text[Max(0, Min(offx, len(line.Text)-1)):Max(0, Min(offx+width, len(line.Text)-1))] {
			var style = defaultStyle
			if line.StyleIds[x] != nil {
				for _, value := range line.StyleIds[x] {
					s := styles[value]
					if value == 0 {
						s = s.Background(tcell.NewRGBColor(m.Editor.theme.Selection.ToRGB()))
					}

					fg, bg, _ := s.Decompose()

					if fg != tcell.ColorDefault {
						style = style.Foreground(fg)
					}
					if bg != tcell.ColorDefault {
						style = style.Background(bg)
					}
				}
			}
			blocksy[y] = append(blocksy[y], Block{Rune: r, Style: style})
		}
	}
	return blocksy
}

func (v *View) drawBlocks(screen tcell.Screen) {
	for y, line := range v.Lines {
		offX := 0
		for x, block := range line {
			if block.Rune == '\t' {
				v.draw(screen, x, y, block)
				v.draw(screen, x+1, y, block)
				v.draw(screen, x+2, y, block)
				v.draw(screen, x+3, y, block)
				offX += 3
				continue
			}
			v.draw(screen, x+offX, y, block)
		}
	}
}

func (v *View) drawCursors(lines []*xi.Line, h int, screen tcell.Screen) {
	for y, line := range lines[v.offy : v.offy+h] {
		if line == nil {
			continue
		}
		for _, cursor := range line.Cursors {
			x := GetCursorVisualX(cursor, line.Text)
			content := v.getContent(screen, x, y)
			content.Style = content.Style.Reverse(true)
			v.draw(screen, x, y, content)
		}
	}
}

func (v *View) draw(screen tcell.Screen, x int, y int, b Block) {

	xMin, yMin, width, height := v.Box.GetInnerRect()
	x = xMin + x - v.offx
	y = yMin + y

	if x < xMin || y < yMin || x >= width+xMin || y >= height+yMin {
		return
	}
	screen.SetContent(x, y, b.Rune, nil, b.Style)
}

func (v *View) getContent(screen tcell.Screen, x int, y int) Block {

	xMin, yMin, width, height := v.Box.GetInnerRect()
	x = xMin + x - v.offx
	y = yMin + y

	if x < xMin || y < yMin || x >= width+xMin || y >= height+yMin {
		return Block{}
	}
	mainc, _, style, _ := screen.GetContent(x, y)
	return Block{Rune: mainc, Style: style}
}

func (v *View) MakeVisible(x, y int) {
	lines := v.dataView[v.curViewID].Lines()
	y -= v.dataView[v.curViewID].LineCache.InvalidBefore()
	x = GetCursorVisualX(x, lines[y].Text)
	_, _, width, height := v.Box.GetInnerRect()

	if y >= v.offy+height {
		v.offy = y - (height - 1)
	}

	if y >= 0 && y < v.offy {
		v.offy = y
	}

	if x >= v.offx+width {
		v.offx = x - (width - 1)
	}
	if x >= 0 && x < v.offx {
		v.offx = x
	}
}

// InputHandler returns the handler for this primitive.
func (v *View) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
	return v.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
		dataview := v.dataView[v.curViewID]
		ctrl := event.Modifiers()&tcell.ModCtrl != 0
		alt := event.Modifiers()&tcell.ModAlt != 0
		shift := event.Modifiers()&tcell.ModShift != 0
		if !ctrl && !alt && !shift {
			switch event.Key() {
			case tcell.KeyEsc:
				v.findstatus = nil
				dataview.CancelOperation()
			case tcell.KeyUp:
				dataview.MoveUp()
			case tcell.KeyDown:
				dataview.MoveDown()
			case tcell.KeyLeft:
				dataview.MoveLeft()
			case tcell.KeyEnter:
				dataview.Newline()
			case tcell.KeyRight:
				dataview.MoveRight()
			case tcell.KeyRune:
				dataview.Insert(string(event.Rune()))
			case tcell.KeyHome:
				dataview.MoveToBeginningOfLine()
			case tcell.KeyEnd:
				dataview.MoveToEndOfLine()
			case tcell.KeyTab:
				dataview.Tab()
			case tcell.KeyBS:
				dataview.DeleteForward()
			case tcell.KeyDelete:
				dataview.DeleteForward()
			case tcell.KeyBackspace2:
				dataview.DeleteBackward()
			case tcell.KeyPgUp:
				dataview.ScrollPageUp()
			case tcell.KeyPgDn:
				dataview.ScrollPageDown()
			default:
				log.Println(event.Name())
			}
		}
		if !ctrl && !alt && shift {
			switch event.Key() {
			case tcell.KeyRune:
				dataview.Insert(string(event.Rune()))
			case tcell.KeyPgUp:
				dataview.ScrollPageUpAndModifySelection()
			case tcell.KeyPgDn:
				dataview.ScrollPageDownAndModifySelection()
			case tcell.KeyHome:
				dataview.MoveToBeginningOfLineAndModifySelection()
			case tcell.KeyEnd:
				dataview.MoveToEndOfLineAndModifySelection()
			}
			switch event.Name() {
			case "Shift+Right":
				dataview.MoveRightAndModifySelection()
			case "Shift+Left":
				dataview.MoveLeftAndModifySelection()
			case "Shift+Up":
				dataview.MoveUpAndModifySelection()
			case "Shift+Down":
				dataview.MoveDownAndModifySelection()
			default:
				log.Println(event.Name())
			}
		}
		if ctrl && alt {
			switch event.Name() {
			case "Alt+Ctrl+Z":
				dataview.Redo()
			case "Alt+Ctrl+L":
				if v.footer.language == "Go" {
					dataview.CancelOperation()
					dataview.SelectAll()
					src := dataview.Copy()
					dataview.Insert(go_plugin.Format(src))
					dataview.CancelOperation()
				} else if v.footer.language == "XML" {
					dataview.CancelOperation()
					dataview.SelectAll()
					src := dataview.Copy()
					dataview.Insert(go_plugin.FormatXml(src))
					dataview.CancelOperation()
				} else if v.footer.language == "JSON" {
					dataview.CancelOperation()
					dataview.SelectAll()
					src := dataview.Copy()
					dataview.Insert(go_plugin.FormatJson(src))
					dataview.CancelOperation()
				}

			}
		}
		if ctrl && !alt && !shift {
			switch event.Key() {
			case tcell.KeyCtrlG:
				v.pages.ShowPage("gotoLine")
				v.pages.SendToBack("editor")
				v.pages.SendToFront("gotoLine")
			case tcell.KeyBS:
				dataview.DeleteWordForward()
			case tcell.KeyHome:
				dataview.MoveToBeginningOfDocument()
			case tcell.KeyEnd:
				dataview.MoveToEndOfDocument()
			case tcell.KeyDelete:
				dataview.DeleteWordForward()
			case tcell.KeyBackspace2:
				dataview.DeleteBackward()
			case tcell.KeyLeft:
				dataview.MoveWordLeft()
			case tcell.KeyRight:
				dataview.MoveWordRight()
			case tcell.KeyUp:
				dataview.MoveLineUp()
			case tcell.KeyDown:
				dataview.MoveLineDown()
			case tcell.KeyCtrlS:
				dataview.Save()
			case tcell.KeyCtrlC:
				data := dataview.Copy()
				if data == "" {
					dataview.SelectLine()
					data = dataview.Copy()
				}
				clipboard.WriteAll(data)
			case tcell.KeyCtrlX:
				clipboard.WriteAll(dataview.Cut())
			case tcell.KeyCtrlA:
				dataview.SelectAll()
			case tcell.KeyCtrlZ:
				dataview.Undo()
			case tcell.KeyCtrlV:
				s, e := clipboard.ReadAll()
				if e != nil {
					log.Println(e)
					return
				}
				dataview.Insert(s)
			case tcell.KeyCtrlQ:
				dataview.Close()
				v.header.path = ""
				v.curViewID = ""
				v.footer.totalLines = 0
				v.footer.cursorX = 0
				v.footer.cursorY = 0
				v.focusFileselector()
			case tcell.KeyCtrlD:
				dataview.DuplicateLine()
			default:
				log.Println(event.Name())
			}
		}
		if !ctrl && alt {
			switch event.Name() {
			case "Alt+Up":
				dataview.AddSelectionAbove()
			case "Alt+Rune[j]":
				if v.findstatus == nil {
					s := dataview.Copy()
					if s == "" {
						dataview.MoveWordLeft()
						dataview.MoveWordRightAndModifySelection()
						s = dataview.Copy()
					}
					dataview.Find(s, false, false, false)
				}
				dataview.FindNext(true, true, "add")
			case "Alt+Down":
				dataview.AddSelectionBelow()
			case "Alt+Rune[0]":
				v.focusFileselector()
			default:
				log.Println(event.Name())
			}
		}
		log.Println(event.Name())
	})
}
<commit_msg>Fixed wrong horizontal scroll<commit_after>package editor

import (
	"log"

	"github.com/atotto/clipboard"
	"github.com/gdamore/tcell"
	"github.com/jantb/olive/go_plugin"
	"github.com/jantb/olive/rpc"
	"github.com/jantb/olive/xi"
	"github.com/rivo/tview"
)

// View implements the view editor view.
type View struct {
	dataView map[string]*Dataview
	Lines    [][]Block
	*tview.Box
	*Editor
	offy, offx, height, width int
	findstatus                *rpc.FindStatus
}

type Block struct {
	Rune  rune
	Style tcell.Style
}

// NewView returns a new view view primitive.
func NewView() *View {
	view := View{
		Box:   tview.NewBox().SetBorder(false),
		Lines: [][]Block{},
	}
	return &view
}

// Draw draws this primitive onto the screen.
func (v *View) Draw(screen tcell.Screen) {
	_, bg, _ := defaultStyle.Decompose()
	v.Box.SetBackgroundColor(bg).Draw(screen)
	_, _, width, height := v.Box.GetInnerRect()
	v.height = height
	v.width = width
	v.Editor.mutex.Lock()
	defer v.Editor.mutex.Unlock()
	dataview := v.dataView[v.curViewID]
	if dataview == nil {
		return
	}

	lines := dataview.Lines()
	blocksy := [][]Block{}
	offy := v.offy
	offx := v.offx
	blocksy = getBlocks(lines, offy, height, blocksy, offx, width, v)

	v.Lines = blocksy
	v.drawBlocks(screen)
	v.drawCursors(lines, height, screen)
}

func getBlocks(lines []*xi.Line, offy int, height int, blocksy [][]Block, offx int, width int, m *View) [][]Block {
	if len(lines) < offy {
		//return
	}
	for y, line := range lines[offy : offy+height] {
		if line == nil {
			continue
		}
		var blocks []Block
		blocksy = append(blocksy, blocks)
		for x, r := range line.Text[Max(0, Min(offx, len(line.Text))):Max(0, Min(offx+width, len(line.Text)))] {
			var style = defaultStyle
			if line.StyleIds[x] != nil {
				for _, value := range line.StyleIds[x] {
					s := styles[value]
					if value == 0 {
						s = s.Background(tcell.NewRGBColor(m.Editor.theme.Selection.ToRGB()))
					}

					fg, bg, _ := s.Decompose()

					if fg != tcell.ColorDefault {
						style = style.Foreground(fg)
					}
					if bg != tcell.ColorDefault {
						style = style.Background(bg)
					}
				}
			}
			blocksy[y] = append(blocksy[y], Block{Rune: r, Style: style})
		}
	}
	return blocksy
}

func (v *View) drawBlocks(screen tcell.Screen) {
	for y, line := range v.Lines {
		for x, block := range line {
			if block.Rune == '\t' {
				v.draw(screen, x, y, block)
				v.draw(screen, x+1, y, block)
				v.draw(screen, x+2, y, block)
				v.draw(screen, x+3, y, block)
				continue
			}
			v.draw(screen, x, y, block)
		}
	}
}

func (v *View) drawCursors(lines []*xi.Line, h int, screen tcell.Screen) {
	for y, line := range lines[v.offy : v.offy+h] {
		if line == nil {
			continue
		}
		for _, cursor := range line.Cursors {
			x := GetCursorVisualX(cursor-v.offx, line.Text[Max(0, Min(v.offx, len(line.Text))):Max(0, Min(v.offx+v.width, len(line.Text)))])
			content := v.getContent(screen, x, y)
			content.Style = content.Style.Reverse(true)
			v.draw(screen, x, y, content)
		}
	}
}

func (v *View) draw(screen tcell.Screen, x int, y int, b Block) {

	xMin, yMin, width, height := v.Box.GetInnerRect()
	x = xMin + x
	y = yMin + y

	if x < xMin || y < yMin || x >= width+xMin || y >= height+yMin {
		return
	}
	screen.SetContent(x, y, b.Rune, nil, b.Style)
}

func (v *View) getContent(screen tcell.Screen, x int, y int) Block {

	xMin, yMin, width, height := v.Box.GetInnerRect()
	x = xMin + x
	y = yMin + y

	if x < xMin || y < yMin || x >= width+xMin || y >= height+yMin {
		return Block{}
	}
	mainc, _, style, _ := screen.GetContent(x, y)
	return Block{Rune: mainc, Style: style}
}

func (v *View) MakeVisible(x, y int) {
	lines := v.dataView[v.curViewID].Lines()
	y -= v.dataView[v.curViewID].LineCache.InvalidBefore()
	x = GetCursorVisualX(x, lines[y].Text)
	_, _, width, height := v.Box.GetInnerRect()

	if y >= v.offy+height {
		v.offy = y - (height - 1)
	}

	if y >= 0 && y < v.offy {
		v.offy = y
	}

	if x >= v.offx+width {
		v.offx = x - (width - 1)
	}
	if x >= 0 && x < v.offx {
		v.offx = x
	}
}

// InputHandler returns the handler for this primitive.
func (v *View) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
	return v.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {
		dataview := v.dataView[v.curViewID]
		ctrl := event.Modifiers()&tcell.ModCtrl != 0
		alt := event.Modifiers()&tcell.ModAlt != 0
		shift := event.Modifiers()&tcell.ModShift != 0
		if !ctrl && !alt && !shift {
			switch event.Key() {
			case tcell.KeyEsc:
				v.findstatus = nil
				dataview.CancelOperation()
			case tcell.KeyUp:
				dataview.MoveUp()
			case tcell.KeyDown:
				dataview.MoveDown()
			case tcell.KeyLeft:
				dataview.MoveLeft()
			case tcell.KeyEnter:
				dataview.Newline()
			case tcell.KeyRight:
				dataview.MoveRight()
			case tcell.KeyRune:
				dataview.Insert(string(event.Rune()))
			case tcell.KeyHome:
				dataview.MoveToBeginningOfLine()
			case tcell.KeyEnd:
				dataview.MoveToEndOfLine()
			case tcell.KeyTab:
				dataview.Tab()
			case tcell.KeyBS:
				dataview.DeleteForward()
			case tcell.KeyDelete:
				dataview.DeleteForward()
			case tcell.KeyBackspace2:
				dataview.DeleteBackward()
			case tcell.KeyPgUp:
				dataview.ScrollPageUp()
			case tcell.KeyPgDn:
				dataview.ScrollPageDown()
			default:
				log.Println(event.Name())
			}
		}
		if !ctrl && !alt && shift {
			switch event.Key() {
			case tcell.KeyRune:
				dataview.Insert(string(event.Rune()))
			case tcell.KeyPgUp:
				dataview.ScrollPageUpAndModifySelection()
			case tcell.KeyPgDn:
				dataview.ScrollPageDownAndModifySelection()
			case tcell.KeyHome:
				dataview.MoveToBeginningOfLineAndModifySelection()
			case tcell.KeyEnd:
				dataview.MoveToEndOfLineAndModifySelection()
			}
			switch event.Name() {
			case "Shift+Right":
				dataview.MoveRightAndModifySelection()
			case "Shift+Left":
				dataview.MoveLeftAndModifySelection()
			case "Shift+Up":
				dataview.MoveUpAndModifySelection()
			case "Shift+Down":
				dataview.MoveDownAndModifySelection()
			default:
				log.Println(event.Name())
			}
		}
		if ctrl && alt {
			switch event.Name() {
			case "Alt+Ctrl+Z":
				dataview.Redo()
			case "Alt+Ctrl+L":
				if v.footer.language == "Go" {
					dataview.CancelOperation()
					dataview.SelectAll()
					src := dataview.Copy()
					dataview.Insert(go_plugin.Format(src))
					dataview.CancelOperation()
				} else if v.footer.language == "XML" {
					dataview.CancelOperation()
					dataview.SelectAll()
					src := dataview.Copy()
					dataview.Insert(go_plugin.FormatXml(src))
					dataview.CancelOperation()
				} else if v.footer.language == "JSON" {
					dataview.CancelOperation()
					dataview.SelectAll()
					src := dataview.Copy()
					dataview.Insert(go_plugin.FormatJson(src))
					dataview.CancelOperation()
				}

			}
		}
		if ctrl && !alt && !shift {
			switch event.Key() {
			case tcell.KeyCtrlG:
				v.pages.ShowPage("gotoLine")
				v.pages.SendToBack("editor")
				v.pages.SendToFront("gotoLine")
			case tcell.KeyBS:
				dataview.DeleteWordForward()
			case tcell.KeyHome:
				dataview.MoveToBeginningOfDocument()
			case tcell.KeyEnd:
				dataview.MoveToEndOfDocument()
			case tcell.KeyDelete:
				dataview.DeleteWordForward()
			case tcell.KeyBackspace2:
				dataview.DeleteBackward()
			case tcell.KeyLeft:
				dataview.MoveWordLeft()
			case tcell.KeyRight:
				dataview.MoveWordRight()
			case tcell.KeyUp:
				dataview.MoveLineUp()
			case tcell.KeyDown:
				dataview.MoveLineDown()
			case tcell.KeyCtrlS:
				dataview.Save()
			case tcell.KeyCtrlC:
				data := dataview.Copy()
				if data == "" {
					dataview.SelectLine()
					data = dataview.Copy()
				}
				clipboard.WriteAll(data)
			case tcell.KeyCtrlX:
				clipboard.WriteAll(dataview.Cut())
			case tcell.KeyCtrlA:
				dataview.SelectAll()
			case tcell.KeyCtrlZ:
				dataview.Undo()
			case tcell.KeyCtrlV:
				s, e := clipboard.ReadAll()
				if e != nil {
					log.Println(e)
					return
				}
				dataview.Insert(s)
			case tcell.KeyCtrlQ:
				dataview.Close()
				v.header.path = ""
				v.curViewID = ""
				v.footer.totalLines = 0
				v.footer.cursorX = 0
				v.footer.cursorY = 0
				v.focusFileselector()
			case tcell.KeyCtrlD:
				dataview.DuplicateLine()
			default:
				log.Println(event.Name())
			}
		}
		if !ctrl && alt {
			switch event.Name() {
			case "Alt+Up":
				dataview.AddSelectionAbove()
			case "Alt+Rune[j]":
				if v.findstatus == nil {
					s := dataview.Copy()
					if s == "" {
						dataview.MoveWordLeft()
						dataview.MoveWordRightAndModifySelection()
						s = dataview.Copy()
					}
					dataview.Find(s, false, false, false)
				}
				dataview.FindNext(true, true, "add")
			case "Alt+Down":
				dataview.AddSelectionBelow()
			case "Alt+Rune[0]":
				v.focusFileselector()
			default:
				log.Println(event.Name())
			}
		}
		log.Println(event.Name())
	})
}
<|endoftext|>


<commit_before>package udp

import (
	"net"

	"github.com/CotaPreco/Horus/receiver"
	"github.com/CotaPreco/Horus/util"
)

type UdpReceiver struct {
	util.Observable
	host            string
	port            int
	receiveStrategy receiver.ReceiveStrategy
}

func NewUdpReceiver(
	host string,
	port int,
	receiveStrategy receiver.ReceiveStrategy,
) *UdpReceiver {
	return &UdpReceiver{
		host:            host,
		port:            port,
		receiveStrategy: receiveStrategy,
	}
}

func (r *UdpReceiver) Receive() {
	conn, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.ParseIP(r.host),
		Port: r.port,
	})

	util.Invariant(
		err == nil,
		"...unexpected error: `%s` (ListenUDP)",
		err,
	)

	defer conn.Close()

	for {
		message := make([]byte, 1024)

		_, _, err := conn.ReadFromUDP(message)

		if err == nil && r.receiveStrategy.CanReceive(message) {
			r.NotifyAll(r.receiveStrategy.Receive(message))
		}
	}
}
<commit_msg>Changing PACKET_SIZE to 512 (UdpReceiver) (refs #5 about `util.Invariant`)<commit_after>package udp

import (
	"net"

	"github.com/CotaPreco/Horus/receiver"
	"github.com/CotaPreco/Horus/util"
)

type UdpReceiver struct {
	util.Observable
	host            string
	port            int
	receiveStrategy receiver.ReceiveStrategy
}

const (
	PACKET_SIZE = 512
)

func NewUdpReceiver(
	host string,
	port int,
	receiveStrategy receiver.ReceiveStrategy,
) *UdpReceiver {
	return &UdpReceiver{
		host:            host,
		port:            port,
		receiveStrategy: receiveStrategy,
	}
}

func (r *UdpReceiver) Receive() {
	conn, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.ParseIP(r.host),
		Port: r.port,
	})

	if err != nil {
		return
	}

	// util.Invariant(
	// 	err == nil,
	// 	"...unexpected error: `%s` (ListenUDP)",
	// 	err,
	// )

	defer conn.Close()

	for {
		message := make([]byte, PACKET_SIZE)

		_, _, err := conn.ReadFromUDP(message)

		if err == nil && r.receiveStrategy.CanReceive(message) {
			r.NotifyAll(r.receiveStrategy.Receive(message))
		}
	}
}
<|endoftext|>