<commit_before>package digitalocean

import (
	"strconv"

	"code.google.com/p/goauth2/oauth"
	"github.com/tarsisazevedo/godo"

	"github.com/tsuru/tsuru/iaas"
)

func init() {
	iaas.RegisterIaasProvider("digitalocean", NewDigitalOceanIaas())
}

type DigitalOceanIaas struct {
	base   iaas.UserDataIaaS
	client *godo.Client
}

func NewDigitalOceanIaas() *DigitalOceanIaas {
	return &DigitalOceanIaas{base: iaas.UserDataIaaS{NamedIaaS: iaas.NamedIaaS{BaseIaaSName: "digitalocean"}}}
}

func (i *DigitalOceanIaas) Auth() error {
	url, err := i.base.GetConfigString("url")
	token, err := i.base.GetConfigString("token")
	if err != nil {
		return err
	}
	t := &oauth.Transport{
		Token: &oauth.Token{AccessToken: token},
	}
	i.client = godo.NewClient(t.Client(), url)
	return nil
}

func (i *DigitalOceanIaas) CreateMachine(params map[string]string) (*iaas.Machine, error) {
	i.Auth()
	createRequest := &godo.DropletCreateRequest{
		Name:   params["name"],
		Region: params["region"],
		Size:   params["size"],
		Image:  params["image"],
	}
	newDroplet, _, err := i.client.Droplets.Create(createRequest)
	if err != nil {
		return nil, err
	}
	droplet := newDroplet.Droplet
	m := &iaas.Machine{
		Address: droplet.Networks.V4[0].IPAddress,
		Id:      strconv.Itoa(droplet.ID),
		Status:  droplet.Status,
	}
	return m, nil
}

func (i *DigitalOceanIaas) DeleteMachine(m *iaas.Machine) error {
	i.Auth()
	machine_id, _ := strconv.Atoi(m.Id)
	_, err := i.client.Droplets.Delete(machine_id)
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>iaas/digitalocean: wait for machine network<commit_after>package digitalocean

import (
	"fmt"
	"strconv"
	"time"

	"code.google.com/p/goauth2/oauth"
	"github.com/tarsisazevedo/godo"

	"github.com/tsuru/tsuru/iaas"
)

func init() {
	iaas.RegisterIaasProvider("digitalocean", NewDigitalOceanIaas())
}

type DigitalOceanIaas struct {
	base   iaas.UserDataIaaS
	client *godo.Client
}

func NewDigitalOceanIaas() *DigitalOceanIaas {
	return &DigitalOceanIaas{base: iaas.UserDataIaaS{NamedIaaS: iaas.NamedIaaS{BaseIaaSName: "digitalocean"}}}
}

func (i *DigitalOceanIaas) Auth() error {
	url, err := i.base.GetConfigString("url")
	token, err := i.base.GetConfigString("token")
	if err != nil {
		return err
	}
	t := &oauth.Transport{
		Token: &oauth.Token{AccessToken: token},
	}
	i.client = godo.NewClient(t.Client(), url)
	return nil
}

func (i *DigitalOceanIaas) CreateMachine(params map[string]string) (*iaas.Machine, error) {
	i.Auth()
	createRequest := &godo.DropletCreateRequest{
		Name:   params["name"],
		Region: params["region"],
		Size:   params["size"],
		Image:  params["image"],
	}
	newDroplet, _, err := i.client.Droplets.Create(createRequest)
	if err != nil {
		return nil, err
	}
	droplet := newDroplet.Droplet
	completed := false
	maxTry := 10
	for !completed && maxTry != 0 {
		d, _, err := i.client.Droplets.Get(droplet.ID)
		if err != nil {
			return nil, err
		}
		if len(d.Droplet.Networks.V4) == 0 {
			maxTry -= 1
			time.Sleep(5 * time.Second)
			continue
		}
		completed = true
		droplet = d.Droplet
	}
	if !completed {
		return nil, fmt.Errorf("Machine created but without network")
	}
	m := &iaas.Machine{
		Address: droplet.Networks.V4[0].IPAddress,
		Id:      strconv.Itoa(droplet.ID),
		Status:  droplet.Status,
	}
	return m, nil
}

func (i *DigitalOceanIaas) DeleteMachine(m *iaas.Machine) error {
	i.Auth()
	machine_id, _ := strconv.Atoi(m.Id)
	_, err := i.client.Droplets.Delete(machine_id)
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>/**
 * Copyright 2015 Comcast Cable Communications Management, LLC
 *
 * 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 jtl

import (
	"errors"
	"strings"

	. "github.com/Comcast/eel/eel/util"
)

type (
	HttpPublisher struct {
		endpoint string
		path     string
		payload  string
		protocol string
		api      string
		verb     string
		auth     map[string]string
		headers  map[string]string
		event    *JDoc
		ctx      Context
	}
)

// NewHttpPublisher creates a new HTTP publisher.
func NewHttpPublisher(ctx Context) EventPublisher {
	hp := new(HttpPublisher)
	hp.ctx = ctx
	hp.protocol = "http"
	hp.api = "http"
	ctx.AddLogValue("destination", "main_http")
	return hp
}

func (p *HttpPublisher) Publish() (string, error) {
	if p.endpoint == "" {
		return "", errors.New("missing endpoint")
	}
	if p.verb == "" {
		return "", errors.New("missing verb")
	}
	resp, _, err := GetRetrier(p.ctx).RetryEndpoint(p.ctx, p.GetUrl(), p.payload, p.verb, p.headers, nil)
	return resp, err
}

func (p *HttpPublisher) GetUrl() string {
	if p.endpoint == "" {
		return p.path
	}
	if p.path == "" {
		return p.endpoint
	}
	if strings.HasSuffix(p.endpoint, "/") && strings.HasPrefix(p.path, "/") {
		return p.endpoint + p.path[1:]
	} else if strings.HasSuffix(p.endpoint, "/") || strings.HasPrefix(p.path, "/") {
		return p.endpoint + p.path
	}
	return p.endpoint + "/" + p.path
}

func (p *HttpPublisher) GetErrors() []error {
	return GetErrors(p.ctx)
}

func (p *HttpPublisher) SetPath(path string) {
	p.path = path
}

func (p *HttpPublisher) GetPath() string {
	return p.path
}

func (p *HttpPublisher) SetEndpoint(endpoint string) {
	p.endpoint = endpoint
}

func (p *HttpPublisher) GetEndpoint() string {
	return p.endpoint
}

func (p *HttpPublisher) SetPayload(payload string) {
	p.payload = payload
}

func (p *HttpPublisher) GetPayload() string {
	return p.payload
}

func (p *HttpPublisher) SetVerb(verb string) {
	p.verb = verb
}

func (p *HttpPublisher) GetVerb() string {
	return p.verb
}

func (p *HttpPublisher) GetProtocol() string {
	return p.protocol
}

func (p *HttpPublisher) GetApi() string {
	return p.api
}

func (p *HttpPublisher) SetAuthInfo(auth map[string]string) {
	p.auth = auth
}

func (p *HttpPublisher) SetHeaders(headers map[string]string) {
	p.headers = headers
}

func (p *HttpPublisher) GetHeaders() map[string]string {
	return p.headers
}

func (p *HttpPublisher) SetPayloadParsed(event *JDoc) {
	p.event = event
}

func (p *HttpPublisher) GetPayloadParsed() *JDoc {
	return p.event
}
<commit_msg>XRULES-4153 eel publish to return error when service response is not 200<commit_after>/**
 * Copyright 2015 Comcast Cable Communications Management, LLC
 *
 * 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 jtl

import (
	"errors"
	"strings"

	. "github.com/Comcast/eel/eel/util"
)

type (
	HttpPublisher struct {
		endpoint string
		path     string
		payload  string
		protocol string
		api      string
		verb     string
		auth     map[string]string
		headers  map[string]string
		event    *JDoc
		ctx      Context
	}
)

// NewHttpPublisher creates a new HTTP publisher.
func NewHttpPublisher(ctx Context) EventPublisher {
	hp := new(HttpPublisher)
	hp.ctx = ctx
	hp.protocol = "http"
	hp.api = "http"
	ctx.AddLogValue("destination", "main_http")
	return hp
}

func (p *HttpPublisher) Publish() (string, error) {
	if p.endpoint == "" {
		return "", errors.New("missing endpoint")
	}
	if p.verb == "" {
		return "", errors.New("missing verb")
	}
	resp, status, err := GetRetrier(p.ctx).RetryEndpoint(p.ctx, p.GetUrl(), p.payload, p.verb, p.headers, nil)
	if err != nil {
		return resp, err
	}
	if status < 200 || status >= 300 {
		return resp, NetworkError{p.endpoint, "endpoint returned error", status}
	}
	return resp, nil
}

func (p *HttpPublisher) GetUrl() string {
	if p.endpoint == "" {
		return p.path
	}
	if p.path == "" {
		return p.endpoint
	}
	if strings.HasSuffix(p.endpoint, "/") && strings.HasPrefix(p.path, "/") {
		return p.endpoint + p.path[1:]
	} else if strings.HasSuffix(p.endpoint, "/") || strings.HasPrefix(p.path, "/") {
		return p.endpoint + p.path
	}
	return p.endpoint + "/" + p.path
}

func (p *HttpPublisher) GetErrors() []error {
	return GetErrors(p.ctx)
}

func (p *HttpPublisher) SetPath(path string) {
	p.path = path
}

func (p *HttpPublisher) GetPath() string {
	return p.path
}

func (p *HttpPublisher) SetEndpoint(endpoint string) {
	p.endpoint = endpoint
}

func (p *HttpPublisher) GetEndpoint() string {
	return p.endpoint
}

func (p *HttpPublisher) SetPayload(payload string) {
	p.payload = payload
}

func (p *HttpPublisher) GetPayload() string {
	return p.payload
}

func (p *HttpPublisher) SetVerb(verb string) {
	p.verb = verb
}

func (p *HttpPublisher) GetVerb() string {
	return p.verb
}

func (p *HttpPublisher) GetProtocol() string {
	return p.protocol
}

func (p *HttpPublisher) GetApi() string {
	return p.api
}

func (p *HttpPublisher) SetAuthInfo(auth map[string]string) {
	p.auth = auth
}

func (p *HttpPublisher) SetHeaders(headers map[string]string) {
	p.headers = headers
}

func (p *HttpPublisher) GetHeaders() map[string]string {
	return p.headers
}

func (p *HttpPublisher) SetPayloadParsed(event *JDoc) {
	p.event = event
}

func (p *HttpPublisher) GetPayloadParsed() *JDoc {
	return p.event
}
<|endoftext|>


<commit_before>package memory

import (
	"sync"
	"time"

	"github.com/grafana/metrictank/idx"
	"github.com/grafana/metrictank/schema"
)

type WriteQueue struct {
	sync.RWMutex
	archives    map[schema.MKey]*idx.Archive
	shutdown    chan struct{}
	done        chan struct{}
	maxBuffered int
	maxDelay    time.Duration
	flushed     chan struct{}

	idx *UnpartitionedMemoryIdx
}

// NewWriteQueue creates a new writeQueue that will add archives to the passed UnpartitionedMemoryIdx
// in batches
func NewWriteQueue(index *UnpartitionedMemoryIdx, maxDelay time.Duration, maxBuffered int) *WriteQueue {
	wq := &WriteQueue{
		archives:    make(map[schema.MKey]*idx.Archive),
		shutdown:    make(chan struct{}),
		done:        make(chan struct{}),
		maxBuffered: maxBuffered,
		maxDelay:    maxDelay,
		flushed:     make(chan struct{}, 1),
		idx:         index,
	}
	go wq.loop()
	return wq
}

func (wq *WriteQueue) Stop() {
	close(wq.shutdown)
	<-wq.done
}

func (wq *WriteQueue) Queue(archive *idx.Archive) {
	wq.Lock()
	wq.archives[archive.Id] = archive
	if len(wq.archives) >= wq.maxBuffered {
		wq.flush()
	}
	wq.Unlock()
}

func (wq *WriteQueue) Get(id schema.MKey) (*idx.Archive, bool) {
	wq.RLock()
	a, ok := wq.archives[id]
	wq.RUnlock()
	return a, ok
}

// flush adds the buffered archives to the memoryIdx.
// callers need to acquire a writeLock before calling this function.
func (wq *WriteQueue) flush() {
	if len(wq.archives) == 0 {
		// non blocking write to the flushed chan.
		// if we cant write to the flushed chan it means there is a previous flush
		// signal that hasnt been processed.  In that case, we dont need to send another one.
		select {
		case wq.flushed <- struct{}{}:
		default:
		}
		return
	}
	wq.idx.Lock()
	for _, archive := range wq.archives {
		wq.idx.add(archive)
	}
	wq.idx.Unlock()
	wq.archives = make(map[schema.MKey]*idx.Archive)
	wq.flushed <- struct{}{}
}

func (wq *WriteQueue) loop() {
	defer close(wq.done)
	timer := time.NewTimer(wq.maxDelay)
	for {
		select {
		case <-wq.flushed:
			if !timer.Stop() {
				<-timer.C
			}
			timer.Reset(wq.maxDelay)
		case <-timer.C:
			wq.Lock()
			wq.flush()
			wq.Unlock()
			timer.Reset(wq.maxDelay)
		case <-wq.shutdown:
			wq.Lock()
			wq.flush()
			wq.Unlock()
			if !timer.Stop() {
				<-timer.C
			}
			return
		}
	}
}
<commit_msg>golang best practice: show mutex along with fields it protects<commit_after>package memory

import (
	"sync"
	"time"

	"github.com/grafana/metrictank/idx"
	"github.com/grafana/metrictank/schema"
)

type WriteQueue struct {
	shutdown    chan struct{}
	done        chan struct{}
	maxBuffered int
	maxDelay    time.Duration
	flushed     chan struct{}

	archives map[schema.MKey]*idx.Archive
	sync.RWMutex

	idx *UnpartitionedMemoryIdx
}

// NewWriteQueue creates a new writeQueue that will add archives to the passed UnpartitionedMemoryIdx
// in batches
func NewWriteQueue(index *UnpartitionedMemoryIdx, maxDelay time.Duration, maxBuffered int) *WriteQueue {
	wq := &WriteQueue{
		archives:    make(map[schema.MKey]*idx.Archive),
		shutdown:    make(chan struct{}),
		done:        make(chan struct{}),
		maxBuffered: maxBuffered,
		maxDelay:    maxDelay,
		flushed:     make(chan struct{}, 1),
		idx:         index,
	}
	go wq.loop()
	return wq
}

func (wq *WriteQueue) Stop() {
	close(wq.shutdown)
	<-wq.done
}

func (wq *WriteQueue) Queue(archive *idx.Archive) {
	wq.Lock()
	wq.archives[archive.Id] = archive
	if len(wq.archives) >= wq.maxBuffered {
		wq.flush()
	}
	wq.Unlock()
}

func (wq *WriteQueue) Get(id schema.MKey) (*idx.Archive, bool) {
	wq.RLock()
	a, ok := wq.archives[id]
	wq.RUnlock()
	return a, ok
}

// flush adds the buffered archives to the memoryIdx.
// callers need to acquire a writeLock before calling this function.
func (wq *WriteQueue) flush() {
	if len(wq.archives) == 0 {
		// non blocking write to the flushed chan.
		// if we cant write to the flushed chan it means there is a previous flush
		// signal that hasnt been processed.  In that case, we dont need to send another one.
		select {
		case wq.flushed <- struct{}{}:
		default:
		}
		return
	}
	wq.idx.Lock()
	for _, archive := range wq.archives {
		wq.idx.add(archive)
	}
	wq.idx.Unlock()
	wq.archives = make(map[schema.MKey]*idx.Archive)
	wq.flushed <- struct{}{}
}

func (wq *WriteQueue) loop() {
	defer close(wq.done)
	timer := time.NewTimer(wq.maxDelay)
	for {
		select {
		case <-wq.flushed:
			if !timer.Stop() {
				<-timer.C
			}
			timer.Reset(wq.maxDelay)
		case <-timer.C:
			wq.Lock()
			wq.flush()
			wq.Unlock()
			timer.Reset(wq.maxDelay)
		case <-wq.shutdown:
			wq.Lock()
			wq.flush()
			wq.Unlock()
			if !timer.Stop() {
				<-timer.C
			}
			return
		}
	}
}
<|endoftext|>


<commit_before>// a simple command-line tool to run the benchmark client.
package main

import (
	"flag"
	"os"

	"veyron.io/veyron/veyron/runtimes/google/ipc/benchmarks"

	"veyron.io/veyron/veyron2/rt"
)

var (
	server      = flag.String("server", "", "object name of the server to connect to")
	count       = flag.Int("count", 1, "number of RPCs to send")
	chunkCount  = flag.Int("chunk_count", 0, "number of stream chunks to send")
	payloadSize = flag.Int("payload_size", 32, "the size of the payload")
)

func main() {
	runtime, err := rt.New()
	if err != nil {
		panic(err)
	}
	defer runtime.Cleanup()

	ctx := runtime.NewContext()
	if *chunkCount == 0 {
		benchmarks.CallEcho(ctx, *server, *count, *payloadSize, os.Stdout)
	} else {
		benchmarks.CallEchoStream(runtime, *server, *count, *chunkCount, *payloadSize, os.Stdout)
	}
}
<commit_msg>Temporarily fix broken build in bmclient/main.go   o I will change bmclient to add new benchmarks introduced by cl/8133 soon.<commit_after>// a simple command-line tool to run the benchmark client.
package main

import (
	"flag"

	"veyron.io/veyron/veyron2/rt"
)

var (
	server      = flag.String("server", "", "object name of the server to connect to")
	count       = flag.Int("count", 1, "number of RPCs to send")
	chunkCount  = flag.Int("chunk_count", 0, "number of stream chunks to send")
	payloadSize = flag.Int("payload_size", 32, "the size of the payload")
)

func main() {
	runtime, err := rt.New()
	if err != nil {
		panic(err)
	}
	defer runtime.Cleanup()

	// TODO(jhahn): Fix this.
	/*
		ctx := runtime.NewContext()
		if *chunkCount == 0 {
			benchmarks.CallEcho(ctx, *server, *count, *payloadSize, os.Stdout)
		} else {
			benchmarks.CallEchoStream(runtime, *server, *count, *chunkCount, *payloadSize, os.Stdout)
		}
	*/
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"runtime"
	"strconv"
	"sync"
	"time"

	"github.com/siesta/redisence"
)

func main() {
	if os.Getenv("GOMAXPROCS") == "" {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	session, err := redisence.New("localhost:6379", 10, time.Second*1)
	if err != nil {
		panic(err)
	}

	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		pingdom(session, 1, 30000)
		wg.Done()
	}()

	// request data from database
	go func() {
		statuko(session, 1, 30000)
		wg.Done()
	}()

	wg.Wait()
}

func pingdom(session *redisence.Session, start, end int) {
	throttleCount := 1500
	req := make([]string, throttleCount)
	count := 0
	for i := start; i <= end; i++ {
		req[count] = strconv.Itoa(i)
		if count == throttleCount-1 {
			err := session.Online(req...)
			if err != nil {
				fmt.Println(err)
			}

			count = 0
		}
		count++
	}
}

func statuko(session *redisence.Session, start, end int) {
	throttleCount := 1500
	req := make([]string, throttleCount)
	count := 0
	for i := start; i <= end; i++ {
		req[count] = strconv.Itoa(i)
		if count == throttleCount-1 {
			_, err := session.Status(req...)
			if err != nil {
				fmt.Println(err)
			}

			count = 0
		}
		count++
	}
}
<commit_msg>Presence: refactor creating backend operation<commit_after>package main

import (
	"fmt"
	"os"
	"runtime"
	"strconv"
	"sync"
	"time"

	"github.com/siesta/redisence"
)

func main() {
	if os.Getenv("GOMAXPROCS") == "" {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	backend, err := redisence.New("localhost:6379", 10, time.Second*1)
	if err != nil {
		panic(err)
	}

	session, err := redisence.New(backend)
	if err != nil {
		panic(err)
	}

	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		pingdom(session, 1, 30000)
		wg.Done()
	}()

	// request data from database
	go func() {
		statuko(session, 1, 30000)
		wg.Done()
	}()

	wg.Wait()
}

func pingdom(session *redisence.Session, start, end int) {
	throttleCount := 1500
	req := make([]string, throttleCount)
	count := 0
	for i := start; i <= end; i++ {
		req[count] = strconv.Itoa(i)
		if count == throttleCount-1 {
			err := session.Online(req...)
			if err != nil {
				fmt.Println(err)
			}

			count = 0
		}
		count++
	}
}

func statuko(session *redisence.Session, start, end int) {
	throttleCount := 1500
	req := make([]string, throttleCount)
	count := 0
	for i := start; i <= end; i++ {
		req[count] = strconv.Itoa(i)
		if count == throttleCount-1 {
			_, err := session.Status(req...)
			if err != nil {
				fmt.Println(err)
			}

			count = 0
		}
		count++
	}
}
<|endoftext|>


<commit_before>package buffer

import (
	"regexp"

	"github.com/zyedidia/micro/v2/internal/util"
)

func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
	lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
	if start.Y > b.LinesNum()-1 {
		start.X = lastcn - 1
	}
	if end.Y > b.LinesNum()-1 {
		end.X = lastcn
	}
	start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
	end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)

	if start.GreaterThan(end) {
		start, end = end, start
	}

	for i := start.Y; i <= end.Y; i++ {
		l := b.LineBytes(i)
		charpos := 0

		if i == start.Y && start.Y == end.Y {
			nchars := util.CharacterCount(l)
			start.X = util.Clamp(start.X, 0, nchars)
			end.X = util.Clamp(end.X, 0, nchars)
			l = util.SliceStart(l, end.X)
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == start.Y {
			nchars := util.CharacterCount(l)
			start.X = util.Clamp(start.X, 0, nchars)
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == end.Y {
			nchars := util.CharacterCount(l)
			end.X = util.Clamp(end.X, 0, nchars)
			l = util.SliceStart(l, end.X)
		}

		match := r.FindIndex(l)

		if match != nil {
			start := Loc{charpos + util.RunePos(l, match[0]), i}
			end := Loc{charpos + util.RunePos(l, match[1]), i}
			return [2]Loc{start, end}, true
		}
	}
	return [2]Loc{}, false
}

func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
	lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
	if start.Y > b.LinesNum()-1 {
		start.X = lastcn - 1
	}
	if end.Y > b.LinesNum()-1 {
		end.X = lastcn
	}
	start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
	end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)

	if start.GreaterThan(end) {
		start, end = end, start
	}

	for i := end.Y; i >= start.Y; i-- {
		l := b.LineBytes(i)
		charpos := 0

		if i == start.Y && start.Y == end.Y {
			nchars := util.CharacterCount(l)
			start.X = util.Clamp(start.X, 0, nchars)
			end.X = util.Clamp(end.X, 0, nchars)
			l = util.SliceStart(l, end.X)
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == start.Y {
			nchars := util.CharacterCount(l)
			start.X = util.Clamp(start.X, 0, nchars)
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == end.Y {
			nchars := util.CharacterCount(l)
			end.X = util.Clamp(end.X, 0, nchars)
			l = util.SliceStart(l, end.X)
		}

		match := r.FindIndex(l)

		if match != nil {
			start := Loc{charpos + util.RunePos(l, match[0]), i}
			end := Loc{charpos + util.RunePos(l, match[1]), i}
			return [2]Loc{start, end}, true
		}
	}
	return [2]Loc{}, false
}

// FindNext finds the next occurrence of a given string in the buffer
// It returns the start and end location of the match (if found) and
// a boolean indicating if it was found
// May also return an error if the search regex is invalid
func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bool) ([2]Loc, bool, error) {
	if s == "" {
		return [2]Loc{}, false, nil
	}

	var r *regexp.Regexp
	var err error

	if !useRegex {
		s = regexp.QuoteMeta(s)
	}

	if b.Settings["ignorecase"].(bool) {
		r, err = regexp.Compile("(?i)" + s)
	} else {
		r, err = regexp.Compile(s)
	}

	if err != nil {
		return [2]Loc{}, false, err
	}

	var found bool
	var l [2]Loc
	if down {
		l, found = b.findDown(r, from, end)
		if !found {
			l, found = b.findDown(r, start, end)
		}
	} else {
		l, found = b.findUp(r, from, start)
		if !found {
			l, found = b.findUp(r, end, start)
		}
	}
	return l, found, nil
}

// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
// and returns the number of replacements made and the number of runes
// added or removed on the last line of the range
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) {
	if start.GreaterThan(end) {
		start, end = end, start
	}

	netrunes := 0

	found := 0
	var deltas []Delta
	for i := start.Y; i <= end.Y; i++ {
		l := b.lines[i].data
		charpos := 0

		if start.Y == end.Y && i == start.Y {
			l = util.SliceStart(l, end.X)
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == start.Y {
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == end.Y {
			l = util.SliceStart(l, end.X)
		}
		newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
			result := []byte{}
			for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
				result = search.Expand(result, replace, in, submatches)
			}
			found++
			if i == end.Y {
				netrunes += util.CharacterCount(result) - util.CharacterCount(in)
			}
			return result
		})

		from := Loc{charpos, i}
		to := Loc{charpos + util.CharacterCount(l), i}

		deltas = append(deltas, Delta{newText, from, to})
	}
	b.MultipleReplace(deltas)

	return found, netrunes
}
<commit_msg>Search the last match on line when search back (#2156)<commit_after>package buffer

import (
	"regexp"

	"github.com/zyedidia/micro/v2/internal/util"
)

func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
	lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
	if start.Y > b.LinesNum()-1 {
		start.X = lastcn - 1
	}
	if end.Y > b.LinesNum()-1 {
		end.X = lastcn
	}
	start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
	end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)

	if start.GreaterThan(end) {
		start, end = end, start
	}

	for i := start.Y; i <= end.Y; i++ {
		l := b.LineBytes(i)
		charpos := 0

		if i == start.Y && start.Y == end.Y {
			nchars := util.CharacterCount(l)
			start.X = util.Clamp(start.X, 0, nchars)
			end.X = util.Clamp(end.X, 0, nchars)
			l = util.SliceStart(l, end.X)
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == start.Y {
			nchars := util.CharacterCount(l)
			start.X = util.Clamp(start.X, 0, nchars)
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == end.Y {
			nchars := util.CharacterCount(l)
			end.X = util.Clamp(end.X, 0, nchars)
			l = util.SliceStart(l, end.X)
		}

		match := r.FindIndex(l)

		if match != nil {
			start := Loc{charpos + util.RunePos(l, match[0]), i}
			end := Loc{charpos + util.RunePos(l, match[1]), i}
			return [2]Loc{start, end}, true
		}
	}
	return [2]Loc{}, false
}

func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
	lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
	if start.Y > b.LinesNum()-1 {
		start.X = lastcn - 1
	}
	if end.Y > b.LinesNum()-1 {
		end.X = lastcn
	}
	start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
	end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)

	if start.GreaterThan(end) {
		start, end = end, start
	}

	for i := end.Y; i >= start.Y; i-- {
		l := b.LineBytes(i)
		charpos := 0

		if i == start.Y && start.Y == end.Y {
			nchars := util.CharacterCount(l)
			start.X = util.Clamp(start.X, 0, nchars)
			end.X = util.Clamp(end.X, 0, nchars)
			l = util.SliceStart(l, end.X)
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == start.Y {
			nchars := util.CharacterCount(l)
			start.X = util.Clamp(start.X, 0, nchars)
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == end.Y {
			nchars := util.CharacterCount(l)
			end.X = util.Clamp(end.X, 0, nchars)
			l = util.SliceStart(l, end.X)
		}

		all_matches := r.FindAllIndex(l, -1)

		if all_matches != nil {
			match := all_matches[len(all_matches)-1]
			start := Loc{charpos + util.RunePos(l, match[0]), i}
			end := Loc{charpos + util.RunePos(l, match[1]), i}
			return [2]Loc{start, end}, true
		}
	}
	return [2]Loc{}, false
}

// FindNext finds the next occurrence of a given string in the buffer
// It returns the start and end location of the match (if found) and
// a boolean indicating if it was found
// May also return an error if the search regex is invalid
func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bool) ([2]Loc, bool, error) {
	if s == "" {
		return [2]Loc{}, false, nil
	}

	var r *regexp.Regexp
	var err error

	if !useRegex {
		s = regexp.QuoteMeta(s)
	}

	if b.Settings["ignorecase"].(bool) {
		r, err = regexp.Compile("(?i)" + s)
	} else {
		r, err = regexp.Compile(s)
	}

	if err != nil {
		return [2]Loc{}, false, err
	}

	var found bool
	var l [2]Loc
	if down {
		l, found = b.findDown(r, from, end)
		if !found {
			l, found = b.findDown(r, start, end)
		}
	} else {
		l, found = b.findUp(r, from, start)
		if !found {
			l, found = b.findUp(r, end, start)
		}
	}
	return l, found, nil
}

// ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
// and returns the number of replacements made and the number of runes
// added or removed on the last line of the range
func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) {
	if start.GreaterThan(end) {
		start, end = end, start
	}

	netrunes := 0

	found := 0
	var deltas []Delta
	for i := start.Y; i <= end.Y; i++ {
		l := b.lines[i].data
		charpos := 0

		if start.Y == end.Y && i == start.Y {
			l = util.SliceStart(l, end.X)
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == start.Y {
			l = util.SliceEnd(l, start.X)
			charpos = start.X
		} else if i == end.Y {
			l = util.SliceStart(l, end.X)
		}
		newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
			result := []byte{}
			for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
				result = search.Expand(result, replace, in, submatches)
			}
			found++
			if i == end.Y {
				netrunes += util.CharacterCount(result) - util.CharacterCount(in)
			}
			return result
		})

		from := Loc{charpos, i}
		to := Loc{charpos + util.CharacterCount(l), i}

		deltas = append(deltas, Delta{newText, from, to})
	}
	b.MultipleReplace(deltas)

	return found, netrunes
}
<|endoftext|>


<commit_before>package cache

import (
	"context"
	"io"
	"sync"

	"github.com/restic/restic/internal/debug"
	"github.com/restic/restic/internal/restic"
)

// Backend wraps a restic.Backend and adds a cache.
type Backend struct {
	restic.Backend
	*Cache

	// inProgress contains the handle for all files that are currently
	// downloaded. The channel in the value is closed as soon as the download
	// is finished.
	inProgressMutex sync.Mutex
	inProgress      map[restic.Handle]chan struct{}
}

// ensure cachedBackend implements restic.Backend
var _ restic.Backend = &Backend{}

func newBackend(be restic.Backend, c *Cache) *Backend {
	return &Backend{
		Backend:    be,
		Cache:      c,
		inProgress: make(map[restic.Handle]chan struct{}),
	}
}

// Remove deletes a file from the backend and the cache if it has been cached.
func (b *Backend) Remove(ctx context.Context, h restic.Handle) error {
	debug.Log("cache Remove(%v)", h)
	err := b.Backend.Remove(ctx, h)
	if err != nil {
		return err
	}

	return b.Cache.Remove(h)
}

var autoCacheTypes = map[restic.FileType]struct{}{
	restic.IndexFile:    {},
	restic.SnapshotFile: {},
}

// Save stores a new file in the backend and the cache.
func (b *Backend) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error {
	if _, ok := autoCacheTypes[h.Type]; !ok {
		return b.Backend.Save(ctx, h, rd)
	}

	debug.Log("Save(%v): auto-store in the cache", h)

	// make sure the reader is at the start
	err := rd.Rewind()
	if err != nil {
		return err
	}

	// first, save in the backend
	err = b.Backend.Save(ctx, h, rd)
	if err != nil {
		return err
	}

	// next, save in the cache
	err = rd.Rewind()
	if err != nil {
		return err
	}

	err = b.Cache.Save(h, rd)
	if err != nil {
		debug.Log("unable to save %v to cache: %v", h, err)
		_ = b.Cache.Remove(h)
		return nil
	}

	return nil
}

var autoCacheFiles = map[restic.FileType]bool{
	restic.IndexFile:    true,
	restic.SnapshotFile: true,
}

func (b *Backend) cacheFile(ctx context.Context, h restic.Handle) error {
	finish := make(chan struct{})

	b.inProgressMutex.Lock()
	other, alreadyDownloading := b.inProgress[h]
	if !alreadyDownloading {
		b.inProgress[h] = finish
	}
	b.inProgressMutex.Unlock()

	if alreadyDownloading {
		debug.Log("readahead %v is already performed by somebody else, delegating...", h)
		<-other
		debug.Log("download %v finished", h)
		return nil
	}

	err := b.Backend.Load(ctx, h, 0, 0, func(rd io.Reader) error {
		return b.Cache.Save(h, rd)
	})
	if err != nil {
		// try to remove from the cache, ignore errors
		_ = b.Cache.Remove(h)
	}

	// signal other waiting goroutines that the file may now be cached
	close(finish)

	// remove the finish channel from the map
	b.inProgressMutex.Lock()
	delete(b.inProgress, h)
	b.inProgressMutex.Unlock()

	return err
}

// loadFromCacheOrDelegate will try to load the file from the cache, and fall
// back to the backend if that fails.
func (b *Backend) loadFromCacheOrDelegate(ctx context.Context, h restic.Handle, length int, offset int64, consumer func(rd io.Reader) error) error {
	rd, err := b.Cache.Load(h, length, offset)
	if err != nil {
		debug.Log("error caching %v: %v, falling back to backend", h, err)
		return b.Backend.Load(ctx, h, length, offset, consumer)
	}

	err = consumer(rd)
	if err != nil {
		_ = rd.Close() // ignore secondary errors
		return err
	}
	return rd.Close()
}

// Load loads a file from the cache or the backend.
func (b *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64, consumer func(rd io.Reader) error) error {
	b.inProgressMutex.Lock()
	waitForFinish, inProgress := b.inProgress[h]
	b.inProgressMutex.Unlock()

	if inProgress {
		debug.Log("downloading %v is already in progress, waiting for finish", h)
		<-waitForFinish
		debug.Log("downloading %v finished", h)
	}

	if b.Cache.Has(h) {
		debug.Log("Load(%v, %v, %v) from cache", h, length, offset)
		rd, err := b.Cache.Load(h, length, offset)
		if err == nil {
			err = consumer(rd)
			if err != nil {
				rd.Close() // ignore secondary errors
				return err
			}
			return rd.Close()
		}
		debug.Log("error loading %v from cache: %v", h, err)
	}

	// partial file requested
	if offset != 0 || length != 0 {
		if b.Cache.PerformReadahead(h) {
			debug.Log("performing readahead for %v", h)

			err := b.cacheFile(ctx, h)
			if err == nil {
				return b.loadFromCacheOrDelegate(ctx, h, length, offset, consumer)
			}

			debug.Log("error caching %v: %v", h, err)
		}

		debug.Log("Load(%v, %v, %v): partial file requested, delegating to backend", h, length, offset)
		return b.Backend.Load(ctx, h, length, offset, consumer)
	}

	// if we don't automatically cache this file type, fall back to the backend
	if _, ok := autoCacheFiles[h.Type]; !ok {
		debug.Log("Load(%v, %v, %v): delegating to backend", h, length, offset)
		return b.Backend.Load(ctx, h, length, offset, consumer)
	}

	debug.Log("auto-store %v in the cache", h)
	err := b.cacheFile(ctx, h)
	if err == nil {
		return b.loadFromCacheOrDelegate(ctx, h, length, offset, consumer)
	}

	debug.Log("error caching %v: %v, falling back to backend", h, err)
	return b.Backend.Load(ctx, h, length, offset, consumer)
}

// Stat tests whether the backend has a file. If it does not exist but still
// exists in the cache, it is removed from the cache.
func (b *Backend) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) {
	debug.Log("cache Stat(%v)", h)

	fi, err := b.Backend.Stat(ctx, h)
	if err != nil {
		if b.Backend.IsNotExist(err) {
			// try to remove from the cache, ignore errors
			_ = b.Cache.Remove(h)
		}

		return fi, err
	}

	return fi, err
}

// IsNotExist returns true if the error is caused by a non-existing file.
func (b *Backend) IsNotExist(err error) bool {
	return b.Backend.IsNotExist(err)
}
<commit_msg>cache: Recheck before downloading<commit_after>package cache

import (
	"context"
	"io"
	"sync"

	"github.com/restic/restic/internal/debug"
	"github.com/restic/restic/internal/restic"
)

// Backend wraps a restic.Backend and adds a cache.
type Backend struct {
	restic.Backend
	*Cache

	// inProgress contains the handle for all files that are currently
	// downloaded. The channel in the value is closed as soon as the download
	// is finished.
	inProgressMutex sync.Mutex
	inProgress      map[restic.Handle]chan struct{}
}

// ensure cachedBackend implements restic.Backend
var _ restic.Backend = &Backend{}

func newBackend(be restic.Backend, c *Cache) *Backend {
	return &Backend{
		Backend:    be,
		Cache:      c,
		inProgress: make(map[restic.Handle]chan struct{}),
	}
}

// Remove deletes a file from the backend and the cache if it has been cached.
func (b *Backend) Remove(ctx context.Context, h restic.Handle) error {
	debug.Log("cache Remove(%v)", h)
	err := b.Backend.Remove(ctx, h)
	if err != nil {
		return err
	}

	return b.Cache.Remove(h)
}

var autoCacheTypes = map[restic.FileType]struct{}{
	restic.IndexFile:    {},
	restic.SnapshotFile: {},
}

// Save stores a new file in the backend and the cache.
func (b *Backend) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error {
	if _, ok := autoCacheTypes[h.Type]; !ok {
		return b.Backend.Save(ctx, h, rd)
	}

	debug.Log("Save(%v): auto-store in the cache", h)

	// make sure the reader is at the start
	err := rd.Rewind()
	if err != nil {
		return err
	}

	// first, save in the backend
	err = b.Backend.Save(ctx, h, rd)
	if err != nil {
		return err
	}

	// next, save in the cache
	err = rd.Rewind()
	if err != nil {
		return err
	}

	err = b.Cache.Save(h, rd)
	if err != nil {
		debug.Log("unable to save %v to cache: %v", h, err)
		_ = b.Cache.Remove(h)
		return nil
	}

	return nil
}

var autoCacheFiles = map[restic.FileType]bool{
	restic.IndexFile:    true,
	restic.SnapshotFile: true,
}

func (b *Backend) cacheFile(ctx context.Context, h restic.Handle) error {
	finish := make(chan struct{})

	b.inProgressMutex.Lock()
	other, alreadyDownloading := b.inProgress[h]
	if !alreadyDownloading {
		b.inProgress[h] = finish
	}
	b.inProgressMutex.Unlock()

	if alreadyDownloading {
		debug.Log("readahead %v is already performed by somebody else, delegating...", h)
		<-other
		debug.Log("download %v finished", h)
		return nil
	}

	// test again, maybe the file was cached in the meantime
	if b.Cache.Has(h) {
		return nil
	}

	err := b.Backend.Load(ctx, h, 0, 0, func(rd io.Reader) error {
		return b.Cache.Save(h, rd)
	})
	if err != nil {
		// try to remove from the cache, ignore errors
		_ = b.Cache.Remove(h)
	}

	// signal other waiting goroutines that the file may now be cached
	close(finish)

	// remove the finish channel from the map
	b.inProgressMutex.Lock()
	delete(b.inProgress, h)
	b.inProgressMutex.Unlock()

	return nil
}

// loadFromCacheOrDelegate will try to load the file from the cache, and fall
// back to the backend if that fails.
func (b *Backend) loadFromCacheOrDelegate(ctx context.Context, h restic.Handle, length int, offset int64, consumer func(rd io.Reader) error) error {
	rd, err := b.Cache.Load(h, length, offset)
	if err != nil {
		debug.Log("error caching %v: %v, falling back to backend", h, err)
		return b.Backend.Load(ctx, h, length, offset, consumer)
	}

	err = consumer(rd)
	if err != nil {
		_ = rd.Close() // ignore secondary errors
		return err
	}
	return rd.Close()
}

// Load loads a file from the cache or the backend.
func (b *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64, consumer func(rd io.Reader) error) error {
	b.inProgressMutex.Lock()
	waitForFinish, inProgress := b.inProgress[h]
	b.inProgressMutex.Unlock()

	if inProgress {
		debug.Log("downloading %v is already in progress, waiting for finish", h)
		<-waitForFinish
		debug.Log("downloading %v finished", h)
	}

	if b.Cache.Has(h) {
		debug.Log("Load(%v, %v, %v) from cache", h, length, offset)
		rd, err := b.Cache.Load(h, length, offset)
		if err == nil {
			err = consumer(rd)
			if err != nil {
				rd.Close() // ignore secondary errors
				return err
			}
			return rd.Close()
		}
		debug.Log("error loading %v from cache: %v", h, err)
	}

	// partial file requested
	if offset != 0 || length != 0 {
		if b.Cache.PerformReadahead(h) {
			debug.Log("performing readahead for %v", h)

			err := b.cacheFile(ctx, h)
			if err == nil {
				return b.loadFromCacheOrDelegate(ctx, h, length, offset, consumer)
			}

			debug.Log("error caching %v: %v", h, err)
		}

		debug.Log("Load(%v, %v, %v): partial file requested, delegating to backend", h, length, offset)
		return b.Backend.Load(ctx, h, length, offset, consumer)
	}

	// if we don't automatically cache this file type, fall back to the backend
	if _, ok := autoCacheFiles[h.Type]; !ok {
		debug.Log("Load(%v, %v, %v): delegating to backend", h, length, offset)
		return b.Backend.Load(ctx, h, length, offset, consumer)
	}

	debug.Log("auto-store %v in the cache", h)
	err := b.cacheFile(ctx, h)
	if err == nil {
		return b.loadFromCacheOrDelegate(ctx, h, length, offset, consumer)
	}

	debug.Log("error caching %v: %v, falling back to backend", h, err)
	return b.Backend.Load(ctx, h, length, offset, consumer)
}

// Stat tests whether the backend has a file. If it does not exist but still
// exists in the cache, it is removed from the cache.
func (b *Backend) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) {
	debug.Log("cache Stat(%v)", h)

	fi, err := b.Backend.Stat(ctx, h)
	if err != nil {
		if b.Backend.IsNotExist(err) {
			// try to remove from the cache, ignore errors
			_ = b.Cache.Remove(h)
		}

		return fi, err
	}

	return fi, err
}

// IsNotExist returns true if the error is caused by a non-existing file.
func (b *Backend) IsNotExist(err error) bool {
	return b.Backend.IsNotExist(err)
}
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 comebackfs

import (
	"errors"

	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/fuse/fuseutil"
)

// Create a read-only file system for browsing the backup rooted by the
// supplied score.
func NewFileSystem(
	score blob.Score,
	blobStore blob.Store) (fs fuseutil.FileSystem, err error) {
	err = errors.New("TODO")
	return
}
<commit_msg>NewFileSystem<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 comebackfs

import (
	"github.com/jacobsa/comeback/internal/blob"
	"github.com/jacobsa/fuse/fuseutil"
)

// Create a read-only file system for browsing the backup rooted by the
// supplied score.
func NewFileSystem(
	score blob.Score,
	blobStore blob.Store) (fs fuseutil.FileSystem, err error) {
	fs = &fileSystem{}
	return
}

////////////////////////////////////////////////////////////////////////
// Internal
////////////////////////////////////////////////////////////////////////

type fileSystem struct {
	fuseutil.NotImplementedFileSystem
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////
<|endoftext|>


<commit_before>package metainfo

import (
	"crypto/sha1" // nolint: gosec
	"errors"
	"fmt"
	"path/filepath"
	"strings"

	"github.com/zeebo/bencode"
)

var errInvalidPieceData = errors.New("invalid piece data")

// Info contains information about torrent.
type Info struct {
	PieceLength uint32             `bencode:"piece length" json:"piece_length"`
	Pieces      []byte             `bencode:"pieces" json:"pieces"`
	Private     bencode.RawMessage `bencode:"private" json:"private"`
	Name        string             `bencode:"name" json:"name"`
	Length      int64              `bencode:"length" json:"length"` // Single File Mode
	Files       []FileDict         `bencode:"files" json:"files"`   // Multiple File mode

	// Calculated fileds
	Hash        [20]byte `bencode:"-" json:"-"`
	TotalLength int64    `bencode:"-" json:"-"`
	NumPieces   uint32   `bencode:"-" json:"-"`
	Bytes       []byte   `bencode:"-" json:"-"`
	private     bool
}

type FileDict struct {
	Length int64    `bencode:"length" json:"length"`
	Path   []string `bencode:"path" json:"path"`
}

// NewInfo returns info from bencoded bytes in b.
func NewInfo(b []byte) (*Info, error) {
	var i Info
	if err := bencode.DecodeBytes(b, &i); err != nil {
		return nil, err
	}
	if uint32(len(i.Pieces))%sha1.Size != 0 {
		return nil, errInvalidPieceData
	}
	if len(i.Private) > 0 {
		var intVal int64
		var stringVal string
		err := bencode.DecodeBytes(i.Private, &intVal)
		if err != nil {
			err = bencode.DecodeBytes(i.Private, &stringVal)
			if err == nil {
				i.private = stringVal == "1"
			}
		} else {
			i.private = intVal == 1
		}
	}
	// ".." is not allowed in file names
	for _, file := range i.Files {
		for _, path := range file.Path {
			if strings.TrimSpace(path) == ".." {
				return nil, fmt.Errorf("invalid file name: %q", filepath.Join(file.Path...))
			}
		}
	}
	i.NumPieces = uint32(len(i.Pieces)) / sha1.Size
	if !i.MultiFile() {
		i.TotalLength = i.Length
	} else {
		for _, f := range i.Files {
			i.TotalLength += f.Length
		}
	}
	totalPieceDataLength := int64(i.PieceLength) * int64(i.NumPieces)
	delta := totalPieceDataLength - i.TotalLength
	if delta >= int64(i.PieceLength) || delta < 0 {
		return nil, errInvalidPieceData
	}
	i.Bytes = b
	hash := sha1.New()   // nolint: gosec
	_, _ = hash.Write(b) // nolint: gosec
	copy(i.Hash[:], hash.Sum(nil))
	return &i, nil
}

func (i *Info) MultiFile() bool {
	return len(i.Files) != 0
}

func (i *Info) HashOf(index uint32) []byte {
	begin := index * sha1.Size
	end := begin + sha1.Size
	return i.Pieces[begin:end]
}

// GetFiles returns the files in torrent as a slice, even if there is a single file.
func (i *Info) GetFiles() []FileDict {
	if i.MultiFile() {
		return i.Files
	}
	return []FileDict{{i.Length, []string{i.Name}}}
}

func (i *Info) IsPrivate() bool {
	if i == nil {
		return false
	}
	return i.private
}
<commit_msg>rename FileDict to File<commit_after>package metainfo

import (
	"crypto/sha1" // nolint: gosec
	"errors"
	"fmt"
	"path/filepath"
	"strings"

	"github.com/zeebo/bencode"
)

var errInvalidPieceData = errors.New("invalid piece data")

// Info contains information about torrent.
type Info struct {
	PieceLength uint32             `bencode:"piece length" json:"piece_length"`
	Pieces      []byte             `bencode:"pieces" json:"pieces"`
	Private     bencode.RawMessage `bencode:"private" json:"private"`
	Name        string             `bencode:"name" json:"name"`
	Length      int64              `bencode:"length" json:"length"` // Single File Mode
	Files       []File             `bencode:"files" json:"files"`   // Multiple File mode

	// Calculated fileds
	Hash        [20]byte `bencode:"-" json:"-"`
	TotalLength int64    `bencode:"-" json:"-"`
	NumPieces   uint32   `bencode:"-" json:"-"`
	Bytes       []byte   `bencode:"-" json:"-"`
	private     bool
}

type File struct {
	Length int64    `bencode:"length" json:"length"`
	Path   []string `bencode:"path" json:"path"`
}

// NewInfo returns info from bencoded bytes in b.
func NewInfo(b []byte) (*Info, error) {
	var i Info
	if err := bencode.DecodeBytes(b, &i); err != nil {
		return nil, err
	}
	if uint32(len(i.Pieces))%sha1.Size != 0 {
		return nil, errInvalidPieceData
	}
	if len(i.Private) > 0 {
		var intVal int64
		var stringVal string
		err := bencode.DecodeBytes(i.Private, &intVal)
		if err != nil {
			err = bencode.DecodeBytes(i.Private, &stringVal)
			if err == nil {
				i.private = stringVal == "1"
			}
		} else {
			i.private = intVal == 1
		}
	}
	// ".." is not allowed in file names
	for _, file := range i.Files {
		for _, path := range file.Path {
			if strings.TrimSpace(path) == ".." {
				return nil, fmt.Errorf("invalid file name: %q", filepath.Join(file.Path...))
			}
		}
	}
	i.NumPieces = uint32(len(i.Pieces)) / sha1.Size
	if !i.MultiFile() {
		i.TotalLength = i.Length
	} else {
		for _, f := range i.Files {
			i.TotalLength += f.Length
		}
	}
	totalPieceDataLength := int64(i.PieceLength) * int64(i.NumPieces)
	delta := totalPieceDataLength - i.TotalLength
	if delta >= int64(i.PieceLength) || delta < 0 {
		return nil, errInvalidPieceData
	}
	i.Bytes = b
	hash := sha1.New()   // nolint: gosec
	_, _ = hash.Write(b) // nolint: gosec
	copy(i.Hash[:], hash.Sum(nil))
	return &i, nil
}

func (i *Info) MultiFile() bool {
	return len(i.Files) != 0
}

func (i *Info) HashOf(index uint32) []byte {
	begin := index * sha1.Size
	end := begin + sha1.Size
	return i.Pieces[begin:end]
}

// GetFiles returns the files in torrent as a slice, even if there is a single file.
func (i *Info) GetFiles() []File {
	if i.MultiFile() {
		return i.Files
	}
	return []File{{i.Length, []string{i.Name}}}
}

func (i *Info) IsPrivate() bool {
	if i == nil {
		return false
	}
	return i.private
}
<|endoftext|>


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

// Package static builds static assets for the frontend and the worker.
package static

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

	"github.com/evanw/esbuild/pkg/api"
)

type Config struct {
	// Entrypoint is a directory in which to to build TypeScript
	// sources.
	EntryPoint string

	// Bundle is true if files imported by an entry file
	// should be joined together in a single output file.
	Bundle bool

	// Watch is true in development. Sourcemaps are placed inline,
	// the output is unminified, and changes to any TypeScript
	// files will force a rebuild of the JavaScript output.
	Watch bool
}

// Build compiles TypeScript files into minified JavaScript
// files using github.com/evanw/esbuild.
//
// This function is used in Server.staticHandler with Watch=true
// when cmd/frontend is run in dev mode and in
// devtools/cmd/static/main.go with Watch=false for building
// productionized assets.
func Build(config Config) (*api.BuildResult, error) {
	files, err := getEntry(config.EntryPoint, config.Bundle)
	if err != nil {
		return nil, err
	}
	options := api.BuildOptions{
		EntryPoints:   files,
		Bundle:        config.Bundle,
		Outdir:        config.EntryPoint,
		Write:         true,
		Platform:      api.PlatformBrowser,
		Format:        api.FormatESModule,
		OutExtensions: map[string]string{".css": ".min.css"},
		External:      []string{"*.svg"},
		Banner: map[string]string{"css": "/*!\n" +
			" * Copyright 2021 The Go Authors. All rights reserved.\n" +
			" * Use of this source code is governed by a BSD-style\n" +
			" * license that can be found in the LICENSE file.\n" +
			" */"},
	}
	if config.Watch {
		options.Sourcemap = api.SourceMapInline
		options.Watch = &api.WatchMode{}
	} else {
		options.MinifyIdentifiers = true
		options.MinifySyntax = true
		options.MinifyWhitespace = true
		options.Sourcemap = api.SourceMapLinked
	}
	result := api.Build(options)
	if len(result.Errors) > 0 {
		return nil, fmt.Errorf("error building static files: %v", result.Errors)
	}
	if len(result.Warnings) > 0 {
		return nil, fmt.Errorf("error building static files: %v", result.Warnings)
	}
	return &result, nil
}

// getEntry walks the the given directory and collects entry file paths
// for esbuild. It ignores test files and files prefixed with an underscore.
// Underscore prefixed files are assumed to be imported by and bundled together
// with the output of an entry file.
func getEntry(dir string, bundle bool) ([]string, error) {
	var matches []string
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		basePath := filepath.Base(path)
		notPartial := !strings.HasPrefix(basePath, "_")
		notTest := !strings.HasSuffix(basePath, ".test.ts")
		isTS := strings.HasSuffix(basePath, ".ts")
		isCSS := strings.HasSuffix(basePath, ".css") && !strings.HasSuffix(basePath, ".min.css")
		if notPartial && notTest && (isTS || (bundle && isCSS)) {
			matches = append(matches, path)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return matches, nil
}
<commit_msg>internal/static: minify generated files in watch mode<commit_after>// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package static builds static assets for the frontend and the worker.
package static

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

	"github.com/evanw/esbuild/pkg/api"
)

type Config struct {
	// Entrypoint is a directory in which to to build TypeScript
	// sources.
	EntryPoint string

	// Bundle is true if files imported by an entry file
	// should be joined together in a single output file.
	Bundle bool

	// Watch is true in development. Sourcemaps are placed inline,
	// the output is unminified, and changes to any TypeScript
	// files will force a rebuild of the JavaScript output.
	Watch bool
}

// Build compiles TypeScript files into minified JavaScript
// files using github.com/evanw/esbuild.
//
// This function is used in Server.staticHandler with Watch=true
// when cmd/frontend is run in dev mode and in
// devtools/cmd/static/main.go with Watch=false for building
// productionized assets.
func Build(config Config) (*api.BuildResult, error) {
	files, err := getEntry(config.EntryPoint, config.Bundle)
	if err != nil {
		return nil, err
	}
	options := api.BuildOptions{
		EntryPoints:   files,
		Bundle:        config.Bundle,
		Outdir:        config.EntryPoint,
		Write:         true,
		Platform:      api.PlatformBrowser,
		Format:        api.FormatESModule,
		OutExtensions: map[string]string{".css": ".min.css"},
		External:      []string{"*.svg"},
		Banner: map[string]string{"css": "/*!\n" +
			" * Copyright 2021 The Go Authors. All rights reserved.\n" +
			" * Use of this source code is governed by a BSD-style\n" +
			" * license that can be found in the LICENSE file.\n" +
			" */"},
	}
	if config.Watch {
		options.Watch = &api.WatchMode{}
	}
	options.MinifyIdentifiers = true
	options.MinifySyntax = true
	options.MinifyWhitespace = true
	options.Sourcemap = api.SourceMapLinked
	result := api.Build(options)
	if len(result.Errors) > 0 {
		return nil, fmt.Errorf("error building static files: %v", result.Errors)
	}
	if len(result.Warnings) > 0 {
		return nil, fmt.Errorf("error building static files: %v", result.Warnings)
	}
	return &result, nil
}

// getEntry walks the the given directory and collects entry file paths
// for esbuild. It ignores test files and files prefixed with an underscore.
// Underscore prefixed files are assumed to be imported by and bundled together
// with the output of an entry file.
func getEntry(dir string, bundle bool) ([]string, error) {
	var matches []string
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		basePath := filepath.Base(path)
		notPartial := !strings.HasPrefix(basePath, "_")
		notTest := !strings.HasSuffix(basePath, ".test.ts")
		isTS := strings.HasSuffix(basePath, ".ts")
		isCSS := strings.HasSuffix(basePath, ".css") && !strings.HasSuffix(basePath, ".min.css")
		if notPartial && notTest && (isTS || (bundle && isCSS)) {
			matches = append(matches, path)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return matches, nil
}
<|endoftext|>


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

// Package syntax 提供对路由字符串语法的分析功能。
package syntax

import (
	"errors"
	"fmt"
	"sort"
	"strings"
)

// 路由中允许的最大参数数量
const maxParamsSize = 255

// MaxPatternDepth 最大的路径深度
const MaxPatternDepth = 255

// 正则语法的起止字符
const (
	Start = '{'
	End   = '}'
)

// 表示当前路由项的类型，同时会被用于 Entry.Priority()
const (
	TypeUnknown = iota
	TypeBasic
	TypeRegexp
	TypeNamed
)

// Syntax 描述指定的字符串所表示的语法结构
type Syntax struct {
	Pattern   string   // 原始的字符串
	HasParams bool     // 是否有参数
	Wildcard  bool     // 是否为通配符模式
	Patterns  []string // 保存着对字符串处理后的结果
	Type      int      // 该语法应该被解析成的类型
}

// IsWildcard 当前字符串是否为通配符模式
func IsWildcard(pattern string) bool {
	return strings.HasSuffix(pattern, "/*")
}

// 判断 str 是一个合法的语法结构还是普通的字符串
func isSyntax(str string) bool {
	return str[0] == Start && str[len(str)-1] == End
}

// New 根据 pattern 生成一个 *Syntax 对象
func New(pattern string) (*Syntax, error) {
	if len(pattern) == 0 || pattern[0] != '/' {
		return nil, errors.New("参数 pattern 必须以 / 开头")
	}

	if strings.Count(pattern, "/") > MaxPatternDepth {
		return nil, fmt.Errorf("匹配字符串最多只能包含 %d 个 / 字符", MaxPatternDepth)
	}

	names := []string{} // 缓存各个参数的名称，用于判断是否有重名

	strs := split(pattern)
	s := &Syntax{
		Pattern:  pattern,
		Patterns: make([]string, 0, len(strs)),
		Wildcard: strings.HasSuffix(pattern, "/*"),
	}
	if len(strs) == 0 ||
		len(strs) == 1 && !isSyntax(strs[0]) {
		s.Type = TypeBasic
		return s, nil
	}

	// 判断类型
	for _, v := range strs {
		s.Patterns = append(s.Patterns, v)

		if !isSyntax(v) { // 普通字符串
			continue
		}

		// 只存在命名，而不存在正则表达式
		if index := strings.IndexByte(v, ':'); index < 0 {
			if s.Type != TypeRegexp {
				s.Type = TypeNamed
			}
		} else {
			s.Type = TypeRegexp
		}
	}

	// 命名参数
	if s.Type == TypeNamed {
		for _, str := range s.Patterns {
			lastIndex := len(str) - 1
			if str[0] == Start && str[lastIndex] == End {
				names = append(names, str[1:lastIndex])
				s.HasParams = true
			}
		}

		goto DUP
	}

	// nType == typeRegexp
	for i, str := range s.Patterns {
		lastIndex := len(str) - 1
		if !isSyntax(str) {
			continue
		}

		str = str[1:lastIndex] // 去掉首尾的{}符号

		index := strings.IndexByte(str, ':')
		if index < 0 { // 只存在命名，而不存在正则表达式，默认匹配[^/]
			s.Patterns[i] = "(?P<" + str + ">[^/]+)"
			s.HasParams = true
			names = append(names, str)
			continue
		}

		if index == 0 { // 不存在命名，但有正则表达式
			s.Patterns[i] = str[1:]
			continue
		}

		s.Patterns[i] = "(?P<" + str[:index] + ">" + str[index+1:] + ")"
		s.HasParams = true
		names = append(names, str[:index])
	}

DUP:
	if index := duplicateName(names); index >= 0 {
		return nil, fmt.Errorf("相同的路由参数名：%v", names[index])
	}

	if len(names) >= maxParamsSize {
		return nil, fmt.Errorf("路径参数最多只能 %d 个，当前数量 %d", maxParamsSize, len(names))
	}

	return s, nil
}

func duplicateName(names []string) int {
	// 先按名称排序，之后只要检测相邻两个名称是否相同即可。
	if len(names) > 1 {
		sort.Strings(names)
		for i := 1; i < len(names); i++ {
			if names[i] == names[i-1] {
				return i
			}
		}
	}

	return -1
}

// 将 str 以 { 和 } 为分隔符进行分隔。
// 符号 { 和 } 必须成对出现，且不能嵌套，否则结果是未知的。
//  /api/{id:\\d+}/users/ ==> {"/api/", "{id:\\d+}", "/users/"}
func split(str string) []string {
	ret := []string{}

	var start, end int
	for {
		if len(str) == 0 {
			return ret
		}

		start = strings.IndexByte(str, Start)
		if start < 0 { // 不存在 start
			return append(ret, str)
		}

		end = strings.IndexByte(str[start:], End)
		if end < 0 { // 不存在 end
			return append(ret, str)
		}
		end++
		end += start

		if start > 0 {
			ret = append(ret, str[:start])
		}

		ret = append(ret, str[start:end])
		str = str[end:]
	}
}

// SlashCount 统计字符串包含的 / 字符数量
func SlashCount(str string) int {
	ret := 0
	for _, c := range str {
		if c == '/' {
			ret++
		}
	}

	return ret
}
<commit_msg>[internal/syntax] 使用自定义的函数代替原来的代码<commit_after>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// Package syntax 提供对路由字符串语法的分析功能。
package syntax

import (
	"errors"
	"fmt"
	"sort"
	"strings"
)

// 路由中允许的最大参数数量
const maxParamsSize = 255

// MaxPatternDepth 最大的路径深度
const MaxPatternDepth = 255

// 正则语法的起止字符
const (
	Start = '{'
	End   = '}'
)

// 表示当前路由项的类型，同时会被用于 Entry.Priority()
const (
	TypeUnknown = iota
	TypeBasic
	TypeRegexp
	TypeNamed
)

// Syntax 描述指定的字符串所表示的语法结构
type Syntax struct {
	Pattern   string   // 原始的字符串
	HasParams bool     // 是否有参数
	Wildcard  bool     // 是否为通配符模式
	Patterns  []string // 保存着对字符串处理后的结果
	Type      int      // 该语法应该被解析成的类型
}

// IsWildcard 当前字符串是否为通配符模式
func IsWildcard(pattern string) bool {
	return strings.HasSuffix(pattern, "/*")
}

// 判断 str 是一个合法的语法结构还是普通的字符串
func isSyntax(str string) bool {
	return str[0] == Start && str[len(str)-1] == End
}

// New 根据 pattern 生成一个 *Syntax 对象
func New(pattern string) (*Syntax, error) {
	if len(pattern) == 0 || pattern[0] != '/' {
		return nil, errors.New("参数 pattern 必须以 / 开头")
	}

	if SlashCount(pattern) > MaxPatternDepth {
		return nil, fmt.Errorf("匹配字符串最多只能包含 %d 个 / 字符", MaxPatternDepth)
	}

	names := []string{} // 缓存各个参数的名称，用于判断是否有重名

	strs := split(pattern)
	s := &Syntax{
		Pattern:  pattern,
		Patterns: make([]string, 0, len(strs)),
		Wildcard: IsWildcard(pattern),
	}
	if len(strs) == 0 ||
		len(strs) == 1 && !isSyntax(strs[0]) {
		s.Type = TypeBasic
		return s, nil
	}

	// 判断类型
	for _, v := range strs {
		s.Patterns = append(s.Patterns, v)

		if !isSyntax(v) { // 普通字符串
			continue
		}

		// 只存在命名，而不存在正则表达式
		if index := strings.IndexByte(v, ':'); index < 0 {
			if s.Type != TypeRegexp {
				s.Type = TypeNamed
			}
		} else {
			s.Type = TypeRegexp
		}
	}

	// 命名参数
	if s.Type == TypeNamed {
		for _, str := range s.Patterns {
			lastIndex := len(str) - 1
			if str[0] == Start && str[lastIndex] == End {
				names = append(names, str[1:lastIndex])
				s.HasParams = true
			}
		}

		goto DUP
	}

	// nType == typeRegexp
	for i, str := range s.Patterns {
		lastIndex := len(str) - 1
		if !isSyntax(str) {
			continue
		}

		str = str[1:lastIndex] // 去掉首尾的{}符号

		index := strings.IndexByte(str, ':')
		if index < 0 { // 只存在命名，而不存在正则表达式，默认匹配[^/]
			s.Patterns[i] = "(?P<" + str + ">[^/]+)"
			s.HasParams = true
			names = append(names, str)
			continue
		}

		if index == 0 { // 不存在命名，但有正则表达式
			s.Patterns[i] = str[1:]
			continue
		}

		s.Patterns[i] = "(?P<" + str[:index] + ">" + str[index+1:] + ")"
		s.HasParams = true
		names = append(names, str[:index])
	}

DUP:
	if index := duplicateName(names); index >= 0 {
		return nil, fmt.Errorf("相同的路由参数名：%v", names[index])
	}

	if len(names) >= maxParamsSize {
		return nil, fmt.Errorf("路径参数最多只能 %d 个，当前数量 %d", maxParamsSize, len(names))
	}

	return s, nil
}

func duplicateName(names []string) int {
	// 先按名称排序，之后只要检测相邻两个名称是否相同即可。
	if len(names) > 1 {
		sort.Strings(names)
		for i := 1; i < len(names); i++ {
			if names[i] == names[i-1] {
				return i
			}
		}
	}

	return -1
}

// 将 str 以 { 和 } 为分隔符进行分隔。
// 符号 { 和 } 必须成对出现，且不能嵌套，否则结果是未知的。
//  /api/{id:\\d+}/users/ ==> {"/api/", "{id:\\d+}", "/users/"}
func split(str string) []string {
	ret := []string{}

	var start, end int
	for {
		if len(str) == 0 {
			return ret
		}

		start = strings.IndexByte(str, Start)
		if start < 0 { // 不存在 start
			return append(ret, str)
		}

		end = strings.IndexByte(str[start:], End)
		if end < 0 { // 不存在 end
			return append(ret, str)
		}
		end++
		end += start

		if start > 0 {
			ret = append(ret, str[:start])
		}

		ret = append(ret, str[start:end])
		str = str[end:]
	}
}

// SlashCount 统计字符串包含的 / 字符数量
func SlashCount(str string) int {
	ret := 0
	for _, c := range str {
		if c == '/' {
			ret++
		}
	}

	return ret
}
<|endoftext|>


<commit_before>package testutil

import "testing"

// FnOK is validate function.
type FnOK func(string) bool

// TestCase is test case data.
type TestCase struct {
	Name  string
	Input string
	Want  bool
}

func Run(t *testing.T, fn FnOK, testCases []TestCase) {
	for _, tt := range testCases {
		tt := tt
		t.Run(tt.Name, func(t *testing.T) {
			got := fn(tt.Input)
			if got != tt.Want {
				t.Fatalf("got %v; want %v", got, tt.Want)
			}
		})
	}
}
<commit_msg>internal/testutil: rm FnOK<commit_after>package testutil

import "testing"

// TestCase is test case data.
type TestCase struct {
	Name  string
	Input string
	Want  bool
}

func Run(t *testing.T, fn func(string) bool, testCases []TestCase) {
	for _, tt := range testCases {
		tt := tt
		t.Run(tt.Name, func(t *testing.T) {
			got := fn(tt.Input)
			if got != tt.Want {
				t.Fatalf("got %v; want %v", got, tt.Want)
			}
		})
	}
}
<|endoftext|>


<commit_before>package TriUI

import (
	"strconv"
	"trident.li/keyval"
	pf "trident.li/pitchfork/lib"
	pu "trident.li/pitchfork/ui"
	tr "trident.li/trident/src/lib"
)

func h_group_members(cui pu.PfUI) {
	path := cui.GetPath()

	if len(path) != 0 && path[0] != "" {
		pu.H_group_member_profile(cui)
		return
	}

	var err error

	tctx := tr.TriGetCtx(cui)
	total := 0
	offset := 0

	offset_v, err := cui.FormValue("offset")
	if err == nil && offset_v != "" {
		offset, _ = strconv.Atoi(offset_v)
	}

	search, err := cui.FormValue("search")
	if err != nil {
		search = ""
	}

	grp := tctx.TriSelectedGroup()

	total, err = grp.GetMembersTot(search)
	if err != nil {
		cui.Err("error: " + err.Error())
		return
	}

	members, err := grp.GetMembers(search, cui.TheUser().GetUserName(), offset, 10, false, cui.IAmGroupAdmin(), false)
	if err != nil {
		cui.Err(err.Error())
		return
	}

	/* Output the page */
	type Page struct {
		*pu.PfPage
		Group        pf.PfGroup
		GroupMembers []pf.PfGroupMember
		PagerOffset  int
		PagerTotal   int
		Search       string
		IsAdmin      bool
	}
	isadmin := cui.IAmGroupAdmin()

	p := Page{cui.Page_def(), grp, members, offset, total, search, isadmin}
	cui.Page_show("group/members.tmpl", p)
}

type NominateAdd struct {
	group        tr.TriGroup
	Action       string          `label:"Action" pftype:"hidden"`
	Vouchee      string          `label:"Username" pfset:"nobody" pfget:"none"`
	Comment      string          `label:"Vouch comment" pftype:"text" hint:"Vouch description for this user" pfreq:"yes"`
	Attestations map[string]bool `label:"Attestations (all required)" hint:"Attestations for this user" options:"GetAttestationOpts"`
	Button       string          `label:"Nominate" pftype:"submit"`
	Error        string          /* Used by pfform() */
}

func (na *NominateAdd) GetAttestationOpts(obj interface{}) (kvs keyval.KeyVals, err error) {
	return na.group.GetAttestationsKVS()
}

func h_group_nominate_existing(cui pu.PfUI) {
	errmsg := ""
	tctx := tr.TriGetCtx(cui)
	grp := tctx.TriSelectedGroup()

	vouchee_name, err := cui.FormValue("vouchee")
	if err != nil {
		pu.H_errtxt(cui, "No valid vouchee")
		return
	}

	err = tctx.SelectVouchee(vouchee_name, pu.PERM_USER_NOMINATE)
	if err != nil {
		pu.H_errtxt(cui, "Vouchee unselectable")
		return
	}

	if cui.IsPOST() {
		action, err := cui.FormValue("action")
		if err == nil && action == "nominate" {
			err = vouch_nominate(cui)
			if err != nil {
				errmsg = err.Error()
			}
		}
	}

	vouchee := tctx.SelectedVouchee()

	type Page struct {
		*pu.PfPage
		Vouchee     string
		GroupName   string
		NominateAdd *NominateAdd
	}

	na := &NominateAdd{group: grp, Vouchee: vouchee.GetUserName(), Action: "nominate", Error: errmsg}

	p := Page{cui.Page_def(), vouchee.GetUserName(), grp.GetGroupName(), na}
	cui.Page_show("group/nominate_existing.tmpl", p)
}

type NominateNew struct {
	group        tr.TriGroup
	Action       string          `label:"Action" pftype:"hidden"`
	Email        string          `label:"Email address of nominee" pfset:"none"`
	FullName     string          `label:"Full Name" hint:"Full Name of this user" pfreq:"yes"`
	Affiliation  string          `label:"Affiliation" hint:"Who the user is affiliated to" pfreq:"yes"`
	Biography    string          `label:"Biography" pftype:"text" hint:"Biography for this user" pfreq:"yes"`
	Vouch        string          `label:"Vouch" pftype:"text" hint:"Vouch for this user" pfreq:"yes"`
	Attestations map[string]bool `label:"Attestations (all required)" hint:"Attestations for this user" options:"GetAttestationOpts"`
	Button       string          `label:"Nominate" pftype:"submit"`
}

func (na *NominateNew) GetAttestationOpts(obj interface{}) (kvs keyval.KeyVals, err error) {
	return na.group.GetAttestationsKVS()
}

func h_group_nominate(cui pu.PfUI) {
	tctx := tr.TriGetCtx(cui)

	var list []pf.PfUser
	notfound := false

	user := tctx.TriSelectedUser()
	grp := tctx.TriSelectedGroup()

	if cui.IsPOST() {
		action, err := cui.FormValue("action")
		if err == nil && action == "nominate" {
			vouch_nominate_new(cui)
			return
		}
	}

	search, err := cui.FormValue("search")
	if err != nil {
		search = ""
	}

	if search != "" {
		list, err = user.GetList(cui, search, 0, 0)
		if err != nil {
			return
		}
		if len(list) == 0 {
			notfound = true
		}
	}

	type Page struct {
		*pu.PfPage
		Search    string
		GroupName string
		Message   string
		Error     string
		Users     []pf.PfUser
		NotFound  bool
		NewForm   *NominateNew
	}

	newform := &NominateNew{group: grp, Action: "nominate", Email: search}

	p := Page{cui.Page_def(), search, grp.GetGroupName(), "", "", list, notfound, newform}
	cui.Page_show("group/nominate.tmpl", p)
}

func h_group(cui pu.PfUI, menu *pu.PfUIMenu) {
	menu.Replace("members", h_group_members)

	m := []pu.PfUIMentry{
		{"nominate", "Nominate", pf.PERM_GROUP_MEMBER, h_group_nominate, nil},
		{"nominate_existing", "Nominate existing user", pf.PERM_GROUP_MEMBER | pf.PERM_HIDDEN, h_group_nominate_existing, nil},
	}

	menu.Add(m...)
}
<commit_msg>Add attestation checkboxes to group based forms<commit_after>package TriUI

import (
	"strconv"
	"trident.li/keyval"
	pf "trident.li/pitchfork/lib"
	pu "trident.li/pitchfork/ui"
	tr "trident.li/trident/src/lib"
)

func h_group_members(cui pu.PfUI) {
	path := cui.GetPath()

	if len(path) != 0 && path[0] != "" {
		pu.H_group_member_profile(cui)
		return
	}

	var err error

	tctx := tr.TriGetCtx(cui)
	total := 0
	offset := 0

	offset_v, err := cui.FormValue("offset")
	if err == nil && offset_v != "" {
		offset, _ = strconv.Atoi(offset_v)
	}

	search, err := cui.FormValue("search")
	if err != nil {
		search = ""
	}

	grp := tctx.TriSelectedGroup()

	total, err = grp.GetMembersTot(search)
	if err != nil {
		cui.Err("error: " + err.Error())
		return
	}

	members, err := grp.GetMembers(search, cui.TheUser().GetUserName(), offset, 10, false, cui.IAmGroupAdmin(), false)
	if err != nil {
		cui.Err(err.Error())
		return
	}

	/* Output the page */
	type Page struct {
		*pu.PfPage
		Group        pf.PfGroup
		GroupMembers []pf.PfGroupMember
		PagerOffset  int
		PagerTotal   int
		Search       string
		IsAdmin      bool
	}
	isadmin := cui.IAmGroupAdmin()

	p := Page{cui.Page_def(), grp, members, offset, total, search, isadmin}
	cui.Page_show("group/members.tmpl", p)
}

type NominateAdd struct {
	group        tr.TriGroup
	Action       string          `label:"Action" pftype:"hidden"`
	Vouchee      string          `label:"Username" pfset:"nobody" pfget:"none"`
	Comment      string          `label:"Vouch comment" pftype:"text" hint:"Vouch description for this user" pfreq:"yes"`
	Attestations map[string]bool `label:"Attestations (all required)" hint:"Attestations for this user" options:"GetAttestationOpts" pfcheckboxmode:"yes"`
	Button       string          `label:"Nominate" pftype:"submit"`
	Error        string          /* Used by pfform() */
}

func (na *NominateAdd) GetAttestationOpts(obj interface{}) (kvs keyval.KeyVals, err error) {
	return na.group.GetAttestationsKVS()
}

func h_group_nominate_existing(cui pu.PfUI) {
	errmsg := ""
	tctx := tr.TriGetCtx(cui)
	grp := tctx.TriSelectedGroup()

	vouchee_name, err := cui.FormValue("vouchee")
	if err != nil {
		pu.H_errtxt(cui, "No valid vouchee")
		return
	}

	err = tctx.SelectVouchee(vouchee_name, pu.PERM_USER_NOMINATE)
	if err != nil {
		pu.H_errtxt(cui, "Vouchee unselectable")
		return
	}

	if cui.IsPOST() {
		action, err := cui.FormValue("action")
		if err == nil && action == "nominate" {
			err = vouch_nominate(cui)
			if err != nil {
				errmsg = err.Error()
			}
		}
	}

	vouchee := tctx.SelectedVouchee()

	type Page struct {
		*pu.PfPage
		Vouchee     string
		GroupName   string
		NominateAdd *NominateAdd
	}

	na := &NominateAdd{group: grp, Vouchee: vouchee.GetUserName(), Action: "nominate", Error: errmsg}

	p := Page{cui.Page_def(), vouchee.GetUserName(), grp.GetGroupName(), na}
	cui.Page_show("group/nominate_existing.tmpl", p)
}

type NominateNew struct {
	group        tr.TriGroup
	Action       string          `label:"Action" pftype:"hidden"`
	Email        string          `label:"Email address of nominee" pfset:"none"`
	FullName     string          `label:"Full Name" hint:"Full Name of this user" pfreq:"yes"`
	Affiliation  string          `label:"Affiliation" hint:"Who the user is affiliated to" pfreq:"yes"`
	Biography    string          `label:"Biography" pftype:"text" hint:"Biography for this user" pfreq:"yes"`
	Vouch        string          `label:"Vouch" pftype:"text" hint:"Vouch for this user" pfreq:"yes"`
	Attestations map[string]bool `label:"Attestations (all required)" hint:"Attestations for this user" options:"GetAttestationOpts" pfcheckboxmode:"yes"`
	Button       string          `label:"Nominate" pftype:"submit"`
}

func (na *NominateNew) GetAttestationOpts(obj interface{}) (kvs keyval.KeyVals, err error) {
	return na.group.GetAttestationsKVS()
}

func h_group_nominate(cui pu.PfUI) {
	tctx := tr.TriGetCtx(cui)

	var list []pf.PfUser
	notfound := false

	user := tctx.TriSelectedUser()
	grp := tctx.TriSelectedGroup()

	if cui.IsPOST() {
		action, err := cui.FormValue("action")
		if err == nil && action == "nominate" {
			vouch_nominate_new(cui)
			return
		}
	}

	search, err := cui.FormValue("search")
	if err != nil {
		search = ""
	}

	if search != "" {
		list, err = user.GetList(cui, search, 0, 0)
		if err != nil {
			return
		}
		if len(list) == 0 {
			notfound = true
		}
	}

	type Page struct {
		*pu.PfPage
		Search    string
		GroupName string
		Message   string
		Error     string
		Users     []pf.PfUser
		NotFound  bool
		NewForm   *NominateNew
	}

	newform := &NominateNew{group: grp, Action: "nominate", Email: search}

	p := Page{cui.Page_def(), search, grp.GetGroupName(), "", "", list, notfound, newform}
	cui.Page_show("group/nominate.tmpl", p)
}

func h_group(cui pu.PfUI, menu *pu.PfUIMenu) {
	menu.Replace("members", h_group_members)

	m := []pu.PfUIMentry{
		{"nominate", "Nominate", pf.PERM_GROUP_MEMBER, h_group_nominate, nil},
		{"nominate_existing", "Nominate existing user", pf.PERM_GROUP_MEMBER | pf.PERM_HIDDEN, h_group_nominate_existing, nil},
	}

	menu.Add(m...)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math"
	"net/http"
	"net/url"
	"os"
	"rais/src/iiif"
	"rais/src/magick"
	"rais/src/openjpeg"
	"rais/src/plugins"
	"rais/src/version"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/hashicorp/golang-lru"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/uoregon-libraries/gopkg/interrupts"
	"github.com/uoregon-libraries/gopkg/logger"
)

var tilePath string
var infoCache *lru.Cache
var tileCache *lru.TwoQueueCache

// Logger is the server's central logger.Logger instance
var Logger *logger.Logger

const defaultAddress = ":12415"
const defaultInfoCacheLen = 10000

// cacheHits and cacheMisses allow some rudimentary tracking of cache value
var cacheHits, cacheMisses int64

var defaultLogLevel = logger.Debug.String()

func main() {
	// Defaults
	viper.SetDefault("Address", defaultAddress)
	viper.SetDefault("InfoCacheLen", defaultInfoCacheLen)
	viper.SetDefault("LogLevel", defaultLogLevel)

	// Allow all configuration to be in environment variables
	viper.SetEnvPrefix("RAIS")
	viper.AutomaticEnv()

	// Config file options
	viper.SetConfigName("rais")
	viper.AddConfigPath("/etc")
	viper.AddConfigPath(".")
	viper.ReadInConfig()

	// CLI flags
	pflag.String("iiif-url", "", `Base URL for serving IIIF requests, e.g., "http://example.com/images/iiif"`)
	viper.BindPFlag("IIIFURL", pflag.CommandLine.Lookup("iiif-url"))
	pflag.String("address", defaultAddress, "http service address")
	viper.BindPFlag("Address", pflag.CommandLine.Lookup("address"))
	pflag.String("tile-path", "", "Base path for images")
	viper.BindPFlag("TilePath", pflag.CommandLine.Lookup("tile-path"))
	pflag.Int("iiif-info-cache-size", defaultInfoCacheLen, "Maximum cached image info entries (IIIF only)")
	viper.BindPFlag("InfoCacheLen", pflag.CommandLine.Lookup("iiif-info-cache-size"))
	pflag.String("capabilities-file", "", "TOML file describing capabilities, rather than everything RAIS supports")
	viper.BindPFlag("CapabilitiesFile", pflag.CommandLine.Lookup("capabilities-file"))
	pflag.String("log-level", defaultLogLevel, "Log level: the server will only log notifications at "+
		"this level and above (must be DEBUG, INFO, WARN, ERROR, or CRIT)")
	viper.BindPFlag("LogLevel", pflag.CommandLine.Lookup("log-level"))
	pflag.Int64("image-max-area", math.MaxInt64, "Maximum area (w x h) of images to be served")
	viper.BindPFlag("ImageMaxArea", pflag.CommandLine.Lookup("image-max-area"))
	pflag.Int("image-max-width", math.MaxInt32, "Maximum width of images to be served")
	viper.BindPFlag("ImageMaxWidth", pflag.CommandLine.Lookup("image-max-width"))
	pflag.Int("image-max-height", math.MaxInt32, "Maximum height of images to be served")
	viper.BindPFlag("ImageMaxHeight", pflag.CommandLine.Lookup("image-max-height"))

	pflag.Parse()

	// Make sure required values exist
	if !viper.IsSet("TilePath") {
		fmt.Println("ERROR: --tile-path is required")
		pflag.Usage()
		os.Exit(1)
	}

	// Make sure we have a valid log level
	var level = logger.LogLevelFromString(viper.GetString("LogLevel"))
	if level == logger.Invalid {
		fmt.Println("ERROR: --log-level must be DEBUG, INFO, WARN, ERROR, or CRIT")
		pflag.Usage()
		os.Exit(1)
	}
	Logger = logger.New(level)
	openjpeg.Logger = Logger
	magick.Logger = Logger

	LoadPlugins(Logger)

	// Pull all values we need for all cases
	tilePath = viper.GetString("TilePath")
	address := viper.GetString("Address")

	ih := NewImageHandler(tilePath)
	ih.Maximums.Area = viper.GetInt64("ImageMaxArea")
	ih.Maximums.Width = viper.GetInt("ImageMaxWidth")
	ih.Maximums.Height = viper.GetInt("ImageMaxHeight")

	// Handle IIIF data only if we have a IIIF URL
	iiifURL := viper.GetString("IIIFURL")
	if iiifURL == "" {
		fmt.Println("ERROR: --iiif-url must be set to the server's public URL")
		pflag.Usage()
		os.Exit(1)
	}

	Logger.Debugf("Attempting to start up IIIF at %s", viper.GetString("IIIFURL"))
	iiifBase, err := url.Parse(iiifURL)
	if err == nil && iiifBase.Scheme == "" {
		err = fmt.Errorf("empty scheme")
	}
	if err == nil && iiifBase.Host == "" {
		err = fmt.Errorf("empty host")
	}
	if err == nil && iiifBase.Path == "" {
		err = fmt.Errorf("empty path")
	}
	if err != nil {
		Logger.Fatalf("Invalid IIIF URL (%s) specified: %s", iiifURL, err)
	}

	icl := viper.GetInt("InfoCacheLen")
	if icl > 0 {
		infoCache, err = lru.New(icl)
		if err != nil {
			Logger.Fatalf("Unable to start info cache: %s", err)
		}
	}

	tcl := viper.GetInt("TileCacheLen")
	if tcl > 0 {
		Logger.Debugf("Creating a tile cache to hold up to %d tiles", tcl)
		tileCache, err = lru.New2Q(tcl)
		if err != nil {
			Logger.Fatalf("Unable to start info cache: %s", err)
		}
	}

	Logger.Infof("IIIF enabled at %s", iiifBase.String())
	ih.EnableIIIF(iiifBase)

	capfile := viper.GetString("CapabilitiesFile")
	if capfile != "" {
		ih.FeatureSet = &iiif.FeatureSet{}
		_, err := toml.DecodeFile(capfile, &ih.FeatureSet)
		if err != nil {
			Logger.Fatalf("Invalid file or formatting in capabilities file '%s'", capfile)
		}
		Logger.Debugf("Setting IIIF capabilities from file '%s'", capfile)
	}

	handle(ih.IIIFBase.Path+"/", http.HandlerFunc(ih.IIIFRoute))
	handle("/images/dzi/", http.HandlerFunc(ih.DZIRoute))
	handle("/version", http.HandlerFunc(VersionHandler))

	if tileCache != nil {
		go func() {
			for {
				time.Sleep(time.Minute * 10)
				Logger.Infof("Cache hits: %d; cache misses: %d", cacheHits, cacheMisses)
			}
		}()
	}

	Logger.Infof("RAIS v%s starting...", version.Version)
	var srv = &http.Server{
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 30 * time.Second,
		Addr:         address,
	}

	interrupts.TrapIntTerm(func() {
		Logger.Infof("Stopping RAIS...")
		srv.Shutdown(nil)

		if len(teardownPlugins) > 0 {
			Logger.Infof("Tearing down plugins")
			for _, plug := range teardownPlugins {
				plug()
			}
			Logger.Infof("Plugin teardown complete")
		}

		Logger.Infof("Stopped")
	})

	if err := srv.ListenAndServe(); err != nil {
		// Don't report a fatal error when we close the server
		if err != http.ErrServerClosed {
			Logger.Fatalf("Error starting listener: %s", err)
		}
	}
}

// handle sends the pattern and raw handler to plugins, and sets up routing on
// whatever is returned (if anything).  All plugins which wrap handlers are
// allowed to run, but the behavior could definitely get weird depending on
// what a given plugin does.  Ye be warned.
func handle(pattern string, handler http.Handler) {
	for _, plug := range wrapHandlerPlugins {
		var h2, err = plug(pattern, handler)
		if err == nil {
			handler = h2
		} else if err != plugins.ErrSkipped {
			logger.Fatalf("Error trying to wrap handler %q: %s", pattern, err)
		}
	}
	http.Handle(pattern, handler)
}

// VersionHandler spits out the raw version string to the browser
func VersionHandler(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte(version.Version))
}
<commit_msg>rais-server: wait for plugins to stop<commit_after>package main

import (
	"fmt"
	"math"
	"net/http"
	"net/url"
	"os"
	"rais/src/iiif"
	"rais/src/magick"
	"rais/src/openjpeg"
	"rais/src/plugins"
	"rais/src/version"
	"sync"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/hashicorp/golang-lru"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/uoregon-libraries/gopkg/interrupts"
	"github.com/uoregon-libraries/gopkg/logger"
)

var tilePath string
var infoCache *lru.Cache
var tileCache *lru.TwoQueueCache

// Logger is the server's central logger.Logger instance
var Logger *logger.Logger

const defaultAddress = ":12415"
const defaultInfoCacheLen = 10000

// cacheHits and cacheMisses allow some rudimentary tracking of cache value
var cacheHits, cacheMisses int64

var defaultLogLevel = logger.Debug.String()

func main() {
	// Defaults
	viper.SetDefault("Address", defaultAddress)
	viper.SetDefault("InfoCacheLen", defaultInfoCacheLen)
	viper.SetDefault("LogLevel", defaultLogLevel)

	// Allow all configuration to be in environment variables
	viper.SetEnvPrefix("RAIS")
	viper.AutomaticEnv()

	// Config file options
	viper.SetConfigName("rais")
	viper.AddConfigPath("/etc")
	viper.AddConfigPath(".")
	viper.ReadInConfig()

	// CLI flags
	pflag.String("iiif-url", "", `Base URL for serving IIIF requests, e.g., "http://example.com/images/iiif"`)
	viper.BindPFlag("IIIFURL", pflag.CommandLine.Lookup("iiif-url"))
	pflag.String("address", defaultAddress, "http service address")
	viper.BindPFlag("Address", pflag.CommandLine.Lookup("address"))
	pflag.String("tile-path", "", "Base path for images")
	viper.BindPFlag("TilePath", pflag.CommandLine.Lookup("tile-path"))
	pflag.Int("iiif-info-cache-size", defaultInfoCacheLen, "Maximum cached image info entries (IIIF only)")
	viper.BindPFlag("InfoCacheLen", pflag.CommandLine.Lookup("iiif-info-cache-size"))
	pflag.String("capabilities-file", "", "TOML file describing capabilities, rather than everything RAIS supports")
	viper.BindPFlag("CapabilitiesFile", pflag.CommandLine.Lookup("capabilities-file"))
	pflag.String("log-level", defaultLogLevel, "Log level: the server will only log notifications at "+
		"this level and above (must be DEBUG, INFO, WARN, ERROR, or CRIT)")
	viper.BindPFlag("LogLevel", pflag.CommandLine.Lookup("log-level"))
	pflag.Int64("image-max-area", math.MaxInt64, "Maximum area (w x h) of images to be served")
	viper.BindPFlag("ImageMaxArea", pflag.CommandLine.Lookup("image-max-area"))
	pflag.Int("image-max-width", math.MaxInt32, "Maximum width of images to be served")
	viper.BindPFlag("ImageMaxWidth", pflag.CommandLine.Lookup("image-max-width"))
	pflag.Int("image-max-height", math.MaxInt32, "Maximum height of images to be served")
	viper.BindPFlag("ImageMaxHeight", pflag.CommandLine.Lookup("image-max-height"))

	pflag.Parse()

	// Make sure required values exist
	if !viper.IsSet("TilePath") {
		fmt.Println("ERROR: --tile-path is required")
		pflag.Usage()
		os.Exit(1)
	}

	// Make sure we have a valid log level
	var level = logger.LogLevelFromString(viper.GetString("LogLevel"))
	if level == logger.Invalid {
		fmt.Println("ERROR: --log-level must be DEBUG, INFO, WARN, ERROR, or CRIT")
		pflag.Usage()
		os.Exit(1)
	}
	Logger = logger.New(level)
	openjpeg.Logger = Logger
	magick.Logger = Logger

	LoadPlugins(Logger)

	// Pull all values we need for all cases
	tilePath = viper.GetString("TilePath")
	address := viper.GetString("Address")

	ih := NewImageHandler(tilePath)
	ih.Maximums.Area = viper.GetInt64("ImageMaxArea")
	ih.Maximums.Width = viper.GetInt("ImageMaxWidth")
	ih.Maximums.Height = viper.GetInt("ImageMaxHeight")

	// Handle IIIF data only if we have a IIIF URL
	iiifURL := viper.GetString("IIIFURL")
	if iiifURL == "" {
		fmt.Println("ERROR: --iiif-url must be set to the server's public URL")
		pflag.Usage()
		os.Exit(1)
	}

	Logger.Debugf("Attempting to start up IIIF at %s", viper.GetString("IIIFURL"))
	iiifBase, err := url.Parse(iiifURL)
	if err == nil && iiifBase.Scheme == "" {
		err = fmt.Errorf("empty scheme")
	}
	if err == nil && iiifBase.Host == "" {
		err = fmt.Errorf("empty host")
	}
	if err == nil && iiifBase.Path == "" {
		err = fmt.Errorf("empty path")
	}
	if err != nil {
		Logger.Fatalf("Invalid IIIF URL (%s) specified: %s", iiifURL, err)
	}

	icl := viper.GetInt("InfoCacheLen")
	if icl > 0 {
		infoCache, err = lru.New(icl)
		if err != nil {
			Logger.Fatalf("Unable to start info cache: %s", err)
		}
	}

	tcl := viper.GetInt("TileCacheLen")
	if tcl > 0 {
		Logger.Debugf("Creating a tile cache to hold up to %d tiles", tcl)
		tileCache, err = lru.New2Q(tcl)
		if err != nil {
			Logger.Fatalf("Unable to start info cache: %s", err)
		}
	}

	Logger.Infof("IIIF enabled at %s", iiifBase.String())
	ih.EnableIIIF(iiifBase)

	capfile := viper.GetString("CapabilitiesFile")
	if capfile != "" {
		ih.FeatureSet = &iiif.FeatureSet{}
		_, err := toml.DecodeFile(capfile, &ih.FeatureSet)
		if err != nil {
			Logger.Fatalf("Invalid file or formatting in capabilities file '%s'", capfile)
		}
		Logger.Debugf("Setting IIIF capabilities from file '%s'", capfile)
	}

	handle(ih.IIIFBase.Path+"/", http.HandlerFunc(ih.IIIFRoute))
	handle("/images/dzi/", http.HandlerFunc(ih.DZIRoute))
	handle("/version", http.HandlerFunc(VersionHandler))

	if tileCache != nil {
		go func() {
			for {
				time.Sleep(time.Minute * 10)
				Logger.Infof("Cache hits: %d; cache misses: %d", cacheHits, cacheMisses)
			}
		}()
	}

	Logger.Infof("RAIS v%s starting...", version.Version)
	var srv = &http.Server{
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 30 * time.Second,
		Addr:         address,
	}

	var wait sync.WaitGroup

	interrupts.TrapIntTerm(func() {
		wait.Add(1)
		Logger.Infof("Stopping RAIS...")
		srv.Shutdown(nil)

		if len(teardownPlugins) > 0 {
			Logger.Infof("Tearing down plugins")
			for _, plug := range teardownPlugins {
				plug()
			}
			Logger.Infof("Plugin teardown complete")
		}

		Logger.Infof("Stopped")
		wait.Done()
	})

	if err := srv.ListenAndServe(); err != nil {
		// Don't report a fatal error when we close the server
		if err != http.ErrServerClosed {
			Logger.Fatalf("Error starting listener: %s", err)
		}
	}
	wait.Wait()
}

// handle sends the pattern and raw handler to plugins, and sets up routing on
// whatever is returned (if anything).  All plugins which wrap handlers are
// allowed to run, but the behavior could definitely get weird depending on
// what a given plugin does.  Ye be warned.
func handle(pattern string, handler http.Handler) {
	for _, plug := range wrapHandlerPlugins {
		var h2, err = plug(pattern, handler)
		if err == nil {
			handler = h2
		} else if err != plugins.ErrSkipped {
			logger.Fatalf("Error trying to wrap handler %q: %s", pattern, err)
		}
	}
	http.Handle(pattern, handler)
}

// VersionHandler spits out the raw version string to the browser
func VersionHandler(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte(version.Version))
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 state_test

import (
	"errors"
	"github.com/jacobsa/comeback/backup"
	"github.com/jacobsa/comeback/backup/mock"
	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/fs"
	"github.com/jacobsa/comeback/fs/mock"
	"github.com/jacobsa/comeback/state"
	. "github.com/jacobsa/oglematchers"
	"github.com/jacobsa/oglemock"
	. "github.com/jacobsa/ogletest"
	"testing"
	"time"
)

func TestScoreMapSaver(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type ScoreMapSaverTest struct {
	sourceMap  state.ScoreMap
	sinkMap    state.ScoreMap
	fileSystem mock_fs.MockFileSystem
	wrapped    mock_backup.MockFileSaver
	saver      backup.FileSaver

	path   string
	scores []blob.Score
	err    error
}

func init() { RegisterTestSuite(&ScoreMapSaverTest{}) }

func (t *ScoreMapSaverTest) SetUp(i *TestInfo) {
	t.sourceMap = state.NewScoreMap()
	t.sinkMap = state.NewScoreMap()
	t.fileSystem = mock_fs.NewMockFileSystem(i.MockController, "fileSystem")
	t.wrapped = mock_backup.NewMockFileSaver(i.MockController, "wrapped")

	t.saver = state.NewScoreMapFileSaver(
		t.sourceMap,
		t.sinkMap,
		t.fileSystem,
		t.wrapped,
	)
}

func (t *ScoreMapSaverTest) call() {
	t.scores, t.err = t.saver.Save(t.path)
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *ScoreMapSaverTest) CallsStat() {
	t.path = "taco"

	// File system
	ExpectCall(t.fileSystem, "Stat")("taco").
		WillOnce(oglemock.Return(fs.DirectoryEntry{}, errors.New("")))

	// Call
	t.call()
}

func (t *ScoreMapSaverTest) StatReturnsError() {
	// File system
	ExpectCall(t.fileSystem, "Stat")(Any()).
		WillOnce(oglemock.Return(fs.DirectoryEntry{}, errors.New("taco")))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("Stat")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *ScoreMapSaverTest) ScoreMapContainsEntry() {
	t.path = "taco"

	expectedKey := state.ScoreMapKey{
		Path:        "taco",
		Permissions: 0644,
		Uid:         17,
		Gid:         19,
		MTime:       time.Now(),
		Inode:       23,
		Size:        29,
	}

	// Source map
	expectedScores := []blob.Score{
		blob.ComputeScore([]byte("foo")),
		blob.ComputeScore([]byte("bar")),
	}

	t.sourceMap.Set(expectedKey, expectedScores)

	// File system
	entry := fs.DirectoryEntry{
		Permissions: 0644,
		Uid:         17,
		Gid:         19,
		MTime:       expectedKey.MTime,
		Inode:       23,
		Size:        29,
	}

	ExpectCall(t.fileSystem, "Stat")(Any()).
		WillOnce(oglemock.Return(entry, nil))

	// Call
	t.call()

	AssertEq(nil, t.err)
	ExpectThat(t.scores, DeepEquals(expectedScores))
}

func (t *ScoreMapSaverTest) CallsWrapped() {
	t.path = "taco"

	// File system
	ExpectCall(t.fileSystem, "Stat")(Any()).
		WillOnce(oglemock.Return(fs.DirectoryEntry{}, nil))

	// Wrapped
	ExpectCall(t.wrapped, "Save")("taco").
		WillOnce(oglemock.Return(nil, errors.New("")))

	// Call
	t.call()
}

func (t *ScoreMapSaverTest) WrappedReturnsError() {
	expectedKey := state.ScoreMapKey{
		Path:        "taco",
		Permissions: 0644,
		Uid:         17,
		Gid:         19,
		MTime:       time.Now(),
		Inode:       23,
		Size:        29,
	}

	// File system
	entry := fs.DirectoryEntry{
		Permissions: 0644,
		Uid:         17,
		Gid:         19,
		MTime:       expectedKey.MTime,
		Inode:       23,
		Size:        29,
	}

	ExpectCall(t.fileSystem, "Stat")(Any()).
		WillOnce(oglemock.Return(entry, nil))

	// Wrapped
	ExpectCall(t.wrapped, "Save")(Any()).
		WillOnce(oglemock.Return(nil, errors.New("taco")))

	// Call
	t.call()

	AssertThat(t.err, Error(Equals("taco")))
	ExpectEq(nil, t.sinkMap.Get(expectedKey))
}

func (t *ScoreMapSaverTest) WrappedReturnsScores() {
	ExpectEq("TODO", "")
}
<commit_msg>ScoreMapSaverTest.WrappedReturnsScores<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 state_test

import (
	"errors"
	"github.com/jacobsa/comeback/backup"
	"github.com/jacobsa/comeback/backup/mock"
	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/fs"
	"github.com/jacobsa/comeback/fs/mock"
	"github.com/jacobsa/comeback/state"
	. "github.com/jacobsa/oglematchers"
	"github.com/jacobsa/oglemock"
	. "github.com/jacobsa/ogletest"
	"testing"
	"time"
)

func TestScoreMapSaver(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type ScoreMapSaverTest struct {
	sourceMap  state.ScoreMap
	sinkMap    state.ScoreMap
	fileSystem mock_fs.MockFileSystem
	wrapped    mock_backup.MockFileSaver
	saver      backup.FileSaver

	path   string
	scores []blob.Score
	err    error
}

func init() { RegisterTestSuite(&ScoreMapSaverTest{}) }

func (t *ScoreMapSaverTest) SetUp(i *TestInfo) {
	t.sourceMap = state.NewScoreMap()
	t.sinkMap = state.NewScoreMap()
	t.fileSystem = mock_fs.NewMockFileSystem(i.MockController, "fileSystem")
	t.wrapped = mock_backup.NewMockFileSaver(i.MockController, "wrapped")

	t.saver = state.NewScoreMapFileSaver(
		t.sourceMap,
		t.sinkMap,
		t.fileSystem,
		t.wrapped,
	)
}

func (t *ScoreMapSaverTest) call() {
	t.scores, t.err = t.saver.Save(t.path)
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *ScoreMapSaverTest) CallsStat() {
	t.path = "taco"

	// File system
	ExpectCall(t.fileSystem, "Stat")("taco").
		WillOnce(oglemock.Return(fs.DirectoryEntry{}, errors.New("")))

	// Call
	t.call()
}

func (t *ScoreMapSaverTest) StatReturnsError() {
	// File system
	ExpectCall(t.fileSystem, "Stat")(Any()).
		WillOnce(oglemock.Return(fs.DirectoryEntry{}, errors.New("taco")))

	// Call
	t.call()

	ExpectThat(t.err, Error(HasSubstr("Stat")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *ScoreMapSaverTest) ScoreMapContainsEntry() {
	t.path = "taco"

	expectedKey := state.ScoreMapKey{
		Path:        "taco",
		Permissions: 0644,
		Uid:         17,
		Gid:         19,
		MTime:       time.Now(),
		Inode:       23,
		Size:        29,
	}

	// Source map
	expectedScores := []blob.Score{
		blob.ComputeScore([]byte("foo")),
		blob.ComputeScore([]byte("bar")),
	}

	t.sourceMap.Set(expectedKey, expectedScores)

	// File system
	entry := fs.DirectoryEntry{
		Permissions: 0644,
		Uid:         17,
		Gid:         19,
		MTime:       expectedKey.MTime,
		Inode:       23,
		Size:        29,
	}

	ExpectCall(t.fileSystem, "Stat")(Any()).
		WillOnce(oglemock.Return(entry, nil))

	// Call
	t.call()

	AssertEq(nil, t.err)
	ExpectThat(t.scores, DeepEquals(expectedScores))
}

func (t *ScoreMapSaverTest) CallsWrapped() {
	t.path = "taco"

	// File system
	ExpectCall(t.fileSystem, "Stat")(Any()).
		WillOnce(oglemock.Return(fs.DirectoryEntry{}, nil))

	// Wrapped
	ExpectCall(t.wrapped, "Save")("taco").
		WillOnce(oglemock.Return(nil, errors.New("")))

	// Call
	t.call()
}

func (t *ScoreMapSaverTest) WrappedReturnsError() {
	expectedKey := state.ScoreMapKey{
		Path:        "taco",
		Permissions: 0644,
		Uid:         17,
		Gid:         19,
		MTime:       time.Now(),
		Inode:       23,
		Size:        29,
	}

	// File system
	entry := fs.DirectoryEntry{
		Permissions: 0644,
		Uid:         17,
		Gid:         19,
		MTime:       expectedKey.MTime,
		Inode:       23,
		Size:        29,
	}

	ExpectCall(t.fileSystem, "Stat")(Any()).
		WillOnce(oglemock.Return(entry, nil))

	// Wrapped
	ExpectCall(t.wrapped, "Save")(Any()).
		WillOnce(oglemock.Return(nil, errors.New("taco")))

	// Call
	t.call()

	AssertThat(t.err, Error(Equals("taco")))
	ExpectEq(nil, t.sinkMap.Get(expectedKey))
}

func (t *ScoreMapSaverTest) WrappedReturnsScores() {
	expectedKey := state.ScoreMapKey{
		Path:        "taco",
		Permissions: 0644,
		Uid:         17,
		Gid:         19,
		MTime:       time.Now(),
		Inode:       23,
		Size:        29,
	}

	// File system
	entry := fs.DirectoryEntry{
		Permissions: 0644,
		Uid:         17,
		Gid:         19,
		MTime:       expectedKey.MTime,
		Inode:       23,
		Size:        29,
	}

	ExpectCall(t.fileSystem, "Stat")(Any()).
		WillOnce(oglemock.Return(entry, nil))

	// Wrapped
	expectedScores := []blob.Score{
		blob.ComputeScore([]byte("foo")),
		blob.ComputeScore([]byte("bar")),
	}

	ExpectCall(t.wrapped, "Save")(Any()).
		WillOnce(oglemock.Return(expectedScores, nil))

	// Call
	t.call()

	AssertEq(nil, t.err)

	ExpectThat(t.sinkMap.Get(expectedKey), DeepEquals(expectedScores))
}
<|endoftext|>