<commit_before>package travis

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"

	"github.com/google/go-github/github"
	"github.com/parkr/auto-reply/ctx"
	"github.com/parkr/auto-reply/search"
)

var (
	travisAPIBaseURL      = "https://api.travis-ci.org"
	travisAPIContentType  = "application/vnd.travis-ci.2+json"
	failingFmtBuildLabels = []string{"tests", "help-wanted"}
)

type travisBuild struct {
	JobIDs []int64 `json:"job_ids"`
}

type travisJob struct {
	State  string
	Config travisJobConfig
}

type travisJobConfig struct {
	Env string
}

func FailingFmtBuildHandler(context *ctx.Context, payload interface{}) error {
	status, ok := payload.(*github.StatusEvent)
	if !ok {
		return context.NewError("FailingFmtBuildHandler: not an status event")
	}

	if *status.State != "failure" {
		return context.NewError("FailingFmtBuildHandler: not a failure status event")
	}

	if *status.Context != "continuous-integration/travis-ci/push" {
		return context.NewError("FailingFmtBuildHandler: not a continuous-integration/travis-ci/push context")
	}

	if status.Branches != nil && len(status.Branches) > 0 && *status.Branches[0].Name != "master" {
		return context.NewError("FailingFmtBuildHandler: not a travis build on the master branch")
	}

	context.SetRepo(*status.Repo.Owner.Login, *status.Repo.Name)

	uri := fmt.Sprintf("/repos/%s/%s/%d", context.Repo.Owner, context.Repo.Name)
	resp, err := httpGetTravis(uri)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: %+v", err)
	}

	build := struct {
		Build travisBuild `json:"build"`
	}{Build: travisBuild{}}
	err = json.NewDecoder(resp.Body).Decode(&build)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: couldn't decode build json: %+v", err)
	}
	log.Printf("FailingFmtBuildHandler: response: %+v %+v", resp, build)

	for _, jobID := range build.Build.JobIDs {
		job := struct {
			Job travisJob `json:"job"`
		}{Job: travisJob{}}
		resp, err := httpGetTravis("/jobs" + strconv.FormatInt(jobID, 10))
		if err != nil {
			return context.NewError("FailingFmtBuildHandler: couldn't get job info from travis: %+v", err)
		}
		err = json.NewDecoder(resp.Body).Decode(&job)
		if err != nil {
			return context.NewError("FailingFmtBuildHandler: couldn't decode job json: %+v", err)
		}
		log.Printf("FailingFmtBuildHandler: job %d response: %+v %+v", jobID, resp, job)
		if job.Job.State == "failure" && job.Job.Config.Env == "TEST_SUITE=fmt" {
			// Winner! Open an issue if there isn't already one.
			issues, err := search.GitHubIssues(context, "repo:%s/%s is:open in:title fmt build is failing")
			if err != nil {
				context.NewError("FailingFmtBuildHandler: not a travis build on the master branch")
			}
			if len(issues) > 0 {
				log.Printf("We already have an issue or issues for this failure! %s", issues[0].HTMLURL)
			} else {
				jobHTMLURL := fmt.Sprintf("https://github.com/%s/%s/jobs/%d", context.Repo.Owner, context.Repo.Name, jobID)
				issue, _, err := context.GitHub.Issues.Create(context.Repo.Owner, context.Repo.Name, &github.IssueRequest{
					Title: github.String("fmt build is failing on master"),
					Body: github.String(fmt.Sprint(
						"Hey @jekyll/maintainers!\n\nIt looks like the fmt build in Travis is failing again: %s :frowning_face:\n\nCould someone please fix this up? Clone down the repo, run `bundle install`, then `script/fmt` to see the failures. File a PR once you're done and say \"Fixes <this issue url>\" in the description.\n\nThanks! :revolving_hearts:",
						jobHTMLURL,
					)),
					Labels: &failingFmtBuildLabels,
				})
				if err != nil {
					return context.NewError("FailingFmtBuildHandler: failed to file an issue: %+v", err)
				}
				log.Printf("Filed issue: %s", *issue.HTMLURL)
				break
			}
		}
	}

	return nil
}

func httpGetTravis(uri string) (*http.Response, error) {
	url := travisAPIBaseURL + uri
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("couldn't create request: %+v", err)
	}
	req.Header.Add("Accept", travisAPIContentType)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("couldn't send request to %s: %+v", url, err)
	}
	return resp, err
}
<commit_msg>Extract the build ID.<commit_after>package travis

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"github.com/google/go-github/github"
	"github.com/parkr/auto-reply/ctx"
	"github.com/parkr/auto-reply/search"
)

var (
	travisAPIBaseURL      = "https://api.travis-ci.org"
	travisAPIContentType  = "application/vnd.travis-ci.2+json"
	failingFmtBuildLabels = []string{"tests", "help-wanted"}
)

type travisBuild struct {
	JobIDs []int64 `json:"job_ids"`
}

type travisJob struct {
	State  string
	Config travisJobConfig
}

type travisJobConfig struct {
	Env string
}

func FailingFmtBuildHandler(context *ctx.Context, payload interface{}) error {
	status, ok := payload.(*github.StatusEvent)
	if !ok {
		return context.NewError("FailingFmtBuildHandler: not an status event")
	}

	if *status.State != "failure" {
		return context.NewError("FailingFmtBuildHandler: not a failure status event")
	}

	if *status.Context != "continuous-integration/travis-ci/push" {
		return context.NewError("FailingFmtBuildHandler: not a continuous-integration/travis-ci/push context")
	}

	if status.Branches != nil && len(status.Branches) > 0 && *status.Branches[0].Name != "master" {
		return context.NewError("FailingFmtBuildHandler: not a travis build on the master branch")
	}

	context.SetRepo(*status.Repo.Owner.Login, *status.Repo.Name)

	// Pull build ID from target_url womp
	buildID, err := buildIDFromTargetURL(*status.TargetURL)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: couldn't extract build ID from %q: %+v", *status.TargetURL, err)
	}
	uri := fmt.Sprintf("/repos/%s/%s/%d", context.Repo.Owner, context.Repo.Name, buildID)
	resp, err := httpGetTravis(uri)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: %+v", err)
	}

	build := struct {
		Build travisBuild `json:"build"`
	}{Build: travisBuild{}}
	err = json.NewDecoder(resp.Body).Decode(&build)
	if err != nil {
		return context.NewError("FailingFmtBuildHandler: couldn't decode build json: %+v", err)
	}
	log.Printf("FailingFmtBuildHandler: response: %+v %+v", resp, build)

	for _, jobID := range build.Build.JobIDs {
		job := struct {
			Job travisJob `json:"job"`
		}{Job: travisJob{}}
		resp, err := httpGetTravis("/jobs" + strconv.FormatInt(jobID, 10))
		if err != nil {
			return context.NewError("FailingFmtBuildHandler: couldn't get job info from travis: %+v", err)
		}
		err = json.NewDecoder(resp.Body).Decode(&job)
		if err != nil {
			return context.NewError("FailingFmtBuildHandler: couldn't decode job json: %+v", err)
		}
		log.Printf("FailingFmtBuildHandler: job %d response: %+v %+v", jobID, resp, job)
		if job.Job.State == "failure" && job.Job.Config.Env == "TEST_SUITE=fmt" {
			// Winner! Open an issue if there isn't already one.
			issues, err := search.GitHubIssues(context, "repo:%s/%s is:open in:title fmt build is failing")
			if err != nil {
				context.NewError("FailingFmtBuildHandler: not a travis build on the master branch")
			}
			if len(issues) > 0 {
				log.Printf("We already have an issue or issues for this failure! %s", issues[0].HTMLURL)
			} else {
				jobHTMLURL := fmt.Sprintf("https://github.com/%s/%s/jobs/%d", context.Repo.Owner, context.Repo.Name, jobID)
				issue, _, err := context.GitHub.Issues.Create(context.Repo.Owner, context.Repo.Name, &github.IssueRequest{
					Title: github.String("fmt build is failing on master"),
					Body: github.String(fmt.Sprint(
						"Hey @jekyll/maintainers!\n\nIt looks like the fmt build in Travis is failing again: %s :frowning_face:\n\nCould someone please fix this up? Clone down the repo, run `bundle install`, then `script/fmt` to see the failures. File a PR once you're done and say \"Fixes <this issue url>\" in the description.\n\nThanks! :revolving_hearts:",
						jobHTMLURL,
					)),
					Labels: &failingFmtBuildLabels,
				})
				if err != nil {
					return context.NewError("FailingFmtBuildHandler: failed to file an issue: %+v", err)
				}
				log.Printf("Filed issue: %s", *issue.HTMLURL)
				break
			}
		}
	}

	return nil
}

func httpGetTravis(uri string) (*http.Response, error) {
	url := travisAPIBaseURL + uri
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("couldn't create request: %+v", err)
	}
	req.Header.Add("Accept", travisAPIContentType)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("couldn't send request to %s: %+v", url, err)
	}
	return resp, err
}

func buildIDFromTargetURL(targetURL string) (int64, error) {
	pieces := strings.Split(targetURL, "/")
	return strconv.ParseInt(pieces[len(pieces)-1], 10, 64)
}
<|endoftext|>


<commit_before>package bootstrap

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

	"github.com/buildkite/agent/bootstrap/shell"
	homedir "github.com/mitchellh/go-homedir"
	"github.com/pkg/errors"
	"golang.org/x/crypto/ssh/knownhosts"
)

type knownHosts struct {
	Shell *shell.Shell
	Path  string
}

func findKnownHosts(sh *shell.Shell) (*knownHosts, error) {
	userHomePath, err := homedir.Dir()
	if err != nil {
		return nil, fmt.Errorf("Could not find the current users home directory (%s)", err)
	}

	// Construct paths to the known_hosts file
	sshDirectory := filepath.Join(userHomePath, ".ssh")
	knownHostPath := filepath.Join(sshDirectory, "known_hosts")

	// Ensure ssh directory exists
	if err := os.MkdirAll(sshDirectory, 0700); err != nil {
		return nil, err
	}

	// Ensure file exists
	if _, err := os.Stat(knownHostPath); err != nil {
		f, err := os.OpenFile(knownHostPath, os.O_CREATE|os.O_WRONLY, 0600)
		if err != nil {
			return nil, errors.Wrapf(err, "Could not create %q", knownHostPath)
		}
		if err = f.Close(); err != nil {
			return nil, err
		}
	}

	return &knownHosts{Shell: sh, Path: knownHostPath}, nil
}

func (kh *knownHosts) Contains(host string) (bool, error) {
	file, err := os.Open(kh.Path)
	if err != nil {
		return false, err
	}
	defer file.Close()

	normalized := knownhosts.Normalize(host)

	// There don't appear to be any libraries to parse known_hosts that don't also want to
	// validate the IP's and host keys. Shelling out to ssh-keygen doesn't support custom ports
	// so I guess we'll do it ourselves.
	//
	// known_host format is:
	// github.com,192.30.253.x ssh-rsa AAAAB3NzaC1yc2E
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		fields := strings.Split(scanner.Text(), " ")
		if len(fields) != 3 {
			continue
		}
		for _, addr := range strings.Split(fields[0], ",") {
			if addr == normalized {
				return true, nil
			}
		}
	}

	return false, nil
}

func (kh *knownHosts) Add(host string) error {
	// Use a lockfile to prevent parallel processes stepping on each other
	lock, err := shell.LockFile(kh.Shell, kh.Path+".lock")
	if err != nil {
		return err
	}
	defer func() {
		if err := lock.Unlock(); err != nil {
			kh.Shell.Warningf("Failed to release known_hosts file lock: %#v", err)
		}
	}()

	// If the keygen output already contains the host, we can skip!
	if contains, _ := kh.Contains(host); contains {
		kh.Shell.Commentf("Host \"%s\" already in list of known hosts at \"%s\"", host, kh.Path)
		return nil
	}

	// Scan the key and then write it to the known_host file
	keyscanOutput, err := sshKeyScan(kh.Shell, host)
	if err != nil {
		return errors.Wrap(err, "Could not perform `ssh-keyscan`")
	}

	// Try and open the existing hostfile in (append_only) mode
	f, err := os.OpenFile(kh.Path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700)
	if err != nil {
		return errors.Wrapf(err, "Could not open %q for appending", kh.Path)
	}
	defer f.Close()

	if _, err = fmt.Fprintf(f, "%s\n", keyscanOutput); err != nil {
		return errors.Wrapf(err, "Could not write to %q", kh.Path)
	}

	return nil
}

// AddFromRepository takes a git repo url, extracts the host and adds it
func (kh *knownHosts) AddFromRepository(repository string) error {
	u, err := ParseGittableURL(repository)
	if err != nil {
		kh.Shell.Warningf("Could not parse \"%s\" as a URL - skipping adding host to SSH known_hosts", repository)
		return err
	}

	host := stripAliasesFromGitHost(u.Host)

	if err = kh.Add(host); err != nil {
		return errors.Wrapf(err, "Failed to add `%s` to known_hosts file `%s`", host, u)
	}

	return nil
}
<commit_msg>Longer story<commit_after>package bootstrap

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

	"github.com/buildkite/agent/bootstrap/shell"
	homedir "github.com/mitchellh/go-homedir"
	"github.com/pkg/errors"
	"golang.org/x/crypto/ssh/knownhosts"
)

type knownHosts struct {
	Shell *shell.Shell
	Path  string
}

func findKnownHosts(sh *shell.Shell) (*knownHosts, error) {
	userHomePath, err := homedir.Dir()
	if err != nil {
		return nil, fmt.Errorf("Could not find the current users home directory (%s)", err)
	}

	// Construct paths to the known_hosts file
	sshDirectory := filepath.Join(userHomePath, ".ssh")
	knownHostPath := filepath.Join(sshDirectory, "known_hosts")

	// Ensure ssh directory exists
	if err := os.MkdirAll(sshDirectory, 0700); err != nil {
		return nil, err
	}

	// Ensure file exists
	if _, err := os.Stat(knownHostPath); err != nil {
		f, err := os.OpenFile(knownHostPath, os.O_CREATE|os.O_WRONLY, 0600)
		if err != nil {
			return nil, errors.Wrapf(err, "Could not create %q", knownHostPath)
		}
		if err = f.Close(); err != nil {
			return nil, err
		}
	}

	return &knownHosts{Shell: sh, Path: knownHostPath}, nil
}

func (kh *knownHosts) Contains(host string) (bool, error) {
	file, err := os.Open(kh.Path)
	if err != nil {
		return false, err
	}
	defer file.Close()

	normalized := knownhosts.Normalize(host)

	// There don't appear to be any libraries to parse known_hosts that don't also want to
	// validate the IP's and host keys. Shelling out to ssh-keygen doesn't support custom ports
	// so I guess we'll do it ourselves.
	//
	// known_host format is defined at https://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT
	// A basic example is:
	// # Comments allowed at start of line
	// closenet,...,192.0.2.53 1024 37 159...93 closenet.example.net
	// cvs.example.net,192.0.2.10 ssh-rsa AAAA1234.....=
	// # A hashed hostname
	// |1|JfKTdBh7rNbXkVAQCRp4OQoPfmI=|USECr3SWf1JUPsms5AqfD5QfxkM= ssh-rsa
	// AAAA1234.....=
	// # A revoked key
	// @revoked * ssh-rsa AAAAB5W...
	// # A CA key, accepted for any host in *.mydomain.com or *.mydomain.org
	// @cert-authority *.mydomain.org,*.mydomain.com ssh-rsa AAAAB5W...
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		fields := strings.Split(scanner.Text(), " ")
		if len(fields) != 3 {
			continue
		}
		for _, addr := range strings.Split(fields[0], ",") {
			if addr == normalized {
				return true, nil
			}
		}
	}

	return false, nil
}

func (kh *knownHosts) Add(host string) error {
	// Use a lockfile to prevent parallel processes stepping on each other
	lock, err := shell.LockFile(kh.Shell, kh.Path+".lock")
	if err != nil {
		return err
	}
	defer func() {
		if err := lock.Unlock(); err != nil {
			kh.Shell.Warningf("Failed to release known_hosts file lock: %#v", err)
		}
	}()

	// If the keygen output already contains the host, we can skip!
	if contains, _ := kh.Contains(host); contains {
		kh.Shell.Commentf("Host \"%s\" already in list of known hosts at \"%s\"", host, kh.Path)
		return nil
	}

	// Scan the key and then write it to the known_host file
	keyscanOutput, err := sshKeyScan(kh.Shell, host)
	if err != nil {
		return errors.Wrap(err, "Could not perform `ssh-keyscan`")
	}

	// Try and open the existing hostfile in (append_only) mode
	f, err := os.OpenFile(kh.Path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700)
	if err != nil {
		return errors.Wrapf(err, "Could not open %q for appending", kh.Path)
	}
	defer f.Close()

	if _, err = fmt.Fprintf(f, "%s\n", keyscanOutput); err != nil {
		return errors.Wrapf(err, "Could not write to %q", kh.Path)
	}

	return nil
}

// AddFromRepository takes a git repo url, extracts the host and adds it
func (kh *knownHosts) AddFromRepository(repository string) error {
	u, err := ParseGittableURL(repository)
	if err != nil {
		kh.Shell.Warningf("Could not parse \"%s\" as a URL - skipping adding host to SSH known_hosts", repository)
		return err
	}

	host := stripAliasesFromGitHost(u.Host)

	if err = kh.Add(host); err != nil {
		return errors.Wrapf(err, "Failed to add `%s` to known_hosts file `%s`", host, u)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"os/signal"
	"fmt"
)

var p Parser
var email EmailNotifier
var lastProblems []Problem
var notifiers []Notifier
var apiClient APIClient

func main() {
	p.Parse("contacts.txt")
	notifiers = append(notifiers, &email)
	// Wait forever
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	//receive Problems through API
	currentProblems, err := apiClient.Receive("localhost:8080")
	fmt.Print(err)
	currentProblems = diffProblems(currentProblems)
	for i := 0; i < len(currentProblems); i++ {
		print(currentProblems[i].Description)
		notify(currentProblems[i])
	}
	<-c
	os.Exit(0)

}
func diffProblems(received []Problem) []Problem {
	for i := 0; i < len(received); i++ {
		for j := 0; j < len(lastProblems); j++ {
			if true {
				received = append(received[:i], received[i+1:]...)
			}
		}
	}
	return received
}

func notify(problem Problem) {
	for i := 0; i < len(notifiers); i++ {
		notifiers[i].SendProblem(problem)
	}
}
<commit_msg>UPD: periodic polling and correct exit<commit_after>package main

import (
	"os"
	"os/signal"
	"fmt"
	"time"
)

var p Parser
var email EmailNotifier
var lastProblems []Problem
var notifiers []Notifier
var apiClient APIClient

func main() {
	p.Parse("contacts.txt")
	notifiers = append(notifiers, &email)
	// Wait forever
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill)
	go func() {
		<-c
		os.Exit(0)
	}()
	for {
		//receive Problems through API
		currentProblems, err := apiClient.Receive("localhost:8080")
		fmt.Print(err)
		currentProblems = diffProblems(currentProblems)
		for i := 0; i < len(currentProblems); i++ {
			print(currentProblems[i].Description)
			notify(currentProblems[i])
		}
		time.Sleep(10* time.Second)
	}

}
func diffProblems(received []Problem) []Problem {
	for i := 0; i < len(received); i++ {
		for j := 0; j < len(lastProblems); j++ {
			if true {
				received = append(received[:i], received[i+1:]...)
			}
		}
	}
	return received
}

func notify(problem Problem) {
	for i := 0; i < len(notifiers); i++ {
		notifiers[i].SendProblem(problem)
	}
}
<|endoftext|>


<commit_before>// Package cache implements the data store behind Airlift server.
package cache

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"time"

	"golang.org/x/crypto/sha3"
)

const SHASize = 64

// Config represents the configurable behavior that Cache needs to operate.
type Config interface {
	MaxAge() int
	MaxSize() int64
	MaxCount() int
	Refresh()
	ProcessHash(buf []byte) string
}

// Cache is an extremely naïve, map-based, fully in-memory key-value store
// configured as a file cache. Only file locations are stored in memory.
// Persistence is achieved through the file system. It is concurrent-access
// safe through locking.
type Cache struct {
	// OnRemove is an arbitrary callback that is called whenever a file is
	// successfully removed.
	OnRemove func(id string)

	*sync.RWMutex
	size  int64                  // the total size of the files
	dir   string                 // path of directory where files are stored
	files map[string]os.FileInfo // map[id]filename
}

// New initializes and returns a new cache object rooted in dirPath.
func New(dirPath string) (*Cache, error) {
	c := &Cache{
		nil,
		new(sync.RWMutex),
		0,
		dirPath,
		make(map[string]os.FileInfo),
	}

	os.MkdirAll(dirPath, 0755)
	dir, err := os.Open(dirPath)
	if err != nil {
		return nil, err
	}
	fis, err := dir.Readdir(0)
	if err != nil {
		return nil, err
	}

	for _, fi := range fis {
		c.size += fi.Size()
		name := fi.Name()
		id := strings.Split(name, ".")[0]
		c.files[id] = fi
	}

	return c, nil
}

// Size returns the total number of bytes taken up by files in the cache.
func (c *Cache) Size() int64 {
	c.RLock()
	defer c.RUnlock()
	return c.size
}

func (c *Cache) filePath(id string) string {
	fi := c.files[id]
	if fi == nil {
		return ""
	}
	return filepath.Join(c.dir, fi.Name())
}

// GetFile returns the path to the file with the given ID in the cache.
func (c *Cache) Get(id string) string {
	c.RLock()
	defer c.RUnlock()
	return c.filePath(id)
}

// Stat returns the os.FileInfo associated with a file.
func (c *Cache) Stat(id string) os.FileInfo {
	c.RLock()
	defer c.RUnlock()
	return c.files[id]
}

// Put copies a file to disk with the given filename and returns its hash.
func (c *Cache) Put(content io.Reader, filename string, conf Config) (string, error) {
	os.MkdirAll(c.dir, 0700)
	dest := filepath.Join(c.dir, filename)
	destFile, err := os.OpenFile(dest, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
	if err != nil {
		os.Remove(dest)
		return "", err
	}

	defer destFile.Close()

	sha := sha3.NewShake256()
	w := io.MultiWriter(destFile, sha)
	n, err := io.Copy(w, content)
	if err != nil {
		os.Remove(dest)
		return "", err
	}
	buf := make([]byte, SHASize)
	sha.Read(buf)
	hash := conf.ProcessHash(buf)

	if f, exist := c.files[hash]; exist {
		log.Printf("overwriting existing file: %s (%d -> %d bytes)", f.Name(), f.Size(), n)
		os.Remove(c.filePath(hash))
	}

	destPath := filepath.Join(c.dir, hash+"."+filename)
	if err := os.Rename(dest, destPath); err != nil {
		os.Remove(dest)
		return "", err
	}

	fi, err := os.Stat(destPath)
	if err != nil {
		os.Remove(dest)
		return "", err
	}

	//conf := config.Get()
	if conf.MaxSize() > 0 {
		_, err = c.CutToSize(conf.MaxSize() * 1024 * 1024)
		if err != nil {
			os.Remove(dest)
			return "", err
		}
	}

	if conf.MaxCount() > 0 {
		_, err = c.CutToCount(conf.MaxCount() - 1)
		if err != nil {
			os.Remove(dest)
			return "", err
		}
	}

	c.Lock()
	c.files[hash] = fi
	c.size += fi.Size()
	c.Unlock()

	return hash, nil
}

func (c *Cache) removeFile(id string) error {
	size := c.files[id].Size()
	if err := os.Remove(c.filePath(id)); err != nil {
		return err
	}
	c.size -= size
	delete(c.files, id)
	if c.OnRemove != nil {
		c.OnRemove(id)
	}
	return nil
}

// Remove removes a file from the cache.
func (c *Cache) Remove(id string) error {
	c.Lock()
	defer c.Unlock()
	return c.removeFile(id)
}

// RemoveOlderThan removes all files in the cache that were modified before t,
// returning the IDs of the deleted files.  If an error is encountered while
// deleting a file, it will not advance any further.
func (c *Cache) RemoveOlderThan(t time.Time) ([]string, error) {
	c.Lock()
	defer c.Unlock()
	ids := []string{}
	for id, fi := range c.files {
		if fi.ModTime().Before(t) {
			if err := c.removeFile(id); err != nil {
				return nil, err
			}
		}
		ids = append(ids, id)
	}
	return ids, nil
}

// MaybeRemoveOlderThan returns the number of files that would be deleted if
// RemoveOlderThan were to be called.
func (c *Cache) MaybeRemoveOlderThan(t time.Time) int {
	c.RLock()
	defer c.RUnlock()
	ids := c.SortedIDs()
	for i, id := range ids {
		fi := c.files[id]
		if fi != nil && !fi.ModTime().Before(t) {
			return i
		}
	}
	return 0
}

// RemoveNewest removes the most recently modified item in the cache. It
// returns the ID of the file that was removed and an error if one was
// encountered.
func (c *Cache) RemoveNewest() (string, error) {
	if len(c.files) == 0 {
		return "", nil
	}
	c.RLock()
	var (
		newest   os.FileInfo
		newestID string
	)
	for id, fi := range c.files {
		if newest != nil && fi.ModTime().After(newest.ModTime()) {
			newest = fi
			newestID = id
		}
	}
	c.RUnlock()

	c.Lock()
	defer c.Unlock()
	return newestID, c.removeFile(newestID)
}

// CutToSize removes the oldest file in the cache until the total size is at
// most n bytes, returning the IDs of the deleted files. Nothing will happen if
// n == 0. To remove all files, use RemoveAll.
func (c *Cache) CutToSize(n int64) ([]string, error) {
	if n == 0 {
		return nil, nil
	}
	c.Lock()
	defer c.Unlock()
	ids := []string{}
	for c.size > n && len(c.files) > 0 {
		id, err := c.removeOldest()
		if err != nil {
			return nil, err
		}
		ids = append(ids, id)
	}
	return ids, nil
}

// MaybeCutToSize returns how many files would be pruned if CutToSize would
// have been called.
func (c *Cache) MaybeCutToSize(n int64) (m int) {
	c.RLock()
	defer c.RUnlock()
	target := c.size - n
	s := int64(0)
	ids := c.SortedIDs()
	for i := 0; i < len(ids) && s <= target; i++ {
		m++
		s += c.files[ids[i]].Size()
	}
	return
}

// CutCoCount removes the oldest file until the number of files in the cache is
// less than or equal to n.
func (c *Cache) CutToCount(n int) (m int, err error) {
	c.Lock()
	defer c.Unlock()

	for len(c.files) > n {
		_, err = c.removeOldest()
		if err != nil {
			return
		}
		m++
	}

	return
}

// removeOldest removes the oldest file in the cache.
func (c *Cache) removeOldest() (string, error) {
	if len(c.files) == 0 {
		return "", nil
	}
	var (
		oldest   os.FileInfo
		oldestID string
	)
	for id, fi := range c.files {
		if oldest == nil || fi.ModTime().Before(oldest.ModTime()) {
			oldest = fi
			oldestID = id
		}
	}
	return oldestID, c.removeFile(oldestID)
}

// RemoveAll removes every file in the cache.
func (c *Cache) RemoveAll() error {
	c.Lock()
	defer c.Unlock()
	for id := range c.files {
		if err := c.removeFile(id); err != nil {
			return err
		}
	}
	return nil
}

// WatchAges starts a blocking server that constantly watches for files to
// become stale, and then deletes them. It should be run in its own goroutine.
func (c *Cache) WatchAges(conf Config) {
	for {
		//conf := config.Get()
		conf.Refresh()
		before := time.Now()
		if conf.MaxAge() > 0 {
			cutoff := before.Add(-time.Duration(conf.MaxAge()) * 24 * time.Hour)
			if _, err := c.RemoveOlderThan(cutoff); err != nil {
				log.Print(err)
			}
		}
		after := time.Now()
		// execute next on the nearest day
		time.Sleep(before.AddDate(0, 0, 1).Truncate(24 * time.Hour).Sub(after))
	}
}

// Len returns the number of files in the cache.
func (c *Cache) Len() int {
	c.RLock()
	defer c.RUnlock()
	return len(c.files)
}

type byModtime struct {
	ids []string
	fis []os.FileInfo
}

func (s byModtime) Len() int { return len(s.ids) }
func (s byModtime) Less(i, j int) bool {
	a := s.fis[i].ModTime()
	b := s.fis[j].ModTime()
	return a.Before(b)
}
func (s byModtime) Swap(i, j int) {
	s.ids[i], s.ids[j] = s.ids[j], s.ids[i]
	s.fis[i], s.fis[j] = s.fis[j], s.fis[i]
}

// SortedIDs returns a slice of every cached file's ID, sorted descending by modification
// time.
func (c *Cache) SortedIDs() []string {
	c.RLock()
	defer c.RUnlock()
	ids := make([]string, 0, len(c.files))
	fis := make([]os.FileInfo, 0, len(c.files))
	for id := range c.files {
		ids = append(ids, id)
		fis = append(fis, c.files[id])
	}
	s := byModtime{ids, fis}

	sort.Sort(s)
	return s.ids
}

// SetDir sets the base directory where files will be stored on disk.
func (c *Cache) SetDir(dir string) {
	c.Lock()
	c.dir = dir
	c.Unlock()
}
<commit_msg>cache: retry hash until it does not collide (resolve #8)<commit_after>// Package cache implements the data store behind Airlift server.
package cache

import (
	"io"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"sync"
	"time"

	"golang.org/x/crypto/sha3"
)

const SHASize = 64

// Config represents the configurable behavior that Cache needs to operate.
type Config interface {
	MaxAge() int
	MaxSize() int64
	MaxCount() int
	Refresh()
	ProcessHash(buf []byte) string
}

// Cache is an extremely naïve, map-based, fully in-memory key-value store
// configured as a file cache. Only file locations are stored in memory.
// Persistence is achieved through the file system. It is concurrent-access
// safe through locking.
type Cache struct {
	// OnRemove is an arbitrary callback that is called whenever a file is
	// successfully removed.
	OnRemove func(id string)

	*sync.RWMutex
	size  int64                  // the total size of the files
	dir   string                 // path of directory where files are stored
	files map[string]os.FileInfo // map[id]filename
}

// New initializes and returns a new cache object rooted in dirPath.
func New(dirPath string) (*Cache, error) {
	c := &Cache{
		nil,
		new(sync.RWMutex),
		0,
		dirPath,
		make(map[string]os.FileInfo),
	}

	os.MkdirAll(dirPath, 0755)
	dir, err := os.Open(dirPath)
	if err != nil {
		return nil, err
	}
	fis, err := dir.Readdir(0)
	if err != nil {
		return nil, err
	}

	for _, fi := range fis {
		c.size += fi.Size()
		name := fi.Name()
		id := strings.Split(name, ".")[0]
		c.files[id] = fi
	}

	return c, nil
}

// Size returns the total number of bytes taken up by files in the cache.
func (c *Cache) Size() int64 {
	c.RLock()
	defer c.RUnlock()
	return c.size
}

func (c *Cache) filePath(id string) string {
	fi := c.files[id]
	if fi == nil {
		return ""
	}
	return filepath.Join(c.dir, fi.Name())
}

// GetFile returns the path to the file with the given ID in the cache.
func (c *Cache) Get(id string) string {
	c.RLock()
	defer c.RUnlock()
	return c.filePath(id)
}

// Stat returns the os.FileInfo associated with a file.
func (c *Cache) Stat(id string) os.FileInfo {
	c.RLock()
	defer c.RUnlock()
	return c.files[id]
}

// Put copies a file to disk with the given filename and returns its hash.
func (c *Cache) Put(content io.Reader, filename string, conf Config) (string, error) {
	os.MkdirAll(c.dir, 0700)
	dest := filepath.Join(c.dir, filename)
	destFile, err := os.OpenFile(dest, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
	if err != nil {
		os.Remove(dest)
		return "", err
	}

	defer destFile.Close()

	sha := sha3.NewShake256()
	w := io.MultiWriter(destFile, sha)
	_, err = io.Copy(w, content)
	if err != nil {
		os.Remove(dest)
		return "", err
	}

	var (
		buf  = make([]byte, SHASize)
		hash string
	)

	for {
		sha.Read(buf)
		hash = conf.ProcessHash(buf)

		if _, exist := c.files[hash]; exist {
			log.Printf("cache: collision detected with ID '%s' - regenerating", hash)
		} else {
			break
		}
	}

	destPath := filepath.Join(c.dir, hash+"."+filename)
	if err := os.Rename(dest, destPath); err != nil {
		os.Remove(dest)
		return "", err
	}

	fi, err := os.Stat(destPath)
	if err != nil {
		os.Remove(dest)
		return "", err
	}

	//conf := config.Get()
	if conf.MaxSize() > 0 {
		_, err = c.CutToSize(conf.MaxSize() * 1024 * 1024)
		if err != nil {
			os.Remove(dest)
			return "", err
		}
	}

	if conf.MaxCount() > 0 {
		_, err = c.CutToCount(conf.MaxCount() - 1)
		if err != nil {
			os.Remove(dest)
			return "", err
		}
	}

	c.Lock()
	c.files[hash] = fi
	c.size += fi.Size()
	c.Unlock()

	return hash, nil
}

func (c *Cache) removeFile(id string) error {
	size := c.files[id].Size()
	if err := os.Remove(c.filePath(id)); err != nil {
		return err
	}
	c.size -= size
	delete(c.files, id)
	if c.OnRemove != nil {
		c.OnRemove(id)
	}
	return nil
}

// Remove removes a file from the cache.
func (c *Cache) Remove(id string) error {
	c.Lock()
	defer c.Unlock()
	return c.removeFile(id)
}

// RemoveOlderThan removes all files in the cache that were modified before t,
// returning the IDs of the deleted files.  If an error is encountered while
// deleting a file, it will not advance any further.
func (c *Cache) RemoveOlderThan(t time.Time) ([]string, error) {
	c.Lock()
	defer c.Unlock()
	ids := []string{}
	for id, fi := range c.files {
		if fi.ModTime().Before(t) {
			if err := c.removeFile(id); err != nil {
				return nil, err
			}
		}
		ids = append(ids, id)
	}
	return ids, nil
}

// MaybeRemoveOlderThan returns the number of files that would be deleted if
// RemoveOlderThan were to be called.
func (c *Cache) MaybeRemoveOlderThan(t time.Time) int {
	c.RLock()
	defer c.RUnlock()
	ids := c.SortedIDs()
	for i, id := range ids {
		fi := c.files[id]
		if fi != nil && !fi.ModTime().Before(t) {
			return i
		}
	}
	return 0
}

// RemoveNewest removes the most recently modified item in the cache. It
// returns the ID of the file that was removed and an error if one was
// encountered.
func (c *Cache) RemoveNewest() (string, error) {
	if len(c.files) == 0 {
		return "", nil
	}
	c.RLock()
	var (
		newest   os.FileInfo
		newestID string
	)
	for id, fi := range c.files {
		if newest != nil && fi.ModTime().After(newest.ModTime()) {
			newest = fi
			newestID = id
		}
	}
	c.RUnlock()

	c.Lock()
	defer c.Unlock()
	return newestID, c.removeFile(newestID)
}

// CutToSize removes the oldest file in the cache until the total size is at
// most n bytes, returning the IDs of the deleted files. Nothing will happen if
// n == 0. To remove all files, use RemoveAll.
func (c *Cache) CutToSize(n int64) ([]string, error) {
	if n == 0 {
		return nil, nil
	}
	c.Lock()
	defer c.Unlock()
	ids := []string{}
	for c.size > n && len(c.files) > 0 {
		id, err := c.removeOldest()
		if err != nil {
			return nil, err
		}
		ids = append(ids, id)
	}
	return ids, nil
}

// MaybeCutToSize returns how many files would be pruned if CutToSize would
// have been called.
func (c *Cache) MaybeCutToSize(n int64) (m int) {
	c.RLock()
	defer c.RUnlock()
	target := c.size - n
	s := int64(0)
	ids := c.SortedIDs()
	for i := 0; i < len(ids) && s <= target; i++ {
		m++
		s += c.files[ids[i]].Size()
	}
	return
}

// CutCoCount removes the oldest file until the number of files in the cache is
// less than or equal to n.
func (c *Cache) CutToCount(n int) (m int, err error) {
	c.Lock()
	defer c.Unlock()

	for len(c.files) > n {
		_, err = c.removeOldest()
		if err != nil {
			return
		}
		m++
	}

	return
}

// removeOldest removes the oldest file in the cache.
func (c *Cache) removeOldest() (string, error) {
	if len(c.files) == 0 {
		return "", nil
	}
	var (
		oldest   os.FileInfo
		oldestID string
	)
	for id, fi := range c.files {
		if oldest == nil || fi.ModTime().Before(oldest.ModTime()) {
			oldest = fi
			oldestID = id
		}
	}
	return oldestID, c.removeFile(oldestID)
}

// RemoveAll removes every file in the cache.
func (c *Cache) RemoveAll() error {
	c.Lock()
	defer c.Unlock()
	for id := range c.files {
		if err := c.removeFile(id); err != nil {
			return err
		}
	}
	return nil
}

// WatchAges starts a blocking server that constantly watches for files to
// become stale, and then deletes them. It should be run in its own goroutine.
func (c *Cache) WatchAges(conf Config) {
	for {
		//conf := config.Get()
		conf.Refresh()
		before := time.Now()
		if conf.MaxAge() > 0 {
			cutoff := before.Add(-time.Duration(conf.MaxAge()) * 24 * time.Hour)
			if _, err := c.RemoveOlderThan(cutoff); err != nil {
				log.Print(err)
			}
		}
		after := time.Now()
		// execute next on the nearest day
		time.Sleep(before.AddDate(0, 0, 1).Truncate(24 * time.Hour).Sub(after))
	}
}

// Len returns the number of files in the cache.
func (c *Cache) Len() int {
	c.RLock()
	defer c.RUnlock()
	return len(c.files)
}

type byModtime struct {
	ids []string
	fis []os.FileInfo
}

func (s byModtime) Len() int { return len(s.ids) }
func (s byModtime) Less(i, j int) bool {
	a := s.fis[i].ModTime()
	b := s.fis[j].ModTime()
	return a.Before(b)
}
func (s byModtime) Swap(i, j int) {
	s.ids[i], s.ids[j] = s.ids[j], s.ids[i]
	s.fis[i], s.fis[j] = s.fis[j], s.fis[i]
}

// SortedIDs returns a slice of every cached file's ID, sorted descending by modification
// time.
func (c *Cache) SortedIDs() []string {
	c.RLock()
	defer c.RUnlock()
	ids := make([]string, 0, len(c.files))
	fis := make([]os.FileInfo, 0, len(c.files))
	for id := range c.files {
		ids = append(ids, id)
		fis = append(fis, c.files[id])
	}
	s := byModtime{ids, fis}

	sort.Sort(s)
	return s.ids
}

// SetDir sets the base directory where files will be stored on disk.
func (c *Cache) SetDir(dir string) {
	c.Lock()
	c.dir = dir
	c.Unlock()
}
<|endoftext|>


<commit_before>// Package cache provides an interface for interfacing with the Glide local cache
package cache

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/Masterminds/glide/msg"
	gpath "github.com/Masterminds/glide/path"
)

// Enabled sets if the cache is globally enabled. Defaults to true.
var Enabled = true

// ErrCacheDisabled is returned with the cache is disabled.
var ErrCacheDisabled = errors.New("Cache disabled")

var isSetup bool

var setupMutex sync.Mutex

// Setup creates the cache location.
func Setup() error {
	setupMutex.Lock()
	defer setupMutex.Unlock()

	if isSetup {
		return nil
	}
	msg.Debug("Setting up the cache directory")
	pths := []string{
		"cache",
		filepath.Join("cache", "src"),
		filepath.Join("cache", "info"),
	}

	for _, l := range pths {
		err := os.MkdirAll(filepath.Join(gpath.Home(), l), 0755)
		if err != nil {
			return err
		}
	}

	isSetup = true
	return nil
}

// SetupReset resets if setup has been completed. The next time setup is run
// it will attempt a full setup.
func SetupReset() {
	isSetup = false
}

// Location returns the location of the cache.
func Location() (string, error) {
	p := filepath.Join(gpath.Home(), "cache")
	err := Setup()

	return p, err
}

// scpSyntaxRe matches the SCP-like addresses used to access repos over SSH.
var scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)

// Key generates a cache key based on a url or scp string. The key is file
// system safe.
func Key(repo string) (string, error) {

	var u *url.URL
	var err error
	var strip bool
	if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil {
		// Match SCP-like syntax and convert it to a URL.
		// Eg, "git@github.com:user/repo" becomes
		// "ssh://git@github.com/user/repo".
		u = &url.URL{
			Scheme: "ssh",
			User:   url.User(m[1]),
			Host:   m[2],
			Path:   "/" + m[3],
		}
		strip = true
	} else {
		u, err = url.Parse(repo)
		if err != nil {
			return "", err
		}
	}

	if strip {
		u.Scheme = ""
	}

	var key string
	if u.Scheme != "" {
		key = u.Scheme + "-"
	}
	if u.User != nil && u.User.Username() != "" {
		key = key + u.User.Username() + "-"
	}
	key = key + u.Host
	if u.Path != "" {
		key = key + strings.Replace(u.Path, "/", "-", -1)
	}

	key = strings.Replace(key, ":", "-", -1)

	return key, nil
}

// RepoInfo holds information about a repo.
type RepoInfo struct {
	DefaultBranch string `json:"default-branch"`
	LastUpdate    string `json:"last-update"`
}

// SaveRepoData stores data about a repo in the Glide cache
func SaveRepoData(key string, data RepoInfo) error {
	if !Enabled {
		return ErrCacheDisabled
	}
	location, err := Location()
	if err != nil {
		return err
	}
	data.LastUpdate = time.Now().String()
	d, err := json.Marshal(data)
	if err != nil {
		return err
	}

	pp := filepath.Join(location, "info")
	err = os.MkdirAll(pp, 0755)
	if err != nil {
		return err
	}

	p := filepath.Join(pp, key+".json")
	f, err := os.Create(p)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = f.Write(d)
	return err
}

// RepoData retrieves cached information about a repo.
func RepoData(key string) (*RepoInfo, error) {
	if !Enabled {
		return &RepoInfo{}, ErrCacheDisabled
	}
	location, err := Location()
	if err != nil {
		return &RepoInfo{}, err
	}
	c := &RepoInfo{}
	p := filepath.Join(location, "info", key+".json")
	f, err := ioutil.ReadFile(p)
	if err != nil {
		return &RepoInfo{}, err
	}
	err = json.Unmarshal(f, c)
	if err != nil {
		return &RepoInfo{}, err
	}
	return c, nil
}

var lockSync sync.Mutex

var lockData = make(map[string]*sync.Mutex)

// Lock locks a particular key name
func Lock(name string) {
	lockSync.Lock()
	m, ok := lockData[name]
	if !ok {
		m = &sync.Mutex{}
		lockData[name] = m
	}
	lockSync.Unlock()
	msg.Debug("Locking %s", name)
	m.Lock()
}

// Unlock unlocks a particular key name
func Unlock(name string) {
	msg.Debug("Unlocking %s", name)
	lockSync.Lock()
	if m, ok := lockData[name]; ok {
		m.Unlock()
	}

	lockSync.Unlock()
}
<commit_msg>Added some base package docs for cache<commit_after>// Package cache provides an interface for interfacing with the Glide local cache
//
// Glide has a local cache of metadata and repositories similar to the GOPATH.
// To store the cache Glide creates a .glide directory with a cache subdirectory.
// This is usually in the users home directory unless there is no accessible
// home directory in which case the .glide directory is in the root of the
// repository.
//
// To get the cache location use the `cache.Location()` function. This will
// return the proper base location in your environment.
//
// Within the cache directory there are two subdirectories. They are the src
// and info directories. The src directory contains version control checkouts
// of the packages. The info direcory contains metadata. The metadata maps to
// the RepoInfo struct. Both stores are happed to keys.
//
// Using the `cache.Key()` function you can get a key for a repo. Pass in a
// location such as `https://github.com/foo/bar` or `git@example.com:foo.git`
// and a key will be returned that can be used for caching operations.
//
// Note, the caching is based on repo rather than package. This is important
// for a couple reasons.
//
// 1. Forks or package replacements are supported in Glide. Where a different
//    repo maps to a package.
// 2. Permissions enable different access. For example `https://example.com/foo.git`
//    and `git@example.com:foo.git` may have access to different branches or tags.
package cache

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/Masterminds/glide/msg"
	gpath "github.com/Masterminds/glide/path"
)

// Enabled sets if the cache is globally enabled. Defaults to true.
var Enabled = true

// ErrCacheDisabled is returned with the cache is disabled.
var ErrCacheDisabled = errors.New("Cache disabled")

var isSetup bool

var setupMutex sync.Mutex

// Setup creates the cache location.
func Setup() error {
	setupMutex.Lock()
	defer setupMutex.Unlock()

	if isSetup {
		return nil
	}
	msg.Debug("Setting up the cache directory")
	pths := []string{
		"cache",
		filepath.Join("cache", "src"),
		filepath.Join("cache", "info"),
	}

	for _, l := range pths {
		err := os.MkdirAll(filepath.Join(gpath.Home(), l), 0755)
		if err != nil {
			return err
		}
	}

	isSetup = true
	return nil
}

// SetupReset resets if setup has been completed. The next time setup is run
// it will attempt a full setup.
func SetupReset() {
	isSetup = false
}

// Location returns the location of the cache.
func Location() (string, error) {
	p := filepath.Join(gpath.Home(), "cache")
	err := Setup()

	return p, err
}

// scpSyntaxRe matches the SCP-like addresses used to access repos over SSH.
var scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)

// Key generates a cache key based on a url or scp string. The key is file
// system safe.
func Key(repo string) (string, error) {

	var u *url.URL
	var err error
	var strip bool
	if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil {
		// Match SCP-like syntax and convert it to a URL.
		// Eg, "git@github.com:user/repo" becomes
		// "ssh://git@github.com/user/repo".
		u = &url.URL{
			Scheme: "ssh",
			User:   url.User(m[1]),
			Host:   m[2],
			Path:   "/" + m[3],
		}
		strip = true
	} else {
		u, err = url.Parse(repo)
		if err != nil {
			return "", err
		}
	}

	if strip {
		u.Scheme = ""
	}

	var key string
	if u.Scheme != "" {
		key = u.Scheme + "-"
	}
	if u.User != nil && u.User.Username() != "" {
		key = key + u.User.Username() + "-"
	}
	key = key + u.Host
	if u.Path != "" {
		key = key + strings.Replace(u.Path, "/", "-", -1)
	}

	key = strings.Replace(key, ":", "-", -1)

	return key, nil
}

// RepoInfo holds information about a repo.
type RepoInfo struct {
	DefaultBranch string `json:"default-branch"`
	LastUpdate    string `json:"last-update"`
}

// SaveRepoData stores data about a repo in the Glide cache
func SaveRepoData(key string, data RepoInfo) error {
	if !Enabled {
		return ErrCacheDisabled
	}
	location, err := Location()
	if err != nil {
		return err
	}
	data.LastUpdate = time.Now().String()
	d, err := json.Marshal(data)
	if err != nil {
		return err
	}

	pp := filepath.Join(location, "info")
	err = os.MkdirAll(pp, 0755)
	if err != nil {
		return err
	}

	p := filepath.Join(pp, key+".json")
	f, err := os.Create(p)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = f.Write(d)
	return err
}

// RepoData retrieves cached information about a repo.
func RepoData(key string) (*RepoInfo, error) {
	if !Enabled {
		return &RepoInfo{}, ErrCacheDisabled
	}
	location, err := Location()
	if err != nil {
		return &RepoInfo{}, err
	}
	c := &RepoInfo{}
	p := filepath.Join(location, "info", key+".json")
	f, err := ioutil.ReadFile(p)
	if err != nil {
		return &RepoInfo{}, err
	}
	err = json.Unmarshal(f, c)
	if err != nil {
		return &RepoInfo{}, err
	}
	return c, nil
}

var lockSync sync.Mutex

var lockData = make(map[string]*sync.Mutex)

// Lock locks a particular key name
func Lock(name string) {
	lockSync.Lock()
	m, ok := lockData[name]
	if !ok {
		m = &sync.Mutex{}
		lockData[name] = m
	}
	lockSync.Unlock()
	msg.Debug("Locking %s", name)
	m.Lock()
}

// Unlock unlocks a particular key name
func Unlock(name string) {
	msg.Debug("Unlocking %s", name)
	lockSync.Lock()
	if m, ok := lockData[name]; ok {
		m.Unlock()
	}

	lockSync.Unlock()
}
<|endoftext|>


<commit_before>package main

import (
	// "container/list"
	"curses"
	// "fmt"
	// "math"
	"os"
	"strings"
)

const (
	NilLine  = "nil line"
	CacheMax = 5
)

type EditBuffer struct {
	fi       *os.FileInfo
	Name     string
	Pathname string
	Lines    []*EditLine
	Line     int
	Column   int
	dirty    bool

	tabs     bool
	tabwidth int
	tabstop  int

	// Stuff for painting
	Anchor     int
	Window     *Window
	X, Y       int
	CurX, CurY int
}

func NewEditBuffer(gs *GlobalState, name string) *EditBuffer {
	eb := new(EditBuffer)
	eb.Pathname = name
	eb.Lines = make([]*EditLine, 0, 100)
	eb.Line = 0
	eb.Column = 0
	eb.dirty = true

	eb.Anchor = eb.Line
	eb.Window = gs.Window
	// eb.ScreenMap = make([]string, eb.Window.Rows-1)
	eb.CurX, eb.CurY = 0, 0
	eb.X, eb.Y = eb.Window.Cols, eb.Window.Rows-1

	return eb
}

func (eb *EditBuffer) GetWindow() *Window {
	return eb.Window
}

func (eb *EditBuffer) SetWindow(w *Window) {
	eb.Window = w
	eb.X, eb.Y = w.Cols, w.Rows
}

func (eb *EditBuffer) SendInput(k int) {
	gs := eb.Window.gs
	switch gs.Mode {
	case INSERT:
		switch k {
		case curses.KEY_BACKSPACE, 127:
			eb.Backspace()
		case 0xd, 0xa:
			eb.NewLine(byte('\n'))
		case ESC:
			eb.MoveLeft()
		default:
			eb.InsertChar(byte(k))
		}
		eb.dirty = true
	case NORMAL:
		switch k {
		case 'j':
			eb.MoveLeft()
		case 'k':
			eb.MoveDown()
		case 'l':
			eb.MoveUp()
		case ';':
			eb.MoveRight()
		case 'p':
			eb.PasteBelow()
		case 'P':
			eb.PasteAbove()
		case 'i':
			// Insert
		case 'a':
			// Append
			eb.MoveRight()
		}
		// XXX Until I fix mapping, mark the whole buffer as dirty on movement
		eb.dirty = true
	case COMMAND: // XXX How did you get here?
	}
}

func (eb *EditBuffer) RunRoutine(fn func(Interacter)) {
	go fn(eb)
}

func (eb *EditBuffer) GetMap() *[]string {
	if eb.dirty {
		eb.MapToScreen()
		eb.dirty = false
	}
	return eb.Window.ScreenMap
}

func (eb *EditBuffer) SetDimensions(x, y int) {
	eb.X, eb.Y = x, y
}

func (eb *EditBuffer) GetCursor() (int, int) {
	return eb.CurX, eb.CurY
}

func (eb *EditBuffer) InsertChar(c byte) {
	eb.Lines[eb.Line].InsertChar(c)
}

func (eb *EditBuffer) MapToScreen() {
	var i int
	smap := *eb.Window.ScreenMap
	for _, e := range eb.Lines[eb.Anchor:] {
		if i >= eb.Y {
			break
		}
		// XXX: screen Lines code for wrap
		row := make([]byte, eb.X)
		// panic(fmt.Sprintf("len of e.raw is %d", len(e.raw())))
		for j, _ := range row {
			row[j] = ' '
		}
		copy(row, e.GetRaw())
		rs := string(row)
		// XXX this is all sorts of wrong, but need to fix line mapping before fixing
		// this
		t := strings.Count(rs, "\t")
		s := strings.Replace(rs, "\t", "        ", -1)
		s = strings.Replace(s, "\n", "", -1)
		smap[i] = s
		if i == eb.Line {
			eb.CurY = i
			eb.CurX = e.b.gs + (t * 7)
		}
		i++
	}
	for i < eb.Y {
		smap[i] = NaL
		i++
	}
}

func (eb *EditBuffer) GoToLine(lno int) {
	if lno < 1 {
		return
	}

	if lno > len(eb.Lines) {
		eb.Line = len(eb.Lines)
	}
	eb.Line = lno - 1
}

func (eb *EditBuffer) Backspace() {
	l := eb.Lines[eb.Line]
	if l.Cursor() == 0 {
		if eb.Line > 0 {
			sav := eb.Lines[eb.Line]
			eb.DeleteLine()
			eb.Lines[eb.Line].Delete(1)
			// There *should* be a newline character to delete from prev
			if sav != nil {
			}
		} else {
			Beep()
		}
	} else {
		l.Delete(1)
	}
}

func (eb *EditBuffer) InsertLine(e *EditLine) *EditLine {
	if len(eb.Lines) > 0 {
		eb.Line += 1
	}
	eb.Lines = append(eb.Lines[:eb.Line], append([]*EditLine{e}, eb.Lines[eb.Line:]...)...)
	return eb.Lines[eb.Line]
}

func (eb *EditBuffer) AppendEmptyLine() *EditLine {
	return eb.InsertLine(NewEditLine([]byte("")))
}

func (eb *EditBuffer) DeleteLine() {
	eb.Lines = append(eb.Lines[:eb.Line], eb.Lines[eb.Line+1:]...)
	if eb.Line > 0 {
		eb.Line -= 1
	}
}

func (eb *EditBuffer) NewLine(d byte) {
	l := eb.Lines[eb.Line]
	l.InsertChar(d)
	newLine := l.AfterCursor()
	l.ClearToEOL()
	eb.InsertLine(NewEditLine(newLine))
}

func (eb *EditBuffer) Top() {
	eb.Line = 0
	eb.Anchor = eb.Line
}

// TODO If the column is the length of a line, set b.Column to -1 so that moving
// vertically will put the cursor at the end of the new line.
func (eb *EditBuffer) MoveHorizontal(dir int) {
	if l := eb.Lines[eb.Line]; !l.MoveCursor(l.Cursor() + dir) {
		Beep()
	} else {
		eb.Column = l.Cursor()
	}
}

func (eb *EditBuffer) MoveLeft() {
	eb.MoveHorizontal(-1)
}

func (eb *EditBuffer) MoveRight() {
	eb.MoveHorizontal(1)
}

func (b *EditBuffer) MoveUp() {
	if b.Line > 0 {
		b.Line -= 1
		if l := b.Lines[b.Line]; len(l.GetRaw()) > b.Column {
			l.MoveCursor(b.Column)
		}
	} else {
		Beep()
	}
}

func (b *EditBuffer) MoveDown() {
	if b.Line < len(b.Lines)-1 {
		b.Line += 1
		if l := b.Lines[b.Line]; len(l.GetRaw()) > b.Column {
			l.MoveCursor(b.Column)
		}
	} else {
		Beep()
	}
}

func (eb *EditBuffer) PasteAbove() {
}

func (eb *EditBuffer) PasteBelow() {
}
<commit_msg>fix gotoline<commit_after>package main

import (
	// "container/list"
	"curses"
	// "fmt"
	// "math"
	"os"
	"strings"
)

const (
	NilLine  = "nil line"
	CacheMax = 5
)

type EditBuffer struct {
	fi       *os.FileInfo
	Name     string
	Pathname string
	Lines    []*EditLine
	Line     int
	Column   int
	dirty    bool

	tabs     bool
	tabwidth int
	tabstop  int

	// Stuff for painting
	Anchor     int
	Window     *Window
	X, Y       int
	CurX, CurY int
}

func NewEditBuffer(gs *GlobalState, name string) *EditBuffer {
	eb := new(EditBuffer)
	eb.Pathname = name
	eb.Lines = make([]*EditLine, 0, 100)
	eb.Line = 0
	eb.Column = 0
	eb.dirty = true

	eb.Anchor = eb.Line
	eb.Window = gs.Window
	// eb.ScreenMap = make([]string, eb.Window.Rows-1)
	eb.CurX, eb.CurY = 0, 0
	eb.X, eb.Y = eb.Window.Cols, eb.Window.Rows-1

	return eb
}

func (eb *EditBuffer) GetWindow() *Window {
	return eb.Window
}

func (eb *EditBuffer) SetWindow(w *Window) {
	eb.Window = w
	eb.X, eb.Y = w.Cols, w.Rows
}

func (eb *EditBuffer) SendInput(k int) {
	gs := eb.Window.gs
	switch gs.Mode {
	case INSERT:
		switch k {
		case curses.KEY_BACKSPACE, 127:
			eb.Backspace()
		case 0xd, 0xa:
			eb.NewLine(byte('\n'))
		case ESC:
			eb.MoveLeft()
		default:
			eb.InsertChar(byte(k))
		}
		eb.dirty = true
	case NORMAL:
		switch k {
		case 'j':
			eb.MoveLeft()
		case 'k':
			eb.MoveDown()
		case 'l':
			eb.MoveUp()
		case ';':
			eb.MoveRight()
		case 'p':
			eb.PasteBelow()
		case 'P':
			eb.PasteAbove()
		case 'i':
			// Insert
		case 'a':
			// Append
			eb.MoveRight()
		}
		// XXX Until I fix mapping, mark the whole buffer as dirty on movement
		eb.dirty = true
	case COMMAND: // XXX How did you get here?
	}
}

func (eb *EditBuffer) RunRoutine(fn func(Interacter)) {
	go fn(eb)
}

func (eb *EditBuffer) GetMap() *[]string {
	if eb.dirty {
		eb.MapToScreen()
		eb.dirty = false
	}
	return eb.Window.ScreenMap
}

func (eb *EditBuffer) SetDimensions(x, y int) {
	eb.X, eb.Y = x, y
}

func (eb *EditBuffer) GetCursor() (int, int) {
	return eb.CurX, eb.CurY
}

func (eb *EditBuffer) InsertChar(c byte) {
	eb.Lines[eb.Line].InsertChar(c)
}

func (eb *EditBuffer) MapToScreen() {
	var i int
	smap := *eb.Window.ScreenMap
	for _, e := range eb.Lines[eb.Anchor:] {
		if i >= eb.Y {
			break
		}
		// XXX: screen Lines code for wrap
		row := make([]byte, eb.X)
		// panic(fmt.Sprintf("len of e.raw is %d", len(e.raw())))
		for j, _ := range row {
			row[j] = ' '
		}
		copy(row, e.GetRaw())
		rs := string(row)
		// XXX this is all sorts of wrong, but need to fix line mapping before fixing
		// this
		t := strings.Count(rs, "\t")
		s := strings.Replace(rs, "\t", "        ", -1)
		s = strings.Replace(s, "\n", "", -1)
		smap[i] = s
		if i == eb.Line {
			eb.CurY = i
			eb.CurX = e.b.gs + (t * 7)
		}
		i++
	}
	for i < eb.Y {
		smap[i] = NaL
		i++
	}
}

func (eb *EditBuffer) GoToLine(lno int) {
	if lno < 1 {
		return
	}

	if lno > len(eb.Lines) {
		eb.Line = len(eb.Lines)
	} else {
		eb.Line = lno - 1
	}
}

func (eb *EditBuffer) Backspace() {
	l := eb.Lines[eb.Line]
	if l.Cursor() == 0 {
		if eb.Line > 0 {
			sav := eb.Lines[eb.Line]
			eb.DeleteLine()
			eb.Lines[eb.Line].Delete(1)
			// There *should* be a newline character to delete from prev
			if sav != nil {
			}
		} else {
			Beep()
		}
	} else {
		l.Delete(1)
	}
}

func (eb *EditBuffer) InsertLine(e *EditLine) *EditLine {
	if len(eb.Lines) > 0 {
		eb.Line += 1
	}
	eb.Lines = append(eb.Lines[:eb.Line], append([]*EditLine{e}, eb.Lines[eb.Line:]...)...)
	return eb.Lines[eb.Line]
}

func (eb *EditBuffer) AppendEmptyLine() *EditLine {
	return eb.InsertLine(NewEditLine([]byte("")))
}

func (eb *EditBuffer) DeleteLine() {
	eb.Lines = append(eb.Lines[:eb.Line], eb.Lines[eb.Line+1:]...)
	if eb.Line > 0 {
		eb.Line -= 1
	}
}

func (eb *EditBuffer) NewLine(d byte) {
	l := eb.Lines[eb.Line]
	l.InsertChar(d)
	newLine := l.AfterCursor()
	l.ClearToEOL()
	eb.InsertLine(NewEditLine(newLine))
}

func (eb *EditBuffer) Top() {
	eb.Line = 0
	eb.Anchor = eb.Line
}

// TODO If the column is the length of a line, set b.Column to -1 so that moving
// vertically will put the cursor at the end of the new line.
func (eb *EditBuffer) MoveHorizontal(dir int) {
	if l := eb.Lines[eb.Line]; !l.MoveCursor(l.Cursor() + dir) {
		Beep()
	} else {
		eb.Column = l.Cursor()
	}
}

func (eb *EditBuffer) MoveLeft() {
	eb.MoveHorizontal(-1)
}

func (eb *EditBuffer) MoveRight() {
	eb.MoveHorizontal(1)
}

func (b *EditBuffer) MoveUp() {
	if b.Line > 0 {
		b.Line -= 1
		if l := b.Lines[b.Line]; len(l.GetRaw()) > b.Column {
			l.MoveCursor(b.Column)
		}
	} else {
		Beep()
	}
}

func (b *EditBuffer) MoveDown() {
	if b.Line < len(b.Lines)-1 {
		b.Line += 1
		if l := b.Lines[b.Line]; len(l.GetRaw()) > b.Column {
			l.MoveCursor(b.Column)
		}
	} else {
		Beep()
	}
}

func (eb *EditBuffer) PasteAbove() {
}

func (eb *EditBuffer) PasteBelow() {
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	// "container/list"
	"curses"
	"fmt"
	// "math"
	"os"
	"strings"
)

const (
	NilLine  = "nil line"
	CacheMax = 5
)

type EditBuffer struct {
	fi       *os.FileInfo
	Name     string
	Pathname string
	Lines    []*EditLine
	Line     int
	Column   int
	dirty    bool // This should become an int, so that updates are just after a given line

	tabs     bool
	tabwidth int
	tabstop  int

	cmdbuff *GapBuffer

	// Stuff for painting
	Anchor     int
	Window     *Window
	X, Y       int
	CurX, CurY int
}

func NewEditBuffer(gs *GlobalState, name string) *EditBuffer {
	eb := new(EditBuffer)
	eb.Pathname = name
	eb.Lines = make([]*EditLine, 0)
	eb.Line = 0
	eb.Column = 0
	eb.dirty = true

	eb.cmdbuff = NewGapBuffer([]byte(""))

	eb.Anchor = eb.Line
	eb.Window = gs.Window
	// eb.ScreenMap = make([]string, eb.Window.Rows-1)
	eb.CurX, eb.CurY = 0, 0
	eb.X, eb.Y = eb.Window.Cols, eb.Window.Rows-1

	return eb
}

func (eb *EditBuffer) GetWindow() *Window {
	return eb.Window
}

func (eb *EditBuffer) SetWindow(w *Window) {
	eb.Window = w
	eb.X, eb.Y = w.Cols, w.Rows
}

func (eb *EditBuffer) SendInput(k int) {
	gs := eb.Window.gs
	switch gs.Mode {
	case INSERT:
		switch k {
		case curses.KEY_BACKSPACE, 127:
			eb.Backspace()
		case 0xd, 0xa:
			eb.NewLine(byte('\n'))
		case ESC:
			eb.MoveLeft()
		default:
			eb.InsertChar(byte(k))
		}
		eb.dirty = true
	case NORMAL:
		/* XXX
		if eb.cmdbuff.String() != "" {
			eb.cmdbuff.InsertChar(byte(k))
			eb.EvalCmdBuff()
			return
		}
		*/

		b := true
		switch k {
		case 'j':
			b = eb.MoveLeft()
		case 'k':
			b = eb.MoveDown()
		case 'l':
			b = eb.MoveUp()
		case ';':
			b = eb.MoveRight()
		case 'p':
			eb.PasteBelow()
		case 'P':
			eb.PasteAbove()
		case 'i':
			// Insert
		case 'a':
			// Append
			eb.MoveRight()
		case 'o':
			// Add a line and go to insert mode
			eb.AppendEmptyLine()
			eb.MoveDown()
		case 'd':
			eb.DeleteLine(eb.Line)
		case 'G':
			eb.LastLine()
		}
		if !b {
			Beep()
		}
		// XXX Until I fix mapping, mark the whole buffer as dirty on movement
		eb.dirty = true
	case COMMAND: // XXX How did you get here?
	}
}

func (eb *EditBuffer) RunRoutine(fn func(Interacter)) {
	go fn(eb)
}

func (eb *EditBuffer) GetMap() *[]string {
	if eb.dirty {
		eb.MapToScreen()
		eb.dirty = false
	}
	return eb.Window.ScreenMap
}

func (eb *EditBuffer) SetDimensions(x, y int) {
	eb.X, eb.Y = x, y
}

func (eb *EditBuffer) GetCursor() (int, int) {
	return eb.CurX, eb.CurY
}

func (eb *EditBuffer) InsertChar(c byte) {
	eb.Lines[eb.Line].InsertChar(c)
}

func (eb *EditBuffer) MapToScreen() {
	var i int
	smap := *eb.Window.ScreenMap
	for _, e := range eb.Lines[eb.Anchor:] {
		if i >= eb.Y {
			break
		}
		// XXX: screen Lines code for wrap
		row := make([]byte, eb.X)
		// panic(fmt.Sprintf("len of e.raw is %d", len(e.raw())))
		for j, _ := range row {
			row[j] = ' '
		}
		copy(row, e.GetRaw())
		rs := string(row)
		// XXX this is all sorts of wrong, but need to fix line mapping before fixing
		// this
		t := strings.Count(rs, "\t")
		s := strings.Replace(rs, "\t", "        ", -1)
		s = strings.Replace(s, "\n", "", -1)
		smap[i] = s
		if i == eb.Line {
			eb.CurY = i
			eb.CurX = e.b.gs + (t * 7)
		}
		i++
	}
	for i < eb.Y {
		smap[i] = NaL
		i++
	}
}

func (eb *EditBuffer) MapLine(el *EditLine) {
	w := eb.X
	o := 0 // XXX offset.  0 until I add line number support
	if w < 0 || o != 0 { return }
}

func (eb *EditBuffer) GoToLine(lno int) {
	if lno < 1 {
		return
	}

	if lno > len(eb.Lines) {
		eb.Line = len(eb.Lines)
	} else {
		eb.Line = lno - 1
	}
}

func (eb *EditBuffer) Backspace() {
	if l := eb.Lines[eb.Line]; l.Cursor() == 0 {
		if eb.Line > 0 {
			sav := eb.DeleteLine(eb.Line)
			eb.Lines[eb.Line].Delete(1)
			if sav != nil {
			}
		} else {
			Beep()
		}
	} else {
		l.Delete(1)
	}
}

// Insert a line at lno, 0-n, into an EditBuffer.
func (eb *EditBuffer) InsertLine(e *EditLine, lno int) {
	if lno < 0 || lno > len(eb.Lines) {
		panic(fmt.Sprintf("Unable to insert line at %d in buffer of %d lines", lno,
			len(eb.Lines)))
	}

	eb.Lines = append(eb.Lines[:lno], append([]*EditLine{e}, eb.Lines[lno:]...)...)
}

func (eb *EditBuffer) AppendEmptyLine() {
	eb.InsertLine(NewEditLine([]byte("")), eb.Line+1)
}

// Delete a line at lno, 0-n, from an EditBuffer
func (eb *EditBuffer) DeleteLine(lno int) *EditLine {
	// If we are removing the 0th line from a file with a single line,
	// after the line is removed, a new one needs to be inserted
	if len(eb.Lines) == 0 {
		// This is an error case, we're going to panic here because it
		// really should not happen
		panic("Trying to delete line 0 in a buffer with no lines")
	}

	// The line that's going away
	ln := eb.Lines[lno]

	eb.Lines = append(eb.Lines[:lno], eb.Lines[lno+1:]...)
	if len(eb.Lines) == 0 {
		eb.InsertLine(NewEditLine([]byte("")), 0)
		eb.Line = 0
		// vim would set "--no lines in buffer--" in this case
	} else if eb.Line > 0 {
		// Move up one line
		eb.Line -= 1
	}
	return ln
}

func (eb *EditBuffer) NewLine(d byte) {
	l := eb.Lines[eb.Line]
	l.InsertChar(d)
	newLine := NewEditLine(l.AfterCursor())
	l.ClearToEOL()
	eb.InsertLine(newLine, eb.Line+1)
	eb.MoveDown()
}

func (eb *EditBuffer) TopLine() {
	eb.Line = 0
	eb.Anchor = eb.Line
}

func (eb *EditBuffer) LastLine() {
	eb.Line = len(eb.Lines) - 1
	// XXX Move anchor
}

// TODO If the column is the length of a line, set b.Column to -1 so that moving
// vertically will put the cursor at the end of the new line.
func (eb *EditBuffer) MoveHorizontal(dir int) bool {
	if l := eb.Lines[eb.Line]; l.MoveCursor(l.Cursor() + dir) {
		eb.Column = l.Cursor()
		return true
	}
	return false
}

func (eb *EditBuffer) MoveLeft() bool {
	return eb.MoveHorizontal(-1)
}

func (eb *EditBuffer) MoveRight() bool {
	return eb.MoveHorizontal(1)
}

func (eb *EditBuffer) MoveVertical(dir int) bool {
	lidx := eb.Line + dir
	if lidx < 0 || lidx > len(eb.Lines)-1 {
		return false
	}

	eb.Line = lidx
	if l := eb.Lines[eb.Line]; len(l.GetRaw()) > eb.Column {
		l.MoveCursor(eb.Column)
	}
	return true
}

func (eb *EditBuffer) MoveUp() bool {
	return eb.MoveVertical(-1)
}

func (eb *EditBuffer) MoveDown() bool {
	return eb.MoveVertical(1)
}

func (eb *EditBuffer) PasteAbove() {
}

func (eb *EditBuffer) PasteBelow() {
}

func (eb *EditBuffer) EvalCmdBuff() {
}

// Reads a file at pathname into EditBuffer eb
// Returns the number of lines read or error
func (eb *EditBuffer) readFile(pathname string, mark int) (int, os.Error) {
	f, err := os.Open(pathname, os.O_RDONLY, 0666)
	if err != nil {
		return -1, err
	}
	defer f.Close()

	rdr := bufio.NewReader(f)
	// XXX fix this loop
	lno := mark
	for {
		if ln, err := rdr.ReadBytes('\n'); err == nil {
			eb.InsertLine(NewEditLine(ln), lno)
		} else {
			if err != os.EOF {
				return -1, err
			} else {
				eb.InsertLine(NewEditLine(ln), lno)
				return lno - mark, nil
			}
		}
		lno++
	}

	return lno - mark, nil
}
<commit_msg>remame<commit_after>package main

import (
	"bufio"
	// "container/list"
	"curses"
	"fmt"
	// "math"
	"os"
	"strings"
)

const (
	NilLine  = "nil line"
	CacheMax = 5
)

type EditBuffer struct {
	fi       *os.FileInfo
	Name     string
	Pathname string
	Lines    []*EditLine
	lno     int
	Column   int
	dirty    bool // This should become an int, so that updates are just after a given line

	tabs     bool
	tabwidth int
	tabstop  int

	cmdbuff *GapBuffer

	// Stuff for painting
	Anchor     int
	Window     *Window
	X, Y       int
	CurX, CurY int
}

func NewEditBuffer(gs *GlobalState, name string) *EditBuffer {
	eb := new(EditBuffer)
	eb.Pathname = name
	eb.Lines = make([]*EditLine, 0)
	eb.lno = 0
	eb.Column = 0
	eb.dirty = true

	eb.cmdbuff = NewGapBuffer([]byte(""))

	eb.Anchor = eb.lno
	eb.Window = gs.Window
	// eb.ScreenMap = make([]string, eb.Window.Rows-1)
	eb.CurX, eb.CurY = 0, 0
	eb.X, eb.Y = eb.Window.Cols, eb.Window.Rows-1

	return eb
}

func (eb *EditBuffer) GetWindow() *Window {
	return eb.Window
}

func (eb *EditBuffer) SetWindow(w *Window) {
	eb.Window = w
	eb.X, eb.Y = w.Cols, w.Rows
}

func (eb *EditBuffer) SendInput(k int) {
	gs := eb.Window.gs
	switch gs.Mode {
	case INSERT:
		switch k {
		case curses.KEY_BACKSPACE, 127:
			eb.Backspace()
		case 0xd, 0xa:
			eb.NewLine(byte('\n'))
		case ESC:
			eb.MoveLeft()
		default:
			eb.InsertChar(byte(k))
		}
		eb.dirty = true
	case NORMAL:
		/* XXX
		if eb.cmdbuff.String() != "" {
			eb.cmdbuff.InsertChar(byte(k))
			eb.EvalCmdBuff()
			return
		}
		*/

		b := true
		switch k {
		case 'j':
			b = eb.MoveLeft()
		case 'k':
			b = eb.MoveDown()
		case 'l':
			b = eb.MoveUp()
		case ';':
			b = eb.MoveRight()
		case 'p':
			eb.PasteBelow()
		case 'P':
			eb.PasteAbove()
		case 'i':
			// Insert
		case 'a':
			// Append
			eb.MoveRight()
		case 'o':
			// Add a line and go to insert mode
			eb.AppendEmptyLine()
			eb.MoveDown()
		case 'd':
			eb.DeleteLine(eb.lno)
		case 'G':
			eb.LastLine()
		}
		if !b {
			Beep()
		}
		// XXX Until I fix mapping, mark the whole buffer as dirty on movement
		eb.dirty = true
	case COMMAND: // XXX How did you get here?
	}
}

func (eb *EditBuffer) RunRoutine(fn func(Interacter)) {
	go fn(eb)
}

func (eb *EditBuffer) GetMap() *[]string {
	if eb.dirty {
		eb.MapToScreen()
		eb.dirty = false
	}
	return eb.Window.ScreenMap
}

func (eb *EditBuffer) SetDimensions(x, y int) {
	eb.X, eb.Y = x, y
}

func (eb *EditBuffer) GetCursor() (int, int) {
	return eb.CurX, eb.CurY
}

func (eb *EditBuffer) InsertChar(c byte) {
	eb.Lines[eb.lno].InsertChar(c)
}

func (eb *EditBuffer) MapToScreen() {
	var i int
	smap := *eb.Window.ScreenMap
	for _, e := range eb.Lines[eb.Anchor:] {
		if i >= eb.Y {
			break
		}
		// XXX: screen Lines code for wrap
		row := make([]byte, eb.X)
		// panic(fmt.Sprintf("len of e.raw is %d", len(e.raw())))
		for j, _ := range row {
			row[j] = ' '
		}
		copy(row, e.GetRaw())
		rs := string(row)
		// XXX this is all sorts of wrong, but need to fix line mapping before fixing
		// this
		t := strings.Count(rs, "\t")
		s := strings.Replace(rs, "\t", "        ", -1)
		s = strings.Replace(s, "\n", "", -1)
		smap[i] = s
		if i == eb.lno {
			eb.CurY = i
			eb.CurX = e.b.gs + (t * 7)
		}
		i++
	}
	for i < eb.Y {
		smap[i] = NaL
		i++
	}
}

func (eb *EditBuffer) MapLine(el *EditLine) {
	w := eb.X
	o := 0 // XXX offset.  0 until I add line number support
	if w < 0 || o != 0 { return }
}

func (eb *EditBuffer) GoToLine(lno int) {
	if lno < 1 {
		return
	}

	if lno > len(eb.Lines) {
		eb.lno = len(eb.Lines)
	} else {
		eb.lno = lno - 1
	}
}

func (eb *EditBuffer) Backspace() {
	if l := eb.Lines[eb.lno]; l.Cursor() == 0 {
		if eb.lno > 0 {
			sav := eb.DeleteLine(eb.lno)
			eb.Lines[eb.lno].Delete(1)
			if sav != nil {
			}
		} else {
			Beep()
		}
	} else {
		l.Delete(1)
	}
}

// Insert a line at lno, 0-n, into an EditBuffer.
func (eb *EditBuffer) InsertLine(e *EditLine, lno int) {
	if lno < 0 || lno > len(eb.Lines) {
		panic(fmt.Sprintf("Unable to insert line at %d in buffer of %d lines", lno,
			len(eb.Lines)))
	}

	eb.Lines = append(eb.Lines[:lno], append([]*EditLine{e}, eb.Lines[lno:]...)...)
}

func (eb *EditBuffer) AppendEmptyLine() {
	eb.InsertLine(NewEditLine([]byte("")), eb.lno+1)
}

// Delete a line at lno, 0-n, from an EditBuffer
func (eb *EditBuffer) DeleteLine(lno int) *EditLine {
	// If we are removing the 0th line from a file with a single line,
	// after the line is removed, a new one needs to be inserted
	if len(eb.Lines) == 0 {
		// This is an error case, we're going to panic here because it
		// really should not happen
		panic("Trying to delete line 0 in a buffer with no lines")
	}

	// The line that's going away
	ln := eb.Lines[lno]

	eb.Lines = append(eb.Lines[:lno], eb.Lines[lno+1:]...)
	if len(eb.Lines) == 0 {
		eb.InsertLine(NewEditLine([]byte("")), 0)
		eb.lno = 0
		// vim would set "--no lines in buffer--" in this case
	} else if eb.lno > 0 {
		// Move up one line
		eb.lno -= 1
	}
	return ln
}

func (eb *EditBuffer) NewLine(d byte) {
	l := eb.Lines[eb.lno]
	l.InsertChar(d)
	newLine := NewEditLine(l.AfterCursor())
	l.ClearToEOL()
	eb.InsertLine(newLine, eb.lno+1)
	eb.MoveDown()
}

func (eb *EditBuffer) TopLine() {
	eb.lno = 0
	eb.Anchor = eb.lno
}

func (eb *EditBuffer) LastLine() {
	eb.lno = len(eb.Lines) - 1
	// XXX Move anchor
}

// TODO If the column is the length of a line, set b.Column to -1 so that moving
// vertically will put the cursor at the end of the new line.
func (eb *EditBuffer) MoveHorizontal(dir int) bool {
	if l := eb.Lines[eb.lno]; l.MoveCursor(l.Cursor() + dir) {
		eb.Column = l.Cursor()
		return true
	}
	return false
}

func (eb *EditBuffer) MoveLeft() bool {
	return eb.MoveHorizontal(-1)
}

func (eb *EditBuffer) MoveRight() bool {
	return eb.MoveHorizontal(1)
}

func (eb *EditBuffer) MoveVertical(dir int) bool {
	lno := eb.lno + dir
	if lno < 0 || lno > len(eb.Lines)-1 {
		return false
	}

	eb.lno = lno
	if l := eb.Lines[eb.lno]; len(l.GetRaw()) > eb.Column {
		l.MoveCursor(eb.Column)
	}
	return true
}

func (eb *EditBuffer) MoveUp() bool {
	return eb.MoveVertical(-1)
}

func (eb *EditBuffer) MoveDown() bool {
	return eb.MoveVertical(1)
}

func (eb *EditBuffer) PasteAbove() {
}

func (eb *EditBuffer) PasteBelow() {
}

func (eb *EditBuffer) EvalCmdBuff() {
}

// Reads a file at pathname into EditBuffer eb
// Returns the number of lines read or error
func (eb *EditBuffer) readFile(pathname string, mark int) (int, os.Error) {
	f, err := os.Open(pathname, os.O_RDONLY, 0666)
	if err != nil {
		return -1, err
	}
	defer f.Close()

	rdr := bufio.NewReader(f)
	// XXX fix this loop
	lno := mark
	for {
		if ln, err := rdr.ReadBytes('\n'); err == nil {
			eb.InsertLine(NewEditLine(ln), lno)
		} else {
			if err != os.EOF {
				return -1, err
			} else {
				eb.InsertLine(NewEditLine(ln), lno)
				return lno - mark, nil
			}
		}
		lno++
	}

	return lno - mark, nil
}
<|endoftext|>


<commit_before>package caddy

import (
	"net/http"

	"github.com/mholt/caddy"
	"github.com/mholt/caddy/caddyhttp/httpserver"

	"github.com/txtdirect/txtdirect"
)

func init() {
	caddy.RegisterPlugin("txtdirect", caddy.Plugin{
		ServerType: "http",
		Action:     setup,
	})
}

var allOptions = []string{"host", "gometa"}

func setup(c *caddy.Controller) error {
	var enable []string
	c.Next() // skip directive name
	for c.NextBlock() {
		option := c.Val()
		switch option {
		case "enable":
			if enable != nil {
				return c.ArgErr()
			}
			enable = c.RemainingArgs()
		case "disable":
			if enable != nil {
				return c.ArgErr()
			}
			enable = removeArrayFromArray(enable, c.RemainingArgs())
		default:
			return c.ArgErr() // unhandled option
		}
	}

	// If nothing is specified, enable everything
	if enable == nil {
		enable = allOptions
	}

	// Add handler to Caddy
	cfg := httpserver.GetConfig(c)
	mid := func(next httpserver.Handler) httpserver.Handler {
		return Redirect{
			Next:   next,
			Enable: enable,
		}
	}
	cfg.AddMiddleware(mid)
	return nil
}

func removeArrayFromArray(array, toBeRemoved []string) []string {
	for _, toRemove := range toBeRemoved {
		for i, option := range array {
			if option == toRemove {
				array[i] = array[len(array)-1]
				array = array[:len(array)-1]
				break
			}
		}
	}
	return array
}

// Redirect is middleware to redirect requests based on TXT records
type Redirect struct {
	Next   httpserver.Handler
	Enable []string
}

func (rd Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	if err := txtdirect.Redirect(w, r, rd.Enable); err != nil {
		return http.StatusInternalServerError, err
	}
	return 0, nil
}
<commit_msg>Use allOptions in disabled case<commit_after>package caddy

import (
	"net/http"

	"github.com/mholt/caddy"
	"github.com/mholt/caddy/caddyhttp/httpserver"

	"github.com/txtdirect/txtdirect"
)

func init() {
	caddy.RegisterPlugin("txtdirect", caddy.Plugin{
		ServerType: "http",
		Action:     setup,
	})
}

var allOptions = []string{"host", "gometa"}

func setup(c *caddy.Controller) error {
	var enable []string
	c.Next() // skip directive name
	for c.NextBlock() {
		option := c.Val()
		switch option {
		case "enable":
			if enable != nil {
				return c.ArgErr()
			}
			enable = c.RemainingArgs()
		case "disable":
			if enable != nil {
				return c.ArgErr()
			}
			enable = removeArrayFromArray(allOptions, c.RemainingArgs())
		default:
			return c.ArgErr() // unhandled option
		}
	}

	// If nothing is specified, enable everything
	if enable == nil {
		enable = allOptions
	}

	// Add handler to Caddy
	cfg := httpserver.GetConfig(c)
	mid := func(next httpserver.Handler) httpserver.Handler {
		return Redirect{
			Next:   next,
			Enable: enable,
		}
	}
	cfg.AddMiddleware(mid)
	return nil
}

func removeArrayFromArray(array, toBeRemoved []string) []string {
	for _, toRemove := range toBeRemoved {
		for i, option := range array {
			if option == toRemove {
				array[i] = array[len(array)-1]
				array = array[:len(array)-1]
				break
			}
		}
	}
	return array
}

// Redirect is middleware to redirect requests based on TXT records
type Redirect struct {
	Next   httpserver.Handler
	Enable []string
}

func (rd Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	if err := txtdirect.Redirect(w, r, rd.Enable); err != nil {
		return http.StatusInternalServerError, err
	}
	return 0, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/nyaxt/otaru/btncrypt"
	"github.com/nyaxt/otaru/facade"
	oflags "github.com/nyaxt/otaru/flags"
	"github.com/nyaxt/otaru/gcloud/auth"
	"github.com/nyaxt/otaru/gcloud/datastore"
	"github.com/nyaxt/otaru/gcloud/gcs"
)

var (
	flagConfigDir = flag.String("configDir", facade.DefaultConfigDir(), "Config dirpath")
)

func Usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "  %s [options]\n", os.Args[0])
	flag.PrintDefaults()
}

func clearBucket(projectName, bucketName string, clisrc auth.ClientSource) error {
	bs, err := gcs.NewGCSBlobStore(projectName, bucketName, clisrc, oflags.O_RDWRCREATE)
	if err != nil {
		return fmt.Errorf("Failed to init GCSBlobStore: %v", err)
	}

	bps, err := bs.ListBlobs()
	if err != nil {
		return fmt.Errorf("Failed to ListBlobs(): %v", err)
	}
	log.Printf("Found %d blobs in bucket \"%s\"", len(bps), bucketName)

	for i, bp := range bps {
		log.Printf("Removing blob %d/%d", i+1, len(bps))
		if err := bs.RemoveBlob(bp); err != nil {
			return fmt.Errorf("Failed to RemoveBlob(%s): %v", bp, err)
		}
	}
	return nil
}

func main() {
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)

	flag.Usage = Usage
	flag.Parse()
	if flag.NArg() != 0 {
		Usage()
		os.Exit(1)
	}

	cfg, err := facade.NewConfig(*flagConfigDir)
	if err != nil {
		log.Printf("%v", err)
		Usage()
		os.Exit(1)
	}

	clisrc, err := auth.GetGCloudClientSource(cfg.CredentialsFilePath, cfg.TokenCacheFilePath, false)
	if err != nil {
		log.Fatalf("Failed to init GCloudClientSource: %v", err)
	}
	key := btncrypt.KeyFromPassword(cfg.Password)
	c, err := btncrypt.NewCipher(key)
	if err != nil {
		log.Fatalf("Failed to init btncrypt.Cipher: %v", err)
	}

	fmt.Printf("Do you really want to proceed with deleting all blobs in gs://%s{,-meta} and its cache in %s?\n", cfg.BucketName, cfg.CacheDir)
	fmt.Printf("Type \"deleteall\" to proceed: ")
	sc := bufio.NewScanner(os.Stdin)
	if !sc.Scan() {
		return
	}
	if sc.Text() != "deleteall" {
		log.Printf("Cancelled.\n")
		os.Exit(1)
	}

	dscfg := datastore.NewConfig(cfg.ProjectName, cfg.BucketName, c, clisrc)
	l := datastore.NewGlobalLocker(dscfg, "otaru-deleteallblobs", facade.GenHostName())
	if err := l.Lock(); err != nil {
		log.Printf("Failed to acquire global lock: %v", err)
		return
	}
	defer l.Unlock()

	if err := clearBucket(cfg.ProjectName, cfg.BucketName, clisrc); err != nil {
		log.Printf("Failed to clear bucket \"%s\": %v", cfg.BucketName, err)
		return
	}
	if cfg.UseSeparateBucketForMetadata {
		metabucketname := fmt.Sprintf("%s-meta", cfg.BucketName)
		if err := clearBucket(cfg.ProjectName, metabucketname, clisrc); err != nil {
			log.Printf("Failed to clear metadata bucket \"%s\": %v", metabucketname, err)
			return
		}
	}
	log.Printf("otaru-deleteallblobs: Successfully completed!")
	log.Printf("Hint: You might also want to run \"otaru-txlogio purge\" to delete inodedb txlogs.")
}
<commit_msg>actually flush cache<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"

	"github.com/nyaxt/otaru/blobstore"
	"github.com/nyaxt/otaru/btncrypt"
	"github.com/nyaxt/otaru/facade"
	oflags "github.com/nyaxt/otaru/flags"
	"github.com/nyaxt/otaru/gcloud/auth"
	"github.com/nyaxt/otaru/gcloud/datastore"
	"github.com/nyaxt/otaru/gcloud/gcs"
)

var (
	flagConfigDir = flag.String("configDir", facade.DefaultConfigDir(), "Config dirpath")
)

func Usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "  %s [options]\n", os.Args[0])
	flag.PrintDefaults()
}

type BlobListerRemover interface {
	blobstore.BlobLister
	blobstore.BlobRemover
}

func clearBlobStore(bs BlobListerRemover) error {
	bps, err := bs.ListBlobs()
	if err != nil {
		return fmt.Errorf("Failed to ListBlobs(): %v", err)
	}
	log.Printf("Found %d blobs!", len(bps))

	for i, bp := range bps {
		log.Printf("Removing blob %d/%d: %s", i+1, len(bps), bp)
		if err := bs.RemoveBlob(bp); err != nil {
			return fmt.Errorf("Failed to RemoveBlob(%s): %v", bp, err)
		}
	}
	return nil
}

func clearCache(cacheDir string) error {
	bs, err := blobstore.NewFileBlobStore(cacheDir, oflags.O_RDWRCREATE)
	if err != nil {
		return fmt.Errorf("Failed to init FileBlobStore: %v", err)
	}

	return clearBlobStore(bs)
}

func clearGCS(projectName, bucketName string, clisrc auth.ClientSource) error {
	bs, err := gcs.NewGCSBlobStore(projectName, bucketName, clisrc, oflags.O_RDWRCREATE)
	if err != nil {
		return fmt.Errorf("Failed to init GCSBlobStore: %v", err)
	}

	return clearBlobStore(bs)
}

func main() {
	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)

	flag.Usage = Usage
	flag.Parse()
	if flag.NArg() != 0 {
		Usage()
		os.Exit(1)
	}

	cfg, err := facade.NewConfig(*flagConfigDir)
	if err != nil {
		log.Printf("%v", err)
		Usage()
		os.Exit(1)
	}

	clisrc, err := auth.GetGCloudClientSource(cfg.CredentialsFilePath, cfg.TokenCacheFilePath, false)
	if err != nil {
		log.Fatalf("Failed to init GCloudClientSource: %v", err)
	}
	key := btncrypt.KeyFromPassword(cfg.Password)
	c, err := btncrypt.NewCipher(key)
	if err != nil {
		log.Fatalf("Failed to init btncrypt.Cipher: %v", err)
	}

	fmt.Printf("Do you really want to proceed with deleting all blobs in gs://%s{,-meta} and its cache in %s?\n", cfg.BucketName, cfg.CacheDir)
	fmt.Printf("Type \"deleteall\" to proceed: ")
	sc := bufio.NewScanner(os.Stdin)
	if !sc.Scan() {
		return
	}
	if sc.Text() != "deleteall" {
		log.Printf("Cancelled.\n")
		os.Exit(1)
	}

	dscfg := datastore.NewConfig(cfg.ProjectName, cfg.BucketName, c, clisrc)
	l := datastore.NewGlobalLocker(dscfg, "otaru-deleteallblobs", facade.GenHostName())
	if err := l.Lock(); err != nil {
		log.Printf("Failed to acquire global lock: %v", err)
		return
	}
	defer l.Unlock()

	if err := clearGCS(cfg.ProjectName, cfg.BucketName, clisrc); err != nil {
		log.Printf("Failed to clear bucket \"%s\": %v", cfg.BucketName, err)
		return
	}
	if cfg.UseSeparateBucketForMetadata {
		metabucketname := fmt.Sprintf("%s-meta", cfg.BucketName)
		if err := clearGCS(cfg.ProjectName, metabucketname, clisrc); err != nil {
			log.Printf("Failed to clear metadata bucket \"%s\": %v", metabucketname, err)
			return
		}
	}
	if err := clearCache(cfg.CacheDir); err != nil {
		log.Printf("Failed to clear cache \"%s\": %v", cfg.CacheDir, err)
		return
	}

	log.Printf("otaru-deleteallblobs: Successfully completed!")
	log.Printf("Hint: You might also want to run \"otaru-txlogio purge\" to delete inodedb txlogs.")
}
<|endoftext|>


<commit_before>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package env

import (
	"fmt"
	"net"
	"os"
	"path/filepath"
	"sync"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/go-framed-msgpack-rpc/rpc"
)

const (
	kbfsSocketFile = "kbfsd.sock"
)

// Context defines the environment for this package
type Context interface {
	GetRunMode() libkb.RunMode
	GetLogDir() string
	GetDataDir() string
	ConfigureSocketInfo() (err error)
	GetGlobalContext() *libkb.GlobalContext
	GetSocket(clearError bool) (net.Conn, rpc.Transporter, bool, error)
	NewRPCLogFactory() rpc.LogFactory
	GetKBFSSocket(clearError bool) (net.Conn, rpc.Transporter, bool, error)
	BindToKBFSSocket() (net.Listener, error)
}

// KBFSContext is an implementation for libkbfs.Context
type KBFSContext struct {
	g *libkb.GlobalContext
	// protects the socket primitives below
	kbfsSocketMtx     sync.RWMutex
	kbfsSocket        libkb.Socket
	kbfsSocketWrapper *libkb.SocketWrapper
}

var _ Context = (*KBFSContext)(nil)

func (c *KBFSContext) initKBFSSocket() {
	c.kbfsSocketMtx.Lock()
	defer c.kbfsSocketMtx.Unlock()
	log := c.g.Log
	bindFile := c.getKBFSSocketFile()
	dialFiles := []string{bindFile}
	c.kbfsSocket = libkb.NewSocketWithFiles(log, bindFile, dialFiles)
}

// NewContextFromGlobalContext constructs a context
func NewContextFromGlobalContext(g *libkb.GlobalContext) *KBFSContext {
	c := &KBFSContext{g: g}
	c.initKBFSSocket()
	return c
}

// NewContext constructs a context. This should only be called once in
// main functions.
func NewContext() *KBFSContext {
	g := libkb.NewGlobalContextInit()
	g.ConfigureConfig()
	g.ConfigureLogging()
	g.ConfigureCaches()
	g.ConfigureMerkleClient()
	return NewContextFromGlobalContext(g)
}

// GetLogDir returns log dir
func (c *KBFSContext) GetLogDir() string {
	return c.g.Env.GetLogDir()
}

// GetDataDir returns log dir
func (c *KBFSContext) GetDataDir() string {
	return c.g.Env.GetDataDir()
}

// GetRunMode returns run mode
func (c *KBFSContext) GetRunMode() libkb.RunMode {
	return c.g.GetRunMode()
}

// GetGlobalContext returns the libkb global context.
func (c *KBFSContext) GetGlobalContext() *libkb.GlobalContext {
	return c.g
}

// GetSocket returns a socket
func (c *KBFSContext) GetSocket(clearError bool) (
	net.Conn, rpc.Transporter, bool, error) {
	return c.g.GetSocket(clearError)
}

// ConfigureSocketInfo configures a socket
func (c *KBFSContext) ConfigureSocketInfo() error {
	return c.g.ConfigureSocketInfo()
}

// NewRPCLogFactory constructs an RPC logger
func (c *KBFSContext) NewRPCLogFactory() rpc.LogFactory {
	return &libkb.RPCLogFactory{Contextified: libkb.NewContextified(c.g)}
}

func (c *KBFSContext) getSandboxSocketFile() string {
	sandboxDir := c.g.Env.HomeFinder.SandboxCacheDir()
	if sandboxDir == "" {
		return ""
	}
	return filepath.Join(sandboxDir, kbfsSocketFile)
}

func (c *KBFSContext) getKBFSSocketFile() string {
	e := c.g.Env
	return e.GetString(
		func() string { return c.getSandboxSocketFile() },
		// TODO: maybe add command-line option here
		func() string { return os.Getenv("KBFS_SOCKET_FILE") },
		func() string { return filepath.Join(e.GetRuntimeDir(), kbfsSocketFile) },
	)
}

func (c *KBFSContext) newTransportFromSocket(s net.Conn) rpc.Transporter {
	return rpc.NewTransport(s, c.NewRPCLogFactory(), libkb.WrapError)
}

// GetKBFSSocket dials the socket configured in `c.kbfsSocket`.
// Adapted from github.com/keybase/client/go/libkb.GlobalContext.GetSocket.
func (c *KBFSContext) GetKBFSSocket(clearError bool) (
	net.Conn, rpc.Transporter, bool, error) {
	var err error
	c.g.Trace("GetSocket", func() error { return err })()

	// Protect all global socket wrapper manipulation with a
	// lock to prevent race conditions.
	c.kbfsSocketMtx.Lock()
	defer c.kbfsSocketMtx.Unlock()

	needWrapper := false
	if c.kbfsSocketWrapper == nil {
		needWrapper = true
		c.g.Log.Debug("empty socket wrapper; need a new one")
	} else if c.kbfsSocketWrapper.Transporter != nil && !c.kbfsSocketWrapper.Transporter.IsConnected() {
		// need reconnect
		c.g.Log.Debug("rpc transport isn't connected, reconnecting...")
		needWrapper = true
	}

	isNew := false
	if needWrapper {
		sw := libkb.SocketWrapper{}
		if c.kbfsSocket == nil {
			sw.Err = fmt.Errorf("Cannot get socket")
		} else {
			sw.Conn, sw.Err = c.kbfsSocket.DialSocket()
			c.g.Log.Debug("DialSocket -> %s", libkb.ErrToOk(sw.Err))
			isNew = true
		}
		if sw.Err == nil {
			sw.Transporter = c.newTransportFromSocket(sw.Conn)
		}
		c.kbfsSocketWrapper = &sw
	}

	// Return the current error no matter what
	sw := c.kbfsSocketWrapper
	if sw.Err != nil && clearError {
		c.kbfsSocketWrapper = nil
	}

	return sw.Conn, sw.Transporter, isNew, sw.Err
}

// BindToKBFSSocket binds to the socket configured in `c.kbfsSocket`.
func (c *KBFSContext) BindToKBFSSocket() (net.Listener, error) {
	c.kbfsSocketMtx.RLock()
	defer c.kbfsSocketMtx.RUnlock()
	return c.kbfsSocket.BindToSocket()
}
<commit_msg>context: Add cleanup function for kbfs socket.<commit_after>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package env

import (
	"errors"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"sync"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/go-framed-msgpack-rpc/rpc"
)

const (
	kbfsSocketFile = "kbfsd.sock"
)

// Context defines the environment for this package
type Context interface {
	GetRunMode() libkb.RunMode
	GetLogDir() string
	GetDataDir() string
	ConfigureSocketInfo() (err error)
	GetGlobalContext() *libkb.GlobalContext
	GetSocket(clearError bool) (net.Conn, rpc.Transporter, bool, error)
	NewRPCLogFactory() rpc.LogFactory
	GetKBFSSocket(clearError bool) (net.Conn, rpc.Transporter, bool, error)
	BindToKBFSSocket() (net.Listener, error)
}

// KBFSContext is an implementation for libkbfs.Context
type KBFSContext struct {
	g *libkb.GlobalContext
	// protects the socket primitives below
	kbfsSocketMtx     sync.RWMutex
	kbfsSocket        libkb.Socket
	kbfsSocketWrapper *libkb.SocketWrapper
}

var _ Context = (*KBFSContext)(nil)

func (c *KBFSContext) initKBFSSocket() {
	c.kbfsSocketMtx.Lock()
	defer c.kbfsSocketMtx.Unlock()
	log := c.g.Log
	bindFile := c.getKBFSSocketFile()
	dialFiles := []string{bindFile}
	c.kbfsSocket = libkb.NewSocketWithFiles(log, bindFile, dialFiles)
}

// NewContextFromGlobalContext constructs a context
func NewContextFromGlobalContext(g *libkb.GlobalContext) *KBFSContext {
	c := &KBFSContext{g: g}
	c.initKBFSSocket()
	return c
}

// NewContext constructs a context. This should only be called once in
// main functions.
func NewContext() *KBFSContext {
	g := libkb.NewGlobalContextInit()
	g.ConfigureConfig()
	g.ConfigureLogging()
	g.ConfigureCaches()
	g.ConfigureMerkleClient()
	return NewContextFromGlobalContext(g)
}

// GetLogDir returns log dir
func (c *KBFSContext) GetLogDir() string {
	return c.g.Env.GetLogDir()
}

// GetDataDir returns log dir
func (c *KBFSContext) GetDataDir() string {
	return c.g.Env.GetDataDir()
}

// GetRunMode returns run mode
func (c *KBFSContext) GetRunMode() libkb.RunMode {
	return c.g.GetRunMode()
}

// GetGlobalContext returns the libkb global context.
func (c *KBFSContext) GetGlobalContext() *libkb.GlobalContext {
	return c.g
}

// GetSocket returns a socket
func (c *KBFSContext) GetSocket(clearError bool) (
	net.Conn, rpc.Transporter, bool, error) {
	return c.g.GetSocket(clearError)
}

// ConfigureSocketInfo configures a socket
func (c *KBFSContext) ConfigureSocketInfo() error {
	return c.g.ConfigureSocketInfo()
}

// NewRPCLogFactory constructs an RPC logger
func (c *KBFSContext) NewRPCLogFactory() rpc.LogFactory {
	return &libkb.RPCLogFactory{Contextified: libkb.NewContextified(c.g)}
}

func (c *KBFSContext) getSandboxSocketFile() string {
	sandboxDir := c.g.Env.HomeFinder.SandboxCacheDir()
	if sandboxDir == "" {
		return ""
	}
	return filepath.Join(sandboxDir, kbfsSocketFile)
}

func (c *KBFSContext) getKBFSSocketFile() string {
	e := c.g.Env
	return e.GetString(
		func() string { return c.getSandboxSocketFile() },
		// TODO: maybe add command-line option here
		func() string { return os.Getenv("KBFS_SOCKET_FILE") },
		func() string { return filepath.Join(e.GetRuntimeDir(), kbfsSocketFile) },
	)
}

func (c *KBFSContext) newTransportFromSocket(s net.Conn) rpc.Transporter {
	return rpc.NewTransport(s, c.NewRPCLogFactory(), libkb.WrapError)
}

// GetKBFSSocket dials the socket configured in `c.kbfsSocket`.
// Adapted from github.com/keybase/client/go/libkb.GlobalContext.GetSocket.
func (c *KBFSContext) GetKBFSSocket(clearError bool) (
	net.Conn, rpc.Transporter, bool, error) {
	var err error
	c.g.Trace("GetSocket", func() error { return err })()

	// Protect all global socket wrapper manipulation with a
	// lock to prevent race conditions.
	c.kbfsSocketMtx.Lock()
	defer c.kbfsSocketMtx.Unlock()

	needWrapper := false
	if c.kbfsSocketWrapper == nil {
		needWrapper = true
		c.g.Log.Debug("empty socket wrapper; need a new one")
	} else if c.kbfsSocketWrapper.Transporter != nil && !c.kbfsSocketWrapper.Transporter.IsConnected() {
		// need reconnect
		c.g.Log.Debug("rpc transport isn't connected, reconnecting...")
		needWrapper = true
	}

	isNew := false
	if needWrapper {
		sw := libkb.SocketWrapper{}
		if c.kbfsSocket == nil {
			sw.Err = fmt.Errorf("Cannot get socket")
		} else {
			sw.Conn, sw.Err = c.kbfsSocket.DialSocket()
			c.g.Log.Debug("DialSocket -> %s", libkb.ErrToOk(sw.Err))
			isNew = true
		}
		if sw.Err == nil {
			sw.Transporter = c.newTransportFromSocket(sw.Conn)
		}
		c.kbfsSocketWrapper = &sw
	}

	// Return the current error no matter what
	sw := c.kbfsSocketWrapper
	if sw.Err != nil && clearError {
		c.kbfsSocketWrapper = nil
	}

	return sw.Conn, sw.Transporter, isNew, sw.Err
}

// cleanupSocketFile cleans up the socket file for binding.
func (c *KBFSContext) cleanupSocketFile() error {
	switch sock := c.kbfsSocket.(type) {
	case libkb.SocketInfo:
		sf := sock.GetBindFile()
		if exists, err := libkb.FileExists(sf); err != nil {
			return err
		} else if exists {
			c.g.Log.Debug("removing stale socket file: %s", sf)
			if err = os.Remove(sf); err != nil {
				c.g.Log.Warning("error removing stale socket file: %s", err)
				return err
			}
		}
	case nil:
		return errors.New("socket not initialized")
	default:
		return errors.New("invalid socket type")
	}
	return nil
}

// BindToKBFSSocket binds to the socket configured in `c.kbfsSocket`.
func (c *KBFSContext) BindToKBFSSocket() (net.Listener, error) {
	c.kbfsSocketMtx.Lock()
	defer c.kbfsSocketMtx.Unlock()
	err := c.cleanupSocketFile()
	if err != nil {
		return nil, err
	}
	return c.kbfsSocket.BindToSocket()
}
<|endoftext|>


<commit_before>package es

type Response struct {
	Took     int            `json:"took"`
	TimedOut bool           `json:"timed_out"`
	Facets   ResponseFacets `json:"facets"`
	Hits     Hits           `json:"hits"`
}

type ResponseFacets map[string]*ResponseFacet

type ResponseFacet struct {
	Type    string       `json:"_type"`
	Missing int          `json:"missing"`
	Total   int          `json:"total"`
	Other   int          `json:"other"`
	Terms   []*FacetTerm `json:"terms"`
}

type FacetTerm struct {
	Term  interface{} `json:"term"`
	Count int         `json:"count"`
}
<commit_msg>add mapping for entries in facet response<commit_after>package es

type Response struct {
	Took     int            `json:"took"`
	TimedOut bool           `json:"timed_out"`
	Facets   ResponseFacets `json:"facets"`
	Hits     Hits           `json:"hits"`
}

type ResponseFacets map[string]*ResponseFacet

type ResponseFacet struct {
	Type    string       `json:"_type"`
	Missing int          `json:"missing"`
	Total   int          `json:"total"`
	Other   int          `json:"other"`
	Terms   []*FacetTerm `json:"terms,omitempty"`
	Entries []*Entry     `json:"entries,omitempty"`
}

type FacetTerm struct {
	Term  interface{} `json:"term"`
	Count int         `json:"count"`
}
<|endoftext|>