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

package rocserv

import (
	"flag"
	"fmt"
	"git.apache.org/thrift.git/lib/go/thrift"
	"github.com/gin-gonic/gin"
	"github.com/julienschmidt/httprouter"
	"github.com/shawnfeng/sutil/slog"
	"github.com/shawnfeng/sutil/slog/statlog"
	"github.com/shawnfeng/sutil/smetric"
	"github.com/shawnfeng/sutil/trace"
	"reflect"
	"sync"
)

const (
	PROCESSOR_HTTP   = "http"
	PROCESSOR_THRIFT = "thrift"
	PROCESSOR_GRPC   = "gprc"
	PROCESSOR_GIN    = "gin"
)

var service = NewService()

type Service struct {
	sbase ServBase

	mutex   sync.Mutex
	servers map[string]interface{}
}

func NewService() *Service {
	return &Service{
		servers: make(map[string]interface{}),
	}
}

type cmdArgs struct {
	logMaxSize    int
	logMaxBackups int
	servLoc       string
	logDir        string
	sessKey       string
	group         string
	disable       bool
}

func (m *Service) parseFlag() (*cmdArgs, error) {
	var serv, logDir, skey, group string
	var logMaxSize, logMaxBackups int
	flag.IntVar(&logMaxSize, "logmaxsize", 0, "logMaxSize is the maximum size in megabytes of the log file")
	flag.IntVar(&logMaxBackups, "logmaxbackups", 0, "logmaxbackups is the maximum number of old log files to retain")
	flag.StringVar(&serv, "serv", "", "servic name")
	flag.StringVar(&logDir, "logdir", "", "serice log dir")
	flag.StringVar(&skey, "skey", "", "service session key")
	flag.StringVar(&group, "group", "", "service group")

	flag.Parse()

	if len(serv) == 0 {
		return nil, fmt.Errorf("serv args need!")
	}

	if len(skey) == 0 {
		return nil, fmt.Errorf("skey args need!")
	}

	return &cmdArgs{
		logMaxSize:    logMaxSize,
		logMaxBackups: logMaxBackups,
		servLoc:       serv,
		logDir:        logDir,
		sessKey:       skey,
		group:         group,
	}, nil

}

func (m *Service) loadDriver(sb ServBase, procs map[string]Processor) (map[string]*ServInfo, error) {
	fun := "Service.loadDriver -->"

	infos := make(map[string]*ServInfo)

	for n, p := range procs {
		addr, driver := p.Driver()
		if driver == nil {
			slog.Infof("%s processor:%s no driver", fun, n)
			continue
		}

		slog.Infof("%s processor:%s type:%s addr:%s", fun, n, reflect.TypeOf(driver), addr)

		switch d := driver.(type) {
		case *httprouter.Router:
			sa, err := powerHttp(addr, d)
			if err != nil {
				return nil, err
			}

			slog.Infof("%s load ok processor:%s serv addr:%s", fun, n, sa)
			infos[n] = &ServInfo{
				Type: PROCESSOR_HTTP,
				Addr: sa,
			}

		case thrift.TProcessor:
			sa, err := powerThrift(addr, d)
			if err != nil {
				return nil, err
			}

			slog.Infof("%s load ok processor:%s serv addr:%s", fun, n, sa)
			infos[n] = &ServInfo{
				Type: PROCESSOR_THRIFT,
				Addr: sa,
			}
		case *GrpcServer:
			sa, err := powerGrpc(addr, d)
			if err != nil {
				return nil, err
			}

			slog.Infof("%s load ok processor:%s serv addr:%s", fun, n, sa)
			infos[n] = &ServInfo{
				Type: PROCESSOR_GRPC,
				Addr: sa,
			}
		case *gin.Engine:
			sa, serv, err := powerGin(addr, d)
			if err != nil {
				return nil, err
			}

			m.addServer(n, serv)

			slog.Infof("%s load ok processor:%s serv addr:%s", fun, n, sa)
			infos[n] = &ServInfo{
				Type: PROCESSOR_GIN,
				Addr: sa,
			}
		default:
			return nil, fmt.Errorf("processor:%s driver not recognition", n)

		}
	}

	return infos, nil
}

func (m *Service) addServer(processor string, server interface{}) {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	m.servers[processor] = server
}

func (m *Service) reloadRouter(processor string, driver interface{}) error {
	//fun := "Service.reloadRouter -->"

	m.mutex.Lock()
	defer m.mutex.Unlock()
	server, ok := m.servers[processor]
	if !ok {
		return fmt.Errorf("processor:%s driver not recognition", processor)
	}

	return reloadRouter(processor, server, driver)
}

func (m *Service) Serve(confEtcd configEtcd, initfn func(ServBase) error, procs map[string]Processor) error {
	fun := "Service.Serve -->"

	args, err := m.parseFlag()
	if err != nil {
		slog.Panicf("%s parse arg err:%s", fun, err)
		return err
	}

	return m.Init(confEtcd, args, initfn, procs)
}

func (m *Service) initLog(sb *ServBaseV2, args *cmdArgs) error {
	fun := "Service.initLog -->"

	logDir := args.logDir
	var logConfig struct {
		Log struct {
			Level string
			Dir   string
		}
	}
	logConfig.Log.Level = "INFO"

	err := sb.ServConfig(&logConfig)
	if err != nil {
		slog.Errorf("%s serv config err:%s", fun, err)
		return err
	}

	var logdir string
	if len(logConfig.Log.Dir) > 0 {
		logdir = fmt.Sprintf("%s/%s", logConfig.Log.Dir, sb.Copyname())
	}

	if len(logDir) > 0 {
		logdir = fmt.Sprintf("%s/%s", logDir, sb.Copyname())
	}

	if logDir == "console" {
		logdir = ""
	}

	slog.Infof("%s init log dir:%s name:%s level:%s", fun, logdir, args.servLoc, logConfig.Log.Level)

	slog.Init(logdir, "serv.log", logConfig.Log.Level)
	statlog.Init(logdir, "stat.log", args.servLoc)
	return nil
}

func (m *Service) Init(confEtcd configEtcd, args *cmdArgs, initfn func(ServBase) error, procs map[string]Processor) error {
	fun := "Service.Init -->"

	servLoc := args.servLoc
	sessKey := args.sessKey

	sb, err := NewServBaseV2(confEtcd, servLoc, sessKey)
	if err != nil {
		slog.Panicf("%s init servbase loc:%s key:%s err:%s", fun, servLoc, sessKey, err)
		return err
	}

	m.initLog(sb, args)
	defer slog.Sync()
	defer statlog.Sync()

	err = initfn(sb)
	if err != nil {
		slog.Panicf("%s callInitFunc err:%s", fun, err)
		return err
	}

	err = m.initProcessor(sb, procs)
	if err != nil {
		slog.Panicf("%s initProcessor err:%s", fun, err)
		return err
	}

	sb.SetGroupAndDisable(args.group, args.disable)

	m.initTracer(servLoc)
	m.initBackdoork(sb)
	m.initMetric(sb)

	var pause chan bool
	pause <- true

	return nil
}

func (m *Service) initProcessor(sb *ServBaseV2, procs map[string]Processor) error {
	fun := "Service.initProcessor -->"

	for n, p := range procs {
		if len(n) == 0 {
			slog.Errorf("%s processor name empty", fun)
			return fmt.Errorf("processor name empty")
		}

		if n[0] == '_' {
			slog.Errorf("%s processor name can not prefix '_'", fun)
			return fmt.Errorf("processor name can not prefix '_'")
		}

		if p == nil {
			slog.Errorf("%s processor:%s is nil", fun, n)
			return fmt.Errorf("processor:%s is nil", n)
		} else {
			err := p.Init()
			if err != nil {
				slog.Errorf("%s processor:%s init err:%s", fun, err)
				return fmt.Errorf("processor:%s init err:%s", n, err)
			}
		}
	}

	infos, err := m.loadDriver(sb, procs)
	if err != nil {
		slog.Errorf("%s load driver err:%s", fun, err)
		return err
	}

	err = sb.RegisterService(infos)
	if err != nil {
		slog.Errorf("%s regist service err:%s", fun, err)
		return err
	}

	return nil
}

func (m *Service) initTracer(servLoc string) error {
	fun := "Service.initTracer -->"

	err := trace.InitDefaultTracer(servLoc)
	if err != nil {
		slog.Errorf("%s init tracer fail:%v", fun, err)
	}

	return err
}

func (m *Service) initBackdoork(sb *ServBaseV2) error {
	fun := "Service.initBackdoork -->"

	backdoor := &backDoorHttp{}
	err := backdoor.Init()
	if err != nil {
		slog.Errorf("%s init backdoor err:%s", fun, err)
		return err
	}

	binfos, err := m.loadDriver(sb, map[string]Processor{"_PROC_BACKDOOR": backdoor})
	if err == nil {
		err = sb.RegisterBackDoor(binfos)
		if err != nil {
			slog.Errorf("%s regist backdoor err:%s", fun, err)
		}

	} else {
		slog.Warnf("%s load backdoor driver err:%s", fun, err)
	}

	return err
}

func (m *Service) initMetric(sb *ServBaseV2) error {
	fun := "Service.initMetric -->"

	metrics := smetric.NewMetricsprocessor()
	err := metrics.Init()
	if err != nil {
		slog.Warnf("%s init metrics err:%s", fun, err)
	}

	minfos, err := m.loadDriver(sb, map[string]Processor{"_PROC_METRICS": metrics})
	if err == nil {
		err = sb.RegisterMetrics(minfos)
		if err != nil {
			slog.Warnf("%s regist backdoor err:%s", fun, err)
		}

	} else {
		slog.Warnf("%s load metrics driver err:%s", fun, err)
	}
	return err
}

func ReloadRouter(processor string, driver interface{}) error {
	return service.reloadRouter(processor, driver)
}

func Serve(etcds []string, baseLoc string, initfn func(ServBase) error, procs map[string]Processor) error {
	return service.Serve(configEtcd{etcds, baseLoc}, initfn, procs)
}

func Init(etcds []string, baseLoc string, servLoc, servKey, logDir string, initfn func(ServBase) error, procs map[string]Processor) error {
	args := &cmdArgs{
		logMaxSize:    0,
		logMaxBackups: 0,
		servLoc:       servLoc,
		logDir:        logDir,
		sessKey:       servKey,
	}
	return service.Init(configEtcd{etcds, baseLoc}, args, initfn, procs)
}

func GetServBase() ServBase {
	return service.sbase
}

func GetServName() (servName string) {
	if service.sbase != nil {
		servName = service.sbase.Servname()
	}
	return
}
func GetServId() (servId int) {
	if service.sbase != nil {
		servId = service.sbase.Servid()
	}
	return
}

func Test(etcds []string, baseLoc, servLoc string, initfn func(ServBase) error) error {
	args := &cmdArgs{
		logMaxSize:    0,
		logMaxBackups: 0,
		servLoc:       servLoc,
		sessKey:       "test",
		logDir:        "console",
		disable:       true,
	}
	return service.Init(configEtcd{etcds, baseLoc}, args, initfn, nil)
}
<commit_msg>MOD:支持master－slave模式<commit_after>// Copyright 2014 The roc Author. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package rocserv

import (
	"flag"
	"fmt"
	"git.apache.org/thrift.git/lib/go/thrift"
	"github.com/gin-gonic/gin"
	"github.com/julienschmidt/httprouter"
	"github.com/shawnfeng/sutil/slog"
	"github.com/shawnfeng/sutil/slog/statlog"
	"github.com/shawnfeng/sutil/smetric"
	"github.com/shawnfeng/sutil/trace"
	"reflect"
	"sync"
)

const (
	PROCESSOR_HTTP   = "http"
	PROCESSOR_THRIFT = "thrift"
	PROCESSOR_GRPC   = "gprc"
	PROCESSOR_GIN    = "gin"

	MODEL_SERVER      = 0
	MODEL_MASTERSLAVE = 1
)

var service = NewService()

type Service struct {
	sbase ServBase

	mutex   sync.Mutex
	servers map[string]interface{}
}

func NewService() *Service {
	return &Service{
		servers: make(map[string]interface{}),
	}
}

type cmdArgs struct {
	logMaxSize    int
	logMaxBackups int
	servLoc       string
	logDir        string
	sessKey       string
	group         string
	disable       bool
	model         int
}

func (m *Service) parseFlag() (*cmdArgs, error) {
	var serv, logDir, skey, group string
	var logMaxSize, logMaxBackups int
	flag.IntVar(&logMaxSize, "logmaxsize", 0, "logMaxSize is the maximum size in megabytes of the log file")
	flag.IntVar(&logMaxBackups, "logmaxbackups", 0, "logmaxbackups is the maximum number of old log files to retain")
	flag.StringVar(&serv, "serv", "", "servic name")
	flag.StringVar(&logDir, "logdir", "", "serice log dir")
	flag.StringVar(&skey, "skey", "", "service session key")
	flag.StringVar(&group, "group", "", "service group")

	flag.Parse()

	if len(serv) == 0 {
		return nil, fmt.Errorf("serv args need!")
	}

	if len(skey) == 0 {
		return nil, fmt.Errorf("skey args need!")
	}

	return &cmdArgs{
		logMaxSize:    logMaxSize,
		logMaxBackups: logMaxBackups,
		servLoc:       serv,
		logDir:        logDir,
		sessKey:       skey,
		group:         group,
	}, nil

}

func (m *Service) loadDriver(sb ServBase, procs map[string]Processor) (map[string]*ServInfo, error) {
	fun := "Service.loadDriver -->"

	infos := make(map[string]*ServInfo)

	for n, p := range procs {
		addr, driver := p.Driver()
		if driver == nil {
			slog.Infof("%s processor:%s no driver", fun, n)
			continue
		}

		slog.Infof("%s processor:%s type:%s addr:%s", fun, n, reflect.TypeOf(driver), addr)

		switch d := driver.(type) {
		case *httprouter.Router:
			sa, err := powerHttp(addr, d)
			if err != nil {
				return nil, err
			}

			slog.Infof("%s load ok processor:%s serv addr:%s", fun, n, sa)
			infos[n] = &ServInfo{
				Type: PROCESSOR_HTTP,
				Addr: sa,
			}

		case thrift.TProcessor:
			sa, err := powerThrift(addr, d)
			if err != nil {
				return nil, err
			}

			slog.Infof("%s load ok processor:%s serv addr:%s", fun, n, sa)
			infos[n] = &ServInfo{
				Type: PROCESSOR_THRIFT,
				Addr: sa,
			}
		case *GrpcServer:
			sa, err := powerGrpc(addr, d)
			if err != nil {
				return nil, err
			}

			slog.Infof("%s load ok processor:%s serv addr:%s", fun, n, sa)
			infos[n] = &ServInfo{
				Type: PROCESSOR_GRPC,
				Addr: sa,
			}
		case *gin.Engine:
			sa, serv, err := powerGin(addr, d)
			if err != nil {
				return nil, err
			}

			m.addServer(n, serv)

			slog.Infof("%s load ok processor:%s serv addr:%s", fun, n, sa)
			infos[n] = &ServInfo{
				Type: PROCESSOR_GIN,
				Addr: sa,
			}
		default:
			return nil, fmt.Errorf("processor:%s driver not recognition", n)

		}
	}

	return infos, nil
}

func (m *Service) addServer(processor string, server interface{}) {
	m.mutex.Lock()
	defer m.mutex.Unlock()

	m.servers[processor] = server
}

func (m *Service) reloadRouter(processor string, driver interface{}) error {
	//fun := "Service.reloadRouter -->"

	m.mutex.Lock()
	defer m.mutex.Unlock()
	server, ok := m.servers[processor]
	if !ok {
		return fmt.Errorf("processor:%s driver not recognition", processor)
	}

	return reloadRouter(processor, server, driver)
}

func (m *Service) Serve(confEtcd configEtcd, initfn func(ServBase) error, procs map[string]Processor) error {
	fun := "Service.Serve -->"

	args, err := m.parseFlag()
	if err != nil {
		slog.Panicf("%s parse arg err:%s", fun, err)
		return err
	}

	return m.Init(confEtcd, args, initfn, procs)
}

func (m *Service) initLog(sb *ServBaseV2, args *cmdArgs) error {
	fun := "Service.initLog -->"

	logDir := args.logDir
	var logConfig struct {
		Log struct {
			Level string
			Dir   string
		}
	}
	logConfig.Log.Level = "INFO"

	err := sb.ServConfig(&logConfig)
	if err != nil {
		slog.Errorf("%s serv config err:%s", fun, err)
		return err
	}

	var logdir string
	if len(logConfig.Log.Dir) > 0 {
		logdir = fmt.Sprintf("%s/%s", logConfig.Log.Dir, sb.Copyname())
	}

	if len(logDir) > 0 {
		logdir = fmt.Sprintf("%s/%s", logDir, sb.Copyname())
	}

	if logDir == "console" {
		logdir = ""
	}

	slog.Infof("%s init log dir:%s name:%s level:%s", fun, logdir, args.servLoc, logConfig.Log.Level)

	slog.Init(logdir, "serv.log", logConfig.Log.Level)
	statlog.Init(logdir, "stat.log", args.servLoc)
	return nil
}

func (m *Service) Init(confEtcd configEtcd, args *cmdArgs, initfn func(ServBase) error, procs map[string]Processor) error {
	fun := "Service.Init -->"

	servLoc := args.servLoc
	sessKey := args.sessKey

	sb, err := NewServBaseV2(confEtcd, servLoc, sessKey)
	if err != nil {
		slog.Panicf("%s init servbase loc:%s key:%s err:%s", fun, servLoc, sessKey, err)
		return err
	}

	m.initLog(sb, args)
	defer slog.Sync()
	defer statlog.Sync()

	err = m.handleModel(sb, servLoc, args.model)
	if err != nil {
		slog.Panicf("%s handleModel err:%s", fun, err)
		return err
	}

	err = initfn(sb)
	if err != nil {
		slog.Panicf("%s callInitFunc err:%s", fun, err)
		return err
	}

	err = m.initProcessor(sb, procs)
	if err != nil {
		slog.Panicf("%s initProcessor err:%s", fun, err)
		return err
	}

	sb.SetGroupAndDisable(args.group, args.disable)

	m.initTracer(servLoc)
	m.initBackdoork(sb)
	m.initMetric(sb)

	var pause chan bool
	pause <- true

	return nil
}

func (m *Service) handleModel(sb *ServBaseV2, servLoc string, model int) error {
	fun := "Service.handleModel -->"

	if model == MODEL_MASTERSLAVE {
		lockKey := fmt.Sprintf("%s-master-slave", servLoc)
		if err := sb.LockGlobal(lockKey); err != nil {
			slog.Errorf("%s LockGlobal key: %s, err: %s", fun, lockKey, err)
			return err
		}

		slog.Infof("%s LockGlobal succ, key: %s", fun, lockKey)
	}

	return nil
}

func (m *Service) initProcessor(sb *ServBaseV2, procs map[string]Processor) error {
	fun := "Service.initProcessor -->"

	for n, p := range procs {
		if len(n) == 0 {
			slog.Errorf("%s processor name empty", fun)
			return fmt.Errorf("processor name empty")
		}

		if n[0] == '_' {
			slog.Errorf("%s processor name can not prefix '_'", fun)
			return fmt.Errorf("processor name can not prefix '_'")
		}

		if p == nil {
			slog.Errorf("%s processor:%s is nil", fun, n)
			return fmt.Errorf("processor:%s is nil", n)
		} else {
			err := p.Init()
			if err != nil {
				slog.Errorf("%s processor:%s init err:%s", fun, err)
				return fmt.Errorf("processor:%s init err:%s", n, err)
			}
		}
	}

	infos, err := m.loadDriver(sb, procs)
	if err != nil {
		slog.Errorf("%s load driver err:%s", fun, err)
		return err
	}

	err = sb.RegisterService(infos)
	if err != nil {
		slog.Errorf("%s regist service err:%s", fun, err)
		return err
	}

	return nil
}

func (m *Service) initTracer(servLoc string) error {
	fun := "Service.initTracer -->"

	err := trace.InitDefaultTracer(servLoc)
	if err != nil {
		slog.Errorf("%s init tracer fail:%v", fun, err)
	}

	return err
}

func (m *Service) initBackdoork(sb *ServBaseV2) error {
	fun := "Service.initBackdoork -->"

	backdoor := &backDoorHttp{}
	err := backdoor.Init()
	if err != nil {
		slog.Errorf("%s init backdoor err:%s", fun, err)
		return err
	}

	binfos, err := m.loadDriver(sb, map[string]Processor{"_PROC_BACKDOOR": backdoor})
	if err == nil {
		err = sb.RegisterBackDoor(binfos)
		if err != nil {
			slog.Errorf("%s regist backdoor err:%s", fun, err)
		}

	} else {
		slog.Warnf("%s load backdoor driver err:%s", fun, err)
	}

	return err
}

func (m *Service) initMetric(sb *ServBaseV2) error {
	fun := "Service.initMetric -->"

	metrics := smetric.NewMetricsprocessor()
	err := metrics.Init()
	if err != nil {
		slog.Warnf("%s init metrics err:%s", fun, err)
	}

	minfos, err := m.loadDriver(sb, map[string]Processor{"_PROC_METRICS": metrics})
	if err == nil {
		err = sb.RegisterMetrics(minfos)
		if err != nil {
			slog.Warnf("%s regist backdoor err:%s", fun, err)
		}

	} else {
		slog.Warnf("%s load metrics driver err:%s", fun, err)
	}
	return err
}

func ReloadRouter(processor string, driver interface{}) error {
	return service.reloadRouter(processor, driver)
}

func Serve(etcds []string, baseLoc string, initfn func(ServBase) error, procs map[string]Processor) error {
	return service.Serve(configEtcd{etcds, baseLoc}, initfn, procs)
}

func MasterSlave(etcds []string, baseLoc string, initfn func(ServBase) error, procs map[string]Processor) error {
	return service.MasterSlave(configEtcd{etcds, baseLoc}, initfn, procs)
}

func (m *Service) MasterSlave(confEtcd configEtcd, initfn func(ServBase) error, procs map[string]Processor) error {
	fun := "Service.MasterSlave -->"

	args, err := m.parseFlag()
	if err != nil {
		slog.Panicf("%s parse arg err:%s", fun, err)
		return err
	}
	args.model = MODEL_MASTERSLAVE

	return m.Init(confEtcd, args, initfn, procs)
}

func Init(etcds []string, baseLoc string, servLoc, servKey, logDir string, initfn func(ServBase) error, procs map[string]Processor) error {
	args := &cmdArgs{
		logMaxSize:    0,
		logMaxBackups: 0,
		servLoc:       servLoc,
		logDir:        logDir,
		sessKey:       servKey,
	}
	return service.Init(configEtcd{etcds, baseLoc}, args, initfn, procs)
}

func GetServBase() ServBase {
	return service.sbase
}

func GetServName() (servName string) {
	if service.sbase != nil {
		servName = service.sbase.Servname()
	}
	return
}
func GetServId() (servId int) {
	if service.sbase != nil {
		servId = service.sbase.Servid()
	}
	return
}

func Test(etcds []string, baseLoc, servLoc string, initfn func(ServBase) error) error {
	args := &cmdArgs{
		logMaxSize:    0,
		logMaxBackups: 0,
		servLoc:       servLoc,
		sessKey:       "test",
		logDir:        "console",
		disable:       true,
	}
	return service.Init(configEtcd{etcds, baseLoc}, args, initfn, nil)
}
<|endoftext|>


<commit_before>package cliutils

import (
	"os"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/jfrog/jfrog-cli-core/utils/coreutils"
	"github.com/jfrog/jfrog-cli/utils/summary"
	serviceutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
	"github.com/jfrog/jfrog-client-go/utils"
	clientutils "github.com/jfrog/jfrog-client-go/utils"
	"github.com/jfrog/jfrog-client-go/utils/errorutils"
	"github.com/jfrog/jfrog-client-go/utils/io/content"
	"github.com/jfrog/jfrog-client-go/utils/log"
	"github.com/pkg/errors"
)

// Error modes (how should the application behave when the CheckError function is invoked):
type OnError string

func init() {
	// Initialize cli-core values.
	cliUserAgent := os.Getenv(UserAgent)
	if cliUserAgent == "" {
		cliUserAgent = ClientAgent + "/" + CliVersion
	}
	coreutils.SetCliUserAgent(cliUserAgent)
	coreutils.SetClientAgent(ClientAgent)
	coreutils.SetVersion(CliVersion)
}

func GetCliError(err error, success, failed int, failNoOp bool) error {
	switch coreutils.GetExitCode(err, success, failed, failNoOp) {
	case coreutils.ExitCodeError:
		{
			var errorMessage string
			if err != nil {
				errorMessage = err.Error()
			}
			return coreutils.CliError{ExitCode: coreutils.ExitCodeError, ErrorMsg: errorMessage}
		}
	case coreutils.ExitCodeFailNoOp:
		return coreutils.CliError{ExitCode: coreutils.ExitCodeFailNoOp, ErrorMsg: "No errors, but also no files affected (fail-no-op flag)."}
	default:
		return nil
	}
}

type detailedSummaryRecord struct {
	Source string `json:"source"`
	Target string `json:"target"`
}

// Print summary report.
// a given non-nil error will pass through and be returned as is if no other errors are raised.
// In case of a nil error, the current function error will be returned.
func summaryPrintError(summaryError, originalError error) error {
	if originalError != nil {
		if summaryError != nil {
			log.Error(summaryError)
		}
		return originalError
	}
	return summaryError
}

// If a resultReader is provided, we will iterate over the result and print a detailed summary including the affected files.
func PrintSummaryReport(success, failed int, reader *content.ContentReader, rtUrl string, originalErr error) error {
	basicSummary, mErr := CreateSummaryReportString(success, failed, originalErr)
	if mErr != nil {
		return summaryPrintError(mErr, originalErr)
	}
	// A reader wasn't provided, prints the basic summary json and return.
	if reader == nil {
		log.Output(basicSummary)
		return summaryPrintError(mErr, originalErr)
	}
	reader.Reset()
	defer reader.Close()
	writer, mErr := content.NewContentWriter("files", false, true)
	if mErr != nil {
		log.Output(basicSummary)
		return summaryPrintError(mErr, originalErr)
	}
	// We remove the closing curly bracket in order to append the affected files array using a responseWriter to write directly to stdout.
	basicSummary = strings.TrimSuffix(basicSummary, "\n}") + ","
	log.Output(basicSummary)
	defer log.Output("}")
	for file := new(serviceutils.FileInfo); reader.NextRecord(file) == nil; file = new(serviceutils.FileInfo) {
		record := detailedSummaryRecord{
			Source: rtUrl + file.ArtifactoryPath,
			Target: file.LocalPath,
		}
		writer.Write(record)
	}
	mErr = writer.Close()
	return summaryPrintError(mErr, originalErr)
}

func CreateSummaryReportString(success, failed int, err error) (string, error) {
	summaryReport := summary.New(err)
	summaryReport.Totals.Success = success
	summaryReport.Totals.Failure = failed
	if err == nil && summaryReport.Totals.Failure != 0 {
		summaryReport.Status = summary.Failure
	}
	content, mErr := summaryReport.Marshal()
	if errorutils.CheckError(mErr) != nil {
		return "", mErr
	}
	return utils.IndentJson(content), mErr
}

func PrintHelpAndReturnError(msg string, context *cli.Context) error {
	log.Error(msg + " " + GetDocumentationMessage())
	cli.ShowCommandHelp(context, context.Command.Name)
	return errors.New(msg)
}

// This function indicates whether the command should be executed without
// confirmation warning or not.
// If the --quiet option was sent, it is used to determine whether to prompt the confirmation or not.
// If not, the command will prompt the confirmation, unless the CI environment variable was set to true.
func GetQuietValue(c *cli.Context) bool {
	if c.IsSet("quiet") {
		return c.Bool("quiet")
	}

	return getCiValue()
}

// This function indicates whether the command should be executed in
// an interactive mode.
// If the --interactive option was sent, it is used to determine the mode.
// If not, the mode will be interactive, unless the CI environment variable was set to true.
func GetInteractiveValue(c *cli.Context) bool {
	if c.IsSet("interactive") {
		return c.BoolT("interactive")
	}

	return !getCiValue()
}

// Return true if the CI environment variable was set to true.
func getCiValue() bool {
	var ci bool
	var err error
	if ci, err = clientutils.GetBoolEnvValue(coreutils.CI, false); err != nil {
		return false
	}
	return ci
}

func GetVersion() string {
	return CliVersion
}

func GetDocumentationMessage() string {
	return "You can read the documentation at https://www.jfrog.com/confluence/display/CLI/JFrog+CLI"
}

func GetBuildName(buildName string) string {
	return getOrDefaultEnv(buildName, coreutils.BuildName)
}

func GetBuildUrl(buildUrl string) string {
	return getOrDefaultEnv(buildUrl, BuildUrl)
}

func GetEnvExclude(envExclude string) string {
	return getOrDefaultEnv(envExclude, EnvExclude)
}

// Return argument if not empty or retrieve from environment variable
func getOrDefaultEnv(arg, envKey string) string {
	if arg != "" {
		return arg
	}
	return os.Getenv(envKey)
}
<commit_msg>Bugfix - upload detailed summary target and source mismatch (#878)<commit_after>package cliutils

import (
	"os"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/jfrog/jfrog-cli-core/utils/coreutils"
	"github.com/jfrog/jfrog-cli/utils/summary"
	serviceutils "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
	"github.com/jfrog/jfrog-client-go/utils"
	clientutils "github.com/jfrog/jfrog-client-go/utils"
	"github.com/jfrog/jfrog-client-go/utils/errorutils"
	"github.com/jfrog/jfrog-client-go/utils/io/content"
	"github.com/jfrog/jfrog-client-go/utils/log"
	"github.com/pkg/errors"
)

// Error modes (how should the application behave when the CheckError function is invoked):
type OnError string

func init() {
	// Initialize cli-core values.
	cliUserAgent := os.Getenv(UserAgent)
	if cliUserAgent == "" {
		cliUserAgent = ClientAgent + "/" + CliVersion
	}
	coreutils.SetCliUserAgent(cliUserAgent)
	coreutils.SetClientAgent(ClientAgent)
	coreutils.SetVersion(CliVersion)
}

func GetCliError(err error, success, failed int, failNoOp bool) error {
	switch coreutils.GetExitCode(err, success, failed, failNoOp) {
	case coreutils.ExitCodeError:
		{
			var errorMessage string
			if err != nil {
				errorMessage = err.Error()
			}
			return coreutils.CliError{ExitCode: coreutils.ExitCodeError, ErrorMsg: errorMessage}
		}
	case coreutils.ExitCodeFailNoOp:
		return coreutils.CliError{ExitCode: coreutils.ExitCodeFailNoOp, ErrorMsg: "No errors, but also no files affected (fail-no-op flag)."}
	default:
		return nil
	}
}

type detailedSummaryRecord struct {
	Source string `json:"source"`
	Target string `json:"target"`
}

// Print summary report.
// a given non-nil error will pass through and be returned as is if no other errors are raised.
// In case of a nil error, the current function error will be returned.
func summaryPrintError(summaryError, originalError error) error {
	if originalError != nil {
		if summaryError != nil {
			log.Error(summaryError)
		}
		return originalError
	}
	return summaryError
}

// If a resultReader is provided, we will iterate over the result and print a detailed summary including the affected files.
func PrintSummaryReport(success, failed int, reader *content.ContentReader, rtUrl string, originalErr error) error {
	basicSummary, mErr := CreateSummaryReportString(success, failed, originalErr)
	if mErr != nil {
		return summaryPrintError(mErr, originalErr)
	}
	// A reader wasn't provided, prints the basic summary json and return.
	if reader == nil {
		log.Output(basicSummary)
		return summaryPrintError(mErr, originalErr)
	}
	reader.Reset()
	defer reader.Close()
	writer, mErr := content.NewContentWriter("files", false, true)
	if mErr != nil {
		log.Output(basicSummary)
		return summaryPrintError(mErr, originalErr)
	}
	// We remove the closing curly bracket in order to append the affected files array using a responseWriter to write directly to stdout.
	basicSummary = strings.TrimSuffix(basicSummary, "\n}") + ","
	log.Output(basicSummary)
	defer log.Output("}")
	for file := new(serviceutils.FileInfo); reader.NextRecord(file) == nil; file = new(serviceutils.FileInfo) {
		source, target := getSourceAndTarget(*file, rtUrl)
		record := detailedSummaryRecord{
			Source: source,
			Target: target,
		}
		writer.Write(record)
	}
	mErr = writer.Close()
	return summaryPrintError(mErr, originalErr)
}

func getSourceAndTarget(file serviceutils.FileInfo, rtUrl string) (source, target string) {
	if rtUrl != "" {
		// Download
		source = rtUrl + file.ArtifactoryPath
		target = file.LocalPath
	} else {
		// Upload
		source = file.LocalPath
		target = file.ArtifactoryPath
	}
	return
}

func CreateSummaryReportString(success, failed int, err error) (string, error) {
	summaryReport := summary.New(err)
	summaryReport.Totals.Success = success
	summaryReport.Totals.Failure = failed
	if err == nil && summaryReport.Totals.Failure != 0 {
		summaryReport.Status = summary.Failure
	}
	content, mErr := summaryReport.Marshal()
	if errorutils.CheckError(mErr) != nil {
		return "", mErr
	}
	return utils.IndentJson(content), mErr
}

func PrintHelpAndReturnError(msg string, context *cli.Context) error {
	log.Error(msg + " " + GetDocumentationMessage())
	cli.ShowCommandHelp(context, context.Command.Name)
	return errors.New(msg)
}

// This function indicates whether the command should be executed without
// confirmation warning or not.
// If the --quiet option was sent, it is used to determine whether to prompt the confirmation or not.
// If not, the command will prompt the confirmation, unless the CI environment variable was set to true.
func GetQuietValue(c *cli.Context) bool {
	if c.IsSet("quiet") {
		return c.Bool("quiet")
	}

	return getCiValue()
}

// This function indicates whether the command should be executed in
// an interactive mode.
// If the --interactive option was sent, it is used to determine the mode.
// If not, the mode will be interactive, unless the CI environment variable was set to true.
func GetInteractiveValue(c *cli.Context) bool {
	if c.IsSet("interactive") {
		return c.BoolT("interactive")
	}

	return !getCiValue()
}

// Return true if the CI environment variable was set to true.
func getCiValue() bool {
	var ci bool
	var err error
	if ci, err = clientutils.GetBoolEnvValue(coreutils.CI, false); err != nil {
		return false
	}
	return ci
}

func GetVersion() string {
	return CliVersion
}

func GetDocumentationMessage() string {
	return "You can read the documentation at https://www.jfrog.com/confluence/display/CLI/JFrog+CLI"
}

func GetBuildName(buildName string) string {
	return getOrDefaultEnv(buildName, coreutils.BuildName)
}

func GetBuildUrl(buildUrl string) string {
	return getOrDefaultEnv(buildUrl, BuildUrl)
}

func GetEnvExclude(envExclude string) string {
	return getOrDefaultEnv(envExclude, EnvExclude)
}

// Return argument if not empty or retrieve from environment variable
func getOrDefaultEnv(arg, envKey string) string {
	if arg != "" {
		return arg
	}
	return os.Getenv(envKey)
}
<|endoftext|>


<commit_before>package fblib

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

	"github.com/davecgh/go-spew/spew"

	"github.com/novatrixtech/go-fbmessenger/fbmodelsend"
)

var logLevelDebug = false

/*
SendTextMessage - Send text message to a recipient on Facebook Messenger
*/
func SendTextMessage(text string, recipient string) {
	letter := new(fbmodelsend.Letter)
	letter.Message.Text = text
	letter.Recipient.ID = recipient

	if err := sendMessage(letter, recipient); err != nil {
		fmt.Print("[sendTextMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
}

/*
SendImageMessage - Send image message to a recipient on Facebook Messenger
*/
func SendImageMessage(url string, recipient string) {
	message := new(fbmodelsend.Letter)

	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "image"
	attch.Payload.URL = url
	message.Message.Attachment = attch

	message.Recipient.ID = recipient

	if err := sendMessage(message, recipient); err != nil {
		fmt.Print("[sendImageMessage] Error during the call to Facebook to send the image message: " + err.Error())
		return
	}
}

/*
SendTypingMessage - Send typing message to user
*/
func SendTypingMessage(onoff bool, recipient string) {
	senderAction := new(fbmodelsend.SenderAction)
	senderAction.Recipient.ID = recipient
	if onoff {
		senderAction.SenderActionState = "typing_on"
	} else {
		senderAction.SenderActionState = "typing_off"
	}
	if err := sendMessage(senderAction, recipient); err != nil {
		fmt.Print("[sendImageMessage] Error during the call to Facebook to send the typing message: " + err.Error())
		return
	}
}

/*
SendGenericTemplateMessage - Sends a generic rich message to Facebook user.
It can include text, buttons, URLs Butttons, lists to reply
*/
func SendGenericTemplateMessage(template []*fbmodelsend.TemplateElement, recipient string) {
	msg := new(fbmodelsend.Letter)
	msg.Recipient.ID = recipient

	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "template"
	attch.Payload.TemplateType = "generic"
	attch.Payload.Elements = template

	msg.Message.Attachment = attch

	if err := sendMessage(msg, recipient); err != nil {
		fmt.Print("[SendGenericTemplateMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
}

/*
SendButtonMessage - Sends a generic rich message to Facebook user.
It can include text, buttons, URLs Butttons, lists to reply
*/
func SendButtonMessage(template []*fbmodelsend.Button, text string, recipient string) {
	msg := new(fbmodelsend.Letter)
	msg.Recipient.ID = recipient

	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "template"
	attch.Payload.TemplateType = "button"
	attch.Payload.Text = text
	attch.Payload.Buttons = template

	msg.Message.Attachment = attch

	if err := sendMessage(msg, recipient); err != nil {
		fmt.Print("[sendTextMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
}

/*
SendURLButtonMessage - Sends a message with a button that redirects the user to an external web page.
*/
func SendURLButtonMessage(text string, buttonTitle string, URL string, recipient string) {

	msgElement := new(fbmodelsend.TemplateElement)
	msgElement.Title = text

	opt1 := new(fbmodelsend.Button)
	opt1.ButtonType = "web_url"
	opt1.Title = buttonTitle
	opt1.URL = URL

	buttons := []*fbmodelsend.Button{opt1}

	msgElement.Buttons = buttons
	elements := []*fbmodelsend.TemplateElement{msgElement}
	SendGenericTemplateMessage(elements, recipient)
}

/*
SendShareMessage sends the message along with Share Button
*/
func SendShareMessage(text string, subtitle string, recipient string) {

	msgElement := new(fbmodelsend.TemplateElement)
	msgElement.Title = text
	msgElement.Subtitle = subtitle

	opt1 := new(fbmodelsend.Button)
	opt1.ButtonType = "element_share"
	buttons := []*fbmodelsend.Button{opt1}

	msgElement.Buttons = buttons
	elements := []*fbmodelsend.TemplateElement{msgElement}
	SendGenericTemplateMessage(elements, recipient)

}

/*
SendQuickReply sends small messages in order to get small and quick answers from the users
*/
func SendQuickReply(text string, options []*fbmodelsend.QuickReply, recipient string) {
	msg := new(fbmodelsend.Letter)
	msg.Recipient.ID = recipient
	msg.Message.Text = text
	msg.Message.QuickReplies = options
	//log.Printf("[SendQuickReply] Enviado: [%s]\n", text)
	if err := sendMessage(msg, recipient); err != nil {
		log.Print("[SendQuickReply] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
}

/*
SendAskUserLocation sends small message asking the users their location
*/
func SendAskUserLocation(text string, recipient string) {
	qr := new(fbmodelsend.QuickReply)
	qr.ContentType = "location"

	arrayQr := []*fbmodelsend.QuickReply{qr}

	SendQuickReply(text, arrayQr, recipient)
}

/*
Send Message - Sends a generic message to Facebook Messenger
*/
func sendMessage(message interface{}, recipient string) error {

	if logLevelDebug {
		scs := spew.ConfigState{Indent: "\t"}
		scs.Dump(message)
		return nil
	}

	url := "https://graph.facebook.com/v2.8/me/messages?access_token=" + fbAccessToken

	data, err := json.Marshal(message)
	if err != nil {
		fmt.Print("[sendMessage] Error to convert message object: " + err.Error())
		return err
	}

	reqFb, _ := http.NewRequest("POST", url, bytes.NewBuffer(data))
	reqFb.Header.Set("Content-Type", "application/json")

	client := &http.Client{}

	//fmt.Println("[sendMessage] Replying at: " + url + " the message " + string(data))

	respFb, err := client.Do(reqFb)
	if err != nil {
		fmt.Print("[sendMessage] Error during the call to Facebook to send the message: " + err.Error())
		return err
	}
	defer respFb.Body.Close()

	if respFb.StatusCode < 200 || respFb.StatusCode >= 300 {
		bodyFromFb, _ := ioutil.ReadAll(respFb.Body)
		status := string(bodyFromFb)
		fmt.Printf("[sendMessage] Response status code: [%d]\n", respFb.StatusCode)
		fmt.Println("[sendMessage] Response status: ", respFb.Status)
		fmt.Println("[sendMessage] Response Body from Facebook: ", status)
		fmt.Printf("[sendMessage] Object sent to Facebook: [%s]\n", string(data))
	}

	return nil
}
<commit_msg>Debug<commit_after>package fblib

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

	"github.com/davecgh/go-spew/spew"

	"github.com/novatrixtech/go-fbmessenger/fbmodelsend"
)

var logLevelDebug = false

/*
SendTextMessage - Send text message to a recipient on Facebook Messenger
*/
func SendTextMessage(text string, recipient string) {
	letter := new(fbmodelsend.Letter)
	letter.Message.Text = text
	letter.Recipient.ID = recipient

	if err := sendMessage(letter, recipient); err != nil {
		fmt.Print("[sendTextMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
}

/*
SendImageMessage - Send image message to a recipient on Facebook Messenger
*/
func SendImageMessage(url string, recipient string) {
	message := new(fbmodelsend.Letter)

	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "image"
	attch.Payload.URL = url
	message.Message.Attachment = attch

	message.Recipient.ID = recipient

	if err := sendMessage(message, recipient); err != nil {
		fmt.Print("[sendImageMessage] Error during the call to Facebook to send the image message: " + err.Error())
		return
	}
}

/*
SendTypingMessage - Send typing message to user
*/
func SendTypingMessage(onoff bool, recipient string) {
	senderAction := new(fbmodelsend.SenderAction)
	senderAction.Recipient.ID = recipient
	if onoff {
		senderAction.SenderActionState = "typing_on"
	} else {
		senderAction.SenderActionState = "typing_off"
	}
	if err := sendMessage(senderAction, recipient); err != nil {
		fmt.Print("[sendImageMessage] Error during the call to Facebook to send the typing message: " + err.Error())
		return
	}
}

/*
SendGenericTemplateMessage - Sends a generic rich message to Facebook user.
It can include text, buttons, URLs Butttons, lists to reply
*/
func SendGenericTemplateMessage(template []*fbmodelsend.TemplateElement, recipient string) {
	msg := new(fbmodelsend.Letter)
	msg.Recipient.ID = recipient

	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "template"
	attch.Payload.TemplateType = "generic"
	attch.Payload.Elements = template

	msg.Message.Attachment = attch

	if err := sendMessage(msg, recipient); err != nil {
		fmt.Print("[SendGenericTemplateMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
}

/*
SendButtonMessage - Sends a generic rich message to Facebook user.
It can include text, buttons, URLs Butttons, lists to reply
*/
func SendButtonMessage(template []*fbmodelsend.Button, text string, recipient string) {
	msg := new(fbmodelsend.Letter)
	msg.Recipient.ID = recipient

	attch := new(fbmodelsend.Attachment)
	attch.AttachmentType = "template"
	attch.Payload.TemplateType = "button"
	attch.Payload.Text = text
	attch.Payload.Buttons = template

	msg.Message.Attachment = attch

	if err := sendMessage(msg, recipient); err != nil {
		fmt.Print("[sendTextMessage] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
}

/*
SendURLButtonMessage - Sends a message with a button that redirects the user to an external web page.
*/
func SendURLButtonMessage(text string, buttonTitle string, URL string, recipient string) {

	msgElement := new(fbmodelsend.TemplateElement)
	msgElement.Title = text

	opt1 := new(fbmodelsend.Button)
	opt1.ButtonType = "web_url"
	opt1.Title = buttonTitle
	opt1.URL = URL

	buttons := []*fbmodelsend.Button{opt1}

	msgElement.Buttons = buttons
	elements := []*fbmodelsend.TemplateElement{msgElement}
	SendGenericTemplateMessage(elements, recipient)
}

/*
SendShareMessage sends the message along with Share Button
*/
func SendShareMessage(text string, subtitle string, recipient string) {

	msgElement := new(fbmodelsend.TemplateElement)
	msgElement.Title = text
	msgElement.Subtitle = subtitle

	opt1 := new(fbmodelsend.Button)
	opt1.ButtonType = "element_share"
	buttons := []*fbmodelsend.Button{opt1}

	msgElement.Buttons = buttons
	elements := []*fbmodelsend.TemplateElement{msgElement}
	SendGenericTemplateMessage(elements, recipient)

}

/*
SendQuickReply sends small messages in order to get small and quick answers from the users
*/
func SendQuickReply(text string, options []*fbmodelsend.QuickReply, recipient string) {
	msg := new(fbmodelsend.Letter)
	msg.Recipient.ID = recipient
	msg.Message.Text = text
	msg.Message.QuickReplies = options
	//log.Printf("[SendQuickReply] Enviado: [%s]\n", text)
	if err := sendMessage(msg, recipient); err != nil {
		log.Print("[SendQuickReply] Error during the call to Facebook to send the text message: " + err.Error())
		return
	}
}

/*
SendAskUserLocation sends small message asking the users their location
*/
func SendAskUserLocation(text string, recipient string) {
	qr := new(fbmodelsend.QuickReply)
	qr.ContentType = "location"

	arrayQr := []*fbmodelsend.QuickReply{qr}

	SendQuickReply(text, arrayQr, recipient)
}

/*
Send Message - Sends a generic message to Facebook Messenger
*/
func sendMessage(message interface{}, recipient string) error {

	if logLevelDebug {
		scs := spew.ConfigState{Indent: "\t"}
		scs.Dump(message)
		return nil
	}

	url := "https://graph.facebook.com/v2.8/me/messages?access_token=" + fbAccessToken

	data, err := json.Marshal(message)
	if err != nil {
		fmt.Print("[sendMessage] Error to convert message object: " + err.Error())
		return err
	}

	reqFb, _ := http.NewRequest("POST", url, bytes.NewBuffer(data))
	reqFb.Header.Set("Content-Type", "application/json")

	client := &http.Client{}

	//fmt.Println("[sendMessage] Replying at: " + url + " the message " + string(data))

	respFb, err := client.Do(reqFb)
	if err != nil {
		fmt.Print("[sendMessage] Error during the call to Facebook to send the message: " + err.Error())
		return err
	}
	defer respFb.Body.Close()

	if respFb.StatusCode < 200 || respFb.StatusCode >= 300 {
		bodyFromFb, _ := ioutil.ReadAll(respFb.Body)
		status := string(bodyFromFb)
		fmt.Printf("[sendMessage] Response status code: [%d]\n", respFb.StatusCode)
		fmt.Println("[sendMessage] Response status: ", respFb.Status)
		fmt.Println("[sendMessage] Response Body from Facebook: ", status)
		fmt.Printf("[sendMessage] Facebook URL Called: [%s]\n", url)
		fmt.Printf("[sendMessage] Object sent to Facebook: [%s]\n", string(data))
	}

	return nil
}
<|endoftext|>


<commit_before>package toolbox

import (
	"path"
	"runtime"
	"strings"
)

// CallerInfo return filename, function or file line from the stack
func CallerInfo(callerIndex int) (string, string, int) {
	var callerPointer = make([]uintptr, 10) // at least 1 entry needed
	runtime.Callers(callerIndex, callerPointer)
	callerInfo := runtime.FuncForPC(callerPointer[0])
	file, line := callerInfo.FileLine(callerPointer[0])
	callerName := callerInfo.Name()
	dotPosition := strings.LastIndex(callerName, ".")
	return file, callerName[dotPosition+1:], line
}

//CallerDirectory returns directory of caller source code directory
func CallerDirectory(callerIndex int) string {
	file, _, _ := CallerInfo(callerIndex)
	parent, _ := path.Split(file)
	return parent
}
<commit_msg>moved from dsunit<commit_after>package toolbox

import (
	"path"
	"runtime"
	"strings"
)

// CallerInfo return filename, function or file line from the stack
func CallerInfo(callerIndex int) (string, string, int) {
	var callerPointer = make([]uintptr, 10) // at least 1 entry needed
	runtime.Callers(callerIndex, callerPointer)
	callerInfo := runtime.FuncForPC(callerPointer[0])
	file, line := callerInfo.FileLine(callerPointer[0])
	callerName := callerInfo.Name()
	dotPosition := strings.LastIndex(callerName, ".")
	return file, callerName[dotPosition+1:], line
}

//CallerDirectory returns directory of caller source code directory
func CallerDirectory(callerIndex int) string {
	file, _, _ := CallerInfo(callerIndex)
	parent, _ := path.Split(file)
	return parent
}

func hasMatch(target string, candidates ...string) bool {
	for _, candidate := range candidates {
		if strings.HasSuffix(target, candidate) {
			return true
		}
	}
	return false
}

//DiscoverCaller returns the first matched caller info
func DiscoverCaller(offset, maxDepth int, ignoreFiles ...string) (string, string, int) {
	var callerPointer = make([]uintptr, maxDepth) // at least 1 entry needed
	var caller *runtime.Func
	var filename string
	var line int
	for i := offset; i < maxDepth; i++ {
		runtime.Callers(i, callerPointer)
		caller = runtime.FuncForPC(callerPointer[0])
		filename, line = caller.FileLine(callerPointer[0])
		if hasMatch(filename, ignoreFiles...) {
			continue
		}
		break
	}
	callerName := caller.Name()
	dotPosition := strings.LastIndex(callerName, ".")
	return filename, callerName[dotPosition+1:], line
}<|endoftext|>


<commit_before>package main

import (
	"github.com/disintegration/imaging"
	"github.com/minotar/minecraft"
	"image"
	"image/png"
	"io"
)

const (
	HeadX      = 8
	HeadY      = 8
	HeadWidth  = 8
	HeadHeight = 8

	HelmX      = 40
	HelmY      = 8
	HelmWidth  = 8
	HelmHeight = 8

	TorsoX      = 20
	TorsoY      = 20
	TorsoWidth  = 8
	TorsoHeight = 12

	RaX      = 44
	RaY      = 20
	RaWidth  = 4
	RaHeight = 12

	RlX      = 4
	RlY      = 20
	RlWidth  = 4
	RlHeight = 12

	LaX      = 36
	LaY      = 52
	LaWidth  = 4
	LaHeight = 12

	LlX      = 20
	LlY      = 52
	LlWidth  = 4
	LlHeight = 12

	// The height of the 'bust' relative to the width of the body (16)
	BustHeight = 16
)

type mcSkin struct {
	Processed image.Image
	minecraft.Skin
}

func (skin *mcSkin) GetHead() error {
	skin.Processed = skin.cropHead(skin.Image)
	return nil
}

func (skin *mcSkin) GetHelm() error {
	skin.Processed = skin.cropHelm(skin.Image)
	return nil
}

func (skin *mcSkin) GetBody() error {
	// Check if 1.8 skin (the max Y bound should be 64)
	render18Skin := true
	bounds := skin.Image.Bounds()
	if bounds.Max.Y != 64 {
		render18Skin = false
	}

	helmImg := skin.cropHelm(skin.Image)
	torsoImg := imaging.Crop(skin.Image, image.Rect(TorsoX, TorsoY, TorsoX+TorsoWidth, TorsoY+TorsoHeight))
	raImg := imaging.Crop(skin.Image, image.Rect(RaX, RaY, RaX+RaWidth, RaY+RaHeight))
	rlImg := imaging.Crop(skin.Image, image.Rect(RlX, RlY, RlX+RlWidth, RlY+RlHeight))

	var laImg, llImg image.Image

	// If the skin is 1.8 then we will use the left arms and legs, otherwise flip the right ones and use them.
	if render18Skin {
		laImg = imaging.Crop(skin.Image, image.Rect(LaX, LaY, LaX+LaWidth, LaY+LaHeight))
		llImg = imaging.Crop(skin.Image, image.Rect(LlX, LlY, LlX+LlWidth, LlY+LlHeight))
	} else {
		laImg = imaging.FlipH(raImg)

		llImg = imaging.FlipH(rlImg)
	}

	// Create a blank canvas for us to draw our body on
	bodyImg := image.NewNRGBA(image.Rect(0, 0, LaWidth+TorsoWidth+RaWidth, HeadHeight+TorsoHeight+LlHeight))
	// Helm
	fastDraw(bodyImg, helmImg.(*image.NRGBA), LaWidth, 0)
	// Torso
	fastDraw(bodyImg, torsoImg, LaWidth, HelmHeight)
	// Left Arm
	fastDraw(bodyImg, laImg.(*image.NRGBA), 0, HelmHeight)
	// Right Arm
	fastDraw(bodyImg, raImg, LaWidth+TorsoWidth, HelmHeight)
	// Left Leg
	fastDraw(bodyImg, llImg.(*image.NRGBA), LaWidth, HelmHeight+TorsoHeight)
	// Right Leg
	fastDraw(bodyImg, rlImg, LaWidth+LlWidth, HelmHeight+TorsoHeight)

	skin.Processed = bodyImg
	return nil
}

func (skin *mcSkin) GetBust() error {
	err := skin.GetBody()
	if err != nil {
		return err
	}

	skin.Processed = imaging.Crop(skin.Processed, image.Rect(0, 0, 16, 16))
	return nil
}

func (skin *mcSkin) WritePNG(w io.Writer) error {
	return png.Encode(w, skin.Processed)
}

func (skin *mcSkin) WriteSkin(w io.Writer) error {
	return png.Encode(w, skin.Image)
}

func (skin *mcSkin) Resize(width uint) {
	skin.Processed = imaging.Resize(skin.Processed, int(width), 0, imaging.NearestNeighbor)
}

// Removes the skin's alpha matte from the given image.
func (skin *mcSkin) removeAlpha(img *image.NRGBA) {
	// If it's already a transparent image, do nothing
	if skin.AlphaSig[3] == 0 {
		return
	}

	// Otherwise loop through all the pixels and fix em
	for i := 0; i < len(img.Pix); i += 4 {
		if img.Pix[i+0] == skin.AlphaSig[0] &&
			img.Pix[i+1] == skin.AlphaSig[1] &&
			img.Pix[i+2] == skin.AlphaSig[2] &&
			img.Pix[i+3] == skin.AlphaSig[3] {
			img.Pix[i+3] = 0
		}
	}
}

func (skin *mcSkin) cropHead(img image.Image) image.Image {
	return imaging.Crop(img, image.Rect(HeadX, HeadY, HeadX+HeadWidth, HeadY+HeadHeight))
}

func (skin *mcSkin) cropHelm(img image.Image) image.Image {
	headImg := skin.cropHead(img)
	helmImg := imaging.Crop(img, image.Rect(HelmX, HelmY, HelmX+HelmWidth, HelmY+HelmHeight))
	skin.removeAlpha(helmImg)
	fastDraw(headImg.(*image.NRGBA), helmImg, 0, 0)

	return headImg
}

func fastDraw(dst *image.NRGBA, src *image.NRGBA, x, y int) {
	bounds := src.Bounds()
	maxY := bounds.Max.Y
	maxX := bounds.Max.X * 4

	pointer := dst.PixOffset(x, y)
	for row := 0; row < maxY; row += 1 {
		for i := 0; i < maxX; i += 4 {
			srcPx := row*src.Stride + i
			dstPx := row*dst.Stride + i + pointer
			if src.Pix[srcPx+3] != 0 {
				dst.Pix[dstPx+0] = src.Pix[srcPx+0]
				dst.Pix[dstPx+1] = src.Pix[srcPx+1]
				dst.Pix[dstPx+2] = src.Pix[srcPx+2]
				dst.Pix[dstPx+3] = 0xFF
			}
		}
	}
}
<commit_msg>Some comments here and there<commit_after>package main

import (
	"github.com/disintegration/imaging"
	"github.com/minotar/minecraft"
	"image"
	"image/png"
	"io"
)

const (
	HeadX      = 8
	HeadY      = 8
	HeadWidth  = 8
	HeadHeight = 8

	HelmX      = 40
	HelmY      = 8
	HelmWidth  = 8
	HelmHeight = 8

	TorsoX      = 20
	TorsoY      = 20
	TorsoWidth  = 8
	TorsoHeight = 12

	RaX      = 44
	RaY      = 20
	RaWidth  = 4
	RaHeight = 12

	RlX      = 4
	RlY      = 20
	RlWidth  = 4
	RlHeight = 12

	LaX      = 36
	LaY      = 52
	LaWidth  = 4
	LaHeight = 12

	LlX      = 20
	LlY      = 52
	LlWidth  = 4
	LlHeight = 12

	// The height of the 'bust' relative to the width of the body (16)
	BustHeight = 16
)

type mcSkin struct {
	Processed image.Image
	minecraft.Skin
}

// Returns the "face" of the skin.
func (skin *mcSkin) GetHead() error {
	skin.Processed = skin.cropHead(skin.Image)
	return nil
}

// Returns the face of the skin overlayed with the helmet texture.
func (skin *mcSkin) GetHelm() error {
	skin.Processed = skin.cropHelm(skin.Image)
	return nil
}

// Gets the full frontal body shot of the image.
func (skin *mcSkin) GetBody() error {
	// Check if 1.8 skin (the max Y bound should be 64). Pre-1.8 skins are
	// 32 pixels in height.
	render18Skin := true
	bounds := skin.Image.Bounds()
	if bounds.Max.Y != 64 {
		render18Skin = false
	}

	// Crop out the parts we'll need to piece together in the image.
	helmImg := skin.cropHelm(skin.Image)
	torsoImg := imaging.Crop(skin.Image, image.Rect(TorsoX, TorsoY, TorsoX+TorsoWidth, TorsoY+TorsoHeight))
	raImg := imaging.Crop(skin.Image, image.Rect(RaX, RaY, RaX+RaWidth, RaY+RaHeight))
	rlImg := imaging.Crop(skin.Image, image.Rect(RlX, RlY, RlX+RlWidth, RlY+RlHeight))

	var laImg, llImg image.Image

	// If the skin is 1.8 then we will use the left arms and legs, otherwise
	// flip the right ones and use them.
	if render18Skin {
		laImg = imaging.Crop(skin.Image, image.Rect(LaX, LaY, LaX+LaWidth, LaY+LaHeight))
		llImg = imaging.Crop(skin.Image, image.Rect(LlX, LlY, LlX+LlWidth, LlY+LlHeight))
	} else {
		laImg = imaging.FlipH(raImg)
		llImg = imaging.FlipH(rlImg)
	}

	// Create a blank canvas for us to draw our body on
	bodyImg := image.NewNRGBA(image.Rect(0, 0, LaWidth+TorsoWidth+RaWidth, HeadHeight+TorsoHeight+LlHeight))
	// Helm
	fastDraw(bodyImg, helmImg.(*image.NRGBA), LaWidth, 0)
	// Torso
	fastDraw(bodyImg, torsoImg, LaWidth, HelmHeight)
	// Left Arm
	fastDraw(bodyImg, laImg.(*image.NRGBA), 0, HelmHeight)
	// Right Arm
	fastDraw(bodyImg, raImg, LaWidth+TorsoWidth, HelmHeight)
	// Left Leg
	fastDraw(bodyImg, llImg.(*image.NRGBA), LaWidth, HelmHeight+TorsoHeight)
	// Right Leg
	fastDraw(bodyImg, rlImg, LaWidth+LlWidth, HelmHeight+TorsoHeight)

	skin.Processed = bodyImg
	return nil
}

// Returns the upper portion of the body - like GetBody, but without the legs.
func (skin *mcSkin) GetBust() error {
	err := skin.GetBody()
	if err != nil {
		return err
	}

	skin.Processed = imaging.Crop(skin.Processed, image.Rect(0, 0, 16, 16))
	return nil
}

// Writes the *processed* image as a PNG to the given writer.
func (skin *mcSkin) WritePNG(w io.Writer) error {
	return png.Encode(w, skin.Processed)
}

// Writes the *original* skin image as a png to the given writer.
func (skin *mcSkin) WriteSkin(w io.Writer) error {
	return png.Encode(w, skin.Image)
}

// Resizes the skin to the given dimensions, keeping aspect ratio.
func (skin *mcSkin) Resize(width uint) {
	skin.Processed = imaging.Resize(skin.Processed, int(width), 0, imaging.NearestNeighbor)
}

// Removes the skin's alpha matte from the given image.
func (skin *mcSkin) removeAlpha(img *image.NRGBA) {
	// If it's already a transparent image, do nothing
	if skin.AlphaSig[3] == 0 {
		return
	}

	// Otherwise loop through all the pixels. Check to see which ones match
	// the alpha signature and set their opacity to be zero.
	for i := 0; i < len(img.Pix); i += 4 {
		if img.Pix[i+0] == skin.AlphaSig[0] &&
			img.Pix[i+1] == skin.AlphaSig[1] &&
			img.Pix[i+2] == skin.AlphaSig[2] &&
			img.Pix[i+3] == skin.AlphaSig[3] {
			img.Pix[i+3] = 0
		}
	}
}

// Returns the head of the skin image.
func (skin *mcSkin) cropHead(img image.Image) image.Image {
	return imaging.Crop(img, image.Rect(HeadX, HeadY, HeadX+HeadWidth, HeadY+HeadHeight))
}

// Returns the head of the skin image overlayed with the helm.
func (skin *mcSkin) cropHelm(img image.Image) image.Image {
	headImg := skin.cropHead(img)
	helmImg := imaging.Crop(img, image.Rect(HelmX, HelmY, HelmX+HelmWidth, HelmY+HelmHeight))
	skin.removeAlpha(helmImg)
	fastDraw(headImg.(*image.NRGBA), helmImg, 0, 0)

	return headImg
}

// Draws the "src" onto the "dst" image at the given x/y bounds, maintaining
// the original size. Pixels with have an alpha of 0x00 are not draw, and
// all others are drawn with an alpha of 0xFF
func fastDraw(dst *image.NRGBA, src *image.NRGBA, x, y int) {
	bounds := src.Bounds()
	maxY := bounds.Max.Y
	maxX := bounds.Max.X * 4

	pointer := dst.PixOffset(x, y)
	for row := 0; row < maxY; row += 1 {
		for i := 0; i < maxX; i += 4 {
			srcPx := row*src.Stride + i
			dstPx := row*dst.Stride + i + pointer
			if src.Pix[srcPx+3] != 0 {
				dst.Pix[dstPx+0] = src.Pix[srcPx+0]
				dst.Pix[dstPx+1] = src.Pix[srcPx+1]
				dst.Pix[dstPx+2] = src.Pix[srcPx+2]
				dst.Pix[dstPx+3] = 0xFF
			}
		}
	}
}
<|endoftext|>


<commit_before>package cortexbot

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sort"
	"strings"

	valid "github.com/asaskevich/govalidator"
	"github.com/go-telegram-bot-api/telegram-bot-api"
	"github.com/ilyaglow/go-cortex"
)

// sendReport sends a report depends on success
func (c *Client) sendReport(r *cortex.Report, callback *tgbotapi.CallbackQuery) error {
	if r.Status == "Failure" {
		return fmt.Errorf("Analyzer %s failed with error message: %s", r.AnalyzerName, r.ReportBody.ErrorMessage)
	}

	// Send JSON file with full report and taxonomies
	tr, _ := json.MarshalIndent(r, "", "  ")

	fb := tgbotapi.FileBytes{
		Name:  fmt.Sprintf("%s-%s.json", r.AnalyzerName, r.ID),
		Bytes: tr,
	}

	attachment := tgbotapi.NewDocumentUpload(callback.Message.Chat.ID, fb)
	attachment.ReplyToMessageID = callback.Message.ReplyToMessage.MessageID
	attachment.Caption = buildTaxonomies(r.Taxonomies())

	_, err := c.Bot.Send(attachment)
	return err
}

// processCallback analyzes observables with a selected set of analyzers
func (c *Client) processCallback(callback *tgbotapi.CallbackQuery) error {
	var j cortex.Observable
	var err error

	if callback.Message.ReplyToMessage.Document != nil {
		link, err := c.Bot.GetFileDirectURL(callback.Message.ReplyToMessage.Document.FileID)
		if err != nil {
			log.Println("Can't get direct link to file")
			return err
		}

		j, err = newFileArtifactFromURL(link, callback.Message.ReplyToMessage.Document.FileName, c.TLP, c.Bot.Client)
		if err != nil {
			log.Println(err.Error())
			return err
		}
	} else {
		j = newArtifact(callback.Message.ReplyToMessage.Text, c.TLP)
	}

	switch callback.Data {
	case "all":
		mul := c.Cortex.Analyzers.NewMultiRun(context.Background(), c.Timeout)
		mul.OnReport = func(r *cortex.Report) {
			err = c.sendReport(r, callback)
			log.Println(err)
		}

		mul.OnError = func(e error, o cortex.Observable, a *cortex.Analyzer) {
			log.Println(fmt.Sprintf("Cortex analyzer %s failed on data %s with an error: %s", a.Name, o.Description(), e.Error()))
		}

		err = mul.Do(j)
		if err != nil {
			return err
		}
	case "close":
		kb := showButton()
		edit := tgbotapi.NewEditMessageReplyMarkup(callback.Message.Chat.ID, callback.Message.MessageID, *kb)
		if _, err := c.Bot.Send(edit); err != nil {
			return err
		}
	case "show":
		kb, err := c.analyzersButtons(dataType(callback.Message.ReplyToMessage.Text))
		if err != nil {
			return err
		}
		edit := tgbotapi.NewEditMessageReplyMarkup(callback.Message.Chat.ID, callback.Message.MessageID, *kb)
		if _, err := c.Bot.Send(edit); err != nil {
			return err
		}
	default:
		r, err := c.Cortex.Analyzers.Run(context.Background(), callback.Data, j, c.Timeout)
		if err != nil {
			msg := tgbotapi.NewMessage(callback.Message.Chat.ID, fmt.Sprintf("%s failed: %s", callback.Data, err.Error()))
			msg.ReplyToMessageID = callback.Message.MessageID
			if _, err := c.Bot.Send(msg); err != nil {
				return err
			}
		} else {
			err = c.sendReport(r, callback)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// analyzersButtons returns a markup of analyzers as buttons
func (c *Client) analyzersButtons(datatype string) (*tgbotapi.InlineKeyboardMarkup, error) {
	analyzers, _, err := c.Cortex.Analyzers.ListByType(context.Background(), datatype)
	if err != nil {
		return nil, err
	}

	var names []string
	for i := range analyzers {
		names = append(names, analyzers[i].Name)
	}
	sort.Strings(names)

	var rows [][]tgbotapi.InlineKeyboardButton
	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("All", "all"),
	})

	for _, n := range names {
		var buttons []tgbotapi.InlineKeyboardButton
		b := tgbotapi.NewInlineKeyboardButtonData(n, n)
		buttons = append(buttons, b)
		rows = append(rows, buttons)
	}

	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("Close", "close"),
	})

	markup := tgbotapi.NewInlineKeyboardMarkup(rows...)
	return &markup, nil
}

// showButton returns only one button, used on close callback
func showButton() *tgbotapi.InlineKeyboardMarkup {
	var rows [][]tgbotapi.InlineKeyboardButton
	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("Show", "show"),
	})

	markup := tgbotapi.NewInlineKeyboardMarkup(rows...)
	return &markup
}

// processMessage asks Cortex about data submitted by a user
func (c *Client) processMessage(input *tgbotapi.Message) error {
	var err error

	msg := tgbotapi.NewMessage(input.Chat.ID, "Select analyzer to run. Choose <All> to run all of them.")
	msg.ReplyToMessageID = input.MessageID

	bmarkup := &tgbotapi.InlineKeyboardMarkup{}
	var dt string

	if input.Document != nil {
		dt = "file"
	} else {
		dt = dataType(input.Text)
	}

	bmarkup, err = c.analyzersButtons(dt)
	if err != nil {
		return err
	}
	msg.ReplyMarkup = bmarkup

	_, err = c.Bot.Send(msg)
	if err != nil {
		return err
	}

	return nil
}

// buildTaxonomies joins taxonomies in one formatted string
// Every taxonomy is separated with two spaces from each other
func buildTaxonomies(txs []cortex.Taxonomy) string {
	var stats []string

	for _, t := range txs {
		stats = append(stats, fmt.Sprintf("%s:%s = %v", t.Namespace, t.Predicate, t.Value))
	}
	return strings.Join(stats, "\n")
}

// newArtifact makes an Artifact depends on its type
func newArtifact(s string, tlp int) cortex.Observable {
	return &cortex.Task{
		Data:     s,
		DataType: dataType(s),
		TLP:      &tlp,
	}
}

func dataType(d string) (t string) {
	if valid.IsIP(d) {
		t = "ip"
	} else if IsDNSName(d) {
		t = "domain"
	} else if IsHash(d) {
		t = "hash"
	} else if valid.IsEmail(d) {
		t = "mail"
	} else if valid.IsURL(d) {
		t = "url"
	} else {
		t = "other"
	}

	return t
}

// newFileArtifactFromURL makes a FileArtifact from URL
func newFileArtifactFromURL(link string, fname string, tlp int, client *http.Client) (cortex.Observable, error) {
	req, err := http.NewRequest("GET", link, nil)
	if err != nil {
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	return &cortex.FileTask{
		FileTaskMeta: cortex.FileTaskMeta{
			DataType: "file",
			TLP:      &tlp,
		},
		FileName: fname,
		Reader:   resp.Body,
	}, nil
}
<commit_msg>Minor cleanup<commit_after>package cortexbot

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"sort"
	"strings"

	valid "github.com/asaskevich/govalidator"
	"github.com/go-telegram-bot-api/telegram-bot-api"
	"github.com/ilyaglow/go-cortex"
)

// sendReport sends a report depends on success
func (c *Client) sendReport(r *cortex.Report, callback *tgbotapi.CallbackQuery) error {
	if r.Status == "Failure" {
		return fmt.Errorf("Analyzer %s failed with error message: %s", r.AnalyzerName, r.ReportBody.ErrorMessage)
	}

	// Send JSON file with full report and taxonomies
	tr, _ := json.MarshalIndent(r, "", "  ")

	fb := tgbotapi.FileBytes{
		Name:  fmt.Sprintf("%s-%s.json", r.AnalyzerName, r.ID),
		Bytes: tr,
	}

	attachment := tgbotapi.NewDocumentUpload(callback.Message.Chat.ID, fb)
	attachment.ReplyToMessageID = callback.Message.ReplyToMessage.MessageID
	attachment.Caption = buildTaxonomies(r.Taxonomies())

	_, err := c.Bot.Send(attachment)
	return err
}

// processCallback analyzes observables with a selected set of analyzers
func (c *Client) processCallback(callback *tgbotapi.CallbackQuery) error {
	var j cortex.Observable
	var err error

	if callback.Message.ReplyToMessage.Document != nil {
		link, err := c.Bot.GetFileDirectURL(callback.Message.ReplyToMessage.Document.FileID)
		if err != nil {
			log.Println("Can't get direct link to file")
			return err
		}

		j, err = newFileArtifactFromURL(link, callback.Message.ReplyToMessage.Document.FileName, c.TLP, c.Bot.Client)
		if err != nil {
			log.Println(err.Error())
			return err
		}
	} else {
		j = newArtifact(callback.Message.ReplyToMessage.Text, c.TLP)
	}

	switch callback.Data {
	case "all":
		mul := c.Cortex.Analyzers.NewMultiRun(context.Background(), c.Timeout)
		mul.OnReport = func(r *cortex.Report) {
			err = c.sendReport(r, callback)
			log.Println(err)
		}

		mul.OnError = func(e error, o cortex.Observable, a *cortex.Analyzer) {
			log.Println(fmt.Sprintf("Cortex analyzer %s failed on data %s with an error: %s", a.Name, o.Description(), e.Error()))
		}

		err = mul.Do(j)
		if err != nil {
			return err
		}
	case "close":
		kb := showButton()
		edit := tgbotapi.NewEditMessageReplyMarkup(callback.Message.Chat.ID, callback.Message.MessageID, *kb)
		if _, err := c.Bot.Send(edit); err != nil {
			return err
		}
	case "show":
		kb, err := c.analyzersButtons(dataType(callback.Message.ReplyToMessage.Text))
		if err != nil {
			return err
		}
		edit := tgbotapi.NewEditMessageReplyMarkup(callback.Message.Chat.ID, callback.Message.MessageID, *kb)
		if _, err := c.Bot.Send(edit); err != nil {
			return err
		}
	default:
		r, err := c.Cortex.Analyzers.Run(context.Background(), callback.Data, j, c.Timeout)
		if err != nil {
			msg := tgbotapi.NewMessage(callback.Message.Chat.ID, fmt.Sprintf("%s failed: %s", callback.Data, err.Error()))
			msg.ReplyToMessageID = callback.Message.MessageID
			if _, err := c.Bot.Send(msg); err != nil {
				return err
			}
		} else {
			err = c.sendReport(r, callback)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// analyzersButtons returns a markup of analyzers as buttons
func (c *Client) analyzersButtons(datatype string) (*tgbotapi.InlineKeyboardMarkup, error) {
	analyzers, _, err := c.Cortex.Analyzers.ListByType(context.Background(), datatype)
	if err != nil {
		return nil, err
	}

	var names []string
	for i := range analyzers {
		names = append(names, analyzers[i].Name)
	}
	sort.Strings(names)

	var rows [][]tgbotapi.InlineKeyboardButton
	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("All", "all"),
	})

	for _, n := range names {
		var buttons []tgbotapi.InlineKeyboardButton
		b := tgbotapi.NewInlineKeyboardButtonData(n, n)
		buttons = append(buttons, b)
		rows = append(rows, buttons)
	}

	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("Close", "close"),
	})

	markup := tgbotapi.NewInlineKeyboardMarkup(rows...)
	return &markup, nil
}

// showButton returns only one button, used on close callback
func showButton() *tgbotapi.InlineKeyboardMarkup {
	var rows [][]tgbotapi.InlineKeyboardButton
	rows = append(rows, []tgbotapi.InlineKeyboardButton{
		tgbotapi.NewInlineKeyboardButtonData("Show", "show"),
	})

	markup := tgbotapi.NewInlineKeyboardMarkup(rows...)
	return &markup
}

// processMessage asks Cortex about data submitted by a user
func (c *Client) processMessage(input *tgbotapi.Message) error {
	msg := tgbotapi.NewMessage(input.Chat.ID, "Select analyzer to run. Choose <All> to run all of them.")
	msg.ReplyToMessageID = input.MessageID

	var dt string

	if input.Document != nil {
		dt = "file"
	} else {
		dt = dataType(input.Text)
	}

	bmarkup, err := c.analyzersButtons(dt)
	if err != nil {
		return err
	}
	msg.ReplyMarkup = bmarkup

	_, err = c.Bot.Send(msg)
	if err != nil {
		return err
	}

	return nil
}

// buildTaxonomies joins taxonomies in one formatted string
// Every taxonomy is separated with two spaces from each other
func buildTaxonomies(txs []cortex.Taxonomy) string {
	var stats []string

	for _, t := range txs {
		stats = append(stats, fmt.Sprintf("%s:%s = %v", t.Namespace, t.Predicate, t.Value))
	}
	return strings.Join(stats, "\n")
}

// newArtifact makes an Artifact depends on its type
func newArtifact(s string, tlp int) cortex.Observable {
	return &cortex.Task{
		Data:     s,
		DataType: dataType(s),
		TLP:      &tlp,
	}
}

func dataType(d string) (t string) {
	if valid.IsIP(d) {
		t = "ip"
	} else if IsDNSName(d) {
		t = "domain"
	} else if IsHash(d) {
		t = "hash"
	} else if valid.IsEmail(d) {
		t = "mail"
	} else if valid.IsURL(d) {
		t = "url"
	} else {
		t = "other"
	}

	return t
}

// newFileArtifactFromURL makes a FileArtifact from URL
func newFileArtifactFromURL(link string, fname string, tlp int, client *http.Client) (cortex.Observable, error) {
	req, err := http.NewRequest("GET", link, nil)
	if err != nil {
		return nil, err
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	return &cortex.FileTask{
		FileTaskMeta: cortex.FileTaskMeta{
			DataType: "file",
			TLP:      &tlp,
		},
		FileName: fname,
		Reader:   resp.Body,
	}, nil
}
<|endoftext|>


<commit_before>// Package profile provides a simple way to manage runtime/pprof
// profiling of your Go application.
package profile

import (
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"runtime/pprof"
	"sync/atomic"
)

// started counts the number of times Start has been called
var started uint32

const (
	cpuMode = iota
	memMode
	blockMode
)

type profile struct {
	// quiet suppresses informational messages during profiling.
	quiet bool

	// noShutdownHook controls whether the profiling package should
	// hook SIGINT to write profiles cleanly.
	noShutdownHook bool

	// mode holds the type of profiling that will be made
	mode int

	// path holds the base path where various profiling files are  written.
	// If blank, the base path will be generated by ioutil.TempDir.
	path string

	// memProfileRate holds the rate for the memory profile.
	memProfileRate int

	// closer holds the cleanup function that run after each profile
	closer func()

	// stopped records if a call to profile.Stop has been made
	stopped uint32
}

// NoShutdownHook controls whether the profiling package should
// hook SIGINT to write profiles cleanly.
// Programs with more sophisticated signal handling should set
// this to true and ensure the Stop() function returned from Start()
// is called during shutdown.
func NoShutdownHook(p *profile) { p.noShutdownHook = true }

// Quiet suppresses informational messages during profiling.
func Quiet(p *profile) { p.quiet = true }

// CPUProfile controls if cpu profiling will be enabled. It disables any previous profiling settings.
func CPUProfile(p *profile) { p.mode = cpuMode }

// DefaultMemProfileRate is the default memory profiling rate.
// See also http://golang.org/pkg/runtime/#pkg-variables
const DefaultMemProfileRate = 4096

// MemProfile controls if memory profiling will be enabled. It disables any previous profiling settings.
func MemProfile(p *profile) {
	p.memProfileRate = DefaultMemProfileRate
	p.mode = memMode
}

// MemProfileRate controls if memory profiling will be enabled. Additionally, it takes a parameter which
// allows the setting of the memory profile rate.
func MemProfileRate(rate int) func(*profile) {
	return func(p *profile) {
		p.memProfileRate = rate
		p.mode = memMode
	}
}

// BlockProfile controls if block (contention) profiling will be enabled. It disables any previous profiling settings.
func BlockProfile(p *profile) { p.mode = blockMode }

// ProfilePath controls the base path where various profiling
// files are written. If blank, the base path will be generated
// by ioutil.TempDir.
func ProfilePath(path string) func(*profile) {
	return func(p *profile) {
		p.path = path
	}
}

// Stop stops the profile and flushes any unwritten data.
func (p *profile) Stop() {
	if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) {
		// someone has already called close
		return
	}
	p.closer()
	atomic.StoreUint32(&started, 0)
}

// Start starts a new profiling session.
// The caller should call the Stop method on the value returned
// to cleanly stop profiling.
func Start(options ...func(*profile)) interface {
	Stop()
} {
	if !atomic.CompareAndSwapUint32(&started, 0, 1) {
		log.Fatal("profile: Start() already called")
	}

	var prof profile
	for _, option := range options {
		option(&prof)
	}

	path, err := func() (string, error) {
		if p := prof.path; p != "" {
			return p, os.MkdirAll(p, 0777)
		}
		return ioutil.TempDir("", "profile")
	}()

	if err != nil {
		log.Fatalf("profile: could not create initial output directory: %v", err)
	}

	switch prof.mode {
	case cpuMode:
		fn := filepath.Join(path, "cpu.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create cpu profile %q: %v", fn, err)
		}
		if !prof.quiet {
			log.Printf("profile: cpu profiling enabled, %s", fn)
		}
		pprof.StartCPUProfile(f)
		prof.closer = func() {
			pprof.StopCPUProfile()
			f.Close()
			if !prof.quiet {
				log.Printf("profile: cpu profiling disabled, %s", fn)
			}
		}

	case memMode:
		fn := filepath.Join(path, "mem.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create memory profile %q: %v", fn, err)
		}
		old := runtime.MemProfileRate
		runtime.MemProfileRate = prof.memProfileRate
		if !prof.quiet {
			log.Printf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn)
		}
		prof.closer = func() {
			pprof.Lookup("heap").WriteTo(f, 0)
			f.Close()
			runtime.MemProfileRate = old
			if !prof.quiet {
				log.Printf("profile: memory profiling disabled, %s", fn)
			}
		}

	case blockMode:
		fn := filepath.Join(path, "block.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create block profile %q: %v", fn, err)
		}
		runtime.SetBlockProfileRate(1)
		if !prof.quiet {
			log.Printf("profile: block profiling enabled, %s", fn)
		}
		prof.closer = func() {
			pprof.Lookup("block").WriteTo(f, 0)
			f.Close()
			runtime.SetBlockProfileRate(0)
			if !prof.quiet {
				log.Printf("profile: block profiling disabled, %s", fn)
			}
		}
	}

	if !prof.noShutdownHook {
		go func() {
			c := make(chan os.Signal, 1)
			signal.Notify(c, os.Interrupt)
			<-c

			log.Println("profile: caught interrupt, stopping profiles")
			prof.Stop()

			os.Exit(0)
		}()
	}

	return &prof
}
<commit_msg>Various cleanups (#21)<commit_after>// Package profile provides a simple way to manage runtime/pprof
// profiling of your Go application.
package profile

import (
	"io/ioutil"
	"log"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"runtime/pprof"
	"sync/atomic"
)

const (
	cpuMode = iota
	memMode
	blockMode
)

type profile struct {
	// quiet suppresses informational messages during profiling.
	quiet bool

	// noShutdownHook controls whether the profiling package should
	// hook SIGINT to write profiles cleanly.
	noShutdownHook bool

	// mode holds the type of profiling that will be made
	mode int

	// path holds the base path where various profiling files are  written.
	// If blank, the base path will be generated by ioutil.TempDir.
	path string

	// memProfileRate holds the rate for the memory profile.
	memProfileRate int

	// closer holds a cleanup function that run after each profile
	closer func()

	// stopped records if a call to profile.Stop has been made
	stopped uint32
}

// NoShutdownHook controls whether the profiling package should
// hook SIGINT to write profiles cleanly.
// Programs with more sophisticated signal handling should set
// this to true and ensure the Stop() function returned from Start()
// is called during shutdown.
func NoShutdownHook(p *profile) { p.noShutdownHook = true }

// Quiet suppresses informational messages during profiling.
func Quiet(p *profile) { p.quiet = true }

// CPUProfile controls if cpu profiling will be enabled. It disables any previous profiling settings.
func CPUProfile(p *profile) { p.mode = cpuMode }

// DefaultMemProfileRate is the default memory profiling rate.
// See also http://golang.org/pkg/runtime/#pkg-variables
const DefaultMemProfileRate = 4096

// MemProfile controls if memory profiling will be enabled. It disables any previous profiling settings.
func MemProfile(p *profile) {
	p.memProfileRate = DefaultMemProfileRate
	p.mode = memMode
}

// MemProfileRate controls if memory profiling will be enabled. Additionally, it takes a parameter which
// allows the setting of the memory profile rate.
func MemProfileRate(rate int) func(*profile) {
	return func(p *profile) {
		p.memProfileRate = rate
		p.mode = memMode
	}
}

// BlockProfile controls if block (contention) profiling will be enabled. It disables any previous profiling settings.
func BlockProfile(p *profile) { p.mode = blockMode }

// ProfilePath controls the base path where various profiling
// files are written. If blank, the base path will be generated
// by ioutil.TempDir.
func ProfilePath(path string) func(*profile) {
	return func(p *profile) {
		p.path = path
	}
}

// Stop stops the profile and flushes any unwritten data.
func (p *profile) Stop() {
	if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) {
		// someone has already called close
		return
	}
	p.closer()
	atomic.StoreUint32(&started, 0)
}

// started is non zero if a profile is running.
var started uint32

// Start starts a new profiling session.
// The caller should call the Stop method on the value returned
// to cleanly stop profiling.
func Start(options ...func(*profile)) interface {
	Stop()
} {
	if !atomic.CompareAndSwapUint32(&started, 0, 1) {
		log.Fatal("profile: Start() already called")
	}

	var prof profile
	for _, option := range options {
		option(&prof)
	}

	path, err := func() (string, error) {
		if p := prof.path; p != "" {
			return p, os.MkdirAll(p, 0777)
		}
		return ioutil.TempDir("", "profile")
	}()

	if err != nil {
		log.Fatalf("profile: could not create initial output directory: %v", err)
	}

	logf := func(format string, args ...interface{}) {
		if !prof.quiet {
			log.Printf(format, args...)
		}
	}

	switch prof.mode {
	case cpuMode:
		fn := filepath.Join(path, "cpu.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create cpu profile %q: %v", fn, err)
		}
		logf("profile: cpu profiling enabled, %s", fn)
		pprof.StartCPUProfile(f)
		prof.closer = func() {
			pprof.StopCPUProfile()
			f.Close()
			logf("profile: cpu profiling disabled, %s", fn)
		}

	case memMode:
		fn := filepath.Join(path, "mem.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create memory profile %q: %v", fn, err)
		}
		old := runtime.MemProfileRate
		runtime.MemProfileRate = prof.memProfileRate
		logf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn)
		prof.closer = func() {
			pprof.Lookup("heap").WriteTo(f, 0)
			f.Close()
			runtime.MemProfileRate = old
			logf("profile: memory profiling disabled, %s", fn)
		}

	case blockMode:
		fn := filepath.Join(path, "block.pprof")
		f, err := os.Create(fn)
		if err != nil {
			log.Fatalf("profile: could not create block profile %q: %v", fn, err)
		}
		runtime.SetBlockProfileRate(1)
		logf("profile: block profiling enabled, %s", fn)
		prof.closer = func() {
			pprof.Lookup("block").WriteTo(f, 0)
			f.Close()
			runtime.SetBlockProfileRate(0)
			logf("profile: block profiling disabled, %s", fn)
		}
	}

	if !prof.noShutdownHook {
		go func() {
			c := make(chan os.Signal, 1)
			signal.Notify(c, os.Interrupt)
			<-c

			log.Println("profile: caught interrupt, stopping profiles")
			prof.Stop()

			os.Exit(0)
		}()
	}

	return &prof
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"os"

	flags "github.com/jessevdk/go-flags"
)

func main() {
	parser := flags.NewParser(nil, flags.Default)
	parser.Usage = "HOSTNAME [OPTIONS]"
	args, _ := parser.Parse()

	if len(args) == 0 {
		os.Exit(1)
	}

	hostname := args[0]
	addr, _ := net.LookupHost(hostname)
	for _, v := range addr {
		ptrAddr, _ := net.LookupAddr(v)
		fmt.Println(v, "->", ptrAddr[0])
	}
}
<commit_msg>Add -v option<commit_after>package main

import (
	"fmt"
	"net"
	"os"

	flags "github.com/jessevdk/go-flags"
)

const version = "1.0"

var opts struct {
	Version bool `short:"v" long:"version" description:"Show version"`
}

func main() {
	parser := flags.NewParser(&opts, flags.Default)
	parser.Usage = "HOSTNAME [OPTIONS]"
	args, _ := parser.Parse()

	if len(args) == 0 {
		if opts.Version {
			fmt.Println("ptrhost version", version)
		}
		os.Exit(1)
	}

	hostname := args[0]
	addr, _ := net.LookupHost(hostname)
	for _, v := range addr {
		ptrAddr, _ := net.LookupAddr(v)
		fmt.Println(v, "->", ptrAddr[0])
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 Yutaka Nishimura. All rights reserved.
// Use of this source code is governed by a Apache License 2.0
// license that can be found in the LICENSE file.

package publish

import (
	"context"
	"io"
	"log"
	"os"
	"time"
)

type Publisher interface {
	Publish(context.Context, io.Reader) error
}

var (
	reader  io.Reader
	timeout time.Duration
	logger  *log.Logger
)

func init() {
	SetLogger(log.New(os.Stdout, "publish: ", log.LstdFlags|log.Lshortfile))
}

func Publish(publishers []Publisher) error {

	errChan := make(chan error, 1)

	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	for _, publisher := range publishers {
		go func() {
			errChan <- publisher.Publish(ctx, reader)
		}()
	}

	select {
	case <-ctx.Done():
		return ctx.Err()
	case err := <-errChan:
		return err
	}
	return nil
}

func SetReader(r io.Reader) {
	reader = r
}

func SetTimeout(t time.Duration) {
	timeout = t
}

func SetLogger(l *log.Logger) {
	logger = l
}
<commit_msg>update publish context<commit_after>// Copyright 2017 Yutaka Nishimura. All rights reserved.
// Use of this source code is governed by a Apache License 2.0
// license that can be found in the LICENSE file.

package publish

import (
	"context"
	"io"
	"log"
	"os"
	"time"
)

type Publisher interface {
	Publish(context.Context, io.Reader) error
}

var (
	reader  io.Reader
	timeout time.Duration
	logger  *log.Logger
)

func init() {
	SetLogger(log.New(os.Stdout, "publish: ", log.LstdFlags|log.Lshortfile))
}

func Publish(publishers []Publisher) error {

	errChan := make(chan error, 1)

	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	for _, publisher := range publishers {
		go func() {
			errChan <- publisher.Publish(ctx, reader)
		}()
		time.Sleep(1 * time.Second)
	}

	select {
	case <-ctx.Done():
		return ctx.Err()
	case err := <-errChan:
		return err
	}
	return nil
}

func SetReader(r io.Reader) {
	reader = r
}

func SetTimeout(t time.Duration) {
	timeout = t
}

func SetLogger(l *log.Logger) {
	logger = l
}
<|endoftext|>


<commit_before>// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

// +build darwin,!kqueue

package notify

/*
#include <CoreServices/CoreServices.h>

typedef void (*CFRunLoopPerformCallBack)(void*);

void gosource(void *);
void gostream(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t);

static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) {
	context->info = (void*) info;
	return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags);
}

#cgo LDFLAGS: -framework CoreServices
*/
import "C"

import (
	"errors"
	"os"
	"sync"
	"sync/atomic"
	"time"
	"unsafe"
)

var nilstream C.FSEventStreamRef

// Default arguments for FSEventStreamCreate function.
var (
	latency C.CFTimeInterval
	flags   = C.FSEventStreamCreateFlags(C.kFSEventStreamCreateFlagFileEvents | C.kFSEventStreamCreateFlagNoDefer)
	since   = uint64(C.FSEventsGetCurrentEventId())
)

var runloop C.CFRunLoopRef // global runloop which all streams are registered with
var wg sync.WaitGroup      // used to wait until the runloop starts

// source is used for synchronization purposes - it signals when runloop has
// started and is ready via the wg. It also serves purpose of a dummy source,
// thanks to it the runloop does not return as it also has at least one source
// registered.
var source = C.CFRunLoopSourceCreate(refZero, 0, &C.CFRunLoopSourceContext{
	perform: (C.CFRunLoopPerformCallBack)(C.gosource),
})

// Errors returned when FSEvents functions fail.
var (
	errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL"))
	errStart  = os.NewSyscallError("FSEventStreamStart", errors.New("false"))
)

// initializes the global runloop and ensures any created stream awaits its
// readiness.
func init() {
	wg.Add(1)
	go func() {
		runloop = C.CFRunLoopGetCurrent()
		C.CFRunLoopAddSource(runloop, source, C.kCFRunLoopDefaultMode)
		C.CFRunLoopRun()
		panic("runloop has just unexpectedly stopped")
	}()
	C.CFRunLoopSourceSignal(source)
}

//export gosource
func gosource(unsafe.Pointer) {
	time.Sleep(time.Second)
	wg.Done()
}

//export gostream
func gostream(_, info uintptr, n C.size_t, paths, flags, ids uintptr) {
	const (
		offchar = unsafe.Sizeof((*C.char)(nil))
		offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0))
		offid   = unsafe.Sizeof(C.FSEventStreamEventId(0))
	)
	if n == 0 {
		return
	}
	ev := make([]FSEvent, 0, int(n))
	for i := uintptr(0); i < uintptr(n); i++ {
		switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); {
		case flags&uint32(FSEventsEventIdsWrapped) != 0:
			atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId()))
		default:
			ev = append(ev, FSEvent{
				Path:  C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))),
				Flags: flags,
				ID:    *(*uint64)(unsafe.Pointer(ids + i*offid)),
			})
		}

	}
	streamFuncs.get(info)(ev)
}

// StreamFunc is a callback called when stream receives file events.
type streamFunc func([]FSEvent)

var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}}

type streamFuncRegistry struct {
	mu sync.Mutex
	m  map[uintptr]streamFunc
	i  uintptr
}

func (r *streamFuncRegistry) get(id uintptr) streamFunc {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.m[id]
}

func (r *streamFuncRegistry) add(fn streamFunc) uintptr {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.i++
	r.m[r.i] = fn
	return r.i
}

func (r *streamFuncRegistry) delete(id uintptr) {
	r.mu.Lock()
	defer r.mu.Unlock()
	delete(r.m, id)
}

// Stream represents single watch-point which listens for events scheduled by
// the global runloop.
type stream struct {
	path string
	ref  C.FSEventStreamRef
	info uintptr
}

// NewStream creates a stream for given path, listening for file events and
// calling fn upon receiving any.
func newStream(path string, fn streamFunc) *stream {
	return &stream{
		path: path,
		info: streamFuncs.add(fn),
	}
}

// Start creates a FSEventStream for the given path and schedules it with
// global runloop. It's a nop if the stream was already started.
func (s *stream) Start() error {
	if s.ref != nilstream {
		return nil
	}
	wg.Wait()
	p := C.CFStringCreateWithCStringNoCopy(refZero, C.CString(s.path), C.kCFStringEncodingUTF8, refZero)
	path := C.CFArrayCreate(refZero, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil)
	ctx := C.FSEventStreamContext{}
	ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags)
	if ref == nilstream {
		return errCreate
	}
	C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode)
	if C.FSEventStreamStart(ref) == C.Boolean(0) {
		C.FSEventStreamInvalidate(ref)
		return errStart
	}
	C.CFRunLoopWakeUp(runloop)
	s.ref = ref
	return nil
}

// Stop stops underlying FSEventStream and unregisters it from global runloop.
func (s *stream) Stop() {
	if s.ref == nilstream {
		return
	}
	wg.Wait()
	C.FSEventStreamStop(s.ref)
	C.FSEventStreamInvalidate(s.ref)
	C.CFRunLoopWakeUp(runloop)
	s.ref = nilstream
	streamFuncs.delete(s.info)
}
<commit_msg>FSEvents: lock the CF run loop goroutine to its thread<commit_after>// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

// +build darwin,!kqueue

package notify

/*
#include <CoreServices/CoreServices.h>

typedef void (*CFRunLoopPerformCallBack)(void*);

void gosource(void *);
void gostream(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t);

static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) {
	context->info = (void*) info;
	return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags);
}

#cgo LDFLAGS: -framework CoreServices
*/
import "C"

import (
	"errors"
	"os"
	"runtime"
	"sync"
	"sync/atomic"
	"time"
	"unsafe"
)

var nilstream C.FSEventStreamRef

// Default arguments for FSEventStreamCreate function.
var (
	latency C.CFTimeInterval
	flags   = C.FSEventStreamCreateFlags(C.kFSEventStreamCreateFlagFileEvents | C.kFSEventStreamCreateFlagNoDefer)
	since   = uint64(C.FSEventsGetCurrentEventId())
)

var runloop C.CFRunLoopRef // global runloop which all streams are registered with
var wg sync.WaitGroup      // used to wait until the runloop starts

// source is used for synchronization purposes - it signals when runloop has
// started and is ready via the wg. It also serves purpose of a dummy source,
// thanks to it the runloop does not return as it also has at least one source
// registered.
var source = C.CFRunLoopSourceCreate(refZero, 0, &C.CFRunLoopSourceContext{
	perform: (C.CFRunLoopPerformCallBack)(C.gosource),
})

// Errors returned when FSEvents functions fail.
var (
	errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL"))
	errStart  = os.NewSyscallError("FSEventStreamStart", errors.New("false"))
)

// initializes the global runloop and ensures any created stream awaits its
// readiness.
func init() {
	wg.Add(1)
	go func() {
		// There is exactly one run loop per thread. Lock this goroutine to its
		// thread to ensure that it's not rescheduled on a different thread while
		// setting up the run loop.
		runtime.LockOSThread()
		runloop = C.CFRunLoopGetCurrent()
		C.CFRunLoopAddSource(runloop, source, C.kCFRunLoopDefaultMode)
		C.CFRunLoopRun()
		panic("runloop has just unexpectedly stopped")
	}()
	C.CFRunLoopSourceSignal(source)
}

//export gosource
func gosource(unsafe.Pointer) {
	time.Sleep(time.Second)
	wg.Done()
}

//export gostream
func gostream(_, info uintptr, n C.size_t, paths, flags, ids uintptr) {
	const (
		offchar = unsafe.Sizeof((*C.char)(nil))
		offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0))
		offid   = unsafe.Sizeof(C.FSEventStreamEventId(0))
	)
	if n == 0 {
		return
	}
	ev := make([]FSEvent, 0, int(n))
	for i := uintptr(0); i < uintptr(n); i++ {
		switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); {
		case flags&uint32(FSEventsEventIdsWrapped) != 0:
			atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId()))
		default:
			ev = append(ev, FSEvent{
				Path:  C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))),
				Flags: flags,
				ID:    *(*uint64)(unsafe.Pointer(ids + i*offid)),
			})
		}

	}
	streamFuncs.get(info)(ev)
}

// StreamFunc is a callback called when stream receives file events.
type streamFunc func([]FSEvent)

var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}}

type streamFuncRegistry struct {
	mu sync.Mutex
	m  map[uintptr]streamFunc
	i  uintptr
}

func (r *streamFuncRegistry) get(id uintptr) streamFunc {
	r.mu.Lock()
	defer r.mu.Unlock()
	return r.m[id]
}

func (r *streamFuncRegistry) add(fn streamFunc) uintptr {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.i++
	r.m[r.i] = fn
	return r.i
}

func (r *streamFuncRegistry) delete(id uintptr) {
	r.mu.Lock()
	defer r.mu.Unlock()
	delete(r.m, id)
}

// Stream represents single watch-point which listens for events scheduled by
// the global runloop.
type stream struct {
	path string
	ref  C.FSEventStreamRef
	info uintptr
}

// NewStream creates a stream for given path, listening for file events and
// calling fn upon receiving any.
func newStream(path string, fn streamFunc) *stream {
	return &stream{
		path: path,
		info: streamFuncs.add(fn),
	}
}

// Start creates a FSEventStream for the given path and schedules it with
// global runloop. It's a nop if the stream was already started.
func (s *stream) Start() error {
	if s.ref != nilstream {
		return nil
	}
	wg.Wait()
	p := C.CFStringCreateWithCStringNoCopy(refZero, C.CString(s.path), C.kCFStringEncodingUTF8, refZero)
	path := C.CFArrayCreate(refZero, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil)
	ctx := C.FSEventStreamContext{}
	ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags)
	if ref == nilstream {
		return errCreate
	}
	C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode)
	if C.FSEventStreamStart(ref) == C.Boolean(0) {
		C.FSEventStreamInvalidate(ref)
		return errStart
	}
	C.CFRunLoopWakeUp(runloop)
	s.ref = ref
	return nil
}

// Stop stops underlying FSEventStream and unregisters it from global runloop.
func (s *stream) Stop() {
	if s.ref == nilstream {
		return
	}
	wg.Wait()
	C.FSEventStreamStop(s.ref)
	C.FSEventStreamInvalidate(s.ref)
	C.CFRunLoopWakeUp(runloop)
	s.ref = nilstream
	streamFuncs.delete(s.info)
}
<|endoftext|>