<commit_before>package main

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

	"github.com/julienschmidt/httprouter"
)

func makeCreateDefHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		defer r.Body.Close()

		// New generator definition with defaults.
		def := NewDef()

		if err := json.NewDecoder(r.Body).Decode(def); err != nil {
			w.WriteHeader(http.StatusUnprocessableEntity)
			fmt.Fprintf(w, err.Error())
			return
		}

		err := s.CreateDef(def)

		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		w.WriteHeader(http.StatusCreated)
	}
}

func makeUpdateDefHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")

		def, err := s.GetDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		id := def.ID

		defer r.Body.Close()

		if err := json.NewDecoder(r.Body).Decode(def); err != nil {
			w.WriteHeader(http.StatusUnprocessableEntity)
			fmt.Fprintf(w, err.Error())
			return
		}

		def.ID = id

		if err = s.UpdateDef(name, def); err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		w.WriteHeader(http.StatusNoContent)
	}
}

func makeGetDefsHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		defs, err := s.GetDefs()
		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		w.Header().Set("content-type", "application/json")

		if err := json.NewEncoder(w).Encode(defs); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, err.Error())
			return
		}
	}
}

func makeDeleteDefHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")

		err := s.DelDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		w.WriteHeader(http.StatusNoContent)
	}
}

func makeGetDefHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")

		def, err := s.GetDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		b, err := json.Marshal(def)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(err.Error()))
			return
		}

		w.Header().Set("content-type", "application/json")
		w.Write(b)
	}
}

func makeGenHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")
		readOnly := r.URL.Query().Get("ro") != ""

		def, err := s.GetDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		// Something else wrong.
		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		defer r.Body.Close()

		if readOnly {
			if err := s.Get(def, r.Body, w); err != nil {
				w.WriteHeader(http.StatusServiceUnavailable)
				fmt.Fprint(w, err.Error())
				return
			}

			return
		}

		if err := s.Gen(def, r.Body, w); err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}
	}
}

func makePutHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")

		def, err := s.GetDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		// Something else wrong.
		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		defer r.Body.Close()

		if err := s.Put(def, r.Body); err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}
	}
}

func makeDeleteHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")

		def, err := s.GetDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		// Something else wrong.
		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		defer r.Body.Close()

		if err := s.Del(def, r.Body); err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}
	}
}
<commit_msg>Copy request body into buffer<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

func makeCreateDefHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		defer r.Body.Close()

		// New generator definition with defaults.
		def := NewDef()

		if err := json.NewDecoder(r.Body).Decode(def); err != nil {
			w.WriteHeader(http.StatusUnprocessableEntity)
			fmt.Fprintf(w, err.Error())
			return
		}

		err := s.CreateDef(def)

		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		w.WriteHeader(http.StatusCreated)
	}
}

func makeUpdateDefHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")

		def, err := s.GetDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		id := def.ID

		defer r.Body.Close()

		if err := json.NewDecoder(r.Body).Decode(def); err != nil {
			w.WriteHeader(http.StatusUnprocessableEntity)
			fmt.Fprintf(w, err.Error())
			return
		}

		def.ID = id

		if err = s.UpdateDef(name, def); err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		w.WriteHeader(http.StatusNoContent)
	}
}

func makeGetDefsHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
		defs, err := s.GetDefs()
		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		w.Header().Set("content-type", "application/json")

		if err := json.NewEncoder(w).Encode(defs); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(w, err.Error())
			return
		}
	}
}

func makeDeleteDefHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")

		err := s.DelDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		w.WriteHeader(http.StatusNoContent)
	}
}

func makeGetDefHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")

		def, err := s.GetDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		b, err := json.Marshal(def)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			w.Write([]byte(err.Error()))
			return
		}

		w.Header().Set("content-type", "application/json")
		w.Write(b)
	}
}

func makeGenHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")
		readOnly := r.URL.Query().Get("ro") != ""

		def, err := s.GetDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		// Something else wrong.
		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		var buf bytes.Buffer
		if _, err := io.Copy(&buf, r.Body); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprint(w, err.Error())
			return
		}

		r.Body.Close()

		if readOnly {
			if err := s.Get(def, &buf, w); err != nil {
				w.WriteHeader(http.StatusServiceUnavailable)
				fmt.Fprint(w, err.Error())
				return
			}

			return
		}

		if err := s.Gen(def, &buf, w); err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}
	}
}

func makePutHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")

		def, err := s.GetDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		// Something else wrong.
		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		var buf bytes.Buffer
		if _, err := io.Copy(&buf, r.Body); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprint(w, err.Error())
			return
		}

		r.Body.Close()

		if err := s.Put(def, &buf); err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}
	}
}

func makeDeleteHandler(s *Server) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		name := p.ByName("name")

		def, err := s.GetDef(name)
		if err == ErrNoDef {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		// Something else wrong.
		if err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}

		var buf bytes.Buffer
		if _, err := io.Copy(&buf, r.Body); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprint(w, err.Error())
			return
		}

		r.Body.Close()

		if err := s.Del(def, &buf); err != nil {
			w.WriteHeader(http.StatusServiceUnavailable)
			fmt.Fprint(w, err.Error())
			return
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
)

type Octets uint32 // all 4 octets, in their respective bit locations

type OctsList []int // len always 4

func (o OctsList) Pack() Octets {
	return (Octets(o[0]) << 24) +
		(Octets(o[1]) << 16) +
		(Octets(o[2]) << 8) +
		(Octets(o[3]))
}

func (ino Octets) List() OctsList {
	return OctsList{
		int((0xFF000000 & ino) >> 24),
		int((0x00FF0000 & ino) >> 16),
		int((0x0000FF00 & ino) >> 8),
		int(0x000000FF & ino),
	}
}

type Addr struct {
	ip   OctsList
	mask OctsList
}

func (a *Addr) String() string {
	return fmt.Sprintf(
		"%d.%d.%d.%d/%d.%d.%d.%d",
		a.ip[0], a.ip[1], a.ip[2], a.ip[3],
		a.mask[0], a.mask[1], a.mask[2], a.mask[3])
}

func (a *Addr) Classful() (OctsList, string) {
	topOctet := a.ip[0]
	switch {
	case topOctet < 128:
		return OctsList{255, 0, 0, 0}, "A"
	case topOctet >= 128 && topOctet < 192:
		return OctsList{255, 255, 0, 0}, "B"
	case topOctet >= 192 && topOctet < 224:
		return OctsList{255, 255, 255, 0}, "C"
	default:
		panic("expected only classes A,B, or C")
	}
}

var hosts = []Addr{
	{ip: OctsList{9, 201, 195, 84}, mask: OctsList{255, 255, 240, 0}},
	{ip: OctsList{128, 10, 189, 215}, mask: OctsList{255, 255, 248, 0}},
	{ip: OctsList{135, 21, 243, 82}, mask: OctsList{255, 255, 224, 0}},
	{ip: OctsList{75, 149, 205, 61}, mask: OctsList{255, 255, 192, 0}},
	{ip: OctsList{7, 105, 198, 111}, mask: OctsList{255, 255, 252, 0}},
}

func main() {
	fmt.Printf("analyzing %d hosts ...\n", len(hosts))
	for _, addr := range hosts {
		ipPck := addr.ip.Pack()
		maskPck := addr.mask.Pack()
		hostMaskPck := ^addr.mask.Pack()

		classMask, klass := addr.Classful()
		classMaskPck := classMask.Pack()

		subnetMaskPck := (^classMaskPck) & maskPck
		fmt.Printf(
			"  network: %v (class %s masked) # %d\n\t%v\n\tsubnet : %d <- %v\n\thost   : %d <- %v\n\n",
			(ipPck & classMaskPck).List(),
			klass, (ipPck & classMaskPck),
			addr.String(),
			ipPck&subnetMaskPck, (ipPck & subnetMaskPck).List(),
			ipPck&hostMaskPck, (ipPck & hostMaskPck).List())
	}
}
<commit_msg>hw4 = awful; wasting more of my time than my essay on david hume...<commit_after>package main

import (
	"fmt"
)

type Octets uint32 // all 4 octets, in their respective bit locations

type OctsList []int // len always 4

func (o OctsList) Pack() Octets {
	return (Octets(o[0]) << 24) +
		(Octets(o[1]) << 16) +
		(Octets(o[2]) << 8) +
		(Octets(o[3]))
}

func (ino Octets) List() OctsList {
	return OctsList{
		int((0xFF000000 & ino) >> 24),
		int((0x00FF0000 & ino) >> 16),
		int((0x0000FF00 & ino) >> 8),
		int(0x000000FF & ino),
	}
}

type Addr struct {
	ip   OctsList
	mask OctsList
}

func (a *Addr) String() string {
	return fmt.Sprintf(
		"%d.%d.%d.%d/%d.%d.%d.%d",
		a.ip[0], a.ip[1], a.ip[2], a.ip[3],
		a.mask[0], a.mask[1], a.mask[2], a.mask[3])
}

func (a *Addr) Classful() (OctsList, uint, string) {
	topOctet := a.ip[0]
	switch {
	case topOctet < 128:
		return OctsList{255, 0, 0, 0}, 8 * 1, "A"
	case topOctet >= 128 && topOctet < 192:
		return OctsList{255, 255, 0, 0}, 8 * 2, "B"
	case topOctet >= 192 && topOctet < 224:
		return OctsList{255, 255, 255, 0}, 8 * 3, "C"
	default:
		panic("expected only classes A,B, or C")
	}
}

var hosts = []Addr{
	{ip: OctsList{9, 201, 195, 84}, mask: OctsList{255, 255, 240, 0}},
	{ip: OctsList{128, 10, 189, 215}, mask: OctsList{255, 255, 248, 0}},
	{ip: OctsList{135, 21, 243, 82}, mask: OctsList{255, 255, 224, 0}},
	{ip: OctsList{75, 149, 205, 61}, mask: OctsList{255, 255, 192, 0}},
	{ip: OctsList{7, 105, 198, 111}, mask: OctsList{255, 255, 252, 0}},

	// TODO(zacsh) remove this sample from the last slide
	{ip: OctsList{128, 10, 211, 78}, mask: OctsList{255, 255, 240, 0}},
}

func countSubnetBits(o Octets) uint {
	var n uint = 0
	for i := n; i < 32; i++ {
		n += 1 & (uint(o) >> i)
	}
	return n
}

func main() {
	fmt.Printf("analyzing %d hosts ...\n", len(hosts))
	for _, addr := range hosts {
		ipPck := addr.ip.Pack()
		maskPck := addr.mask.Pack()
		hostMaskPck := ^addr.mask.Pack()

		classMask, cidrOffset, klass := addr.Classful()
		classMaskPck := classMask.Pack()

		subnetMaskPck := (^classMaskPck) & maskPck

		fmt.Printf(
			"  network: %v (class %s masked) # %d\n\t%v\n\tsubnet : %d <- %v\n\thost   : %d <- %v\n\n",
			(ipPck & classMaskPck).List(),
			klass, (ipPck & classMaskPck),
			addr.String(),
			ipPck&subnetMaskPck, (ipPck & subnetMaskPck).List(),
			ipPck&hostMaskPck, (ipPck & hostMaskPck).List())

		// TODO(zacsh) cleanup, and push back up to above printf, after fully
		// matching nitty-griddy meaningless busy work in the last slide
		fmt.Printf("[dbg] binary value debugging:\n     ip:\t%032b\nclsmask:\t%032b\n  sbmsk:\t%032b\n\n",
			ipPck, classMaskPck, maskPck)
		networkid := (ipPck & classMaskPck) >> (32 - cidrOffset)
		fmt.Printf("[dbg] binary arithmetic debugging:\nip&clss:\t%032b\nip&class:\t%d\n\n",
			networkid, networkid)
		uniqSubnetBits := (^classMaskPck) & maskPck
		uniqSubnetBitCount := countSubnetBits(uniqSubnetBits)
		subnetID := (uniqSubnetBits & ipPck) >> (32 - cidrOffset - uniqSubnetBitCount)
		fmt.Printf("[dbg] subnet mask debugging:\n  subnet bits:\t%d\n  subnet #:\t%d\n\n",
			uniqSubnetBitCount,
			subnetID)
	}
}
<|endoftext|>


<commit_before>package i18n

import (
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"github.com/qor/admin"
	"github.com/qor/cache"
	"github.com/qor/cache/memory"
	"github.com/qor/qor"
	"github.com/qor/qor/resource"
	"github.com/qor/qor/utils"
	"github.com/theplant/cldr"
)

// Default default locale for i18n
var Default = "en-US"

// I18n struct that hold all translations
type I18n struct {
	Resource        *admin.Resource
	scope           string
	value           string
	Backends        []Backend
	FallbackLocales map[string][]string
	fallbackLocales []string
	cacheStore      cache.CacheStoreInterface
}

// ResourceName change display name in qor admin
func (I18n) ResourceName() string {
	return "Translation"
}

// Backend defined methods that needs for translation backend
type Backend interface {
	LoadTranslations() []*Translation
	SaveTranslation(*Translation) error
	DeleteTranslation(*Translation) error
}

// Translation is a struct for translations, including Translation Key, Locale, Value
type Translation struct {
	Key     string
	Locale  string
	Value   string
	Backend Backend `json:"-"`
}

// New initialize I18n with backends
func New(backends ...Backend) *I18n {
	i18n := &I18n{Backends: backends, cacheStore: memory.New()}
	i18n.loadToCacheStore()
	return i18n
}

// SetCacheStore set i18n's cache store
func (i18n *I18n) SetCacheStore(cacheStore cache.CacheStoreInterface) {
	i18n.cacheStore = cacheStore
	i18n.loadToCacheStore()
}

func (i18n *I18n) loadToCacheStore() {
	backends := i18n.Backends
	for i := len(backends) - 1; i >= 0; i-- {
		var backend = backends[i]
		for _, translation := range backend.LoadTranslations() {
			i18n.AddTranslation(translation)
		}
	}
}

// LoadTranslations load translations as map `map[locale]map[key]*Translation`
func (i18n *I18n) LoadTranslations() map[string]map[string]*Translation {
	var translations = map[string]map[string]*Translation{}

	for i := len(i18n.Backends); i > 0; i-- {
		backend := i18n.Backends[i-1]
		for _, translation := range backend.LoadTranslations() {
			if translations[translation.Locale] == nil {
				translations[translation.Locale] = map[string]*Translation{}
			}
			translations[translation.Locale][translation.Key] = translation
		}
	}
	return translations
}

// AddTranslation add translation
func (i18n *I18n) AddTranslation(translation *Translation) error {
	return i18n.cacheStore.Set(cacheKey(translation.Locale, translation.Key), translation)
}

// SaveTranslation save translation
func (i18n *I18n) SaveTranslation(translation *Translation) error {
	for _, backend := range i18n.Backends {
		if backend.SaveTranslation(translation) == nil {
			i18n.AddTranslation(translation)
			return nil
		}
	}

	return errors.New("failed to save translation")
}

// DeleteTranslation delete translation
func (i18n *I18n) DeleteTranslation(translation *Translation) (err error) {
	for _, backend := range i18n.Backends {
		backend.DeleteTranslation(translation)
	}

	return i18n.cacheStore.Delete(cacheKey(translation.Locale, translation.Key))
}

// Scope i18n scope
func (i18n *I18n) Scope(scope string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: scope, value: i18n.value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: i18n.fallbackLocales}
}

// Default default value of translation if key is missing
func (i18n *I18n) Default(value string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: i18n.scope, value: value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: i18n.fallbackLocales}
}

//  default value of translation if key is missing
func (i18n *I18n) Fallbacks(locale ...string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: i18n.scope, value: i18n.value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: locale}
}

// T translate with locale, key and arguments
func (i18n *I18n) T(locale, key string, args ...interface{}) template.HTML {
	var (
		value           = i18n.value
		translationKey  = key
		fallbackLocales = i18n.fallbackLocales
	)

	if locale == "" {
		locale = Default
	}

	if locales, ok := i18n.FallbackLocales[locale]; ok {
		fallbackLocales = append(fallbackLocales, locales...)
	}
	fallbackLocales = append(fallbackLocales, Default)

	if i18n.scope != "" {
		translationKey = strings.Join([]string{i18n.scope, key}, ".")
	}

	var translation Translation
	if err := i18n.cacheStore.Unmarshal(cacheKey(locale, key), &translation); err != nil || translation.Value == "" {
		for _, fallbackLocale := range fallbackLocales {
			if err := i18n.cacheStore.Unmarshal(cacheKey(fallbackLocale, key), &translation); err == nil && translation.Value != "" {
				break
			}
		}

		if translation.Value == "" {
			// Get default translation if not translated
			if err := i18n.cacheStore.Unmarshal(cacheKey(Default, key), &translation); err != nil || translation.Value == "" {
				// If not initialized
				var defaultBackend Backend
				if len(i18n.Backends) > 0 {
					defaultBackend = i18n.Backends[0]
				}
				translation = Translation{Key: translationKey, Value: value, Locale: locale, Backend: defaultBackend}

				// Save translation
				i18n.SaveTranslation(&translation)
			}
		}
	}

	if translation.Value != "" {
		value = translation.Value
	} else {
		value = key
	}

	if str, err := cldr.Parse(locale, value, args...); err == nil {
		value = str
	}

	return template.HTML(value)
}

// RenderInlineEditAssets render inline edit html, it is using: http://vitalets.github.io/x-editable/index.html
// You could use Bootstrap or JQuery UI by set isIncludeExtendAssetLib to false and load files by yourself
func RenderInlineEditAssets(isIncludeJQuery bool, isIncludeExtendAssetLib bool) (template.HTML, error) {
	for _, gopath := range strings.Split(os.Getenv("GOPATH"), ":") {
		var content string
		var hasError bool

		if isIncludeJQuery {
			content = `<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>`
		}

		if isIncludeExtendAssetLib {
			if extendLib, err := ioutil.ReadFile(filepath.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/inline-edit-libs.tmpl")); err == nil {
				content += string(extendLib)
			} else {
				hasError = true
			}

			if css, err := ioutil.ReadFile(filepath.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/assets/stylesheets/i18n-inline.css")); err == nil {
				content += fmt.Sprintf("<style>%s</style>", string(css))
			} else {
				hasError = true
			}

		}

		if js, err := ioutil.ReadFile(filepath.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/assets/javascripts/i18n-inline.js")); err == nil {
			content += fmt.Sprintf("<script type=\"text/javascript\">%s</script>", string(js))
		} else {
			hasError = true
		}

		if !hasError {
			return template.HTML(content), nil
		}
	}

	return template.HTML(""), errors.New("templates not found")
}

func getLocaleFromContext(context *qor.Context) string {
	if locale := utils.GetLocale(context); locale != "" {
		return locale
	}

	return Default
}

type availableLocalesInterface interface {
	AvailableLocales() []string
}

type viewableLocalesInterface interface {
	ViewableLocales() []string
}

type editableLocalesInterface interface {
	EditableLocales() []string
}

func getAvailableLocales(req *http.Request, currentUser qor.CurrentUser) []string {
	if user, ok := currentUser.(viewableLocalesInterface); ok {
		return user.ViewableLocales()
	}

	if user, ok := currentUser.(availableLocalesInterface); ok {
		return user.AvailableLocales()
	}
	return []string{Default}
}

func getEditableLocales(req *http.Request, currentUser qor.CurrentUser) []string {
	if user, ok := currentUser.(editableLocalesInterface); ok {
		return user.EditableLocales()
	}

	if user, ok := currentUser.(availableLocalesInterface); ok {
		return user.AvailableLocales()
	}
	return []string{Default}
}

// ConfigureQorResource configure qor resource for qor admin
func (i18n *I18n) ConfigureQorResource(res resource.Resourcer) {
	if res, ok := res.(*admin.Resource); ok {
		i18n.Resource = res
		res.UseTheme("i18n")
		res.GetAdmin().Config.I18n = i18n
		res.SearchAttrs("value") // generate search handler for i18n

		var getPrimaryLocale = func(context *admin.Context) string {
			if locale := context.Request.Form.Get("primary_locale"); locale != "" {
				return locale
			}
			if availableLocales := getAvailableLocales(context.Request, context.CurrentUser); len(availableLocales) > 0 {
				return availableLocales[0]
			}
			return ""
		}

		var getEditingLocale = func(context *admin.Context) string {
			if locale := context.Request.Form.Get("to_locale"); locale != "" {
				return locale
			}
			return getLocaleFromContext(context.Context)
		}

		type matchedTranslation struct {
			Key           string
			PrimaryLocale string
			PrimaryValue  string
			EditingLocale string
			EditingValue  string
		}

		res.GetAdmin().RegisterFuncMap("i18n_available_translations", func(context *admin.Context) (results []matchedTranslation) {
			var (
				translationsMap     = i18n.LoadTranslations()
				matchedTranslations = map[string]matchedTranslation{}
				keys                = []string{}
				keyword             = strings.ToLower(context.Request.URL.Query().Get("keyword"))
				primaryLocale       = getPrimaryLocale(context)
				editingLocale       = getEditingLocale(context)
			)

			var filterTranslations = func(translations map[string]*Translation, isPrimary bool) {
				if translations != nil {
					for key, translation := range translations {
						if (keyword == "") || (strings.Index(strings.ToLower(translation.Key), keyword) != -1 ||
							strings.Index(strings.ToLower(translation.Value), keyword) != -1) {
							if _, ok := matchedTranslations[key]; !ok {
								var t = matchedTranslation{
									Key:           key,
									PrimaryLocale: primaryLocale,
									EditingLocale: editingLocale,
									EditingValue:  translation.Value,
								}

								if localeTranslations, ok := translationsMap[primaryLocale]; ok {
									if v, ok := localeTranslations[key]; ok {
										t.PrimaryValue = v.Value
									}
								}

								matchedTranslations[key] = t
								keys = append(keys, key)
							}
						}
					}
				}
			}

			filterTranslations(translationsMap[getEditingLocale(context)], false)
			if primaryLocale != editingLocale {
				filterTranslations(translationsMap[getPrimaryLocale(context)], true)
			}

			sort.Strings(keys)

			pagination := context.Searcher.Pagination
			pagination.Total = len(keys)
			pagination.PerPage, _ = strconv.Atoi(context.Request.URL.Query().Get("per_page"))
			pagination.CurrentPage, _ = strconv.Atoi(context.Request.URL.Query().Get("page"))

			if pagination.CurrentPage == 0 {
				pagination.CurrentPage = 1
			}

			if pagination.PerPage == 0 {
				pagination.PerPage = 25
			}

			if pagination.CurrentPage > 0 {
				pagination.Pages = pagination.Total / pagination.PerPage
			}

			context.Searcher.Pagination = pagination

			var paginationKeys []string
			if pagination.CurrentPage == -1 {
				paginationKeys = keys
			} else {
				lastIndex := pagination.CurrentPage * pagination.PerPage
				if pagination.Total < lastIndex {
					lastIndex = pagination.Total
				}

				startIndex := (pagination.CurrentPage - 1) * pagination.PerPage
				if lastIndex >= startIndex {
					paginationKeys = keys[startIndex:lastIndex]
				}
			}

			for _, key := range paginationKeys {
				results = append(results, matchedTranslations[key])
			}
			return results
		})

		res.GetAdmin().RegisterFuncMap("i18n_primary_locale", getPrimaryLocale)

		res.GetAdmin().RegisterFuncMap("i18n_editing_locale", getEditingLocale)

		res.GetAdmin().RegisterFuncMap("i18n_viewable_locales", func(context admin.Context) []string {
			return getAvailableLocales(context.Request, context.CurrentUser)
		})

		res.GetAdmin().RegisterFuncMap("i18n_editable_locales", func(context admin.Context) []string {
			return getEditableLocales(context.Request, context.CurrentUser)
		})

		controller := i18nController{i18n}
		router := res.GetAdmin().GetRouter()
		router.Get(res.ToParam(), controller.Index, &admin.RouteConfig{Resource: res})
		router.Post(res.ToParam(), controller.Update, &admin.RouteConfig{Resource: res})
		router.Put(res.ToParam(), controller.Update, &admin.RouteConfig{Resource: res})

		res.GetAdmin().RegisterViewPath("github.com/qor/i18n/views")
	}
}

func cacheKey(strs ...string) string {
	return strings.Join(strs, "/")
}
<commit_msg>Revert "Update i18n based on admin changes"<commit_after>package i18n

import (
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"

	"github.com/qor/admin"
	"github.com/qor/cache"
	"github.com/qor/cache/memory"
	"github.com/qor/qor"
	"github.com/qor/qor/resource"
	"github.com/qor/qor/utils"
	"github.com/theplant/cldr"
)

// Default default locale for i18n
var Default = "en-US"

// I18n struct that hold all translations
type I18n struct {
	Resource        *admin.Resource
	scope           string
	value           string
	Backends        []Backend
	FallbackLocales map[string][]string
	fallbackLocales []string
	cacheStore      cache.CacheStoreInterface
}

// ResourceName change display name in qor admin
func (I18n) ResourceName() string {
	return "Translation"
}

// Backend defined methods that needs for translation backend
type Backend interface {
	LoadTranslations() []*Translation
	SaveTranslation(*Translation) error
	DeleteTranslation(*Translation) error
}

// Translation is a struct for translations, including Translation Key, Locale, Value
type Translation struct {
	Key     string
	Locale  string
	Value   string
	Backend Backend `json:"-"`
}

// New initialize I18n with backends
func New(backends ...Backend) *I18n {
	i18n := &I18n{Backends: backends, cacheStore: memory.New()}
	i18n.loadToCacheStore()
	return i18n
}

// SetCacheStore set i18n's cache store
func (i18n *I18n) SetCacheStore(cacheStore cache.CacheStoreInterface) {
	i18n.cacheStore = cacheStore
	i18n.loadToCacheStore()
}

func (i18n *I18n) loadToCacheStore() {
	backends := i18n.Backends
	for i := len(backends) - 1; i >= 0; i-- {
		var backend = backends[i]
		for _, translation := range backend.LoadTranslations() {
			i18n.AddTranslation(translation)
		}
	}
}

// LoadTranslations load translations as map `map[locale]map[key]*Translation`
func (i18n *I18n) LoadTranslations() map[string]map[string]*Translation {
	var translations = map[string]map[string]*Translation{}

	for i := len(i18n.Backends); i > 0; i-- {
		backend := i18n.Backends[i-1]
		for _, translation := range backend.LoadTranslations() {
			if translations[translation.Locale] == nil {
				translations[translation.Locale] = map[string]*Translation{}
			}
			translations[translation.Locale][translation.Key] = translation
		}
	}
	return translations
}

// AddTranslation add translation
func (i18n *I18n) AddTranslation(translation *Translation) error {
	return i18n.cacheStore.Set(cacheKey(translation.Locale, translation.Key), translation)
}

// SaveTranslation save translation
func (i18n *I18n) SaveTranslation(translation *Translation) error {
	for _, backend := range i18n.Backends {
		if backend.SaveTranslation(translation) == nil {
			i18n.AddTranslation(translation)
			return nil
		}
	}

	return errors.New("failed to save translation")
}

// DeleteTranslation delete translation
func (i18n *I18n) DeleteTranslation(translation *Translation) (err error) {
	for _, backend := range i18n.Backends {
		backend.DeleteTranslation(translation)
	}

	return i18n.cacheStore.Delete(cacheKey(translation.Locale, translation.Key))
}

// Scope i18n scope
func (i18n *I18n) Scope(scope string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: scope, value: i18n.value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: i18n.fallbackLocales}
}

// Default default value of translation if key is missing
func (i18n *I18n) Default(value string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: i18n.scope, value: value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: i18n.fallbackLocales}
}

//  default value of translation if key is missing
func (i18n *I18n) Fallbacks(locale ...string) admin.I18n {
	return &I18n{cacheStore: i18n.cacheStore, scope: i18n.scope, value: i18n.value, Backends: i18n.Backends, Resource: i18n.Resource, FallbackLocales: i18n.FallbackLocales, fallbackLocales: locale}
}

// T translate with locale, key and arguments
func (i18n *I18n) T(locale, key string, args ...interface{}) template.HTML {
	var (
		value           = i18n.value
		translationKey  = key
		fallbackLocales = i18n.fallbackLocales
	)

	if locale == "" {
		locale = Default
	}

	if locales, ok := i18n.FallbackLocales[locale]; ok {
		fallbackLocales = append(fallbackLocales, locales...)
	}
	fallbackLocales = append(fallbackLocales, Default)

	if i18n.scope != "" {
		translationKey = strings.Join([]string{i18n.scope, key}, ".")
	}

	var translation Translation
	if err := i18n.cacheStore.Unmarshal(cacheKey(locale, key), &translation); err != nil || translation.Value == "" {
		for _, fallbackLocale := range fallbackLocales {
			if err := i18n.cacheStore.Unmarshal(cacheKey(fallbackLocale, key), &translation); err == nil && translation.Value != "" {
				break
			}
		}

		if translation.Value == "" {
			// Get default translation if not translated
			if err := i18n.cacheStore.Unmarshal(cacheKey(Default, key), &translation); err != nil || translation.Value == "" {
				// If not initialized
				var defaultBackend Backend
				if len(i18n.Backends) > 0 {
					defaultBackend = i18n.Backends[0]
				}
				translation = Translation{Key: translationKey, Value: value, Locale: locale, Backend: defaultBackend}

				// Save translation
				i18n.SaveTranslation(&translation)
			}
		}
	}

	if translation.Value != "" {
		value = translation.Value
	} else {
		value = key
	}

	if str, err := cldr.Parse(locale, value, args...); err == nil {
		value = str
	}

	return template.HTML(value)
}

// RenderInlineEditAssets render inline edit html, it is using: http://vitalets.github.io/x-editable/index.html
// You could use Bootstrap or JQuery UI by set isIncludeExtendAssetLib to false and load files by yourself
func RenderInlineEditAssets(isIncludeJQuery bool, isIncludeExtendAssetLib bool) (template.HTML, error) {
	for _, gopath := range strings.Split(os.Getenv("GOPATH"), ":") {
		var content string
		var hasError bool

		if isIncludeJQuery {
			content = `<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>`
		}

		if isIncludeExtendAssetLib {
			if extendLib, err := ioutil.ReadFile(filepath.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/inline-edit-libs.tmpl")); err == nil {
				content += string(extendLib)
			} else {
				hasError = true
			}

			if css, err := ioutil.ReadFile(filepath.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/assets/stylesheets/i18n-inline.css")); err == nil {
				content += fmt.Sprintf("<style>%s</style>", string(css))
			} else {
				hasError = true
			}

		}

		if js, err := ioutil.ReadFile(filepath.Join(gopath, "src/github.com/qor/i18n/views/themes/i18n/assets/javascripts/i18n-inline.js")); err == nil {
			content += fmt.Sprintf("<script type=\"text/javascript\">%s</script>", string(js))
		} else {
			hasError = true
		}

		if !hasError {
			return template.HTML(content), nil
		}
	}

	return template.HTML(""), errors.New("templates not found")
}

func getLocaleFromContext(context *qor.Context) string {
	if locale := utils.GetLocale(context); locale != "" {
		return locale
	}

	return Default
}

type availableLocalesInterface interface {
	AvailableLocales() []string
}

type viewableLocalesInterface interface {
	ViewableLocales() []string
}

type editableLocalesInterface interface {
	EditableLocales() []string
}

func getAvailableLocales(req *http.Request, currentUser qor.CurrentUser) []string {
	if user, ok := currentUser.(viewableLocalesInterface); ok {
		return user.ViewableLocales()
	}

	if user, ok := currentUser.(availableLocalesInterface); ok {
		return user.AvailableLocales()
	}
	return []string{Default}
}

func getEditableLocales(req *http.Request, currentUser qor.CurrentUser) []string {
	if user, ok := currentUser.(editableLocalesInterface); ok {
		return user.EditableLocales()
	}

	if user, ok := currentUser.(availableLocalesInterface); ok {
		return user.AvailableLocales()
	}
	return []string{Default}
}

// ConfigureQorResource configure qor resource for qor admin
func (i18n *I18n) ConfigureQorResource(res resource.Resourcer) {
	if res, ok := res.(*admin.Resource); ok {
		i18n.Resource = res
		res.UseTheme("i18n")
		res.GetAdmin().I18n = i18n
		res.SearchAttrs("value") // generate search handler for i18n

		var getPrimaryLocale = func(context *admin.Context) string {
			if locale := context.Request.Form.Get("primary_locale"); locale != "" {
				return locale
			}
			if availableLocales := getAvailableLocales(context.Request, context.CurrentUser); len(availableLocales) > 0 {
				return availableLocales[0]
			}
			return ""
		}

		var getEditingLocale = func(context *admin.Context) string {
			if locale := context.Request.Form.Get("to_locale"); locale != "" {
				return locale
			}
			return getLocaleFromContext(context.Context)
		}

		type matchedTranslation struct {
			Key           string
			PrimaryLocale string
			PrimaryValue  string
			EditingLocale string
			EditingValue  string
		}

		res.GetAdmin().RegisterFuncMap("i18n_available_translations", func(context *admin.Context) (results []matchedTranslation) {
			var (
				translationsMap     = i18n.LoadTranslations()
				matchedTranslations = map[string]matchedTranslation{}
				keys                = []string{}
				keyword             = strings.ToLower(context.Request.URL.Query().Get("keyword"))
				primaryLocale       = getPrimaryLocale(context)
				editingLocale       = getEditingLocale(context)
			)

			var filterTranslations = func(translations map[string]*Translation, isPrimary bool) {
				if translations != nil {
					for key, translation := range translations {
						if (keyword == "") || (strings.Index(strings.ToLower(translation.Key), keyword) != -1 ||
							strings.Index(strings.ToLower(translation.Value), keyword) != -1) {
							if _, ok := matchedTranslations[key]; !ok {
								var t = matchedTranslation{
									Key:           key,
									PrimaryLocale: primaryLocale,
									EditingLocale: editingLocale,
									EditingValue:  translation.Value,
								}

								if localeTranslations, ok := translationsMap[primaryLocale]; ok {
									if v, ok := localeTranslations[key]; ok {
										t.PrimaryValue = v.Value
									}
								}

								matchedTranslations[key] = t
								keys = append(keys, key)
							}
						}
					}
				}
			}

			filterTranslations(translationsMap[getEditingLocale(context)], false)
			if primaryLocale != editingLocale {
				filterTranslations(translationsMap[getPrimaryLocale(context)], true)
			}

			sort.Strings(keys)

			pagination := context.Searcher.Pagination
			pagination.Total = len(keys)
			pagination.PerPage, _ = strconv.Atoi(context.Request.URL.Query().Get("per_page"))
			pagination.CurrentPage, _ = strconv.Atoi(context.Request.URL.Query().Get("page"))

			if pagination.CurrentPage == 0 {
				pagination.CurrentPage = 1
			}

			if pagination.PerPage == 0 {
				pagination.PerPage = 25
			}

			if pagination.CurrentPage > 0 {
				pagination.Pages = pagination.Total / pagination.PerPage
			}

			context.Searcher.Pagination = pagination

			var paginationKeys []string
			if pagination.CurrentPage == -1 {
				paginationKeys = keys
			} else {
				lastIndex := pagination.CurrentPage * pagination.PerPage
				if pagination.Total < lastIndex {
					lastIndex = pagination.Total
				}

				startIndex := (pagination.CurrentPage - 1) * pagination.PerPage
				if lastIndex >= startIndex {
					paginationKeys = keys[startIndex:lastIndex]
				}
			}

			for _, key := range paginationKeys {
				results = append(results, matchedTranslations[key])
			}
			return results
		})

		res.GetAdmin().RegisterFuncMap("i18n_primary_locale", getPrimaryLocale)

		res.GetAdmin().RegisterFuncMap("i18n_editing_locale", getEditingLocale)

		res.GetAdmin().RegisterFuncMap("i18n_viewable_locales", func(context admin.Context) []string {
			return getAvailableLocales(context.Request, context.CurrentUser)
		})

		res.GetAdmin().RegisterFuncMap("i18n_editable_locales", func(context admin.Context) []string {
			return getEditableLocales(context.Request, context.CurrentUser)
		})

		controller := i18nController{i18n}
		router := res.GetAdmin().GetRouter()
		router.Get(res.ToParam(), controller.Index, &admin.RouteConfig{Resource: res})
		router.Post(res.ToParam(), controller.Update, &admin.RouteConfig{Resource: res})
		router.Put(res.ToParam(), controller.Update, &admin.RouteConfig{Resource: res})

		res.GetAdmin().RegisterViewPath("github.com/qor/i18n/views")
	}
}

func cacheKey(strs ...string) string {
	return strings.Join(strs, "/")
}
<|endoftext|>


<commit_before><commit_msg>Correctly detecting running WM<commit_after><|endoftext|>


<commit_before>package dataupload

import (
	"archive/tar"
	"compress/gzip"
	"github.com/gaia-docker/tugbot-result-service/pool"
	"github.com/gaia-docker/tugbot-result-service/testutils"
	"github.com/stretchr/testify/assert"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"testing"
)

func TestUploadHandlerEmptyBody(t *testing.T) {

	handler := NewUploadHandler(pool.NewHub())

	assert.HTTPError(t, handler.ServeHTTP, "POST", "/results", url.Values{})
}

func TestUploadHandler(t *testing.T) {

	testFile := "test.tar.gz"
	createTestGzip(testFile)
	hub := pool.NewHub()
	go hub.Run()

	code := uploadResults(t, hub, testFile)
	assert.Equal(t, http.StatusOK, code)

	matches, _ := filepath.Glob("resultService*")
	cleanup(&matches[0], testFile)
	hub.CloseBroadcastChannel()
}

func uploadResults(t *testing.T, hub *pool.Hub, testFile string) int {

	file, err := os.Open(testFile)
	assert.NoError(t, err)
	defer file.Close()

	req, err := http.NewRequest("POST", "/results?mainfile=tugbot.txt", file)
	assert.NoError(t, err)

	req.Header.Add("Content-Type", "gzip")

	rr := httptest.NewRecorder()
	handler := NewUploadHandler(hub)
	handler.ServeHTTP(rr, req)

	return rr.Code
}

func createTestGzip(testFile string) {

	file, err := os.Create(testFile)
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()
	// set up the gzip writer
	gw := gzip.NewWriter(file)
	defer gw.Close()

	testutils.CreateTar(tar.NewWriter(gw), testFiles)
}
<commit_msg>added test<commit_after>package dataupload

import (
	"archive/tar"
	"compress/gzip"
	"github.com/gaia-docker/tugbot-result-service/pool"
	"github.com/gaia-docker/tugbot-result-service/testutils"
	"github.com/stretchr/testify/assert"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"testing"
)

func TestUploadHandlerEmptyBody(t *testing.T) {

	handler := NewUploadHandler(pool.NewHub())

	assert.HTTPError(t, handler.ServeHTTP, "POST", "/results", url.Values{})
}

func TestUploadHandler(t *testing.T) {

	testUploadHandler(t, "/results?mainfile=tugbot.txt")
}

func TestUploadHandlerNoMainfile(t *testing.T) {

	testUploadHandler(t, "/results")
}

func testUploadHandler(t *testing.T, urlSuffix string) {

	testFile := "test.tar.gz"
	createTestGzip(testFile)
	hub := pool.NewHub()
	go hub.Run()

	code := uploadResults(t, hub, testFile, urlSuffix)
	assert.Equal(t, http.StatusOK, code)

	matches, _ := filepath.Glob("resultService*")
	cleanup(&matches[0], testFile)
	hub.CloseBroadcastChannel()
}

func uploadResults(t *testing.T, hub *pool.Hub, testFile, urlSuffix string) int {

	file, err := os.Open(testFile)
	assert.NoError(t, err)
	defer file.Close()

	req, err := http.NewRequest("POST", urlSuffix, file)
	assert.NoError(t, err)

	req.Header.Add("Content-Type", "gzip")

	rr := httptest.NewRecorder()
	handler := NewUploadHandler(hub)
	handler.ServeHTTP(rr, req)

	return rr.Code
}

func createTestGzip(testFile string) {

	file, err := os.Create(testFile)
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()
	// set up the gzip writer
	gw := gzip.NewWriter(file)
	defer gw.Close()

	testutils.CreateTar(tar.NewWriter(gw), testFiles)
}
<|endoftext|>


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

package mutable

import (
	"fmt"
	"math"
	"os"
	"time"

	"github.com/jacobsa/timeutil"
	"golang.org/x/net/context"
)

// A mutable view on some content. Created with an initial read-only view,
// which then can be modified by the user and read back. Keeps track of which
// portion of the content has been dirtied.
//
// External synchronization is required.
type Content interface {
	// Panic if any internal invariants are violated.
	CheckInvariants()

	// Destroy any state used by the object, putting it into an indeterminate
	// state. The object must not be used again.
	Destroy()

	// Read part of the content, with semantics equivalent to io.ReaderAt aside
	// from context support.
	ReadAt(ctx context.Context, buf []byte, offset int64) (n int, err error)

	// Return information about the current state of the content.
	Stat(ctx context.Context) (sr StatResult, err error)

	// Write into the content, with semantics equivalent to io.WriterAt aside from
	// context support.
	WriteAt(ctx context.Context, buf []byte, offset int64) (n int, err error)

	// Truncate our the content to the given number of bytes, extending if n is
	// greater than the current size.
	Truncate(ctx context.Context, n int64) (err error)
}

type StatResult struct {
	// The current size in bytes of the content.
	Size int64

	// It is guaranteed that all bytes in the range [0, DirtyThreshold) are
	// unmodified from the original content with which the mutable content object
	// was created.
	DirtyThreshold int64

	// The time at which the content was last updated, or nil if we've never
	// changed it.
	Mtime *time.Time
}

// Create a mutable content object whose initial contents are given by the
// supplied file. The caller must ensure that the file is not used further by
// anybody else.
func NewContent(
	initialContent *os.File,
	clock timeutil.Clock) (mc Content, err error) {
	// Find the file's size.
	size, err := initialContent.Seek(0, 2)
	if err != nil {
		err = fmt.Errorf("Seek: %v", err)
		return
	}

	// Create the Content.
	mc = &mutableContent{
		clock:          clock,
		contents:       initialContent,
		dirtyThreshold: size,
	}

	return
}

type mutableContent struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	clock timeutil.Clock

	/////////////////////////
	// Mutable state
	/////////////////////////

	destroyed bool

	// Have we been dirtied since we were created?
	dirty bool

	// A file containing our current contents.
	contents *os.File

	// The lowest byte index that has been modified from the initial contents.
	//
	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	dirtyThreshold int64

	// The time at which a method that modifies our contents was last called, or
	// nil if never.
	//
	// INVARIANT: dirty => mtime != nil
	mtime *time.Time
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (mc *mutableContent) CheckInvariants() {
	if mc.destroyed {
		panic("Use of destroyed mutableContent object.")
	}

	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	if !mc.dirty {
		sr, err := mc.Stat(context.Background())
		if err != nil {
			panic(fmt.Sprintf("Stat: %v", err))
		}

		if sr.DirtyThreshold != sr.Size {
			panic(fmt.Sprintf("Mismatch: %d vs. %d", sr.DirtyThreshold, sr.Size))
		}
	}

	// INVARIANT: dirty => mtime != nil
	if mc.dirty && mc.mtime == nil {
		panic("Expected a non-nil mtime")
	}
}

func (mc *mutableContent) Destroy() {
	mc.destroyed = true

	if mc.initialContent != nil {
		mc.initialContent.Destroy()
		mc.initialContent = nil
	}

	if mc.readWriteLease != nil {
		mc.readWriteLease.Downgrade().Revoke()
		mc.readWriteLease = nil
	}
}

func (mc *mutableContent) Release() (rwl lease.ReadWriteLease) {
	if !mc.dirty() {
		return
	}

	rwl = mc.readWriteLease
	mc.readWriteLease = nil
	mc.Destroy()

	return
}

func (mc *mutableContent) ReadAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Serve from the appropriate place.
	if mc.dirty() {
		n, err = mc.readWriteLease.ReadAt(buf, offset)
	} else {
		n, err = mc.initialContent.ReadAt(ctx, buf, offset)
	}

	return
}

func (mc *mutableContent) Stat(
	ctx context.Context) (sr StatResult, err error) {
	sr.DirtyThreshold = mc.dirtyThreshold
	sr.Mtime = mc.mtime

	// Get the size from the appropriate place.
	if mc.dirty() {
		sr.Size, err = mc.readWriteLease.Size()
		if err != nil {
			return
		}
	} else {
		sr.Size = mc.initialContent.Size()
	}

	return
}

func (mc *mutableContent) WriteAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Make sure we have a read/write lease.
	if err = mc.ensureReadWriteLease(ctx); err != nil {
		err = fmt.Errorf("ensureReadWriteLease: %v", err)
		return
	}

	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, offset)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	n, err = mc.readWriteLease.WriteAt(buf, offset)

	return
}

func (mc *mutableContent) Truncate(
	ctx context.Context,
	n int64) (err error) {
	// Make sure we have a read/write lease.
	if err = mc.ensureReadWriteLease(ctx); err != nil {
		err = fmt.Errorf("ensureReadWriteLease: %v", err)
		return
	}

	// Convert to signed, which is what lease.ReadWriteLease wants.
	if n > math.MaxInt64 {
		err = fmt.Errorf("Illegal offset: %v", n)
		return
	}

	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, n)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	err = mc.readWriteLease.Truncate(int64(n))

	return
}

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

func minInt64(a int64, b int64) int64 {
	if a < b {
		return a
	}

	return b
}

// Ensure that mc.readWriteLease is non-nil with an authoritative view of mc's
// contents.
func (mc *mutableContent) ensureReadWriteLease(
	ctx context.Context) (err error) {
	// Is there anything to do?
	if mc.readWriteLease != nil {
		return
	}

	// Set up the read/write lease.
	rwl, err := mc.initialContent.Upgrade(ctx)
	if err != nil {
		err = fmt.Errorf("initialContent.Upgrade: %v", err)
		return
	}

	mc.readWriteLease = rwl
	mc.initialContent = nil

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

package mutable

import (
	"fmt"
	"math"
	"os"
	"time"

	"github.com/jacobsa/timeutil"
	"golang.org/x/net/context"
)

// A mutable view on some content. Created with an initial read-only view,
// which then can be modified by the user and read back. Keeps track of which
// portion of the content has been dirtied.
//
// External synchronization is required.
type Content interface {
	// Panic if any internal invariants are violated.
	CheckInvariants()

	// Destroy any state used by the object, putting it into an indeterminate
	// state. The object must not be used again.
	Destroy()

	// Read part of the content, with semantics equivalent to io.ReaderAt aside
	// from context support.
	ReadAt(ctx context.Context, buf []byte, offset int64) (n int, err error)

	// Return information about the current state of the content.
	Stat(ctx context.Context) (sr StatResult, err error)

	// Write into the content, with semantics equivalent to io.WriterAt aside from
	// context support.
	WriteAt(ctx context.Context, buf []byte, offset int64) (n int, err error)

	// Truncate our the content to the given number of bytes, extending if n is
	// greater than the current size.
	Truncate(ctx context.Context, n int64) (err error)
}

type StatResult struct {
	// The current size in bytes of the content.
	Size int64

	// It is guaranteed that all bytes in the range [0, DirtyThreshold) are
	// unmodified from the original content with which the mutable content object
	// was created.
	DirtyThreshold int64

	// The time at which the content was last updated, or nil if we've never
	// changed it.
	Mtime *time.Time
}

// Create a mutable content object whose initial contents are given by the
// supplied file. The caller must ensure that the file is not used further by
// anybody else.
func NewContent(
	initialContent *os.File,
	clock timeutil.Clock) (mc Content, err error) {
	// Find the file's size.
	size, err := initialContent.Seek(0, 2)
	if err != nil {
		err = fmt.Errorf("Seek: %v", err)
		return
	}

	// Create the Content.
	mc = &mutableContent{
		clock:          clock,
		contents:       initialContent,
		dirtyThreshold: size,
	}

	return
}

type mutableContent struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	clock timeutil.Clock

	/////////////////////////
	// Mutable state
	/////////////////////////

	destroyed bool

	// Have we been dirtied since we were created?
	dirty bool

	// A file containing our current contents.
	contents *os.File

	// The lowest byte index that has been modified from the initial contents.
	//
	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	dirtyThreshold int64

	// The time at which a method that modifies our contents was last called, or
	// nil if never.
	//
	// INVARIANT: dirty => mtime != nil
	mtime *time.Time
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (mc *mutableContent) CheckInvariants() {
	if mc.destroyed {
		panic("Use of destroyed mutableContent object.")
	}

	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	if !mc.dirty {
		sr, err := mc.Stat(context.Background())
		if err != nil {
			panic(fmt.Sprintf("Stat: %v", err))
		}

		if sr.DirtyThreshold != sr.Size {
			panic(fmt.Sprintf("Mismatch: %d vs. %d", sr.DirtyThreshold, sr.Size))
		}
	}

	// INVARIANT: dirty => mtime != nil
	if mc.dirty && mc.mtime == nil {
		panic("Expected a non-nil mtime")
	}
}

func (mc *mutableContent) Destroy() {
	mc.destroyed = true

	// Throw away the file.
	mc.contents.Close()
	mc.contents = nil
}

func (mc *mutableContent) Release() (rwl lease.ReadWriteLease) {
	if !mc.dirty() {
		return
	}

	rwl = mc.readWriteLease
	mc.readWriteLease = nil
	mc.Destroy()

	return
}

func (mc *mutableContent) ReadAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Serve from the appropriate place.
	if mc.dirty() {
		n, err = mc.readWriteLease.ReadAt(buf, offset)
	} else {
		n, err = mc.initialContent.ReadAt(ctx, buf, offset)
	}

	return
}

func (mc *mutableContent) Stat(
	ctx context.Context) (sr StatResult, err error) {
	sr.DirtyThreshold = mc.dirtyThreshold
	sr.Mtime = mc.mtime

	// Get the size from the appropriate place.
	if mc.dirty() {
		sr.Size, err = mc.readWriteLease.Size()
		if err != nil {
			return
		}
	} else {
		sr.Size = mc.initialContent.Size()
	}

	return
}

func (mc *mutableContent) WriteAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Make sure we have a read/write lease.
	if err = mc.ensureReadWriteLease(ctx); err != nil {
		err = fmt.Errorf("ensureReadWriteLease: %v", err)
		return
	}

	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, offset)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	n, err = mc.readWriteLease.WriteAt(buf, offset)

	return
}

func (mc *mutableContent) Truncate(
	ctx context.Context,
	n int64) (err error) {
	// Make sure we have a read/write lease.
	if err = mc.ensureReadWriteLease(ctx); err != nil {
		err = fmt.Errorf("ensureReadWriteLease: %v", err)
		return
	}

	// Convert to signed, which is what lease.ReadWriteLease wants.
	if n > math.MaxInt64 {
		err = fmt.Errorf("Illegal offset: %v", n)
		return
	}

	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, n)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	err = mc.readWriteLease.Truncate(int64(n))

	return
}

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

func minInt64(a int64, b int64) int64 {
	if a < b {
		return a
	}

	return b
}

// Ensure that mc.readWriteLease is non-nil with an authoritative view of mc's
// contents.
func (mc *mutableContent) ensureReadWriteLease(
	ctx context.Context) (err error) {
	// Is there anything to do?
	if mc.readWriteLease != nil {
		return
	}

	// Set up the read/write lease.
	rwl, err := mc.initialContent.Upgrade(ctx)
	if err != nil {
		err = fmt.Errorf("initialContent.Upgrade: %v", err)
		return
	}

	mc.readWriteLease = rwl
	mc.initialContent = nil

	return
}
<|endoftext|>


<commit_before>// Copyright 2017 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package git

import (
	"context"
	"fmt"
	"testing"

	"github.com/keybase/client/go/externals"
	"github.com/keybase/client/go/kbfs"
	"github.com/keybase/client/go/kbtest"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/client/go/teams"
	"github.com/stretchr/testify/require"
)

// Copied from the teams tests.
func SetupTest(tb testing.TB, name string, depth int) (tc libkb.TestContext) {
	tc = libkb.SetupTest(tb, name, depth+1)
	tc.G.SetServices(externals.GetServices())
	teams.NewTeamLoaderAndInstall(tc.G)
	return tc
}

func doPut(t *testing.T, g *libkb.GlobalContext, teamName string, repoID string, repoName string) {
	err := PutMetadata(context.Background(), g, keybase1.PutGitMetadataArg{
		Folder: keybase1.Folder{
			Name:       teamName,
			Private:    true,
			FolderType: keybase1.FolderType_TEAM,
		},
		RepoID: keybase1.RepoID(repoID),
		Metadata: keybase1.GitLocalMetadata{
			RepoName: keybase1.GitRepoName(repoName),
		},
	})
	require.NoError(t, err)
}

func TestPutAndGet(t *testing.T) {
	tc := SetupTest(t, "team", 1)
	defer tc.Cleanup()

	// Note that the length limit for a team name, with the additional suffix
	// below, is 16 characters. We have 5 to play with, including the implicit
	// underscore after the prefix.
	u, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)

	// Create two teams, so that we can test filtering by TeamID.
	teamName1 := u.Username + "t1"
	err = teams.CreateRootTeam(context.Background(), tc.G, teamName1, keybase1.TeamSettings{})
	require.NoError(t, err)
	team1, err := tc.G.GetTeamLoader().Load(context.Background(), keybase1.LoadTeamArg{Name: teamName1})
	require.NoError(t, err)

	teamName2 := u.Username + "t2"
	err = teams.CreateRootTeam(context.Background(), tc.G, teamName2, keybase1.TeamSettings{})
	require.NoError(t, err)

	// Create two git repos, one in each team. Remember that all we're
	// "creating" here is metadata.
	doPut(t, tc.G, teamName1, "abc123", "repoNameFirst")
	doPut(t, tc.G, teamName2, "def456", "repoNameSecond")
	expectedIDNames := map[string]string{
		"abc123": "repoNameFirst",
		"def456": "repoNameSecond",
	}

	// Get all repos, and make sure both come back.
	allRepos, err := GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 2, len(allRepos), "expected to get both repos back, found: %d", len(allRepos))
	for _, repo := range allRepos {
		require.Equal(t, expectedIDNames[string(repo.RepoID)], string(repo.LocalMetadata.RepoName))
		require.Equal(t, repo.Folder.FolderType, keybase1.FolderType_TEAM)
		require.Equal(t, repo.Folder.Private, true)
		require.Equal(t, repo.ServerMetadata.LastModifyingUsername, u.Username)
		require.Equal(t, repo.CanDelete, true)
	}

	// Now get the repos for just one team. Should be only one of the two we just created.
	oneRepoList, err := GetMetadata(context.Background(), tc.G, keybase1.Folder{
		Name:       teamName1,
		Private:    true,
		FolderType: keybase1.FolderType_TEAM,
	})
	require.NoError(t, err)
	require.Equal(t, 1, len(oneRepoList), "expected to get only one repo back, found: %d", len(oneRepoList))
	oneRepo := oneRepoList[0]
	require.Equal(t, "repoNameFirst", string(oneRepo.LocalMetadata.RepoName))
	require.Equal(t, kbtest.DefaultDeviceName, oneRepo.ServerMetadata.LastModifyingDeviceName)
	require.Equal(t, string(team1.Chain.Id+"_abc123"), oneRepo.GlobalUniqueID)
	require.Equal(t, "keybase://team/"+teamName1+"/repoNameFirst", oneRepo.RepoUrl)
	require.Equal(t, oneRepo.CanDelete, true)
}

func TestDeleteRepo(t *testing.T) {
	tc := SetupTest(t, "team", 1)
	defer tc.Cleanup()

	// Note that the length limit for a team name, with the additional suffix
	// below, is 16 characters. We have 5 to play with, including the implicit
	// underscore after the prefix.
	u, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)

	// Create a personal repo for this user. (This also exercises the
	// personal-repos-create-an-implicit-team flow.)
	repoName := keybase1.GitRepoName("testRepo123")
	folder := keybase1.Folder{
		Name:       u.Username,
		Private:    true,
		FolderType: keybase1.FolderType_PRIVATE,
	}
	err = PutMetadata(context.Background(), tc.G, keybase1.PutGitMetadataArg{
		Folder: folder,
		RepoID: keybase1.RepoID("abc123"),
		Metadata: keybase1.GitLocalMetadata{
			RepoName: repoName,
		},
	})
	require.NoError(t, err)

	// Get all repos, and make sure we see that one.
	allRepos, err := GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 1, len(allRepos), "expected to see 1 git repo, saw %d", len(allRepos))
	require.Equal(t, repoName, allRepos[0].LocalMetadata.RepoName)

	// Now delete that repo.
	err = DeleteMetadata(context.Background(), tc.G, folder, repoName)
	require.NoError(t, err)

	// Finally, get all repos again, and make sure it's gone.
	allRepos, err = GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 0, len(allRepos), "expected the repo to get deleted")
}

func TestPutAndGetImplicitTeam(t *testing.T) {
	testPutAndGetImplicitTeam(t, false)
	testPutAndGetImplicitTeam(t, true)
}
func testPutAndGetImplicitTeam(t *testing.T, public bool) {
	t.Logf("running with public:%v", public)

	folderType := keybase1.FolderType_PRIVATE
	if public {
		folderType = keybase1.FolderType_PUBLIC
	}

	publicnessStr := "private"
	if public {
		publicnessStr = "public"
	}

	tc := SetupTest(t, "team", 1)
	defer tc.Cleanup()

	t.Logf("signup users")
	u1, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)
	u2, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)

	// Create two repos, one in u2's personal directory (since we're signed in
	// as u2 at this point), and one in the directory that u1 and u2 share. One
	// of the things we'll be testing is that the second one does *not* show up
	// in the results of GetAllMetadata. This is a product choice -- we want to
	// pretent they kinda don't exist.

	t.Logf("me only repo")
	repoName1 := keybase1.GitRepoName("me only repo")
	testFolder1 := keybase1.Folder{
		Name:       u2.Username,
		Private:    !public,
		FolderType: folderType,
	}
	err = PutMetadata(context.TODO(), tc.G, keybase1.PutGitMetadataArg{
		Folder: testFolder1,
		RepoID: keybase1.RepoID("abc123"),
		Metadata: keybase1.GitLocalMetadata{
			RepoName: repoName1,
		},
	})
	require.NoError(t, err)

	t.Logf("second repo")
	repoName2 := keybase1.GitRepoName(fmt.Sprintf("two person %s repo", publicnessStr))
	normalizedTLFName, err := kbfs.NormalizeNamesInTLF([]string{u1.Username, u2.Username}, nil, "")
	require.NoError(t, err)
	testFolder2 := keybase1.Folder{
		Name:       normalizedTLFName,
		Private:    !public,
		FolderType: folderType,
	}
	err = PutMetadata(context.Background(), tc.G, keybase1.PutGitMetadataArg{
		Folder: testFolder2,
		RepoID: keybase1.RepoID("abc123"),
		Metadata: keybase1.GitLocalMetadata{
			RepoName: repoName2,
		},
	})
	require.NoError(t, err)

	// Now make sure we can query these repos (or not, as appropriate for the
	// multi-person case).

	assertStuffAboutRepo := func(t *testing.T, repo keybase1.GitRepoResult, folder keybase1.Folder, repoName keybase1.GitRepoName) {
		require.Equal(t, repoName, repo.LocalMetadata.RepoName)
		require.Equal(t, folderType, repo.Folder.FolderType)
		require.Equal(t, !public, repo.Folder.Private)
		require.Equal(t, kbtest.DefaultDeviceName, repo.ServerMetadata.LastModifyingDeviceName)
		require.Equal(t, "keybase://"+publicnessStr+"/"+folder.Name+"/"+string(repoName), repo.RepoUrl)
		require.Equal(t, repo.CanDelete, true)
	}

	t.Logf("assertions")
	// Test fetching the repo with GetMetadata.
	oneRepo, err := GetMetadata(context.Background(), tc.G, testFolder1)
	require.NoError(t, err)
	require.Equal(t, 1, len(oneRepo))
	assertStuffAboutRepo(t, oneRepo[0], testFolder1, repoName1)

	// Also test fetching the 2-person repo with GetMetadata. This
	// *should* work, even though it's hidden from GetAllMetadata.
	oneRepo, err = GetMetadata(context.Background(), tc.G, testFolder2)
	require.NoError(t, err)
	require.Equal(t, 1, len(oneRepo))
	assertStuffAboutRepo(t, oneRepo[0], testFolder2, repoName2)

	// Finally test the GetAllMetadata results. This should *not* see the
	// 2-person repo.
	allRepos, err := GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 1, len(allRepos), "the two-person repo should be hidden!")
	assertStuffAboutRepo(t, allRepos[0], testFolder1, repoName1)
}

func TestPutAndGetWritersCantDelete(t *testing.T) {
	tc := SetupTest(t, "team", 1)
	defer tc.Cleanup()

	u1, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)
	u2, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)

	// u2 creates a team where u1 is just a writer
	teamName := u2.Username + "t1"
	err = teams.CreateRootTeam(context.Background(), tc.G, teamName, keybase1.TeamSettings{})
	require.NoError(t, err)
	_, err = teams.AddMember(context.Background(), tc.G, teamName, u1.Username, keybase1.TeamRole_WRITER)
	if err != nil {
		t.Fatal(err)
	}

	// Create a git repo for that team
	doPut(t, tc.G, teamName, "abc123", "dummyRepoName")

	// Load the repo and confirm that u2 sees it as CanDelete=true.
	repos, err := GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 1, len(repos), "expected exactly one repo")
	require.Equal(t, true, repos[0].CanDelete, "owners/admins should be able to delete")

	// Now log in as u1, load the repo again, and confirm that u1 sees it as CanDelete=FALSE.
	err = tc.G.Logout()
	require.NoError(t, err)
	err = u1.Login(tc.G)
	require.NoError(t, err)
	repos2, err := GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 1, len(repos2), "expected exactly one repo")
	require.Equal(t, false, repos2[0].CanDelete, "non-admins must not be able to delete")
}
<commit_msg>fix test<commit_after>// Copyright 2017 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package git

import (
	"context"
	"fmt"
	"testing"

	"github.com/keybase/client/go/externals"
	"github.com/keybase/client/go/kbfs"
	"github.com/keybase/client/go/kbtest"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/client/go/teams"
	"github.com/stretchr/testify/require"
)

// Copied from the teams tests.
func SetupTest(tb testing.TB, name string, depth int) (tc libkb.TestContext) {
	tc = libkb.SetupTest(tb, name, depth+1)
	tc.G.SetServices(externals.GetServices())
	teams.NewTeamLoaderAndInstall(tc.G)
	return tc
}

func doPut(t *testing.T, g *libkb.GlobalContext, teamName string, repoID string, repoName string) {
	err := PutMetadata(context.Background(), g, keybase1.PutGitMetadataArg{
		Folder: keybase1.Folder{
			Name:       teamName,
			Private:    true,
			FolderType: keybase1.FolderType_TEAM,
		},
		RepoID: keybase1.RepoID(repoID),
		Metadata: keybase1.GitLocalMetadata{
			RepoName: keybase1.GitRepoName(repoName),
		},
	})
	require.NoError(t, err)
}

func TestPutAndGet(t *testing.T) {
	tc := SetupTest(t, "team", 1)
	defer tc.Cleanup()

	// Note that the length limit for a team name, with the additional suffix
	// below, is 16 characters. We have 5 to play with, including the implicit
	// underscore after the prefix.
	u, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)

	// Create two teams, so that we can test filtering by TeamID.
	teamName1 := u.Username + "t1"
	err = teams.CreateRootTeam(context.Background(), tc.G, teamName1, keybase1.TeamSettings{})
	require.NoError(t, err)
	team1, err := tc.G.GetTeamLoader().Load(context.Background(), keybase1.LoadTeamArg{Name: teamName1})
	require.NoError(t, err)

	teamName2 := u.Username + "t2"
	err = teams.CreateRootTeam(context.Background(), tc.G, teamName2, keybase1.TeamSettings{})
	require.NoError(t, err)

	// Create two git repos, one in each team. Remember that all we're
	// "creating" here is metadata.
	doPut(t, tc.G, teamName1, "abc123", "repoNameFirst")
	doPut(t, tc.G, teamName2, "def456", "repoNameSecond")
	expectedIDNames := map[string]string{
		"abc123": "repoNameFirst",
		"def456": "repoNameSecond",
	}

	// Get all repos, and make sure both come back.
	allRepos, err := GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 2, len(allRepos), "expected to get both repos back, found: %d", len(allRepos))
	for _, repoRes := range allRepos {
		repo, err := repoRes.GetIfOk()
		require.NoError(t, err)
		require.Equal(t, expectedIDNames[string(repo.RepoID)], string(repo.LocalMetadata.RepoName))
		require.Equal(t, repo.Folder.FolderType, keybase1.FolderType_TEAM)
		require.Equal(t, repo.Folder.Private, true)
		require.Equal(t, repo.ServerMetadata.LastModifyingUsername, u.Username)
		require.Equal(t, repo.CanDelete, true)
	}

	// Now get the repos for just one team. Should be only one of the two we just created.
	oneRepoList, err := GetMetadata(context.Background(), tc.G, keybase1.Folder{
		Name:       teamName1,
		Private:    true,
		FolderType: keybase1.FolderType_TEAM,
	})
	require.NoError(t, err)
	require.Equal(t, 1, len(oneRepoList), "expected to get only one repo back, found: %d", len(oneRepoList))
	oneRepo, err := oneRepoList[0].GetIfOk()
	require.NoError(t, err)
	require.Equal(t, "repoNameFirst", string(oneRepo.LocalMetadata.RepoName))
	require.Equal(t, kbtest.DefaultDeviceName, oneRepo.ServerMetadata.LastModifyingDeviceName)
	require.Equal(t, string(team1.Chain.Id+"_abc123"), oneRepo.GlobalUniqueID)
	require.Equal(t, "keybase://team/"+teamName1+"/repoNameFirst", oneRepo.RepoUrl)
	require.Equal(t, oneRepo.CanDelete, true)
}

func TestDeleteRepo(t *testing.T) {
	tc := SetupTest(t, "team", 1)
	defer tc.Cleanup()

	// Note that the length limit for a team name, with the additional suffix
	// below, is 16 characters. We have 5 to play with, including the implicit
	// underscore after the prefix.
	u, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)

	// Create a personal repo for this user. (This also exercises the
	// personal-repos-create-an-implicit-team flow.)
	repoName := keybase1.GitRepoName("testRepo123")
	folder := keybase1.Folder{
		Name:       u.Username,
		Private:    true,
		FolderType: keybase1.FolderType_PRIVATE,
	}
	err = PutMetadata(context.Background(), tc.G, keybase1.PutGitMetadataArg{
		Folder: folder,
		RepoID: keybase1.RepoID("abc123"),
		Metadata: keybase1.GitLocalMetadata{
			RepoName: repoName,
		},
	})
	require.NoError(t, err)

	// Get all repos, and make sure we see that one.
	allRepos, err := GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 1, len(allRepos), "expected to see 1 git repo, saw %d", len(allRepos))
	firstRepo, err := allRepos[0].GetIfOk()
	require.NoError(t, err)
	require.Equal(t, repoName, firstRepo.LocalMetadata.RepoName)

	// Now delete that repo.
	err = DeleteMetadata(context.Background(), tc.G, folder, repoName)
	require.NoError(t, err)

	// Finally, get all repos again, and make sure it's gone.
	allRepos, err = GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 0, len(allRepos), "expected the repo to get deleted")
}

func TestPutAndGetImplicitTeam(t *testing.T) {
	testPutAndGetImplicitTeam(t, false)
	testPutAndGetImplicitTeam(t, true)
}
func testPutAndGetImplicitTeam(t *testing.T, public bool) {
	t.Logf("running with public:%v", public)

	folderType := keybase1.FolderType_PRIVATE
	if public {
		folderType = keybase1.FolderType_PUBLIC
	}

	publicnessStr := "private"
	if public {
		publicnessStr = "public"
	}

	tc := SetupTest(t, "team", 1)
	defer tc.Cleanup()

	t.Logf("signup users")
	u1, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)
	u2, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)

	// Create two repos, one in u2's personal directory (since we're signed in
	// as u2 at this point), and one in the directory that u1 and u2 share. One
	// of the things we'll be testing is that the second one does *not* show up
	// in the results of GetAllMetadata. This is a product choice -- we want to
	// pretent they kinda don't exist.

	t.Logf("me only repo")
	repoName1 := keybase1.GitRepoName("me only repo")
	testFolder1 := keybase1.Folder{
		Name:       u2.Username,
		Private:    !public,
		FolderType: folderType,
	}
	err = PutMetadata(context.TODO(), tc.G, keybase1.PutGitMetadataArg{
		Folder: testFolder1,
		RepoID: keybase1.RepoID("abc123"),
		Metadata: keybase1.GitLocalMetadata{
			RepoName: repoName1,
		},
	})
	require.NoError(t, err)

	t.Logf("second repo")
	repoName2 := keybase1.GitRepoName(fmt.Sprintf("two person %s repo", publicnessStr))
	normalizedTLFName, err := kbfs.NormalizeNamesInTLF([]string{u1.Username, u2.Username}, nil, "")
	require.NoError(t, err)
	testFolder2 := keybase1.Folder{
		Name:       normalizedTLFName,
		Private:    !public,
		FolderType: folderType,
	}
	err = PutMetadata(context.Background(), tc.G, keybase1.PutGitMetadataArg{
		Folder: testFolder2,
		RepoID: keybase1.RepoID("abc123"),
		Metadata: keybase1.GitLocalMetadata{
			RepoName: repoName2,
		},
	})
	require.NoError(t, err)

	// Now make sure we can query these repos (or not, as appropriate for the
	// multi-person case).

	assertStuffAboutRepo := func(t *testing.T, repoRes keybase1.GitRepoResult, folder keybase1.Folder, repoName keybase1.GitRepoName) {
		repo, err := repoRes.GetIfOk()
		require.NoError(t, err)
		require.Equal(t, repoName, repo.LocalMetadata.RepoName)
		require.Equal(t, folderType, repo.Folder.FolderType)
		require.Equal(t, !public, repo.Folder.Private)
		require.Equal(t, kbtest.DefaultDeviceName, repo.ServerMetadata.LastModifyingDeviceName)
		require.Equal(t, "keybase://"+publicnessStr+"/"+folder.Name+"/"+string(repoName), repo.RepoUrl)
		require.Equal(t, repo.CanDelete, true)
	}

	t.Logf("assertions")
	// Test fetching the repo with GetMetadata.
	oneRepo, err := GetMetadata(context.Background(), tc.G, testFolder1)
	require.NoError(t, err)
	require.Equal(t, 1, len(oneRepo))
	assertStuffAboutRepo(t, oneRepo[0], testFolder1, repoName1)

	// Also test fetching the 2-person repo with GetMetadata. This
	// *should* work, even though it's hidden from GetAllMetadata.
	oneRepo, err = GetMetadata(context.Background(), tc.G, testFolder2)
	require.NoError(t, err)
	require.Equal(t, 1, len(oneRepo))
	assertStuffAboutRepo(t, oneRepo[0], testFolder2, repoName2)

	// Finally test the GetAllMetadata results. This should *not* see the
	// 2-person repo.
	allRepos, err := GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 1, len(allRepos), "the two-person repo should be hidden!")
	assertStuffAboutRepo(t, allRepos[0], testFolder1, repoName1)
}

func TestPutAndGetWritersCantDelete(t *testing.T) {
	tc := SetupTest(t, "team", 1)
	defer tc.Cleanup()

	u1, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)
	u2, err := kbtest.CreateAndSignupFakeUser("t", tc.G)
	require.NoError(t, err)

	// u2 creates a team where u1 is just a writer
	teamName := u2.Username + "t1"
	err = teams.CreateRootTeam(context.Background(), tc.G, teamName, keybase1.TeamSettings{})
	require.NoError(t, err)
	_, err = teams.AddMember(context.Background(), tc.G, teamName, u1.Username, keybase1.TeamRole_WRITER)
	if err != nil {
		t.Fatal(err)
	}

	// Create a git repo for that team
	doPut(t, tc.G, teamName, "abc123", "dummyRepoName")

	// Load the repo and confirm that u2 sees it as CanDelete=true.
	repos, err := GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 1, len(repos), "expected exactly one repo")
	firstRepo, err := repos[0].GetIfOk()
	require.NoError(t, err)
	require.Equal(t, true, firstRepo.CanDelete, "owners/admins should be able to delete")

	// Now log in as u1, load the repo again, and confirm that u1 sees it as CanDelete=FALSE.
	err = tc.G.Logout()
	require.NoError(t, err)
	err = u1.Login(tc.G)
	require.NoError(t, err)
	repos2, err := GetAllMetadata(context.Background(), tc.G)
	require.NoError(t, err)
	require.Equal(t, 1, len(repos2), "expected exactly one repo")
	repo2, err := repos2[0].GetIfOk()
	require.NoError(t, err)
	require.Equal(t, false, repo2.CanDelete, "non-admins must not be able to delete")
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

// +build !windows
// socket_nix.go

package libkb

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

	"github.com/keybase/client/go/logger"
)

// Though I've never seen this come up in production, it definitely comes up
// in systests that multiple go-routines might race over the current
// working directory as they do the (chdir && dial) dance below. Make sure
// a lock is held whenever operating on sockets, so that two racing goroutines
// can't conflict here.
var bindLock sync.Mutex

func (s SocketInfo) BindToSocket() (net.Listener, error) {

	// Lock so that multiple goroutines can't race over current working dir.
	// See note above.
	bindLock.Lock()
	defer bindLock.Unlock()

	bindFile := s.bindFile
	if err := MakeParentDirs(bindFile); err != nil {
		return nil, err
	}

	// Path can't be longer than 108 characters.
	// In this case Chdir to the file directory first.
	// https://github.com/golang/go/issues/6895#issuecomment-98006662
	if len(bindFile) >= 108 {

		prevWd, err := os.Getwd()
		if err != nil {
			return nil, fmt.Errorf("Error getting working directory: %s", err)
		}
		s.log.Warning("Changing current working directory because path for binding is too long")
		if err := os.Chdir(filepath.Dir(bindFile)); err != nil {
			return nil, fmt.Errorf("Path can't be longer than 108 characters (failed to chdir): %s", err)
		}
		defer os.Chdir(prevWd)
		bindFile = filepath.Base(bindFile)
	}

	s.log.Info("Binding to unix:%s", bindFile)
	return net.Listen("unix", bindFile)
}

func (s SocketInfo) DialSocket() (net.Conn, error) {
	errs := []error{}
	for _, file := range s.dialFiles {
		ret, err := s.dialSocket(file)
		if err == nil {
			return ret, nil
		}
		errs = append(errs, err)
	}
	return nil, CombineErrors(errs...)
}

func (s SocketInfo) dialSocket(dialFile string) (net.Conn, error) {

	// Lock so that multiple goroutines can't race over current working dir.
	// See note above.
	bindLock.Lock()
	defer bindLock.Unlock()

	if dialFile == "" {
		return nil, fmt.Errorf("Can't dial empty path")
	}
	// Path can't be longer than 108 characters.
	// In this case Chdir to the file directory first.
	// https://github.com/golang/go/issues/6895#issuecomment-98006662
	if len(dialFile) >= 108 {
		prevWd, err := os.Getwd()
		if err != nil {
			return nil, fmt.Errorf("Error getting working directory: %s", err)
		}
		s.log.Warning("Changing current working directory because path for dialing is too long")
		if err := os.Chdir(filepath.Dir(dialFile)); err != nil {
			return nil, fmt.Errorf("Path can't be longer than 108 characters (failed to chdir): %s", err)
		}
		defer os.Chdir(prevWd)
		dialFile = filepath.Base(dialFile)
	}

	s.log.Debug("Dialing unix:%s", dialFile)
	return net.Dial("unix", dialFile)
}

func NewSocket(g *GlobalContext) (ret Socket, err error) {
	var dialFiles []string
	dialFiles, err = g.Env.GetSocketDialFiles()
	if err != nil {
		return
	}
	var bindFile string
	bindFile, err = g.Env.GetSocketBindFile()
	if err != nil {
		return
	}
	log := g.Log
	if log == nil {
		log = logger.NewNull()
	}
	ret = SocketInfo{
		log:       log,
		dialFiles: dialFiles,
		bindFile:  bindFile,
	}
	return
}

func NewSocketWithFiles(
	log logger.Logger, bindFile string, dialFiles []string) Socket {
	return SocketInfo{
		log:       log,
		bindFile:  bindFile,
		dialFiles: dialFiles,
	}
}

// net.errClosing isn't exported, so do this.. UGLY!
func IsSocketClosedError(e error) bool {
	return strings.HasSuffix(e.Error(), "use of closed network connection")
}
<commit_msg>lower socket hack limit<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

// +build !windows
// socket_nix.go

package libkb

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

	"github.com/keybase/client/go/logger"
)

// Though I've never seen this come up in production, it definitely comes up
// in systests that multiple go-routines might race over the current
// working directory as they do the (chdir && dial) dance below. Make sure
// a lock is held whenever operating on sockets, so that two racing goroutines
// can't conflict here.
var bindLock sync.Mutex

func (s SocketInfo) BindToSocket() (net.Listener, error) {

	// Lock so that multiple goroutines can't race over current working dir.
	// See note above.
	bindLock.Lock()
	defer bindLock.Unlock()

	bindFile := s.bindFile
	if err := MakeParentDirs(bindFile); err != nil {
		return nil, err
	}

	// Path can't be longer than N characters.
	// In this case Chdir to the file directory first and use a local path.
	// On many linuxes, N=108, on some N=106, and on macOS N=104.
	// N=104 is the lowest I know of.
	// It's the length of the path buffer in sockaddr_un.
	// And there may be a null-terminator in there, not sure, so make it 103 for good luck.
	// https://github.com/golang/go/issues/6895#issuecomment-98006662
	// https://gist.github.com/mlsteele/16dc5b6eb3d112b914183928c9af71b8#file-un-h-L79
	// We could always Chdir, but then this function would be non-threadsafe more of the time.
	// Pick your poison.
	if len(bindFile) >= 103 {
		prevWd, err := os.Getwd()
		if err != nil {
			return nil, fmt.Errorf("Error getting working directory: %s", err)
		}
		s.log.Warning("Changing current working directory because path for binding is too long")
		if err := os.Chdir(filepath.Dir(bindFile)); err != nil {
			return nil, fmt.Errorf("Path can't be longer than 108 characters (failed to chdir): %s", err)
		}
		defer os.Chdir(prevWd)
		bindFile = filepath.Base(bindFile)
	}

	s.log.Info("Binding to unix:%s", bindFile)
	return net.Listen("unix", bindFile)
}

func (s SocketInfo) DialSocket() (net.Conn, error) {
	errs := []error{}
	for _, file := range s.dialFiles {
		ret, err := s.dialSocket(file)
		if err == nil {
			return ret, nil
		}
		errs = append(errs, err)
	}
	return nil, CombineErrors(errs...)
}

func (s SocketInfo) dialSocket(dialFile string) (net.Conn, error) {

	// Lock so that multiple goroutines can't race over current working dir.
	// See note above.
	bindLock.Lock()
	defer bindLock.Unlock()

	if dialFile == "" {
		return nil, fmt.Errorf("Can't dial empty path")
	}
	// Path can't be longer than 108 characters.
	// In this case Chdir to the file directory first.
	// https://github.com/golang/go/issues/6895#issuecomment-98006662
	if len(dialFile) >= 108 {
		prevWd, err := os.Getwd()
		if err != nil {
			return nil, fmt.Errorf("Error getting working directory: %s", err)
		}
		s.log.Warning("Changing current working directory because path for dialing is too long")
		if err := os.Chdir(filepath.Dir(dialFile)); err != nil {
			return nil, fmt.Errorf("Path can't be longer than 108 characters (failed to chdir): %s", err)
		}
		defer os.Chdir(prevWd)
		dialFile = filepath.Base(dialFile)
	}

	s.log.Debug("Dialing unix:%s", dialFile)
	return net.Dial("unix", dialFile)
}

func NewSocket(g *GlobalContext) (ret Socket, err error) {
	var dialFiles []string
	dialFiles, err = g.Env.GetSocketDialFiles()
	if err != nil {
		return
	}
	var bindFile string
	bindFile, err = g.Env.GetSocketBindFile()
	if err != nil {
		return
	}
	log := g.Log
	if log == nil {
		log = logger.NewNull()
	}
	ret = SocketInfo{
		log:       log,
		dialFiles: dialFiles,
		bindFile:  bindFile,
	}
	return
}

func NewSocketWithFiles(
	log logger.Logger, bindFile string, dialFiles []string) Socket {
	return SocketInfo{
		log:       log,
		bindFile:  bindFile,
		dialFiles: dialFiles,
	}
}

// net.errClosing isn't exported, so do this.. UGLY!
func IsSocketClosedError(e error) bool {
	return strings.HasSuffix(e.Error(), "use of closed network connection")
}
<|endoftext|>