<commit_before>package services

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"time"

	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/log"
)

// HTTPClient will be set to a default httpclient if not set
var HTTPClient sdk.HTTPClient

// DoJSONRequest performs an http request on a service
func DoJSONRequest(srvs []sdk.Service, method, path string, in interface{}, out interface{}, mods ...sdk.RequestModifier) (int, error) {
	var lastErr error
	var lastCode int
	var attempt int
	for {
		attempt++
		for i := range srvs {
			srv := &srvs[i]
			code, err := doJSONRequest(srv, method, path, in, out, mods...)
			if err == nil {
				return code, nil
			}
			lastErr = err
			lastCode = code
		}
		if lastErr != nil || attempt > 5 {
			break
		}
	}
	return lastCode, lastErr
}

// DoJSONRequest performs an http request on service
func doJSONRequest(srv *sdk.Service, method, path string, in interface{}, out interface{}, mods ...sdk.RequestModifier) (int, error) {
	var b = []byte{}
	var err error

	if in != nil {
		b, err = json.Marshal(in)
		if err != nil {
			return 0, sdk.WrapError(err, "services.doJSONRequest> Unable to marshal input")
		}
	}

	mods = append(mods, sdk.SetHeader("Content-Type", "application/json"))
	res, code, err := DoRequest(srv, method, path, b, mods...)
	if err != nil {
		return code, sdk.WrapError(err, "services.doJSONRequest> Unable to perform request")
	}

	if out != nil {
		if err := json.Unmarshal(res, out); err != nil {
			return code, sdk.WrapError(err, "services.doJSONRequest> Unable to marshal output")
		}
	}

	return code, nil
}

// PostMultipart post a file content through multipart upload
func PostMultipart(srvs []sdk.Service, path string, filename string, fileContents []byte, out interface{}, mods ...sdk.RequestModifier) (int, error) {
	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile("file", filename)
	if err != nil {
		return 0, err
	}
	part.Write(fileContents)
	if err := writer.Close(); err != nil {
		return 0, err
	}

	mods = append(mods, sdk.SetHeader("Content-Type", "multipart/form-data"))

	var lastErr error
	var lastCode int
	var attempt int
	for {
		attempt++
		for i := range srvs {
			srv := &srvs[i]
			res, code, err := DoRequest(srv, "POST", path, body.Bytes(), mods...)
			lastCode = code
			lastErr = err

			if err == nil {
				return code, nil
			}

			if out != nil {
				if err := json.Unmarshal(res, out); err != nil {
					return code, err
				}
			}
		}
		if lastErr == nil {
			break
		}
	}
	return lastCode, lastErr
}

// DoRequest performs an http request on service
func DoRequest(srv *sdk.Service, method, path string, args []byte, mods ...sdk.RequestModifier) ([]byte, int, error) {
	if HTTPClient == nil {
		HTTPClient = &http.Client{
			Timeout: 60 * time.Second,
		}
	}

	var requestError error
	var req *http.Request
	if args != nil {
		req, requestError = http.NewRequest(method, srv.HTTPURL+path, bytes.NewReader(args))
	} else {
		req, requestError = http.NewRequest(method, srv.HTTPURL+path, nil)
	}
	if requestError != nil {
		return nil, 0, requestError
	}

	req.Header.Set("Connection", "close")
	req.Header.Add(sdk.RequestedWithHeader, sdk.RequestedWithValue)
	for i := range mods {
		if mods[i] != nil {
			mods[i](req)
		}
	}

	// Authentify the request with the hash
	basedHash := base64.StdEncoding.EncodeToString([]byte(srv.Hash))
	req.Header.Set(sdk.AuthHeader, basedHash)

	log.Debug("services.DoRequest> request: %s", req.URL.String())

	//Do the request
	resp, errDo := HTTPClient.Do(req)
	if errDo != nil {
		return nil, 0, sdk.WrapError(errDo, "services.DoRequest> Request failed")
	}
	defer resp.Body.Close()

	// Read the body
	body, errBody := ioutil.ReadAll(resp.Body)
	if errBody != nil {
		return nil, resp.StatusCode, sdk.WrapError(errBody, "services.DoRequest> Unable to read body")
	}

	log.Debug("services.DoRequest> response: %s", string(body))

	// if everything is fine, return body
	if resp.StatusCode < 500 {
		return body, resp.StatusCode, nil
	}

	// Try to catch the CDS Error
	if cdserr := sdk.DecodeError(body); cdserr != nil {
		return nil, resp.StatusCode, cdserr
	}

	return nil, resp.StatusCode, fmt.Errorf("Request Failed")
}
<commit_msg>fix (api): services.DoRequest error (#1609)<commit_after>package services

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"time"

	"github.com/ovh/cds/sdk"
	"github.com/ovh/cds/sdk/log"
)

// HTTPClient will be set to a default httpclient if not set
var HTTPClient sdk.HTTPClient

// DoJSONRequest performs an http request on a service
func DoJSONRequest(srvs []sdk.Service, method, path string, in interface{}, out interface{}, mods ...sdk.RequestModifier) (int, error) {
	var lastErr error
	var lastCode int
	var attempt int
	for {
		attempt++
		for i := range srvs {
			srv := &srvs[i]
			code, err := doJSONRequest(srv, method, path, in, out, mods...)
			if err == nil {
				return code, nil
			}
			lastErr = err
			lastCode = code
		}
		if lastErr != nil || attempt > 5 {
			break
		}
	}
	return lastCode, lastErr
}

// DoJSONRequest performs an http request on service
func doJSONRequest(srv *sdk.Service, method, path string, in interface{}, out interface{}, mods ...sdk.RequestModifier) (int, error) {
	var b = []byte{}
	var err error

	if in != nil {
		b, err = json.Marshal(in)
		if err != nil {
			return 0, sdk.WrapError(err, "services.doJSONRequest> Unable to marshal input")
		}
	}

	mods = append(mods, sdk.SetHeader("Content-Type", "application/json"))
	res, code, err := DoRequest(srv, method, path, b, mods...)
	if err != nil {
		return code, sdk.WrapError(err, "services.doJSONRequest> Unable to perform request")
	}

	if out != nil {
		if err := json.Unmarshal(res, out); err != nil {
			return code, sdk.WrapError(err, "services.doJSONRequest> Unable to marshal output")
		}
	}

	return code, nil
}

// PostMultipart post a file content through multipart upload
func PostMultipart(srvs []sdk.Service, path string, filename string, fileContents []byte, out interface{}, mods ...sdk.RequestModifier) (int, error) {
	body := new(bytes.Buffer)
	writer := multipart.NewWriter(body)
	part, err := writer.CreateFormFile("file", filename)
	if err != nil {
		return 0, err
	}
	part.Write(fileContents)
	if err := writer.Close(); err != nil {
		return 0, err
	}

	mods = append(mods, sdk.SetHeader("Content-Type", "multipart/form-data"))

	var lastErr error
	var lastCode int
	var attempt int
	for {
		attempt++
		for i := range srvs {
			srv := &srvs[i]
			res, code, err := DoRequest(srv, "POST", path, body.Bytes(), mods...)
			lastCode = code
			lastErr = err

			if err == nil {
				return code, nil
			}

			if out != nil {
				if err := json.Unmarshal(res, out); err != nil {
					return code, err
				}
			}
		}
		if lastErr == nil {
			break
		}
	}
	return lastCode, lastErr
}

// DoRequest performs an http request on service
func DoRequest(srv *sdk.Service, method, path string, args []byte, mods ...sdk.RequestModifier) ([]byte, int, error) {
	if HTTPClient == nil {
		HTTPClient = &http.Client{
			Timeout: 60 * time.Second,
		}
	}

	var requestError error
	var req *http.Request
	if args != nil {
		req, requestError = http.NewRequest(method, srv.HTTPURL+path, bytes.NewReader(args))
	} else {
		req, requestError = http.NewRequest(method, srv.HTTPURL+path, nil)
	}
	if requestError != nil {
		return nil, 0, requestError
	}

	req.Header.Set("Connection", "close")
	req.Header.Add(sdk.RequestedWithHeader, sdk.RequestedWithValue)
	for i := range mods {
		if mods[i] != nil {
			mods[i](req)
		}
	}

	// Authentify the request with the hash
	basedHash := base64.StdEncoding.EncodeToString([]byte(srv.Hash))
	req.Header.Set(sdk.AuthHeader, basedHash)

	log.Debug("services.DoRequest> request: %s", req.URL.String())

	//Do the request
	resp, errDo := HTTPClient.Do(req)
	if errDo != nil {
		return nil, 0, sdk.WrapError(errDo, "services.DoRequest> Request failed")
	}
	defer resp.Body.Close()

	// Read the body
	body, errBody := ioutil.ReadAll(resp.Body)
	if errBody != nil {
		return nil, resp.StatusCode, sdk.WrapError(errBody, "services.DoRequest> Unable to read body")
	}

	log.Debug("services.DoRequest> response: %s", string(body))

	// if everything is fine, return body
	if resp.StatusCode < 400 {
		return body, resp.StatusCode, nil
	}

	// Try to catch the CDS Error
	if cdserr := sdk.DecodeError(body); cdserr != nil {
		return nil, resp.StatusCode, cdserr
	}

	return nil, resp.StatusCode, fmt.Errorf("Request Failed")
}
<|endoftext|>


<commit_before>// -*- tab-width: 4; -*-

package main

import (
	"bufio"
	"fmt"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"
	"unicode"
)

// TODO for test!
var cacheonly bool = false

type Tweeter struct {
	Nick string
	URL  string
}
type Tweet struct {
	Tweeter Tweeter
	Created time.Time
	Text    string
}

// typedef to be able to attach sort methods
type Tweets []Tweet

func (tweets Tweets) Len() int {
	return len(tweets)
}
func (tweets Tweets) Less(i, j int) bool {
	return tweets[i].Created.Before(tweets[j].Created)
}
func (tweets Tweets) Swap(i, j int) {
	tweets[i], tweets[j] = tweets[j], tweets[i]
}

const maxfetchers = 50

func get_tweets(cache Cache) Tweets {
	var mu sync.RWMutex

	fmt.Fprintf(os.Stderr, "following: %d, fetching: ", len(conf.Following))

	tweetsch := make(chan Tweets)
	var wg sync.WaitGroup
	// max parallel http fetchers
	var fetchers = make(chan struct{}, maxfetchers)

	for nick, url := range conf.Following {
		wg.Add(1)

		fetchers <- struct{}{}

		go func(nick string, url string) {
			defer wg.Done()

			req, err := http.NewRequest("GET", url, nil)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%s: http.NewRequest fail: %s", url, err)
				tweetsch <- nil
				return
			}

			if conf.Nick != "" && conf.Twturl != "" {
				// TODO: version goes here
				req.Header.Set("User-Agent",
					fmt.Sprintf("%s/0.1 (+%s; @%s)", progname, conf.Twturl, conf.Nick))
			}

			mu.RLock()
			if cached, ok := cache[url]; ok {
				if cached.Lastmodified != "" {
					req.Header.Set("If-Modified-Since", cached.Lastmodified)
				}
			}
			mu.RUnlock()

			client := http.Client{}
			resp, err := client.Do(req)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%s: client.Do fail: %s", url, err)
				tweetsch <- nil
				return
			}
			defer resp.Body.Close()

			actualurl := resp.Request.URL.String()
			if actualurl != url {
				url = actualurl
			}

			var tweets Tweets

			switch resp.StatusCode {
			case http.StatusOK: // 200
				scanner := bufio.NewScanner(resp.Body)
				tweets = parse_file(scanner, Tweeter{Nick: nick, URL: url})
				lastmodified := resp.Header.Get("Last-Modified")
				mu.Lock()
				cache[url] = Cached{Tweets: tweets, Lastmodified: lastmodified}
				mu.Unlock()
			case http.StatusNotModified: // 304
				mu.RLock()
				tweets = cache[url].Tweets
				mu.RUnlock()
			}

			tweetsch <- tweets

		}(nick, url)

		<-fetchers
	}

	// close tweets channel when all goroutines are done
	go func() {
		wg.Wait()
		close(tweetsch)
	}()

	var alltweets Tweets
	var n = 0
	// loop until channel closed
	for tweets := range tweetsch {
		n++
		fmt.Fprintf(os.Stderr, "%d ", n)
		alltweets = append(alltweets, tweets...)
	}
	fmt.Fprintf(os.Stderr, "\n")

	return alltweets
}

func parse_file(scanner *bufio.Scanner, tweeter Tweeter) Tweets {
	var tweets Tweets
	for scanner.Scan() {
		line := strings.TrimRightFunc(scanner.Text(),
			func(r rune) bool {
				return unicode.IsSpace(r)
			})
		if len(line) == 0 {
			continue
		}
		parts := strings.SplitN(line, "\t", 2)
		if len(parts) != 2 {
			fmt.Fprintf(os.Stderr, "could not parse: '%s' (source:%s)\n", line, tweeter.URL)
			continue
		}
		tweets = append(tweets,
			Tweet{
				Tweeter: tweeter,
				Created: parsetime(parts[0]),
				Text:    parts[1],
			})
	}
	return tweets
}

func parsetime(timestr string) time.Time {
	var tm time.Time
	var err error
	// Twtxt clients generally uses basically time.RFC3339Nano, but sometimes
	// there's a colon in the timezone, or no timezone at all.
	for _, layout := range []string{
		"2006-01-02T15:04:05.999999999Z07:00",
		"2006-01-02T15:04:05.999999999Z0700",
		"2006-01-02T15:04:05.999999999",
	} {
		tm, err = time.Parse(layout, timestr)
		if err != nil {
			continue
		} else {
			break
		}
	}
	if err != nil {
		return time.Unix(0, 0)
	}
	return tm.Round(time.Second)
}
<commit_msg>Fetching progress is counting down<commit_after>// -*- tab-width: 4; -*-

package main

import (
	"bufio"
	"fmt"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"
	"unicode"
)

// TODO for test!
var cacheonly bool = false

type Tweeter struct {
	Nick string
	URL  string
}
type Tweet struct {
	Tweeter Tweeter
	Created time.Time
	Text    string
}

// typedef to be able to attach sort methods
type Tweets []Tweet

func (tweets Tweets) Len() int {
	return len(tweets)
}
func (tweets Tweets) Less(i, j int) bool {
	return tweets[i].Created.Before(tweets[j].Created)
}
func (tweets Tweets) Swap(i, j int) {
	tweets[i], tweets[j] = tweets[j], tweets[i]
}

const maxfetchers = 50

func get_tweets(cache Cache) Tweets {
	var mu sync.RWMutex

	tweetsch := make(chan Tweets)
	var wg sync.WaitGroup
	// max parallel http fetchers
	var fetchers = make(chan struct{}, maxfetchers)

	for nick, url := range conf.Following {
		wg.Add(1)

		fetchers <- struct{}{}

		go func(nick string, url string) {
			defer wg.Done()

			req, err := http.NewRequest("GET", url, nil)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%s: http.NewRequest fail: %s", url, err)
				tweetsch <- nil
				return
			}

			if conf.Nick != "" && conf.Twturl != "" {
				// TODO: version goes here
				req.Header.Set("User-Agent",
					fmt.Sprintf("%s/0.1 (+%s; @%s)", progname, conf.Twturl, conf.Nick))
			}

			mu.RLock()
			if cached, ok := cache[url]; ok {
				if cached.Lastmodified != "" {
					req.Header.Set("If-Modified-Since", cached.Lastmodified)
				}
			}
			mu.RUnlock()

			client := http.Client{}
			resp, err := client.Do(req)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%s: client.Do fail: %s", url, err)
				tweetsch <- nil
				return
			}
			defer resp.Body.Close()

			actualurl := resp.Request.URL.String()
			if actualurl != url {
				url = actualurl
			}

			var tweets Tweets

			switch resp.StatusCode {
			case http.StatusOK: // 200
				scanner := bufio.NewScanner(resp.Body)
				tweets = parse_file(scanner, Tweeter{Nick: nick, URL: url})
				lastmodified := resp.Header.Get("Last-Modified")
				mu.Lock()
				cache[url] = Cached{Tweets: tweets, Lastmodified: lastmodified}
				mu.Unlock()
			case http.StatusNotModified: // 304
				mu.RLock()
				tweets = cache[url].Tweets
				mu.RUnlock()
			}

			tweetsch <- tweets

		}(nick, url)

		<-fetchers
	}

	// close tweets channel when all goroutines are done
	go func() {
		wg.Wait()
		close(tweetsch)
	}()

	fmt.Fprintf(os.Stderr, "fetching: ")
	var alltweets Tweets
	var n = 0
	// loop until channel closed
	for tweets := range tweetsch {
		n++
		fmt.Fprintf(os.Stderr, "%d ", len(conf.Following)+1-n)
		alltweets = append(alltweets, tweets...)
	}
	fmt.Fprintf(os.Stderr, "\n")

	return alltweets
}

func parse_file(scanner *bufio.Scanner, tweeter Tweeter) Tweets {
	var tweets Tweets
	for scanner.Scan() {
		line := strings.TrimRightFunc(scanner.Text(),
			func(r rune) bool {
				return unicode.IsSpace(r)
			})
		if len(line) == 0 {
			continue
		}
		parts := strings.SplitN(line, "\t", 2)
		if len(parts) != 2 {
			fmt.Fprintf(os.Stderr, "could not parse: '%s' (source:%s)\n", line, tweeter.URL)
			continue
		}
		tweets = append(tweets,
			Tweet{
				Tweeter: tweeter,
				Created: parsetime(parts[0]),
				Text:    parts[1],
			})
	}
	return tweets
}

func parsetime(timestr string) time.Time {
	var tm time.Time
	var err error
	// Twtxt clients generally uses basically time.RFC3339Nano, but sometimes
	// there's a colon in the timezone, or no timezone at all.
	for _, layout := range []string{
		"2006-01-02T15:04:05.999999999Z07:00",
		"2006-01-02T15:04:05.999999999Z0700",
		"2006-01-02T15:04:05.999999999",
	} {
		tm, err = time.Parse(layout, timestr)
		if err != nil {
			continue
		} else {
			break
		}
	}
	if err != nil {
		return time.Unix(0, 0)
	}
	return tm.Round(time.Second)
}
<|endoftext|>


<commit_before>package geojson

import (
	"encoding/json"
	"testing"
)

func TestFeature(t *testing.T) {
	tt := test{t}
	res := `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":null}`
	p := NewPoint(Coordinate{1.0, 2.0})
	f := NewFeature(p, nil, nil)
	tt.AssertMarshal(f, res)
}

func TestFeatureWithId(t *testing.T) {
	tt := test{t}
	res := `{"type":"Feature","id":12,"geometry":{"type":"Point","coordinates":[1,2]},"properties":null}`
	p := NewPoint(Coordinate{1.0, 2.0})
	f := NewFeature(p, nil, 12)
	tt.AssertMarshal(f, res)
}

func TestFeatureWithProps(t *testing.T) {
	tt := test{t}
	res := `{"type":"Feature","id":12,"geometry":{"type":"Point","coordinates":[1,2]},"properties":{"foo":"bar","one":1}}`
	props := map[string]interface{}{"foo": "bar", "one": 1}
	p := NewPoint(Coordinate{1.0, 2.0})
	f := NewFeature(p, props, 12)
	tt.AssertMarshal(f, res)
}

func TestFeatureWithCRS(t *testing.T) {
	tt := test{t}
	res := `{"type":"Feature","id":12,"geometry":{"type":"Point","coordinates":[1,2]},"properties":null,"crs":{"type":"name","properties":{"name":"osm.some.com"}}}`
	crs := NewNamedCRS("osm.some.com")
	p := NewPoint(Coordinate{1.0, 2.0})
	f := NewFeature(p, nil, 12)
	f.Crs = crs
	tt.AssertMarshal(f, res)
}

func TestCRS(t *testing.T) {
	var (
		res string
		crs *CRS
	)
	tt := test{t}
	res = `{"type":"name","properties":{"name":"osm.some.com"}}`
	crs = NewNamedCRS("osm.some.com")
	tt.AssertMarshal(crs, res)
	res = `{"type":"link","properties":{"href":"file.crs","type":"osm"}}`
	crs = NewLinkedCRS("file.crs", "osm")
	tt.AssertMarshal(crs, res)
}

func TestUnmarshalFeatureAndPoint(t *testing.T) {
	var (
		uf  Feature
		err error
		p   *Point
		ok  bool
	)
	tt := test{t}
	res := `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":null}`
	err = json.Unmarshal([]byte(res), &uf)
	tt.AssertEq(err, nil, "No error expected 1")
	g, err := uf.GetGeometry()
	tt.AssertEq(err, nil, "No error expected 2")
	p, ok = g.(*Point)
	tt.AssertEq(ok, true, "No error expected 3")
	tt.AssertEq(p.Type, "Point", "Wrong geometry type")
}

func TestUnmarshalFeatureAndLineString(t *testing.T) {
	var (
		uf  Feature
		err error
		ls  *LineString
		ok  bool
	)
	tt := test{t}
	res := `{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1,2],[2,3.0]]},"properties":null}`
	err = json.Unmarshal([]byte(res), &uf)
	tt.AssertEq(err, nil, "No error expected 1")
	g, err := uf.GetGeometry()
	tt.AssertEq(err, nil, "No error expected 2")
	ls, ok = g.(*LineString)
	tt.AssertEq(ok, true, "No error expected 3")
	tt.AssertEq(ls.Type, "LineString", "Wrong geometry type")
	tt.AssertCoordinates(ls.Coordinates[0], Coordinate{1, 2}, "Coordinate test 1")
	tt.AssertCoordinates(ls.Coordinates[1], Coordinate{2, 3}, "Coordinate test 2")
}

func TestUnmarshalFeatureAndMultiPoint(t *testing.T) {
	var (
		uf  Feature
		err error
		mp  *MultiPoint
		ok  bool
	)
	tt := test{t}
	res := `{"type":"Feature","geometry":{"type":"MultiPoint","coordinates":[[1.1,2],[2,3.0]]},"properties":null}`
	err = json.Unmarshal([]byte(res), &uf)
	tt.AssertEq(err, nil, "No error expected 1")
	g, err := uf.GetGeometry()
	tt.AssertEq(err, nil, "No error expected 2")
	mp, ok = g.(*MultiPoint)
	tt.AssertEq(ok, true, "No error expected 3")
	tt.AssertEq(mp.Type, "MultiPoint", "Wrong geometry type")
	tt.AssertCoordinates(mp.Coordinates[0], Coordinate{1.1, 2}, "Coordinate test 1")
	tt.AssertCoordinates(mp.Coordinates[1], Coordinate{2, 3}, "Coordinate test 2")
}

func TestUnmarshalFeatureAndMultiLineString(t *testing.T) {
	var (
		uf  Feature
		err error
		mls *MultiLineString
		ok  bool
	)
	tt := test{t}
	res := `{"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[1.1,2],[2,3.0]], [[2.1,3],[3,4.0]]]},"properties":null}`
	err = json.Unmarshal([]byte(res), &uf)
	tt.AssertEq(err, nil, "No error expected 1")
	g, err := uf.GetGeometry()
	_ = g
	tt.AssertEq(err, nil, "No error expected 2")
	mls, ok = g.(*MultiLineString)
	_ = mls
	_ = ok
	tt.AssertEq(ok, true, "No error expected 3")
	tt.AssertEq(mls.Type, "MultiLineString", "Wrong geometry type")
	tt.AssertCoordinates(mls.Coordinates[0][0], Coordinate{1.1, 2}, "Coordinate test 1")
	tt.AssertCoordinates(mls.Coordinates[0][1], Coordinate{2, 3}, "Coordinate test 2")
}

func TestParseCoordinate(t *testing.T) {
	var (
		icoord interface{}
		coord  Coordinate
		err    error
	)
	tt := test{t}
	icoord = []interface{}{1, 2.0}
	coord, _ = parseCoordinate(icoord)
	_ = coord
	tt.AssertEq(coord[0], Coord(1), "Parse coordinate test 1")
	tt.AssertEq(coord[1], Coord(2.0), "Parse coordinate test 2")
	icoord = []interface{}{1, "Ala ma kota"}
	_, err = parseCoordinate(icoord)
	tt.AssertNeq(err, nil, "Error expected")
}

func TestParseCoordinates(t *testing.T) {
	var (
		icoords interface{}
		err     error
	)
	icoords = []interface{}{[]interface{}{1, 0}, []interface{}{1.1, 2.0}}
	tt := test{t}
	if coords, err := parseCoordinates(icoords); err != nil {
		t.Error(err)
	} else {
		tt.AssertEq(coords[0], Coordinate{1, 0}, "Parse first coordinate error")
		tt.AssertEq(coords[1], Coordinate{1.1, 2}, "Parse second coordinate error")
	}
	_, err = parseCoordinates([][]interface{}{{"1", 1}, {"asd", 0}})
	tt.AssertNeq(err, nil, "Error expected")
}

func TestParseMultiline(t *testing.T) {
	var (
		icoords interface{}
		err     error
	)
	icoords = []interface{}{[]interface{}{[]interface{}{1, 1}, []interface{}{2, 2}},
		[]interface{}{[]interface{}{3, 3}, []interface{}{4, 4}}}
	tt := test{t}
	if ml, err := parseMultiLine(icoords); err != nil {
		t.Error(err)
	} else {
		tt.AssertEq(ml[0][0], Coordinate{1, 1}, "Parse first coordinate error")
		tt.AssertEq(ml[1][1], Coordinate{4, 4}, "Parse last coordinate error")
	}
	_, err = parseCoordinates([]interface{}{1, 2})
	tt.AssertNeq(err, nil, "Error expected")
}
<commit_msg>skip runtime panic<commit_after>package geojson

import (
	"encoding/json"
	"testing"
)

func TestFeature(t *testing.T) {
	tt := test{t}
	res := `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":null}`
	p := NewPoint(Coordinate{1.0, 2.0})
	f := NewFeature(p, nil, nil)
	tt.AssertMarshal(f, res)
}

func TestFeatureWithId(t *testing.T) {
	tt := test{t}
	res := `{"type":"Feature","id":12,"geometry":{"type":"Point","coordinates":[1,2]},"properties":null}`
	p := NewPoint(Coordinate{1.0, 2.0})
	f := NewFeature(p, nil, 12)
	tt.AssertMarshal(f, res)
}

func TestFeatureWithProps(t *testing.T) {
	tt := test{t}
	res := `{"type":"Feature","id":12,"geometry":{"type":"Point","coordinates":[1,2]},"properties":{"foo":"bar","one":1}}`
	props := map[string]interface{}{"foo": "bar", "one": 1}
	p := NewPoint(Coordinate{1.0, 2.0})
	f := NewFeature(p, props, 12)
	tt.AssertMarshal(f, res)
}

func TestFeatureWithCRS(t *testing.T) {
	tt := test{t}
	res := `{"type":"Feature","id":12,"geometry":{"type":"Point","coordinates":[1,2]},"properties":null,"crs":{"type":"name","properties":{"name":"osm.some.com"}}}`
	crs := NewNamedCRS("osm.some.com")
	p := NewPoint(Coordinate{1.0, 2.0})
	f := NewFeature(p, nil, 12)
	f.Crs = crs
	tt.AssertMarshal(f, res)
}

func TestCRS(t *testing.T) {
	var (
		res string
		crs *CRS
	)
	tt := test{t}
	res = `{"type":"name","properties":{"name":"osm.some.com"}}`
	crs = NewNamedCRS("osm.some.com")
	tt.AssertMarshal(crs, res)
	res = `{"type":"link","properties":{"href":"file.crs","type":"osm"}}`
	crs = NewLinkedCRS("file.crs", "osm")
	tt.AssertMarshal(crs, res)
}

func TestUnmarshalFeatureAndPoint(t *testing.T) {
	var (
		uf  Feature
		err error
		p   *Point
		ok  bool
	)
	tt := test{t}
	res := `{"type":"Feature","geometry":{"type":"Point","coordinates":[1,2]},"properties":null}`
	err = json.Unmarshal([]byte(res), &uf)
	tt.AssertEq(err, nil, "No error expected 1")
	g, err := uf.GetGeometry()
	tt.AssertEq(err, nil, "No error expected 2")
	p, ok = g.(*Point)
	tt.AssertEq(ok, true, "No error expected 3")
	tt.AssertEq(p.Type, "Point", "Wrong geometry type")
}

func TestUnmarshalFeatureAndLineString(t *testing.T) {
	var (
		uf  Feature
		err error
		ls  *LineString
		ok  bool
	)
	tt := test{t}
	res := `{"type":"Feature","geometry":{"type":"LineString","coordinates":[[1,2],[2,3.0]]},"properties":null}`
	err = json.Unmarshal([]byte(res), &uf)
	tt.AssertEq(err, nil, "No error expected 1")
	g, err := uf.GetGeometry()
	tt.AssertEq(err, nil, "No error expected 2")
	ls, ok = g.(*LineString)
	tt.AssertEq(ok, true, "No error expected 3")
	tt.AssertEq(ls.Type, "LineString", "Wrong geometry type")
	tt.AssertCoordinates(ls.Coordinates[0], Coordinate{1, 2}, "Coordinate test 1")
	tt.AssertCoordinates(ls.Coordinates[1], Coordinate{2, 3}, "Coordinate test 2")
}

func TestUnmarshalFeatureAndMultiPoint(t *testing.T) {
	var (
		uf  Feature
		err error
		mp  *MultiPoint
		ok  bool
	)
	tt := test{t}
	res := `{"type":"Feature","geometry":{"type":"MultiPoint","coordinates":[[1.1,2],[2,3.0]]},"properties":null}`
	err = json.Unmarshal([]byte(res), &uf)
	tt.AssertEq(err, nil, "No error expected 1")
	g, err := uf.GetGeometry()
	tt.AssertEq(err, nil, "No error expected 2")
	mp, ok = g.(*MultiPoint)
	tt.AssertEq(ok, true, "No error expected 3")
	tt.AssertEq(mp.Type, "MultiPoint", "Wrong geometry type")
	tt.AssertCoordinates(mp.Coordinates[0], Coordinate{1.1, 2}, "Coordinate test 1")
	tt.AssertCoordinates(mp.Coordinates[1], Coordinate{2, 3}, "Coordinate test 2")
}

func TestUnmarshalFeatureAndMultiLineString(t *testing.T) {
	var (
		uf  Feature
		err error
		mls *MultiLineString
		ok  bool
	)
	tt := test{t}
	res := `{"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[1.1,2],[2,3.0]], [[2.1,3],[3,4.0]]]},"properties":null}`
	err = json.Unmarshal([]byte(res), &uf)
	tt.AssertEq(err, nil, "No error expected 1")
	g, err := uf.GetGeometry()
	_ = g
	tt.AssertEq(err, nil, "No error expected 2")
	mls, ok = g.(*MultiLineString)
	_ = mls
	_ = ok
	tt.AssertEq(ok, true, "No error expected 3")
	tt.AssertEq(mls.Type, "MultiLineString", "Wrong geometry type")
	tt.AssertCoordinates(mls.Coordinates[0][0], Coordinate{1.1, 2}, "Coordinate test 1")
	tt.AssertCoordinates(mls.Coordinates[0][1], Coordinate{2, 3}, "Coordinate test 2")
}

func TestParseCoordinate(t *testing.T) {
	var (
		icoord interface{}
		coord  Coordinate
		err    error
	)
	tt := test{t}
	icoord = []interface{}{1, 2.0}
	coord, _ = parseCoordinate(icoord)
	_ = coord
	tt.AssertEq(coord[0], Coord(1), "Parse coordinate test 1")
	tt.AssertEq(coord[1], Coord(2.0), "Parse coordinate test 2")
	icoord = []interface{}{1, "Ala ma kota"}
	_, err = parseCoordinate(icoord)
	tt.AssertNeq(err, nil, "Error expected")
}

func TestParseCoordinates(t *testing.T) {
	var (
		icoords interface{}
		err     error
	)
	icoords = []interface{}{[]interface{}{1, 0}, []interface{}{1.1, 2.0}}
	tt := test{t}
	if coords, err := parseCoordinates(icoords); err != nil {
		t.Error(err)
	} else {
		tt.AssertCoordinates(coords[0], Coordinate{1, 0}, "Parse first coordinate error")
		tt.AssertCoordinates(coords[1], Coordinate{1.1, 2}, "Parse second coordinate error")
	}
	_, err = parseCoordinates([][]interface{}{{"1", 1}, {"asd", 0}})
	tt.AssertNeq(err, nil, "Error expected")
}

func TestParseMultiline(t *testing.T) {
	var (
		icoords interface{}
		err     error
	)
	icoords = []interface{}{[]interface{}{[]interface{}{1, 1}, []interface{}{2, 2}},
		[]interface{}{[]interface{}{3, 3}, []interface{}{4, 4}}}
	tt := test{t}
	if ml, err := parseMultiLine(icoords); err != nil {
		t.Error(err)
	} else {
		tt.AssertCoordinates(ml[0][0], Coordinate{1, 1}, "Parse first coordinate error")
		tt.AssertCoordinates(ml[1][1], Coordinate{4, 4}, "Parse last coordinate error")
	}
	_, err = parseCoordinates([]interface{}{1, 2})
	tt.AssertNeq(err, nil, "Error expected")
}
<|endoftext|>


<commit_before>package docker

import (
	"context"
	"reflect"
	"sync"
	"testing"

	"github.com/docker/docker/api/types/events"
)

var noop = func(ctx context.Context, event events.Message) error { return nil }

func TestEventListeners_RegisterCallbacks(t *testing.T) {
	type fields struct {
		actions map[SourceType][]EventCallback
	}
	type args struct {
		source SourceType
		action EventCallback
	}
	tests := []struct {
		name     string
		args     []args
		expected fields
	}{
		{
			"Add one callback for a source type",
			[]args{
				args{
					ContainerSource,
					noop,
				},
			},
			fields{
				map[SourceType][]EventCallback{
					ContainerSource: []EventCallback{noop},
				},
			},
		},

		{
			"Add two callbacks for a source type",
			[]args{
				args{
					ContainerSource,
					noop,
				},
				args{
					ContainerSource,
					noop,
				},
			},
			fields{
				map[SourceType][]EventCallback{
					ContainerSource: []EventCallback{
						noop, noop,
					},
				},
			},
		},
		{
			"Add one callback for two source types",
			[]args{
				args{
					ContainerSource,
					noop,
				},
				args{
					ImageSource,
					noop,
				},
			},
			fields{
				map[SourceType][]EventCallback{
					ContainerSource: []EventCallback{
						noop,
					},
					ImageSource: []EventCallback{
						noop,
					},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			el := &registry{actions: make(map[SourceType][]EventCallback)}
			for _, args := range tt.args {
				el.Register(args.source, args.action)
			}

			if !eq(el.actions, tt.expected.actions) {
				t.Errorf("Adding events listener fails. Expected: %v, got: %v", tt.expected.actions, el.actions)
			}
		})
	}
}

//Checks if both map are equal, by checking the length, keys and number of actions per key
func eq(a, b map[SourceType][]EventCallback) bool {
	if len(a) != len(b) {
		return false
	}

	for k, v := range a {
		if w, ok := b[k]; !ok || len(v) != len(w) {
			return false
		}
	}

	return true
}

func Test_notifyCallbacks(t *testing.T) {

	var callback = func(
		wg *sync.WaitGroup,
		invocations map[SourceType]int) EventCallback {
		return func(ctx context.Context, event events.Message) error {
			defer wg.Done()
			t := SourceType(event.Type)
			invocations[t] = invocations[t] + 1
			return nil
		}
	}
	type args struct {
		r        *registry
		sources  []SourceType
		messages []events.Message
	}
	tests := []struct {
		name string
		args args
		want map[SourceType]int
	}{
		{
			"No messages, no calls to callbacks",
			args{
				r:        &registry{actions: make(map[SourceType][]EventCallback)},
				sources:  []SourceType{ContainerSource},
				messages: []events.Message{},
			},
			map[SourceType]int{},
		},
		{
			"One message, one callback called",
			args{
				r:       &registry{actions: make(map[SourceType][]EventCallback)},
				sources: []SourceType{ContainerSource},
				messages: []events.Message{
					events.Message{Type: "container"},
				},
			},
			map[SourceType]int{
				ContainerSource: 1,
			},
		},
		{
			"Two messages, two callbacks called",
			args{
				r:       &registry{actions: make(map[SourceType][]EventCallback)},
				sources: []SourceType{ContainerSource},
				messages: []events.Message{
					events.Message{Type: "container"},
					events.Message{Type: "container"},
				},
			},
			map[SourceType]int{
				ContainerSource: 2,
			},
		},
		{
			"Two messages of one type, one message of another type, three callbacks called",
			args{
				r:       &registry{actions: make(map[SourceType][]EventCallback)},
				sources: []SourceType{ContainerSource, ImageSource},
				messages: []events.Message{
					events.Message{Type: "container"},
					events.Message{Type: "container"},
					events.Message{Type: "image"},
				},
			},
			map[SourceType]int{
				ContainerSource: 2,
				ImageSource:     1,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			notify := notifyCallbacks(tt.args.r)
			invocations := make(map[SourceType]int)
			var wg sync.WaitGroup

			for _, s := range tt.args.sources {
				tt.args.r.Register(s, callback(&wg, invocations))
			}
			for _, m := range tt.args.messages {
				wg.Add(1)
				notify(context.Background(), m)
			}
			wg.Wait()
			if !reflect.DeepEqual(invocations, tt.want) {
				t.Errorf("notifyCallbacks() = %v, want %v", invocations, tt.want)
			}
		})
	}
}
<commit_msg>Sync map writes on test<commit_after>package docker

import (
	"context"
	"reflect"
	"sync"
	"testing"

	"github.com/docker/docker/api/types/events"
)

var noop = func(ctx context.Context, event events.Message) error { return nil }

func TestEventListeners_RegisterCallbacks(t *testing.T) {
	type fields struct {
		actions map[SourceType][]EventCallback
	}
	type args struct {
		source SourceType
		action EventCallback
	}
	tests := []struct {
		name     string
		args     []args
		expected fields
	}{
		{
			"Add one callback for a source type",
			[]args{
				args{
					ContainerSource,
					noop,
				},
			},
			fields{
				map[SourceType][]EventCallback{
					ContainerSource: []EventCallback{noop},
				},
			},
		},

		{
			"Add two callbacks for a source type",
			[]args{
				args{
					ContainerSource,
					noop,
				},
				args{
					ContainerSource,
					noop,
				},
			},
			fields{
				map[SourceType][]EventCallback{
					ContainerSource: []EventCallback{
						noop, noop,
					},
				},
			},
		},
		{
			"Add one callback for two source types",
			[]args{
				args{
					ContainerSource,
					noop,
				},
				args{
					ImageSource,
					noop,
				},
			},
			fields{
				map[SourceType][]EventCallback{
					ContainerSource: []EventCallback{
						noop,
					},
					ImageSource: []EventCallback{
						noop,
					},
				},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			el := &registry{actions: make(map[SourceType][]EventCallback)}
			for _, args := range tt.args {
				el.Register(args.source, args.action)
			}

			if !eq(el.actions, tt.expected.actions) {
				t.Errorf("Adding events listener fails. Expected: %v, got: %v", tt.expected.actions, el.actions)
			}
		})
	}
}

//Checks if both map are equal, by checking the length, keys and number of actions per key
func eq(a, b map[SourceType][]EventCallback) bool {
	if len(a) != len(b) {
		return false
	}

	for k, v := range a {
		if w, ok := b[k]; !ok || len(v) != len(w) {
			return false
		}
	}

	return true
}

type invocations struct {
	m map[SourceType]int
	sync.Mutex
}

func Test_notifyCallbacks(t *testing.T) {

	var callback = func(
		wg *sync.WaitGroup,
		i invocations) EventCallback {
		return func(ctx context.Context, event events.Message) error {
			defer wg.Done()
			i.Lock()
			defer i.Unlock()
			t := SourceType(event.Type)
			i.m[t] = i.m[t] + 1
			return nil
		}
	}
	type args struct {
		r        *registry
		sources  []SourceType
		messages []events.Message
	}
	tests := []struct {
		name string
		args args
		want map[SourceType]int
	}{
		{
			"No messages, no calls to callbacks",
			args{
				r:        &registry{actions: make(map[SourceType][]EventCallback)},
				sources:  []SourceType{ContainerSource},
				messages: []events.Message{},
			},
			map[SourceType]int{},
		},
		{
			"One message, one callback called",
			args{
				r:       &registry{actions: make(map[SourceType][]EventCallback)},
				sources: []SourceType{ContainerSource},
				messages: []events.Message{
					events.Message{Type: "container"},
				},
			},
			map[SourceType]int{
				ContainerSource: 1,
			},
		},
		{
			"Two messages, two callbacks called",
			args{
				r:       &registry{actions: make(map[SourceType][]EventCallback)},
				sources: []SourceType{ContainerSource},
				messages: []events.Message{
					events.Message{Type: "container"},
					events.Message{Type: "container"},
				},
			},
			map[SourceType]int{
				ContainerSource: 2,
			},
		},
		{
			"5 messages of one type, one message of another type, three callbacks called",
			args{
				r:       &registry{actions: make(map[SourceType][]EventCallback)},
				sources: []SourceType{ContainerSource, ImageSource},
				messages: []events.Message{
					events.Message{Type: "container"},
					events.Message{Type: "container"},
					events.Message{Type: "container"},
					events.Message{Type: "container"},
					events.Message{Type: "container"},
					events.Message{Type: "image"},
				},
			},
			map[SourceType]int{
				ContainerSource: 5,
				ImageSource:     1,
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			notify := notifyCallbacks(tt.args.r)
			invocations := invocations{m: make(map[SourceType]int)}

			var wg sync.WaitGroup

			for _, s := range tt.args.sources {
				tt.args.r.Register(s, callback(&wg, invocations))
			}
			for _, m := range tt.args.messages {
				wg.Add(1)
				notify(context.Background(), m)
			}
			wg.Wait()
			if !reflect.DeepEqual(invocations.m, tt.want) {
				t.Errorf("notifyCallbacks() = %v, want %v", invocations, tt.want)
			}
		})
	}
}
<|endoftext|>


<commit_before>package transcode

import (

)

// mp3Codec contains the codec describing MP3
const mp3Codec = "MP3"

// mp3FFmpegCodec contains the ffmpeg codec describing MP3
const mp3FFmpegCodec = "libpm3lame"

// MP3CBROptions represents the options for a MP3 CBR transcoder
type MP3CBROptions struct {
	quality string
}

// Codec returns the codec used
func (m MP3CBROptions) Codec() string {
	return mp3Codec
}

// FFmpegCodec returns the codec used by ffmpeg
func (m MP3CBROptions) FFmpegCodec() string {
	return mp3FFmpegCodec
}

// Quality returns the quality used
func (m MP3CBROptions) Quality() string {
	return m.quality
}

// FFmpegQuality returns the quality flag used by ffmpeg
func (m MP3CBROptions) FFmpegQuality() string {
	return "-something " + m.quality
}

// MP3VBROptions represents the options for a MP3 VBR transcoder
type MP3VBROptions struct {
	quality string
}

// Codec returns the codec used
func (m MP3VBROptions) Codec() string {
	return mp3Codec
}

// FFmpegCodec returns the codec used by ffmpeg
func (m MP3VBROptions) FFmpegCodec() string {
	return mp3FFmpegCodec
}

// Quality returns the quality used
func (m MP3VBROptions) Quality() string {
	return m.quality
}

// FFmpegQuality returns the quality flag used by ffmpeg
func (m MP3VBROptions) FFmpegQuality() string {
	// Return the number after 'V'
	return "-qscale:a " + string(m.quality[1])
}
<commit_msg>Fix typo<commit_after>package transcode

import (

)

// mp3Codec contains the codec describing MP3
const mp3Codec = "MP3"

// mp3FFmpegCodec contains the ffmpeg codec describing MP3
const mp3FFmpegCodec = "libmp3lame"

// MP3CBROptions represents the options for a MP3 CBR transcoder
type MP3CBROptions struct {
	quality string
}

// Codec returns the codec used
func (m MP3CBROptions) Codec() string {
	return mp3Codec
}

// FFmpegCodec returns the codec used by ffmpeg
func (m MP3CBROptions) FFmpegCodec() string {
	return mp3FFmpegCodec
}

// Quality returns the quality used
func (m MP3CBROptions) Quality() string {
	return m.quality
}

// FFmpegQuality returns the quality flag used by ffmpeg
func (m MP3CBROptions) FFmpegQuality() string {
	return "-something " + m.quality
}

// MP3VBROptions represents the options for a MP3 VBR transcoder
type MP3VBROptions struct {
	quality string
}

// Codec returns the codec used
func (m MP3VBROptions) Codec() string {
	return mp3Codec
}

// FFmpegCodec returns the codec used by ffmpeg
func (m MP3VBROptions) FFmpegCodec() string {
	return mp3FFmpegCodec
}

// Quality returns the quality used
func (m MP3VBROptions) Quality() string {
	return m.quality
}

// FFmpegQuality returns the quality flag used by ffmpeg
func (m MP3VBROptions) FFmpegQuality() string {
	// Return the number after 'V'
	return "-qscale:a " + string(m.quality[1])
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"compress/gzip"
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"runtime"
	"time"

	"bitbucket.org/kardianos/osext"
	"github.com/inconshreveable/go-update"
	"github.com/kr/binarydist"
)

var cmdUpdate = &Command{
	Run:      runUpdate,
	Usage:    "update",
	Category: "hk",
	Long: `
Update downloads and installs the next version of hk.

This command is unlisted, since users never have to run it directly.
`,
}

func runUpdate(cmd *Command, args []string) {
	if updater == nil {
		log.Fatal("Dev builds don't support auto-updates")
	}
	if err := updater.update(); err != nil {
		log.Fatal(err)
	}
}

const (
	upcktimePath = "cktime"
	plat         = runtime.GOOS + "-" + runtime.GOARCH
)

var ErrHashMismatch = errors.New("new file hash mismatch after patch")

// Update protocol.
//
//   GET hk.heroku.com/hk/current/linux-amd64.json
//
//   200 ok
//   {
//       "Version": "2",
//       "Sha256": "..." // base64
//   }
//
// then
//
//   GET hkpatch.s3.amazonaws.com/hk/1/2/linux-amd64
//
//   200 ok
//   [bsdiff data]
//
// or
//
//   GET hkdist.s3.amazonaws.com/hk/2/linux-amd64.gz
//
//   200 ok
//   [gzipped executable data]
type Updater struct {
	apiURL  string
	cmdName string
	binURL  string
	diffURL string
	dir     string
	info    struct {
		Version string
		Sha256  []byte
	}
}

func (u *Updater) backgroundRun() {
	os.MkdirAll(u.dir, 0777)
	if u.wantUpdate() {
		if err := update.SanityCheck(); err != nil {
			// fail
			return
		}
		self, err := osext.Executable()
		if err != nil {
			// fail update, couldn't figure out path to self
			return
		}
		// TODO(bgentry): logger isn't on Windows. Replace w/ proper error reports.
		l := exec.Command("logger", "-thk")
		c := exec.Command(self, "update")
		if w, err := l.StdinPipe(); err == nil && l.Start() == nil {
			c.Stdout = w
			c.Stderr = w
		}
		c.Start()
	}
}

func (u *Updater) wantUpdate() bool {
	path := u.dir + upcktimePath
	if Version == "dev" || readTime(path).After(time.Now()) {
		return false
	}
	wait := 12*time.Hour + randDuration(8*time.Hour)
	return writeTime(path, time.Now().Add(wait))
}

func (u *Updater) update() error {
	path, err := osext.Executable()
	if err != nil {
		return err
	}
	old, err := os.Open(path)
	if err != nil {
		return err
	}
	defer old.Close()

	err = u.fetchInfo()
	if err != nil {
		return err
	}
	if u.info.Version == Version {
		return nil
	}
	bin, err := u.fetchAndVerifyPatch(old)
	if err != nil {
		if err == ErrHashMismatch {
			log.Println("update: hash mismatch from patched binary")
		} else {
			log.Println("update: patching binary,", err)
		}
		bin, err = u.fetchAndVerifyFullBin()
		if err != nil {
			if err == ErrHashMismatch {
				log.Println("update: hash mismatch from full binary")
			} else {
				log.Println("update: fetching full binary,", err)
			}
			return err
		}
	}

	// close the old binary before installing because on windows
	// it can't be renamed if a handle to the file is still open
	old.Close()

	err, errRecover := update.FromStream(bytes.NewBuffer(bin))
	if errRecover != nil {
		return fmt.Errorf("update and recovery errors: %q %q", err, errRecover)
	}
	if err != nil {
		return err
	}
	log.Printf("Updated v%s -> v%s.", Version, u.info.Version)
	return nil
}

func (u *Updater) fetchInfo() error {
	r, err := fetch(u.apiURL + u.cmdName + "/current/" + plat + ".json")
	if err != nil {
		return err
	}
	defer r.Close()
	err = json.NewDecoder(r).Decode(&u.info)
	if err != nil {
		return err
	}
	if len(u.info.Sha256) != sha256.Size {
		return errors.New("bad cmd hash in info")
	}
	return nil
}

func (u *Updater) fetchAndVerifyPatch(old io.Reader) ([]byte, error) {
	bin, err := u.fetchAndApplyPatch(old)
	if err != nil {
		return nil, err
	}
	if !verifySha(bin, u.info.Sha256) {
		return nil, ErrHashMismatch
	}
	return bin, nil
}

func (u *Updater) fetchAndApplyPatch(old io.Reader) ([]byte, error) {
	r, err := fetch(u.diffURL + u.cmdName + "/" + Version + "/" + u.info.Version + "/" + plat)
	if err != nil {
		return nil, err
	}
	defer r.Close()
	var buf bytes.Buffer
	err = binarydist.Patch(old, &buf, r)
	return buf.Bytes(), err
}

func (u *Updater) fetchAndVerifyFullBin() ([]byte, error) {
	bin, err := u.fetchBin()
	if err != nil {
		return nil, err
	}
	verified := verifySha(bin, u.info.Sha256)
	if !verified {
		return nil, ErrHashMismatch
	}
	return bin, nil
}

func (u *Updater) fetchBin() ([]byte, error) {
	r, err := fetch(u.binURL + u.cmdName + "/" + u.info.Version + "/" + plat + ".gz")
	if err != nil {
		return nil, err
	}
	defer r.Close()
	buf := new(bytes.Buffer)
	gz, err := gzip.NewReader(r)
	if err != nil {
		return nil, err
	}
	if _, err = io.Copy(buf, gz); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

// returns a random duration in [0,n).
func randDuration(n time.Duration) time.Duration {
	return time.Duration(rand.Int63n(int64(n)))
}

func fetch(url string) (io.ReadCloser, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("bad http status from %s: %v", url, resp.Status)
	}
	return resp.Body, nil
}

func readTime(path string) time.Time {
	p, err := ioutil.ReadFile(path)
	if os.IsNotExist(err) {
		return time.Time{}
	}
	if err != nil {
		return time.Now().Add(1000 * time.Hour)
	}
	t, err := time.Parse(time.RFC3339, string(p))
	if err != nil {
		return time.Now().Add(1000 * time.Hour)
	}
	return t
}

func verifySha(bin []byte, sha []byte) bool {
	h := sha256.New()
	h.Write(bin)
	return bytes.Equal(h.Sum(nil), sha)
}

func writeTime(path string, t time.Time) bool {
	return ioutil.WriteFile(path, []byte(t.Format(time.RFC3339)), 0644) == nil
}
<commit_msg>cleanly fall through to full update when no patch is available<commit_after>package main

import (
	"bytes"
	"compress/gzip"
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"runtime"
	"time"

	"bitbucket.org/kardianos/osext"
	"github.com/inconshreveable/go-update"
	"github.com/kr/binarydist"
)

var cmdUpdate = &Command{
	Run:      runUpdate,
	Usage:    "update",
	Category: "hk",
	Long: `
Update downloads and installs the next version of hk.

This command is unlisted, since users never have to run it directly.
`,
}

func runUpdate(cmd *Command, args []string) {
	if updater == nil {
		log.Fatal("Dev builds don't support auto-updates")
	}
	if err := updater.update(); err != nil {
		log.Fatal(err)
	}
}

const (
	upcktimePath = "cktime"
	plat         = runtime.GOOS + "-" + runtime.GOARCH
)

var ErrHashMismatch = errors.New("new file hash mismatch after patch")

// Update protocol.
//
//   GET hk.heroku.com/hk/current/linux-amd64.json
//
//   200 ok
//   {
//       "Version": "2",
//       "Sha256": "..." // base64
//   }
//
// then
//
//   GET hkpatch.s3.amazonaws.com/hk/1/2/linux-amd64
//
//   200 ok
//   [bsdiff data]
//
// or
//
//   GET hkdist.s3.amazonaws.com/hk/2/linux-amd64.gz
//
//   200 ok
//   [gzipped executable data]
type Updater struct {
	apiURL  string
	cmdName string
	binURL  string
	diffURL string
	dir     string
	info    struct {
		Version string
		Sha256  []byte
	}
}

func (u *Updater) backgroundRun() {
	os.MkdirAll(u.dir, 0777)
	if u.wantUpdate() {
		if err := update.SanityCheck(); err != nil {
			// fail
			return
		}
		self, err := osext.Executable()
		if err != nil {
			// fail update, couldn't figure out path to self
			return
		}
		// TODO(bgentry): logger isn't on Windows. Replace w/ proper error reports.
		l := exec.Command("logger", "-thk")
		c := exec.Command(self, "update")
		if w, err := l.StdinPipe(); err == nil && l.Start() == nil {
			c.Stdout = w
			c.Stderr = w
		}
		c.Start()
	}
}

func (u *Updater) wantUpdate() bool {
	path := u.dir + upcktimePath
	if Version == "dev" || readTime(path).After(time.Now()) {
		return false
	}
	wait := 12*time.Hour + randDuration(8*time.Hour)
	return writeTime(path, time.Now().Add(wait))
}

func (u *Updater) update() error {
	path, err := osext.Executable()
	if err != nil {
		return err
	}
	old, err := os.Open(path)
	if err != nil {
		return err
	}
	defer old.Close()

	err = u.fetchInfo()
	if err != nil {
		return err
	}
	if u.info.Version == Version {
		return nil
	}
	bin, err := u.fetchAndVerifyPatch(old)
	if err != nil {
		switch err {
		case ErrNoPatchAvailable:
			log.Println("update: no patch available, falling back to full binary")
		case ErrHashMismatch:
			log.Println("update: hash mismatch from patched binary")
		default:
			log.Println("update: patching binary,", err)
		}
		bin, err = u.fetchAndVerifyFullBin()
		if err != nil {
			if err == ErrHashMismatch {
				log.Println("update: hash mismatch from full binary")
			} else {
				log.Println("update: fetching full binary,", err)
			}
			return err
		}
	}

	// close the old binary before installing because on windows
	// it can't be renamed if a handle to the file is still open
	old.Close()

	err, errRecover := update.FromStream(bytes.NewBuffer(bin))
	if errRecover != nil {
		return fmt.Errorf("update and recovery errors: %q %q", err, errRecover)
	}
	if err != nil {
		return err
	}
	log.Printf("Updated v%s -> v%s.", Version, u.info.Version)
	return nil
}

func (u *Updater) fetchInfo() error {
	r, err := fetch(u.apiURL + u.cmdName + "/current/" + plat + ".json")
	if err != nil {
		return err
	}
	defer r.Close()
	err = json.NewDecoder(r).Decode(&u.info)
	if err != nil {
		return err
	}
	if len(u.info.Sha256) != sha256.Size {
		return errors.New("bad cmd hash in info")
	}
	return nil
}

func (u *Updater) fetchAndVerifyPatch(old io.Reader) ([]byte, error) {
	bin, err := u.fetchAndApplyPatch(old)
	if err != nil {
		return nil, err
	}
	if !verifySha(bin, u.info.Sha256) {
		return nil, ErrHashMismatch
	}
	return bin, nil
}

func (u *Updater) fetchAndApplyPatch(old io.Reader) ([]byte, error) {
	r, err := fetch(u.diffURL + u.cmdName + "/" + Version + "/" + u.info.Version + "/" + plat)
	if err != nil {
		return nil, err
	}
	defer r.Close()
	var buf bytes.Buffer
	err = binarydist.Patch(old, &buf, r)
	return buf.Bytes(), err
}

func (u *Updater) fetchAndVerifyFullBin() ([]byte, error) {
	bin, err := u.fetchBin()
	if err != nil {
		return nil, err
	}
	verified := verifySha(bin, u.info.Sha256)
	if !verified {
		return nil, ErrHashMismatch
	}
	return bin, nil
}

func (u *Updater) fetchBin() ([]byte, error) {
	r, err := fetch(u.binURL + u.cmdName + "/" + u.info.Version + "/" + plat + ".gz")
	if err != nil {
		return nil, err
	}
	defer r.Close()
	buf := new(bytes.Buffer)
	gz, err := gzip.NewReader(r)
	if err != nil {
		return nil, err
	}
	if _, err = io.Copy(buf, gz); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

// returns a random duration in [0,n).
func randDuration(n time.Duration) time.Duration {
	return time.Duration(rand.Int63n(int64(n)))
}

var ErrNoPatchAvailable = errors.New("no patch available")

func fetch(url string) (io.ReadCloser, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	switch resp.StatusCode {
	case 200:
		return resp.Body, nil
	case 401, 403, 404:
		return nil, ErrNoPatchAvailable
	default:
		return nil, fmt.Errorf("bad http status from %s: %v", url, resp.Status)
	}
	panic("not reached")
}

func readTime(path string) time.Time {
	p, err := ioutil.ReadFile(path)
	if os.IsNotExist(err) {
		return time.Time{}
	}
	if err != nil {
		return time.Now().Add(1000 * time.Hour)
	}
	t, err := time.Parse(time.RFC3339, string(p))
	if err != nil {
		return time.Now().Add(1000 * time.Hour)
	}
	return t
}

func verifySha(bin []byte, sha []byte) bool {
	h := sha256.New()
	h.Write(bin)
	return bytes.Equal(h.Sum(nil), sha)
}

func writeTime(path string, t time.Time) bool {
	return ioutil.WriteFile(path, []byte(t.Format(time.RFC3339)), 0644) == nil
}
<|endoftext|>


<commit_before>/*
Package update allows a program to "self-update", replacing its executable file
with new bytes.

Package update provides the facility to create user experiences like auto-updating
or user-approved updates which manifest as user prompts in commercial applications
with copy similar to "Restart to being using the new version of X".

Updating your program to a new version is as easy as:

	err := update.FromUrl("http://release.example.com/2.0/myprogram")
	if err != nil {
		fmt.Printf("Update failed: %v", err)
	}

The most low-level API is FromStream() which updates the current executable
with the bytes read from an io.Reader.

Additional APIs are provided for common update strategies which include
updating from a file with FromFile() and updating from the internet with
FromUrl().

Using the more advaced Download.UpdateFromUrl() API gives you the ability
to resume an interrupted download to enable large updates to complete even
over intermittent or slow connections. This API also enables more fine-grained
control over how the update is downloaded from the internet as well as access to
download progress,
*/
package update

import (
	"compress/gzip"
	"fmt"
	execpath "github.com/inconshreveable/go-execpath"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
)

type MeteredReader struct {
	rd        io.ReadCloser
	totalSize int64
	progress  chan int
	totalRead int64
	ticks     int64
}

func (m *MeteredReader) Close() error {
	return m.rd.Close()
}

func (m *MeteredReader) Read(b []byte) (n int, err error) {
	chunkSize := (m.totalSize / 100) + 1
	lenB := int64(len(b))

	var nChunk int
	for start := int64(0); start < lenB; start += int64(nChunk) {
		end := start + chunkSize
		if end > lenB {
			end = lenB
		}

		nChunk, err = m.rd.Read(b[start:end])

		n += nChunk
		m.totalRead += int64(nChunk)

		if m.totalRead > (m.ticks * chunkSize) {
			m.ticks += 1
			// try to send on channel, but don't block if it's full
			select {
			case m.progress <- int(m.ticks + 1):
			default:
			}

			// give the progress channel consumer a chance to run
			runtime.Gosched()
		}

		if err != nil {
			return
		}
	}

	return
}

// We wrap the round tripper when making requests
// because we need to add headers to the requests we make
// even when they are requests made after a redirect
type RoundTripper struct {
	RoundTripFn func(*http.Request) (*http.Response, error)
}

func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	return rt.RoundTripFn(r)
}

// Type Download encapsulates the necessary parameters and state
// needed to download an update from the internet. Create an instance
// with the NewDownload() factory function.
//
// You may only use a Download once,
type Download struct {
	// net/http.Client to use when downloading the update.
	// If nil, a default http.Client is used
	HttpClient *http.Client

	// Path on the file system to dowload the update to
	// If empty, a temporary file is used.
	// After the download begins, this path will be set
	// so that the client can use it to resume aborted
	// downloads
	Path string

	// Progress returns the percentage of the download
	// completed as an integer between 0 and 100
	Progress chan (int)

	// HTTP Method to use in the download request. Default is "GET"
	Method string

	// HTTP URL to issue the download request to
	Url string

	// Set to true when the server confirms a new version is available
	// even if the updating process encounters an error later on
	Available bool
}

// NewDownload initializes a new Download object
func NewDownload(url string) *Download {
	return &Download{
		HttpClient: new(http.Client),
		Progress:   make(chan int),
		Method:     "GET",
		Url: url,
	}
}

func (d *Download) sharedHttp(offset int64) (resp *http.Response, err error) {
	// create the download request
	req, err := http.NewRequest(d.Method, d.Url, nil)
	if err != nil {
		return
	}

	// we have to add headers like this so they get used across redirects
	trans := d.HttpClient.Transport
	if trans == nil {
		trans = http.DefaultTransport
	}

	d.HttpClient.Transport = &RoundTripper{
		RoundTripFn: func(r *http.Request) (*http.Response, error) {
			// add header for download continuation
			if offset > 0 {
				r.Header.Add("Range", fmt.Sprintf("%d-", offset))
			}

			// ask for gzipped content so that net/http won't unzip it for us
			// and destroy the content length header we need for progress calculations
			r.Header.Add("Accept-Encoding", "gzip")

			return trans.RoundTrip(r)
		},
	}

	// issue the download request
	return d.HttpClient.Do(req)
}

func (d *Download) Check() (available bool, err error) {
	resp, err := d.sharedHttp(0)
	if err != nil {
		return
	}
	resp.Body.Close()

	switch resp.StatusCode {
	// ok
	case 200, 206:
		available = true

	// no update available
	case 204:
		available = false

	// server error
	default:
		err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
		return
	}

	return
}

// Get() downloads the given url from the internet to a file on disk
// and then calls FromStream() to update the current program's executable file
// with the contents of that file.
//
// If the update is successful, the downloaded file will be erased from disk.
// Otherwise, it will remain in d.Path to allow the download to resume later
// or be skipped entirely.
//
// Only HTTP/1.1 servers that implement the Range header support resuming a
// partially completed download.
//
// UpdateFromUrl() uses HTTP status codes to determine what action to take.
//
// - The HTTP server should return 200 or 206 for the update to be downloaded.
//
// - The HTTP server should return 204 if no update is available at this time.
//
// - If the HTTP server returns a 3XX redirect, it will be followed
// according to d.HttpClient's redirect policy.
//
// - Any other HTTP status code will cause UpdateFromUrl to return an error.
func (d *Download) Get() (err error) {
	var offset int64 = 0
	var fp *os.File

	// Close the progress channel whenever this function completes
	defer close(d.Progress)

	// open a file where we will stream the downloaded update to
	// we do this first because if the caller specified a non-empty dlpath
	// we need to determine how large it is in order to resume the download
	if d.Path == "" {
		// no dlpath specified, use a random tempfile
		fp, err = ioutil.TempFile("", "update")
		if err != nil {
			return
		}
		defer fp.Close()

		// remember the path
		d.Path = fp.Name()
	} else {
		fp, err = os.OpenFile(d.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600)
		if err != nil {
			return
		}
		defer fp.Close()

		// determine the file size so we can resume the download, if possible
		var fi os.FileInfo
		fi, err = fp.Stat()
		if err != nil {
			return
		}

		offset = fi.Size()
	}

	// start downloading the file
	resp, err := d.sharedHttp(offset)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	// ok
	case 200, 206:
		d.Available = true

	// no update available
	case 204:
		return

	// server error
	default:
		err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
		return
	}

	// Determine how much we have to download
	// net/http sets this to -1 when it is unknown
	clength := resp.ContentLength

	// Read the content from the response body
	rd := resp.Body

	// meter the rate at which we download content for
	// progress reporting if we know how much to expect
	if clength > 0 {
		rd = &MeteredReader{rd: rd, totalSize: clength, progress: d.Progress}
	}

	// Decompress the content if necessary
	if resp.Header.Get("Content-Encoding") == "gzip" {
		rd, err = gzip.NewReader(rd)
		if err != nil {
			return
		}
	}

	// Download the update
	_, err = io.Copy(fp, rd)
	if err != nil {
		return
	}

	return
}

func (d *Download) GetAndUpdate() (err error, errRecover error) {
	// check before we download if this will work
	if err = SanityCheck(); err != nil {
		// keep the contract that d.Progress will close whenever Get() terminates
		close(d.Progress)
		return
	}

	// download the update
	if err = d.Get(); err != nil || !d.Available {
		return
	}

	// apply the update
	if err, errRecover = FromFile(d.Path); err != nil || errRecover != nil {
		return
	}

	// remove the temporary file
	os.Remove(d.Path)
	return
}

// FromUrl downloads the contents of the given url and uses them to update
// the current program's executable file. It is a convenience function which is equivalent to
//
// 	NewDownload(url).GetAndUpdate()
//
// See Download.Get() for more details.
func FromUrl(url string) (err error, errRecover error) {
	return NewDownload(url).GetAndUpdate()
}

// FromFile reads the contents of the given file and uses them
// to update the current program's executable file by calling FromStream().
func FromFile(filepath string) (err error, errRecover error) {
	// open the new binary
	fp, err := os.Open(filepath)
	if err != nil {
		return
	}
	defer fp.Close()

	// do the update
	return FromStream(fp)
}

// FromStream reads the contents of the supplied io.Reader newBinary
// and uses them to update the current program's executable file.
//
// FromStream performs the following actions to ensure a cross-platform safe
// update:
//
// - Creates a new file, /path/to/.program-name.new with mode 0755 and copies
// the contents of newBinary into the file
//
// - Renames the current program's executable file from /path/to/program-name
// to /path/to/.program-name.old
//
// - Renames /path/to/.program-name.new to /path/to/program-name
//
// - If the rename is successful, it erases /path/to/.program.old. If this operation
// fails, no error is reported.
//
// - If the rename is unsuccessful, it attempts to rename /path/to/.program-name.old
// back to /path/to/program-name. If this operation fails, the error is not reported
// in order to not mask the error that caused the rename recovery attempt.
func FromStream(newBinary io.Reader) (err error, errRecover error) {
	// get the path to the executable
	thisExecPath, err := execpath.Get()
	if err != nil {
		return
	}

	// get the directory the executable exists in
	execDir := filepath.Dir(thisExecPath)
	execName := filepath.Base(thisExecPath)

	// Copy the contents of of newbinary to a the new executable file
	newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
	fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if err != nil {
		return
	}
	defer fp.Close()
	_, err = io.Copy(fp, newBinary)

	// if we don't call fp.Close(), windows won't let us move the new executable
	// because the file will still be "in use"
	fp.Close()

	// this is where we'll move the executable to so that we can swap in the updated replacement
	oldExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.old", execName))

	// delete any existing old exec file - this is necessary on Windows for two reasons:
	// 1. after a successful update, windows can't remove the .old file because the process is still running
	// 2. windows rename operations fail if the destination file already exists
	_ = os.Remove(oldExecPath)

	// move the existing executable to a new file in the same directory
	err = os.Rename(thisExecPath, oldExecPath)
	if err != nil {
		return
	}

	// move the new exectuable in to become the new program
	err = os.Rename(newExecPath, thisExecPath)

	if err != nil {
		// copy unsuccessful
		errRecover = os.Rename(oldExecPath, thisExecPath)
	} else {
		// copy successful, remove the old binary
		_ = os.Remove(oldExecPath)
	}

	return
}

// SanityCheck() attempts to determine whether an in-place executable update could
// succeed by performing preliminary checks (to establish valid permissions, etc).
// This helps avoid downloading updates when we know the update can't be successfully
// applied later.
func SanityCheck() (err error) {
	// get the path to the executable
	thisExecPath, err := execpath.Get()
	if err != nil {
		return
	}

	// get the directory the executable exists in
	execDir := filepath.Dir(thisExecPath)
	execName := filepath.Base(thisExecPath)

	// attempt to open a file in the executable's directory
	newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
	fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if err != nil {
		return
	}
	fp.Close()

	_ = os.Remove(newExecPath)
	return
}
<commit_msg>deprecate go-execpath and switch to osext<commit_after>/*
Package update allows a program to "self-update", replacing its executable file
with new bytes.

Package update provides the facility to create user experiences like auto-updating
or user-approved updates which manifest as user prompts in commercial applications
with copy similar to "Restart to being using the new version of X".

Updating your program to a new version is as easy as:

	err := update.FromUrl("http://release.example.com/2.0/myprogram")
	if err != nil {
		fmt.Printf("Update failed: %v", err)
	}

The most low-level API is FromStream() which updates the current executable
with the bytes read from an io.Reader.

Additional APIs are provided for common update strategies which include
updating from a file with FromFile() and updating from the internet with
FromUrl().

Using the more advaced Download.UpdateFromUrl() API gives you the ability
to resume an interrupted download to enable large updates to complete even
over intermittent or slow connections. This API also enables more fine-grained
control over how the update is downloaded from the internet as well as access to
download progress,
*/
package update

import (
	"compress/gzip"
	"fmt"
	"bitbucket.org/kardianos/osext"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
)

type MeteredReader struct {
	rd        io.ReadCloser
	totalSize int64
	progress  chan int
	totalRead int64
	ticks     int64
}

func (m *MeteredReader) Close() error {
	return m.rd.Close()
}

func (m *MeteredReader) Read(b []byte) (n int, err error) {
	chunkSize := (m.totalSize / 100) + 1
	lenB := int64(len(b))

	var nChunk int
	for start := int64(0); start < lenB; start += int64(nChunk) {
		end := start + chunkSize
		if end > lenB {
			end = lenB
		}

		nChunk, err = m.rd.Read(b[start:end])

		n += nChunk
		m.totalRead += int64(nChunk)

		if m.totalRead > (m.ticks * chunkSize) {
			m.ticks += 1
			// try to send on channel, but don't block if it's full
			select {
			case m.progress <- int(m.ticks + 1):
			default:
			}

			// give the progress channel consumer a chance to run
			runtime.Gosched()
		}

		if err != nil {
			return
		}
	}

	return
}

// We wrap the round tripper when making requests
// because we need to add headers to the requests we make
// even when they are requests made after a redirect
type RoundTripper struct {
	RoundTripFn func(*http.Request) (*http.Response, error)
}

func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
	return rt.RoundTripFn(r)
}

// Type Download encapsulates the necessary parameters and state
// needed to download an update from the internet. Create an instance
// with the NewDownload() factory function.
//
// You may only use a Download once,
type Download struct {
	// net/http.Client to use when downloading the update.
	// If nil, a default http.Client is used
	HttpClient *http.Client

	// Path on the file system to dowload the update to
	// If empty, a temporary file is used.
	// After the download begins, this path will be set
	// so that the client can use it to resume aborted
	// downloads
	Path string

	// Progress returns the percentage of the download
	// completed as an integer between 0 and 100
	Progress chan (int)

	// HTTP Method to use in the download request. Default is "GET"
	Method string

	// HTTP URL to issue the download request to
	Url string

	// Set to true when the server confirms a new version is available
	// even if the updating process encounters an error later on
	Available bool
}

// NewDownload initializes a new Download object
func NewDownload(url string) *Download {
	return &Download{
		HttpClient: new(http.Client),
		Progress:   make(chan int),
		Method:     "GET",
		Url: url,
	}
}

func (d *Download) sharedHttp(offset int64) (resp *http.Response, err error) {
	// create the download request
	req, err := http.NewRequest(d.Method, d.Url, nil)
	if err != nil {
		return
	}

	// we have to add headers like this so they get used across redirects
	trans := d.HttpClient.Transport
	if trans == nil {
		trans = http.DefaultTransport
	}

	d.HttpClient.Transport = &RoundTripper{
		RoundTripFn: func(r *http.Request) (*http.Response, error) {
			// add header for download continuation
			if offset > 0 {
				r.Header.Add("Range", fmt.Sprintf("%d-", offset))
			}

			// ask for gzipped content so that net/http won't unzip it for us
			// and destroy the content length header we need for progress calculations
			r.Header.Add("Accept-Encoding", "gzip")

			return trans.RoundTrip(r)
		},
	}

	// issue the download request
	return d.HttpClient.Do(req)
}

func (d *Download) Check() (available bool, err error) {
	resp, err := d.sharedHttp(0)
	if err != nil {
		return
	}
	resp.Body.Close()

	switch resp.StatusCode {
	// ok
	case 200, 206:
		available = true

	// no update available
	case 204:
		available = false

	// server error
	default:
		err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
		return
	}

	return
}

// Get() downloads the given url from the internet to a file on disk
// and then calls FromStream() to update the current program's executable file
// with the contents of that file.
//
// If the update is successful, the downloaded file will be erased from disk.
// Otherwise, it will remain in d.Path to allow the download to resume later
// or be skipped entirely.
//
// Only HTTP/1.1 servers that implement the Range header support resuming a
// partially completed download.
//
// UpdateFromUrl() uses HTTP status codes to determine what action to take.
//
// - The HTTP server should return 200 or 206 for the update to be downloaded.
//
// - The HTTP server should return 204 if no update is available at this time.
//
// - If the HTTP server returns a 3XX redirect, it will be followed
// according to d.HttpClient's redirect policy.
//
// - Any other HTTP status code will cause UpdateFromUrl to return an error.
func (d *Download) Get() (err error) {
	var offset int64 = 0
	var fp *os.File

	// Close the progress channel whenever this function completes
	defer close(d.Progress)

	// open a file where we will stream the downloaded update to
	// we do this first because if the caller specified a non-empty dlpath
	// we need to determine how large it is in order to resume the download
	if d.Path == "" {
		// no dlpath specified, use a random tempfile
		fp, err = ioutil.TempFile("", "update")
		if err != nil {
			return
		}
		defer fp.Close()

		// remember the path
		d.Path = fp.Name()
	} else {
		fp, err = os.OpenFile(d.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0600)
		if err != nil {
			return
		}
		defer fp.Close()

		// determine the file size so we can resume the download, if possible
		var fi os.FileInfo
		fi, err = fp.Stat()
		if err != nil {
			return
		}

		offset = fi.Size()
	}

	// start downloading the file
	resp, err := d.sharedHttp(offset)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	// ok
	case 200, 206:
		d.Available = true

	// no update available
	case 204:
		return

	// server error
	default:
		err = fmt.Errorf("Non 2XX response when downloading update: %s", resp.Status)
		return
	}

	// Determine how much we have to download
	// net/http sets this to -1 when it is unknown
	clength := resp.ContentLength

	// Read the content from the response body
	rd := resp.Body

	// meter the rate at which we download content for
	// progress reporting if we know how much to expect
	if clength > 0 {
		rd = &MeteredReader{rd: rd, totalSize: clength, progress: d.Progress}
	}

	// Decompress the content if necessary
	if resp.Header.Get("Content-Encoding") == "gzip" {
		rd, err = gzip.NewReader(rd)
		if err != nil {
			return
		}
	}

	// Download the update
	_, err = io.Copy(fp, rd)
	if err != nil {
		return
	}

	return
}

func (d *Download) GetAndUpdate() (err error, errRecover error) {
	// check before we download if this will work
	if err = SanityCheck(); err != nil {
		// keep the contract that d.Progress will close whenever Get() terminates
		close(d.Progress)
		return
	}

	// download the update
	if err = d.Get(); err != nil || !d.Available {
		return
	}

	// apply the update
	if err, errRecover = FromFile(d.Path); err != nil || errRecover != nil {
		return
	}

	// remove the temporary file
	os.Remove(d.Path)
	return
}

// FromUrl downloads the contents of the given url and uses them to update
// the current program's executable file. It is a convenience function which is equivalent to
//
// 	NewDownload(url).GetAndUpdate()
//
// See Download.Get() for more details.
func FromUrl(url string) (err error, errRecover error) {
	return NewDownload(url).GetAndUpdate()
}

// FromFile reads the contents of the given file and uses them
// to update the current program's executable file by calling FromStream().
func FromFile(filepath string) (err error, errRecover error) {
	// open the new binary
	fp, err := os.Open(filepath)
	if err != nil {
		return
	}
	defer fp.Close()

	// do the update
	return FromStream(fp)
}

// FromStream reads the contents of the supplied io.Reader newBinary
// and uses them to update the current program's executable file.
//
// FromStream performs the following actions to ensure a cross-platform safe
// update:
//
// - Creates a new file, /path/to/.program-name.new with mode 0755 and copies
// the contents of newBinary into the file
//
// - Renames the current program's executable file from /path/to/program-name
// to /path/to/.program-name.old
//
// - Renames /path/to/.program-name.new to /path/to/program-name
//
// - If the rename is successful, it erases /path/to/.program.old. If this operation
// fails, no error is reported.
//
// - If the rename is unsuccessful, it attempts to rename /path/to/.program-name.old
// back to /path/to/program-name. If this operation fails, the error is not reported
// in order to not mask the error that caused the rename recovery attempt.
func FromStream(newBinary io.Reader) (err error, errRecover error) {
	// get the path to the executable
	thisExecPath, err := osext.Executable()
	if err != nil {
		return
	}

	// get the directory the executable exists in
	execDir := filepath.Dir(thisExecPath)
	execName := filepath.Base(thisExecPath)

	// Copy the contents of of newbinary to a the new executable file
	newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
	fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if err != nil {
		return
	}
	defer fp.Close()
	_, err = io.Copy(fp, newBinary)

	// if we don't call fp.Close(), windows won't let us move the new executable
	// because the file will still be "in use"
	fp.Close()

	// this is where we'll move the executable to so that we can swap in the updated replacement
	oldExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.old", execName))

	// delete any existing old exec file - this is necessary on Windows for two reasons:
	// 1. after a successful update, windows can't remove the .old file because the process is still running
	// 2. windows rename operations fail if the destination file already exists
	_ = os.Remove(oldExecPath)

	// move the existing executable to a new file in the same directory
	err = os.Rename(thisExecPath, oldExecPath)
	if err != nil {
		return
	}

	// move the new exectuable in to become the new program
	err = os.Rename(newExecPath, thisExecPath)

	if err != nil {
		// copy unsuccessful
		errRecover = os.Rename(oldExecPath, thisExecPath)
	} else {
		// copy successful, remove the old binary
		_ = os.Remove(oldExecPath)
	}

	return
}

// SanityCheck() attempts to determine whether an in-place executable update could
// succeed by performing preliminary checks (to establish valid permissions, etc).
// This helps avoid downloading updates when we know the update can't be successfully
// applied later.
func SanityCheck() (err error) {
	// get the path to the executable
	thisExecPath, err := osext.Executable()
	if err != nil {
		return
	}

	// get the directory the executable exists in
	execDir := filepath.Dir(thisExecPath)
	execName := filepath.Base(thisExecPath)

	// attempt to open a file in the executable's directory
	newExecPath := filepath.Join(execDir, fmt.Sprintf(".%s.new", execName))
	fp, err := os.OpenFile(newExecPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
	if err != nil {
		return
	}
	fp.Close()

	_ = os.Remove(newExecPath)
	return
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha1"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"time"

	"github.com/dickeyxxx/golock"
	"github.com/franela/goreq"
)

var updateTopic = &Topic{
	Name:        "update",
	Description: "update heroku-cli",
}

var updateCmd = &Command{
	Topic:       "update",
	Hidden:      true,
	Description: "updates heroku-cli",
	Args:        []Arg{{Name: "channel", Optional: true}},
	Flags:       []Flag{{Name: "background", Hidden: true}},
	Run: func(ctx *Context) {
		channel := ctx.Args.(map[string]string)["channel"]
		if channel == "" {
			channel = Channel
		}
		t := "foreground"
		if ctx.Flags["background"] == true {
			t = "background"
		}
		Update(channel, t)
	},
}

var binPath = filepath.Join(AppDir, "heroku-cli")
var updateLockPath = filepath.Join(AppDir, "updating.lock")
var autoupdateFile = filepath.Join(AppDir, "autoupdate")

func init() {
	if runtime.GOOS == "windows" {
		binPath = binPath + ".exe"
	}
	goreq.SetConnectTimeout(5 * time.Second)
	goreq.DefaultTransport.TLSClientConfig = &tls.Config{RootCAs: getCACerts()}
}

func getCACerts() *x509.CertPool {
	certs := x509.NewCertPool()
	path := filepath.Join(AppDir, "cacert.pem")
	data, err := ioutil.ReadFile(path)
	if err != nil {
		downloadCert(path)
		data, err = ioutil.ReadFile(path)
		if err != nil {
			PrintError(err)
			return nil
		}
	}
	ok := certs.AppendCertsFromPEM(data)
	if !ok {
		Warn("Error parsing " + path)
		return nil
	}
	return certs
}

func downloadCert(path string) {
	f, err := os.Create(path)
	if err != nil {
		PrintError(err)
		return
	}
	res, err := goreq.Request{
		Uri:       "https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		PrintError(err)
		return
	}
	defer res.Body.Close()
	defer f.Close()
	io.Copy(f, res.Body)
}

// Update updates the CLI and plugins
func Update(channel string, t string) {
	if !IsUpdateNeeded(t) {
		return
	}
	done := make(chan bool)
	go func() {
		touchAutoupdateFile()
		updateCLI(channel)
		updatePlugins(t)
		done <- true
	}()
	select {
	case <-time.After(time.Second * 120):
		Errln("Timed out while updating")
	case <-done:
	}
}

func updatePlugins(t string) {
	updated := false
	plugins := PluginNames()
	if len(plugins) == 0 {
		return
	}
	Err("updating plugins... ")
	if t == "foreground" || t == "block" {
		b, _ := node.UpdatePackages()
		if len(b) > 0 {
			updated = true
		}
	} else {
		for _, name := range plugins {
			lockfile := updateLockPath + "." + name
			LogIfError(golock.Lock(lockfile))
			b, _ := node.UpdatePackage(name)
			LogIfError(golock.Unlock(lockfile))
			if len(b) > 0 {
				updated = true
			}
		}
	}
	Errln("done")
	if updated {
		Err("rebuilding plugins cache... ")
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	}
}

func updateCLI(channel string) {
	manifest, err := getUpdateManifest(channel)
	if err != nil {
		Warn("Error updating CLI")
		PrintError(err)
		return
	}
	if manifest.Version == Version && manifest.Channel == Channel {
		return
	}
	if !updatable() {
		Errf("Out of date: You are running %s but %s is out\n", Version, manifest.Version)
		return
	}
	LogIfError(golock.Lock(updateLockPath))
	defer golock.Unlock(updateLockPath)
	Errf("updating v4 CLI to %s (%s)... ", manifest.Version, manifest.Channel)
	build := manifest.Builds[runtime.GOOS][runtime.GOARCH]
	// on windows we can't remove an existing file or remove the running binary
	// so we download the file to binName.new
	// move the running binary to binName.old (deleting any existing file first)
	// rename the downloaded file to binName
	if err := downloadBin(binPath+".new", build.URL); err != nil {
		panic(err)
	}
	if fileSha1(binPath+".new") != build.Sha1 {
		panic("SHA mismatch")
	}
	os.Remove(binPath + ".old")
	os.Rename(binPath, binPath+".old")
	if err := os.Rename(binPath+".new", binPath); err != nil {
		panic(err)
	}
	os.Remove(binPath + ".old")
	Errln("done")
}

// IsUpdateNeeded checks if an update is available
func IsUpdateNeeded(t string) bool {
	f, err := os.Stat(autoupdateFile)
	if err != nil {
		return true
	}
	if t == "background" {
		return time.Since(f.ModTime()) > 4*time.Hour
	} else if t == "block" {
		return time.Since(f.ModTime()) > 168*time.Hour
	}
	return true
}

func touchAutoupdateFile() {
	out, err := os.OpenFile(autoupdateFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}
	out.WriteString(time.Now().String())
}

type manifest struct {
	Channel, Version string
	Builds           map[string]map[string]struct {
		URL, Sha1 string
	}
}

func getUpdateManifest(channel string) (*manifest, error) {
	res, err := goreq.Request{
		Uri:       "https://d1gvo455cekpjp.cloudfront.net/" + channel + "/manifest.json",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		return nil, err
	}
	var m manifest
	res.Body.FromJsonTo(&m)
	return &m, nil
}

func updatable() bool {
	path, err := filepath.Abs(os.Args[0])
	if err != nil {
		Errln(err)
	}
	return path == binPath
}

func downloadBin(path, url string) error {
	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
	if err != nil {
		return err
	}
	res, err := goreq.Request{
		Uri:       url + ".gz",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		return err
	}
	if res.StatusCode != 200 {
		b, _ := res.Body.ToString()
		return errors.New(b)
	}
	defer res.Body.Close()
	_, err = io.Copy(out, res.Body)
	if err != nil {
		return err
	}
	return out.Close()
}

func fileSha1(path string) string {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", sha1.Sum(data))
}

// TriggerBackgroundUpdate will trigger an update to the client in the background
func TriggerBackgroundUpdate() {
	if IsUpdateNeeded("background") {
		exec.Command(binPath, "update", "--background").Start()
	}
}
<commit_msg>only block for updates if it has been 2 weeks<commit_after>package main

import (
	"crypto/sha1"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"time"

	"github.com/dickeyxxx/golock"
	"github.com/franela/goreq"
)

var updateTopic = &Topic{
	Name:        "update",
	Description: "update heroku-cli",
}

var updateCmd = &Command{
	Topic:       "update",
	Hidden:      true,
	Description: "updates heroku-cli",
	Args:        []Arg{{Name: "channel", Optional: true}},
	Flags:       []Flag{{Name: "background", Hidden: true}},
	Run: func(ctx *Context) {
		channel := ctx.Args.(map[string]string)["channel"]
		if channel == "" {
			channel = Channel
		}
		t := "foreground"
		if ctx.Flags["background"] == true {
			t = "background"
		}
		Update(channel, t)
	},
}

var binPath = filepath.Join(AppDir, "heroku-cli")
var updateLockPath = filepath.Join(AppDir, "updating.lock")
var autoupdateFile = filepath.Join(AppDir, "autoupdate")

func init() {
	if runtime.GOOS == "windows" {
		binPath = binPath + ".exe"
	}
	goreq.SetConnectTimeout(5 * time.Second)
	goreq.DefaultTransport.TLSClientConfig = &tls.Config{RootCAs: getCACerts()}
}

func getCACerts() *x509.CertPool {
	certs := x509.NewCertPool()
	path := filepath.Join(AppDir, "cacert.pem")
	data, err := ioutil.ReadFile(path)
	if err != nil {
		downloadCert(path)
		data, err = ioutil.ReadFile(path)
		if err != nil {
			PrintError(err)
			return nil
		}
	}
	ok := certs.AppendCertsFromPEM(data)
	if !ok {
		Warn("Error parsing " + path)
		return nil
	}
	return certs
}

func downloadCert(path string) {
	f, err := os.Create(path)
	if err != nil {
		PrintError(err)
		return
	}
	res, err := goreq.Request{
		Uri:       "https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		PrintError(err)
		return
	}
	defer res.Body.Close()
	defer f.Close()
	io.Copy(f, res.Body)
}

// Update updates the CLI and plugins
func Update(channel string, t string) {
	if !IsUpdateNeeded(t) {
		return
	}
	done := make(chan bool)
	go func() {
		touchAutoupdateFile()
		updateCLI(channel)
		updatePlugins(t)
		done <- true
	}()
	select {
	case <-time.After(time.Second * 120):
		Errln("Timed out while updating")
	case <-done:
	}
}

func updatePlugins(t string) {
	updated := false
	plugins := PluginNames()
	if len(plugins) == 0 {
		return
	}
	Err("updating plugins... ")
	if t == "foreground" || t == "block" {
		b, _ := node.UpdatePackages()
		if len(b) > 0 {
			updated = true
		}
	} else {
		for _, name := range plugins {
			lockfile := updateLockPath + "." + name
			LogIfError(golock.Lock(lockfile))
			b, _ := node.UpdatePackage(name)
			LogIfError(golock.Unlock(lockfile))
			if len(b) > 0 {
				updated = true
			}
		}
	}
	Errln("done")
	if updated {
		Err("rebuilding plugins cache... ")
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	}
}

func updateCLI(channel string) {
	manifest, err := getUpdateManifest(channel)
	if err != nil {
		Warn("Error updating CLI")
		PrintError(err)
		return
	}
	if manifest.Version == Version && manifest.Channel == Channel {
		return
	}
	if !updatable() {
		Errf("Out of date: You are running %s but %s is out\n", Version, manifest.Version)
		return
	}
	LogIfError(golock.Lock(updateLockPath))
	defer golock.Unlock(updateLockPath)
	Errf("updating v4 CLI to %s (%s)... ", manifest.Version, manifest.Channel)
	build := manifest.Builds[runtime.GOOS][runtime.GOARCH]
	// on windows we can't remove an existing file or remove the running binary
	// so we download the file to binName.new
	// move the running binary to binName.old (deleting any existing file first)
	// rename the downloaded file to binName
	if err := downloadBin(binPath+".new", build.URL); err != nil {
		panic(err)
	}
	if fileSha1(binPath+".new") != build.Sha1 {
		panic("SHA mismatch")
	}
	os.Remove(binPath + ".old")
	os.Rename(binPath, binPath+".old")
	if err := os.Rename(binPath+".new", binPath); err != nil {
		panic(err)
	}
	os.Remove(binPath + ".old")
	Errln("done")
}

// IsUpdateNeeded checks if an update is available
func IsUpdateNeeded(t string) bool {
	f, err := os.Stat(autoupdateFile)
	if err != nil {
		return true
	}
	if t == "background" {
		return time.Since(f.ModTime()) > 4*time.Hour
	} else if t == "block" {
		return time.Since(f.ModTime()) > 336*time.Hour
	}
	return true
}

func touchAutoupdateFile() {
	out, err := os.OpenFile(autoupdateFile, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		panic(err)
	}
	out.WriteString(time.Now().String())
}

type manifest struct {
	Channel, Version string
	Builds           map[string]map[string]struct {
		URL, Sha1 string
	}
}

func getUpdateManifest(channel string) (*manifest, error) {
	res, err := goreq.Request{
		Uri:       "https://d1gvo455cekpjp.cloudfront.net/" + channel + "/manifest.json",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		return nil, err
	}
	var m manifest
	res.Body.FromJsonTo(&m)
	return &m, nil
}

func updatable() bool {
	path, err := filepath.Abs(os.Args[0])
	if err != nil {
		Errln(err)
	}
	return path == binPath
}

func downloadBin(path, url string) error {
	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
	if err != nil {
		return err
	}
	res, err := goreq.Request{
		Uri:       url + ".gz",
		ShowDebug: debugging,
	}.Do()
	if err != nil {
		return err
	}
	if res.StatusCode != 200 {
		b, _ := res.Body.ToString()
		return errors.New(b)
	}
	defer res.Body.Close()
	_, err = io.Copy(out, res.Body)
	if err != nil {
		return err
	}
	return out.Close()
}

func fileSha1(path string) string {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", sha1.Sum(data))
}

// TriggerBackgroundUpdate will trigger an update to the client in the background
func TriggerBackgroundUpdate() {
	if IsUpdateNeeded("background") {
		exec.Command(binPath, "update", "--background").Start()
	}
}
<|endoftext|>


<commit_before>// fcs-dev runs the lsst-ccs/fcs docker image with a few options
package main

import (
	"flag"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

var (
	lsst = flag.String("lsst", "", "path to LSST FCS code tree (default=$PWD)")
)

func main() {
	flag.Parse()
	if *lsst == "" || *lsst == "." {
		wd, err := os.Getwd()
		if err != nil {
			log.Fatalf("could not retrieve $PWD: %v\n", err)
		}
		*lsst = wd
	}

	sshdir := filepath.Join(os.Getenv("HOME"), ".ssh")
	gopath := strings.Split(os.Getenv("GOPATH"), ":")[0]

	subcmd := []string{
		"run", "-it",
		"-p=50000:50000",
		"--net=host",
		"-v", *lsst + ":/opt/lsst",
	}

	if _, err := os.Stat(sshdir); err == nil {
		subcmd = append(subcmd, "-v", sshdir+":/home/lsst/.ssh")
	}
	if _, err := os.Stat(gopath); err == nil && gopath != "" {
		subcmd = append(subcmd, "-v", gopath+":/go")
	}

	subcmd = append(subcmd, "lsst-ccs/fcs")

	switch flag.NArg() {
	case 0:
		subcmd = append(subcmd, "/bin/bash")
	default:
		subcmd = append(subcmd, flag.Args()...)
	}

	cmd := exec.Command("docker", subcmd...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err := cmd.Run()
	if err != nil {
		log.Fatalf("error running docker: %v\n", err)
	}
}
<commit_msg>fcs-dev: run with user/group privileges<commit_after>// fcs-dev runs the lsst-ccs/fcs docker image with a few options
package main

import (
	"flag"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"strings"
)

var (
	lsst = flag.String("lsst", "", "path to LSST FCS code tree (default=$PWD)")
)

func main() {
	flag.Parse()
	if *lsst == "" || *lsst == "." {
		wd, err := os.Getwd()
		if err != nil {
			log.Fatalf("could not retrieve $PWD: %v\n", err)
		}
		*lsst = wd
	}

	sshdir := filepath.Join(os.Getenv("HOME"), ".ssh")
	gopath := strings.Split(os.Getenv("GOPATH"), ":")[0]

	usr, err := user.Current()
	if err != nil {
		log.Fatalf("could not retrieve current user infos: %v\n", err)
	}

	subcmd := []string{
		"run", "-it",
		"-p=50000:50000",
		"--user=" + usr.Uid + ":" + usr.Gid,
		"--net=host",
		"-v", *lsst + ":/opt/lsst",
	}

	if _, err := os.Stat(sshdir); err == nil {
		subcmd = append(subcmd, "-v", sshdir+":/home/lsst/.ssh")
	}
	if _, err := os.Stat(gopath); err == nil && gopath != "" {
		subcmd = append(subcmd, "-v", gopath+":/go")
	}

	subcmd = append(subcmd, "lsst-ccs/fcs")

	switch flag.NArg() {
	case 0:
		subcmd = append(subcmd, "/bin/bash")
	default:
		subcmd = append(subcmd, flag.Args()...)
	}

	cmd := exec.Command("docker", subcmd...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err = cmd.Run()
	if err != nil {
		log.Fatalf("error running docker: %v\n", err)
	}
}
<|endoftext|>


<commit_before>package vanity // import "kkn.fi/vanity"

import (
	"fmt"
	"net/http"
)

type (
	// Path is the path component of the HTTP request sent by Go tool or browser.
	Path string
	// Package defines Go package that has vanity import defined by Path,
	// VCS system type and VCS URL.
	Package struct {
		// Path is path component of vanity url.
		Path Path
		// VCSSystem is version control system used by the project.
		// Usually 'git' or 'hg'.
		VCSSystem string
		// VCSURL is the HTTP URL for project's version control system.
		VCSURL string
	}
	// Server is the actual HTTP server for Go vanity domains.
	Server struct {
		// Domain is the vanity domain.
		Domain *string
		// Config contains settings for vanity packages.
		Config map[Path]Package
	}
)

// NewPackage returns a new Package given a path, VCS system and VCS URL.
func NewPackage(path, vcssystem, vcsurl string) *Package {
	p := &Package{
		Path:      Path(path),
		VCSSystem: vcssystem,
		VCSURL:    vcsurl,
	}
	return p
}

// NewServer returns a new Vanity Server given domain name and vanity package configuration.
func NewServer(domain string, config map[Path]Package) *Server {
	s := &Server{
		Domain: &domain,
		Config: config,
	}
	return s
}

func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Content-Type", "text/html; charset=utf-8")
	if r.Method != http.MethodGet {
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
		return
	}

	pack, ok := s.Config[Path(r.URL.Path)]
	if !ok {
		http.NotFound(w, r)
		return
	}
	if r.FormValue("go-get") != "1" {
		url := fmt.Sprintf("https://godoc.org/%v%v", *s.Domain, r.URL.Path)
		http.Redirect(w, r, url, http.StatusTemporaryRedirect)
		return
	}
	i := fmt.Sprintf("%s%s %s %s", *s.Domain, pack.Path, pack.VCSSystem, pack.VCSURL)
	fmt.Fprintf(w, `<meta name="go-import" content="%s">`, i)
}
<commit_msg>Add Package.GoImportLink()<commit_after>package vanity // import "kkn.fi/vanity"

import (
	"fmt"
	"net/http"
)

type (
	// Path is the path component of the HTTP request sent by Go tool or browser.
	Path string
	// Package defines Go package that has vanity import defined by Path,
	// VCS system type and VCS URL.
	Package struct {
		// Path is path component of vanity url.
		Path Path
		// VCSSystem is version control system used by the project.
		// Usually 'git' or 'hg'.
		VCSSystem string
		// VCSURL is the HTTP URL for project's version control system.
		VCSURL string
	}
	// Server is the actual HTTP server for Go vanity domains.
	Server struct {
		// Domain is the vanity domain.
		Domain *string
		// Config contains settings for vanity packages.
		Config map[Path]Package
	}
)

// NewPackage returns a new Package given a path, VCS system and VCS URL.
func NewPackage(path, vcssystem, vcsurl string) *Package {
	p := &Package{
		Path:      Path(path),
		VCSSystem: vcssystem,
		VCSURL:    vcsurl,
	}
	return p
}

// NewServer returns a new Vanity Server given domain name and vanity package configuration.
func NewServer(domain string, config map[Path]Package) *Server {
	s := &Server{
		Domain: &domain,
		Config: config,
	}
	return s
}

// GoImportLink creates the link used in HTML <meta/> tag
// where domain is the domain name of the server.
func (p Package) GoImportLink(domain string) string {
	return fmt.Sprintf("%s%s %s %s", domain, p.Path, p.VCSSystem, p.VCSURL)
}

func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Content-Type", "text/html; charset=utf-8")
	if r.Method != http.MethodGet {
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
		return
	}

	pack, ok := s.Config[Path(r.URL.Path)]
	if !ok {
		http.NotFound(w, r)
		return
	}
	if r.FormValue("go-get") != "1" {
		url := fmt.Sprintf("https://godoc.org/%v%v", *s.Domain, r.URL.Path)
		http.Redirect(w, r, url, http.StatusTemporaryRedirect)
		return
	}
	link := pack.GoImportLink(*s.Domain)
	fmt.Fprintf(w, `<meta name="go-import" content="%s">`, link)
}
<|endoftext|>