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

package tp

import (
	"context"
	"crypto/tls"
	"fmt"
	"net"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/henrylee2cn/goutil"
	"github.com/henrylee2cn/goutil/coarsetime"
	"github.com/henrylee2cn/goutil/errors"
	"github.com/henrylee2cn/teleport/codec"
	"github.com/henrylee2cn/teleport/socket"
)

// Peer peer which is server or client.
type Peer struct {
	PullRouter          *Router
	PushRouter          *Router
	pluginContainer     PluginContainer
	sessHub             *SessionHub
	closeCh             chan struct{}
	freeContext         *readHandleCtx
	ctxLock             sync.Mutex
	defaultReadTimeout  time.Duration // readdeadline for underlying net.Conn
	defaultWriteTimeout time.Duration // writedeadline for underlying net.Conn
	tlsConfig           *tls.Config
	slowCometDuration   time.Duration
	defaultBodyCodec    byte
	printBody           bool
	countTime           bool
	timeNow             func() time.Time
	timeSince           func(time.Time) time.Duration
	mu                  sync.Mutex

	network string

	// only for client role
	defaultDialTimeout time.Duration
	redialTimes        int32

	// only for server role
	listenAddr string
	listen     net.Listener
}

// NewPeer creates a new peer.
func NewPeer(cfg PeerConfig, plugin ...Plugin) *Peer {
	err := cfg.check()
	if err != nil {
		Fatalf("%v", err)
	}
	var pluginContainer = newPluginContainer()
	if err = pluginContainer.Add(plugin...); err != nil {
		Fatalf("%v", err)
	}
	var p = &Peer{
		PullRouter:          newPullRouter(pluginContainer),
		PushRouter:          newPushRouter(pluginContainer),
		pluginContainer:     pluginContainer,
		sessHub:             newSessionHub(),
		defaultReadTimeout:  cfg.DefaultReadTimeout,
		defaultWriteTimeout: cfg.DefaultWriteTimeout,
		closeCh:             make(chan struct{}),
		slowCometDuration:   cfg.slowCometDuration,
		defaultDialTimeout:  cfg.DefaultDialTimeout,
		network:             cfg.Network,
		listenAddr:          cfg.ListenAddress,
		printBody:           cfg.PrintBody,
		countTime:           cfg.CountTime,
		redialTimes:         cfg.RedialTimes,
	}
	if c, err := codec.GetByName(cfg.DefaultBodyCodec); err != nil {
		Fatalf("%v", err)
	} else {
		p.defaultBodyCodec = c.Id()
	}
	if p.countTime {
		p.timeNow = time.Now
		p.timeSince = time.Since
	} else {
		t0 := time.Time{}
		p.timeNow = func() time.Time { return t0 }
		p.timeSince = func(time.Time) time.Duration { return 0 }
	}

	addPeer(p)
	return p
}

// GetSession gets the session by id.
func (p *Peer) GetSession(sessionId string) (Session, bool) {
	return p.sessHub.Get(sessionId)
}

// RangeSession ranges all sessions.
// If fn returns false, stop traversing.
func (p *Peer) RangeSession(fn func(sess Session) bool) {
	p.sessHub.sessions.Range(func(key, value interface{}) bool {
		return fn(value.(*session))
	})
}

// CountSession returns the number of sessions.
func (p *Peer) CountSession() int {
	return p.sessHub.sessions.Len()
}

// Dial connects with the peer of the destination address.
func (p *Peer) Dial(addr string, protoFunc ...socket.ProtoFunc) (Session, *Rerror) {
	return p.newSessionForClient(func() (net.Conn, error) {
		return net.DialTimeout(p.network, addr, p.defaultDialTimeout)
	}, addr, protoFunc)
}

// DialContext connects with the peer of the destination address,
// using the provided context.
func (p *Peer) DialContext(ctx context.Context, addr string, protoFunc ...socket.ProtoFunc) (Session, *Rerror) {
	return p.newSessionForClient(func() (net.Conn, error) {
		var d net.Dialer
		return d.DialContext(ctx, p.network, addr)
	}, addr, protoFunc)
}

func (p *Peer) newSessionForClient(dialFunc func() (net.Conn, error), addr string, protoFuncs []socket.ProtoFunc) (*session, *Rerror) {
	var conn, dialErr = dialFunc()
	if dialErr != nil {
		rerr := rerror_dialFailed.Copy()
		rerr.Detail = dialErr.Error()
		return nil, rerr
	}
	if p.tlsConfig != nil {
		conn = tls.Client(conn, p.tlsConfig)
	}
	var sess = newSession(p, conn, protoFuncs)
	if p.redialTimes > 0 {
		sess.redialForClientFunc = func() bool {
			var err error
			for i := p.redialTimes; i > 0; i-- {
				err = p.renewSessionForClient(sess, dialFunc, addr, protoFuncs)
				if err == nil {
					return true
				}
			}
			if err != nil {
				Errorf("redial fail (network:%s, addr:%s, id:%s): %s", p.network, addr, sess.Id(), err.Error())
			}
			return false
		}
	}
	sess.socket.SetId(sess.LocalIp())
	if rerr := p.pluginContainer.PostDial(sess); rerr != nil {
		sess.Close()
		return nil, rerr
	}
	AnywayGo(sess.startReadAndHandle)
	p.sessHub.Set(sess)
	Infof("dial ok (network:%s, addr:%s, id:%s)", p.network, addr, sess.Id())
	return sess, nil
}

func (p *Peer) renewSessionForClient(sess *session, dialFunc func() (net.Conn, error), addr string, protoFuncs []socket.ProtoFunc) error {
	var conn, dialErr = dialFunc()
	if dialErr != nil {
		return dialErr
	}
	if p.tlsConfig != nil {
		conn = tls.Client(conn, p.tlsConfig)
	}
	oldIp := sess.LocalIp()
	oldId := sess.Id()
	sess.socket.Reset(conn, protoFuncs...)
	if oldIp == oldId {
		sess.socket.SetId(sess.LocalIp())
	} else {
		sess.socket.SetId(oldId)
	}
	if rerr := p.pluginContainer.PostDial(sess); rerr != nil {
		sess.Close()
		return rerr.ToError()
	}
	atomic.StoreInt32(&sess.status, statusOk)
	AnywayGo(sess.startReadAndHandle)
	p.sessHub.Set(sess)
	Infof("redial ok (network:%s, addr:%s, id:%s)", p.network, addr, sess.Id())
	return nil
}

// ErrListenClosed listener is closed error.
var ErrListenClosed = errors.New("listener is closed")

// Listen turns on the listening service.
func (p *Peer) Listen(protoFunc ...socket.ProtoFunc) error {
	lis, err := listen(p.network, p.listenAddr, p.tlsConfig)
	if err != nil {
		Fatalf("%v", err)
	}
	defer lis.Close()
	p.listen = lis

	network := lis.Addr().Network()
	addr := lis.Addr().String()
	Printf("listen ok (network:%s, addr:%s)", network, addr)

	var (
		tempDelay time.Duration // how long to sleep on accept failure
		closeCh   = p.closeCh
	)
	for {
		conn, e := lis.Accept()
		if e != nil {
			select {
			case <-closeCh:
				return ErrListenClosed
			default:
			}
			if ne, ok := e.(net.Error); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}

				Tracef("accept error: %s; retrying in %v", e.Error(), tempDelay)

				time.Sleep(tempDelay)
				continue
			}
			return e
		}
		tempDelay = 0
		AnywayGo(func() {
			if c, ok := conn.(*tls.Conn); ok {
				if p.defaultReadTimeout > 0 {
					c.SetReadDeadline(coarsetime.CeilingTimeNow().Add(p.defaultReadTimeout))
				}
				if p.defaultWriteTimeout > 0 {
					c.SetReadDeadline(coarsetime.CeilingTimeNow().Add(p.defaultWriteTimeout))
				}
				if err := c.Handshake(); err != nil {
					Errorf("TLS handshake error from %s: %s", c.RemoteAddr(), err.Error())
					return
				}
			}
			var sess = newSession(p, conn, protoFunc)
			if rerr := p.pluginContainer.PostAccept(sess); rerr != nil {
				sess.Close()
				return
			}
			Tracef("accept session(network:%s, addr:%s) ok", network, sess.RemoteIp(), sess.Id())
			p.sessHub.Set(sess)
			sess.startReadAndHandle()
		})
	}
}

// ServeConn serves the connection and returns a session.
func (p *Peer) ServeConn(conn net.Conn, protoFunc ...socket.ProtoFunc) (Session, error) {
	network := conn.LocalAddr().Network()
	if strings.Contains(network, "udp") {
		return nil, fmt.Errorf("Invalid network: %s,\nrefer to the following: tcp, tcp4, tcp6, unix or unixpacket", network)
	}
	if c, ok := conn.(*tls.Conn); ok {
		if p.defaultReadTimeout > 0 {
			c.SetReadDeadline(coarsetime.CeilingTimeNow().Add(p.defaultReadTimeout))
		}
		if p.defaultWriteTimeout > 0 {
			c.SetReadDeadline(coarsetime.CeilingTimeNow().Add(p.defaultWriteTimeout))
		}
		if err := c.Handshake(); err != nil {
			return nil, errors.Errorf("TLS handshake error from %s: %s", c.RemoteAddr(), err.Error())
		}
	}
	var sess = newSession(p, conn, protoFunc)
	if rerr := p.pluginContainer.PostAccept(sess); rerr != nil {
		sess.Close()
		return nil, rerr.ToError()
	}
	Tracef("accept session(network:%s, addr:%s) ok", network, sess.RemoteIp(), sess.Id())
	p.sessHub.Set(sess)
	AnywayGo(sess.startReadAndHandle)
	return sess, nil
}

// Close closes peer.
func (p *Peer) Close() (err error) {
	defer func() {
		if p := recover(); p != nil {
			err = errors.Errorf("panic:\n%v\n%s", p, goutil.PanicTrace(2))
		}
	}()
	close(p.closeCh)
	delete(peers.list, p)
	var (
		count int
		errCh = make(chan error, 10)
	)
	p.sessHub.Range(func(sess *session) bool {
		count++
		if !Go(func() {
			errCh <- sess.Close()
		}) {
			errCh <- sess.Close()
		}
		return true
	})
	for i := 0; i < count; i++ {
		err = errors.Merge(err, <-errCh)
	}
	close(errCh)
	return err
}

func (p *Peer) getContext(s *session, withWg bool) *readHandleCtx {
	p.ctxLock.Lock()
	if withWg {
		// count get context
		s.graceCtxWaitGroup.Add(1)
	}
	ctx := p.freeContext
	if ctx == nil {
		ctx = newReadHandleCtx()
	} else {
		p.freeContext = ctx.next
	}
	ctx.reInit(s)
	p.ctxLock.Unlock()
	return ctx
}

func (p *Peer) putContext(ctx *readHandleCtx, withWg bool) {
	p.ctxLock.Lock()
	defer p.ctxLock.Unlock()
	if withWg {
		// count get context
		ctx.sess.graceCtxWaitGroup.Done()
	}
	ctx.clean()
	ctx.next = p.freeContext
	p.freeContext = ctx
}
<commit_msg>Optimize error text<commit_after>// Copyright 2015-2017 HenryLee. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tp

import (
	"context"
	"crypto/tls"
	"fmt"
	"net"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/henrylee2cn/goutil"
	"github.com/henrylee2cn/goutil/coarsetime"
	"github.com/henrylee2cn/goutil/errors"
	"github.com/henrylee2cn/teleport/codec"
	"github.com/henrylee2cn/teleport/socket"
)

// Peer peer which is server or client.
type Peer struct {
	PullRouter          *Router
	PushRouter          *Router
	pluginContainer     PluginContainer
	sessHub             *SessionHub
	closeCh             chan struct{}
	freeContext         *readHandleCtx
	ctxLock             sync.Mutex
	defaultReadTimeout  time.Duration // readdeadline for underlying net.Conn
	defaultWriteTimeout time.Duration // writedeadline for underlying net.Conn
	tlsConfig           *tls.Config
	slowCometDuration   time.Duration
	defaultBodyCodec    byte
	printBody           bool
	countTime           bool
	timeNow             func() time.Time
	timeSince           func(time.Time) time.Duration
	mu                  sync.Mutex

	network string

	// only for client role
	defaultDialTimeout time.Duration
	redialTimes        int32

	// only for server role
	listenAddr string
	listen     net.Listener
}

// NewPeer creates a new peer.
func NewPeer(cfg PeerConfig, plugin ...Plugin) *Peer {
	err := cfg.check()
	if err != nil {
		Fatalf("%v", err)
	}
	var pluginContainer = newPluginContainer()
	if err = pluginContainer.Add(plugin...); err != nil {
		Fatalf("%v", err)
	}
	var p = &Peer{
		PullRouter:          newPullRouter(pluginContainer),
		PushRouter:          newPushRouter(pluginContainer),
		pluginContainer:     pluginContainer,
		sessHub:             newSessionHub(),
		defaultReadTimeout:  cfg.DefaultReadTimeout,
		defaultWriteTimeout: cfg.DefaultWriteTimeout,
		closeCh:             make(chan struct{}),
		slowCometDuration:   cfg.slowCometDuration,
		defaultDialTimeout:  cfg.DefaultDialTimeout,
		network:             cfg.Network,
		listenAddr:          cfg.ListenAddress,
		printBody:           cfg.PrintBody,
		countTime:           cfg.CountTime,
		redialTimes:         cfg.RedialTimes,
	}
	if c, err := codec.GetByName(cfg.DefaultBodyCodec); err != nil {
		Fatalf("%v", err)
	} else {
		p.defaultBodyCodec = c.Id()
	}
	if p.countTime {
		p.timeNow = time.Now
		p.timeSince = time.Since
	} else {
		t0 := time.Time{}
		p.timeNow = func() time.Time { return t0 }
		p.timeSince = func(time.Time) time.Duration { return 0 }
	}

	addPeer(p)
	return p
}

// GetSession gets the session by id.
func (p *Peer) GetSession(sessionId string) (Session, bool) {
	return p.sessHub.Get(sessionId)
}

// RangeSession ranges all sessions.
// If fn returns false, stop traversing.
func (p *Peer) RangeSession(fn func(sess Session) bool) {
	p.sessHub.sessions.Range(func(key, value interface{}) bool {
		return fn(value.(*session))
	})
}

// CountSession returns the number of sessions.
func (p *Peer) CountSession() int {
	return p.sessHub.sessions.Len()
}

// Dial connects with the peer of the destination address.
func (p *Peer) Dial(addr string, protoFunc ...socket.ProtoFunc) (Session, *Rerror) {
	return p.newSessionForClient(func() (net.Conn, error) {
		return net.DialTimeout(p.network, addr, p.defaultDialTimeout)
	}, addr, protoFunc)
}

// DialContext connects with the peer of the destination address,
// using the provided context.
func (p *Peer) DialContext(ctx context.Context, addr string, protoFunc ...socket.ProtoFunc) (Session, *Rerror) {
	return p.newSessionForClient(func() (net.Conn, error) {
		var d net.Dialer
		return d.DialContext(ctx, p.network, addr)
	}, addr, protoFunc)
}

func (p *Peer) newSessionForClient(dialFunc func() (net.Conn, error), addr string, protoFuncs []socket.ProtoFunc) (*session, *Rerror) {
	var conn, dialErr = dialFunc()
	if dialErr != nil {
		rerr := rerror_dialFailed.Copy()
		rerr.Detail = dialErr.Error()
		return nil, rerr
	}
	if p.tlsConfig != nil {
		conn = tls.Client(conn, p.tlsConfig)
	}
	var sess = newSession(p, conn, protoFuncs)
	if p.redialTimes > 0 {
		sess.redialForClientFunc = func() bool {
			var err error
			for i := p.redialTimes; i > 0; i-- {
				err = p.renewSessionForClient(sess, dialFunc, addr, protoFuncs)
				if err == nil {
					return true
				}
			}
			if err != nil {
				Errorf("redial fail (network:%s, addr:%s, id:%s): %s", p.network, addr, sess.Id(), err.Error())
			}
			return false
		}
	}
	sess.socket.SetId(sess.LocalIp())
	if rerr := p.pluginContainer.PostDial(sess); rerr != nil {
		sess.Close()
		return nil, rerr
	}
	AnywayGo(sess.startReadAndHandle)
	p.sessHub.Set(sess)
	Infof("dial ok (network:%s, addr:%s, id:%s)", p.network, addr, sess.Id())
	return sess, nil
}

func (p *Peer) renewSessionForClient(sess *session, dialFunc func() (net.Conn, error), addr string, protoFuncs []socket.ProtoFunc) error {
	var conn, dialErr = dialFunc()
	if dialErr != nil {
		return dialErr
	}
	if p.tlsConfig != nil {
		conn = tls.Client(conn, p.tlsConfig)
	}
	oldIp := sess.LocalIp()
	oldId := sess.Id()
	sess.socket.Reset(conn, protoFuncs...)
	if oldIp == oldId {
		sess.socket.SetId(sess.LocalIp())
	} else {
		sess.socket.SetId(oldId)
	}
	if rerr := p.pluginContainer.PostDial(sess); rerr != nil {
		sess.Close()
		return rerr.ToError()
	}
	atomic.StoreInt32(&sess.status, statusOk)
	AnywayGo(sess.startReadAndHandle)
	p.sessHub.Set(sess)
	Infof("redial ok (network:%s, addr:%s, id:%s)", p.network, addr, sess.Id())
	return nil
}

// ErrListenClosed listener is closed error.
var ErrListenClosed = errors.New("listener is closed")

// Listen turns on the listening service.
func (p *Peer) Listen(protoFunc ...socket.ProtoFunc) error {
	lis, err := listen(p.network, p.listenAddr, p.tlsConfig)
	if err != nil {
		Fatalf("%v", err)
	}
	defer lis.Close()
	p.listen = lis

	network := lis.Addr().Network()
	addr := lis.Addr().String()
	Printf("listen ok (network:%s, addr:%s)", network, addr)

	var (
		tempDelay time.Duration // how long to sleep on accept failure
		closeCh   = p.closeCh
	)
	for {
		conn, e := lis.Accept()
		if e != nil {
			select {
			case <-closeCh:
				return ErrListenClosed
			default:
			}
			if ne, ok := e.(net.Error); ok && ne.Temporary() {
				if tempDelay == 0 {
					tempDelay = 5 * time.Millisecond
				} else {
					tempDelay *= 2
				}
				if max := 1 * time.Second; tempDelay > max {
					tempDelay = max
				}

				Tracef("accept error: %s; retrying in %v", e.Error(), tempDelay)

				time.Sleep(tempDelay)
				continue
			}
			return e
		}
		tempDelay = 0
		AnywayGo(func() {
			if c, ok := conn.(*tls.Conn); ok {
				if p.defaultReadTimeout > 0 {
					c.SetReadDeadline(coarsetime.CeilingTimeNow().Add(p.defaultReadTimeout))
				}
				if p.defaultWriteTimeout > 0 {
					c.SetReadDeadline(coarsetime.CeilingTimeNow().Add(p.defaultWriteTimeout))
				}
				if err := c.Handshake(); err != nil {
					Errorf("TLS handshake error from %s: %s", c.RemoteAddr(), err.Error())
					return
				}
			}
			var sess = newSession(p, conn, protoFunc)
			if rerr := p.pluginContainer.PostAccept(sess); rerr != nil {
				sess.Close()
				return
			}
			Tracef("accept session(network:%s, addr:%s) ok", network, sess.RemoteIp(), sess.Id())
			p.sessHub.Set(sess)
			sess.startReadAndHandle()
		})
	}
}

// ServeConn serves the connection and returns a session.
func (p *Peer) ServeConn(conn net.Conn, protoFunc ...socket.ProtoFunc) (Session, error) {
	network := conn.LocalAddr().Network()
	if strings.Contains(network, "udp") {
		return nil, fmt.Errorf("invalid network: %s,\nrefer to the following: tcp, tcp4, tcp6, unix or unixpacket", network)
	}
	if c, ok := conn.(*tls.Conn); ok {
		if p.defaultReadTimeout > 0 {
			c.SetReadDeadline(coarsetime.CeilingTimeNow().Add(p.defaultReadTimeout))
		}
		if p.defaultWriteTimeout > 0 {
			c.SetReadDeadline(coarsetime.CeilingTimeNow().Add(p.defaultWriteTimeout))
		}
		if err := c.Handshake(); err != nil {
			return nil, errors.Errorf("TLS handshake error from %s: %s", c.RemoteAddr(), err.Error())
		}
	}
	var sess = newSession(p, conn, protoFunc)
	if rerr := p.pluginContainer.PostAccept(sess); rerr != nil {
		sess.Close()
		return nil, rerr.ToError()
	}
	Tracef("accept session(network:%s, addr:%s) ok", network, sess.RemoteIp(), sess.Id())
	p.sessHub.Set(sess)
	AnywayGo(sess.startReadAndHandle)
	return sess, nil
}

// Close closes peer.
func (p *Peer) Close() (err error) {
	defer func() {
		if p := recover(); p != nil {
			err = errors.Errorf("panic:\n%v\n%s", p, goutil.PanicTrace(2))
		}
	}()
	close(p.closeCh)
	delete(peers.list, p)
	var (
		count int
		errCh = make(chan error, 10)
	)
	p.sessHub.Range(func(sess *session) bool {
		count++
		if !Go(func() {
			errCh <- sess.Close()
		}) {
			errCh <- sess.Close()
		}
		return true
	})
	for i := 0; i < count; i++ {
		err = errors.Merge(err, <-errCh)
	}
	close(errCh)
	return err
}

func (p *Peer) getContext(s *session, withWg bool) *readHandleCtx {
	p.ctxLock.Lock()
	if withWg {
		// count get context
		s.graceCtxWaitGroup.Add(1)
	}
	ctx := p.freeContext
	if ctx == nil {
		ctx = newReadHandleCtx()
	} else {
		p.freeContext = ctx.next
	}
	ctx.reInit(s)
	p.ctxLock.Unlock()
	return ctx
}

func (p *Peer) putContext(ctx *readHandleCtx, withWg bool) {
	p.ctxLock.Lock()
	defer p.ctxLock.Unlock()
	if withWg {
		// count get context
		ctx.sess.graceCtxWaitGroup.Done()
	}
	ctx.clean()
	ctx.next = p.freeContext
	p.freeContext = ctx
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/go-fsnotify/fsnotify"
	"github.com/shiwork/fsmonitor"
	"github.com/shiwork/pepe/config"
	"github.com/shiwork/slack/incoming"
	"log"
	"os"
	"path"
	"strings"
)

var confPath = os.Getenv("PEPE_CONFIG")

func main() {
	conf, err := config.Parse(confPath)
	if err != nil {
		log.Fatal(err)
	}

	watcher, err := fsmonitor.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}

	var slackConfig incoming.Config
	slackConfig.WebHookURL = conf.Slack.IncomingWebHook

	// 監視ディレクトリの追加
	for _, value := range conf.Watches {
		log.Println(value.Dir)
		err = watcher.Watch(value.Dir)
		if err != nil {
			log.Fatal(err)
		}
	}

	log.Println("start watch")
	for {
		select {
		case event := <-watcher.Events:
			log.Println("event:", event)

			switch {
			case event.Op&fsnotify.Create == fsnotify.Create:
				var filePath string
				var dirPath string

				for _, value := range conf.Watches {
					if strings.HasPrefix(event.Name, value.Dir) {
						filePath = strings.Replace(event.Name, value.Dir, "", -1)
						dirPath = value.URL
						break
					}
				}

				// create post message
				var _, filename = path.Split(event.Name)

				var message string
				message = "<" + dirPath + filePath + "|" + filename + ">"

				// post message to Slack
				err := incoming.Post(
					slackConfig,
					incoming.Payload{
						[]incoming.Attachment{
							incoming.Attachment{
								message,
								message,
								"",
								[]incoming.Field{
									incoming.Field{
										"",
										"",
										false,
									},
								},
							},
						},
					},
				)
				if err != nil {
					log.Println("error:", err)
				}
			}
		case err := <-watcher.Error:
			log.Println("error:", err)
		}
	}
}
<commit_msg>メッセージの送信時に不要なField送らないように変更<commit_after>package main

import (
    "github.com/go-fsnotify/fsnotify"
    "github.com/shiwork/fsmonitor"
    "github.com/shiwork/pepe/config"
    "github.com/shiwork/slack/incoming"
    "log"
    "os"
    "path"
    "strings"
)

var confPath = os.Getenv("PEPE_CONFIG")

func main() {
    conf, err := config.Parse(confPath)
    if err != nil {
        log.Fatal(err)
    }

    watcher, err := fsmonitor.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }

    var slackConfig incoming.Config
    slackConfig.WebHookURL = conf.Slack.IncomingWebHook

    // 監視ディレクトリの追加
    for _, value := range conf.Watches {
        log.Println(value.Dir)
        err = watcher.Watch(value.Dir)
        if err != nil {
            log.Fatal(err)
        }
    }

    log.Println("start watch")
    for {
        select {
        case event := <-watcher.Events:
            log.Println("event:", event)

            switch {
                case event.Op&fsnotify.Create == fsnotify.Create:
                var filePath string
                var dirPath string

                for _, value := range conf.Watches {
                    if strings.HasPrefix(event.Name, value.Dir) {
                        filePath = strings.Replace(event.Name, value.Dir, "", -1)
                        dirPath = value.URL
                        break
                    }
                }

                // create post message
                var _, filename = path.Split(event.Name)

                var message string
                message = "<" + dirPath + filePath + "|" + filename + ">"

                // post message to Slack
                err := incoming.Post(
                slackConfig,
                incoming.Payload{
                    []incoming.Attachment{
                        incoming.Attachment{
                            Fallback: message,
                            Pretext: message,
                        },
                    },
                },
                )
                if err != nil {
                    log.Println("error:", err)
                }
            }
        case err := <-watcher.Error:
            log.Println("error:", err)
        }
    }
}
<|endoftext|>


<commit_before>package picoweb

import (
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"time"

	"strconv"

	"github.com/julienschmidt/httprouter"
	"github.com/tylerb/graceful"

	"strings"

	"github.com/googollee/go-socket.io"
)

var (
	RequestCount int
	isDev        bool
	flash        Flash
)

type Pico struct {
	mux          *httprouter.Router
	server       *graceful.Server
	c            chan os.Signal
	sio          *socketio.Server
	trackSession bool
	cookieName   string
	pre          PicoHandler
	post         PicoHandler
}

type PicoHandler func(c *Context)

func (p *Pico) Get(pattern string, fn PicoHandler) {
	p.mux.GET(pattern, middle(fn))
}

func (p *Pico) Post(pattern string, fn PicoHandler) {
	p.mux.POST(pattern, middle(fn))
}

func (p *Pico) Options(pattern string, fn PicoHandler) {
	p.mux.OPTIONS(pattern, middle(fn))
}

func (p *Pico) Put(pattern string, fn PicoHandler) {
	p.mux.PUT(pattern, middle(fn))
}

func (p *Pico) Delete(pattern string, fn PicoHandler) {
	p.mux.DELETE(pattern, middle(fn))
}

func (p *Pico) Static(urlPath, diskPath string) {
	if urlPath[len(urlPath)-1] == '/' {
		urlPath = urlPath[:len(urlPath)-1]
	}

	if urlPath[0:1] != "/" {
		urlPath = "/" + urlPath
	}

	p.mux.ServeFiles(urlPath+"/*filepath", http.Dir(diskPath))
}

func (p *Pico) EnableSocketIoOn(url string) {
	var err error
	p.sio, err = socketio.NewServer(nil)
	p.mux.GET(url, middlehttp(p.sio))
	fmt.Println(err)
}

func (p *Pico) OnConnection(fn func(s Socket)) {
	p.sio.On("connection", socket_middle(fn))
}

func (p *Pico) OnError(fn func(s Socket, e error)) {
	p.sio.On("error", socket_middle_error(fn))
}

func (p *Pico) On(event string, fn func(msg string)) {
	p.sio.On(event, fn)
}

func (p *Pico) GetFlash(sessionId string) interface{} {
	return flash.Get(sessionId)
}

func (p *Pico) SetFlash(sessionId string, value interface{}) {
	flash.Set(sessionId, value)
}

// func (p *Pico) TrackSessionUsingCookie(cookieName string) {
// 	p.trackSession = true
// 	p.cookieName = cookieName
// }

func (p *Pico) ListenS(port string) {
	if strings.Index(port, ":") == 0 {
		port = port[1:]
	}

	po, err := strconv.ParseInt(port, 10, 32)
	if err != nil {
		panic("Port Error (:9999 etc)")
	}
	if po < 0 || po > 65000 {
		panic("PORT is OUT of Range")
	}
	p.Listen(int(po))
}

func (p *Pico) Listen(port int) error {
	envPort := os.Getenv("PORT")
	if len(envPort) > 0 {
		pi, err := strconv.Atoi(envPort)
		if err == nil && pi < 65000 {
			port = pi
		}
	}

	p.server = &graceful.Server{
		Timeout: 2 * time.Second,
		Server: &http.Server{
			Addr:    ":" + strconv.Itoa(port),
			Handler: p.mux,
		},
	}
	flash = make(Flash)
	return p.server.ListenAndServe()
}

func (p *Pico) Production() {
	isDev = false
}

func (p *Pico) StopOnInit() {
	p.StopOnIntWithFunc(nil)
}

func (p *Pico) StopOnIntWithFunc(fn func()) {
	p.c = make(chan os.Signal, 1)
	signal.Notify(p.c, os.Interrupt)
	signal.Notify(p.c, os.Kill)

	go func() {
		<-p.c
		if isDev {
			fmt.Println("Shutting Down!")
		}

		p.Stop()

		if isDev {
			fmt.Println("Done!")
		}

		close(p.c)
		if fn != nil {
			fn()
		}

		os.Exit(0)

	}()
}

func (p *Pico) Stop() {
	p.server.Stop(time.Second * 2)
	fmt.Println("Waiting on Stop Channel")
	flash.Clear()
	<-p.server.StopChan()
	fmt.Println("Channel Returned")
}

func New() *Pico {
	isDev = true
	return &Pico{mux: httprouter.New()}
}
<commit_msg>Init to Int<commit_after>package picoweb

import (
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"time"

	"strconv"

	"github.com/julienschmidt/httprouter"
	"github.com/tylerb/graceful"

	"strings"

	"github.com/googollee/go-socket.io"
)

var (
	RequestCount int
	isDev        bool
	flash        Flash
)

type Pico struct {
	mux          *httprouter.Router
	server       *graceful.Server
	c            chan os.Signal
	sio          *socketio.Server
	trackSession bool
	cookieName   string
	pre          PicoHandler
	post         PicoHandler
}

type PicoHandler func(c *Context)

func (p *Pico) Get(pattern string, fn PicoHandler) {
	p.mux.GET(pattern, middle(fn))
}

func (p *Pico) Post(pattern string, fn PicoHandler) {
	p.mux.POST(pattern, middle(fn))
}

func (p *Pico) Options(pattern string, fn PicoHandler) {
	p.mux.OPTIONS(pattern, middle(fn))
}

func (p *Pico) Put(pattern string, fn PicoHandler) {
	p.mux.PUT(pattern, middle(fn))
}

func (p *Pico) Delete(pattern string, fn PicoHandler) {
	p.mux.DELETE(pattern, middle(fn))
}

func (p *Pico) Static(urlPath, diskPath string) {
	if urlPath[len(urlPath)-1] == '/' {
		urlPath = urlPath[:len(urlPath)-1]
	}

	if urlPath[0:1] != "/" {
		urlPath = "/" + urlPath
	}

	p.mux.ServeFiles(urlPath+"/*filepath", http.Dir(diskPath))
}

func (p *Pico) EnableSocketIoOn(url string) {
	var err error
	p.sio, err = socketio.NewServer(nil)
	p.mux.GET(url, middlehttp(p.sio))
	fmt.Println(err)
}

func (p *Pico) OnConnection(fn func(s Socket)) {
	p.sio.On("connection", socket_middle(fn))
}

func (p *Pico) OnError(fn func(s Socket, e error)) {
	p.sio.On("error", socket_middle_error(fn))
}

func (p *Pico) On(event string, fn func(msg string)) {
	p.sio.On(event, fn)
}

func (p *Pico) GetFlash(sessionId string) interface{} {
	return flash.Get(sessionId)
}

func (p *Pico) SetFlash(sessionId string, value interface{}) {
	flash.Set(sessionId, value)
}

// func (p *Pico) TrackSessionUsingCookie(cookieName string) {
// 	p.trackSession = true
// 	p.cookieName = cookieName
// }

func (p *Pico) ListenS(port string) {
	if strings.Index(port, ":") == 0 {
		port = port[1:]
	}

	po, err := strconv.ParseInt(port, 10, 32)
	if err != nil {
		panic("Port Error (:9999 etc)")
	}
	if po < 0 || po > 65000 {
		panic("PORT is OUT of Range")
	}
	p.Listen(int(po))
}

func (p *Pico) Listen(port int) error {
	envPort := os.Getenv("PORT")
	if len(envPort) > 0 {
		pi, err := strconv.Atoi(envPort)
		if err == nil && pi < 65000 {
			port = pi
		}
	}

	p.server = &graceful.Server{
		Timeout: 2 * time.Second,
		Server: &http.Server{
			Addr:    ":" + strconv.Itoa(port),
			Handler: p.mux,
		},
	}
	flash = make(Flash)
	return p.server.ListenAndServe()
}

func (p *Pico) Production() {
	isDev = false
}

func (p *Pico) StopOnInt() {
	p.StopOnIntWithFunc(nil)
}

func (p *Pico) StopOnIntWithFunc(fn func()) {
	p.c = make(chan os.Signal, 1)
	signal.Notify(p.c, os.Interrupt)
	signal.Notify(p.c, os.Kill)

	go func() {
		<-p.c
		if isDev {
			fmt.Println("Shutting Down!")
		}

		p.Stop()

		if isDev {
			fmt.Println("Done!")
		}

		close(p.c)
		if fn != nil {
			fn()
		}

		os.Exit(0)

	}()
}

func (p *Pico) Stop() {
	p.server.Stop(time.Second * 2)
	fmt.Println("Waiting on Stop Channel")
	flash.Clear()
	<-p.server.StopChan()
	fmt.Println("Channel Returned")
}

func New() *Pico {
	isDev = true
	return &Pico{mux: httprouter.New()}
}
<|endoftext|>


<commit_before>// Alexandria
//
// Copyright (C) 2015-2016  Colin Benner
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package alexandria

import (
	"fmt"
	"strings"
)

// Figure out what type of a document we have
func DocumentType(m Metadata) string {
	return m.Type
}

// Return the lines of the last LaTeX comment block without the leading %.  In
// addition, leading and trailing whitespace is removed.
func findMetadataLines(doc string) []string {
	var metadata []string
	for _, line_ := range strings.Split(doc, "\n") {
		line := strings.TrimSpace(line_)
		if line == "" {
			continue
		}

		// If the current line does not begin with a LaTeX comment, we
		// have not yet encountered the last block of comments, so we
		// should discard all data gathered so far.
		if line[0] != '%' {
			metadata = make([]string, 0, 100) // FIXME magic constant
			continue
		}
		// Remove any leading % and whitespaces
		line = strings.TrimLeft(line, "%  \t")
		if line != "" {
			metadata = append(metadata, line)
		}
	}
	return metadata
}

// Parse a comma separated list of tags into a slice, tranfsorming everything
// to lower case.
func parseTags(line string) []string {
	var tags []string
	for _, tag := range strings.Split(line, ",") {
		tmp := strings.ToLower(strings.TrimSpace(tag))
		if tmp != "" {
			tags = append(tags, tmp)
		}
	}
	return tags
}

// Parse the tags in the given scroll content.  The format of a scroll is
// generally of the following form:
//
//	\LaTeX\ code ...
//
//	% @source Author: Title
//	% @source Lemma 3.2, p. 41
//	% @type proposition
//	% counter-example, analysis, TopOloGY, Weierstraß
//
// In this example, the scroll contains a proposition and is tagged with
// 'counter-example', 'analysis', 'topology' and 'weierstraß'.  It can be found
// in Author: Title as Lemma 3.2 on pase 41.  All the metadata is stored in the
// final block of LaTeX comments.  Also, we simply ignore any empty lines.
func ParseMetadata(doc string) Metadata {
	// TODO Handle different types of tags: @source, @doctype, @keywords, and normal tags.
	var source []string
	var scroll_type string
	var tags []string

	for _, line := range findMetadataLines(doc) {
		switch {
		case strings.HasPrefix(line, "@source "):
			source = append(source, strings.TrimSpace(strings.TrimPrefix(line, "@source ")))
		case strings.HasPrefix(line, "@type "):
			tmp := strings.TrimSpace(strings.TrimPrefix(line, "@type "))
			if scroll_type != "" {
				panic(fmt.Sprintf("Scroll has two different types: %s and %s", scroll_type, tmp))
			} else {
				scroll_type = tmp
			}
		default:
			tags = append(tags, parseTags(line)...)
		}
	}

	return Metadata{scroll_type, source, tags}
}

func StripComments(doc string) string {
	var content string
	for _, line_ := range strings.Split(doc, "\n\n") {
		line := strings.TrimSpace(line_)
		if len(line) > 0 && line[0] == '%' {
			continue
		}
		content += line + "\n"
	}
	return strings.TrimSpace(content)
}
<commit_msg>Allow multiple types per scroll<commit_after>// Alexandria
//
// Copyright (C) 2015-2016  Colin Benner
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package alexandria

import (
	"strings"
)

// Figure out what type of a document we have
func DocumentType(m Metadata) string {
	return m.Type
}

// Return the lines of the last LaTeX comment block without the leading %.  In
// addition, leading and trailing whitespace is removed.
func findMetadataLines(doc string) []string {
	var metadata []string
	for _, line_ := range strings.Split(doc, "\n") {
		line := strings.TrimSpace(line_)
		if line == "" {
			continue
		}

		// If the current line does not begin with a LaTeX comment, we
		// have not yet encountered the last block of comments, so we
		// should discard all data gathered so far.
		if line[0] != '%' {
			metadata = make([]string, 0, 100) // FIXME magic constant
			continue
		}
		// Remove any leading % and whitespaces
		line = strings.TrimLeft(line, "%  \t")
		if line != "" {
			metadata = append(metadata, line)
		}
	}
	return metadata
}

// Parse a comma separated list of tags into a slice, tranfsorming everything
// to lower case.
func parseTags(line string) []string {
	var tags []string
	for _, tag := range strings.Split(line, ",") {
		tmp := strings.ToLower(strings.TrimSpace(tag))
		if tmp != "" {
			tags = append(tags, tmp)
		}
	}
	return tags
}

// Parse the tags in the given scroll content.  The format of a scroll is
// generally of the following form:
//
//	\LaTeX\ code ...
//
//	% @source Author: Title
//	% @source Lemma 3.2, p. 41
//	% @type proposition, definition
//	% counter-example, analysis, TopOloGY, Weierstraß
//
// In this example, the scroll contains a proposition and is tagged with
// 'counter-example', 'analysis', 'topology' and 'weierstraß'.  It can be found
// in Author: Title as Lemma 3.2 on pase 41.  All the metadata is stored in the
// final block of LaTeX comments.  Also, we simply ignore any empty lines.
func ParseMetadata(doc string) Metadata {
	// TODO Handle different types of tags: @source, @doctype, @keywords, and normal tags.
	var source []string
	var scroll_type string
	var tags []string

	for _, line := range findMetadataLines(doc) {
		switch {
		case strings.HasPrefix(line, "@source "):
			source = append(source, strings.TrimSpace(strings.TrimPrefix(line, "@source ")))
		case strings.HasPrefix(line, "@type "):
			tmp := strings.TrimSpace(strings.TrimPrefix(line, "@type "))
			for _, type_ := range strings.Split(tmp, ",") {
				// Ignore all but the first type, the other ones are just for searching
				if scroll_type == "" {
					scroll_type = strings.TrimSpace(type_)
					break
				}
			}
		default:
			tags = append(tags, parseTags(line)...)
		}
	}

	return Metadata{scroll_type, source, tags}
}

func StripComments(doc string) string {
	var content string
	for _, line_ := range strings.Split(doc, "\n\n") {
		line := strings.TrimSpace(line_)
		if len(line) > 0 && line[0] == '%' {
			continue
		}
		content += line + "\n"
	}
	return strings.TrimSpace(content)
}
<|endoftext|>


<commit_before>// Alexandria
//
// Copyright (C) 2015-2016  Colin Benner
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package alexandria

import (
	"strings"
)

// Figure out what type of a document we have
func (s *Scroll) DocumentType() string {
	return s.Type
}

// Return the lines of the last LaTeX comment block without the leading %.  In
// addition, leading and trailing whitespace is removed.
func findMetadataLines(doc string) []string {
	var metadata []string
	for _, line_ := range strings.Split(doc, "\n") {
		line := strings.TrimSpace(line_)
		if line == "" {
			continue
		}

		// If the current line does not begin with a LaTeX comment, we
		// have not yet encountered the last block of comments, so we
		// should discard all data gathered so far.
		if line[0] != '%' {
			metadata = make([]string, 0, 100) // FIXME magic constant
			continue
		}
		// Remove any leading % and whitespaces
		line = strings.TrimLeft(line, "%  \t")
		if line != "" {
			metadata = append(metadata, line)
		}
	}
	return metadata
}

// Parse a comma separated list of tags into a slice, tranfsorming everything
// to lower case.
func parseTags(line string) []string {
	var tags []string
	for _, tag := range strings.Split(line, ",") {
		tmp := strings.ToLower(strings.TrimSpace(tag))
		if tmp != "" {
			tags = append(tags, tmp)
		}
	}
	return tags
}

// Parse the tags in the given scroll content.  The format of a scroll is
// generally of the following form:
//
//	\LaTeX\ code ...
//
//	% @source Author: Title
//	% @source Lemma 3.2, p. 41
//	% @type proposition, definition
//	% counter-example, analysis, TopOloGY, Weierstraß
//
// In this example, the scroll contains a proposition and is tagged with
// 'counter-example', 'analysis', 'topology' and 'weierstraß'.  It can be found
// in Author: Title as Lemma 3.2 on pase 41.  All the metadata is stored in the
// final block of LaTeX comments.  Also, we simply ignore any empty lines.
func Parse(id, doc string) Scroll {
	// TODO Handle different types of tags: @source, @doctype, @keywords, and normal tags.
	var source []string
	var hidden []string
	var scroll_type string
	var tags []string
	var other_lines []string

	for _, line := range findMetadataLines(doc) {
		switch {
		case strings.HasPrefix(line, "@hidden "):
			hidden = append(hidden, parseTags(strings.TrimPrefix(line, "@hidden "))...)
		case strings.HasPrefix(line, "@source "):
			source = append(source, strings.TrimSpace(strings.TrimPrefix(line, "@source ")))
		case strings.HasPrefix(line, "@type "):
			tmp := strings.TrimSpace(strings.TrimPrefix(line, "@type "))
			for _, type_ := range strings.Split(tmp, ",") {
				// Ignore all but the first type, the other
				// ones are just for searching
				if scroll_type == "" {
					scroll_type = strings.TrimSpace(type_)
					break
				}
			}
		case strings.HasPrefix(line, "@"):
			// Do not strip the @[a-zA-Z0-9\-]** prefix, otherwise
			// there is no way to tell what the line signifies.
			other_lines = append(other_lines, line)
		default:
			tags = append(tags, parseTags(line)...)
		}
	}
	content := stripComments(doc)

	return Scroll{Id: Id(id), Content: content, Type: scroll_type,
		SourceLines: source, Tags: tags, Hidden: hidden,
		OtherLines: other_lines}
}

// Remove all lines that only contain a LaTeX comment.  This removes all the
// medatata from a scroll.
func stripComments(doc string) string {
	var content string
	for _, line_ := range strings.Split(doc, "\n\n") {
		line := strings.TrimSpace(line_)
		if len(line) > 0 && line[0] == '%' {
			continue
		}
		content += line + "\n"
	}
	return strings.TrimSpace(content)
}
<commit_msg>Preserve captialization in tags<commit_after>// Alexandria
//
// Copyright (C) 2015-2016  Colin Benner
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package alexandria

import (
	"strings"
)

// Figure out what type of a document we have
func (s *Scroll) DocumentType() string {
	return s.Type
}

// Return the lines of the last LaTeX comment block without the leading %.  In
// addition, leading and trailing whitespace is removed.
func findMetadataLines(doc string) []string {
	var metadata []string
	for _, line_ := range strings.Split(doc, "\n") {
		line := strings.TrimSpace(line_)
		if line == "" {
			continue
		}

		// If the current line does not begin with a LaTeX comment, we
		// have not yet encountered the last block of comments, so we
		// should discard all data gathered so far.
		if line[0] != '%' {
			metadata = make([]string, 0, 100) // FIXME magic constant
			continue
		}
		// Remove any leading % and whitespaces
		line = strings.TrimLeft(line, "%  \t")
		if line != "" {
			metadata = append(metadata, line)
		}
	}
	return metadata
}

// Parse a comma separated list of tags into a slice, tranfsorming everything
// to lower case.
func parseTags(line string) []string {
	var tags []string
	for _, tag := range strings.Split(line, ",") {
		tmp := strings.TrimSpace(tag)
		if tmp != "" {
			tags = append(tags, tmp)
		}
	}
	return tags
}

// Parse the tags in the given scroll content.  The format of a scroll is
// generally of the following form:
//
//	\LaTeX\ code ...
//
//	% @source Author: Title
//	% @source Lemma 3.2, p. 41
//	% @type proposition, definition
//	% counter-example, analysis, TopOloGY, Weierstraß
//
// In this example, the scroll contains a proposition and is tagged with
// 'counter-example', 'analysis', 'topology' and 'weierstraß'.  It can be found
// in Author: Title as Lemma 3.2 on pase 41.  All the metadata is stored in the
// final block of LaTeX comments.  Also, we simply ignore any empty lines.
func Parse(id, doc string) Scroll {
	// TODO Handle different types of tags: @source, @doctype, @keywords, and normal tags.
	var source []string
	var hidden []string
	var scroll_type string
	var tags []string
	var other_lines []string

	for _, line := range findMetadataLines(doc) {
		switch {
		case strings.HasPrefix(line, "@hidden "):
			hidden = append(hidden, parseTags(strings.TrimPrefix(line, "@hidden "))...)
		case strings.HasPrefix(line, "@source "):
			source = append(source, strings.TrimSpace(strings.TrimPrefix(line, "@source ")))
		case strings.HasPrefix(line, "@type "):
			tmp := strings.TrimSpace(strings.TrimPrefix(line, "@type "))
			for _, type_ := range strings.Split(tmp, ",") {
				// Ignore all but the first type, the other
				// ones are just for searching
				if scroll_type == "" {
					scroll_type = strings.TrimSpace(type_)
					break
				}
			}
		case strings.HasPrefix(line, "@"):
			// Do not strip the @[a-zA-Z0-9\-]** prefix, otherwise
			// there is no way to tell what the line signifies.
			other_lines = append(other_lines, line)
		default:
			tags = append(tags, parseTags(line)...)
		}
	}
	content := stripComments(doc)

	return Scroll{Id: Id(id), Content: content, Type: scroll_type,
		SourceLines: source, Tags: tags, Hidden: hidden,
		OtherLines: other_lines}
}

// Remove all lines that only contain a LaTeX comment.  This removes all the
// medatata from a scroll.
func stripComments(doc string) string {
	var content string
	for _, line_ := range strings.Split(doc, "\n\n") {
		line := strings.TrimSpace(line_)
		if len(line) > 0 && line[0] == '%' {
			continue
		}
		content += line + "\n"
	}
	return strings.TrimSpace(content)
}
<|endoftext|>


<commit_before>package routinepool

import "errors"

var errBlocked = errors.New("Pool stopped, cannot push any further tasks")

//Pool is the struct which handles the worker pool
type Pool struct {
	//size is the pool size, i.e. no.of go routines
	size uint64

	//channelSize is size of the channel buffer to which the works/jobs are sent
	channelSize uint8

	//workerPool is the channel buffer to which all works/jobs are sent
	workerPool chan poolFn

	//active is the channel used to keep count of active works
	active chan *struct{}

	//block if set to true, will block any further jobs being pushed/send to the pool
	block bool

	//quit is used to exit all the routes
	quit chan struct{}
}

type poolFn func()

type worker struct {
	fn poolFn
}

type workerPool chan []worker

//Push pushes a task to the worker pool
func (p *Pool) Push(work poolFn) error {
	if p.block {
		return errBlocked
	}

	p.workerPool <- work
	return nil
}

//Start starts the worker Q
func (p *Pool) Start() {
	p.quit = make(chan struct{})

	for i := uint64(0); i < p.size; i++ {
		go func(pl *Pool, quit chan struct{}) {
			for {
				select {
				//Listening for quit signal
				case <-quit:
					return

				// Pulling work from the channel buffer
				case work := <-pl.workerPool:
					p.active <- nil
					work()
					if pl.block == false {
						<-p.active
					}
				}
			}
		}(p, p.quit)
	}
}

//Stop stops the pool and exits all the go routines immediately
func (p *Pool) Stop() {
	//block accepting any furhter tasks
	p.block = true

	//Graceful shutdown of pool, makes sure if all pending tasks are completed
	for {
		select {
		case <-p.active:
			if len(p.workerPool) == 0 && len(p.active) == 0 {
				//close all channels
				close(p.quit)
				close(p.active)
				close(p.workerPool)
				return
			}
		}
	}
}

//Active returns the number of active jobs
func (p *Pool) Active() int {
	return len(p.active)
}

//Pending returns the number of jobs in queue
func (p *Pool) Pending() int {
	return len(p.workerPool)
}

//New returns a Pool object pointer with all the default values set
func New(pSize uint64, csize uint8) *Pool {
	p := &Pool{
		size:        pSize,
		channelSize: csize,
	}

	//pSize = 0 will fall back to 1
	if p.size == 0 {
		p.size = 1
	}

	if p.channelSize == 0 {
		p.channelSize = 100
	}

	p.workerPool = make(chan poolFn, p.channelSize)
	p.active = make(chan *struct{}, p.size)

	return p
}
<commit_msg>fixed race condition while graceful shutting down<commit_after>package routinepool

import "errors"

var errBlocked = errors.New("Pool stopped, cannot push any further tasks")

//Pool is the struct which handles the worker pool
type Pool struct {
	//size is the pool size, i.e. no.of go routines
	size uint64

	//channelSize is size of the channel buffer to which the works/jobs are sent
	channelSize uint8

	//workerPool is the channel buffer to which all works/jobs are sent
	workerPool chan poolFn

	//active is the channel used to keep count of active works
	active chan *struct{}

	//block if set to true, will block any further jobs being pushed/send to the pool
	block bool

	//quit is used to exit all the routes
	quit chan struct{}
}

type poolFn func()

type worker struct {
	fn poolFn
}

type workerPool chan []worker

//Push pushes a task to the worker pool
func (p *Pool) Push(work poolFn) error {
	if p.block {
		return errBlocked
	}

	p.workerPool <- work
	return nil
}

//Start starts the worker Q
func (p *Pool) Start() {
	p.quit = make(chan struct{})

	for i := uint64(0); i < p.size; i++ {
		go func(pl *Pool) {
			for {
				select {
				//Listening for quit signal
				case <-pl.quit:
					return

				// Pulling work from the channel buffer
				case work := <-pl.workerPool:
					pl.active <- nil
					work()
				}
			}
		}(p)
	}

	//Graceful shutdown of pool, makes sure if all pending tasks are completed
	go func() {
		for {
			select {
			case <-p.active:
				// Initiate shutdown only if Stop() (i.e. blocked = true) is called and workerpool is blocked from
				// accepting any further tasks.
				if p.block {
					if len(p.workerPool) == 0 && len(p.active) == 0 {
						//close all channels
						close(p.quit)
						close(p.workerPool)
						close(p.active)
						return
					}
				}

			}
		}
	}()
}

//Stop stops the pool and exits all the go routines immediately
func (p *Pool) Stop() {
	//block accepting any furhter tasks
	p.block = true
}

//Active returns the number of active jobs
func (p *Pool) Active() int {
	return len(p.active)
}

//Pending returns the number of jobs in queue
func (p *Pool) Pending() int {
	return len(p.workerPool)
}

//New returns a Pool object pointer with all the default values set
func New(pSize uint64, csize uint8) *Pool {
	p := &Pool{
		size:        pSize,
		channelSize: csize,
	}

	//pSize = 0 will fall back to 1
	if p.size == 0 {
		p.size = 1
	}

	if p.channelSize == 0 {
		p.channelSize = 100
	}

	p.workerPool = make(chan poolFn, p.channelSize)
	p.active = make(chan *struct{}, p.size)

	return p
}
<|endoftext|>


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

package s3git

import (
	"errors"
	"fmt"

	"github.com/s3git/s3git-go/internal/backend"
	"github.com/s3git/s3git-go/internal/cas"
	"github.com/s3git/s3git-go/internal/core"
	"github.com/s3git/s3git-go/internal/kv"

	"bytes"
	"encoding/hex"
	"sync"
)

// Perform a push to the back end for the repository
func (repo Repository) Push(hydrated bool, progress func(maxTicks int64)) error {

	list, err := kv.ListLevel1Prefixes()
	if err != nil {
		return err
	}

	return push(list, hydrated, progress)
}

// Push any new commit objects including all added objects to the back end store
func push(prefixChan <-chan []byte, hydrated bool, progress func(maxTicks int64)) error {

	client, err := backend.GetDefaultClient()
	if err != nil {
		return err
	}

	// Get map of prefixes already in store
	prefixesInBackend, err := listPrefixes(client)
	if err != nil {
		return err
	}

	prefixesToPush := []string{}

	for prefixByte := range prefixChan {

		prefix := hex.EncodeToString(prefixByte)

		_, verified := prefixesInBackend[prefix]

		// We can safely skip in case a prefix object is verified (pushed as last object)
		if !verified {

			prefixesToPush = append(prefixesToPush, prefix)
		}
	}

	if len(prefixesToPush) == 0 {
		return nil
	}

	progress(int64(len(prefixesToPush)))

	for _, prefix := range prefixesToPush {

		// Get prefix object
		po, err := core.GetPrefixObject(prefix)
		if err != nil {
			return err
		}

		// Get commit object
		co, err := core.GetCommitObject(po.S3gitFollowMe)
		if err != nil {
			return err
		}

		// Check if there is a tree object
		if co.S3gitTree != "" {
			// Get tree object
			to, err := core.GetTreeObject(co.S3gitTree)
			if err != nil {
				return err
			}

			// first push all added blobs in this commit ...
			err = pushBlobRange(to.S3gitAdded, nil, hydrated, client)
			if err != nil {
				return err
			}

			// then push tree object
			_, err = pushBlob(co.S3gitTree, nil, client)
			if err != nil {
				return err
			}
		}

		// Check if there is a snapshot object
		if co.S3gitSnapshot != "" {

			// Just push snapshot object (no need to read it as it does not reference any new objects)
			_, err = pushBlob(co.S3gitSnapshot, nil, client)
			if err != nil {
				return err
			}
		}

		// then push commit object
		_, err = pushBlob(po.S3gitFollowMe, nil, client)
		if err != nil {
			return err
		}

		// ...  finally push prefix object itself
		// (if something goes in chain above, the prefix object will be missing so
		//  will be (attempted to) uploaded again during the next push)
		_, err = pushBlob(prefix, nil, client)
		if err != nil {
			return err
		}

		progress(int64(len(prefixesToPush)))
	}

	return nil
}

// Push a blob to the back end store
func pushBlob(hash string, size *uint64, client backend.Backend) (newlyUploaded bool, err error) {

	// TODO: Consider whether we want to verify again...
	if false {
		verified, err := client.VerifyHash(hash)
		if err != nil {
			return false, err
		} else if verified { // Resource already in back-end
			return false, nil
		}
	}

	// TODO: [perf] for back ends storing whole files: consider multipart upload?
	cr := cas.MakeReader(hash)
	if cr == nil {
		panic(errors.New("Failed to create cas reader"))
	}

	err = client.UploadWithReader(hash, cr)
	if err != nil {
		return false, err
	}

	// Move blob from .stage to .cache directory upon successful upload
	err = cas.MoveBlobToCache(hash)
	if err != nil {
		return false, err
	}

	return true, nil
}

// Push a blob to the back end store in deduplicated format
func PushBlobDeduped(hash string, size *uint64, client backend.Backend) (newlyUploaded bool, err error) {

	// TODO: [perf] for back ends storing chunks: upload chunks in parallel

	hx, err := hex.DecodeString(hash)
	if err != nil {
		return false, err
	}

	// Get hashes for leaves
	leafHashes, _, err := kv.GetLevel1(hx)
	if err != nil {
		return false, err
	} else if len(leafHashes) == 0 {
		return false, errors.New(fmt.Sprintf("Unable to push an empty blob: %s", hash))
	}

	// Iterate over the leaves and push up to remote
	for i := 0; i < len(leafHashes); i += cas.KeySize {

		// TODO: verify whether leaf blob is already in back end, and skip if so
		err := cas.PushLeafBlob(hex.EncodeToString(leafHashes[i:i+cas.KeySize]), client)
		if err != nil {
			return false, err
		}
	}

	// Finally upload root hash
	b := bytes.NewBuffer(leafHashes)
	err = client.UploadWithReader(hash, b)
	if err != nil {
		return false, err
	}

	// Move blob from .stage to .cache directory upon successful upload
	err = cas.MoveBlobToCache(hash)
	if err != nil {
		return false, err
	}

	return true, nil
}

// Push a range of blobs to the back end store in parallel
//
// See https://github.com/adonovan/gopl.io/blob/master/ch8/thumbnail/thumbnail_test.go
//
func pushBlobRange(hashes []string, size *uint64, hydrated bool, client backend.Backend) error {

	var wg sync.WaitGroup
	var msgs = make(chan string)
	var results = make(chan error)

	for i := 0; i < min(len(hashes), 100); i++ {
		wg.Add(1)

		go func() {
			defer wg.Done()
			for hash := range msgs {

				pushHydratedToRemote := hydrated
				if !checkIfLeavesAreEqualSize(hash) {
					pushHydratedToRemote = false // Cannot push hydrated to remote back end when eg rolling hash is used (as we do not know where the boundaries are)
				}
				var err error
				if pushHydratedToRemote {
					_, err = pushBlob(hash, size, client)
				} else {
					_, err = PushBlobDeduped(hash, size, client)
				}
				results <- err
			}
		}()
	}

	go func() {
		for _, hash := range hashes {
			msgs <- hash
		}
		close(msgs)
	}()

	go func() {
		wg.Wait()
		close(results)
	}()

	var err error
	for e := range results {
		if e != nil {
			err = e
		}
	}

	return err
}

func checkIfLeavesAreEqualSize(hash string) bool {
	// TODO: Implement: iterate over all leaves, check whether (except for last node) all sizes are equal
	return true
}

// List prefixes at back end store, doing 16 lists in parallel
func listPrefixes(client backend.Backend) (map[string]bool, error) {

	var wg sync.WaitGroup
	var results = make(chan []string)

	for i := 0x0; i <= 0xf; i++ {
		wg.Add(1)

		go func(i int) {
			defer wg.Done()
			result := make([]string, 0, 1000)

			client.List(fmt.Sprintf("%s%x", core.Prefix(), i), func(key string) {
				result = append(result, key)

				// TODO: WE NEED TO wg.Done() HERE WHEN LAST KEY HAS BEEN RECEIVED
				//       -- SEE /Users/frankw/golang/src/github.com/fwessels/listperf/listperf.go
				// IMPORTANT: WE WILL BE MISSING OBJECTS HERE
			})

			results <- result
		}(i)
	}

	go func() {
		wg.Wait()
		close(results)
	}()

	prefixHash := make(map[string]bool)
	for result := range results {
		for _, r := range result {
			prefixHash[r] = true
		}
	}

	return prefixHash, nil
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
<commit_msg>Add children recursively when pushing snapshot<commit_after>/*
 * Copyright 2016 Frank Wessels <fwessels@xs4all.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package s3git

import (
	"errors"
	"fmt"

	"github.com/s3git/s3git-go/internal/backend"
	"github.com/s3git/s3git-go/internal/cas"
	"github.com/s3git/s3git-go/internal/core"
	"github.com/s3git/s3git-go/internal/kv"

	"bytes"
	"encoding/hex"
	"sync"
)

// Perform a push to the back end for the repository
func (repo Repository) Push(hydrated bool, progress func(maxTicks int64)) error {

	list, err := kv.ListLevel1Prefixes()
	if err != nil {
		return err
	}

	return push(list, hydrated, progress)
}

// Push any new commit objects including all added objects to the back end store
func push(prefixChan <-chan []byte, hydrated bool, progress func(maxTicks int64)) error {

	client, err := backend.GetDefaultClient()
	if err != nil {
		return err
	}

	// Get map of prefixes already in store
	prefixesInBackend, err := listPrefixes(client)
	if err != nil {
		return err
	}

	prefixesToPush := []string{}

	for prefixByte := range prefixChan {

		prefix := hex.EncodeToString(prefixByte)

		_, verified := prefixesInBackend[prefix]

		// We can safely skip in case a prefix object is verified (pushed as last object)
		if !verified {

			prefixesToPush = append(prefixesToPush, prefix)
		}
	}

	if len(prefixesToPush) == 0 {
		return nil
	}

	progress(int64(len(prefixesToPush)))

	for _, prefix := range prefixesToPush {

		// Get prefix object
		po, err := core.GetPrefixObject(prefix)
		if err != nil {
			return err
		}

		// Get commit object
		co, err := core.GetCommitObject(po.S3gitFollowMe)
		if err != nil {
			return err
		}

		// Check if there is a tree object
		if co.S3gitTree != "" {
			// Get tree object
			to, err := core.GetTreeObject(co.S3gitTree)
			if err != nil {
				return err
			}

			// first push all added blobs in this commit ...
			err = pushBlobRange(to.S3gitAdded, nil, hydrated, client)
			if err != nil {
				return err
			}

			// then push tree object
			_, err = pushBlob(co.S3gitTree, nil, client)
			if err != nil {
				return err
			}
		}

		// Check if there is a snapshot object
		if co.S3gitSnapshot != "" {

			err = pushSnapshotWithChildren(co.S3gitSnapshot, client)
			if err != nil {
				return err
			}
		}

		// then push commit object
		_, err = pushBlob(po.S3gitFollowMe, nil, client)
		if err != nil {
			return err
		}

		// ...  finally push prefix object itself
		// (if something goes in chain above, the prefix object will be missing so
		//  will be (attempted to) uploaded again during the next push)
		_, err = pushBlob(prefix, nil, client)
		if err != nil {
			return err
		}

		progress(int64(len(prefixesToPush)))
	}

	return nil
}

func pushSnapshotWithChildren(hash string, client backend.Backend) error {

	// Get snapshot object
	so, err := core.GetSnapshotObject(hash)
	if err != nil {
		return err
	}

	for _, entry := range so.S3gitEntries {
		if entry.IsDirectory() {
			err = pushSnapshotWithChildren(entry.Blob, client)
		}
	}

	// Push snapshot object
	_, err = pushBlob(hash, nil, client)
	if err != nil {
		return err
	}

	return nil
}
// Push a blob to the back end store
func pushBlob(hash string, size *uint64, client backend.Backend) (newlyUploaded bool, err error) {

	// TODO: Consider whether we want to verify again...
	if false {
		verified, err := client.VerifyHash(hash)
		if err != nil {
			return false, err
		} else if verified { // Resource already in back-end
			return false, nil
		}
	}

	// TODO: [perf] for back ends storing whole files: consider multipart upload?
	cr := cas.MakeReader(hash)
	if cr == nil {
		panic(errors.New("Failed to create cas reader"))
	}

	err = client.UploadWithReader(hash, cr)
	if err != nil {
		return false, err
	}

	// Move blob from .stage to .cache directory upon successful upload
	err = cas.MoveBlobToCache(hash)
	if err != nil {
		return false, err
	}

	return true, nil
}

// Push a blob to the back end store in deduplicated format
func PushBlobDeduped(hash string, size *uint64, client backend.Backend) (newlyUploaded bool, err error) {

	// TODO: [perf] for back ends storing chunks: upload chunks in parallel

	hx, err := hex.DecodeString(hash)
	if err != nil {
		return false, err
	}

	// Get hashes for leaves
	leafHashes, _, err := kv.GetLevel1(hx)
	if err != nil {
		return false, err
	} else if len(leafHashes) == 0 {
		return false, errors.New(fmt.Sprintf("Unable to push an empty blob: %s", hash))
	}

	// Iterate over the leaves and push up to remote
	for i := 0; i < len(leafHashes); i += cas.KeySize {

		// TODO: verify whether leaf blob is already in back end, and skip if so
		err := cas.PushLeafBlob(hex.EncodeToString(leafHashes[i:i+cas.KeySize]), client)
		if err != nil {
			return false, err
		}
	}

	// Finally upload root hash
	b := bytes.NewBuffer(leafHashes)
	err = client.UploadWithReader(hash, b)
	if err != nil {
		return false, err
	}

	// Move blob from .stage to .cache directory upon successful upload
	err = cas.MoveBlobToCache(hash)
	if err != nil {
		return false, err
	}

	return true, nil
}

// Push a range of blobs to the back end store in parallel
//
// See https://github.com/adonovan/gopl.io/blob/master/ch8/thumbnail/thumbnail_test.go
//
func pushBlobRange(hashes []string, size *uint64, hydrated bool, client backend.Backend) error {

	var wg sync.WaitGroup
	var msgs = make(chan string)
	var results = make(chan error)

	for i := 0; i < min(len(hashes), 100); i++ {
		wg.Add(1)

		go func() {
			defer wg.Done()
			for hash := range msgs {

				pushHydratedToRemote := hydrated
				if !checkIfLeavesAreEqualSize(hash) {
					pushHydratedToRemote = false // Cannot push hydrated to remote back end when eg rolling hash is used (as we do not know where the boundaries are)
				}
				var err error
				if pushHydratedToRemote {
					_, err = pushBlob(hash, size, client)
				} else {
					_, err = PushBlobDeduped(hash, size, client)
				}
				results <- err
			}
		}()
	}

	go func() {
		for _, hash := range hashes {
			msgs <- hash
		}
		close(msgs)
	}()

	go func() {
		wg.Wait()
		close(results)
	}()

	var err error
	for e := range results {
		if e != nil {
			err = e
		}
	}

	return err
}

func checkIfLeavesAreEqualSize(hash string) bool {
	// TODO: Implement: iterate over all leaves, check whether (except for last node) all sizes are equal
	return true
}

// List prefixes at back end store, doing 16 lists in parallel
func listPrefixes(client backend.Backend) (map[string]bool, error) {

	var wg sync.WaitGroup
	var results = make(chan []string)

	for i := 0x0; i <= 0xf; i++ {
		wg.Add(1)

		go func(i int) {
			defer wg.Done()
			result := make([]string, 0, 1000)

			client.List(fmt.Sprintf("%s%x", core.Prefix(), i), func(key string) {
				result = append(result, key)

				// TODO: WE NEED TO wg.Done() HERE WHEN LAST KEY HAS BEEN RECEIVED
				//       -- SEE /Users/frankw/golang/src/github.com/fwessels/listperf/listperf.go
				// IMPORTANT: WE WILL BE MISSING OBJECTS HERE
			})

			results <- result
		}(i)
	}

	go func() {
		wg.Wait()
		close(results)
	}()

	prefixHash := make(map[string]bool)
	for result := range results {
		for _, r := range result {
			prefixHash[r] = true
		}
	}

	return prefixHash, nil
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/mgutz/ansi"
	"github.com/phrase/phraseapp-api-client/Godeps/_workspace/src/gopkg.in/yaml.v2"
	"github.com/phrase/phraseapp-go/phraseapp"
)

type PushCommand struct {
	Verbose bool `cli:"opt --verbose default=false"`
}

func (cmd *PushCommand) Run() error {
	if cmd.Verbose {
		Debug = true
	}

	sources, err := SourcesFromConfig()
	if err != nil {
		return err
	}

	for _, source := range sources {
		err := source.Push()
		if err != nil {
			fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
		}
	}
	return nil
}

type Sources []*Source

type Source struct {
	File           string      `yaml:"file,omitempty"`
	ProjectId      string      `yaml:"project_id,omitempty"`
	AccessToken    string      `yaml:"access_token,omitempty"`
	FileFormat     string      `yaml:"file_format,omitempty"`
	Params         *PushParams `yaml:"params,omitempty"`
	RemoteLocales  []*phraseapp.Locale
	PathComponents *PathComponents
	Extension      string
}

type PushParams struct {
	FileFormat         string                  `yaml:"file_format,omitempty"`
	LocaleId           string                  `yaml:"locale_id,omitempty"`
	ConvertEmoji       *bool                   `yaml:"convert_emoji,omitempty"`
	FormatOptions      *map[string]interface{} `yaml:"format_options,omitempty"`
	SkipUnverification *bool                   `yaml:"skip_unverification,omitempty"`
	SkipUploadTags     *bool                   `yaml:"skip_upload_tags,omitempty"`
	Tags               []string                `yaml:"tags,omitempty"`
	UpdateTranslations *bool                   `yaml:"update_translations,omitempty"`
}

func (source *Source) Push() error {
	Authenticate()

	remoteLocales, err := RemoteLocales(source.ProjectId)
	if err != nil {
		return err
	}
	source.RemoteLocales = remoteLocales

	localeFiles, err := source.LocaleFiles()
	if err != nil {
		return err
	}

	for _, localeFile := range localeFiles {
		fmt.Println("Uploading", localeFile.RelPath())
		err = source.uploadFile(localeFile)
		if err != nil {
			printErr(err, "")
		}
		sharedMessage("push", localeFile)
	}

	return nil
}

func (source *Source) uploadFile(localeFile *LocaleFile) error {
	uploadParams, err := source.setUploadParams(localeFile)
	if err != nil {
		return err
	}

	aUpload, err := phraseapp.UploadCreate(source.ProjectId, uploadParams)
	if err != nil {
		return err
	}

	printSummary(&aUpload.Summary)

	return nil
}

func (source *Source) IsDir() bool {
	return source.PathComponents.IsDir
}

func (source *Source) GlobPattern() string {
	return source.PathComponents.GlobPattern
}

func (source *Source) LocaleFiles() (LocaleFiles, error) {
	source.Extension = filepath.Ext(source.File)

	filePaths, err := source.glob()
	if err != nil {
		return nil, err
	}
	var localeFiles LocaleFiles
	for _, path := range filePaths {
		localeFile := source.generateLocaleForFile(path)
		localeFiles = append(localeFiles, localeFile)
	}
	return localeFiles, nil
}

func (source *Source) generateLocaleForFile(path string) *LocaleFile {
	absolutePath, err := filepath.Abs(path)
	if err != nil {
		printErr(err, "")
		return nil
	}

	return &LocaleFile{Path: absolutePath}
}

func (source *Source) FileWithoutPlaceholder() string {
	re := regexp.MustCompile("<(locale_name|tag|locale_code)>")
	return strings.TrimSuffix(re.ReplaceAllString(source.File, ""), source.Extension)
}

func (source *Source) glob() ([]string, error) {
	pattern := source.FileWithoutPlaceholder() + "*" + source.Extension
	files, err := filepath.Glob(pattern)

	if Debug {
		fmt.Println("Found", len(files), "files matching the source pattern", pattern)
	}
	if err != nil {
		return nil, err
	}

	return files, nil
}

func SourcesFromConfig() (Sources, error) {
	content, err := ConfigContent()
	if err != nil {
		return nil, err
	}

	var config *PushConfig

	err = yaml.Unmarshal([]byte(content), &config)
	if err != nil {
		return nil, err
	}

	token := config.Phraseapp.AccessToken
	projectId := config.Phraseapp.ProjectId
	fileFormat := config.Phraseapp.FileFormat
	sources := *config.Phraseapp.Push.Sources

	for _, source := range sources {
		if source.ProjectId == "" {
			source.ProjectId = projectId
		}
		if source.AccessToken == "" {
			source.AccessToken = token
		}
		if source.FileFormat == "" {
			source.FileFormat = fileFormat
		}
	}

	return sources, nil
}

func (source *Source) setUploadParams(localeFile *LocaleFile) (*phraseapp.LocaleFileImportParams, error) {
	uploadParams := new(phraseapp.LocaleFileImportParams)
	uploadParams.File = localeFile.Path
	uploadParams.FileFormat = &source.FileFormat

	if localeFile.Id != "" {
		uploadParams.LocaleId = &(localeFile.Id)
	}

	if source.Params == nil {
		return uploadParams, nil
	}

	params := source.Params

	localeId := params.LocaleId
	if localeId != "" {
		uploadParams.LocaleId = &localeId
	}

	format := params.FileFormat
	if format != "" {
		uploadParams.FileFormat = &format
	}

	convertEmoji := params.ConvertEmoji
	if convertEmoji != nil {
		uploadParams.ConvertEmoji = convertEmoji
	}

	formatOptions := params.FormatOptions
	if formatOptions != nil {
		uploadParams.FormatOptions = formatOptions
	}

	skipUnverification := params.SkipUnverification
	if skipUnverification != nil {
		uploadParams.SkipUnverification = skipUnverification
	}

	skipUploadTags := params.SkipUploadTags
	if skipUploadTags != nil {
		uploadParams.SkipUploadTags = skipUploadTags
	}

	tags := params.Tags

	if tags != nil {
		uploadParams.Tags = tags
	}

	updateTranslations := params.UpdateTranslations
	if updateTranslations != nil {
		uploadParams.UpdateTranslations = updateTranslations
	}

	return uploadParams, nil
}

// Parsing
type PushConfig struct {
	Phraseapp struct {
		AccessToken string `yaml:"access_token"`
		ProjectId   string `yaml:"project_id"`
		FileFormat  string `yaml:"file_format,omitempty"`
		Push        struct {
			Sources *Sources
		}
	}
}

func printSummary(summary *phraseapp.SummaryType) {
	newItems := []int64{summary.LocalesCreated, summary.TranslationsUpdated, summary.TranslationKeysCreated, summary.TranslationsCreated}
	var changed bool
	for _, item := range newItems {
		if item > 0 {
			changed = true
		}
	}
	if changed || Debug {
		localesCreated := joinMessage("Locales created: ", fmt.Sprintf("%d", summary.LocalesCreated))
		keysCreated := joinMessage("Keys created: ", fmt.Sprintf("%d", summary.TranslationKeysCreated))
		translationsCreated := joinMessage("Translations created: ", fmt.Sprintf("%d", summary.TranslationsCreated))
		translationsUpdated := joinMessage("Translations updated: ", fmt.Sprintf("%d", summary.TranslationsUpdated))
		formatted := fmt.Sprintf("%s - %s - %s - %s", localesCreated, keysCreated, translationsCreated, translationsUpdated)
		fmt.Println(formatted)
	}
}

func joinMessage(msg, stat string) string {
	green := ansi.ColorCode("green+b:black")
	reset := ansi.ColorCode("reset")
	return strings.Join([]string{msg, green, stat, reset}, "")
}
<commit_msg>adds asterix to glob - needs testing<commit_after>package main

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/mgutz/ansi"
	"github.com/phrase/phraseapp-api-client/Godeps/_workspace/src/gopkg.in/yaml.v2"
	"github.com/phrase/phraseapp-go/phraseapp"
)

type PushCommand struct {
	Verbose bool `cli:"opt --verbose default=false"`
}

func (cmd *PushCommand) Run() error {
	if cmd.Verbose {
		Debug = true
	}

	sources, err := SourcesFromConfig()
	if err != nil {
		return err
	}

	for _, source := range sources {
		err := source.Push()
		if err != nil {
			fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
		}
	}
	return nil
}

type Sources []*Source

type Source struct {
	File           string      `yaml:"file,omitempty"`
	ProjectId      string      `yaml:"project_id,omitempty"`
	AccessToken    string      `yaml:"access_token,omitempty"`
	FileFormat     string      `yaml:"file_format,omitempty"`
	Params         *PushParams `yaml:"params,omitempty"`
	RemoteLocales  []*phraseapp.Locale
	PathComponents *PathComponents
	Extension      string
}

type PushParams struct {
	FileFormat         string                  `yaml:"file_format,omitempty"`
	LocaleId           string                  `yaml:"locale_id,omitempty"`
	ConvertEmoji       *bool                   `yaml:"convert_emoji,omitempty"`
	FormatOptions      *map[string]interface{} `yaml:"format_options,omitempty"`
	SkipUnverification *bool                   `yaml:"skip_unverification,omitempty"`
	SkipUploadTags     *bool                   `yaml:"skip_upload_tags,omitempty"`
	Tags               []string                `yaml:"tags,omitempty"`
	UpdateTranslations *bool                   `yaml:"update_translations,omitempty"`
}

func (source *Source) Push() error {
	Authenticate()

	remoteLocales, err := RemoteLocales(source.ProjectId)
	if err != nil {
		return err
	}
	source.RemoteLocales = remoteLocales

	localeFiles, err := source.LocaleFiles()
	if err != nil {
		return err
	}

	for _, localeFile := range localeFiles {
		fmt.Println("Uploading", localeFile.RelPath())
		err = source.uploadFile(localeFile)
		if err != nil {
			printErr(err, "")
		}
		sharedMessage("push", localeFile)
	}

	return nil
}

func (source *Source) uploadFile(localeFile *LocaleFile) error {
	uploadParams, err := source.setUploadParams(localeFile)
	if err != nil {
		return err
	}

	aUpload, err := phraseapp.UploadCreate(source.ProjectId, uploadParams)
	if err != nil {
		return err
	}

	printSummary(&aUpload.Summary)

	return nil
}

func (source *Source) IsDir() bool {
	return source.PathComponents.IsDir
}

func (source *Source) GlobPattern() string {
	return source.PathComponents.GlobPattern
}

func (source *Source) LocaleFiles() (LocaleFiles, error) {
	source.Extension = filepath.Ext(source.File)

	filePaths, err := source.glob()
	if err != nil {
		return nil, err
	}
	var localeFiles LocaleFiles
	for _, path := range filePaths {
		localeFile := source.generateLocaleForFile(path)
		localeFiles = append(localeFiles, localeFile)
	}
	return localeFiles, nil
}

func (source *Source) generateLocaleForFile(path string) *LocaleFile {
	absolutePath, err := filepath.Abs(path)
	if err != nil {
		printErr(err, "")
		return nil
	}

	return &LocaleFile{Path: absolutePath}
}

func (source *Source) FileWithoutPlaceholder() string {
	re := regexp.MustCompile("<(locale_name|tag|locale_code)>")
	return strings.TrimSuffix(re.ReplaceAllString(source.File, "*"), source.Extension)
}

func (source *Source) glob() ([]string, error) {
	pattern := source.FileWithoutPlaceholder() + "*" + source.Extension
	files, err := filepath.Glob(pattern)

	if Debug {
		fmt.Println("Found", len(files), "files matching the source pattern", pattern)
	}
	if err != nil {
		return nil, err
	}

	return files, nil
}

func SourcesFromConfig() (Sources, error) {
	content, err := ConfigContent()
	if err != nil {
		return nil, err
	}

	var config *PushConfig

	err = yaml.Unmarshal([]byte(content), &config)
	if err != nil {
		return nil, err
	}

	token := config.Phraseapp.AccessToken
	projectId := config.Phraseapp.ProjectId
	fileFormat := config.Phraseapp.FileFormat
	sources := *config.Phraseapp.Push.Sources

	for _, source := range sources {
		if source.ProjectId == "" {
			source.ProjectId = projectId
		}
		if source.AccessToken == "" {
			source.AccessToken = token
		}
		if source.FileFormat == "" {
			source.FileFormat = fileFormat
		}
	}

	return sources, nil
}

func (source *Source) setUploadParams(localeFile *LocaleFile) (*phraseapp.LocaleFileImportParams, error) {
	uploadParams := new(phraseapp.LocaleFileImportParams)
	uploadParams.File = localeFile.Path
	uploadParams.FileFormat = &source.FileFormat

	if localeFile.Id != "" {
		uploadParams.LocaleId = &(localeFile.Id)
	}

	if source.Params == nil {
		return uploadParams, nil
	}

	params := source.Params

	localeId := params.LocaleId
	if localeId != "" {
		uploadParams.LocaleId = &localeId
	}

	format := params.FileFormat
	if format != "" {
		uploadParams.FileFormat = &format
	}

	convertEmoji := params.ConvertEmoji
	if convertEmoji != nil {
		uploadParams.ConvertEmoji = convertEmoji
	}

	formatOptions := params.FormatOptions
	if formatOptions != nil {
		uploadParams.FormatOptions = formatOptions
	}

	skipUnverification := params.SkipUnverification
	if skipUnverification != nil {
		uploadParams.SkipUnverification = skipUnverification
	}

	skipUploadTags := params.SkipUploadTags
	if skipUploadTags != nil {
		uploadParams.SkipUploadTags = skipUploadTags
	}

	tags := params.Tags

	if tags != nil {
		uploadParams.Tags = tags
	}

	updateTranslations := params.UpdateTranslations
	if updateTranslations != nil {
		uploadParams.UpdateTranslations = updateTranslations
	}

	return uploadParams, nil
}

// Parsing
type PushConfig struct {
	Phraseapp struct {
		AccessToken string `yaml:"access_token"`
		ProjectId   string `yaml:"project_id"`
		FileFormat  string `yaml:"file_format,omitempty"`
		Push        struct {
			Sources *Sources
		}
	}
}

func printSummary(summary *phraseapp.SummaryType) {
	newItems := []int64{summary.LocalesCreated, summary.TranslationsUpdated, summary.TranslationKeysCreated, summary.TranslationsCreated}
	var changed bool
	for _, item := range newItems {
		if item > 0 {
			changed = true
		}
	}
	if changed || Debug {
		localesCreated := joinMessage("Locales created: ", fmt.Sprintf("%d", summary.LocalesCreated))
		keysCreated := joinMessage("Keys created: ", fmt.Sprintf("%d", summary.TranslationKeysCreated))
		translationsCreated := joinMessage("Translations created: ", fmt.Sprintf("%d", summary.TranslationsCreated))
		translationsUpdated := joinMessage("Translations updated: ", fmt.Sprintf("%d", summary.TranslationsUpdated))
		formatted := fmt.Sprintf("%s - %s - %s - %s", localesCreated, keysCreated, translationsCreated, translationsUpdated)
		fmt.Println(formatted)
	}
}

func joinMessage(msg, stat string) string {
	green := ansi.ColorCode("green+b:black")
	reset := ansi.ColorCode("reset")
	return strings.Join([]string{msg, green, stat, reset}, "")
}
<|endoftext|>


<commit_before>package limiter

import (
	"fmt"
	"strconv"
	"strings"
	"time"
)

// Rate is the rate.
type Rate struct {
	Formatted string
	Period    time.Duration
	Limit     int64
}

// NewRateFromFormatted returns the rate from the formatted version.
func NewRateFromFormatted(formatted string) (Rate, error) {
	rate := Rate{}

	values := strings.Split(formatted, "-")
	if len(values) != 2 {
		return rate, fmt.Errorf("Incorrect format '%s'", formatted)
	}

	periods := map[string]bool{
		"S": true, // Second
		"M": true, // Minute
		"H": true, // Hour
	}

	limit, period := values[0], strings.ToUpper(values[1])

	if _, ok := periods[period]; !ok {
		return rate, fmt.Errorf("Incorrect period '%s'", period)
	}

	var (
		p time.Duration
		l int
	)

	switch period {
	case "S":
		p = 1 * time.Second
	case "M":
		p = 1 * time.Minute
	case "H":
		p = 1 * time.Hour
	}

	l, err := strconv.Atoi(limit)
	if err != nil {
		return rate, fmt.Errorf("Incorrect limit '%s'", limit)
	}

	return Rate{
		Formatted: formatted,
		Period:    p,
		Limit:     int64(l),
	}, nil
}
<commit_msg>Re-use duration from time constant<commit_after>package limiter

import (
	"fmt"
	"strconv"
	"strings"
	"time"
)

// Rate is the rate.
type Rate struct {
	Formatted string
	Period    time.Duration
	Limit     int64
}

// NewRateFromFormatted returns the rate from the formatted version.
func NewRateFromFormatted(formatted string) (Rate, error) {
	rate := Rate{}

	values := strings.Split(formatted, "-")
	if len(values) != 2 {
		return rate, fmt.Errorf("Incorrect format '%s'", formatted)
	}

	periods := map[string]time.Duration{
		"S": time.Second, // Second
		"M": time.Minute, // Minute
		"H": time.Hour,   // Hour
	}

	limit, period := values[0], strings.ToUpper(values[1])

	duration, ok := periods[period]

	if !ok {
		return rate, fmt.Errorf("Incorrect period '%s'", period)
	}

	var (
		p time.Duration
		l int
	)

	p = 1 * duration

	l, err := strconv.Atoi(limit)
	if err != nil {
		return rate, fmt.Errorf("Incorrect limit '%s'", limit)
	}

	return Rate{
		Formatted: formatted,
		Period:    p,
		Limit:     int64(l),
	}, nil
}
<|endoftext|>


<commit_before>package rbac

import (
	"database/sql"
	"fmt"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

type Rbac interface {
	Assign(Role, Permission) (int64, error)
	Unassign(Role, Permission) error
	Check(permission Permission, userId int64) (bool, error)
	Reset(ensure bool)

	Permissions() PermissionManager
	Roles() RoleManager
	Users() UserManager
}

type Config struct {
	Name     string
	Host     string
	Port     int
	Username string
	Password string
}

type rbac struct {
	permissions PermissionManager
	roles       RoleManager
	users       UserManager

	db *sql.DB
}

func New(config *Config) Rbac {
	var rbac = new(rbac)

	rbac.roles = NewRoleManager(rbac)
	rbac.permissions = NewPermissionManager(rbac)
	rbac.users = NewUserManager(rbac)

	var err error
	rbac.db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true", config.Username, config.Password, config.Host, config.Port, config.Name))
	if err != nil {
		log.Fatal(err)
	}

	return rbac
}

// Assign a role to a permission (or vice-verse)
//
// @param mixed $Role
//         Id, Title and Path
// @param mixed $Permission
//         Id, Title and Path
// @return boolean inserted or existing
//
// @todo: Check for valid permissions/roles
// @todo: Implement custom error handler
func (r *rbac) Assign(role Role, permission Permission) (int64, error) {
	var err error
	var roleId int64
	var permissionId int64

	roleId, err = r.Roles().GetRoleId(role)
	if err != nil {
		return 0, err
	}

	permissionId, err = r.permissions.GetPermissionId(permission)
	if err != nil {
		return 0, err
	}

	res, err := r.db.Exec("INSERT INTO rolepermissions (RoleID, PermissionID, AssignmentDate) VALUES(?,?,?)", roleId, permissionId, time.Now().Nanosecond())
	if err != nil {
		return 0, err
	}

	insertId, _ := res.LastInsertId()

	return insertId, nil
}

func (r *rbac) Unassign(role Role, permission Permission) error {
	var err error
	var roleId int64
	var permissionId int64

	roleId, err = r.Roles().GetRoleId(role)
	if err != nil {
		return err
	}

	permissionId, err = r.permissions.GetPermissionId(permission)
	if err != nil {
		return err
	}

	_, err = r.db.Exec("DELETE FROM rolepermissions WHERE RoleId=? AND PermissionID=?", roleId, permissionId)

	if err != nil {
		return err
	}

	return nil
}

func (r rbac) Check(permission Permission, userId int64) (bool, error) {
	if userId == 0 {
		return false, fmt.Errorf("userId cannot be null")
	}

	permissionId, err := r.permissions.GetPermissionId(permission)
	if err != nil {
		return false, err
	}

	if permissionId == 0 {
		return false, fmt.Errorf("permission not found")
	}

	lastPart := `
	ON ( TR.ID = TRel.RoleID)
 							WHERE
 							TUrel.UserID=?
 							AND
 							TPdirect.ID=?
	`
	query := fmt.Sprintf(`SELECT COUNT(*) AS Result
	FROM
		userroles AS TUrel
	JOIN roles AS TRdirect ON (TRdirect.ID=TUrel.RoleID)
	JOIN roles AS TR ON ( TR.Lft BETWEEN TRdirect.Lft AND TRdirect.Rght)
	JOIN
		(permissions AS TPdirect
			JOIN permissions AS TP ON (TPdirect.Lft BETWEEN TP.Lft AND TP.Rght)
			JOIN rolepermissions AS TRel ON (TP.ID=TRel.PermissionID)
		) %s`, lastPart)

	var result int64
	err = r.db.QueryRow(query, userId, permissionId).Scan(&result)
	if err != nil {
		if err != sql.ErrNoRows {
			return false, err
		}
	}

	if result > 0 {
		return true, nil
	}

	return false, nil
}

func (r rbac) Reset(ensure bool) {
	if err := r.roles.ResetAssignments(ensure); err != nil {
		log.Fatal(err)
	}
	if err := r.roles.Reset(ensure); err != nil {
		log.Fatal(err)
	}

	if err := r.permissions.Reset(ensure); err != nil {
		log.Fatal(err)
	}

	if err := r.users.ResetAssignments(ensure); err != nil {
		log.Fatal(err)
	}
}

func (r *rbac) rootId() int64 {
	return 1
}

func (r rbac) Permissions() PermissionManager {
	return r.permissions
}

func (r rbac) Roles() RoleManager {
	return r.roles
}

func (r rbac) Users() UserManager {
	return r.users
}
<commit_msg> Changes to be committed: 	modified:   rbac.go<commit_after>package rbac

import (
	"database/sql"
	"fmt"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

type Rbac interface {

	// Assign a role to a permission (or vice-verse)
	//
	// @param mixed $Role
	//         Id, Title and Path
	// @param mixed $Permission
	//         Id, Title and Path
	// @return boolean inserted or existing
	//
	// @todo: Check for valid permissions/roles
	// @todo: Implement custom error handler
	Assign(Role, Permission) (int64, error)
	Unassign(Role, Permission) error
	Check(permission Permission, userId int64) (bool, error)
	Reset(ensure bool)

	Permissions() PermissionManager
	Roles() RoleManager
	Users() UserManager
}

type Config struct {
	Name     string
	Host     string
	Port     int
	Username string
	Password string
}

type rbac struct {
	permissions PermissionManager
	roles       RoleManager
	users       UserManager

	db *sql.DB
}

func New(config *Config) Rbac {
	var rbac = new(rbac)

	rbac.roles = NewRoleManager(rbac)
	rbac.permissions = NewPermissionManager(rbac)
	rbac.users = NewUserManager(rbac)

	var err error
	rbac.db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true", config.Username, config.Password, config.Host, config.Port, config.Name))
	if err != nil {
		log.Fatal(err)
	}

	return rbac
}

func (r *rbac) Assign(role Role, permission Permission) (int64, error) {
	var err error
	var roleId int64
	var permissionId int64

	roleId, err = r.Roles().GetRoleId(role)
	if err != nil {
		return 0, err
	}

	permissionId, err = r.permissions.GetPermissionId(permission)
	if err != nil {
		return 0, err
	}

	res, err := r.db.Exec("INSERT INTO rolepermissions (RoleID, PermissionID, AssignmentDate) VALUES(?,?,?)", roleId, permissionId, time.Now().Nanosecond())
	if err != nil {
		return 0, err
	}

	insertId, _ := res.LastInsertId()

	return insertId, nil
}

func (r *rbac) Unassign(role Role, permission Permission) error {
	var err error
	var roleId int64
	var permissionId int64

	roleId, err = r.Roles().GetRoleId(role)
	if err != nil {
		return err
	}

	permissionId, err = r.permissions.GetPermissionId(permission)
	if err != nil {
		return err
	}

	_, err = r.db.Exec("DELETE FROM rolepermissions WHERE RoleId=? AND PermissionID=?", roleId, permissionId)

	if err != nil {
		return err
	}

	return nil
}

func (r rbac) Check(permission Permission, userId int64) (bool, error) {
	if userId == 0 {
		return false, fmt.Errorf("userId cannot be null")
	}

	permissionId, err := r.permissions.GetPermissionId(permission)
	if err != nil {
		return false, err
	}

	if permissionId == 0 {
		return false, fmt.Errorf("permission not found")
	}

	lastPart := `
	ON ( TR.ID = TRel.RoleID)
 							WHERE
 							TUrel.UserID=?
 							AND
 							TPdirect.ID=?
	`
	query := fmt.Sprintf(`SELECT COUNT(*) AS Result
	FROM
		userroles AS TUrel
	JOIN roles AS TRdirect ON (TRdirect.ID=TUrel.RoleID)
	JOIN roles AS TR ON ( TR.Lft BETWEEN TRdirect.Lft AND TRdirect.Rght)
	JOIN
		(permissions AS TPdirect
			JOIN permissions AS TP ON (TPdirect.Lft BETWEEN TP.Lft AND TP.Rght)
			JOIN rolepermissions AS TRel ON (TP.ID=TRel.PermissionID)
		) %s`, lastPart)

	var result int64
	err = r.db.QueryRow(query, userId, permissionId).Scan(&result)
	if err != nil {
		if err != sql.ErrNoRows {
			return false, err
		}
	}

	if result > 0 {
		return true, nil
	}

	return false, nil
}

func (r rbac) Reset(ensure bool) {
	if err := r.roles.ResetAssignments(ensure); err != nil {
		log.Fatal(err)
	}
	if err := r.roles.Reset(ensure); err != nil {
		log.Fatal(err)
	}

	if err := r.permissions.Reset(ensure); err != nil {
		log.Fatal(err)
	}

	if err := r.users.ResetAssignments(ensure); err != nil {
		log.Fatal(err)
	}
}

func (r *rbac) rootId() int64 {
	return 1
}

func (r rbac) Permissions() PermissionManager {
	return r.permissions
}

func (r rbac) Roles() RoleManager {
	return r.roles
}

func (r rbac) Users() UserManager {
	return r.users
}
<|endoftext|>