<commit_before>/*
Copyright 2018, 2019 the Velero contributors.

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 restic

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/tools/cache"

	velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
	velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
	velerov1informers "github.com/heptio/velero/pkg/generated/informers/externalversions/velero/v1"
	velerov1listers "github.com/heptio/velero/pkg/generated/listers/velero/v1"
)

// repositoryEnsurer ensures that Velero restic repositories are created and ready.
type repositoryEnsurer struct {
	log        logrus.FieldLogger
	repoLister velerov1listers.ResticRepositoryLister
	repoClient velerov1client.ResticRepositoriesGetter

	readyChansLock sync.Mutex
	readyChans     map[string]chan *velerov1api.ResticRepository

	// repoLocksMu synchronizes reads/writes to the repoLocks map itself
	// since maps are not threadsafe.
	repoLocksMu sync.Mutex
	repoLocks   map[repoKey]*sync.Mutex
}

type repoKey struct {
	volumeNamespace string
	backupLocation  string
}

func newRepositoryEnsurer(repoInformer velerov1informers.ResticRepositoryInformer, repoClient velerov1client.ResticRepositoriesGetter, log logrus.FieldLogger) *repositoryEnsurer {
	r := &repositoryEnsurer{
		log:        log,
		repoLister: repoInformer.Lister(),
		repoClient: repoClient,
		readyChans: make(map[string]chan *velerov1api.ResticRepository),
		repoLocks:  make(map[repoKey]*sync.Mutex),
	}

	repoInformer.Informer().AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			UpdateFunc: func(old, upd interface{}) {
				oldObj := old.(*velerov1api.ResticRepository)
				newObj := upd.(*velerov1api.ResticRepository)

				// we're only interested in phase-changing updates
				if oldObj.Status.Phase == newObj.Status.Phase {
					return
				}

				// we're only interested in updates where the updated object is either Ready or NotReady
				if newObj.Status.Phase != velerov1api.ResticRepositoryPhaseReady && newObj.Status.Phase != velerov1api.ResticRepositoryPhaseNotReady {
					return
				}

				r.readyChansLock.Lock()
				defer r.readyChansLock.Unlock()

				key := repoLabels(newObj.Spec.VolumeNamespace, newObj.Spec.BackupStorageLocation).String()
				readyChan, ok := r.readyChans[key]
				if !ok {
					log.Debugf("No ready channel found for repository %s/%s", newObj.Namespace, newObj.Name)
					return
				}

				readyChan <- newObj
				delete(r.readyChans, key)
			},
		},
	)

	return r
}

func repoLabels(volumeNamespace, backupLocation string) labels.Set {
	return map[string]string{
		velerov1api.ResticVolumeNamespaceLabel: volumeNamespace,
		velerov1api.StorageLocationLabel:       backupLocation,
	}
}

func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNamespace, backupLocation string) (*velerov1api.ResticRepository, error) {
	log := r.log.WithField("volumeNamespace", volumeNamespace).WithField("backupLocation", backupLocation)

	// It's only safe to have one instance of this method executing concurrently for a
	// given volumeNamespace + backupLocation, so synchronize based on that. It's fine
	// to run concurrently for *different* namespaces/locations. If you had 2 goroutines
	// running this for the same inputs, both might find no ResticRepository exists, then
	// both would create new ones for the same namespace/location.
	//
	// This issue could probably be avoided if we had a deterministic name for
	// each restic repository, and we just tried to create it, checked for an
	// AlreadyExists err, and then waited for it to be ready. However, there are
	// already repositories in the wild with non-deterministic names (i.e. using
	// GenerateName) which poses a backwards compatibility problem.
	log.Debug("Acquiring lock")

	repoMu := r.repoLock(volumeNamespace, backupLocation)
	repoMu.Lock()
	defer func() {
		repoMu.Unlock()
		log.Debug("Released lock")
	}()

	log.Debug("Acquired lock")

	selector := labels.SelectorFromSet(repoLabels(volumeNamespace, backupLocation))

	repos, err := r.repoLister.ResticRepositories(namespace).List(selector)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	if len(repos) > 1 {
		return nil, errors.Errorf("more than one ResticRepository found for workload namespace %q, backup storage location %q", volumeNamespace, backupLocation)
	}
	if len(repos) == 1 {
		if repos[0].Status.Phase != velerov1api.ResticRepositoryPhaseReady {
			return nil, errors.Errorf("restic repository is not ready: %s", repos[0].Status.Message)
		}

		log.Debug("Ready repository found")
		return repos[0], nil
	}

	log.Debug("No repository found, creating one")

	// no repo found: create one and wait for it to be ready
	repo := &velerov1api.ResticRepository{
		ObjectMeta: metav1.ObjectMeta{
			Namespace:    namespace,
			GenerateName: fmt.Sprintf("%s-%s-", volumeNamespace, backupLocation),
			Labels:       repoLabels(volumeNamespace, backupLocation),
		},
		Spec: velerov1api.ResticRepositorySpec{
			VolumeNamespace:       volumeNamespace,
			BackupStorageLocation: backupLocation,
			MaintenanceFrequency:  metav1.Duration{Duration: DefaultMaintenanceFrequency},
		},
	}

	readyChan := r.getReadyChan(selector.String())
	defer close(readyChan)

	if _, err := r.repoClient.ResticRepositories(namespace).Create(repo); err != nil {
		return nil, errors.Wrapf(err, "unable to create restic repository resource")
	}

	select {
	// repositories should become either ready or not ready quickly if they're
	// newly created.
	case <-time.After(time.Minute):
		return nil, errors.New("timed out waiting for restic repository to become ready")
	case <-ctx.Done():
		return nil, errors.New("timed out waiting for restic repository to become ready")
	case res := <-readyChan:
		if res.Status.Phase == velerov1api.ResticRepositoryPhaseNotReady {
			return nil, errors.Errorf("restic repository is not ready: %s", res.Status.Message)
		}

		return res, nil
	}
}

func (r *repositoryEnsurer) getReadyChan(name string) chan *velerov1api.ResticRepository {
	r.readyChansLock.Lock()
	defer r.readyChansLock.Unlock()

	r.readyChans[name] = make(chan *velerov1api.ResticRepository)
	return r.readyChans[name]
}

func (r *repositoryEnsurer) repoLock(volumeNamespace, backupLocation string) *sync.Mutex {
	r.repoLocksMu.Lock()
	defer r.repoLocksMu.Unlock()

	key := repoKey{
		volumeNamespace: volumeNamespace,
		backupLocation:  backupLocation,
	}

	if r.repoLocks[key] == nil {
		r.repoLocks[key] = new(sync.Mutex)
	}

	return r.repoLocks[key]
}
<commit_msg>repo ensurer: rename readyChans to repoChans<commit_after>/*
Copyright 2018, 2019 the Velero contributors.

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 restic

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/tools/cache"

	velerov1api "github.com/heptio/velero/pkg/apis/velero/v1"
	velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
	velerov1informers "github.com/heptio/velero/pkg/generated/informers/externalversions/velero/v1"
	velerov1listers "github.com/heptio/velero/pkg/generated/listers/velero/v1"
)

// repositoryEnsurer ensures that Velero restic repositories are created and ready.
type repositoryEnsurer struct {
	log        logrus.FieldLogger
	repoLister velerov1listers.ResticRepositoryLister
	repoClient velerov1client.ResticRepositoriesGetter

	repoChansLock sync.Mutex
	repoChans     map[string]chan *velerov1api.ResticRepository

	// repoLocksMu synchronizes reads/writes to the repoLocks map itself
	// since maps are not threadsafe.
	repoLocksMu sync.Mutex
	repoLocks   map[repoKey]*sync.Mutex
}

type repoKey struct {
	volumeNamespace string
	backupLocation  string
}

func newRepositoryEnsurer(repoInformer velerov1informers.ResticRepositoryInformer, repoClient velerov1client.ResticRepositoriesGetter, log logrus.FieldLogger) *repositoryEnsurer {
	r := &repositoryEnsurer{
		log:        log,
		repoLister: repoInformer.Lister(),
		repoClient: repoClient,
		repoChans:  make(map[string]chan *velerov1api.ResticRepository),
		repoLocks:  make(map[repoKey]*sync.Mutex),
	}

	repoInformer.Informer().AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			UpdateFunc: func(old, upd interface{}) {
				oldObj := old.(*velerov1api.ResticRepository)
				newObj := upd.(*velerov1api.ResticRepository)

				// we're only interested in phase-changing updates
				if oldObj.Status.Phase == newObj.Status.Phase {
					return
				}

				// we're only interested in updates where the updated object is either Ready or NotReady
				if newObj.Status.Phase != velerov1api.ResticRepositoryPhaseReady && newObj.Status.Phase != velerov1api.ResticRepositoryPhaseNotReady {
					return
				}

				r.repoChansLock.Lock()
				defer r.repoChansLock.Unlock()

				key := repoLabels(newObj.Spec.VolumeNamespace, newObj.Spec.BackupStorageLocation).String()
				repoChan, ok := r.repoChans[key]
				if !ok {
					log.Debugf("No ready channel found for repository %s/%s", newObj.Namespace, newObj.Name)
					return
				}

				repoChan <- newObj
				delete(r.repoChans, key)
			},
		},
	)

	return r
}

func repoLabels(volumeNamespace, backupLocation string) labels.Set {
	return map[string]string{
		velerov1api.ResticVolumeNamespaceLabel: volumeNamespace,
		velerov1api.StorageLocationLabel:       backupLocation,
	}
}

func (r *repositoryEnsurer) EnsureRepo(ctx context.Context, namespace, volumeNamespace, backupLocation string) (*velerov1api.ResticRepository, error) {
	log := r.log.WithField("volumeNamespace", volumeNamespace).WithField("backupLocation", backupLocation)

	// It's only safe to have one instance of this method executing concurrently for a
	// given volumeNamespace + backupLocation, so synchronize based on that. It's fine
	// to run concurrently for *different* namespaces/locations. If you had 2 goroutines
	// running this for the same inputs, both might find no ResticRepository exists, then
	// both would create new ones for the same namespace/location.
	//
	// This issue could probably be avoided if we had a deterministic name for
	// each restic repository, and we just tried to create it, checked for an
	// AlreadyExists err, and then waited for it to be ready. However, there are
	// already repositories in the wild with non-deterministic names (i.e. using
	// GenerateName) which poses a backwards compatibility problem.
	log.Debug("Acquiring lock")

	repoMu := r.repoLock(volumeNamespace, backupLocation)
	repoMu.Lock()
	defer func() {
		repoMu.Unlock()
		log.Debug("Released lock")
	}()

	log.Debug("Acquired lock")

	selector := labels.SelectorFromSet(repoLabels(volumeNamespace, backupLocation))

	repos, err := r.repoLister.ResticRepositories(namespace).List(selector)
	if err != nil {
		return nil, errors.WithStack(err)
	}
	if len(repos) > 1 {
		return nil, errors.Errorf("more than one ResticRepository found for workload namespace %q, backup storage location %q", volumeNamespace, backupLocation)
	}
	if len(repos) == 1 {
		if repos[0].Status.Phase != velerov1api.ResticRepositoryPhaseReady {
			return nil, errors.Errorf("restic repository is not ready: %s", repos[0].Status.Message)
		}

		log.Debug("Ready repository found")
		return repos[0], nil
	}

	log.Debug("No repository found, creating one")

	// no repo found: create one and wait for it to be ready
	repo := &velerov1api.ResticRepository{
		ObjectMeta: metav1.ObjectMeta{
			Namespace:    namespace,
			GenerateName: fmt.Sprintf("%s-%s-", volumeNamespace, backupLocation),
			Labels:       repoLabels(volumeNamespace, backupLocation),
		},
		Spec: velerov1api.ResticRepositorySpec{
			VolumeNamespace:       volumeNamespace,
			BackupStorageLocation: backupLocation,
			MaintenanceFrequency:  metav1.Duration{Duration: DefaultMaintenanceFrequency},
		},
	}

	repoChan := r.getrepoChan(selector.String())
	defer close(repoChan)

	if _, err := r.repoClient.ResticRepositories(namespace).Create(repo); err != nil {
		return nil, errors.Wrapf(err, "unable to create restic repository resource")
	}

	select {
	// repositories should become either ready or not ready quickly if they're
	// newly created.
	case <-time.After(time.Minute):
		return nil, errors.New("timed out waiting for restic repository to become ready")
	case <-ctx.Done():
		return nil, errors.New("timed out waiting for restic repository to become ready")
	case res := <-repoChan:
		if res.Status.Phase == velerov1api.ResticRepositoryPhaseNotReady {
			return nil, errors.Errorf("restic repository is not ready: %s", res.Status.Message)
		}

		return res, nil
	}
}

func (r *repositoryEnsurer) getrepoChan(name string) chan *velerov1api.ResticRepository {
	r.repoChansLock.Lock()
	defer r.repoChansLock.Unlock()

	r.repoChans[name] = make(chan *velerov1api.ResticRepository)
	return r.repoChans[name]
}

func (r *repositoryEnsurer) repoLock(volumeNamespace, backupLocation string) *sync.Mutex {
	r.repoLocksMu.Lock()
	defer r.repoLocksMu.Unlock()

	key := repoKey{
		volumeNamespace: volumeNamespace,
		backupLocation:  backupLocation,
	}

	if r.repoLocks[key] == nil {
		r.repoLocks[key] = new(sync.Mutex)
	}

	return r.repoLocks[key]
}
<|endoftext|>


<commit_before>package hitGox

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/valyala/fasthttp"
)

type (
	// HostersList is a response body about hosters.
	HostersList struct {
		Hosters []Hoster `json:"hosters"`
	}

	// Hoster is a single list item about hoster.
	Hoster struct {
		UserName
		UserLogo string `json:"user_logo"`
	}
)

// GetHosters returns channel hosters.
//
// When a user isn’t found, this API returns a regular response but with all values containing null.
//
// Editors can read this API.
func GetHosters(channel string, authToken AuthToken) (HostersList, error) {
	var args fasthttp.Args
	args.Add("authToken", authToken.AuthToken)
	requestURL := fmt.Sprintf("%s/hosters/%s?%s", API, channel, args.String())
	_, body, err := fasthttp.Get(nil, requestURL)
	if err != nil {
		return HostersList{}, err
	}
	var obj HostersList
	if err = json.NewDecoder(bytes.NewReader(body)).Decode(&obj); err != nil {
		return HostersList{}, err
	}
	return obj, nil
}
<commit_msg>✅ Updated getHosters<commit_after>package hitGox

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/valyala/fasthttp"
)

// HostersList contains information about hosted channels.
type HostersList struct {
	Hosters []struct {
		UserLogo string `json:"user_logo"`
		UserName string `json:"user_name"`
	} `json:"hosters"`
}

// GetHosters returns a list of channels hosting channel.
//
// Editors can read this API.
func (account *Account) GetHosters(channel string) (*HostersList, error) {
	switch {
	case account.AuthToken == "":
		return nil, errors.New("authtoken in account can not be empty")
	case channel == "":
		return nil, errors.New("channel can not be empty")
	}

	var args fasthttp.Args
	args.Add("authToken", account.AuthToken)

	url := fmt.Sprint(APIEndpoint, "/hosters/", channel)
	resp, err := get(url, &args)
	if err != nil {
		return nil, err
	}

	var obj HostersList
	if err = json.NewDecoder(bytes.NewReader(resp)).Decode(&obj); err != nil {
		return nil, err
	}

	return &obj, nil
}
<|endoftext|>


<commit_before>// Copyright 2013 Xing Xing <mikespook@gmail.com>.
// All rights reserved.
// Use of this source code is governed by a commercial
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"net/http"
	"os"
	"path"
	"time"

	"github.com/mikespook/ghoko"
	"github.com/mikespook/golib/log"
	"github.com/mikespook/golib/pid"
	"github.com/mikespook/golib/signal"
)

var (
	addr       string
	scriptPath string
	secret     string
	tlsCert    string
	tlsKey     string
	pidFile    string
	rootUrl    string
)

func init() {
	if !flag.Parsed() {
		flag.StringVar(&addr, "addr", ":3080", "Address of HTTP service")
		flag.StringVar(&scriptPath, "script", path.Dir(os.Args[0]), "Path of lua files")
		flag.StringVar(&secret, "secret", "", "Secret token")
		flag.StringVar(&tlsCert, "tls-cert", "", "TLS cert file")
		flag.StringVar(&tlsKey, "tls-key", "", "TLS key file")
		flag.StringVar(&pidFile, "pid", "", "PID file")
		flag.StringVar(&rootUrl, "root", "/", "Root path of URL")
		flag.Parse()
	}
	log.InitWithFlag()
}

func main() {
	log.Messagef("Starting: webhook=%q script=%q", ghoko.CallbackUrl(tlsCert, tlsKey, addr, rootUrl), scriptPath)
	if pidFile != "" {
		if p, err := pid.New(pidFile); err != nil {
			log.Error(err)
		} else {
			defer func() {
				if err := p.Close(); err != nil {
					log.Error(err)
				}
			}()
			log.Messagef("PID: %d file=%q", p.Pid, pidFile)
		}
	}
	defer func() {
		log.Message("Exited!")
		time.Sleep(time.Millisecond * 100)
	}()

	// Begin
	p := path.Clean(scriptPath)
	ghk := ghoko.New(p, secret, rootUrl)
	go func() {
		defer func() {
			if err := signal.Send(os.Getpid(), os.Interrupt); err != nil {
				panic(err)
			}
		}()
		var err error
		if tlsCert == "" || tlsKey == "" {
			err = http.ListenAndServe(addr, ghk)
		} else {
			err = http.ListenAndServeTLS(addr, tlsCert, tlsKey, ghk)
		}
		if err != nil {
			log.Error(err)
		}
	}()
	// End

	sh := signal.NewHandler()
	sh.Bind(os.Interrupt, func() bool { return true })
	sh.Loop()
}
<commit_msg>removed waiting when exit<commit_after>// Copyright 2013 Xing Xing <mikespook@gmail.com>.
// All rights reserved.
// Use of this source code is governed by a commercial
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"net/http"
	"os"
	"path"

	"github.com/mikespook/ghoko"
	"github.com/mikespook/golib/log"
	"github.com/mikespook/golib/pid"
	"github.com/mikespook/golib/signal"
)

var (
	addr       string
	scriptPath string
	secret     string
	tlsCert    string
	tlsKey     string
	pidFile    string
	rootUrl    string
)

func init() {
	if !flag.Parsed() {
		flag.StringVar(&addr, "addr", ":3080", "Address of HTTP service")
		flag.StringVar(&scriptPath, "script", path.Dir(os.Args[0]), "Path of lua files")
		flag.StringVar(&secret, "secret", "", "Secret token")
		flag.StringVar(&tlsCert, "tls-cert", "", "TLS cert file")
		flag.StringVar(&tlsKey, "tls-key", "", "TLS key file")
		flag.StringVar(&pidFile, "pid", "", "PID file")
		flag.StringVar(&rootUrl, "root", "/", "Root path of URL")
		flag.Parse()
	}
	log.InitWithFlag()
}

func main() {
	log.Messagef("Starting: webhook=%q script=%q", ghoko.CallbackUrl(tlsCert, tlsKey, addr, rootUrl), scriptPath)
	if pidFile != "" {
		if p, err := pid.New(pidFile); err != nil {
			log.Error(err)
		} else {
			defer func() {
				if err := p.Close(); err != nil {
					log.Error(err)
				}
			}()
			log.Messagef("PID: %d file=%q", p.Pid, pidFile)
		}
	}
	defer func() {
		log.Message("Exited!")
	}()

	// Begin
	p := path.Clean(scriptPath)
	ghk := ghoko.New(p, secret, rootUrl)
	go func() {
		defer func() {
			if err := signal.Send(os.Getpid(), os.Interrupt); err != nil {
				panic(err)
			}
		}()
		var err error
		if tlsCert == "" || tlsKey == "" {
			err = http.ListenAndServe(addr, ghk)
		} else {
			err = http.ListenAndServeTLS(addr, tlsCert, tlsKey, ghk)
		}
		if err != nil {
			log.Error(err)
		}
	}()
	// End

	sh := signal.NewHandler()
	sh.Bind(os.Interrupt, func() bool { return true })
	sh.Loop()
}
<|endoftext|>


<commit_before>// Keybase file system

package main

import (
	"flag"
	_ "fmt"
	"log"
	"os"
	"os/signal"
	"runtime/pprof"

	"github.com/hanwen/go-fuse/fuse/nodefs"
	libkb "github.com/keybase/go-libkb"
	libkbfs "github.com/keybase/go-libkbfs-priv"
)

func GetUI() libkb.UI {
	ui := &libkbfs.UI{}
	ui.Configure()
	return ui
}

var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
var memprofile = flag.String("memprofile", "", "write memory profile to file")

func main() {
	flag.Parse()
	if len(flag.Args()) < 1 {
		log.Fatal("Usage:\n  kbfs MOUNTPOINT")
	}

	var cpuProfFile *os.File
	if *cpuprofile != "" {
		var err error
		cpuProfFile, err = os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(cpuProfFile)
		defer cpuProfFile.Close()
	}

	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, os.Interrupt, os.Kill)
	go func() {
		_ = <-sigchan
		if *cpuprofile != "" {
			pprof.StopCPUProfile()
		}

		if *memprofile != "" {
			f, err := os.Create(*memprofile)
			if err != nil {
				log.Fatal(err)
			}
			pprof.WriteHeapProfile(f)
			f.Close()
		}
		os.Exit(1)
	}()

	// TODO: make this an option:
	localUsers := true

	config := libkbfs.NewConfigLocal()

	libkb.G.Init()
	libkb.G.ConfigureConfig()
	libkb.G.ConfigureLogging()
	libkb.G.ConfigureCaches()
	libkb.G.ConfigureMerkleClient()
	libkb.G.SetUI(GetUI())

	if !localUsers {
		libkb.G.ConfigureAPI()
		if ok, err := libkb.G.Session.LoadAndCheck(); !ok || err != nil {
			log.Fatalf("Couldn't load session: %v\n", err)
		}
	} else {
		k := libkbfs.NewKBPKILocal(libkb.UID{1}, []*libkbfs.LocalUser{
			&libkbfs.LocalUser{"strib", libkb.UID{1}, []string{"github:strib"}},
			&libkbfs.LocalUser{"max", libkb.UID{2}, []string{"twitter:maxtaco"}},
			&libkbfs.LocalUser{
				"chris", libkb.UID{3}, []string{"twitter:malgorithms"}},
		})
		config.SetKBPKI(k)
	}

	root := libkbfs.NewFuseRoot(config)

	server, _, err := nodefs.MountRoot(flag.Arg(0), root, nil)
	if err != nil {
		log.Fatalf("Mount fail: %v\n", err)
	}

	//server.SetDebug(true)
	server.Serve()
}
<commit_msg>kbfsd: add a few more flags to make debugging stuff optional<commit_after>// Keybase file system

package main

import (
	"flag"
	_ "fmt"
	"log"
	"os"
	"os/signal"
	"runtime/pprof"

	"github.com/hanwen/go-fuse/fuse/nodefs"
	libkb "github.com/keybase/go-libkb"
	libkbfs "github.com/keybase/go-libkbfs-priv"
)

func GetUI() libkb.UI {
	ui := &libkbfs.UI{}
	ui.Configure()
	return ui
}

var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
var memprofile = flag.String("memprofile", "", "write memory profile to file")
var local = flag.Bool("local", false,
	"use a fake local user DB instead of Keybase")
var localUser = flag.String("localuser", "strib",
	"fake local user (only valid when local=true")
var debug = flag.Bool("debug", false, "Print FUSE debug messages")

func main() {
	flag.Parse()
	if len(flag.Args()) < 1 {
		log.Fatal("Usage:\n  kbfs MOUNTPOINT")
	}

	var cpuProfFile *os.File
	if *cpuprofile != "" {
		var err error
		cpuProfFile, err = os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(cpuProfFile)
		defer cpuProfFile.Close()
	}

	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, os.Interrupt, os.Kill)
	go func() {
		_ = <-sigchan
		if *cpuprofile != "" {
			pprof.StopCPUProfile()
		}

		if *memprofile != "" {
			f, err := os.Create(*memprofile)
			if err != nil {
				log.Fatal(err)
			}
			pprof.WriteHeapProfile(f)
			f.Close()
		}
		os.Exit(1)
	}()

	config := libkbfs.NewConfigLocal()

	libkb.G.Init()
	libkb.G.ConfigureConfig()
	libkb.G.ConfigureLogging()
	libkb.G.ConfigureCaches()
	libkb.G.ConfigureMerkleClient()
	libkb.G.SetUI(GetUI())

	if !*local {
		libkb.G.ConfigureAPI()
		if ok, err := libkb.G.Session.LoadAndCheck(); !ok || err != nil {
			log.Fatalf("Couldn't load session: %v\n", err)
		}
	} else {
		var localUid libkb.UID
		switch {
		case *localUser == "strib":
			localUid = libkb.UID{1}
		case *localUser == "max":
			localUid = libkb.UID{2}
		case *localUser == "chris":
			localUid = libkb.UID{3}
		}
		k := libkbfs.NewKBPKILocal(localUid, []*libkbfs.LocalUser{
			&libkbfs.LocalUser{"strib", libkb.UID{1}, []string{"github:strib"}},
			&libkbfs.LocalUser{"max", libkb.UID{2}, []string{"twitter:maxtaco"}},
			&libkbfs.LocalUser{
				"chris", libkb.UID{3}, []string{"twitter:malgorithms"}},
		})
		config.SetKBPKI(k)
	}

	root := libkbfs.NewFuseRoot(config)

	server, _, err := nodefs.MountRoot(flag.Arg(0), root, nil)
	if err != nil {
		log.Fatalf("Mount fail: %v\n", err)
	}

	if *debug {
		server.SetDebug(true)
	}
	server.Serve()
}
<|endoftext|>


<commit_before>/*
   Copyright (c) 2014, Percona LLC and/or its affiliates. All rights reserved.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package main

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/percona/cloud-protocol/proto"
	"github.com/percona/percona-agent/agent"
	"github.com/percona/percona-agent/mysql"
	"github.com/percona/percona-agent/pct"
	"log"
	"net"
	"net/url"
	"os"
	"regexp"
	"time"
)

type Flags map[string]bool

var portNumberRe = regexp.MustCompile(`\.\d+$`)

type Installer struct {
	term        *Terminal
	basedir     string
	api         pct.APIConnector
	agentConfig *agent.Config
	flags       Flags
	// --
	hostname string
}

func NewInstaller(term *Terminal, basedir string, api pct.APIConnector, agentConfig *agent.Config, flags Flags) *Installer {
	if agentConfig.ApiHostname == "" {
		agentConfig.ApiHostname = agent.DEFAULT_API_HOSTNAME
	}
	hostname, _ := os.Hostname()
	installer := &Installer{
		term:        term,
		basedir:     basedir,
		api:         api,
		agentConfig: agentConfig,
		flags:       flags,
		// --
		hostname: hostname,
	}
	return installer
}

func (i *Installer) Run() error {

	/**
	 * Check for pt-agent, upgrade if found.
	 */

	var ptagentDSN *mysql.DSN
	ptagentUpgrade := false
	ptagentConf := "/root/.pt-agent.conf"
	if pct.FileExists(ptagentConf) {
		fmt.Println("Found pt-agent, upgrading and removing because it is no longer supported...")
		ptagentUpgrade = true

		// Stop pt-agent
		if err := StopPTAgent(); err != nil {
			fmt.Printf("Error stopping pt-agent: %s\n\n", err)
			fmt.Println("WARNING: pt-agent must be stopped before installing percona-agent.  " +
				"Please verify that pt-agent is not running and has been removed from cron.  " +
				"Enter 'Y' to confirm and continue installing percona-agent.")
			ok, err := i.term.PromptBool("pt-agent has stopped?", "N")
			if err != nil {
				return err
			}
			if !ok {
				return fmt.Errorf("Failed to stop pt-agent")
			}
		}

		// Get its settings (API key, UUID, etc.).
		agent, dsn, err := GetPTAgentSettings(ptagentConf)
		if err != nil {
			return fmt.Errorf("Error upgrading pt-agent: %s", err)
		}
		if agent.ApiKey != "" {
			i.agentConfig.ApiKey = agent.ApiKey
		}
		if agent.AgentUuid != "" {
			i.agentConfig.AgentUuid = agent.AgentUuid
			fmt.Printf("Upgrading pt-agent %s...\n", agent.AgentUuid)
		}
		ptagentDSN = dsn
	}

	/**
	 * Get the API key.
	 */

	fmt.Printf("API host: %s\n", i.agentConfig.ApiHostname)

	for i.agentConfig.ApiKey == "" {
		apiKey, err := i.term.PromptString("API key", "")
		if err != nil {
			return err
		}
		if apiKey == "" {
			fmt.Println("API key is required, please try again.")
			continue
		}
		i.agentConfig.ApiKey = apiKey
		break
	}

	/**
	 * Verify the API key by pinging the API.
	 */

VERIFY_API_KEY:
	for {
		startTime := time.Now()
		fmt.Printf("Verifying API key %s...\n", i.agentConfig.ApiKey)
		code, err := pct.Ping(i.agentConfig.ApiHostname, i.agentConfig.ApiKey)
		elapsedTime := time.Since(startTime)
		elapsedTimeInSeconds := elapsedTime / time.Second

		timeout := false
		if urlErr, ok := err.(*url.Error); ok {
			if netOpErr, ok := urlErr.Err.(*net.OpError); ok && netOpErr.Timeout() {
				timeout = true
			}
		}
		if i.flags["debug"] {
			log.Printf("code=%d\n", code)
			log.Printf("err=%s\n", err)
		}
		ok := false
		if timeout {
			fmt.Printf(
				"Error: API connection timeout (%ds): %s\n"+
					"Before you try again, please check your connection and DNS configuration.\n",
				elapsedTimeInSeconds,
				err,
			)
		} else if err != nil {
			fmt.Printf("Error: %s\n", err)
		} else if code >= 500 {
			fmt.Printf("Sorry, there's an API problem (status code %d). "+
				"Please try to install again. If the problem continues, contact Percona.\n",
				code)
		} else if code == 401 {
			return fmt.Errorf("Access denied.  Check the API key and try again.")
		} else if code >= 300 {
			fmt.Printf("Sorry, there's an installer problem (status code %d). "+
				"Please try to install again. If the problem continues, contact Percona.\n",
				code)
		} else if code != 200 {
			fmt.Printf("Sorry, there's an installer problem (status code %d). "+
				"Please try to install again. If the problem continues, contact Percona.\n",
				code)
		} else {
			ok = true
		}

		if !ok {
			again, err := i.term.PromptBool("Try again?", "Y")
			if err != nil {
				return err
			}
			if !again {
				return fmt.Errorf("Failed to verify API key")
			}
			continue VERIFY_API_KEY
		}

		fmt.Printf("API key %s is OK\n", i.agentConfig.ApiKey)

		if elapsedTimeInSeconds >= 0 {
			fmt.Printf(
				"WARNING: We have detected that request to api took %d second(-s) while usually it shouldn't take more than 1s.\n"+
					"This might be due to connection problems or slow DNS resolution.\n"+
					"Before you continue please check your connection and DNS configuration as this might impact performance of percona-agent.\n"+
					"If you are using CentOS or Fedora 19+ in a vagrant box then you might be interested in this bug report:\n"+
					"https://github.com/mitchellh/vagrant/issues/1172\n",
				elapsedTimeInSeconds,
			)
			proceed, err := i.term.PromptBool("Continue anyway?", "Y")
			if err != nil {
				return err
			}
			if !proceed {
				return fmt.Errorf("Failed because of slow connection")
			}
		}

		break
	}

	var si *proto.ServerInstance
	var mi *proto.MySQLInstance

	/**
	 * Create new service instances.
	 */

	var err error

	if i.flags["create-server-instance"] {
		si, err = i.createServerInstance()
		if err != nil {
			return err
		}
		fmt.Printf("Created server instance: hostname=%s id=%d\n", si.Hostname, si.Id)
	} else {
		fmt.Println("Not creating server instance (-create-server-instance=false)")
	}

	if i.flags["create-mysql-instance"] {
		// Create MySQL user for agent, or using existing one, then verify MySQL connection.
		agentDSN, err := i.doMySQL(ptagentDSN)
		if err != nil {
			return err
		}
		// Create MySQL instance in API.
		mi, err = i.createMySQLInstance(agentDSN)
		if err != nil {
			return err
		}
		fmt.Printf("Created MySQL instance: dsn=%s hostname=%s id=%d\n", mi.DSN, mi.Hostname, si.Id)
	} else {
		fmt.Println("Not creating MySQL instance (-create-mysql-instance=false)")
	}

	if err := i.writeInstances(si, mi); err != nil {
		return fmt.Errorf("Created agent but failed to write service instances: %s", err)
	}

	/**
	 * Get default configs for all services.
	 */

	configs := []proto.AgentConfig{}

	if i.flags["start-services"] {
		// Server metrics monitor
		config, err := i.getMmServerConfig(si)
		if err != nil {
			fmt.Println(err)
			fmt.Println("WARNING: cannot start server metrics monitor")
		} else {
			configs = append(configs, *config)
		}

		if i.flags["start-mysql-services"] {
			// MySQL metrics tracker
			config, err = i.getMmMySQLConfig(mi)
			if err != nil {
				fmt.Println(err)
				fmt.Println("WARNING: cannot start MySQL metrics monitor")
			} else {
				configs = append(configs, *config)
			}

			// MySQL config tracker
			config, err = i.getSysconfigMySQLConfig(mi)
			if err != nil {
				fmt.Println(err)
				fmt.Println("WARNING: cannot start MySQL configuration monitor")
			} else {
				configs = append(configs, *config)
			}

			// QAN
			// MySQL is local if the server hostname == MySQL hostname without port number.
			if i.hostname == portNumberRe.ReplaceAllLiteralString(mi.Hostname, "") {
				if i.flags["debug"] {
					log.Printf("MySQL is local")
				}
				config, err := i.getQanConfig(mi)
				if err != nil {
					fmt.Println(err)
					fmt.Println("WARNING: cannot start Query Analytics")
				} else {
					configs = append(configs, *config)
				}
			}
		} else {
			fmt.Println("Not starting MySQL services (-start-mysql-services=false)")
		}
	} else {
		fmt.Println("Not starting default services (-start-services=false)")
	}

	/**
	 * Create agent with initial service configs.
	 */

	if ptagentUpgrade {
		agent, err := i.updateAgent(i.agentConfig.AgentUuid)
		if err != nil {
			return err
		}
		fmt.Println("pt-agent upgraded to percona-agent")
		if err := i.writeConfigs(agent, configs); err != nil {
			return fmt.Errorf("Upgraded pt-agent but failed to write percona-agent configs: %s", err)
		}
	} else if i.flags["create-agent"] {
		agent, err := i.createAgent(configs)
		if err != nil {
			return err
		}
		fmt.Printf("Created agent: uuid=%s\n", agent.Uuid)

		if err := i.writeConfigs(agent, configs); err != nil {
			return fmt.Errorf("Created agent but failed to write configs: %s", err)
		}
	} else {
		fmt.Println("Not creating agent (-create-agent=false)")
	}

	/**
	 * Remove pt-agent if upgrading.
	 */

	if ptagentUpgrade {
		RemovePTAgent(ptagentConf)
		fmt.Println("pt-agent removed")
	}

	return nil // success
}
<commit_msg>PCT-617: hotfix, user should be warn if request took at least 5s, not 0s<commit_after>/*
   Copyright (c) 2014, Percona LLC and/or its affiliates. All rights reserved.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package main

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/percona/cloud-protocol/proto"
	"github.com/percona/percona-agent/agent"
	"github.com/percona/percona-agent/mysql"
	"github.com/percona/percona-agent/pct"
	"log"
	"net"
	"net/url"
	"os"
	"regexp"
	"time"
)

type Flags map[string]bool

var portNumberRe = regexp.MustCompile(`\.\d+$`)

type Installer struct {
	term        *Terminal
	basedir     string
	api         pct.APIConnector
	agentConfig *agent.Config
	flags       Flags
	// --
	hostname string
}

func NewInstaller(term *Terminal, basedir string, api pct.APIConnector, agentConfig *agent.Config, flags Flags) *Installer {
	if agentConfig.ApiHostname == "" {
		agentConfig.ApiHostname = agent.DEFAULT_API_HOSTNAME
	}
	hostname, _ := os.Hostname()
	installer := &Installer{
		term:        term,
		basedir:     basedir,
		api:         api,
		agentConfig: agentConfig,
		flags:       flags,
		// --
		hostname: hostname,
	}
	return installer
}

func (i *Installer) Run() error {

	/**
	 * Check for pt-agent, upgrade if found.
	 */

	var ptagentDSN *mysql.DSN
	ptagentUpgrade := false
	ptagentConf := "/root/.pt-agent.conf"
	if pct.FileExists(ptagentConf) {
		fmt.Println("Found pt-agent, upgrading and removing because it is no longer supported...")
		ptagentUpgrade = true

		// Stop pt-agent
		if err := StopPTAgent(); err != nil {
			fmt.Printf("Error stopping pt-agent: %s\n\n", err)
			fmt.Println("WARNING: pt-agent must be stopped before installing percona-agent.  " +
				"Please verify that pt-agent is not running and has been removed from cron.  " +
				"Enter 'Y' to confirm and continue installing percona-agent.")
			ok, err := i.term.PromptBool("pt-agent has stopped?", "N")
			if err != nil {
				return err
			}
			if !ok {
				return fmt.Errorf("Failed to stop pt-agent")
			}
		}

		// Get its settings (API key, UUID, etc.).
		agent, dsn, err := GetPTAgentSettings(ptagentConf)
		if err != nil {
			return fmt.Errorf("Error upgrading pt-agent: %s", err)
		}
		if agent.ApiKey != "" {
			i.agentConfig.ApiKey = agent.ApiKey
		}
		if agent.AgentUuid != "" {
			i.agentConfig.AgentUuid = agent.AgentUuid
			fmt.Printf("Upgrading pt-agent %s...\n", agent.AgentUuid)
		}
		ptagentDSN = dsn
	}

	/**
	 * Get the API key.
	 */

	fmt.Printf("API host: %s\n", i.agentConfig.ApiHostname)

	for i.agentConfig.ApiKey == "" {
		apiKey, err := i.term.PromptString("API key", "")
		if err != nil {
			return err
		}
		if apiKey == "" {
			fmt.Println("API key is required, please try again.")
			continue
		}
		i.agentConfig.ApiKey = apiKey
		break
	}

	/**
	 * Verify the API key by pinging the API.
	 */

VERIFY_API_KEY:
	for {
		startTime := time.Now()
		fmt.Printf("Verifying API key %s...\n", i.agentConfig.ApiKey)
		code, err := pct.Ping(i.agentConfig.ApiHostname, i.agentConfig.ApiKey)
		elapsedTime := time.Since(startTime)
		elapsedTimeInSeconds := elapsedTime / time.Second

		timeout := false
		if urlErr, ok := err.(*url.Error); ok {
			if netOpErr, ok := urlErr.Err.(*net.OpError); ok && netOpErr.Timeout() {
				timeout = true
			}
		}
		if i.flags["debug"] {
			log.Printf("code=%d\n", code)
			log.Printf("err=%s\n", err)
		}
		ok := false
		if timeout {
			fmt.Printf(
				"Error: API connection timeout (%ds): %s\n"+
					"Before you try again, please check your connection and DNS configuration.\n",
				elapsedTimeInSeconds,
				err,
			)
		} else if err != nil {
			fmt.Printf("Error: %s\n", err)
		} else if code >= 500 {
			fmt.Printf("Sorry, there's an API problem (status code %d). "+
				"Please try to install again. If the problem continues, contact Percona.\n",
				code)
		} else if code == 401 {
			return fmt.Errorf("Access denied.  Check the API key and try again.")
		} else if code >= 300 {
			fmt.Printf("Sorry, there's an installer problem (status code %d). "+
				"Please try to install again. If the problem continues, contact Percona.\n",
				code)
		} else if code != 200 {
			fmt.Printf("Sorry, there's an installer problem (status code %d). "+
				"Please try to install again. If the problem continues, contact Percona.\n",
				code)
		} else {
			ok = true
		}

		if !ok {
			again, err := i.term.PromptBool("Try again?", "Y")
			if err != nil {
				return err
			}
			if !again {
				return fmt.Errorf("Failed to verify API key")
			}
			continue VERIFY_API_KEY
		}

		fmt.Printf("API key %s is OK\n", i.agentConfig.ApiKey)

		// https://jira.percona.com/browse/PCT-617
		// Warn user if request took at least 5s
		if elapsedTimeInSeconds >= 5 {
			fmt.Printf(
				"WARNING: We have detected that request to api took %d second(-s) while usually it shouldn't take more than 1s.\n"+
					"This might be due to connection problems or slow DNS resolution.\n"+
					"Before you continue please check your connection and DNS configuration as this might impact performance of percona-agent.\n"+
					"If you are using CentOS or Fedora 19+ in a vagrant box then you might be interested in this bug report:\n"+
					"https://github.com/mitchellh/vagrant/issues/1172\n",
				elapsedTimeInSeconds,
			)
			proceed, err := i.term.PromptBool("Continue anyway?", "Y")
			if err != nil {
				return err
			}
			if !proceed {
				return fmt.Errorf("Failed because of slow connection")
			}
		}

		break
	}

	var si *proto.ServerInstance
	var mi *proto.MySQLInstance

	/**
	 * Create new service instances.
	 */

	var err error

	if i.flags["create-server-instance"] {
		si, err = i.createServerInstance()
		if err != nil {
			return err
		}
		fmt.Printf("Created server instance: hostname=%s id=%d\n", si.Hostname, si.Id)
	} else {
		fmt.Println("Not creating server instance (-create-server-instance=false)")
	}

	if i.flags["create-mysql-instance"] {
		// Create MySQL user for agent, or using existing one, then verify MySQL connection.
		agentDSN, err := i.doMySQL(ptagentDSN)
		if err != nil {
			return err
		}
		// Create MySQL instance in API.
		mi, err = i.createMySQLInstance(agentDSN)
		if err != nil {
			return err
		}
		fmt.Printf("Created MySQL instance: dsn=%s hostname=%s id=%d\n", mi.DSN, mi.Hostname, si.Id)
	} else {
		fmt.Println("Not creating MySQL instance (-create-mysql-instance=false)")
	}

	if err := i.writeInstances(si, mi); err != nil {
		return fmt.Errorf("Created agent but failed to write service instances: %s", err)
	}

	/**
	 * Get default configs for all services.
	 */

	configs := []proto.AgentConfig{}

	if i.flags["start-services"] {
		// Server metrics monitor
		config, err := i.getMmServerConfig(si)
		if err != nil {
			fmt.Println(err)
			fmt.Println("WARNING: cannot start server metrics monitor")
		} else {
			configs = append(configs, *config)
		}

		if i.flags["start-mysql-services"] {
			// MySQL metrics tracker
			config, err = i.getMmMySQLConfig(mi)
			if err != nil {
				fmt.Println(err)
				fmt.Println("WARNING: cannot start MySQL metrics monitor")
			} else {
				configs = append(configs, *config)
			}

			// MySQL config tracker
			config, err = i.getSysconfigMySQLConfig(mi)
			if err != nil {
				fmt.Println(err)
				fmt.Println("WARNING: cannot start MySQL configuration monitor")
			} else {
				configs = append(configs, *config)
			}

			// QAN
			// MySQL is local if the server hostname == MySQL hostname without port number.
			if i.hostname == portNumberRe.ReplaceAllLiteralString(mi.Hostname, "") {
				if i.flags["debug"] {
					log.Printf("MySQL is local")
				}
				config, err := i.getQanConfig(mi)
				if err != nil {
					fmt.Println(err)
					fmt.Println("WARNING: cannot start Query Analytics")
				} else {
					configs = append(configs, *config)
				}
			}
		} else {
			fmt.Println("Not starting MySQL services (-start-mysql-services=false)")
		}
	} else {
		fmt.Println("Not starting default services (-start-services=false)")
	}

	/**
	 * Create agent with initial service configs.
	 */

	if ptagentUpgrade {
		agent, err := i.updateAgent(i.agentConfig.AgentUuid)
		if err != nil {
			return err
		}
		fmt.Println("pt-agent upgraded to percona-agent")
		if err := i.writeConfigs(agent, configs); err != nil {
			return fmt.Errorf("Upgraded pt-agent but failed to write percona-agent configs: %s", err)
		}
	} else if i.flags["create-agent"] {
		agent, err := i.createAgent(configs)
		if err != nil {
			return err
		}
		fmt.Printf("Created agent: uuid=%s\n", agent.Uuid)

		if err := i.writeConfigs(agent, configs); err != nil {
			return fmt.Errorf("Created agent but failed to write configs: %s", err)
		}
	} else {
		fmt.Println("Not creating agent (-create-agent=false)")
	}

	/**
	 * Remove pt-agent if upgrading.
	 */

	if ptagentUpgrade {
		RemovePTAgent(ptagentConf)
		fmt.Println("pt-agent removed")
	}

	return nil // success
}
<|endoftext|>


<commit_before>package k8s

import (
	"context"
	"fmt"
	"os"
	"path"
	"reflect"

	"github.com/scaleway/scaleway-cli/internal/core"
	k8s "github.com/scaleway/scaleway-sdk-go/api/k8s/v1"
	"github.com/scaleway/scaleway-sdk-go/scw"
)

const (
	kubeLocationDir      = ".kube"
	kubeconfigAPIVersion = "v1"
	kubeconfigKind       = "Config"
)

type k8sKubeconfigInstallRequest struct {
	ClusterID         string
	Region            scw.Region
	KeepCurentContext bool
}

func k8sKubeconfigInstallCommand() *core.Command {
	return &core.Command{
		Short: `Install a kubeconfig`,
		Long: `Retrieve the kubeconfig for a specified cluster and write it on disk.
It will merge the new kubeconfig in the file pointed by the KUBECONFIG variable. If empty it will default to $HOME/.kube/config.`,
		Namespace: "k8s",
		Verb:      "install",
		Resource:  "kubeconfig",
		ArgsType:  reflect.TypeOf(k8sKubeconfigInstallRequest{}),
		ArgSpecs: core.ArgSpecs{
			{
				Name:       "cluster-id",
				Short:      "Cluster ID from which to retrieve the kubeconfig",
				Required:   true,
				Positional: true,
			},
			{
				Name:  "keep-current-context",
				Short: "Whether or not to keep the current kubeconfig context unmodified",
			},
			core.RegionArgSpec(),
		},
		Run: k8sKubeconfigInstallRun,
		Examples: []*core.Example{
			{
				Short:   "Install the kubeconfig for a given cluster and using the new context",
				Request: `{"cluster_id": "11111111-1111-1111-1111-111111111111"}`,
			},
		},
		SeeAlsos: []*core.SeeAlso{
			{
				Command: "scw k8s kubeconfig uninstall",
				Short:   "Uninstall a kubeconfig",
			},
		},
	}
}

func k8sKubeconfigInstallRun(ctx context.Context, argsI interface{}) (i interface{}, e error) {
	request := argsI.(*k8sKubeconfigInstallRequest)

	kubeconfigRequest := &k8s.GetClusterKubeConfigRequest{
		Region:    request.Region,
		ClusterID: request.ClusterID,
	}

	client := core.ExtractClient(ctx)
	apiK8s := k8s.NewAPI(client)

	// get the wanted kubeconfig
	kubeconfig, err := apiK8s.GetClusterKubeConfig(kubeconfigRequest)
	if err != nil {
		return nil, err
	}

	kubeconfigPath, err := getKubeconfigPath(ctx)
	if err != nil {
		return nil, err
	}

	// create the kubeconfig file if it does not exist
	if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
		// make sure the directory exists
		err = os.MkdirAll(path.Dir(kubeconfigPath), 0755)
		if err != nil {
			return nil, err
		}

		// create the file
		f, err := os.OpenFile(kubeconfigPath, os.O_CREATE, 0644)
		if err != nil {
			return nil, err
		}
		f.Close()
	}

	existingKubeconfig, err := openAndUnmarshalKubeconfig(kubeconfigPath)
	if err != nil {
		return nil, err
	}

	// loop through all clusters and insert the wanted one if it does not exist
	clusterFoundInExistingKubeconfig := false
	for _, cluster := range existingKubeconfig.Clusters {
		if cluster.Name == kubeconfig.Clusters[0].Name+"-"+request.ClusterID {
			clusterFoundInExistingKubeconfig = true
			cluster.Cluster = kubeconfig.Clusters[0].Cluster
			break
		}
	}
	if !clusterFoundInExistingKubeconfig {
		existingKubeconfig.Clusters = append(existingKubeconfig.Clusters, &k8s.KubeconfigClusterWithName{
			Name:    kubeconfig.Clusters[0].Name + "-" + request.ClusterID,
			Cluster: kubeconfig.Clusters[0].Cluster,
		})
	}

	// loop through all contexts and insert the wanted one if it does not exist
	contextFoundInExistingKubeconfig := false
	for _, kubeconfigContext := range existingKubeconfig.Contexts {
		if kubeconfigContext.Name == kubeconfig.Contexts[0].Name+"-"+request.ClusterID {
			contextFoundInExistingKubeconfig = true
			kubeconfigContext.Context = k8s.KubeconfigContext{
				Cluster: kubeconfig.Clusters[0].Name + "-" + request.ClusterID,
				User:    kubeconfig.Users[0].Name + "-" + request.ClusterID,
			}
			break
		}
	}
	if !contextFoundInExistingKubeconfig {
		existingKubeconfig.Contexts = append(existingKubeconfig.Contexts, &k8s.KubeconfigContextWithName{
			Name: kubeconfig.Contexts[0].Name + "-" + request.ClusterID,
			Context: k8s.KubeconfigContext{
				Cluster: kubeconfig.Clusters[0].Name + "-" + request.ClusterID,
				User:    kubeconfig.Users[0].Name + "-" + request.ClusterID,
			},
		})
	}

	// loop through all users and insert the wanted one if it does not exist
	userFoundInExistingKubeconfig := false
	for _, user := range existingKubeconfig.Users {
		if user.Name == kubeconfig.Users[0].Name+"-"+request.ClusterID {
			userFoundInExistingKubeconfig = true
			user.User = kubeconfig.Users[0].User
			break
		}
	}
	if !userFoundInExistingKubeconfig {
		existingKubeconfig.Users = append(existingKubeconfig.Users, &k8s.KubeconfigUserWithName{
			Name: kubeconfig.Users[0].Name + "-" + request.ClusterID,
			User: kubeconfig.Users[0].User,
		})
	}

	// set the current context to the new one
	if !request.KeepCurentContext {
		existingKubeconfig.CurrentContext = kubeconfig.Contexts[0].Name + "-" + request.ClusterID
	}

	// if it's a new file, set the correct config in the file
	if existingKubeconfig.APIVersion == "" {
		existingKubeconfig.APIVersion = kubeconfigAPIVersion
	}
	if existingKubeconfig.Kind == "" {
		existingKubeconfig.Kind = kubeconfigKind
	}

	err = marshalAndWriteKubeconfig(existingKubeconfig, kubeconfigPath)
	if err != nil {
		return nil, err
	}

	return fmt.Sprintf("Kubeconfig for cluster %s successfully written at %s", request.ClusterID, kubeconfigPath), nil
}
<commit_msg>fix(k8s): fix typo in arg name (#970)<commit_after>package k8s

import (
	"context"
	"fmt"
	"os"
	"path"
	"reflect"

	"github.com/scaleway/scaleway-cli/internal/core"
	k8s "github.com/scaleway/scaleway-sdk-go/api/k8s/v1"
	"github.com/scaleway/scaleway-sdk-go/scw"
)

const (
	kubeLocationDir      = ".kube"
	kubeconfigAPIVersion = "v1"
	kubeconfigKind       = "Config"
)

type k8sKubeconfigInstallRequest struct {
	ClusterID          string
	Region             scw.Region
	KeepCurrentContext bool
}

func k8sKubeconfigInstallCommand() *core.Command {
	return &core.Command{
		Short: `Install a kubeconfig`,
		Long: `Retrieve the kubeconfig for a specified cluster and write it on disk.
It will merge the new kubeconfig in the file pointed by the KUBECONFIG variable. If empty it will default to $HOME/.kube/config.`,
		Namespace: "k8s",
		Verb:      "install",
		Resource:  "kubeconfig",
		ArgsType:  reflect.TypeOf(k8sKubeconfigInstallRequest{}),
		ArgSpecs: core.ArgSpecs{
			{
				Name:       "cluster-id",
				Short:      "Cluster ID from which to retrieve the kubeconfig",
				Required:   true,
				Positional: true,
			},
			{
				Name:  "keep-current-context",
				Short: "Whether or not to keep the current kubeconfig context unmodified",
			},
			core.RegionArgSpec(),
		},
		Run: k8sKubeconfigInstallRun,
		Examples: []*core.Example{
			{
				Short:   "Install the kubeconfig for a given cluster and using the new context",
				Request: `{"cluster_id": "11111111-1111-1111-1111-111111111111"}`,
			},
		},
		SeeAlsos: []*core.SeeAlso{
			{
				Command: "scw k8s kubeconfig uninstall",
				Short:   "Uninstall a kubeconfig",
			},
		},
	}
}

func k8sKubeconfigInstallRun(ctx context.Context, argsI interface{}) (i interface{}, e error) {
	request := argsI.(*k8sKubeconfigInstallRequest)

	kubeconfigRequest := &k8s.GetClusterKubeConfigRequest{
		Region:    request.Region,
		ClusterID: request.ClusterID,
	}

	client := core.ExtractClient(ctx)
	apiK8s := k8s.NewAPI(client)

	// get the wanted kubeconfig
	kubeconfig, err := apiK8s.GetClusterKubeConfig(kubeconfigRequest)
	if err != nil {
		return nil, err
	}

	kubeconfigPath, err := getKubeconfigPath(ctx)
	if err != nil {
		return nil, err
	}

	// create the kubeconfig file if it does not exist
	if _, err := os.Stat(kubeconfigPath); os.IsNotExist(err) {
		// make sure the directory exists
		err = os.MkdirAll(path.Dir(kubeconfigPath), 0755)
		if err != nil {
			return nil, err
		}

		// create the file
		f, err := os.OpenFile(kubeconfigPath, os.O_CREATE, 0644)
		if err != nil {
			return nil, err
		}
		f.Close()
	}

	existingKubeconfig, err := openAndUnmarshalKubeconfig(kubeconfigPath)
	if err != nil {
		return nil, err
	}

	// loop through all clusters and insert the wanted one if it does not exist
	clusterFoundInExistingKubeconfig := false
	for _, cluster := range existingKubeconfig.Clusters {
		if cluster.Name == kubeconfig.Clusters[0].Name+"-"+request.ClusterID {
			clusterFoundInExistingKubeconfig = true
			cluster.Cluster = kubeconfig.Clusters[0].Cluster
			break
		}
	}
	if !clusterFoundInExistingKubeconfig {
		existingKubeconfig.Clusters = append(existingKubeconfig.Clusters, &k8s.KubeconfigClusterWithName{
			Name:    kubeconfig.Clusters[0].Name + "-" + request.ClusterID,
			Cluster: kubeconfig.Clusters[0].Cluster,
		})
	}

	// loop through all contexts and insert the wanted one if it does not exist
	contextFoundInExistingKubeconfig := false
	for _, kubeconfigContext := range existingKubeconfig.Contexts {
		if kubeconfigContext.Name == kubeconfig.Contexts[0].Name+"-"+request.ClusterID {
			contextFoundInExistingKubeconfig = true
			kubeconfigContext.Context = k8s.KubeconfigContext{
				Cluster: kubeconfig.Clusters[0].Name + "-" + request.ClusterID,
				User:    kubeconfig.Users[0].Name + "-" + request.ClusterID,
			}
			break
		}
	}
	if !contextFoundInExistingKubeconfig {
		existingKubeconfig.Contexts = append(existingKubeconfig.Contexts, &k8s.KubeconfigContextWithName{
			Name: kubeconfig.Contexts[0].Name + "-" + request.ClusterID,
			Context: k8s.KubeconfigContext{
				Cluster: kubeconfig.Clusters[0].Name + "-" + request.ClusterID,
				User:    kubeconfig.Users[0].Name + "-" + request.ClusterID,
			},
		})
	}

	// loop through all users and insert the wanted one if it does not exist
	userFoundInExistingKubeconfig := false
	for _, user := range existingKubeconfig.Users {
		if user.Name == kubeconfig.Users[0].Name+"-"+request.ClusterID {
			userFoundInExistingKubeconfig = true
			user.User = kubeconfig.Users[0].User
			break
		}
	}
	if !userFoundInExistingKubeconfig {
		existingKubeconfig.Users = append(existingKubeconfig.Users, &k8s.KubeconfigUserWithName{
			Name: kubeconfig.Users[0].Name + "-" + request.ClusterID,
			User: kubeconfig.Users[0].User,
		})
	}

	// set the current context to the new one
	if !request.KeepCurrentContext {
		existingKubeconfig.CurrentContext = kubeconfig.Contexts[0].Name + "-" + request.ClusterID
	}

	// if it's a new file, set the correct config in the file
	if existingKubeconfig.APIVersion == "" {
		existingKubeconfig.APIVersion = kubeconfigAPIVersion
	}
	if existingKubeconfig.Kind == "" {
		existingKubeconfig.Kind = kubeconfigKind
	}

	err = marshalAndWriteKubeconfig(existingKubeconfig, kubeconfigPath)
	if err != nil {
		return nil, err
	}

	return fmt.Sprintf("Kubeconfig for cluster %s successfully written at %s", request.ClusterID, kubeconfigPath), nil
}
<|endoftext|>


<commit_before>package gpio

import (
	"fmt"
	"os"
	"syscall"

	"github.com/ungerik/go-quick"
)

const (
	HIGH = true
	LOW  = false

	_EPOLLET = 1 << 31
)

type Edge string

const (
	NO_EDGE      Edge = "none"
	RISING_EDGE  Edge = "rising"
	FALLING_EDGE Edge = "falling"
	BOTH_EDGES   Edge = "both"
)

type Direction string

const (
	INPUT  Direction = "in"
	OUTPUT Direction = "out"
	// ALT0   Direction = 4
)

// TODO: How is this configured?
// Maybe see https://github.com/adafruit/PyBBIO/blob/master/bbio/bbio.py
type PullUpDown int

const (
	PUD_OFF  PullUpDown = 0
	PUD_DOWN PullUpDown = 1
	PUD_UP   PullUpDown = 2
)

type GPIO struct {
	nr    int
	value *os.File
	epfd  quick.SyncInt
}

// NewGPIO exports the GPIO pin nr.
func NewGPIO(nr int) (*GPIO, error) {
	gpio := &GPIO{nr: nr}

	export, err := os.OpenFile("/sys/class/gpio/export", os.O_WRONLY, 0666)
	if err != nil {
		return nil, err
	}
	defer export.Close()

	_, err = fmt.Fprintf(export, "%d", gpio.nr)
	if err != nil {
		return nil, err
	}

	return gpio, nil
}

// Close unexports the GPIO pin.
func (gpio *GPIO) Close() error {
	gpio.RemoveEdgeDetect()

	if gpio.value != nil {
		gpio.value.Close()
	}

	unexport, err := os.OpenFile("/sys/class/gpio/unexport", os.O_WRONLY, 0666)
	if err != nil {
		return err
	}
	defer unexport.Close()

	_, err = fmt.Fprintf(unexport, "%d", gpio.nr)
	return err
}

func (gpio *GPIO) Direction() (Direction, error) {
	filename := fmt.Sprintf("/sys/class/gpio/gpio%d/direction", gpio.nr)
	file, err := os.OpenFile(filename, os.O_RDONLY|syscall.O_NONBLOCK, 0666)
	if err != nil {
		return "", err
	}
	defer file.Close()
	direction := make([]byte, 3)
	_, err = file.Read(direction)
	if err != nil {
		return "", err
	}
	if Direction(direction) == OUTPUT {
		return OUTPUT, nil
	} else {
		return INPUT, nil
	}
}

func (gpio *GPIO) SetDirection(direction Direction) error {
	filename := fmt.Sprintf("/sys/class/gpio/gpio%d/direction", gpio.nr)
	file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
	if err != nil {
		return err
	}
	defer file.Close()
	_, err = file.Write([]byte(direction))
	return err
}

func (gpio *GPIO) openValueFile() error {
	if gpio.value != nil {
		return nil
	}
	filename := fmt.Sprintf("/sys/class/gpio/gpio%d/value", gpio.nr)
	file, err := os.OpenFile(filename, os.O_RDWR, 0666)
	if err == nil {
		gpio.value = file
	}
	return err
}

func (gpio *GPIO) Value() (bool, error) {
	if err := gpio.openValueFile(); err != nil {
		return false, err
	}
	gpio.value.Seek(0, os.SEEK_SET)
	val := make([]byte, 1)
	_, err := gpio.value.Read(val)
	if err != nil {
		return false, err
	}
	return val[0] == '1', nil
}

func (gpio *GPIO) SetValue(value bool) (err error) {
	if err = gpio.openValueFile(); err != nil {
		return err
	}
	gpio.value.Seek(0, os.SEEK_SET)
	if value {
		_, err = gpio.value.Write([]byte{'1'})
	} else {
		_, err = gpio.value.Write([]byte{'0'})
	}
	return err
}

func (gpio *GPIO) SetEdge(edge Edge) error {
	filename := fmt.Sprintf("/sys/class/gpio/gpio%d/edge", gpio.nr)
	file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
	if err != nil {
		return err
	}
	defer file.Close()
	_, err = file.Write([]byte(edge))
	return err
}

func (gpio *GPIO) AddEdgeDetect(edge Edge) (chan bool, error) {
	gpio.RemoveEdgeDetect()

	err := gpio.SetDirection(INPUT)
	if err != nil {
		return nil, err
	}
	err = gpio.SetEdge(edge)
	if err != nil {
		return nil, err
	}
	err = gpio.openValueFile()
	if err != nil {
		return nil, err
	}

	epfd, err := syscall.EpollCreate(1)
	if err != nil {
		return nil, err
	}

	event := &syscall.EpollEvent{
		Events: syscall.EPOLLIN | _EPOLLET | syscall.EPOLLPRI,
		Fd:     int32(gpio.value.Fd()),
	}
	err = syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, int(gpio.value.Fd()), event)
	if err != nil {
		syscall.Close(epfd)
		return nil, err
	}

	// / first time triggers with current state, so ignore
	_, err = syscall.EpollWait(epfd, make([]syscall.EpollEvent, 1), -1)
	if err != nil {
		syscall.Close(epfd)
		return nil, err
	}

	gpio.epfd.Set(epfd)

	valueChan := make(chan bool)
	go func() {
		for gpio.epfd.Get() != 0 {
			n, _ := syscall.EpollWait(epfd, make([]syscall.EpollEvent, 1), -1)
			if n > 0 {
				value, err := gpio.Value()
				if err == nil {
					valueChan <- value
				}
			}
		}
	}()
	return valueChan, nil
}

func (gpio *GPIO) RemoveEdgeDetect() {
	epfd := gpio.epfd.Swap(0)
	if epfd != 0 {
		syscall.Close(epfd)
	}
}

func (gpio *GPIO) BlockingWaitForEdge(edge Edge) (value bool, err error) {
	valueChan, err := gpio.AddEdgeDetect(edge)
	if err == nil {
		value = <-valueChan
		gpio.RemoveEdgeDetect()
	}
	return value, err
}
<commit_msg>added direction to NewGPIO<commit_after>package gpio

import (
	"fmt"
	"os"
	"syscall"

	"github.com/ungerik/go-quick"
)

const (
	HIGH = true
	LOW  = false

	_EPOLLET = 1 << 31
)

type Edge string

const (
	NO_EDGE      Edge = "none"
	RISING_EDGE  Edge = "rising"
	FALLING_EDGE Edge = "falling"
	BOTH_EDGES   Edge = "both"
)

type Direction string

const (
	INPUT  Direction = "in"
	OUTPUT Direction = "out"
	// ALT0   Direction = 4
)

// TODO: How is this configured?
// Maybe see https://github.com/adafruit/PyBBIO/blob/master/bbio/bbio.py
type PullUpDown int

const (
	PUD_OFF  PullUpDown = 0
	PUD_DOWN PullUpDown = 1
	PUD_UP   PullUpDown = 2
)

type GPIO struct {
	nr    int
	value *os.File
	epfd  quick.SyncInt
}

// NewGPIO exports the GPIO pin nr.
func NewGPIO(nr int, direction Direction) (*GPIO, error) {
	gpio := &GPIO{nr: nr}

	export, err := os.OpenFile("/sys/class/gpio/export", os.O_WRONLY, 0666)
	if err != nil {
		return nil, err
	}
	defer export.Close()

	_, err = fmt.Fprintf(export, "%d", gpio.nr)
	if err != nil {
		return nil, err
	}

	err = gpio.SetDirection(direction)
	if err != nil {
		return nil, err
	}

	return gpio, nil
}

// Close unexports the GPIO pin.
func (gpio *GPIO) Close() error {
	gpio.RemoveEdgeDetect()

	if gpio.value != nil {
		gpio.value.Close()
	}

	unexport, err := os.OpenFile("/sys/class/gpio/unexport", os.O_WRONLY, 0666)
	if err != nil {
		return err
	}
	defer unexport.Close()

	_, err = fmt.Fprintf(unexport, "%d", gpio.nr)
	return err
}

func (gpio *GPIO) Direction() (Direction, error) {
	filename := fmt.Sprintf("/sys/class/gpio/gpio%d/direction", gpio.nr)
	file, err := os.OpenFile(filename, os.O_RDONLY|syscall.O_NONBLOCK, 0666)
	if err != nil {
		return "", err
	}
	defer file.Close()
	direction := make([]byte, 3)
	_, err = file.Read(direction)
	if err != nil {
		return "", err
	}
	if Direction(direction) == OUTPUT {
		return OUTPUT, nil
	} else {
		return INPUT, nil
	}
}

func (gpio *GPIO) SetDirection(direction Direction) error {
	filename := fmt.Sprintf("/sys/class/gpio/gpio%d/direction", gpio.nr)
	file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
	if err != nil {
		return err
	}
	defer file.Close()
	_, err = file.Write([]byte(direction))
	return err
}

func (gpio *GPIO) openValueFile() error {
	if gpio.value != nil {
		return nil
	}
	filename := fmt.Sprintf("/sys/class/gpio/gpio%d/value", gpio.nr)
	file, err := os.OpenFile(filename, os.O_RDWR, 0666)
	if err == nil {
		gpio.value = file
	}
	return err
}

func (gpio *GPIO) Value() (bool, error) {
	if err := gpio.openValueFile(); err != nil {
		return false, err
	}
	gpio.value.Seek(0, os.SEEK_SET)
	val := make([]byte, 1)
	_, err := gpio.value.Read(val)
	if err != nil {
		return false, err
	}
	return val[0] == '1', nil
}

func (gpio *GPIO) SetValue(value bool) (err error) {
	if err = gpio.openValueFile(); err != nil {
		return err
	}
	gpio.value.Seek(0, os.SEEK_SET)
	if value {
		_, err = gpio.value.Write([]byte{'1'})
	} else {
		_, err = gpio.value.Write([]byte{'0'})
	}
	return err
}

func (gpio *GPIO) SetEdge(edge Edge) error {
	filename := fmt.Sprintf("/sys/class/gpio/gpio%d/edge", gpio.nr)
	file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
	if err != nil {
		return err
	}
	defer file.Close()
	_, err = file.Write([]byte(edge))
	return err
}

func (gpio *GPIO) AddEdgeDetect(edge Edge) (chan bool, error) {
	gpio.RemoveEdgeDetect()

	err := gpio.SetDirection(INPUT)
	if err != nil {
		return nil, err
	}
	err = gpio.SetEdge(edge)
	if err != nil {
		return nil, err
	}
	err = gpio.openValueFile()
	if err != nil {
		return nil, err
	}

	epfd, err := syscall.EpollCreate(1)
	if err != nil {
		return nil, err
	}

	event := &syscall.EpollEvent{
		Events: syscall.EPOLLIN | _EPOLLET | syscall.EPOLLPRI,
		Fd:     int32(gpio.value.Fd()),
	}
	err = syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, int(gpio.value.Fd()), event)
	if err != nil {
		syscall.Close(epfd)
		return nil, err
	}

	// / first time triggers with current state, so ignore
	_, err = syscall.EpollWait(epfd, make([]syscall.EpollEvent, 1), -1)
	if err != nil {
		syscall.Close(epfd)
		return nil, err
	}

	gpio.epfd.Set(epfd)

	valueChan := make(chan bool)
	go func() {
		for gpio.epfd.Get() != 0 {
			n, _ := syscall.EpollWait(epfd, make([]syscall.EpollEvent, 1), -1)
			if n > 0 {
				value, err := gpio.Value()
				if err == nil {
					valueChan <- value
				}
			}
		}
	}()
	return valueChan, nil
}

func (gpio *GPIO) RemoveEdgeDetect() {
	epfd := gpio.epfd.Swap(0)
	if epfd != 0 {
		syscall.Close(epfd)
	}
}

func (gpio *GPIO) BlockingWaitForEdge(edge Edge) (value bool, err error) {
	valueChan, err := gpio.AddEdgeDetect(edge)
	if err == nil {
		value = <-valueChan
		gpio.RemoveEdgeDetect()
	}
	return value, err
}
<|endoftext|>


<commit_before>package navigator

import (
	"go/ast"
	"go/parser"
	"go/token"
	"strings"

	"github.com/nelsam/gxui"
	"github.com/nelsam/gxui/math"
)

type genericTreeNode struct {
	gxui.AdapterBase

	name     string
	path     string
	color    gxui.Color
	children []gxui.TreeNode
}

func (t genericTreeNode) Count() int {
	return len(t.children)
}

func (t genericTreeNode) ItemIndex(item gxui.AdapterItem) int {
	for i, child := range t.children {
		if strings.HasPrefix(item.(string), child.Item().(string)) {
			return i
		}
	}
	return -1
}

func (t genericTreeNode) Size(theme gxui.Theme) math.Size {
	return math.Size{
		W: 20 * theme.DefaultMonospaceFont().GlyphMaxSize().W,
		H: theme.DefaultMonospaceFont().GlyphMaxSize().H,
	}
}

func (t genericTreeNode) Item() gxui.AdapterItem {
	return t.path
}

func (t genericTreeNode) NodeAt(index int) gxui.TreeNode {
	return t.children[index]
}

func (t genericTreeNode) Create(theme gxui.Theme) gxui.Control {
	label := theme.CreateLabel()
	label.SetText(t.name)
	label.SetColor(t.color)
	return label
}

type Location struct {
	filename string
	pos      int
}

func (l Location) File() string {
	return l.filename
}

func (l Location) Pos() int {
	return l.pos
}

type Name struct {
	genericTreeNode
	Location
}

type TOC struct {
	genericTreeNode

	pkg  string
	path string
}

func NewTOC(path string) *TOC {
	toc := &TOC{}
	toc.Init(path)
	return toc
}

func (t *TOC) Init(path string) {
	t.path = path
	t.Reload()
}

func (t *TOC) Reload() {
	pkgs, err := parser.ParseDir(token.NewFileSet(), t.path, nil, 0)
	if err != nil {
		return
	}
	for name, pkg := range pkgs {
		if strings.HasSuffix(name, "_test") {
			continue
		}
		t.pkg = name
		t.addPkg(pkg)
	}
}

func (t *TOC) addPkg(pkg *ast.Package) {
	var (
		consts  []gxui.TreeNode
		vars    []gxui.TreeNode
		typeMap = make(map[string]*Name)
		types   []gxui.TreeNode
		funcs   []gxui.TreeNode
	)
	for filename, f := range pkg.Files {
		if strings.HasSuffix(filename, "_test.go") {
			continue
		}
		for _, decl := range f.Decls {
			switch src := decl.(type) {
			case *ast.GenDecl:
				switch src.Tok.String() {
				case "const":
					consts = append(consts, valueNamesFrom(filename, "constants", src.Specs)...)
				case "var":
					vars = append(vars, valueNamesFrom(filename, "global vars", src.Specs)...)
				case "type":
					// I have yet to see a case where a type declaration has more than one Specs.
					typeSpec := src.Specs[0].(*ast.TypeSpec)
					typeName := typeSpec.Name.String()

					// We can't guarantee that the type declaration was found before method
					// declarations, so the value may already exist in the map.
					typ, ok := typeMap[typeName]
					if !ok {
						typ = &Name{}
						typeMap[typeName] = typ
					}
					typ.name = typeName
					typ.path = "types." + typ.name
					typ.color = dirColor
					typ.filename = filename
					typ.pos = int(typeSpec.Pos())
					types = append(types, typ)
				}
			case *ast.FuncDecl:
				var name Name
				name.name = src.Name.String()
				name.path = "funcs." + name.name
				name.color = dirColor
				name.filename = filename
				name.pos = int(src.Pos())
				if src.Recv == nil {
					funcs = append(funcs, name)
					continue
				}
				recvTyp := src.Recv.List[0].Type
				if starExpr, ok := recvTyp.(*ast.StarExpr); ok {
					recvTyp = starExpr.X
				}
				recvTypeName := recvTyp.(*ast.Ident).String()
				typ, ok := typeMap[recvTypeName]
				if !ok {
					typ = &Name{}
					typeMap[recvTypeName] = typ
				}
				name.path = "types." + recvTypeName + "." + name.name
				typ.children = append(typ.children, name)
			}
		}
	}
	t.children = append(t.children,
		genericTreeNode{name: "constants", path: "constants", color: dirColor, children: consts},
		genericTreeNode{name: "global vars", path: "global vars", color: dirColor, children: vars},
		genericTreeNode{name: "types", path: "types", color: dirColor, children: types},
		genericTreeNode{name: "funcs", path: "funcs", color: dirColor, children: funcs},
	)
}

func valueNamesFrom(filename, parentName string, specs []ast.Spec) (names []gxui.TreeNode) {
	for _, spec := range specs {
		valSpec, ok := spec.(*ast.ValueSpec)
		if !ok {
			continue
		}
		for _, name := range valSpec.Names {
			var newName Name
			newName.name = name.String()
			newName.path = parentName + "." + newName.name
			newName.color = dirColor
			newName.filename = filename
			newName.pos = int(name.Pos())
			names = append(names, newName)
		}
	}
	return
}
<commit_msg>Make the package TOC support test packages<commit_after>package navigator

import (
	"go/ast"
	"go/parser"
	"go/token"
	"log"
	"strings"

	"github.com/nelsam/gxui"
	"github.com/nelsam/gxui/math"
)

type genericTreeNode struct {
	gxui.AdapterBase

	name     string
	path     string
	color    gxui.Color
	children []gxui.TreeNode
}

func (t genericTreeNode) Count() int {
	return len(t.children)
}

func (t genericTreeNode) ItemIndex(item gxui.AdapterItem) int {
	path := item.(string)
	for i, child := range t.children {
		childPath := child.Item().(string)
		if path == childPath || strings.HasPrefix(path, childPath+".") {
			return i
		}
	}
	return -1
}

func (t genericTreeNode) Size(theme gxui.Theme) math.Size {
	return math.Size{
		W: 20 * theme.DefaultMonospaceFont().GlyphMaxSize().W,
		H: theme.DefaultMonospaceFont().GlyphMaxSize().H,
	}
}

func (t genericTreeNode) Item() gxui.AdapterItem {
	return t.path
}

func (t genericTreeNode) NodeAt(index int) gxui.TreeNode {
	return t.children[index]
}

func (t genericTreeNode) Create(theme gxui.Theme) gxui.Control {
	label := theme.CreateLabel()
	label.SetText(t.name)
	label.SetColor(t.color)
	return label
}

// skippingTreeNode is a gxui.TreeNode that will skip to the child
// node if there is exactly one child node on implementations of all
// gxui.TreeNode methods.  This means that any number of nested
// skippingTreeNodes will display as a single node, so long as no
// child contains more than one child.
type skippingTreeNode struct {
	genericTreeNode
}

func (t skippingTreeNode) Count() int {
	if len(t.children) == 1 {
		return t.children[0].Count()
	}
	return t.genericTreeNode.Count()
}

func (t skippingTreeNode) ItemIndex(item gxui.AdapterItem) int {
	if len(t.children) == 1 {
		return t.children[0].ItemIndex(item)
	}
	return t.genericTreeNode.ItemIndex(item)
}

func (t skippingTreeNode) Item() gxui.AdapterItem {
	if len(t.children) == 1 {
		return t.children[0].Item()
	}
	return t.genericTreeNode.Item()
}

func (t skippingTreeNode) NodeAt(index int) gxui.TreeNode {
	if len(t.children) == 1 {
		return t.children[0].NodeAt(index)
	}
	return t.genericTreeNode.NodeAt(index)
}

func (t skippingTreeNode) Create(theme gxui.Theme) gxui.Control {
	if len(t.children) == 1 {
		return t.children[0].Create(theme)
	}
	return t.genericTreeNode.Create(theme)
}

type Location struct {
	filename string
	pos      int
}

func (l Location) File() string {
	return l.filename
}

func (l Location) Pos() int {
	return l.pos
}

type Name struct {
	genericTreeNode
	Location
}

type TOC struct {
	skippingTreeNode

	path string
}

func NewTOC(path string) *TOC {
	toc := &TOC{}
	toc.Init(path)
	return toc
}

func (t *TOC) Init(path string) {
	t.path = path
	t.Reload()
}

func (t *TOC) Reload() {
	pkgs, err := parser.ParseDir(token.NewFileSet(), t.path, nil, 0)
	if err != nil {
		log.Printf("Error parsing dir: %s", err)
	}
	for _, pkg := range pkgs {
		t.children = append(t.children, t.parsePkg(pkg))
	}
}

func (t *TOC) parsePkg(pkg *ast.Package) genericTreeNode {
	var (
		pkgNode = genericTreeNode{name: pkg.Name, path: pkg.Name, color: dirColor}

		consts  []gxui.TreeNode
		vars    []gxui.TreeNode
		typeMap = make(map[string]*Name)
		types   []gxui.TreeNode
		funcs   []gxui.TreeNode
	)
	for filename, f := range pkg.Files {
		for _, decl := range f.Decls {
			switch src := decl.(type) {
			case *ast.GenDecl:
				switch src.Tok.String() {
				case "const":
					consts = append(consts, valueNamesFrom(filename, pkg.Name+".constants", src.Specs)...)
				case "var":
					vars = append(vars, valueNamesFrom(filename, pkg.Name+".global vars", src.Specs)...)
				case "type":
					// I have yet to see a case where a type declaration has more than one Specs.
					typeSpec := src.Specs[0].(*ast.TypeSpec)
					typeName := typeSpec.Name.String()

					// We can't guarantee that the type declaration was found before method
					// declarations, so the value may already exist in the map.
					typ, ok := typeMap[typeName]
					if !ok {
						typ = &Name{}
						typeMap[typeName] = typ
					}
					typ.name = typeName
					typ.path = pkg.Name + ".types." + typ.name
					typ.color = dirColor
					typ.filename = filename
					typ.pos = int(typeSpec.Pos())
					types = append(types, typ)
				}
			case *ast.FuncDecl:
				var name Name
				name.name = src.Name.String()
				name.path = pkg.Name + ".funcs." + name.name
				name.color = dirColor
				name.filename = filename
				name.pos = int(src.Pos())
				if src.Recv == nil {
					funcs = append(funcs, name)
					continue
				}
				recvTyp := src.Recv.List[0].Type
				if starExpr, ok := recvTyp.(*ast.StarExpr); ok {
					recvTyp = starExpr.X
				}
				recvTypeName := recvTyp.(*ast.Ident).String()
				typ, ok := typeMap[recvTypeName]
				if !ok {
					typ = &Name{}
					typeMap[recvTypeName] = typ
				}
				name.path = pkg.Name + ".types." + recvTypeName + "." + name.name
				typ.children = append(typ.children, name)
			}
		}
	}
	pkgNode.children = []gxui.TreeNode{
		genericTreeNode{name: "constants", path: pkg.Name + ".constants", color: dirColor, children: consts},
		genericTreeNode{name: "global vars", path: pkg.Name + ".global vars", color: dirColor, children: vars},
		genericTreeNode{name: "types", path: pkg.Name + ".types", color: dirColor, children: types},
		genericTreeNode{name: "funcs", path: pkg.Name + ".funcs", color: dirColor, children: funcs},
	}
	return pkgNode
}

func valueNamesFrom(filename, parentName string, specs []ast.Spec) (names []gxui.TreeNode) {
	for _, spec := range specs {
		valSpec, ok := spec.(*ast.ValueSpec)
		if !ok {
			continue
		}
		for _, name := range valSpec.Names {
			var newName Name
			newName.name = name.String()
			newName.path = parentName + "." + newName.name
			newName.color = dirColor
			newName.filename = filename
			newName.pos = int(name.Pos())
			names = append(names, newName)
		}
	}
	return
}
<|endoftext|>


<commit_before>package hardcode

import (
	"bytes"
	"unicode/utf8"

	"github.com/valyala/bytebufferpool"
)

var domainChars = func() (as asciiSet) {
	for i := 'a'; i <= 'z'; i++ {
		as.add(byte(i))
	}
	for i := 'A'; i <= 'Z'; i++ {
		as.add(byte(i))
	}
	for i := '0'; i <= '9'; i++ {
		as.add(byte(i))
	}
	as.add(byte('-'))
	as.add(byte('_'))
	return
}()

var (
	userapiStr            = []byte("userapi")
	vkuserStr             = []byte("vkuser")
	vkcdnStr              = []byte("vk-cdn")
	vkStr                 = []byte("vk")
	comStr                = []byte("com")
	netStr                = []byte("net")
	meStr                 = []byte("me")
	videoStr              = []byte("video")
	audioStr              = []byte("audio")
	liveStr               = []byte("live")
	escapedSlashStr       = []byte(`\/`)
	escapedDoubleSlashStr = []byte(`\/\/`)
	dotStr                = []byte(`.`)
	jsonHttpsStr          = []byte(`"https:`)
	docPathStr            = []byte(`doc`)
	imagesPathStr         = []byte(`images\/`)
	imagesPath2Str        = []byte(`\/images\/`)
	stickerPathStr        = []byte(`sticker`)
	stickersPathEndingStr = []byte(`s_`)
	videoHlsStr           = []byte(`video_hls.php`)
)

const (
	maxDomainPartLen      = 15
	escapedDoubleSlashLen = 4
	jsonHttpsLen          = 7
)

type HardcodedDomainReplaceConfig struct {
	Pool bytebufferpool.Pool

	// Домен, который просто пропускает трафик через себя без обработки, обычно domain.com\/_\/
	SimpleReplace string

	// Домен для замены с обработкой, обычно domain.com\/@
	SmartReplace string
}

type hardcodedDomainReplace struct {
	pool   bytebufferpool.Pool
	simple []byte
	smart  []byte
}

type insertion struct {
	offset  int
	content []byte
}

// Этот реплейс работает абсолютно так же, как вместе взятые следующие регулярки:
// - "https:\\/\\/[-_a-zA-Z0-9]{1,15}\.(?:userapi\.com|vk-cdn\.net|vk\.(?:me|com)|vkuser(?:live|video|audio)\.(?:net|com))\\/
//    -> "https:\/\/proxy_domain\/_\/$1\/
// - "https:\/\/vk.com\/video_hls.php
//    -> "https:\/\/proxy_domain\/@vk.com\/video_hls.php
// - "https:\\/\\/vk\.com\\/((?:\\/)?images\\/|sticker(:?\\/|s_)|doc-?[0-9]+_)
//    -> "https:\/\/proxy_domain\/_\/vk.com\/$1
func NewHardcodedDomainReplace(config HardcodedDomainReplaceConfig) *hardcodedDomainReplace {
	v := &hardcodedDomainReplace{
		pool:   config.Pool,
		simple: []byte(config.SimpleReplace),
		smart:  []byte(config.SmartReplace),
	}
	return v
}

func (v *hardcodedDomainReplace) Apply(input *bytebufferpool.ByteBuffer) *bytebufferpool.ByteBuffer {
	var output *bytebufferpool.ByteBuffer
	var host [][]byte
	offset := 0
	inputLen := len(input.B)
	var insertions []insertion
	for {
		index := bytes.Index(input.B[offset:], escapedDoubleSlashStr)
		if index == -1 {
			break
		}
		match := offset + index
		offset += index + escapedDoubleSlashLen
		if offset+5 > inputLen {
			break
		}

		// Проверка на то что ссылка начинается с http и стоит в начале json строки
		if match < jsonHttpsLen || !bytes.Equal(input.B[match-jsonHttpsLen:match], jsonHttpsStr) {
			continue
		}

		// Чтение домена
		domainLength := bytes.Index(input.B[offset:offset+maxDomainPartLen*3], escapedSlashStr)
		if domainLength == -1 {
			continue
		}

		// Проверка домена на допустимые символы
		host = bytes.SplitN(input.B[offset:offset+domainLength], dotStr, 3)
		for i := len(host) - 1; i >= 0; i-- {
			if len(host[i]) > maxDomainPartLen || !testDomainPart(host[i]) {
				continue
			}
		}

		ins := v.simple
		// Проверка что домен можно проксировать
		if len(host) == 2 {
			if bytes.Equal(host[0], vkStr) && bytes.Equal(host[1], comStr) { // vk.com
				// Слишком короткий путь, гуляем
				if offset+domainLength+5 > inputLen || input.B[offset+domainLength] != '\\' {
					continue
				}
				path := input.B[offset+domainLength+2:]

				if bytes.HasPrefix(path, docPathStr) { // vk.com/doc[-0-9]*
					c := path[len(docPathStr)]
					if c != '-' && !(c >= '0' && c <= '9') {
						continue
					}
				} else if bytes.HasPrefix(path, imagesPathStr) || bytes.HasPrefix(path, imagesPath2Str) { // vk.com//?images/*
					// allow
				} else if bytes.HasPrefix(path, stickerPathStr) { // vk.com/sticker*
					path2 := path[len(stickerPathStr):]
					if !bytes.HasPrefix(path2, escapedSlashStr) && // vk.com/sticker/*
						!bytes.HasPrefix(path2, stickersPathEndingStr) { // vk.com/stickers_
						continue
					}
				} else if bytes.HasPrefix(path, videoHlsStr) {
					ins = v.smart
				} else {
					continue
				}
			} else {
				continue
			}
		} else if len(host) == 3 {
			if bytes.Equal(host[1], userapiStr) { // *.userapi.com
				if !bytes.Equal(host[2], comStr) {
					continue
				}
			} else if bytes.Equal(host[1], vkcdnStr) { // *.vk-cdn.net
				if !bytes.Equal(host[2], netStr) {
					continue
				}
			} else if bytes.Equal(host[1], vkStr) { // *.vk.com
				if bytes.Equal(host[2], comStr) {
					// Домен m.vk.com не проксим
					if len(host[0]) == 1 && host[0][0] == 'm' {
						continue
					}
				} else if !bytes.Equal(host[2], meStr) { // *.vk.me
					continue
				} else {
					continue
				}
			} else if bytes.HasPrefix(host[1], vkuserStr) { // *.vkuser(audio|video|live).(net|com)
				if !bytes.Equal(host[2], comStr) && !bytes.Equal(host[2], netStr) {
					continue
				}
				r := host[1][len(vkuserStr):]
				if !bytes.Equal(r, videoStr) && !bytes.Equal(r, audioStr) && !bytes.Equal(r, liveStr) {
					continue
				}
			} else {
				continue
			}
		} else {
			continue
		}
		if insertions == nil {
			// Прикинем что в ответе на каждые 300 байт по одной ссылке
			insertions = make([]insertion, 0, max(16, roundUpToPowerOfTwo(inputLen/300)))
		}
		insertions = append(insertions, insertion{
			offset:  offset,
			content: ins,
		})
	}
	if insertions == nil {
		return input
	}

	neededLength := inputLen
	for _, ins := range insertions {
		neededLength += len(ins.content)
	}

	output = v.pool.Get()
	if cap(output.B) < neededLength {
		v.pool.Put(output)
		output = &bytebufferpool.ByteBuffer{}
		output.B = make([]byte, 0, neededLength)
	}

	lastAppend := 0
	for _, ins := range insertions {
		output.B = append(append(output.B, input.B[lastAppend:ins.offset]...), ins.content...)
		lastAppend = ins.offset
	}
	output.B = append(output.B, input.B[lastAppend:]...)

	v.pool.Put(input)
	return output
}

func testDomainPart(part []byte) bool {
	for i := len(part) - 1; i >= 0; i-- {
		if !domainChars.contains(part[i]) {
			return false
		}
	}
	return true
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

// https://stackoverflow.com/a/466242/6620659
func roundUpToPowerOfTwo(i int) int {
	i--
	i |= i >> 1
	i |= i >> 2
	i |= i >> 4
	i |= i >> 8
	i |= i >> 16
	return i + 1
}

// asciiSet is a 32-byte value, where each bit represents the presence of a
// given ASCII character in the set. The 128-bits of the lower 16 bytes,
// starting with the least-significant bit of the lowest word to the
// most-significant bit of the highest word, map to the full range of all
// 128 ASCII characters. The 128-bits of the upper 16 bytes will be zeroed,
// ensuring that any non-ASCII character will be reported as not in the set.
type asciiSet [8]uint32

// contains reports whether c is inside the set.
func (as *asciiSet) add(c byte) bool {
	if c >= utf8.RuneSelf {
		return false
	}
	as[c>>5] |= 1 << uint(c&31)
	return true
}

func (as *asciiSet) contains(c byte) bool {
	return (as[c>>5] & (1 << uint(c&31))) != 0
}
<commit_msg>Фикс выхода за границы массива<commit_after>package hardcode

import (
	"bytes"
	"unicode/utf8"

	"github.com/valyala/bytebufferpool"
)

var domainChars = func() (as asciiSet) {
	for i := 'a'; i <= 'z'; i++ {
		as.add(byte(i))
	}
	for i := 'A'; i <= 'Z'; i++ {
		as.add(byte(i))
	}
	for i := '0'; i <= '9'; i++ {
		as.add(byte(i))
	}
	as.add(byte('-'))
	as.add(byte('_'))
	return
}()

var (
	userapiStr            = []byte("userapi")
	vkuserStr             = []byte("vkuser")
	vkcdnStr              = []byte("vk-cdn")
	vkStr                 = []byte("vk")
	comStr                = []byte("com")
	netStr                = []byte("net")
	meStr                 = []byte("me")
	videoStr              = []byte("video")
	audioStr              = []byte("audio")
	liveStr               = []byte("live")
	escapedSlashStr       = []byte(`\/`)
	escapedDoubleSlashStr = []byte(`\/\/`)
	dotStr                = []byte(`.`)
	jsonHttpsStr          = []byte(`"https:`)
	docPathStr            = []byte(`doc`)
	imagesPathStr         = []byte(`images\/`)
	imagesPath2Str        = []byte(`\/images\/`)
	stickerPathStr        = []byte(`sticker`)
	stickersPathEndingStr = []byte(`s_`)
	videoHlsStr           = []byte(`video_hls.php`)
)

const (
	maxDomainPartLen      = 15
	escapedDoubleSlashLen = 4
	jsonHttpsLen          = 7
)

type HardcodedDomainReplaceConfig struct {
	Pool bytebufferpool.Pool

	// Домен, который просто пропускает трафик через себя без обработки, обычно domain.com\/_\/
	SimpleReplace string

	// Домен для замены с обработкой, обычно domain.com\/@
	SmartReplace string
}

type hardcodedDomainReplace struct {
	pool   bytebufferpool.Pool
	simple []byte
	smart  []byte
}

type insertion struct {
	offset  int
	content []byte
}

// Этот реплейс работает абсолютно так же, как вместе взятые следующие регулярки:
// - "https:\\/\\/[-_a-zA-Z0-9]{1,15}\.(?:userapi\.com|vk-cdn\.net|vk\.(?:me|com)|vkuser(?:live|video|audio)\.(?:net|com))\\/
//    -> "https:\/\/proxy_domain\/_\/$1\/
// - "https:\/\/vk.com\/video_hls.php
//    -> "https:\/\/proxy_domain\/@vk.com\/video_hls.php
// - "https:\\/\\/vk\.com\\/((?:\\/)?images\\/|sticker(:?\\/|s_)|doc-?[0-9]+_)
//    -> "https:\/\/proxy_domain\/_\/vk.com\/$1
func NewHardcodedDomainReplace(config HardcodedDomainReplaceConfig) *hardcodedDomainReplace {
	v := &hardcodedDomainReplace{
		pool:   config.Pool,
		simple: []byte(config.SimpleReplace),
		smart:  []byte(config.SmartReplace),
	}
	return v
}

func (v *hardcodedDomainReplace) Apply(input *bytebufferpool.ByteBuffer) *bytebufferpool.ByteBuffer {
	var output *bytebufferpool.ByteBuffer
	var host [][]byte
	offset := 0
	inputLen := len(input.B)
	var insertions []insertion
	for {
		index := bytes.Index(input.B[offset:], escapedDoubleSlashStr)
		if index == -1 {
			break
		}
		match := offset + index
		offset += index + escapedDoubleSlashLen
		if offset+5 > inputLen {
			break
		}

		// Проверка на то что ссылка начинается с http и стоит в начале json строки
		if match < jsonHttpsLen || !bytes.Equal(input.B[match-jsonHttpsLen:match], jsonHttpsStr) {
			continue
		}

		// Чтение домена
		domainLength := bytes.Index(input.B[offset:min(inputLen, offset+maxDomainPartLen*3)], escapedSlashStr)
		if domainLength == -1 {
			continue
		}

		// Проверка домена на допустимые символы
		host = bytes.SplitN(input.B[offset:offset+domainLength], dotStr, 3)
		for i := len(host) - 1; i >= 0; i-- {
			if len(host[i]) > maxDomainPartLen || !testDomainPart(host[i]) {
				continue
			}
		}

		ins := v.simple
		// Проверка что домен можно проксировать
		if len(host) == 2 {
			if bytes.Equal(host[0], vkStr) && bytes.Equal(host[1], comStr) { // vk.com
				// Слишком короткий путь, гуляем
				if offset+domainLength+5 > inputLen || input.B[offset+domainLength] != '\\' {
					continue
				}
				path := input.B[offset+domainLength+2:]

				if bytes.HasPrefix(path, docPathStr) { // vk.com/doc[-0-9]*
					c := path[len(docPathStr)]
					if c != '-' && !(c >= '0' && c <= '9') {
						continue
					}
				} else if bytes.HasPrefix(path, imagesPathStr) || bytes.HasPrefix(path, imagesPath2Str) { // vk.com//?images/*
					// allow
				} else if bytes.HasPrefix(path, stickerPathStr) { // vk.com/sticker*
					path2 := path[len(stickerPathStr):]
					if !bytes.HasPrefix(path2, escapedSlashStr) && // vk.com/sticker/*
						!bytes.HasPrefix(path2, stickersPathEndingStr) { // vk.com/stickers_
						continue
					}
				} else if bytes.HasPrefix(path, videoHlsStr) {
					ins = v.smart
				} else {
					continue
				}
			} else {
				continue
			}
		} else if len(host) == 3 {
			if bytes.Equal(host[1], userapiStr) { // *.userapi.com
				if !bytes.Equal(host[2], comStr) {
					continue
				}
			} else if bytes.Equal(host[1], vkcdnStr) { // *.vk-cdn.net
				if !bytes.Equal(host[2], netStr) {
					continue
				}
			} else if bytes.Equal(host[1], vkStr) { // *.vk.com
				if bytes.Equal(host[2], comStr) {
					// Домен m.vk.com не проксим
					if len(host[0]) == 1 && host[0][0] == 'm' {
						continue
					}
				} else if !bytes.Equal(host[2], meStr) { // *.vk.me
					continue
				} else {
					continue
				}
			} else if bytes.HasPrefix(host[1], vkuserStr) { // *.vkuser(audio|video|live).(net|com)
				if !bytes.Equal(host[2], comStr) && !bytes.Equal(host[2], netStr) {
					continue
				}
				r := host[1][len(vkuserStr):]
				if !bytes.Equal(r, videoStr) && !bytes.Equal(r, audioStr) && !bytes.Equal(r, liveStr) {
					continue
				}
			} else {
				continue
			}
		} else {
			continue
		}
		if insertions == nil {
			// Прикинем что в ответе на каждые 300 байт по одной ссылке
			insertions = make([]insertion, 0, max(16, roundUpToPowerOfTwo(inputLen/300)))
		}
		insertions = append(insertions, insertion{
			offset:  offset,
			content: ins,
		})
	}
	if insertions == nil {
		return input
	}

	neededLength := inputLen
	for _, ins := range insertions {
		neededLength += len(ins.content)
	}

	output = v.pool.Get()
	if cap(output.B) < neededLength {
		v.pool.Put(output)
		output = &bytebufferpool.ByteBuffer{}
		output.B = make([]byte, 0, neededLength)
	}

	lastAppend := 0
	for _, ins := range insertions {
		output.B = append(append(output.B, input.B[lastAppend:ins.offset]...), ins.content...)
		lastAppend = ins.offset
	}
	output.B = append(output.B, input.B[lastAppend:]...)

	v.pool.Put(input)
	return output
}

func testDomainPart(part []byte) bool {
	for i := len(part) - 1; i >= 0; i-- {
		if !domainChars.contains(part[i]) {
			return false
		}
	}
	return true
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

func min(a, b int) int {
	if a < b {
		return a
	} else {
		return b
	}
}

// https://stackoverflow.com/a/466242/6620659
func roundUpToPowerOfTwo(i int) int {
	i--
	i |= i >> 1
	i |= i >> 2
	i |= i >> 4
	i |= i >> 8
	i |= i >> 16
	return i + 1
}

// asciiSet is a 32-byte value, where each bit represents the presence of a
// given ASCII character in the set. The 128-bits of the lower 16 bytes,
// starting with the least-significant bit of the lowest word to the
// most-significant bit of the highest word, map to the full range of all
// 128 ASCII characters. The 128-bits of the upper 16 bytes will be zeroed,
// ensuring that any non-ASCII character will be reported as not in the set.
type asciiSet [8]uint32

// contains reports whether c is inside the set.
func (as *asciiSet) add(c byte) bool {
	if c >= utf8.RuneSelf {
		return false
	}
	as[c>>5] |= 1 << uint(c&31)
	return true
}

func (as *asciiSet) contains(c byte) bool {
	return (as[c>>5] & (1 << uint(c&31))) != 0
}
<|endoftext|>