<commit_before>package main

import (
	"sort"
	"strings"

	"github.com/nsf/termbox-go"
)

type ResultCollection []*Result

func (rc ResultCollection) Len() int {
	return len(rc)
}
func (rc ResultCollection) Swap(i, j int) {
	rc[i], rc[j] = rc[j], rc[i]
}
func (rc ResultCollection) Less(i, j int) bool {
	return rc[i].score > rc[j].score
}

type Results struct {
	// Array of results to be filtered
	allresults ResultCollection
	results    ResultCollection

	// Current user input
	lastuserinput string

	// Visible result lines
	top_result    int
	bottom_result int

	// Total number of results
	result_count int

	// Index of currently selected line
	result_selected int

	// View size
	x, y, h, w int
}

func (r *Results) SelectFirst() {
	r.result_selected = 0
	r.top_result = 0

	if r.result_count > r.h {
		r.bottom_result = r.h
	} else {
		r.bottom_result = r.result_count
	}
}

func (r *Results) SelectPrevious() *Result {
	if r.result_selected > 0 {
		r.result_selected--
	}
	if r.top_result > 0 {
		r.top_result--
		r.bottom_result--
	}

	return r.results[r.result_selected]
}

func (r *Results) SelectNext() *Result {
	if r.result_selected < (r.result_count - 1) {
		r.result_selected++

		if r.result_selected >= r.bottom_result {
			r.top_result++
			r.bottom_result++
		}
	}

	return r.results[r.result_selected]
}

func (r *Results) Insert(s string) {
	result := new(Result)
	result.contents = strings.ToLower(s)
	result.displayContents = s
	r.allresults = append(r.allresults, result)
	r.result_count++
}

func (r *Results) Queue(s string) {
}

func tbprint(x, y int, fg, bg termbox.Attribute, msg string) {
	for _, c := range msg {
		termbox.SetCell(x, y, c, fg, bg)
		x++
	}
}

func (r *Results) Draw() {

	tclear(r.x, r.y, r.w, r.h)

	cy := r.y

	for cnt, res := range r.results[r.top_result:r.bottom_result] {
		is_selected := (cnt + r.top_result) == r.result_selected
		res.Draw(r.x, cy, r.w, is_selected)
		cy++
	}
}

func (r *Results) ToggleMark() {
	if r.result_count > 0 {
		r.results[r.result_selected].marked = !r.results[r.result_selected].marked
		r.SelectNext()
	}
}

func (r *Results) ToggleMarkAll() {
	for _, res := range r.results {
		res.marked = !res.marked
	}
}

func (r *Results) SetSize(x, y, w, h int) {
	r.x, r.y, r.w, r.h = x, y, w, h

	r.top_result = 0
	if r.result_count > r.h {
		r.bottom_result = r.h
	} else {
		r.bottom_result = r.result_count
	}
}

func (r *Results) CopyAll() {
	r.results = r.allresults
}

func (r *Results) Filter(userinput string, keypressed chan bool) {
	if len(userinput) == 0 {
		r.results = r.allresults
		r.result_count = len(r.allresults)

		for _, res := range r.results {
			res.highlighted = nil
		}

		r.SelectFirst()
		return
	}

	initialset := r.allresults

	// Optimization
	// Now invalid because results are changing...
	// if len(r.lastuserinput) > 0 && strings.HasPrefix(userinput, r.lastuserinput) {
	// 	initialset = r.results
	// 	if len(r.results) == 0 {
	// 		r.result_count = 0
	// 		r.SelectFirst()
	// 		return
	// 	}
	// }
	// r.lastuserinput = userinput

	r.results = make([]*Result, 0, 100)
	r.result_count = 0

	// Filter
	rchan := make(chan *Result)
	quit := make(chan bool)

	go func() {
		for _, entry := range initialset {
			best := score2(entry.contents, userinput)
			entry.score, entry.highlighted = best.score, best.highlight
			rchan <- entry
		}
		quit <- true
	}()

	// Cancellable
Loop:
	for {
		select {
		case res := <-rchan:
			if res.score > 0 {
				r.results = append(r.results, res)
				r.result_count++
			}
		case <-quit:
			break Loop
		case <-keypressed:
			return
		}
	}

	// Sort
	sort.Sort(r.results)

	// TODO: better cursor behaviouree
	r.SelectFirst()

}

func (r *Results) GetSelected() *Result {
	return r.results[r.result_selected]
}
<commit_msg>Renames<commit_after>package main

import (
	"sort"
	"strings"

	"github.com/nsf/termbox-go"
)

type ResultCollection []*Result

func (rc ResultCollection) Len() int {
	return len(rc)
}
func (rc ResultCollection) Swap(i, j int) {
	rc[i], rc[j] = rc[j], rc[i]
}
func (rc ResultCollection) Less(i, j int) bool {
	return rc[i].score > rc[j].score
}

type Results struct {
	// Array of results to be filtered
	initialset ResultCollection
	currentset ResultCollection

	// Current user input
	lastuserinput string

	// Visible result lines
	top_result    int
	bottom_result int

	// Total number of results
	result_count int

	// Index of currently selected line
	result_selected int

	// View size
	x, y, h, w int
}

func (r *Results) SelectFirst() {
	r.result_selected = 0
	r.top_result = 0

	if r.result_count > r.h {
		r.bottom_result = r.h
	} else {
		r.bottom_result = r.result_count
	}
}

func (r *Results) SelectPrevious() *Result {
	if r.result_selected > 0 {
		r.result_selected--
	}
	if r.top_result > 0 {
		r.top_result--
		r.bottom_result--
	}

	return r.currentset[r.result_selected]
}

func (r *Results) SelectNext() *Result {
	if r.result_selected < (r.result_count - 1) {
		r.result_selected++

		if r.result_selected >= r.bottom_result {
			r.top_result++
			r.bottom_result++
		}
	}

	return r.currentset[r.result_selected]
}

func (r *Results) Insert(s string) {
	result := new(Result)
	result.contents = strings.ToLower(s)
	result.displayContents = s
	r.initialset = append(r.initialset, result)
	r.result_count++
}

func (r *Results) Queue(s string) {
}

func tbprint(x, y int, fg, bg termbox.Attribute, msg string) {
	for _, c := range msg {
		termbox.SetCell(x, y, c, fg, bg)
		x++
	}
}

func (r *Results) Draw() {

	tclear(r.x, r.y, r.w, r.h)

	cy := r.y

	for cnt, res := range r.currentset[r.top_result:r.bottom_result] {
		is_selected := (cnt + r.top_result) == r.result_selected
		res.Draw(r.x, cy, r.w, is_selected)
		cy++
	}
}

func (r *Results) ToggleMark() {
	if r.result_count > 0 {
		r.currentset[r.result_selected].marked = !r.currentset[r.result_selected].marked
		r.SelectNext()
	}
}

func (r *Results) ToggleMarkAll() {
	for _, res := range r.currentset {
		res.marked = !res.marked
	}
}

func (r *Results) SetSize(x, y, w, h int) {
	r.x, r.y, r.w, r.h = x, y, w, h

	r.top_result = 0
	if r.result_count > r.h {
		r.bottom_result = r.h
	} else {
		r.bottom_result = r.result_count
	}
}

func (r *Results) CopyAll() {
	r.currentset = r.initialset
}

func (r *Results) Filter(userinput string, keypressed chan bool) {
	if len(userinput) == 0 {
		r.currentset = r.initialset
		r.result_count = len(r.initialset)

		for _, res := range r.currentset {
			res.highlighted = nil
		}

		r.SelectFirst()
		return
	}

	// Optimization
	// Now invalid because results are changing...
	// if len(r.lastuserinput) > 0 && strings.HasPrefix(userinput, r.lastuserinput) {
	// 	initialset = r.currentset
	// 	if len(r.currentset) == 0 {
	// 		r.result_count = 0
	// 		r.SelectFirst()
	// 		return
	// 	}
	// }
	// r.lastuserinput = userinput

	r.currentset = make([]*Result, 0, 100)
	r.result_count = 0

	// Filter
	rchan := make(chan *Result)
	quit := make(chan bool)

	go func() {
		for _, entry := range r.initialset {
			best := score2(entry.contents, userinput)
			entry.score, entry.highlighted = best.score, best.highlight
			rchan <- entry
		}
		quit <- true
	}()

	// Cancellable
Loop:
	for {
		select {
		case res := <-rchan:
			if res.score > 0 {
				r.currentset = append(r.currentset, res)
				r.result_count++
			}
		case <-quit:
			break Loop
		case <-keypressed:
			return
		}
	}

	// Sort
	sort.Sort(r.currentset)

	// TODO: better cursor behaviouree
	r.SelectFirst()

}

func (r *Results) GetSelected() *Result {
	return r.currentset[r.result_selected]
}
<|endoftext|>


<commit_before>package gyazo

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"github.com/google/go-querystring/query"
)

const (
	version         = "0.1"
	defaultEndpoint = "https://api.gyazo.com"
	uploadEndpoint  = "https://upload.gyazo.com"
)

// Client manages communication with the Gyazo API
type Client struct {
	// Gyazo API access token.
	token string

	// client provides request to API endpoints.
	client *http.Client

	// DefaultEndpint is Gyazo API endpoint.
	DefaultEndpoint string

	// UploadEndpint is Gyazo upload API endpoint.
	UploadEndpoint string
}

// Image represents a uploaded image.
//
// Gyazo API docs: https://gyazo.com/api/docs/image
type Image struct {
	ID           string `json:"image_id"`
	PermalinkURL string `json:"permalink_url"`
	ThumbURL     string `json:"thumb_url"`
	URL          string `json:"url"`
	Type         string `json:"type"`
}

// ListOptions specifies the optional parameters to the List.
type ListOptions struct {
	Page    int `url:"page,omitempty"`
	PerPage int `url:"per_page,omitempty"`
}

// NewClient returns a new Gyazo API client.
func NewClient(token string) (*Client, error) {
	if token == "" {
		return nil, errors.New("access token is empty")
	}

	c := &Client{token, http.DefaultClient, defaultEndpoint, uploadEndpoint}
	return c, nil
}

// List returns user images
func (c *Client) List(opts *ListOptions) (*[]Image, error) {
	url := c.DefaultEndpoint + "/api/images"
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.token))

	// Build and set query parameters
	if opts != nil {
		params, err := query.Values(opts)
		if err != nil {
			return nil, err
		}
		req.URL.RawQuery = params.Encode()
	}

	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return nil, errors.New(res.Status)
	}

	list := new([]Image)
	if err = json.NewDecoder(res.Body).Decode(&list); err != nil {
		return nil, err
	}
	return list, nil
}
<commit_msg>Add some property to parse a response json<commit_after>package gyazo

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"github.com/google/go-querystring/query"
)

const (
	version         = "0.1"
	defaultEndpoint = "https://api.gyazo.com"
	uploadEndpoint  = "https://upload.gyazo.com"
)

// Client manages communication with the Gyazo API
type Client struct {
	// Gyazo API access token.
	token string

	// client provides request to API endpoints.
	client *http.Client

	// DefaultEndpint is Gyazo API endpoint.
	DefaultEndpoint string

	// UploadEndpint is Gyazo upload API endpoint.
	UploadEndpoint string
}

// Image represents a uploaded image.
//
// Gyazo API docs: https://gyazo.com/api/docs/image
type Image struct {
	ID           string `json:"image_id"`
	PermalinkURL string `json:"permalink_url"`
	ThumbURL     string `json:"thumb_url"`
	URL          string `json:"url"`
	Type         string `json:"type"`
	Star         string `json:"star"`
	CreatedAt    string `json:"created_at"`
}

// ListOptions specifies the optional parameters to the List.
type ListOptions struct {
	Page    int `url:"page,omitempty"`
	PerPage int `url:"per_page,omitempty"`
}

// NewClient returns a new Gyazo API client.
func NewClient(token string) (*Client, error) {
	if token == "" {
		return nil, errors.New("access token is empty")
	}

	c := &Client{token, http.DefaultClient, defaultEndpoint, uploadEndpoint}
	return c, nil
}

// List returns user images
func (c *Client) List(opts *ListOptions) (*[]Image, error) {
	url := c.DefaultEndpoint + "/api/images"
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.token))

	// Build and set query parameters
	if opts != nil {
		params, err := query.Values(opts)
		if err != nil {
			return nil, err
		}
		req.URL.RawQuery = params.Encode()
	}

	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		return nil, errors.New(res.Status)
	}

	list := new([]Image)
	if err = json.NewDecoder(res.Body).Decode(&list); err != nil {
		return nil, err
	}
	return list, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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 defaultserviceplan

import (
	"errors"
	"fmt"
	"io"

	"github.com/golang/glog"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apiserver/pkg/admission"

	informers "github.com/kubernetes-incubator/service-catalog/pkg/client/informers_generated/internalversion"
	internalversion "github.com/kubernetes-incubator/service-catalog/pkg/client/listers_generated/servicecatalog/internalversion"

	"github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog"
	scadmission "github.com/kubernetes-incubator/service-catalog/pkg/apiserver/admission"
)

const (
	// PluginName is name of admission plug-in
	PluginName = "DefaultServicePlan"
)

// Register registers a plugin
func Register(plugins *admission.Plugins) {
	plugins.Register(PluginName, func(io.Reader) (admission.Interface, error) {
		return NewDefaultServicePlan()
	})
}

// exists is an implementation of admission.Interface.
// It checks to see if Service Instance is being created without
// a Service Plan if there is only one Service Plan for the
// specified Service and defaults to that value.
// that the cluster actually has support for it.
type defaultServicePlan struct {
	*admission.Handler
	scLister internalversion.ServiceClassLister
}

var _ = scadmission.WantsInternalServiceCatalogInformerFactory(&defaultServicePlan{})

func (d *defaultServicePlan) Admit(a admission.Attributes) error {
	// we need to wait for our caches to warm
	if !d.WaitForReady() {
		return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
	}

	// We only care about service Instances
	if a.GetResource().Group != servicecatalog.GroupName || a.GetResource().GroupResource() != servicecatalog.Resource("instances") {
		return nil
	}
	instance, ok := a.GetObject().(*servicecatalog.Instance)
	if !ok {
		return apierrors.NewBadRequest("Resource was marked with kind Instance but was unable to be converted")
	}
	// If the plan is specified, let it through and have the controller
	// deal with finding the right plan, etc.
	if len(instance.Spec.PlanName) > 0 {
		return nil
	}

	sc, err := d.scLister.Get(instance.Spec.ServiceClassName)
	if err != nil {
		if !apierrors.IsNotFound(err) {
			return admission.NewForbidden(a, err)
		}
		msg := fmt.Sprintf("ServiceClass %q does not exist, can not figure out the default Service Plan.", instance.Spec.ServiceClassName)
		glog.V(4).Info(msg)
		return admission.NewForbidden(a, errors.New(msg))
	}
	if len(sc.Plans) > 1 {
		msg := fmt.Sprintf("ServiceClass %q has more than one plan, PlanName must be specified", instance.Spec.ServiceClassName)
		glog.V(4).Info(msg)
		return admission.NewForbidden(a, errors.New(msg))
	}

	p := sc.Plans[0]
	glog.V(4).Infof("Using default plan %s for Service Class %s for instance %s",
		p.Name, sc.Name, instance.Name)
	instance.Spec.PlanName = p.Name
	return nil
}

// NewDefaultServicePlan creates a new admission control handler that
// fills in a default Service Plan if omitted from Service Instance
// creation request and if there exists only one plan in the
// specified Service Class
func NewDefaultServicePlan() (admission.Interface, error) {
	return &defaultServicePlan{
		Handler: admission.NewHandler(admission.Create, admission.Update),
	}, nil
}

func (d *defaultServicePlan) SetInternalServiceCatalogInformerFactory(f informers.SharedInformerFactory) {
	scInformer := f.Servicecatalog().InternalVersion().ServiceClasses()
	d.scLister = scInformer.Lister()
	d.SetReadyFunc(scInformer.Informer().HasSynced)
}

func (d *defaultServicePlan) Validate() error {
	if d.scLister == nil {
		return errors.New("missing service class lister")
	}
	return nil
}
<commit_msg>address one more PR comment<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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 defaultserviceplan

import (
	"errors"
	"fmt"
	"io"

	"github.com/golang/glog"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apiserver/pkg/admission"

	informers "github.com/kubernetes-incubator/service-catalog/pkg/client/informers_generated/internalversion"
	internalversion "github.com/kubernetes-incubator/service-catalog/pkg/client/listers_generated/servicecatalog/internalversion"

	"github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog"
	scadmission "github.com/kubernetes-incubator/service-catalog/pkg/apiserver/admission"
)

const (
	// PluginName is name of admission plug-in
	PluginName = "DefaultServicePlan"
)

// Register registers a plugin
func Register(plugins *admission.Plugins) {
	plugins.Register(PluginName, func(io.Reader) (admission.Interface, error) {
		return NewDefaultServicePlan()
	})
}

// exists is an implementation of admission.Interface.
// It checks to see if Service Instance is being created without
// a Service Plan if there is only one Service Plan for the
// specified Service and defaults to that value.
// that the cluster actually has support for it.
type defaultServicePlan struct {
	*admission.Handler
	scLister internalversion.ServiceClassLister
}

var _ = scadmission.WantsInternalServiceCatalogInformerFactory(&defaultServicePlan{})

func (d *defaultServicePlan) Admit(a admission.Attributes) error {
	// we need to wait for our caches to warm
	if !d.WaitForReady() {
		return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
	}

	// We only care about service Instances
	if a.GetResource().Group != servicecatalog.GroupName || a.GetResource().GroupResource() != servicecatalog.Resource("instances") {
		return nil
	}
	instance, ok := a.GetObject().(*servicecatalog.Instance)
	if !ok {
		return apierrors.NewBadRequest("Resource was marked with kind Instance but was unable to be converted")
	}
	// If the plan is specified, let it through and have the controller
	// deal with finding the right plan, etc.
	if len(instance.Spec.PlanName) > 0 {
		return nil
	}

	sc, err := d.scLister.Get(instance.Spec.ServiceClassName)
	if err != nil {
		if !apierrors.IsNotFound(err) {
			return admission.NewForbidden(a, err)
		}
		msg := fmt.Sprintf("ServiceClass %q does not exist, can not figure out the default Service Plan.", instance.Spec.ServiceClassName)
		glog.V(4).Info(msg)
		return admission.NewForbidden(a, errors.New(msg))
	}
	if len(sc.Plans) > 1 {
		msg := fmt.Sprintf("ServiceClass %q has more than one plan, PlanName must be specified", instance.Spec.ServiceClassName)
		glog.V(4).Info(msg)
		return admission.NewForbidden(a, errors.New(msg))
	}

	p := sc.Plans[0]
	glog.V(4).Infof("Using default plan %q for Service Class %q for instance %s",
		p.Name, sc.Name, instance.Name)
	instance.Spec.PlanName = p.Name
	return nil
}

// NewDefaultServicePlan creates a new admission control handler that
// fills in a default Service Plan if omitted from Service Instance
// creation request and if there exists only one plan in the
// specified Service Class
func NewDefaultServicePlan() (admission.Interface, error) {
	return &defaultServicePlan{
		Handler: admission.NewHandler(admission.Create, admission.Update),
	}, nil
}

func (d *defaultServicePlan) SetInternalServiceCatalogInformerFactory(f informers.SharedInformerFactory) {
	scInformer := f.Servicecatalog().InternalVersion().ServiceClasses()
	d.scLister = scInformer.Lister()
	d.SetReadyFunc(scInformer.Informer().HasSynced)
}

func (d *defaultServicePlan) Validate() error {
	if d.scLister == nil {
		return errors.New("missing service class lister")
	}
	return nil
}
<|endoftext|>


<commit_before>package test

import (
	"os"
	"testing"

	"github.com/nbusy/devastator"
)

func TestAuth(t *testing.T) {
	// t.Fatal("Unauthorized clients cannot call any function other than method.auth and method.close") // call to randomized and all registered routes here
	// t.Fatal("Anonymous calls to method.auth and method.close should be allowed")
}

func TestValidClientCertAuth(t *testing.T) {
	s := NewServerHelper(t)
	defer s.Stop()
	c := NewClientHelper(t).DefaultCert().Dial()
	defer c.Close()
	id := c.WriteRequest("echo", nil)
	_, res, _ := c.ReadMsg(nil)

	if res.ID != id {
		t.Fatal("Authentication failed with a valid client certificate. Got server response:", res)
	}
}

// todo: no cert, no signature cert, invalid CA signed cert, expired cert...
func TestInvalidClientCertAuth(t *testing.T) {
	s := NewServerHelper(t)
	defer s.Stop()
	c := NewClientHelper(t).Dial()
	defer c.Close()

	_ = c.WriteRequest("echo", nil)

	if !c.VerifyConnClosed() {
		t.Fatal("Authenticated successfully with invalid client certificate.")
	}
}

func TestGoogleRegister(t *testing.T) {
	token := os.Getenv("GOOGLE_ACCESS_TOKEN")
	if token == "" {
		t.Skip("Missing 'GOOGLE_ACCESS_TOKEN' environment variable. Skipping Google sign-in testing.")
	}

	s := NewServerHelper(t)
	c := NewClientHelper(t).Dial()

	c.WriteRequest("auth.google", map[string]string{"accessToken": token})
	res := c.ReadRes(&devastator.CertResponse{}) // todo: we need to be able to specify return type here, otherwise we get a map[]

	if res.Error != nil {
		t.Fatal("Google+ first sign-in/registration failed with valid credentials:", res.Error)
	}

	c.Close()
	s.Stop()

	// now connect to server with our new client certificate
	r := res.Result.(*devastator.CertResponse)
	cert, key := r.Cert, r.Key

	s = NewServerHelper(t)
	c = NewClientHelper(t).Cert(cert, key).Dial()

	_ = c.WriteRequest("echo", nil)
	res = c.ReadRes(nil)

	if res.Error != nil {
		t.Fatal("Failed to connect to the server with certificates created after Google+ sign-in:", res.Error)
	}

	c.Close()
	s.Stop()
}

func TestGoogleAuth(t *testing.T) {
	s := NewServerHelper(t)
	defer s.Stop()
	c := NewClientHelper(t).Dial()
	defer c.Close()

	// t.Fatal("Google+ second sign-in (regular) failed with valid credentials")
	// t.Fatal("Google+ sign-in passed with invalid credentials")
	// t.Fatal("Authentication was not ACKed")
}
<commit_msg>rename invalid Google+ auth test<commit_after>package test

import (
	"os"
	"testing"

	"github.com/nbusy/devastator"
)

func TestAuth(t *testing.T) {
	// t.Fatal("Unauthorized clients cannot call any function other than method.auth and method.close") // call to randomized and all registered routes here
	// t.Fatal("Anonymous calls to method.auth and method.close should be allowed")
}

func TestValidClientCertAuth(t *testing.T) {
	s := NewServerHelper(t)
	defer s.Stop()
	c := NewClientHelper(t).DefaultCert().Dial()
	defer c.Close()
	id := c.WriteRequest("echo", nil)
	_, res, _ := c.ReadMsg(nil)

	if res.ID != id {
		t.Fatal("Authentication failed with a valid client certificate. Got server response:", res)
	}
}

// todo: no cert, no signature cert, invalid CA signed cert, expired cert...
func TestInvalidClientCertAuth(t *testing.T) {
	s := NewServerHelper(t)
	defer s.Stop()
	c := NewClientHelper(t).Dial()
	defer c.Close()

	_ = c.WriteRequest("echo", nil)

	if !c.VerifyConnClosed() {
		t.Fatal("Authenticated successfully with invalid client certificate.")
	}
}

func TestGoogleAuth(t *testing.T) {
	token := os.Getenv("GOOGLE_ACCESS_TOKEN")
	if token == "" {
		t.Skip("Missing 'GOOGLE_ACCESS_TOKEN' environment variable. Skipping Google sign-in testing.")
	}

	s := NewServerHelper(t)
	c := NewClientHelper(t).Dial()

	c.WriteRequest("auth.google", map[string]string{"accessToken": token})
	res := c.ReadRes(&devastator.CertResponse{}) // todo: we need to be able to specify return type here, otherwise we get a map[]

	if res.Error != nil {
		t.Fatal("Google+ first sign-in/registration failed with valid credentials:", res.Error)
	}

	c.Close()
	s.Stop()

	// now connect to server with our new client certificate
	r := res.Result.(*devastator.CertResponse)
	cert, key := r.Cert, r.Key

	s = NewServerHelper(t)
	c = NewClientHelper(t).Cert(cert, key).Dial()

	_ = c.WriteRequest("echo", nil)
	res = c.ReadRes(nil)

	if res.Error != nil {
		t.Fatal("Failed to connect to the server with certificates created after Google+ sign-in:", res.Error)
	}

	c.Close()
	s.Stop()
}

func TestInvalidGoogleAuth(t *testing.T) {
	s := NewServerHelper(t)
	defer s.Stop()
	c := NewClientHelper(t).Dial()
	defer c.Close()

	// t.Fatal("Google+ second sign-in (regular) failed with valid credentials")
	// t.Fatal("Google+ sign-in passed with invalid credentials")
}
<|endoftext|>


<commit_before>// Copyright © 2017 The Kubicorn Authors
//
// 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 resources

import (
	"testing"

	"github.com/kris-nova/kubicorn/apis/cluster"
)

func TestExpectedHappy(t *testing.T) {
	instance := InstanceGroup{
		Shared: Shared{
			Name: "SharedName",
		},
		ServerPool: &cluster.ServerPool{
			Identifier: "ClusterPool1",
			Size:       "5",
			Image:      "server-os-image",
			MaxCount:   5,
			BootstrapScripts: []string{
				"script1.sh",
			},
		},
	}

	knownCluster := &cluster.Cluster{
		Name:    "ClusterName",
		CloudId: "test-123",
		SSH: &cluster.SSH{
			PublicKeyFingerprint: "fingerprint",
		},
		Location: "Location-us",
	}

	_, resource, err := instance.Expected(knownCluster)
	if err != nil {
		t.Fatalf("Error while creating resource %v", err)
	}

	tt := []struct {
		name     string
		actual   interface{}
		expected interface{}
	}{
		{"Shared.cloudId", resource.(*InstanceGroup).Shared.CloudID, "ClusterPool1"},
		{"Size", resource.(*InstanceGroup).Size, "5"},
		{"Label Amount", len(resource.(*InstanceGroup).Labels), 1},
		{"Label group", resource.(*InstanceGroup).Labels["group"], "sharedname"},
		{"Location", resource.(*InstanceGroup).Location, "Location-us"},
		{"Image", resource.(*InstanceGroup).Image, "server-os-image"},
		{"Count", resource.(*InstanceGroup).Count, 5},
		{"SSHFingerprint", resource.(*InstanceGroup).SSHFingerprint, "fingerprint"},
		{"Bootstrapscript", resource.(*InstanceGroup).BootstrapScripts[0], "script1.sh"},
		{"Cache", resource, instance.CachedExpected},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			if tc.actual != tc.expected {
				t.Fatalf("%v should be %v got %v\n", tc.name, tc.expected, tc.actual)
			}
		})
	}
}
<commit_msg>Removed tests that where no longer needed.<commit_after>// Copyright © 2017 The Kubicorn Authors
//
// 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 resources

import (
	"testing"

	"github.com/kris-nova/kubicorn/apis/cluster"
)

func TestExpectedHappy(t *testing.T) {
	instance := InstanceGroup{
		Shared: Shared{
			Name: "SharedName",
		},
		ServerPool: &cluster.ServerPool{
			Identifier: "ClusterPool1",
			Size:       "5",
			Image:      "server-os-image",
			MaxCount:   5,
			BootstrapScripts: []string{
				"script1.sh",
			},
		},
	}

	knownCluster := &cluster.Cluster{
		Name:    "ClusterName",
		CloudId: "test-123",
		SSH: &cluster.SSH{
			PublicKeyFingerprint: "fingerprint",
		},
		Location: "Location-us",
	}

	_, resource, err := instance.Expected(knownCluster)
	if err != nil {
		t.Fatalf("Error while creating resource %v", err)
	}

	tt := []struct {
		name     string
		actual   interface{}
		expected interface{}
	}{
		{"Shared.cloudId", resource.(*InstanceGroup).Shared.CloudID, "ClusterPool1"},
		{"Size", resource.(*InstanceGroup).Size, "5"},
		{"Location", resource.(*InstanceGroup).Location, "Location-us"},
		{"Image", resource.(*InstanceGroup).Image, "server-os-image"},
		{"Count", resource.(*InstanceGroup).Count, 5},
		{"SSHFingerprint", resource.(*InstanceGroup).SSHFingerprint, "fingerprint"},
		{"Bootstrapscript", resource.(*InstanceGroup).BootstrapScripts[0], "script1.sh"},
		{"Cache", resource, instance.CachedExpected},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			if tc.actual != tc.expected {
				t.Fatalf("%v should be %v got %v\n", tc.name, tc.expected, tc.actual)
			}
		})
	}
}
<|endoftext|>


<commit_before>// Copyright © 2015 Alexey Khalyapin - halyapin@gmail.com
//
// This program or package and any associated files are licensed under the
// GNU GENERAL PUBLIC LICENSE Version 2 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.gnu.org/licenses/gpl-2.0.html

package reverse

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

var Urls *urlStore

func init() {
	Urls = &urlStore{store: make(map[string]url)}
}

// Adds url to store
func Add(urlName string, urlAddr string, params ...string) string {
	return Urls.MustAdd(urlName, urlAddr, params...)
}

// Reverse url by name
func Rev(urlName string, params ...string) string {
	return Urls.MustReverse(urlName, params...)
}

// Gets raw url by name
func Get(urlName string) string {
	return Urls.Get(urlName)
}

type url struct {
	url    string
	params []string
}

type urlStore struct {
	store map[string]url
}

// Adds a Url to the Store
func (us *urlStore) Add(urlName, urlAddr string, params ...string) (string, error) {
	return us.AddGr(urlName, "", urlAddr, params)
}

// Adds a Url and panics if error
func (us urlStore) MustAdd(urlName, urlAddr string, params ...string) string {
	addr, err := us.Add(urlName, urlAddr, params...)
	if err != nil {
		panic(err)
	}
	return addr
}

// Adds with group refix
func (us *urlStore) AddGr(urlName, group, urlAddr string, params ...string) (string, error) {
	if _, ok := us.store[urlName]; ok {
		return "", errors.New("Url already exists. Try to use .Get() method.")
	}

	tmpUrl := url{group + urlAddr, params}
	us.store[urlName] = tmpUrl
	return urlAddr, nil
}

// Adds a Url with group prefix
func (us urlStore) MustAddGr(urlName, group, urlAddr string, params ...string) string {
	addr, err := us.AddGr(urlName, group, urlAddr, params...)
	if err != nil {
		panic(err)
	}
	return addr
}

// Gets raw url string
func (us urlStore) Get(urlName string) string {
	return us.store[urlName].url
}

// Gets reversed url
func (us urlStore) Reverse(urlName string, params ...string) (string, error) {
	if len(params) != len(us.store[urlName].params) {
		return "", errors.New("Bad Url Reverse: mismatch params")
	}
	res := us.store[urlName].url
	for i, val := range params {
		res = strings.Replace(res, us.store[urlName].params[i], val, 1)
	}
	return res, nil
}

// Gets reversed url and panics if error
func (us urlStore) MustReverse(urlName string, params ...string) string {
	res, err := us.Reverse(urlName, params...)
	if err != nil {
		panic(err)
	}
	return res
}

func (us urlStore) Sting() string {
	return fmt.Sprint(us.store)
}

// For testing
func (us urlStore) getParam(urlName string, num int) string {
	return us.store[urlName].params[num]
}

<commit_msg>bugfix<commit_after>// Copyright © 2015 Alexey Khalyapin - halyapin@gmail.com
//
// This program or package and any associated files are licensed under the
// GNU GENERAL PUBLIC LICENSE Version 2 (the "License"); you may not use these files
// except in compliance with the License. You can get a copy of the License
// at: http://www.gnu.org/licenses/gpl-2.0.html

package reverse

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

var Urls *urlStore

func init() {
	Urls = &urlStore{store: make(map[string]url)}
}

// Adds url to store
func Add(urlName string, urlAddr string, params ...string) string {
	return Urls.MustAdd(urlName, urlAddr, params...)
}

// Reverse url by name
func Rev(urlName string, params ...string) string {
	return Urls.MustReverse(urlName, params...)
}

// Gets raw url by name
func Get(urlName string) string {
	return Urls.Get(urlName)
}

type url struct {
	url    string
	params []string
}

type urlStore struct {
	store map[string]url
}

// Adds a Url to the Store
func (us *urlStore) Add(urlName, urlAddr string, params ...string) (string, error) {
	return us.AddGr(urlName, "", urlAddr, params...)
}

// Adds a Url and panics if error
func (us urlStore) MustAdd(urlName, urlAddr string, params ...string) string {
	addr, err := us.Add(urlName, urlAddr, params...)
	if err != nil {
		panic(err)
	}
	return addr
}

// Adds with group refix
func (us *urlStore) AddGr(urlName, group, urlAddr string, params ...string) (string, error) {
	if _, ok := us.store[urlName]; ok {
		return "", errors.New("Url already exists. Try to use .Get() method.")
	}

	tmpUrl := url{group + urlAddr, params}
	us.store[urlName] = tmpUrl
	return urlAddr, nil
}

// Adds a Url with group prefix
func (us urlStore) MustAddGr(urlName, group, urlAddr string, params ...string) string {
	addr, err := us.AddGr(urlName, group, urlAddr, params...)
	if err != nil {
		panic(err)
	}
	return addr
}

// Gets raw url string
func (us urlStore) Get(urlName string) string {
	return us.store[urlName].url
}

// Gets reversed url
func (us urlStore) Reverse(urlName string, params ...string) (string, error) {
	if len(params) != len(us.store[urlName].params) {
		return "", errors.New("Bad Url Reverse: mismatch params")
	}
	res := us.store[urlName].url
	for i, val := range params {
		res = strings.Replace(res, us.store[urlName].params[i], val, 1)
	}
	return res, nil
}

// Gets reversed url and panics if error
func (us urlStore) MustReverse(urlName string, params ...string) string {
	res, err := us.Reverse(urlName, params...)
	if err != nil {
		panic(err)
	}
	return res
}

func (us urlStore) Sting() string {
	return fmt.Sprint(us.store)
}

// For testing
func (us urlStore) getParam(urlName string, num int) string {
	return us.store[urlName].params[num]
}

<|endoftext|>


<commit_before>package common

import "errors"
import "fmt"
import "io"
import "net"
import "net/url"
import "os"
import "strconv"
import "strings"

import "github.com/couchbase/cbauth"
import "github.com/couchbase/indexing/secondary/dcp"
import "github.com/couchbase/indexing/secondary/dcp/transport/client"

// ExcludeStrings will exclude strings in `excludes` from `strs`. preserves the
// order of `strs` in the result.
func ExcludeStrings(strs []string, excludes []string) []string {
	cache := make(map[string]bool)
	for _, s := range excludes {
		cache[s] = true
	}
	ss := make([]string, 0, len(strs))
	for _, s := range strs {
		if _, ok := cache[s]; ok == false {
			ss = append(ss, s)
		}
	}
	return ss
}

// CommonStrings returns intersection of two set of strings.
func CommonStrings(xs []string, ys []string) []string {
	ss := make([]string, 0, len(xs))
	cache := make(map[string]bool)
	for _, x := range xs {
		cache[x] = true
	}
	for _, y := range ys {
		if _, ok := cache[y]; ok {
			ss = append(ss, y)
		}
	}
	return ss
}

// HasString does membership check for a string.
func HasString(str string, strs []string) bool {
	for _, s := range strs {
		if str == s {
			return true
		}
	}
	return false
}

// ExcludeUint32 remove items from list.
func ExcludeUint32(xs []uint32, from []uint32) []uint32 {
	fromSubXs := make([]uint32, 0, len(from))
	for _, num := range from {
		if HasUint32(num, xs) == false {
			fromSubXs = append(fromSubXs, num)
		}
	}
	return fromSubXs
}

// ExcludeUint64 remove items from list.
func ExcludeUint64(xs []uint64, from []uint64) []uint64 {
	fromSubXs := make([]uint64, 0, len(from))
	for _, num := range from {
		if HasUint64(num, xs) == false {
			fromSubXs = append(fromSubXs, num)
		}
	}
	return fromSubXs
}

// RemoveUint32 delete `item` from list `xs`.
func RemoveUint32(item uint32, xs []uint32) []uint32 {
	ys := make([]uint32, 0, len(xs))
	for _, x := range xs {
		if x == item {
			continue
		}
		ys = append(ys, x)
	}
	return ys
}

// RemoveUint16 delete `item` from list `xs`.
func RemoveUint16(item uint16, xs []uint16) []uint16 {
	ys := make([]uint16, 0, len(xs))
	for _, x := range xs {
		if x == item {
			continue
		}
		ys = append(ys, x)
	}
	return ys
}

// RemoveString delete `item` from list `xs`.
func RemoveString(item string, xs []string) []string {
	ys := make([]string, 0, len(xs))
	for _, x := range xs {
		if x == item {
			continue
		}
		ys = append(ys, x)
	}
	return ys
}

// HasUint32 does membership check for a uint32 integer.
func HasUint32(item uint32, xs []uint32) bool {
	for _, x := range xs {
		if x == item {
			return true
		}
	}
	return false
}

// HasUint64 does membership check for a uint32 integer.
func HasUint64(item uint64, xs []uint64) bool {
	for _, x := range xs {
		if x == item {
			return true
		}
	}
	return false
}

// FailsafeOp can be used by gen-server implementors to avoid infinitely
// blocked API calls.
func FailsafeOp(
	reqch, respch chan []interface{},
	cmd []interface{},
	finch chan bool) ([]interface{}, error) {

	select {
	case reqch <- cmd:
		if respch != nil {
			select {
			case resp := <-respch:
				return resp, nil
			case <-finch:
				return nil, ErrorClosed
			}
		}
	case <-finch:
		return nil, ErrorClosed
	}
	return nil, nil
}

// FailsafeOpAsync is same as FailsafeOp that can be used for
// asynchronous operation, that is, caller does not wait for response.
func FailsafeOpAsync(
	reqch chan []interface{}, cmd []interface{}, finch chan bool) error {

	select {
	case reqch <- cmd:
	case <-finch:
		return ErrorClosed
	}
	return nil
}

// FailsafeOpNoblock is same as FailsafeOpAsync that can be used for
// non-blocking operation, that is, if `reqch` is full caller does not block.
func FailsafeOpNoblock(
	reqch chan []interface{}, cmd []interface{}, finch chan bool) error {

	select {
	case reqch <- cmd:
	case <-finch:
		return ErrorClosed
	default:
		return ErrorChannelFull
	}
	return nil
}

// OpError suppliments FailsafeOp used by gen-servers.
func OpError(err error, vals []interface{}, idx int) error {
	if err != nil {
		return err
	} else if vals[idx] == nil {
		return nil
	}
	return vals[idx].(error)
}

// cbauth admin authentication helper
// Uses default cbauth env variables internally to provide auth creds
type CbAuthHandler struct {
	Hostport string
	Bucket   string
}

func (ah *CbAuthHandler) GetCredentials() (string, string) {
	u, p, err := cbauth.GetHTTPServiceAuth(ah.Hostport)
	if err != nil {
		panic(err)
	}

	return u, p
}

func (ah *CbAuthHandler) AuthenticateMemcachedConn(host string, conn *memcached.Client) error {
	u, p, err := cbauth.GetMemcachedServiceAuth(host)
	if err != nil {
		panic(err)
	}
	_, err = conn.Auth(u, p)
	_, err = conn.SelectBucket(ah.Bucket)
	return err
}

// GetKVAddrs gather the list of kvnode-address based on the latest vbmap.
func GetKVAddrs(cluster, pooln, bucketn string) ([]string, error) {
	b, err := ConnectBucket(cluster, pooln, bucketn)
	if err != nil {
		return nil, err
	}
	defer b.Close()

	b.Refresh()
	m, err := b.GetVBmap(nil)
	if err != nil {
		return nil, err
	}

	kvaddrs := make([]string, 0, len(m))
	for kvaddr := range m {
		kvaddrs = append(kvaddrs, kvaddr)
	}
	return kvaddrs, nil
}

// IsIPLocal return whether `ip` address is loopback address or
// compares equal with local-IP-address.
func IsIPLocal(ip string) bool {
	netIP := net.ParseIP(ip)

	// if loopback address, return true
	if netIP.IsLoopback() {
		return true
	}

	// compare with the local ip
	if localIP, err := GetLocalIP(); err == nil {
		if localIP.Equal(netIP) {
			return true
		}
	}
	return false
}

// GetLocalIP return the first external-IP4 configured for the first
// interface connected to this node.
func GetLocalIP() (net.IP, error) {
	interfaces, err := net.Interfaces()
	if err != nil {
		return nil, err
	}
	for _, iface := range interfaces {
		if (iface.Flags & net.FlagUp) == 0 {
			continue // interface down
		}
		if (iface.Flags & net.FlagLoopback) != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return nil, err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip != nil && !ip.IsLoopback() {
				if ip = ip.To4(); ip != nil {
					return ip, nil
				}
			}
		}
	}
	return nil, errors.New("cannot find local IP address")
}

// ExitOnStdinClose is exit handler to be used with ns-server.
func ExitOnStdinClose() {
	buf := make([]byte, 4)
	for {
		_, err := os.Stdin.Read(buf)
		if err != nil {
			if err == io.EOF {
				os.Exit(0)
			}

			panic(fmt.Sprintf("Stdin: Unexpected error occured %v", err))
		}
	}
}

// GetColocatedHost find the server addr for localhost and return the same.
func GetColocatedHost(cluster string) (string, error) {
	// get vbmap from bucket connection.
	bucket, err := ConnectBucket(cluster, "default", "default")
	if err != nil {
		return "", err
	}
	defer bucket.Close()

	hostports := bucket.NodeAddresses()
	serversM := make(map[string]string)
	servers := make([]string, 0)
	for _, hostport := range hostports {
		host, _, err := net.SplitHostPort(hostport)
		if err != nil {
			return "", err
		}
		serversM[host] = hostport
		servers = append(servers, host)
	}

	for _, server := range servers {
		addrs, err := net.LookupIP(server)
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			if IsIPLocal(addr.String()) {
				return serversM[server], nil
			}
		}
	}
	return "", errors.New("unknown host")
}

func CrashOnError(err error) {
	if err != nil {
		panic(err)
	}
}

func ClusterAuthUrl(cluster string) (string, error) {
	adminUser, adminPasswd, err := cbauth.GetHTTPServiceAuth(cluster)
	if err != nil {
		return "", err
	}

	clusterUrl := url.URL{
		Scheme: "http",
		Host:   cluster,
		User:   url.UserPassword(adminUser, adminPasswd),
	}

	return clusterUrl.String(), nil
}

func ClusterUrl(cluster string) string {
	host := cluster
	if strings.HasPrefix(cluster, "http") {
		u, err := url.Parse(cluster)
		if err != nil {
			panic(err) // TODO: should we panic ?
		}
		host = u.Host
	}
	clusterUrl := url.URL{
		Scheme: "http",
		Host:   host,
	}

	return clusterUrl.String()
}

func MaybeSetEnv(key, value string) string {
	if s := os.Getenv(key); s != "" {
		return s
	}
	os.Setenv(key, value)
	return value
}

func EquivalentIP(
	raddr string,
	raddrs []string) (this string, other string, err error) {

	host, port, err := net.SplitHostPort(raddr)
	if err != nil {
		return "", "", err
	}
	netIP := net.ParseIP(host)

	for _, raddr1 := range raddrs {
		host1, port1, err := net.SplitHostPort(raddr1)
		if err != nil {
			return "", "", err
		}
		netIP1 := net.ParseIP(host1)
		// check whether ports are same.
		if port != port1 {
			continue
		}
		// check whether both are local-ip.
		if IsIPLocal(host) && IsIPLocal(host1) {
			return raddr, raddr1, nil // raddr => raddr1
		}
		// check wethere they are coming from the same remote.
		if netIP.Equal(netIP1) {
			return raddr, raddr1, nil // raddr == raddr1
		}
	}
	return raddr, raddr, nil
}

//---------------------
// SDK bucket operation
//---------------------

// ConnectBucket will instantiate a couchbase-bucket instance with cluster.
// caller's responsibility to close the bucket.
func ConnectBucket(cluster, pooln, bucketn string) (*couchbase.Bucket, error) {
	ah := &CbAuthHandler{
		Hostport: cluster,
		Bucket:   bucketn,
	}
	couch, err := couchbase.ConnectWithAuth("http://"+cluster, ah)
	if err != nil {
		return nil, err
	}
	pool, err := couch.GetPool(pooln)
	if err != nil {
		return nil, err
	}
	bucket, err := pool.GetBucket(bucketn)
	if err != nil {
		return nil, err
	}
	return bucket, err
}

// MaxVbuckets return the number of vbuckets in bucket.
func MaxVbuckets(bucket *couchbase.Bucket) (int, error) {
	count := 0
	m, err := bucket.GetVBmap(nil)
	if err == nil {
		for _, vbnos := range m {
			count += len(vbnos)
		}
	}
	return count, err
}

// BucketTs return bucket timestamp for all vbucket.
func BucketTs(bucket *couchbase.Bucket, maxvb int) (seqnos, vbuuids []uint64) {
	seqnos = make([]uint64, maxvb)
	vbuuids = make([]uint64, maxvb)
	// for all nodes in cluster
	for _, nodestat := range bucket.GetStats("vbucket-seqno") {
		// for all vbuckets
		for i := 0; i < maxvb; i++ {
			vbkey := "vb_" + strconv.Itoa(i) + ":high_seqno"
			if highseqno, ok := nodestat[vbkey]; ok {
				if s, err := strconv.Atoi(highseqno); err == nil {
					seqnos[i] = uint64(s)
				}
			}
			vbkey = "vb_" + strconv.Itoa(i) + ":uuid"
			if vbuuid, ok := nodestat[vbkey]; ok {
				if uuid, err := strconv.Atoi(vbuuid); err == nil {
					vbuuids[i] = uint64(uuid)
				}
			}
		}
	}
	return seqnos, vbuuids
}
<commit_msg>projector stat fix<commit_after>package common

import "errors"
import "fmt"
import "io"
import "net"
import "net/url"
import "os"
import "strconv"
import "strings"

import "github.com/couchbase/cbauth"
import "github.com/couchbase/indexing/secondary/dcp"
import "github.com/couchbase/indexing/secondary/dcp/transport/client"

// ExcludeStrings will exclude strings in `excludes` from `strs`. preserves the
// order of `strs` in the result.
func ExcludeStrings(strs []string, excludes []string) []string {
	cache := make(map[string]bool)
	for _, s := range excludes {
		cache[s] = true
	}
	ss := make([]string, 0, len(strs))
	for _, s := range strs {
		if _, ok := cache[s]; ok == false {
			ss = append(ss, s)
		}
	}
	return ss
}

// CommonStrings returns intersection of two set of strings.
func CommonStrings(xs []string, ys []string) []string {
	ss := make([]string, 0, len(xs))
	cache := make(map[string]bool)
	for _, x := range xs {
		cache[x] = true
	}
	for _, y := range ys {
		if _, ok := cache[y]; ok {
			ss = append(ss, y)
		}
	}
	return ss
}

// HasString does membership check for a string.
func HasString(str string, strs []string) bool {
	for _, s := range strs {
		if str == s {
			return true
		}
	}
	return false
}

// ExcludeUint32 remove items from list.
func ExcludeUint32(xs []uint32, from []uint32) []uint32 {
	fromSubXs := make([]uint32, 0, len(from))
	for _, num := range from {
		if HasUint32(num, xs) == false {
			fromSubXs = append(fromSubXs, num)
		}
	}
	return fromSubXs
}

// ExcludeUint64 remove items from list.
func ExcludeUint64(xs []uint64, from []uint64) []uint64 {
	fromSubXs := make([]uint64, 0, len(from))
	for _, num := range from {
		if HasUint64(num, xs) == false {
			fromSubXs = append(fromSubXs, num)
		}
	}
	return fromSubXs
}

// RemoveUint32 delete `item` from list `xs`.
func RemoveUint32(item uint32, xs []uint32) []uint32 {
	ys := make([]uint32, 0, len(xs))
	for _, x := range xs {
		if x == item {
			continue
		}
		ys = append(ys, x)
	}
	return ys
}

// RemoveUint16 delete `item` from list `xs`.
func RemoveUint16(item uint16, xs []uint16) []uint16 {
	ys := make([]uint16, 0, len(xs))
	for _, x := range xs {
		if x == item {
			continue
		}
		ys = append(ys, x)
	}
	return ys
}

// RemoveString delete `item` from list `xs`.
func RemoveString(item string, xs []string) []string {
	ys := make([]string, 0, len(xs))
	for _, x := range xs {
		if x == item {
			continue
		}
		ys = append(ys, x)
	}
	return ys
}

// HasUint32 does membership check for a uint32 integer.
func HasUint32(item uint32, xs []uint32) bool {
	for _, x := range xs {
		if x == item {
			return true
		}
	}
	return false
}

// HasUint64 does membership check for a uint32 integer.
func HasUint64(item uint64, xs []uint64) bool {
	for _, x := range xs {
		if x == item {
			return true
		}
	}
	return false
}

// FailsafeOp can be used by gen-server implementors to avoid infinitely
// blocked API calls.
func FailsafeOp(
	reqch, respch chan []interface{},
	cmd []interface{},
	finch chan bool) ([]interface{}, error) {

	select {
	case reqch <- cmd:
		if respch != nil {
			select {
			case resp := <-respch:
				return resp, nil
			case <-finch:
				return nil, ErrorClosed
			}
		}
	case <-finch:
		return nil, ErrorClosed
	}
	return nil, nil
}

// FailsafeOpAsync is same as FailsafeOp that can be used for
// asynchronous operation, that is, caller does not wait for response.
func FailsafeOpAsync(
	reqch chan []interface{}, cmd []interface{}, finch chan bool) error {

	select {
	case reqch <- cmd:
	case <-finch:
		return ErrorClosed
	}
	return nil
}

// FailsafeOpNoblock is same as FailsafeOpAsync that can be used for
// non-blocking operation, that is, if `reqch` is full caller does not block.
func FailsafeOpNoblock(
	reqch chan []interface{}, cmd []interface{}, finch chan bool) error {

	select {
	case reqch <- cmd:
	case <-finch:
		return ErrorClosed
	default:
		return ErrorChannelFull
	}
	return nil
}

// OpError suppliments FailsafeOp used by gen-servers.
func OpError(err error, vals []interface{}, idx int) error {
	if err != nil {
		return err
	} else if vals[idx] == nil {
		return nil
	}
	return vals[idx].(error)
}

// cbauth admin authentication helper
// Uses default cbauth env variables internally to provide auth creds
type CbAuthHandler struct {
	Hostport string
	Bucket   string
}

func (ah *CbAuthHandler) GetCredentials() (string, string) {
	u, p, err := cbauth.GetHTTPServiceAuth(ah.Hostport)
	if err != nil {
		panic(err)
	}

	return u, p
}

func (ah *CbAuthHandler) AuthenticateMemcachedConn(host string, conn *memcached.Client) error {
	u, p, err := cbauth.GetMemcachedServiceAuth(host)
	if err != nil {
		panic(err)
	}
	_, err = conn.Auth(u, p)
	_, err = conn.SelectBucket(ah.Bucket)
	return err
}

// GetKVAddrs gather the list of kvnode-address based on the latest vbmap.
func GetKVAddrs(cluster, pooln, bucketn string) ([]string, error) {
	b, err := ConnectBucket(cluster, pooln, bucketn)
	if err != nil {
		return nil, err
	}
	defer b.Close()

	b.Refresh()
	m, err := b.GetVBmap(nil)
	if err != nil {
		return nil, err
	}

	kvaddrs := make([]string, 0, len(m))
	for kvaddr := range m {
		kvaddrs = append(kvaddrs, kvaddr)
	}
	return kvaddrs, nil
}

// IsIPLocal return whether `ip` address is loopback address or
// compares equal with local-IP-address.
func IsIPLocal(ip string) bool {
	netIP := net.ParseIP(ip)

	// if loopback address, return true
	if netIP.IsLoopback() {
		return true
	}

	// compare with the local ip
	if localIP, err := GetLocalIP(); err == nil {
		if localIP.Equal(netIP) {
			return true
		}
	}
	return false
}

// GetLocalIP return the first external-IP4 configured for the first
// interface connected to this node.
func GetLocalIP() (net.IP, error) {
	interfaces, err := net.Interfaces()
	if err != nil {
		return nil, err
	}
	for _, iface := range interfaces {
		if (iface.Flags & net.FlagUp) == 0 {
			continue // interface down
		}
		if (iface.Flags & net.FlagLoopback) != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return nil, err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip != nil && !ip.IsLoopback() {
				if ip = ip.To4(); ip != nil {
					return ip, nil
				}
			}
		}
	}
	return nil, errors.New("cannot find local IP address")
}

// ExitOnStdinClose is exit handler to be used with ns-server.
func ExitOnStdinClose() {
	buf := make([]byte, 4)
	for {
		_, err := os.Stdin.Read(buf)
		if err != nil {
			if err == io.EOF {
				os.Exit(0)
			}

			panic(fmt.Sprintf("Stdin: Unexpected error occured %v", err))
		}
	}
}

// GetColocatedHost find the server addr for localhost and return the same.
func GetColocatedHost(cluster string) (string, error) {
	// get vbmap from bucket connection.
	bucket, err := ConnectBucket(cluster, "default", "default")
	if err != nil {
		return "", err
	}
	defer bucket.Close()

	hostports := bucket.NodeAddresses()
	serversM := make(map[string]string)
	servers := make([]string, 0)
	for _, hostport := range hostports {
		host, _, err := net.SplitHostPort(hostport)
		if err != nil {
			return "", err
		}
		serversM[host] = hostport
		servers = append(servers, host)
	}

	for _, server := range servers {
		addrs, err := net.LookupIP(server)
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			if IsIPLocal(addr.String()) {
				return serversM[server], nil
			}
		}
	}
	return "", errors.New("unknown host")
}

func CrashOnError(err error) {
	if err != nil {
		panic(err)
	}
}

func ClusterAuthUrl(cluster string) (string, error) {
	adminUser, adminPasswd, err := cbauth.GetHTTPServiceAuth(cluster)
	if err != nil {
		return "", err
	}

	clusterUrl := url.URL{
		Scheme: "http",
		Host:   cluster,
		User:   url.UserPassword(adminUser, adminPasswd),
	}

	return clusterUrl.String(), nil
}

func ClusterUrl(cluster string) string {
	host := cluster
	if strings.HasPrefix(cluster, "http") {
		u, err := url.Parse(cluster)
		if err != nil {
			panic(err) // TODO: should we panic ?
		}
		host = u.Host
	}
	clusterUrl := url.URL{
		Scheme: "http",
		Host:   host,
	}

	return clusterUrl.String()
}

func MaybeSetEnv(key, value string) string {
	if s := os.Getenv(key); s != "" {
		return s
	}
	os.Setenv(key, value)
	return value
}

func EquivalentIP(
	raddr string,
	raddrs []string) (this string, other string, err error) {

	host, port, err := net.SplitHostPort(raddr)
	if err != nil {
		return "", "", err
	}
	netIP := net.ParseIP(host)

	for _, raddr1 := range raddrs {
		host1, port1, err := net.SplitHostPort(raddr1)
		if err != nil {
			return "", "", err
		}
		netIP1 := net.ParseIP(host1)
		// check whether ports are same.
		if port != port1 {
			continue
		}
		// check whether both are local-ip.
		if IsIPLocal(host) && IsIPLocal(host1) {
			return raddr, raddr1, nil // raddr => raddr1
		}
		// check wethere they are coming from the same remote.
		if netIP.Equal(netIP1) {
			return raddr, raddr1, nil // raddr == raddr1
		}
	}
	return raddr, raddr, nil
}

//---------------------
// SDK bucket operation
//---------------------

// ConnectBucket will instantiate a couchbase-bucket instance with cluster.
// caller's responsibility to close the bucket.
func ConnectBucket(cluster, pooln, bucketn string) (*couchbase.Bucket, error) {
	ah := &CbAuthHandler{
		Hostport: cluster,
		Bucket:   bucketn,
	}
	couch, err := couchbase.ConnectWithAuth("http://"+cluster, ah)
	if err != nil {
		return nil, err
	}
	pool, err := couch.GetPool(pooln)
	if err != nil {
		return nil, err
	}
	bucket, err := pool.GetBucket(bucketn)
	if err != nil {
		return nil, err
	}
	return bucket, err
}

// MaxVbuckets return the number of vbuckets in bucket.
func MaxVbuckets(bucket *couchbase.Bucket) (int, error) {
	count := 0
	m, err := bucket.GetVBmap(nil)
	if err == nil {
		for _, vbnos := range m {
			count += len(vbnos)
		}
	}
	return count, err
}

// BucketTs return bucket timestamp for all vbucket.
func BucketTs(bucket *couchbase.Bucket, maxvb int) (seqnos, vbuuids []uint64) {
	seqnos = make([]uint64, maxvb)
	vbuuids = make([]uint64, maxvb)
	// for all nodes in cluster
	for _, nodestat := range bucket.GetStats("vbucket-seqno") {
		// for all vbuckets
		for i := 0; i < maxvb; i++ {
			vbno_str := strconv.Itoa(i)
			vbstatkey := "vb_" + vbno_str
			if state, ok := nodestat[vbstatkey]; ok && state == "active" {
				vbkey := "vb_" + vbno_str + ":high_seqno"
				if highseqno, ok := nodestat[vbkey]; ok {
					if s, err := strconv.Atoi(highseqno); err == nil {
						seqnos[i] = uint64(s)
					}
				}
				vbkey = "vb_" + vbno_str + ":uuid"
				if vbuuid, ok := nodestat[vbkey]; ok {
					if uuid, err := strconv.Atoi(vbuuid); err == nil {
						vbuuids[i] = uint64(uuid)
					}
				}
			}
		}
	}
	return seqnos, vbuuids
}
<|endoftext|>


<commit_before>package config

import (
	"errors"
	"fmt"
)

type DeployArgs struct {
	AWSRegion   string
	Domain      string
	TLSCert     string
	TLSKey      string
	WorkerCount int
	WorkerSize  string
}

var WorkerSizes = []string{"medium", "large", "xlarge"}

// Validate validates that flag interdependencies
func (args DeployArgs) Validate() error {
	if args.TLSKey != "" && args.TLSCert == "" {
		return errors.New("--tls-key requires --tls-cert to also be provided")
	}
	if args.TLSCert != "" && args.TLSKey == "" {
		return errors.New("--tls-cert requires --tls-key to also be provided")
	}
	if (args.TLSKey != "" || args.TLSCert != "") && args.Domain == "" {
		return errors.New("custom certificates require --domain to be provided")
	}
	if args.WorkerCount < 1 {
		return errors.New("minimum of workers is 1")
	}

	knownSize := false
	for _, size := range WorkerSizes {
		if args.WorkerSize == size {
			knownSize = true
		}
	}

	if !knownSize {
		return fmt.Errorf("unknown worker size: `%s`. Valid sizes are: %v", args.WorkerSize, WorkerSizes)
	}

	return nil
}
<commit_msg>fix linting errors in config package<commit_after>package config

import (
	"errors"
	"fmt"
)

// DeployArgs are arguments passed to the deploy command
type DeployArgs struct {
	AWSRegion   string
	Domain      string
	TLSCert     string
	TLSKey      string
	WorkerCount int
	WorkerSize  string
}

// WorkerSizes are the permitted concourse worker sizes
var WorkerSizes = []string{"medium", "large", "xlarge"}

// Validate validates that flag interdependencies
func (args DeployArgs) Validate() error {
	err := args.validateCertFields()
	if err != nil {
		return err
	}

	knownSize := false
	for _, size := range WorkerSizes {
		if args.WorkerSize == size {
			knownSize = true
		}
	}

	if !knownSize {
		return fmt.Errorf("unknown worker size: `%s`. Valid sizes are: %v", args.WorkerSize, WorkerSizes)
	}

	return nil
}

func (args DeployArgs) validateCertFields() error {
	if args.TLSKey != "" && args.TLSCert == "" {
		return errors.New("--tls-key requires --tls-cert to also be provided")
	}
	if args.TLSCert != "" && args.TLSKey == "" {
		return errors.New("--tls-cert requires --tls-key to also be provided")
	}
	if (args.TLSKey != "" || args.TLSCert != "") && args.Domain == "" {
		return errors.New("custom certificates require --domain to be provided")
	}
	if args.WorkerCount < 1 {
		return errors.New("minimum of workers is 1")
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright (C) 2018 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package model

import (
	"bytes"
	"context"
	"io/ioutil"
	"path/filepath"
	"testing"
	"time"

	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/db"
	"github.com/syncthing/syncthing/lib/db/backend"
	"github.com/syncthing/syncthing/lib/fs"
	"github.com/syncthing/syncthing/lib/protocol"
	"github.com/syncthing/syncthing/lib/scanner"
)

func TestRecvOnlyRevertDeletes(t *testing.T) {
	// Make sure that we delete extraneous files and directories when we hit
	// Revert.

	// Get us a model up and running

	m, f := setupROFolder()
	ffs := f.Filesystem()
	defer cleanupModelAndRemoveDir(m, ffs.URI())

	// Create some test data

	for _, dir := range []string{".stfolder", "ignDir", "unknownDir"} {
		must(t, ffs.MkdirAll(dir, 0755))
	}
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "ignDir/ignFile"), []byte("hello\n"), 0644))
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "unknownDir/unknownFile"), []byte("hello\n"), 0644))
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), ".stignore"), []byte("ignDir\n"), 0644))

	knownFiles := setupKnownFiles(t, ffs, []byte("hello\n"))

	// Send and index update for the known stuff

	m.Index(device1, "ro", knownFiles)
	f.updateLocalsFromScanning(knownFiles)

	size := m.GlobalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
	}

	// Start the folder. This will cause a scan, should discover the other stuff in the folder

	m.startFolder("ro")
	m.ScanFolder("ro")

	// We should now have two files and two directories.

	size = m.GlobalSize("ro")
	if size.Files != 2 || size.Directories != 2 {
		t.Fatalf("Global: expected 2 files and 2 directories: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 2 || size.Directories != 2 {
		t.Fatalf("Local: expected 2 files and 2 directories: %+v", size)
	}
	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files+size.Directories == 0 {
		t.Fatalf("ROChanged: expected something: %+v", size)
	}

	// Revert should delete the unknown stuff

	m.Revert("ro")

	// These should still exist
	for _, p := range []string{"knownDir/knownFile", "ignDir/ignFile"} {
		if _, err := ffs.Stat(p); err != nil {
			t.Error("Unexpected error:", err)
		}
	}

	// These should have been removed
	for _, p := range []string{"unknownDir", "unknownDir/unknownFile"} {
		if _, err := ffs.Stat(p); !fs.IsNotExist(err) {
			t.Error("Unexpected existing thing:", p)
		}
	}

	// We should now have one file and directory again.

	size = m.GlobalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Global: expected 1 files and 1 directories: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Local: expected 1 files and 1 directories: %+v", size)
	}
}

func TestRecvOnlyRevertNeeds(t *testing.T) {
	// Make sure that a new file gets picked up and considered latest, then
	// gets considered old when we hit Revert.

	// Get us a model up and running

	m, f := setupROFolder()
	ffs := f.Filesystem()
	defer cleanupModelAndRemoveDir(m, ffs.URI())

	// Create some test data

	must(t, ffs.MkdirAll(".stfolder", 0755))
	oldData := []byte("hello\n")
	knownFiles := setupKnownFiles(t, ffs, oldData)

	// Send and index update for the known stuff

	m.Index(device1, "ro", knownFiles)
	f.updateLocalsFromScanning(knownFiles)

	// Start the folder. This will cause a scan.

	m.startFolder("ro")
	m.ScanFolder("ro")

	// Everything should be in sync.

	size := m.GlobalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
	}
	size = m.NeedSize("ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("Need: expected nothing: %+v", size)
	}
	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("ROChanged: expected nothing: %+v", size)
	}

	// Update the file.

	newData := []byte("totally different data\n")
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), newData, 0644))

	// Rescan.

	must(t, m.ScanFolder("ro"))

	// We now have a newer file than the rest of the cluster. Global state should reflect this.

	size = m.GlobalSize("ro")
	const sizeOfDir = 128
	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
		t.Fatalf("Global: expected no change due to the new file: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
		t.Fatalf("Local: expected the new file to be reflected: %+v", size)
	}
	size = m.NeedSize("ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("Need: expected nothing: %+v", size)
	}
	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files+size.Directories == 0 {
		t.Fatalf("ROChanged: expected something: %+v", size)
	}

	// We hit the Revert button. The file that was new should become old.

	m.Revert("ro")

	size = m.GlobalSize("ro")
	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
		t.Fatalf("Global: expected the global size to revert: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
		t.Fatalf("Local: expected the local size to remain: %+v", size)
	}
	size = m.NeedSize("ro")
	if size.Files != 1 || size.Bytes != int64(len(oldData)) {
		t.Fatalf("Local: expected to need the old file data: %+v", size)
	}
}

func TestRecvOnlyUndoChanges(t *testing.T) {
	testOs := &fatalOs{t}

	// Get us a model up and running

	m, f := setupROFolder()
	ffs := f.Filesystem()
	defer cleanupModelAndRemoveDir(m, ffs.URI())

	// Create some test data

	must(t, ffs.MkdirAll(".stfolder", 0755))
	oldData := []byte("hello\n")
	knownFiles := setupKnownFiles(t, ffs, oldData)

	m.fmut.Lock()
	fset := m.folderFiles["ro"]
	m.fmut.Unlock()
	folderFs := fset.MtimeFS()

	// Send and index update for the known stuff

	m.Index(device1, "ro", knownFiles)
	f.updateLocalsFromScanning(knownFiles)

	// Start the folder. This will cause a scan.

	m.startFolder("ro")
	m.ScanFolder("ro")

	// Everything should be in sync.

	size := m.GlobalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
	}
	size = m.NeedSize("ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("Need: expected nothing: %+v", size)
	}
	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("ROChanged: expected nothing: %+v", size)
	}

	// Create a file and modify another

	file := filepath.Join(ffs.URI(), "foo")
	must(t, ioutil.WriteFile(file, []byte("hello\n"), 0644))

	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), []byte("bye\n"), 0644))

	m.ScanFolder("ro")

	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files != 2 {
		t.Fatalf("Receive only: expected 2 files: %+v", size)
	}

	// Remove the file again and undo the modification

	testOs.Remove(file)
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), oldData, 0644))
	folderFs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime())

	m.ScanFolder("ro")

	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files+size.Directories+size.Deleted != 0 {
		t.Fatalf("Receive only: expected all zero: %+v", size)
	}
}

func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.FileInfo {
	t.Helper()

	must(t, ffs.MkdirAll("knownDir", 0755))
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), data, 0644))

	t0 := time.Now().Add(-1 * time.Minute)
	must(t, ffs.Chtimes("knownDir/knownFile", t0, t0))

	fi, err := ffs.Stat("knownDir/knownFile")
	if err != nil {
		t.Fatal(err)
	}
	blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil, true)
	knownFiles := []protocol.FileInfo{
		{
			Name:        "knownDir",
			Type:        protocol.FileInfoTypeDirectory,
			Permissions: 0755,
			Version:     protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
			Sequence:    42,
		},
		{
			Name:        "knownDir/knownFile",
			Type:        protocol.FileInfoTypeFile,
			Permissions: 0644,
			Size:        fi.Size(),
			ModifiedS:   fi.ModTime().Unix(),
			ModifiedNs:  int32(fi.ModTime().UnixNano() % 1e9),
			Version:     protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
			Sequence:    42,
			Blocks:      blocks,
		},
	}

	return knownFiles
}

func setupROFolder() (*model, *sendOnlyFolder) {
	w := createTmpWrapper(defaultCfg)
	fcfg := testFolderConfigTmp()
	fcfg.ID = "ro"
	fcfg.Type = config.FolderTypeReceiveOnly
	w.SetFolder(fcfg)

	m := newModel(w, myID, "syncthing", "dev", db.NewLowlevel(backend.OpenMemory()), nil)

	m.ServeBackground()

	// Folder should only be added, not started.
	m.removeFolder(fcfg)
	m.addFolder(fcfg)

	m.fmut.RLock()
	f := &sendOnlyFolder{
		folder: folder{
			stateTracker:        newStateTracker(fcfg.ID, m.evLogger),
			fset:                m.folderFiles[fcfg.ID],
			FolderConfiguration: fcfg,
		},
	}
	m.fmut.RUnlock()

	return m, f
}<commit_msg>lib/model: gofmt lol :(<commit_after>// Copyright (C) 2018 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package model

import (
	"bytes"
	"context"
	"io/ioutil"
	"path/filepath"
	"testing"
	"time"

	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/db"
	"github.com/syncthing/syncthing/lib/db/backend"
	"github.com/syncthing/syncthing/lib/fs"
	"github.com/syncthing/syncthing/lib/protocol"
	"github.com/syncthing/syncthing/lib/scanner"
)

func TestRecvOnlyRevertDeletes(t *testing.T) {
	// Make sure that we delete extraneous files and directories when we hit
	// Revert.

	// Get us a model up and running

	m, f := setupROFolder()
	ffs := f.Filesystem()
	defer cleanupModelAndRemoveDir(m, ffs.URI())

	// Create some test data

	for _, dir := range []string{".stfolder", "ignDir", "unknownDir"} {
		must(t, ffs.MkdirAll(dir, 0755))
	}
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "ignDir/ignFile"), []byte("hello\n"), 0644))
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "unknownDir/unknownFile"), []byte("hello\n"), 0644))
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), ".stignore"), []byte("ignDir\n"), 0644))

	knownFiles := setupKnownFiles(t, ffs, []byte("hello\n"))

	// Send and index update for the known stuff

	m.Index(device1, "ro", knownFiles)
	f.updateLocalsFromScanning(knownFiles)

	size := m.GlobalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
	}

	// Start the folder. This will cause a scan, should discover the other stuff in the folder

	m.startFolder("ro")
	m.ScanFolder("ro")

	// We should now have two files and two directories.

	size = m.GlobalSize("ro")
	if size.Files != 2 || size.Directories != 2 {
		t.Fatalf("Global: expected 2 files and 2 directories: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 2 || size.Directories != 2 {
		t.Fatalf("Local: expected 2 files and 2 directories: %+v", size)
	}
	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files+size.Directories == 0 {
		t.Fatalf("ROChanged: expected something: %+v", size)
	}

	// Revert should delete the unknown stuff

	m.Revert("ro")

	// These should still exist
	for _, p := range []string{"knownDir/knownFile", "ignDir/ignFile"} {
		if _, err := ffs.Stat(p); err != nil {
			t.Error("Unexpected error:", err)
		}
	}

	// These should have been removed
	for _, p := range []string{"unknownDir", "unknownDir/unknownFile"} {
		if _, err := ffs.Stat(p); !fs.IsNotExist(err) {
			t.Error("Unexpected existing thing:", p)
		}
	}

	// We should now have one file and directory again.

	size = m.GlobalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Global: expected 1 files and 1 directories: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Local: expected 1 files and 1 directories: %+v", size)
	}
}

func TestRecvOnlyRevertNeeds(t *testing.T) {
	// Make sure that a new file gets picked up and considered latest, then
	// gets considered old when we hit Revert.

	// Get us a model up and running

	m, f := setupROFolder()
	ffs := f.Filesystem()
	defer cleanupModelAndRemoveDir(m, ffs.URI())

	// Create some test data

	must(t, ffs.MkdirAll(".stfolder", 0755))
	oldData := []byte("hello\n")
	knownFiles := setupKnownFiles(t, ffs, oldData)

	// Send and index update for the known stuff

	m.Index(device1, "ro", knownFiles)
	f.updateLocalsFromScanning(knownFiles)

	// Start the folder. This will cause a scan.

	m.startFolder("ro")
	m.ScanFolder("ro")

	// Everything should be in sync.

	size := m.GlobalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
	}
	size = m.NeedSize("ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("Need: expected nothing: %+v", size)
	}
	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("ROChanged: expected nothing: %+v", size)
	}

	// Update the file.

	newData := []byte("totally different data\n")
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), newData, 0644))

	// Rescan.

	must(t, m.ScanFolder("ro"))

	// We now have a newer file than the rest of the cluster. Global state should reflect this.

	size = m.GlobalSize("ro")
	const sizeOfDir = 128
	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
		t.Fatalf("Global: expected no change due to the new file: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
		t.Fatalf("Local: expected the new file to be reflected: %+v", size)
	}
	size = m.NeedSize("ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("Need: expected nothing: %+v", size)
	}
	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files+size.Directories == 0 {
		t.Fatalf("ROChanged: expected something: %+v", size)
	}

	// We hit the Revert button. The file that was new should become old.

	m.Revert("ro")

	size = m.GlobalSize("ro")
	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(oldData)) {
		t.Fatalf("Global: expected the global size to revert: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 1 || size.Bytes != sizeOfDir+int64(len(newData)) {
		t.Fatalf("Local: expected the local size to remain: %+v", size)
	}
	size = m.NeedSize("ro")
	if size.Files != 1 || size.Bytes != int64(len(oldData)) {
		t.Fatalf("Local: expected to need the old file data: %+v", size)
	}
}

func TestRecvOnlyUndoChanges(t *testing.T) {
	testOs := &fatalOs{t}

	// Get us a model up and running

	m, f := setupROFolder()
	ffs := f.Filesystem()
	defer cleanupModelAndRemoveDir(m, ffs.URI())

	// Create some test data

	must(t, ffs.MkdirAll(".stfolder", 0755))
	oldData := []byte("hello\n")
	knownFiles := setupKnownFiles(t, ffs, oldData)

	m.fmut.Lock()
	fset := m.folderFiles["ro"]
	m.fmut.Unlock()
	folderFs := fset.MtimeFS()

	// Send and index update for the known stuff

	m.Index(device1, "ro", knownFiles)
	f.updateLocalsFromScanning(knownFiles)

	// Start the folder. This will cause a scan.

	m.startFolder("ro")
	m.ScanFolder("ro")

	// Everything should be in sync.

	size := m.GlobalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Global: expected 1 file and 1 directory: %+v", size)
	}
	size = m.LocalSize("ro")
	if size.Files != 1 || size.Directories != 1 {
		t.Fatalf("Local: expected 1 file and 1 directory: %+v", size)
	}
	size = m.NeedSize("ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("Need: expected nothing: %+v", size)
	}
	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files+size.Directories > 0 {
		t.Fatalf("ROChanged: expected nothing: %+v", size)
	}

	// Create a file and modify another

	file := filepath.Join(ffs.URI(), "foo")
	must(t, ioutil.WriteFile(file, []byte("hello\n"), 0644))

	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), []byte("bye\n"), 0644))

	m.ScanFolder("ro")

	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files != 2 {
		t.Fatalf("Receive only: expected 2 files: %+v", size)
	}

	// Remove the file again and undo the modification

	testOs.Remove(file)
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), oldData, 0644))
	folderFs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime())

	m.ScanFolder("ro")

	size = m.ReceiveOnlyChangedSize("ro")
	if size.Files+size.Directories+size.Deleted != 0 {
		t.Fatalf("Receive only: expected all zero: %+v", size)
	}
}

func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.FileInfo {
	t.Helper()

	must(t, ffs.MkdirAll("knownDir", 0755))
	must(t, ioutil.WriteFile(filepath.Join(ffs.URI(), "knownDir/knownFile"), data, 0644))

	t0 := time.Now().Add(-1 * time.Minute)
	must(t, ffs.Chtimes("knownDir/knownFile", t0, t0))

	fi, err := ffs.Stat("knownDir/knownFile")
	if err != nil {
		t.Fatal(err)
	}
	blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), protocol.BlockSize(int64(len(data))), int64(len(data)), nil, true)
	knownFiles := []protocol.FileInfo{
		{
			Name:        "knownDir",
			Type:        protocol.FileInfoTypeDirectory,
			Permissions: 0755,
			Version:     protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
			Sequence:    42,
		},
		{
			Name:        "knownDir/knownFile",
			Type:        protocol.FileInfoTypeFile,
			Permissions: 0644,
			Size:        fi.Size(),
			ModifiedS:   fi.ModTime().Unix(),
			ModifiedNs:  int32(fi.ModTime().UnixNano() % 1e9),
			Version:     protocol.Vector{Counters: []protocol.Counter{{ID: 42, Value: 42}}},
			Sequence:    42,
			Blocks:      blocks,
		},
	}

	return knownFiles
}

func setupROFolder() (*model, *sendOnlyFolder) {
	w := createTmpWrapper(defaultCfg)
	fcfg := testFolderConfigTmp()
	fcfg.ID = "ro"
	fcfg.Type = config.FolderTypeReceiveOnly
	w.SetFolder(fcfg)

	m := newModel(w, myID, "syncthing", "dev", db.NewLowlevel(backend.OpenMemory()), nil)

	m.ServeBackground()

	// Folder should only be added, not started.
	m.removeFolder(fcfg)
	m.addFolder(fcfg)

	m.fmut.RLock()
	f := &sendOnlyFolder{
		folder: folder{
			stateTracker:        newStateTracker(fcfg.ID, m.evLogger),
			fset:                m.folderFiles[fcfg.ID],
			FolderConfiguration: fcfg,
		},
	}
	m.fmut.RUnlock()

	return m, f
}
<|endoftext|>


<commit_before>package zego

type OrganizationArray struct {
	Organizations []*Organization
}

type Organization struct {
	Id                 int                    `json:"id"`
	ExternalId         string                 `json:"external_id"`
	Url                string                 `json:"url"`
	Name               string                 `json:"name"`
	CreatedAt          string                 `json:"created_at"`
	UpdatedAt          string                 `json:"updated_at"`
	DomainNames        []string               `json:"domain_names"`
	Details            string                 `json:"details"`
	Notes              string                 `json:"notes"`
	GroupId            int                    `json:"group_id"`
	SharedTickets      bool                   `json:"shared_tickets"`
	SharedComments     bool                   `json:"shared_comments"`
	Tags               []string               `json:"tags"`
	OrganizationFields []*OrganizationalField `json:"organization_fields"`
}

type OrganizationalField struct {
	OrgDropdown string  `json:"org_dropdown"`
	OrgDecimal  float32 `json:"org_decimal"`
}

func (a Auth) ListOrganizations() (*Resource, error) {

	path := "/organizations.json"
	resource, err := api(a, "GET", path, "")
	if err != nil {
		return nil, err
	}

	return resource, nil

}
<commit_msg>added list user orgs<commit_after>package zego

type OrganizationArray struct {
	Organizations []*Organization
}

type Organization struct {
	Id                 int                    `json:"id"`
	ExternalId         string                 `json:"external_id"`
	Url                string                 `json:"url"`
	Name               string                 `json:"name"`
	CreatedAt          string                 `json:"created_at"`
	UpdatedAt          string                 `json:"updated_at"`
	DomainNames        []string               `json:"domain_names"`
	Details            string                 `json:"details"`
	Notes              string                 `json:"notes"`
	GroupId            int                    `json:"group_id"`
	SharedTickets      bool                   `json:"shared_tickets"`
	SharedComments     bool                   `json:"shared_comments"`
	Tags               []string               `json:"tags"`
	OrganizationFields []*OrganizationalField `json:"organization_fields"`
}

type OrganizationalField struct {
	OrgDropdown string  `json:"org_dropdown"`
	OrgDecimal  float32 `json:"org_decimal"`
}

func (a Auth) ListOrganizations() (*Resource, error) {

	path := "/organizations.json"
	resource, err := api(a, "GET", path, "")
	if err != nil {
		return nil, err
	}

	return resource, nil

}

func (a Auth) ListUserOrganizations(user_id string) (*Resource, error) {

	path := "/users/" + user_id + "/organizations.json"
	resource, err := api(a, "GET", path, "")
	if err != nil {
		return nil, err
	}

	return resource, nil

}
<|endoftext|>


<commit_before>package bslack

import (
	"fmt"
	"html"
	"regexp"
	"time"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/nlopes/slack"
)

func (b *Bslack) handleSlack() {
	messages := make(chan *config.Message)
	if b.GetString(incomingWebhookConfig) != "" {
		b.Log.Debugf("Choosing webhooks based receiving")
		go b.handleMatterHook(messages)
	} else {
		b.Log.Debugf("Choosing token based receiving")
		go b.handleSlackClient(messages)
	}
	time.Sleep(time.Second)
	b.Log.Debug("Start listening for Slack messages")
	for message := range messages {
		if message.Event != config.EVENT_USER_TYPING {
			b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
		}

		// cleanup the message
		message.Text = b.replaceMention(message.Text)
		message.Text = b.replaceVariable(message.Text)
		message.Text = b.replaceChannel(message.Text)
		message.Text = b.replaceURL(message.Text)
		message.Text = html.UnescapeString(message.Text)

		// Add the avatar
		message.Avatar = b.getAvatar(message.UserID)

		b.Log.Debugf("<= Message is %#v", message)
		b.Remote <- *message
	}
}

func (b *Bslack) handleSlackClient(messages chan *config.Message) {
	for msg := range b.rtm.IncomingEvents {
		if msg.Type != sUserTyping && msg.Type != sLatencyReport {
			b.Log.Debugf("== Receiving event %#v", msg.Data)
		}
		switch ev := msg.Data.(type) {
		case *slack.UserTypingEvent:
			rmsg, err := b.handleTypingEvent(ev)
			if err != nil {
				b.Log.Errorf("%#v", err)
				continue
			}

			messages <- rmsg
		case *slack.MessageEvent:
			if b.skipMessageEvent(ev) {
				b.Log.Debugf("Skipped message: %#v", ev)
				continue
			}
			rmsg, err := b.handleMessageEvent(ev)
			if err != nil {
				b.Log.Errorf("%#v", err)
				continue
			}
			messages <- rmsg
		case *slack.OutgoingErrorEvent:
			b.Log.Debugf("%#v", ev.Error())
		case *slack.ChannelJoinedEvent:
			// When we join a channel we update the full list of users as
			// well as the information for the channel that we joined as this
			// should now tell that we are a member of it.
			b.populateUsers()

			b.channelsMutex.Lock()
			b.channelsByID[ev.Channel.ID] = &ev.Channel
			b.channelsByName[ev.Channel.Name] = &ev.Channel
			b.channelsMutex.Unlock()
		case *slack.ConnectedEvent:
			b.si = ev.Info
			b.populateChannels()
			b.populateUsers()
		case *slack.InvalidAuthEvent:
			b.Log.Fatalf("Invalid Token %#v", ev)
		case *slack.ConnectionErrorEvent:
			b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
		default:
		}
	}
}

func (b *Bslack) handleMatterHook(messages chan *config.Message) {
	for {
		message := b.mh.Receive()
		b.Log.Debugf("receiving from matterhook (slack) %#v", message)
		if message.UserName == "slackbot" {
			continue
		}
		messages <- &config.Message{
			Username: message.UserName,
			Text:     message.Text,
			Channel:  message.ChannelName,
		}
	}
}

// skipMessageEvent skips event that need to be skipped :-)
func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
	switch ev.SubType {
	case sChannelLeave, sChannelJoin:
		return b.GetBool(noSendJoinConfig)
	case sPinnedItem, sUnpinnedItem:
		return true
	}

	// Skip any messages that we made ourselves or from 'slackbot' (see #527).
	if ev.Username == sSlackBotUser ||
		(b.rtm != nil && ev.Username == b.si.User.Name) ||
		(len(ev.Attachments) > 0 && ev.Attachments[0].CallbackID == "matterbridge_"+b.uuid) {
		return true
	}

	// It seems ev.SubMessage.Edited == nil when slack unfurls.
	// Do not forward these messages. See Github issue #266.
	if ev.SubMessage != nil &&
		ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp &&
		ev.SubMessage.Edited == nil {
		return true
	}
	return false
}

// handleMessageEvent handles the message events. Together with any called sub-methods,
// this method implements the following event processing pipeline:
//
// 1. Check if the message should be ignored.
//    NOTE: This is not actually part of the method below but is done just before it
//          is called via the 'skipMessageEvent()' method.
// 2. Populate the Matterbridge message that will be sent to the router based on the
//    received event and logic that is common to all events that are not skipped.
// 3. Detect and handle any message that is "status" related (think join channel, etc.).
//    This might result in an early exit from the pipeline and passing of the
//    pre-populated message to the Matterbridge router.
// 4. Handle the specific case of messages that edit existing messages depending on
//    configuration.
// 5. Handle any attachments of the received event.
// 6. Check that the Matterbridge message that we end up with after at the end of the
//    pipeline is valid before sending it to the Matterbridge router.
func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, error) {
	rmsg, err := b.populateReceivedMessage(ev)
	if err != nil {
		return nil, err
	}

	// Handle some message types early.
	if b.handleStatusEvent(ev, rmsg) {
		return rmsg, nil
	}

	b.handleAttachments(ev, rmsg)

	// Verify that we have the right information and the message
	// is well-formed before sending it out to the router.
	if len(ev.Files) == 0 && (rmsg.Text == "" || rmsg.Username == "") {
		if ev.BotID != "" {
			// This is probably a webhook we couldn't resolve.
			return nil, fmt.Errorf("message handling resulted in an empty bot message (probably an incoming webhook we couldn't resolve): %#v", ev)
		}
		return nil, fmt.Errorf("message handling resulted in an empty message: %#v", ev)
	}
	return rmsg, nil
}

func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) bool {
	switch ev.SubType {
	case sChannelJoined, sMemberJoined:
		b.populateUsers()
		// There's no further processing needed on channel events
		// so we return 'true'.
		return true
	case sChannelJoin, sChannelLeave:
		rmsg.Username = sSystemUser
		rmsg.Event = config.EVENT_JOIN_LEAVE
	case sChannelTopic, sChannelPurpose:
		rmsg.Event = config.EVENT_TOPIC_CHANGE
	case sMessageDeleted:
		rmsg.Text = config.EVENT_MSG_DELETE
		rmsg.Event = config.EVENT_MSG_DELETE
		rmsg.ID = "slack " + ev.DeletedTimestamp
		// If a message is being deleted we do not need to process
		// the event any further so we return 'true'.
		return true
	case sMeMessage:
		rmsg.Event = config.EVENT_USER_ACTION
	}
	return false
}

func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) {
	// File comments are set by the system (because there is no username given).
	if ev.SubType == sFileComment {
		rmsg.Username = sSystemUser
	}

	// See if we have some text in the attachments.
	if rmsg.Text == "" {
		for _, attach := range ev.Attachments {
			if attach.Text != "" {
				if attach.Title != "" {
					rmsg.Text = attach.Title + "\n"
				}
				rmsg.Text += attach.Text
			} else {
				rmsg.Text = attach.Fallback
			}
		}
	}

	// Save the attachments, so that we can send them to other slack (compatible) bridges.
	if len(ev.Attachments) > 0 {
		rmsg.Extra[sSlackAttachment] = append(rmsg.Extra[sSlackAttachment], ev.Attachments)
	}

	// If we have files attached, download them (in memory) and put a pointer to it in msg.Extra.
	for _, f := range ev.Files {
		err := b.handleDownloadFile(rmsg, &f)
		if err != nil {
			b.Log.Errorf("Could not download incoming file: %#v", err)
		}
	}
}

var commentRE = regexp.MustCompile(`.*?commented: (.*)`)

func (b *Bslack) handleTypingEvent(ev *slack.UserTypingEvent) (*config.Message, error) {
	var err error
	// use our own func because rtm.GetChannelInfo doesn't work for private channels
	channelInfo, err := b.getChannelByID(ev.Channel)
	if err != nil {
		return nil, err
	}

	rmsg := config.Message{
		Channel: channelInfo.Name,
		Account: b.Account,
		Event:   config.EVENT_USER_TYPING,
	}

	return &rmsg, nil

}

// handleDownloadFile handles file download
func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File) error {
	if b.fileIsAvailable(file) {
		return nil
	}

	// Check that the file is neither too large nor blacklisted.
	if err := helper.HandleDownloadSize(b.Log, rmsg, file.Name, int64(file.Size), b.General); err != nil {
		b.Log.WithError(err).Infof("Skipping download of incoming file.")
		return nil
	}

	// Actually download the file.
	data, err := helper.DownloadFileAuth(file.URLPrivateDownload, "Bearer "+b.GetString(tokenConfig))
	if err != nil {
		return fmt.Errorf("download %s failed %#v", file.URLPrivateDownload, err)
	}

	// Add the downloaded data to the message.
	var comment string
	if results := commentRE.FindAllStringSubmatch(rmsg.Text, -1); len(results) > 0 {
		comment = results[0][1]
	}
	helper.HandleDownloadData(b.Log, rmsg, file.Name, comment, file.URLPrivateDownload, data, b.General)
	return nil
}

func (b *Bslack) fileIsAvailable(file *slack.File) bool {
	// Only download a file if it is not in the cache or if it has been entered more than a minute ago.
	if ts, ok := b.cache.Get("file" + file.ID); ok && time.Since(ts.(time.Time)) > time.Minute {
		return true
	} else if ts, ok = b.cache.Get("filename" + file.Name); ok && time.Since(ts.(time.Time)) > 10*time.Second {
		return true
	}
	return false
}
<commit_msg>Act only on UserTypingEvents when enabled<commit_after>package bslack

import (
	"fmt"
	"html"
	"regexp"
	"time"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	"github.com/nlopes/slack"
)

func (b *Bslack) handleSlack() {
	messages := make(chan *config.Message)
	if b.GetString(incomingWebhookConfig) != "" {
		b.Log.Debugf("Choosing webhooks based receiving")
		go b.handleMatterHook(messages)
	} else {
		b.Log.Debugf("Choosing token based receiving")
		go b.handleSlackClient(messages)
	}
	time.Sleep(time.Second)
	b.Log.Debug("Start listening for Slack messages")
	for message := range messages {
		if message.Event != config.EVENT_USER_TYPING {
			b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account)
		}

		// cleanup the message
		message.Text = b.replaceMention(message.Text)
		message.Text = b.replaceVariable(message.Text)
		message.Text = b.replaceChannel(message.Text)
		message.Text = b.replaceURL(message.Text)
		message.Text = html.UnescapeString(message.Text)

		// Add the avatar
		message.Avatar = b.getAvatar(message.UserID)

		b.Log.Debugf("<= Message is %#v", message)
		b.Remote <- *message
	}
}

func (b *Bslack) handleSlackClient(messages chan *config.Message) {
	for msg := range b.rtm.IncomingEvents {
		if msg.Type != sUserTyping && msg.Type != sLatencyReport {
			b.Log.Debugf("== Receiving event %#v", msg.Data)
		}
		switch ev := msg.Data.(type) {
		case *slack.UserTypingEvent:
			if !b.GetBool("ShowUserTyping") {
				continue
			}
			rmsg, err := b.handleTypingEvent(ev)
			if err != nil {
				b.Log.Errorf("%#v", err)
				continue
			}

			messages <- rmsg
		case *slack.MessageEvent:
			if b.skipMessageEvent(ev) {
				b.Log.Debugf("Skipped message: %#v", ev)
				continue
			}
			rmsg, err := b.handleMessageEvent(ev)
			if err != nil {
				b.Log.Errorf("%#v", err)
				continue
			}
			messages <- rmsg
		case *slack.OutgoingErrorEvent:
			b.Log.Debugf("%#v", ev.Error())
		case *slack.ChannelJoinedEvent:
			// When we join a channel we update the full list of users as
			// well as the information for the channel that we joined as this
			// should now tell that we are a member of it.
			b.populateUsers()

			b.channelsMutex.Lock()
			b.channelsByID[ev.Channel.ID] = &ev.Channel
			b.channelsByName[ev.Channel.Name] = &ev.Channel
			b.channelsMutex.Unlock()
		case *slack.ConnectedEvent:
			b.si = ev.Info
			b.populateChannels()
			b.populateUsers()
		case *slack.InvalidAuthEvent:
			b.Log.Fatalf("Invalid Token %#v", ev)
		case *slack.ConnectionErrorEvent:
			b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
		default:
		}
	}
}

func (b *Bslack) handleMatterHook(messages chan *config.Message) {
	for {
		message := b.mh.Receive()
		b.Log.Debugf("receiving from matterhook (slack) %#v", message)
		if message.UserName == "slackbot" {
			continue
		}
		messages <- &config.Message{
			Username: message.UserName,
			Text:     message.Text,
			Channel:  message.ChannelName,
		}
	}
}

// skipMessageEvent skips event that need to be skipped :-)
func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
	switch ev.SubType {
	case sChannelLeave, sChannelJoin:
		return b.GetBool(noSendJoinConfig)
	case sPinnedItem, sUnpinnedItem:
		return true
	}

	// Skip any messages that we made ourselves or from 'slackbot' (see #527).
	if ev.Username == sSlackBotUser ||
		(b.rtm != nil && ev.Username == b.si.User.Name) ||
		(len(ev.Attachments) > 0 && ev.Attachments[0].CallbackID == "matterbridge_"+b.uuid) {
		return true
	}

	// It seems ev.SubMessage.Edited == nil when slack unfurls.
	// Do not forward these messages. See Github issue #266.
	if ev.SubMessage != nil &&
		ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp &&
		ev.SubMessage.Edited == nil {
		return true
	}
	return false
}

// handleMessageEvent handles the message events. Together with any called sub-methods,
// this method implements the following event processing pipeline:
//
// 1. Check if the message should be ignored.
//    NOTE: This is not actually part of the method below but is done just before it
//          is called via the 'skipMessageEvent()' method.
// 2. Populate the Matterbridge message that will be sent to the router based on the
//    received event and logic that is common to all events that are not skipped.
// 3. Detect and handle any message that is "status" related (think join channel, etc.).
//    This might result in an early exit from the pipeline and passing of the
//    pre-populated message to the Matterbridge router.
// 4. Handle the specific case of messages that edit existing messages depending on
//    configuration.
// 5. Handle any attachments of the received event.
// 6. Check that the Matterbridge message that we end up with after at the end of the
//    pipeline is valid before sending it to the Matterbridge router.
func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, error) {
	rmsg, err := b.populateReceivedMessage(ev)
	if err != nil {
		return nil, err
	}

	// Handle some message types early.
	if b.handleStatusEvent(ev, rmsg) {
		return rmsg, nil
	}

	b.handleAttachments(ev, rmsg)

	// Verify that we have the right information and the message
	// is well-formed before sending it out to the router.
	if len(ev.Files) == 0 && (rmsg.Text == "" || rmsg.Username == "") {
		if ev.BotID != "" {
			// This is probably a webhook we couldn't resolve.
			return nil, fmt.Errorf("message handling resulted in an empty bot message (probably an incoming webhook we couldn't resolve): %#v", ev)
		}
		return nil, fmt.Errorf("message handling resulted in an empty message: %#v", ev)
	}
	return rmsg, nil
}

func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) bool {
	switch ev.SubType {
	case sChannelJoined, sMemberJoined:
		b.populateUsers()
		// There's no further processing needed on channel events
		// so we return 'true'.
		return true
	case sChannelJoin, sChannelLeave:
		rmsg.Username = sSystemUser
		rmsg.Event = config.EVENT_JOIN_LEAVE
	case sChannelTopic, sChannelPurpose:
		rmsg.Event = config.EVENT_TOPIC_CHANGE
	case sMessageDeleted:
		rmsg.Text = config.EVENT_MSG_DELETE
		rmsg.Event = config.EVENT_MSG_DELETE
		rmsg.ID = "slack " + ev.DeletedTimestamp
		// If a message is being deleted we do not need to process
		// the event any further so we return 'true'.
		return true
	case sMeMessage:
		rmsg.Event = config.EVENT_USER_ACTION
	}
	return false
}

func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) {
	// File comments are set by the system (because there is no username given).
	if ev.SubType == sFileComment {
		rmsg.Username = sSystemUser
	}

	// See if we have some text in the attachments.
	if rmsg.Text == "" {
		for _, attach := range ev.Attachments {
			if attach.Text != "" {
				if attach.Title != "" {
					rmsg.Text = attach.Title + "\n"
				}
				rmsg.Text += attach.Text
			} else {
				rmsg.Text = attach.Fallback
			}
		}
	}

	// Save the attachments, so that we can send them to other slack (compatible) bridges.
	if len(ev.Attachments) > 0 {
		rmsg.Extra[sSlackAttachment] = append(rmsg.Extra[sSlackAttachment], ev.Attachments)
	}

	// If we have files attached, download them (in memory) and put a pointer to it in msg.Extra.
	for _, f := range ev.Files {
		err := b.handleDownloadFile(rmsg, &f)
		if err != nil {
			b.Log.Errorf("Could not download incoming file: %#v", err)
		}
	}
}

var commentRE = regexp.MustCompile(`.*?commented: (.*)`)

func (b *Bslack) handleTypingEvent(ev *slack.UserTypingEvent) (*config.Message, error) {
	var err error
	// use our own func because rtm.GetChannelInfo doesn't work for private channels
	channelInfo, err := b.getChannelByID(ev.Channel)
	if err != nil {
		return nil, err
	}

	rmsg := config.Message{
		Channel: channelInfo.Name,
		Account: b.Account,
		Event:   config.EVENT_USER_TYPING,
	}

	return &rmsg, nil

}

// handleDownloadFile handles file download
func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File) error {
	if b.fileIsAvailable(file) {
		return nil
	}

	// Check that the file is neither too large nor blacklisted.
	if err := helper.HandleDownloadSize(b.Log, rmsg, file.Name, int64(file.Size), b.General); err != nil {
		b.Log.WithError(err).Infof("Skipping download of incoming file.")
		return nil
	}

	// Actually download the file.
	data, err := helper.DownloadFileAuth(file.URLPrivateDownload, "Bearer "+b.GetString(tokenConfig))
	if err != nil {
		return fmt.Errorf("download %s failed %#v", file.URLPrivateDownload, err)
	}

	// Add the downloaded data to the message.
	var comment string
	if results := commentRE.FindAllStringSubmatch(rmsg.Text, -1); len(results) > 0 {
		comment = results[0][1]
	}
	helper.HandleDownloadData(b.Log, rmsg, file.Name, comment, file.URLPrivateDownload, data, b.General)
	return nil
}

func (b *Bslack) fileIsAvailable(file *slack.File) bool {
	// Only download a file if it is not in the cache or if it has been entered more than a minute ago.
	if ts, ok := b.cache.Get("file" + file.ID); ok && time.Since(ts.(time.Time)) > time.Minute {
		return true
	} else if ts, ok = b.cache.Get("filename" + file.Name); ok && time.Since(ts.(time.Time)) > 10*time.Second {
		return true
	}
	return false
}
<|endoftext|>