<commit_before>package scipipe

import (
	"fmt"
	"os/exec"
	re "regexp"
	str "strings"
	// "time"
	"errors"
)

type ShellTask struct {
	task
	_OutOnly     bool
	InPorts      map[string]chan *FileTarget
	InPaths      map[string]string
	OutPorts     map[string]chan *FileTarget
	OutPathFuncs map[string]func() string
	Command      string
}

func NewShellTask(command string) *ShellTask {
	return &ShellTask{
		Command:      command,
		InPorts:      make(map[string]chan *FileTarget),
		InPaths:      make(map[string]string),
		OutPorts:     make(map[string]chan *FileTarget),
		OutPathFuncs: make(map[string]func() string),
	}
}

func Sh(cmd string) *ShellTask {
	// Create task
	t := NewShellTask(cmd)

	// Find in/out port names, and set up in port lists
	r, err := re.Compile("{(o|i):([^{}:]+)}")
	Check(err)
	ms := r.FindAllStringSubmatch(cmd, -1)
	for _, m := range ms {
		if len(m) < 3 {
			Check(errors.New("Too few matches"))
		}
		typ := m[1]
		name := m[2]
		if typ == "o" {
			t.OutPorts[name] = make(chan *FileTarget, BUFSIZE)
		} // else if typ == "i" {
		// Set up a channel on the inports, even though this is
		// often replaced by another tasks output port channel.
		// It might be nice to have it init'ed with a channel
		// anyways, for use cases when we want to send FileTargets
		// on the inport manually.
		// t.InPorts[name] = make(chan *FileTarget, BUFSIZE)
		// }
	}
	return t
}

func (t *ShellTask) Run() {
	fmt.Println("Entering task: ", t.Command)
	defer t.closeOutChans()

	// Main loop
	continueLoop := true
	for continueLoop {
		// If there are no inports, we know we should exit the loop
		// directly after executing the command, and sending the outputs
		if len(t.InPorts) == 0 {
			continueLoop = false
		}

		// Read from inports
		inPortsOpen := t.receiveInputs()
		if !inPortsOpen {
			break
		}

		// Execute command
		t.formatAndExecute(t.Command)

		// Send
		t.sendOutputs()
	}
	fmt.Println("Exiting task:  ", t.Command)
}

func (t *ShellTask) closeOutChans() {
	// Close output channels
	for _, ochan := range t.OutPorts {
		close(ochan)
	}
}

func (t *ShellTask) receiveInputs() bool {
	inPortsOpen := true
	// Read input targets on in-ports and set up path mappings
	for iname, ichan := range t.InPorts {
		infile, open := <-ichan
		if !open {
			inPortsOpen = false
			continue
		}
		fmt.Println("Receiving file:", infile.GetPath())
		t.InPaths[iname] = infile.GetPath()
	}
	return inPortsOpen
}

func (t *ShellTask) sendOutputs() {
	// Send output targets on out ports
	for oname, ochan := range t.OutPorts {
		fun := t.OutPathFuncs[oname]
		baseName := fun()
		ft := NewFileTarget(baseName)
		fmt.Println("Sending file:  ", ft.GetPath())
		ochan <- ft
	}
}

func (t *ShellTask) formatAndExecute(cmd string) {
	cmd = t.replacePlaceholdersInCmd(cmd)
	fmt.Println("Executing cmd: ", cmd)
	_, err := exec.Command("bash", "-c", cmd).Output()
	Check(err)
}

func (t *ShellTask) replacePlaceholdersInCmd(cmd string) string {
	r, err := re.Compile("{(o|i):([^{}:]+)}")
	Check(err)
	ms := r.FindAllStringSubmatch(cmd, -1)
	for _, m := range ms {
		whole := m[0]
		typ := m[1]
		name := m[2]
		var newstr string
		if typ == "o" {
			if t.OutPathFuncs[name] != nil {
				newstr = t.OutPathFuncs[name]()
			} else {
				msg := fmt.Sprint("Missing outpath function for outport '", name, "' of shell task '", t.Command, "'")
				Check(errors.New(msg))
			}
		} else if typ == "i" {
			if t.InPaths[name] != "" {
				newstr = t.InPaths[name]
			} else {
				msg := fmt.Sprint("Missing inpath for inport '", name, "' of shell task '", t.Command, "'")
				Check(errors.New(msg))
			}
		}
		if newstr == "" {
			msg := fmt.Sprint("Replace failed for port ", name, " in task '", t.Command, "'")
			Check(errors.New(msg))
		}
		cmd = str.Replace(cmd, whole, newstr, -1)
	}
	return cmd
}

func (t *ShellTask) GetInPath(inPort string) string {
	var inPath string
	if t.InPaths[inPort] != "" {
		inPath = t.InPaths[inPort]
	} else {
		msg := fmt.Sprint("t.GetInPath(): Missing inpath for inport '", inPort, "' of shell task '", t.Command, "'")
		Check(errors.New(msg))
	}
	return inPath
}
<commit_msg>Refactor: Break out initPortsFromCmdPattern() from NewShellTask() factory method<commit_after>package scipipe

import (
	"fmt"
	"os/exec"
	re "regexp"
	str "strings"
	// "time"
	"errors"
)

type ShellTask struct {
	task
	_OutOnly     bool
	InPorts      map[string]chan *FileTarget
	InPaths      map[string]string
	OutPorts     map[string]chan *FileTarget
	OutPathFuncs map[string]func() string
	Command      string
}

func NewShellTask(command string) *ShellTask {
	return &ShellTask{
		Command:      command,
		InPorts:      make(map[string]chan *FileTarget),
		InPaths:      make(map[string]string),
		OutPorts:     make(map[string]chan *FileTarget),
		OutPathFuncs: make(map[string]func() string),
	}
}

func Sh(cmd string) *ShellTask {
	t := NewShellTask(cmd)
	t.initPortsFromCmdPattern(cmd)
	return t
}

func (t *ShellTask) initPortsFromCmdPattern(cmd string) {
	// Find in/out port names, and set up in port lists
	r, err := re.Compile("{(o|i):([^{}:]+)}")
	Check(err)
	ms := r.FindAllStringSubmatch(cmd, -1)
	for _, m := range ms {
		if len(m) < 3 {
			Check(errors.New("Too few matches"))
		}
		typ := m[1]
		name := m[2]
		if typ == "o" {
			t.OutPorts[name] = make(chan *FileTarget, BUFSIZE)
		} // else if typ == "i" {
		// Set up a channel on the inports, even though this is
		// often replaced by another tasks output port channel.
		// It might be nice to have it init'ed with a channel
		// anyways, for use cases when we want to send FileTargets
		// on the inport manually.
		// t.InPorts[name] = make(chan *FileTarget, BUFSIZE)
		// }
	}
}

func (t *ShellTask) Run() {
	fmt.Println("Entering task: ", t.Command)
	defer t.closeOutChans()

	// Main loop
	continueLoop := true
	for continueLoop {
		// If there are no inports, we know we should exit the loop
		// directly after executing the command, and sending the outputs
		if len(t.InPorts) == 0 {
			continueLoop = false
		}

		// Read from inports
		inPortsOpen := t.receiveInputs()
		if !inPortsOpen {
			break
		}

		// Execute command
		t.formatAndExecute(t.Command)

		// Send
		t.sendOutputs()
	}
	fmt.Println("Exiting task:  ", t.Command)
}

func (t *ShellTask) closeOutChans() {
	// Close output channels
	for _, ochan := range t.OutPorts {
		close(ochan)
	}
}

func (t *ShellTask) receiveInputs() bool {
	inPortsOpen := true
	// Read input targets on in-ports and set up path mappings
	for iname, ichan := range t.InPorts {
		infile, open := <-ichan
		if !open {
			inPortsOpen = false
			continue
		}
		fmt.Println("Receiving file:", infile.GetPath())
		t.InPaths[iname] = infile.GetPath()
	}
	return inPortsOpen
}

func (t *ShellTask) sendOutputs() {
	// Send output targets on out ports
	for oname, ochan := range t.OutPorts {
		fun := t.OutPathFuncs[oname]
		baseName := fun()
		ft := NewFileTarget(baseName)
		fmt.Println("Sending file:  ", ft.GetPath())
		ochan <- ft
	}
}

func (t *ShellTask) formatAndExecute(cmd string) {
	cmd = t.replacePlaceholdersInCmd(cmd)
	fmt.Println("Executing cmd: ", cmd)
	_, err := exec.Command("bash", "-c", cmd).Output()
	Check(err)
}

func (t *ShellTask) replacePlaceholdersInCmd(cmd string) string {
	r, err := re.Compile("{(o|i):([^{}:]+)}")
	Check(err)
	ms := r.FindAllStringSubmatch(cmd, -1)
	for _, m := range ms {
		whole := m[0]
		typ := m[1]
		name := m[2]
		var newstr string
		if typ == "o" {
			if t.OutPathFuncs[name] != nil {
				newstr = t.OutPathFuncs[name]()
			} else {
				msg := fmt.Sprint("Missing outpath function for outport '", name, "' of shell task '", t.Command, "'")
				Check(errors.New(msg))
			}
		} else if typ == "i" {
			if t.InPaths[name] != "" {
				newstr = t.InPaths[name]
			} else {
				msg := fmt.Sprint("Missing inpath for inport '", name, "' of shell task '", t.Command, "'")
				Check(errors.New(msg))
			}
		}
		if newstr == "" {
			msg := fmt.Sprint("Replace failed for port ", name, " in task '", t.Command, "'")
			Check(errors.New(msg))
		}
		cmd = str.Replace(cmd, whole, newstr, -1)
	}
	return cmd
}

func (t *ShellTask) GetInPath(inPort string) string {
	var inPath string
	if t.InPaths[inPort] != "" {
		inPath = t.InPaths[inPort]
	} else {
		msg := fmt.Sprint("t.GetInPath(): Missing inpath for inport '", inPort, "' of shell task '", t.Command, "'")
		Check(errors.New(msg))
	}
	return inPath
}
<|endoftext|>


<commit_before>package ss

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
)

// JSON field prefixes.
const (
	pre2D       = "media_box2d_"
	pre3D       = "media_box3d_"
	preFlyer    = "media_flyer_"
	preDate     = "date_"
	preGenre    = "genres_"
	preSynopsis = "synopsis_"
)

const (
	baseURL      = "http://www.screenscraper.fr/"
	gameInfoPath = "api/jeuInfos.php"
	userInfoPath = "api/ssuserInfos.php"
)

// ErrNotFound is the error returned when a ROM isn't found.
var ErrNotFound = errors.New("not found")

// DevInfo is the information about the developer and used across APIs.
type DevInfo struct {
	ID       string
	Password string
	Name     string
}

// UserInfo is information about the user making the call.
type UserInfo struct {
	ID       string
	Password string
}

type UserInfoResp struct {
	ID              string `xml:"ssuser>id"`
	Level           int    `xml:"ssuser>niveau"`
	Contribution    int    `xml:"ssuser>contribution"`
	UploadedSystems int    `xml:"ssuser>uploadsysteme"`
	UploadedInfo    int    `xml:"ssuser>uploadinfos"`
	ROMsAssociated  int    `xml:"ssuser>romasso"`
	UpdatedMedia    int    `xml:"ssuser>uploadmedia"`
	FavoriteRegion  string `xml:"ssuser>favregion"`
	MaxThreads      int    `xml:"ssuser>maxthreads"`
}

// GameInfoReq is the information we use in the GameInfo command.
type GameInfoReq struct {
	Name    string
	SHA1    string
	RomType string
}

type BoxArt struct {
	Box2D map[string]string `json:"media_boxs2d"`
	Box3D map[string]string `json:"media_boxs3d"`
}

type Media struct {
	Screenshot    string            `json:"media_screenshot"`
	ScreenMarquee string            `json:"media_screenmarquee"`
	Marquee       string            `json:"media_marquee"`
	Video         string            `json:"media_video"`
	Flyers        map[string]string `json:"media_flyers"`
	BoxArt        BoxArt            `json:"media_boxs"`
}

func getPrefix(m map[string]string, pre string) (string, bool) {
	for k, v := range m {
		if strings.HasPrefix(k, pre) {
			return v, true
		}
	}
	return "", false
}

func getSuffix(m map[string]string, pre string, suf []string) (string, bool) {
	if m == nil {
		return "", false
	}
	for _, x := range suf {
		if i, ok := m[pre+x]; ok {
			return i, true
		}
		if x == "xx" {
			if i, ok := getPrefix(m, pre); ok {
				return i, true
			}
		}
	}
	return "", false
}

func (m Media) Box2D(r []string) (string, bool) {
	return getSuffix(m.BoxArt.Box2D, pre2D, r)
}

func (m Media) Box3D(r []string) (string, bool) {
	return getSuffix(m.BoxArt.Box3D, pre3D, r)
}

func (m Media) Flyer(r []string) (string, bool) {
	return getSuffix(m.Flyers, preFlyer, r)
}

type ROM struct {
	FileName   string `json:"romfilename"`
	SHA1       string `json:"romsha1"`
	RegionsRaw string `json:"romregions"`
}

func (r ROM) Regions() []string {
	var x []string
	for _, p := range strings.Split(r.RegionsRaw, ",") {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		x = append(x, p)
	}
	return x
}

type Game struct {
	Synopsis  map[string]string          `json:"synopsis"`
	ID        string                     `json:"id"`
	Name      string                     `json:"nom"`
	Names     map[string]string          `json:"noms"`
	Regions   []string                   `json:"regionshortnames"`
	Publisher string                     `json:"editeur"`
	Developer string                     `json:"developpeur"`
	Players   string                     `json:"joueurs"`
	Rating    string                     `json:"note"`
	Dates     map[string]string          `json:"dates"`
	Genres    map[string]json.RawMessage `json:"genres:`
	Media     Media                      `json:"medias"`
	ROMs      []ROM                      `json:"roms"`
	genres    map[string]string
}

func (g Game) Date(r []string) (string, bool) {
	return getSuffix(g.Dates, preDate, r)
}

func (g *Game) decodeGenre() {
	g.genres = make(map[string]string)
	for k, v := range g.Genres {
		if strings.HasSuffix(k, "_medias") || strings.HasSuffix(k, "_id") {
			continue
		}
		s := []string{}
		if err := json.Unmarshal(v, &s); err == nil {
			g.genres[k] = strings.Join(s, " / ")
		}
	}
}

func (g Game) Genre(l []string) (string, bool) {
	if g.genres == nil {
		g.decodeGenre()
	}
	return getSuffix(g.genres, preGenre, l)
}

func (g Game) Desc(l []string) (string, bool) {
	return getSuffix(g.Synopsis, preSynopsis, l)
}

func (g Game) ROM(req GameInfoReq) ROM {
	for _, x := range g.ROMs {
		if x.SHA1 == req.SHA1 {
			return x
		}
	}
	for _, x := range g.ROMs {
		if x.FileName == req.Name {
			return x
		}
	}
	return ROM{}
}

type Response struct {
	Game Game `json:"jeu"`
}

type GameInfoResp struct {
	Response Response `json:"response"`
}

func SanitizeURL(s string) string {
	u, err := url.Parse(s)
	if err != nil {
		return s
	}
	v := u.Query()
	v.Set("devid", "xxx")
	v.Set("devpassword", "yyy")
	v.Set("softname", "zzz")
	v.Del("ssid")
	v.Del("sspassword")
	u.RawQuery = v.Encode()
	return u.String()
}

func User(dev DevInfo, user UserInfo) (*UserInfoResp, error) {
	u, err := url.Parse(baseURL)
	u.Path = userInfoPath
	q := url.Values{}
	q.Set("output", "xml")
	q.Set("devid", dev.ID)
	q.Set("devpassword", dev.Password)
	if dev.Name != "" {
		q.Set("softname", dev.Name)
	}
	q.Set("ssid", user.ID)
	q.Set("sspassword", user.Password)
	u.RawQuery = q.Encode()
	resp, err := http.Get(u.String())
	if err != nil {
		v := u.Query()
		v.Set("devid", "xxx")
		v.Set("devpassword", "yyy")
		v.Set("softname", "zzz")
		u.RawQuery = v.Encode()
		return nil, fmt.Errorf("getting game url:%s, error:%s", u, err)
	}
	defer resp.Body.Close()
	r := &UserInfoResp{}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if err := xml.Unmarshal(b, r); err != nil {
		return nil, fmt.Errorf("ss: cannot parse response: %q", err)
	}
	return r, nil
}

func Threads(dev DevInfo, user UserInfo) int {
	if user.ID == "" || user.Password == "" {
		return 1
	}
	i, err := User(dev, user)
	if err != nil {
		log.Print("error getting allowed threads defaulting to 1")
		return 1
	}
	if i.MaxThreads < 1 {
		return 1
	}
	return i.MaxThreads
}

// GameInfo is the call to get game info.
func GameInfo(dev DevInfo, user UserInfo, req GameInfoReq) (*GameInfoResp, error) {
	u, err := url.Parse(baseURL)
	u.Path = gameInfoPath
	q := url.Values{}
	q.Set("output", "json")
	q.Set("devid", dev.ID)
	q.Set("devpassword", dev.Password)
	if dev.Name != "" {
		q.Set("softname", dev.Name)
	}
	if user.ID != "" {
		q.Set("ssid", user.ID)
	}
	if user.Password != "" {
		q.Set("sspassword", user.Password)
	}
	if req.SHA1 != "" {
		q.Set("sha1", req.SHA1)
	}
	if req.RomType == "" {
		q.Set("romtype", "rom")
	} else {
		q.Set("romtype", req.RomType)
	}
	if req.Name != "" {
		q.Set("romnom", req.Name)
	}
	u.RawQuery = q.Encode()
	resp, err := http.Get(u.String())
	if err != nil {
		if uerr, ok := err.(*url.Error); ok {
			uerr.URL = SanitizeURL(uerr.URL)
			return nil, uerr
		}
		return nil, err
	}
	defer resp.Body.Close()
	r := &GameInfoResp{}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if bytes.HasPrefix(b, []byte("Erreur : Rom/Iso/Dossier non trouv")) {
		return nil, ErrNotFound
	}
	if err := json.Unmarshal(b, r); err != nil {
		if err.Error() == "invalid character 'm' looking for beginning of value" {
			return nil, fmt.Errorf("ss: cannot parse response(%s): %q", string(b), err)
		}
		if err.Error() == "invalid character 'A' looking for beginning of value" {
			return nil, fmt.Errorf("ss: cannot parse response(%s): %q", string(b), err)
		}
		return nil, fmt.Errorf("ss: cannot parse response: %q", err)
	}
	return r, nil
}
<commit_msg>Harden code to future changes in ScreenScraper API.<commit_after>package ss

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
)

// JSON field prefixes.
const (
	pre2D       = "media_box2d_"
	pre3D       = "media_box3d_"
	preFlyer    = "media_flyer_"
	preDate     = "date_"
	preGenre    = "genres_"
	preSynopsis = "synopsis_"
)

const (
	baseURL      = "http://www.screenscraper.fr/"
	gameInfoPath = "api/jeuInfos.php"
	userInfoPath = "api/ssuserInfos.php"
)

// ErrNotFound is the error returned when a ROM isn't found.
var ErrNotFound = errors.New("not found")

// DevInfo is the information about the developer and used across APIs.
type DevInfo struct {
	ID       string
	Password string
	Name     string
}

// UserInfo is information about the user making the call.
type UserInfo struct {
	ID       string
	Password string
}

type UserInfoResp struct {
	ID              string `xml:"ssuser>id"`
	Level           int    `xml:"ssuser>niveau"`
	Contribution    int    `xml:"ssuser>contribution"`
	UploadedSystems int    `xml:"ssuser>uploadsysteme"`
	UploadedInfo    int    `xml:"ssuser>uploadinfos"`
	ROMsAssociated  int    `xml:"ssuser>romasso"`
	UpdatedMedia    int    `xml:"ssuser>uploadmedia"`
	FavoriteRegion  string `xml:"ssuser>favregion"`
	MaxThreads      int    `xml:"ssuser>maxthreads"`
}

// GameInfoReq is the information we use in the GameInfo command.
type GameInfoReq struct {
	Name    string
	SHA1    string
	RomType string
}

type SafeStringMap struct {
	Map map[string]string
}

func (s *SafeStringMap) UnmarshalJSON(b []byte) error {
	if s.Map == nil {
		s.Map = make(map[string]string)
	}
	x := make(map[string]json.RawMessage)
	if err := json.Unmarshal(b, &x); err != nil {
		log.Print("json: %v", err)
		return nil
	}
	for k, v := range x {
		var y string
		if err := json.Unmarshal(v, &y); err == nil {
			s.Map[k] = y
		}
	}
	return nil
}

type BoxArt struct {
	Box2D SafeStringMap `json:"media_boxs2d"`
	Box3D SafeStringMap `json:"media_boxs3d"`
}

type Media struct {
	Screenshot    string        `json:"media_screenshot"`
	ScreenMarquee string        `json:"media_screenmarquee"`
	Marquee       string        `json:"media_marquee"`
	Video         string        `json:"media_video"`
	Flyers        SafeStringMap `json:"media_flyers"`
	BoxArt        BoxArt        `json:"media_boxs"`
}

func getPrefix(m map[string]string, pre string) (string, bool) {
	for k, v := range m {
		if strings.HasPrefix(k, pre) {
			return v, true
		}
	}
	return "", false
}

func getSuffix(m map[string]string, pre string, suf []string) (string, bool) {
	if m == nil {
		return "", false
	}
	for _, x := range suf {
		if i, ok := m[pre+x]; ok {
			return i, true
		}
		if x == "xx" {
			if i, ok := getPrefix(m, pre); ok {
				return i, true
			}
		}
	}
	return "", false
}

func (m Media) Box2D(r []string) (string, bool) {
	return getSuffix(m.BoxArt.Box2D.Map, pre2D, r)
}

func (m Media) Box3D(r []string) (string, bool) {
	return getSuffix(m.BoxArt.Box3D.Map, pre3D, r)
}

func (m Media) Flyer(r []string) (string, bool) {
	return getSuffix(m.Flyers.Map, preFlyer, r)
}

type ROM struct {
	FileName   string `json:"romfilename"`
	SHA1       string `json:"romsha1"`
	RegionsRaw string `json:"romregions"`
}

func (r ROM) Regions() []string {
	var x []string
	for _, p := range strings.Split(r.RegionsRaw, ",") {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		x = append(x, p)
	}
	return x
}

type Game struct {
	Synopsis  SafeStringMap              `json:"synopsis"`
	ID        string                     `json:"id"`
	Name      string                     `json:"nom"`
	Names     SafeStringMap              `json:"noms"`
	Regions   []string                   `json:"regionshortnames"`
	Publisher string                     `json:"editeur"`
	Developer string                     `json:"developpeur"`
	Players   string                     `json:"joueurs"`
	Rating    string                     `json:"note"`
	Dates     SafeStringMap              `json:"dates"`
	Genres    map[string]json.RawMessage `json:"genres:`
	Media     Media                      `json:"medias"`
	ROMs      []ROM                      `json:"roms"`
	genres    map[string]string
}

func (g Game) Date(r []string) (string, bool) {
	return getSuffix(g.Dates.Map, preDate, r)
}

func (g *Game) decodeGenre() {
	g.genres = make(map[string]string)
	for k, v := range g.Genres {
		if strings.HasSuffix(k, "_medias") || strings.HasSuffix(k, "_id") {
			continue
		}
		s := []string{}
		if err := json.Unmarshal(v, &s); err == nil {
			g.genres[k] = strings.Join(s, " / ")
		}
	}
}

func (g Game) Genre(l []string) (string, bool) {
	if g.genres == nil {
		g.decodeGenre()
	}
	return getSuffix(g.genres, preGenre, l)
}

func (g Game) Desc(l []string) (string, bool) {
	return getSuffix(g.Synopsis.Map, preSynopsis, l)
}

func (g Game) ROM(req GameInfoReq) ROM {
	for _, x := range g.ROMs {
		if strings.ToLower(x.SHA1) == strings.ToLower(req.SHA1) {
			return x
		}
	}
	for _, x := range g.ROMs {
		if x.FileName == req.Name {
			return x
		}
	}
	return ROM{}
}

type Response struct {
	Game Game `json:"jeu"`
}

type GameInfoResp struct {
	Response Response `json:"response"`
}

func SanitizeURL(s string) string {
	u, err := url.Parse(s)
	if err != nil {
		return s
	}
	v := u.Query()
	v.Set("devid", "xxx")
	v.Set("devpassword", "yyy")
	v.Set("softname", "zzz")
	v.Del("ssid")
	v.Del("sspassword")
	u.RawQuery = v.Encode()
	return u.String()
}

func User(dev DevInfo, user UserInfo) (*UserInfoResp, error) {
	u, err := url.Parse(baseURL)
	u.Path = userInfoPath
	q := url.Values{}
	q.Set("output", "xml")
	q.Set("devid", dev.ID)
	q.Set("devpassword", dev.Password)
	if dev.Name != "" {
		q.Set("softname", dev.Name)
	}
	q.Set("ssid", user.ID)
	q.Set("sspassword", user.Password)
	u.RawQuery = q.Encode()
	resp, err := http.Get(u.String())
	if err != nil {
		v := u.Query()
		v.Set("devid", "xxx")
		v.Set("devpassword", "yyy")
		v.Set("softname", "zzz")
		u.RawQuery = v.Encode()
		return nil, fmt.Errorf("getting game url:%s, error:%s", u, err)
	}
	defer resp.Body.Close()
	r := &UserInfoResp{}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if err := xml.Unmarshal(b, r); err != nil {
		return nil, fmt.Errorf("ss: cannot parse response: %q", err)
	}
	return r, nil
}

func Threads(dev DevInfo, user UserInfo) int {
	if user.ID == "" || user.Password == "" {
		return 1
	}
	i, err := User(dev, user)
	if err != nil {
		log.Print("error getting allowed threads defaulting to 1")
		return 1
	}
	if i.MaxThreads < 1 {
		return 1
	}
	return i.MaxThreads
}

// GameInfo is the call to get game info.
func GameInfo(dev DevInfo, user UserInfo, req GameInfoReq) (*GameInfoResp, error) {
	u, err := url.Parse(baseURL)
	u.Path = gameInfoPath
	q := url.Values{}
	q.Set("output", "json")
	q.Set("devid", dev.ID)
	q.Set("devpassword", dev.Password)
	if dev.Name != "" {
		q.Set("softname", dev.Name)
	}
	if user.ID != "" {
		q.Set("ssid", user.ID)
	}
	if user.Password != "" {
		q.Set("sspassword", user.Password)
	}
	if req.SHA1 != "" {
		q.Set("sha1", req.SHA1)
	}
	if req.RomType == "" {
		q.Set("romtype", "rom")
	} else {
		q.Set("romtype", req.RomType)
	}
	if req.Name != "" {
		q.Set("romnom", req.Name)
	}
	u.RawQuery = q.Encode()
	resp, err := http.Get(u.String())
	if err != nil {
		if uerr, ok := err.(*url.Error); ok {
			uerr.URL = SanitizeURL(uerr.URL)
			return nil, uerr
		}
		return nil, err
	}
	defer resp.Body.Close()
	r := &GameInfoResp{}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if bytes.HasPrefix(b, []byte("Erreur : Rom/Iso/Dossier non trouv")) {
		return nil, ErrNotFound
	}
	if err := json.Unmarshal(b, r); err != nil {
		if err.Error() == "invalid character 'm' looking for beginning of value" {
			return nil, fmt.Errorf("ss: cannot parse response(%s): %q", string(b), err)
		}
		if err.Error() == "invalid character 'A' looking for beginning of value" {
			return nil, fmt.Errorf("ss: cannot parse response(%s): %q", string(b), err)
		}
		return nil, fmt.Errorf("ss: cannot parse response: %q", err)
	}
	return r, nil
}
<|endoftext|>


<commit_before>package boardgame

//Stack is one of the fundamental types in BoardGame. It represents an ordered
//stack of 0 or more Components, all from the same Deck. Each deck has 0 or
//more Stacks based off of it, and together they include all components in
//that deck, with no component residing in more than one stack. Stacks model
//things like a stack of cards, a collection of resource tokens, etc.
type Stack struct {
	//The Game that we're associated with
	Game *Game
	//DeckName is the name of the deck in the game's ComponentChest that this
	//Stack is tied to.
	DeckName string
	//The indexes from the given deck that this stack contains, in order.
	Indexes []int
}

//Len returns the number of items in the stack.
func (s *Stack) Len() int {
	return len(s.Indexes)
}

//ComponentAt fetches the component object representing the n-th object in
//this stack.
func (s *Stack) ComponentAt(index int) Component {

	if index >= s.Len() || index < 0 {
		return nil
	}

	deck := s.Game.Chest[s.DeckName]

	if deck == nil {
		return nil
	}

	//We don't need to check that s.Indexes[index] is valid because it was
	//checked when it was set, and Decks are immutable.
	return deck.Components[s.Indexes[index]]
}

//InsertFront puts the component at index 0 in this stack, moving all other
//items down by one. The Component you insert should not currently be a member
//of any other stacks, to maintain the deck invariant.
func (s *Stack) InsertFront(c Component) {
	if c.Deck() != s.DeckName {
		//We can only add items that are in our deck.

		//TODO: communicate an error
		return
	}

	s.Indexes = append([]int{c.DeckIndex()}, s.Indexes...)
}

/*

//InsertBack puts the component at the last index in this stack. The
//Component you insert should not currently be a member of any other stacks,
//to maintain the deck invariant.
func (s *Stack) InsertBack(c Component) {

}

//RemoveFront removes the component from the first slot in this stack,
//shifting all later components down by 1. You should then insert the
//component in another stack to maintain the deck invariant.
func (s *Stack) RemoveFront() Component {

}

//RemoveBack removes the component from the last slot in this stack. You
//should then insert the component in another stack to maintain the deck
//invariant.
func (s *Stack) RemoveBack() Component {

}

*/
<commit_msg>Verify that a component is legal for this game before being inserted into stack.<commit_after>package boardgame

//Stack is one of the fundamental types in BoardGame. It represents an ordered
//stack of 0 or more Components, all from the same Deck. Each deck has 0 or
//more Stacks based off of it, and together they include all components in
//that deck, with no component residing in more than one stack. Stacks model
//things like a stack of cards, a collection of resource tokens, etc.
type Stack struct {
	//The Game that we're associated with
	Game *Game
	//DeckName is the name of the deck in the game's ComponentChest that this
	//Stack is tied to.
	DeckName string
	//The indexes from the given deck that this stack contains, in order.
	Indexes []int
}

//Len returns the number of items in the stack.
func (s *Stack) Len() int {
	return len(s.Indexes)
}

//ComponentAt fetches the component object representing the n-th object in
//this stack.
func (s *Stack) ComponentAt(index int) Component {

	if index >= s.Len() || index < 0 {
		return nil
	}

	deck := s.Game.Chest[s.DeckName]

	if deck == nil {
		return nil
	}

	//We don't need to check that s.Indexes[index] is valid because it was
	//checked when it was set, and Decks are immutable.
	return deck.Components[s.Indexes[index]]
}

//InsertFront puts the component at index 0 in this stack, moving all other
//items down by one. The Component you insert should not currently be a member
//of any other stacks, to maintain the deck invariant.
func (s *Stack) InsertFront(c Component) {

	if c.GameName() != s.Game.Name {
		//We can only add components designed for this game
		return
	}

	if c.Deck() != s.DeckName {
		//We can only add items that are in our deck.

		//TODO: communicate an error
		return
	}

	s.Indexes = append([]int{c.DeckIndex()}, s.Indexes...)
}

/*

//InsertBack puts the component at the last index in this stack. The
//Component you insert should not currently be a member of any other stacks,
//to maintain the deck invariant.
func (s *Stack) InsertBack(c Component) {

}

//RemoveFront removes the component from the first slot in this stack,
//shifting all later components down by 1. You should then insert the
//component in another stack to maintain the deck invariant.
func (s *Stack) RemoveFront() Component {

}

//RemoveBack removes the component from the last slot in this stack. You
//should then insert the component in another stack to maintain the deck
//invariant.
func (s *Stack) RemoveBack() Component {

}

*/
<|endoftext|>


<commit_before>package slack

import (
	"context"
	"net/url"
	"strconv"
)

const (
	DEFAULT_STARS_USER  = ""
	DEFAULT_STARS_COUNT = 100
	DEFAULT_STARS_PAGE  = 1
)

type StarsParameters struct {
	User  string
	Count int
	Page  int
}

type StarredItem Item

type listResponseFull struct {
	Items  []Item `json:"items"`
	Paging `json:"paging"`
	SlackResponse
}

// NewStarsParameters initialises StarsParameters with default values
func NewStarsParameters() StarsParameters {
	return StarsParameters{
		User:  DEFAULT_STARS_USER,
		Count: DEFAULT_STARS_COUNT,
		Page:  DEFAULT_STARS_PAGE,
	}
}

// AddStar stars an item in a channel
func (api *Client) AddStar(channel string, item ItemRef) error {
	return api.AddStarContext(context.Background(), channel, item)
}

// AddStarContext stars an item in a channel with a custom context
func (api *Client) AddStarContext(ctx context.Context, channel string, item ItemRef) error {
	values := url.Values{
		"channel": {channel},
		"token":   {api.token},
	}
	if item.Timestamp != "" {
		values.Set("timestamp", item.Timestamp)
	}
	if item.File != "" {
		values.Set("file", item.File)
	}
	if item.Comment != "" {
		values.Set("file_comment", item.Comment)
	}

	response := &SlackResponse{}
	if err := api.postMethod(ctx, "stars.add", values, response); err != nil {
		return err
	}

	return response.Err()
}

// RemoveStar removes a starred item from a channel
func (api *Client) RemoveStar(channel string, item ItemRef) error {
	return api.RemoveStarContext(context.Background(), channel, item)
}

// RemoveStarContext removes a starred item from a channel with a custom context
func (api *Client) RemoveStarContext(ctx context.Context, channel string, item ItemRef) error {
	values := url.Values{
		"channel": {channel},
		"token":   {api.token},
	}
	if item.Timestamp != "" {
		values.Set("timestamp", item.Timestamp)
	}
	if item.File != "" {
		values.Set("file", item.File)
	}
	if item.Comment != "" {
		values.Set("file_comment", item.Comment)
	}

	response := &SlackResponse{}
	if err := api.postMethod(ctx, "stars.remove", values, response); err != nil {
		return err
	}

	return response.Err()
}

// ListStars returns information about the stars a user added
func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
	return api.ListStarsContext(context.Background(), params)
}

// ListStarsContext returns information about the stars a user added with a custom context
func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) ([]Item, *Paging, error) {
	values := url.Values{
		"token": {api.token},
	}
	if params.User != DEFAULT_STARS_USER {
		values.Add("user", params.User)
	}
	if params.Count != DEFAULT_STARS_COUNT {
		values.Add("count", strconv.Itoa(params.Count))
	}
	if params.Page != DEFAULT_STARS_PAGE {
		values.Add("page", strconv.Itoa(params.Page))
	}

	response := &listResponseFull{}
	err := api.postMethod(ctx, "stars.list", values, response)
	if err != nil {
		return nil, nil, err
	}

	if err := response.Err(); err != nil {
		return nil, nil, err
	}

	return response.Items, &response.Paging, nil
}

// GetStarred returns a list of StarredItem items.
//
// The user then has to iterate over them and figure out what they should
// be looking at according to what is in the Type.
//    for _, item := range items {
//        switch c.Type {
//        case "file_comment":
//            log.Println(c.Comment)
//        case "file":
//             ...
//
//    }
// This function still exists to maintain backwards compatibility.
// I exposed it as returning []StarredItem, so it shall stay as StarredItem
func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) {
	return api.GetStarredContext(context.Background(), params)
}

// GetStarredContext returns a list of StarredItem items with a custom context
//
// For more details see GetStarred
func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters) ([]StarredItem, *Paging, error) {
	items, paging, err := api.ListStarsContext(ctx, params)
	if err != nil {
		return nil, nil, err
	}
	starredItems := make([]StarredItem, len(items))
	for i, item := range items {
		starredItems[i] = StarredItem(item)
	}
	return starredItems, paging, nil
}

type listResponsePaginated struct {
	Items []Item `json:"items"`
	SlackResponse
	Metadata ResponseMetadata `json:"response_metadata"`
}

// StarredItemPagination allows for paginating over the starred items
type StarredItemPagination struct {
	Items        []Item
	limit        int
	previousResp *ResponseMetadata
	c            *Client
}

// ListStarsOption options for the GetUsers method call.
type ListStarsOption func(*StarredItemPagination)

// ListAllStars returns the complete list of starred items
func (api *Client) ListAllStars() ([]Item, error) {
	return api.ListAllStarsContext(context.Background())
}

// ListAllStarsContext returns the list of users (with their detailed information) with a custom context
func (api *Client) ListAllStarsContext(ctx context.Context) (results []Item, err error) {
	var p StarredItemPagination

	for p = api.ListStarsPaginated(); !p.done(err); p, err = p.next(ctx) {
		results = append(results, p.Items...)
	}

	return results, p.failure(err)
}

// ListStarsPaginated fetches users in a paginated fashion, see ListStarsPaginationContext for usage.
func (api *Client) ListStarsPaginated(options ...ListStarsOption) StarredItemPagination {
	return newStarPagination(api, options...)
}

func newStarPagination(c *Client, options ...ListStarsOption) (sip StarredItemPagination) {
	sip = StarredItemPagination{
		c:     c,
		limit: 200, // per slack api documentation.
	}

	for _, opt := range options {
		opt(&sip)
	}

	return sip
}

// done checks if the pagination has completed
func (StarredItemPagination) done(err error) bool {
	return err == errPaginationComplete
}

// done checks if pagination failed.
func (t StarredItemPagination) failure(err error) error {
	if t.done(err) {
		return nil
	}

	return err
}

// next gets the next list of starred items based on the cursor value
func (t StarredItemPagination) next(ctx context.Context) (_ StarredItemPagination, err error) {
	var (
		resp *listResponsePaginated
	)

	if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") {
		return t, errPaginationComplete
	}

	t.previousResp = t.previousResp.initialize()

	values := url.Values{
		"limit":  {strconv.Itoa(t.limit)},
		"token":  {t.c.token},
		"cursor": {t.previousResp.Cursor},
	}

	if err = t.c.postMethod(ctx, "stars.list", values, &resp); err != nil {
		return t, err
	}

	t.previousResp = &resp.Metadata
	t.Items = resp.Items

	return t, nil
}
<commit_msg>add rate limiting to ListAllStars method<commit_after>package slack

import (
	"context"
	"net/url"
	"strconv"
	"time"
)

const (
	DEFAULT_STARS_USER  = ""
	DEFAULT_STARS_COUNT = 100
	DEFAULT_STARS_PAGE  = 1
)

type StarsParameters struct {
	User  string
	Count int
	Page  int
}

type StarredItem Item

type listResponseFull struct {
	Items  []Item `json:"items"`
	Paging `json:"paging"`
	SlackResponse
}

// NewStarsParameters initialises StarsParameters with default values
func NewStarsParameters() StarsParameters {
	return StarsParameters{
		User:  DEFAULT_STARS_USER,
		Count: DEFAULT_STARS_COUNT,
		Page:  DEFAULT_STARS_PAGE,
	}
}

// AddStar stars an item in a channel
func (api *Client) AddStar(channel string, item ItemRef) error {
	return api.AddStarContext(context.Background(), channel, item)
}

// AddStarContext stars an item in a channel with a custom context
func (api *Client) AddStarContext(ctx context.Context, channel string, item ItemRef) error {
	values := url.Values{
		"channel": {channel},
		"token":   {api.token},
	}
	if item.Timestamp != "" {
		values.Set("timestamp", item.Timestamp)
	}
	if item.File != "" {
		values.Set("file", item.File)
	}
	if item.Comment != "" {
		values.Set("file_comment", item.Comment)
	}

	response := &SlackResponse{}
	if err := api.postMethod(ctx, "stars.add", values, response); err != nil {
		return err
	}

	return response.Err()
}

// RemoveStar removes a starred item from a channel
func (api *Client) RemoveStar(channel string, item ItemRef) error {
	return api.RemoveStarContext(context.Background(), channel, item)
}

// RemoveStarContext removes a starred item from a channel with a custom context
func (api *Client) RemoveStarContext(ctx context.Context, channel string, item ItemRef) error {
	values := url.Values{
		"channel": {channel},
		"token":   {api.token},
	}
	if item.Timestamp != "" {
		values.Set("timestamp", item.Timestamp)
	}
	if item.File != "" {
		values.Set("file", item.File)
	}
	if item.Comment != "" {
		values.Set("file_comment", item.Comment)
	}

	response := &SlackResponse{}
	if err := api.postMethod(ctx, "stars.remove", values, response); err != nil {
		return err
	}

	return response.Err()
}

// ListStars returns information about the stars a user added
func (api *Client) ListStars(params StarsParameters) ([]Item, *Paging, error) {
	return api.ListStarsContext(context.Background(), params)
}

// ListStarsContext returns information about the stars a user added with a custom context
func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters) ([]Item, *Paging, error) {
	values := url.Values{
		"token": {api.token},
	}
	if params.User != DEFAULT_STARS_USER {
		values.Add("user", params.User)
	}
	if params.Count != DEFAULT_STARS_COUNT {
		values.Add("count", strconv.Itoa(params.Count))
	}
	if params.Page != DEFAULT_STARS_PAGE {
		values.Add("page", strconv.Itoa(params.Page))
	}

	response := &listResponseFull{}
	err := api.postMethod(ctx, "stars.list", values, response)
	if err != nil {
		return nil, nil, err
	}

	if err := response.Err(); err != nil {
		return nil, nil, err
	}

	return response.Items, &response.Paging, nil
}

// GetStarred returns a list of StarredItem items.
//
// The user then has to iterate over them and figure out what they should
// be looking at according to what is in the Type.
//    for _, item := range items {
//        switch c.Type {
//        case "file_comment":
//            log.Println(c.Comment)
//        case "file":
//             ...
//
//    }
// This function still exists to maintain backwards compatibility.
// I exposed it as returning []StarredItem, so it shall stay as StarredItem
func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) {
	return api.GetStarredContext(context.Background(), params)
}

// GetStarredContext returns a list of StarredItem items with a custom context
//
// For more details see GetStarred
func (api *Client) GetStarredContext(ctx context.Context, params StarsParameters) ([]StarredItem, *Paging, error) {
	items, paging, err := api.ListStarsContext(ctx, params)
	if err != nil {
		return nil, nil, err
	}
	starredItems := make([]StarredItem, len(items))
	for i, item := range items {
		starredItems[i] = StarredItem(item)
	}
	return starredItems, paging, nil
}

type listResponsePaginated struct {
	Items []Item `json:"items"`
	SlackResponse
	Metadata ResponseMetadata `json:"response_metadata"`
}

// StarredItemPagination allows for paginating over the starred items
type StarredItemPagination struct {
	Items        []Item
	limit        int
	previousResp *ResponseMetadata
	c            *Client
}

// ListStarsOption options for the GetUsers method call.
type ListStarsOption func(*StarredItemPagination)

// ListAllStars returns the complete list of starred items
func (api *Client) ListAllStars() ([]Item, error) {
	return api.ListAllStarsContext(context.Background())
}

// ListAllStarsContext returns the list of users (with their detailed information) with a custom context
func (api *Client) ListAllStarsContext(ctx context.Context) (results []Item, err error) {
	// var p StarredItemPagination

	// for p = api.ListStarsPaginated(); !p.done(err); p, err = p.next(ctx) {
	// 	results = append(results, p.Items...)
	// }
	p := api.ListStarsPaginated()
	for err == nil {
		p, err = p.next(ctx)
		if err == nil {
			results = append(results, p.Items...)
		} else if rateLimitedError, ok := err.(*RateLimitedError); ok {
			select {
			case <-ctx.Done():
				err = ctx.Err()
			case <-time.After(rateLimitedError.RetryAfter):
				err = nil
			}
		}
	}

	return results, p.failure(err)
}

// ListStarsPaginated fetches users in a paginated fashion, see ListStarsPaginationContext for usage.
func (api *Client) ListStarsPaginated(options ...ListStarsOption) StarredItemPagination {
	return newStarPagination(api, options...)
}

func newStarPagination(c *Client, options ...ListStarsOption) (sip StarredItemPagination) {
	sip = StarredItemPagination{
		c:     c,
		limit: 200, // per slack api documentation.
	}

	for _, opt := range options {
		opt(&sip)
	}

	return sip
}

// done checks if the pagination has completed
func (StarredItemPagination) done(err error) bool {
	return err == errPaginationComplete
}

// done checks if pagination failed.
func (t StarredItemPagination) failure(err error) error {
	if t.done(err) {
		return nil
	}

	return err
}

// next gets the next list of starred items based on the cursor value
func (t StarredItemPagination) next(ctx context.Context) (_ StarredItemPagination, err error) {
	var (
		resp *listResponsePaginated
	)

	if t.c == nil || (t.previousResp != nil && t.previousResp.Cursor == "") {
		return t, errPaginationComplete
	}

	t.previousResp = t.previousResp.initialize()

	values := url.Values{
		"limit":  {strconv.Itoa(t.limit)},
		"token":  {t.c.token},
		"cursor": {t.previousResp.Cursor},
	}

	if err = t.c.postMethod(ctx, "stars.list", values, &resp); err != nil {
		return t, err
	}

	t.previousResp = &resp.Metadata
	t.Items = resp.Items

	return t, nil
}
<|endoftext|>


<commit_before>package main

import (
  "fmt"
  "github.com/kr/pretty"
  "os"
  "os/exec"
  "path/filepath"
  "sync"
)

var _ = pretty.Println // lol
var _ = os.Stdout

var flagProcfile string
var flagEnv string
var wg sync.WaitGroup

var cmdStart = &Command{
  Run: runStart,
  Usage: "start [-f procfile] [-e env] [-c concurrency]",
  Short: "Start the application",
  Long: `
Start the application specified by a Procfile (defaults to ./Procfile)

Examples:

  forego start
  forego start -f Procfile.test -e .env.test
`,
}

func init() {
  cmdStart.Flag.StringVar(&flagProcfile, "f", "Procfile", "procfile")
  cmdStart.Flag.StringVar(&flagEnv, "e", "", "env")
}

func runStart(cmd *Command, args []string) {
  root := filepath.Dir(flagProcfile)
  if (flagEnv == "") {
    flagEnv = filepath.Join(root, ".env")
  }
  pf, err := ReadProcfile(flagProcfile)
  handleError(err)
  env, err := ReadEnv(flagEnv)
  handleError(err)

  ps_env := []string{}
  for name, val := range env {
    ps_env = append(ps_env, fmt.Sprintf("%s=%s", name, val))
  }
  ps_env = append(ps_env, "LANG=en_US.UTF-8")

  for idx, proc := range pf.entries {
    wg.Add(1)
    go func(idx int, proc ProcfileEntry) {
      command := []string{"/bin/bash", "-c", proc.command}
      ps := exec.Command(command[0], command[1:]...)
      ps.Dir = root
      ps.Env = ps_env
      ps.Stdin = nil
      ps.Stdout = createOutlet(proc.name, idx, false)
      ps.Stderr = createOutlet(proc.name, idx, true)
      ps.Start()
      ps.Wait()
      fmt.Println("process died")
      wg.Done()
    }(idx, proc)
  }
  wg.Wait()
  fmt.Println("afterdone")
}
<commit_msg>inherit surrounding env<commit_after>package main

import (
  "fmt"
  "github.com/kr/pretty"
  "os"
  "os/exec"
  "path/filepath"
  "sync"
)

var _ = pretty.Println // lol
var _ = os.Stdout

var flagProcfile string
var flagEnv string
var wg sync.WaitGroup

var cmdStart = &Command{
  Run: runStart,
  Usage: "start [-f procfile] [-e env] [-c concurrency]",
  Short: "Start the application",
  Long: `
Start the application specified by a Procfile (defaults to ./Procfile)

Examples:

  forego start
  forego start -f Procfile.test -e .env.test
`,
}

func init() {
  cmdStart.Flag.StringVar(&flagProcfile, "f", "Procfile", "procfile")
  cmdStart.Flag.StringVar(&flagEnv, "e", "", "env")
}

func runStart(cmd *Command, args []string) {
  root := filepath.Dir(flagProcfile)
  if (flagEnv == "") {
    flagEnv = filepath.Join(root, ".env")
  }
  pf, err := ReadProcfile(flagProcfile)
  handleError(err)
  env, err := ReadEnv(flagEnv)
  handleError(err)

  ps_env := os.Environ()
  for name, val := range env {
    ps_env = append(ps_env, fmt.Sprintf("%s=%s", name, val))
  }
  ps_env = append(ps_env, "LANG=en_US.UTF-8")

  for idx, proc := range pf.entries {
    wg.Add(1)
    go func(idx int, proc ProcfileEntry) {
      command := []string{"/bin/bash", "-c", proc.command}
      ps := exec.Command(command[0], command[1:]...)
      ps.Dir = root
      ps.Env = ps_env
      ps.Stdin = nil
      ps.Stdout = createOutlet(proc.name, idx, false)
      ps.Stderr = createOutlet(proc.name, idx, true)
      ps.Start()
      ps.Wait()
      fmt.Println("process died")
      wg.Done()
    }(idx, proc)
  }
  wg.Wait()
  fmt.Println("afterdone")
}
<|endoftext|>


<commit_before>package action

import (
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/Masterminds/glide/cache"
	"github.com/Masterminds/glide/cfg"
	"github.com/Masterminds/glide/msg"
	gpath "github.com/Masterminds/glide/path"
	"github.com/Masterminds/semver"
	"github.com/Masterminds/vcs"
)

// ConfigWizard reads configuration from a glide.yaml file and attempts to suggest
// improvements. The wizard is interactive.
func ConfigWizard(base string) {
	_, err := gpath.Glide()
	glidefile := gpath.GlideFile
	if err != nil {
		msg.Info("Unable to find a glide.yaml file. Would you like to create one now? Yes (Y) or No (N)")
		bres := msg.PromptUntilYorN()
		if bres {
			// Guess deps
			conf := guessDeps(base, false)
			// Write YAML
			if err := conf.WriteFile(glidefile); err != nil {
				msg.Die("Could not save %s: %s", glidefile, err)
			}
		} else {
			msg.Err("Unable to find configuration file. Please create configuration information to continue.")
		}
	}

	conf := EnsureConfig()

	err = cache.Setup()
	if err != nil {
		msg.Die("Problem setting up cache: %s", err)
	}

	msg.Info("Looking for dependencies to make suggestions on")
	msg.Info("--> Scanning for dependencies not using verion ranges")
	msg.Info("--> Scanning for dependencies using commit ids")
	var deps []*cfg.Dependency
	for _, dep := range conf.Imports {
		if wizardLookInto(dep) {
			deps = append(deps, dep)
		}
	}

	msg.Info("Gathering information on each dependency")
	msg.Info("--> This may take a moment. Especially on a codebase with many dependencies")
	msg.Info("--> Gathering release information for dependencies")
	msg.Info("--> Looking for dependency imports where versions are commit ids")
	for _, dep := range deps {
		wizardFindVersions(dep)
	}

	var changes int
	for _, dep := range deps {
		var remote string
		if dep.Repository != "" {
			remote = dep.Repository
		} else {
			remote = "https://" + dep.Name
		}

		// First check, ask if the tag should be used instead of the commit id for it.
		cur := cache.MemCurrent(remote)
		if cur != "" && cur != dep.Reference {
			wizardSugOnce()
			var dres bool
			asked, use, val := wizardOnce("current")
			if !use {
				dres = wizardAskCurrent(cur, dep)
			}
			if !asked {
				as := wizardRemember()
				wizardSetOnce("current", as, dres)
			}

			if asked && use {
				dres = val.(bool)
			}

			if dres {
				msg.Info("Updating %s to use the tag %s instead of commit id %s", dep.Name, cur, dep.Reference)
				dep.Reference = cur
				changes++
			}
		}

		// Second check, if no version is being used and there's a semver release ask about latest.
		memlatest := cache.MemLatest(remote)
		if dep.Reference == "" && memlatest != "" {
			wizardSugOnce()
			var dres bool
			asked, use, val := wizardOnce("latest")
			if !use {
				dres = wizardAskLatest(memlatest, dep)
			}
			if !asked {
				as := wizardRemember()
				wizardSetOnce("latest", as, dres)
			}

			if asked && use {
				dres = val.(bool)
			}

			if dres {
				msg.Info("Updating %s to use the release %s instead of no release", dep.Name, memlatest)
				dep.Reference = memlatest
				changes++
			}
		}

		// Third check, if the version is semver offer to use a range instead.
		sv, err := semver.NewVersion(dep.Reference)
		if err == nil {
			wizardSugOnce()
			var res string
			asked, use, val := wizardOnce("range")
			if !use {
				res = wizardAskRange(sv, dep)
			}
			if !asked {
				as := wizardRemember()
				wizardSetOnce("range", as, res)
			}

			if asked && use {
				res = val.(string)
			}

			if res == "m" {
				r := "^" + sv.String()
				msg.Info("Updating %s to use the range %s instead of commit id %s", dep.Name, r, dep.Reference)
				dep.Reference = r
				changes++
			} else if res == "p" {
				r := "~" + sv.String()
				msg.Info("Updating %s to use the range %s instead of commit id %s", dep.Name, r, dep.Reference)
				dep.Reference = r
				changes++
			}
		}
	}

	if changes > 0 {
		msg.Info("Configuration changes have been made. Would you like to write these")
		msg.Info("changes to your configuration file? Yes (Y) or No (N)")
		dres := msg.PromptUntilYorN()
		if dres {
			msg.Info("Writing updates to configuration file (%s)", glidefile)
			if err := conf.WriteFile(glidefile); err != nil {
				msg.Die("Could not save %s: %s", glidefile, err)
			}
			msg.Info("You can now edit the glide.yaml file.:")
			msg.Info("--> For more information on versions and ranges see https://glide.sh/docs/versions/")
			msg.Info("--> For details on additional metadata see https://glide.sh/docs/glide.yaml/")
		} else {
			msg.Warn("Change not written to configuration file")
		}
	} else {
		msg.Info("No proposed changes found. Have a nice day.")
	}
}

var wizardOnceVal = make(map[string]interface{})
var wizardOnceDo = make(map[string]bool)
var wizardOnceAsked = make(map[string]bool)

var wizardSuggeseOnce bool

func wizardSugOnce() {
	if !wizardSuggeseOnce {
		msg.Info("Here are some suggestions...")
	}
	wizardSuggeseOnce = true
}

// Returns if it's you should prompt, if not prompt if you should use stored value,
// and stored value if it has one.
func wizardOnce(name string) (bool, bool, interface{}) {
	return wizardOnceAsked[name], wizardOnceDo[name], wizardOnceVal[name]
}

func wizardSetOnce(name string, prompt bool, val interface{}) {
	wizardOnceAsked[name] = true
	wizardOnceDo[name] = prompt
	wizardOnceVal[name] = val
}

func wizardRemember() bool {
	msg.Info("Would you like to remember the previous decision and apply it to future")
	msg.Info("dependencies? Yes (Y) or No (N)")
	return msg.PromptUntilYorN()
}

func wizardAskRange(ver *semver.Version, d *cfg.Dependency) string {
	vstr := ver.String()
	msg.Info("The package %s appears to use semantic versions (http://semver.org).", d.Name)
	msg.Info("Would you like to track the latest minor or patch releases (major.minor.path)?")
	msg.Info("Tracking minor version releases would use '>= %s, < %d.0.0' ('^%s'). Tracking patch version", vstr, ver.Major()+1, vstr)
	msg.Info("releases would use '>= %s, < %d.%d.0' ('~%s'). For more information on Glide versions", vstr, ver.Major(), ver.Minor()+1, vstr)
	msg.Info("and ranges see https://glide.sh/docs/versions")
	msg.Info("Minor (M), Patch (P), or Skip Ranges (S)?")
	res, err := msg.PromptUntil([]string{"minor", "m", "patch", "p", "skip ranges", "s"})
	if err != nil {
		msg.Die("Error processing response: %s", err)
	}
	if res == "m" || res == "minor" {
		return "m"
	} else if res == "p" || res == "patch" {
		return "p"
	}

	return "s"
}

func wizardAskCurrent(cur string, d *cfg.Dependency) bool {
	msg.Info("The package %s is currently set to use the version %s.", d.Name, d.Reference)
	msg.Info("There is an equivalent semantic version (http://semver.org) release of %s. Would", cur)
	msg.Info("you like to use that instead? Yes (Y) or No (N)")
	return msg.PromptUntilYorN()
}

func wizardAskLatest(latest string, d *cfg.Dependency) bool {
	msg.Info("The package %s appears to have Semantic Version releases (http://semver.org). ", d.Name)
	msg.Info("The latestrelease is %s. You are currently not using a release. Would you like", latest)
	msg.Info("to use this release? Yes (Y) or No (N)")
	return msg.PromptUntilYorN()
}

func wizardLookInto(d *cfg.Dependency) bool {
	_, err := semver.NewConstraint(d.Reference)

	// The existing version is already a valid semver constraint so we skip suggestions.
	if err == nil {
		return false
	}

	return true
}

// Note, this really needs a simpler name.
var createGitParseVersion = regexp.MustCompile(`(?m-s)(?:tags)/(\S+)$`)

func wizardFindVersions(d *cfg.Dependency) {
	l, err := cache.Location()
	if err != nil {
		msg.Debug("Problem detecting cache location: %s", err)
		return
	}
	var remote string
	if d.Repository != "" {
		remote = d.Repository
	} else {
		remote = "https://" + d.Name
	}

	key, err := cache.Key(remote)
	if err != nil {
		msg.Debug("Problem generating cache key for %s: %s", remote, err)
		return
	}

	local := filepath.Join(l, "src", key)
	repo, err := vcs.NewRepo(remote, local)
	if err != nil {
		msg.Debug("Problem getting repo instance: %s", err)
		return
	}

	var useLocal bool
	if _, err = os.Stat(local); err == nil {
		useLocal = true
	}

	// Git endpoints allow for querying without fetching the codebase locally.
	// We try that first to avoid fetching right away. Is this premature
	// optimization?
	cc := true
	if !useLocal && repo.Vcs() == vcs.Git {
		out, err2 := exec.Command("git", "ls-remote", remote).CombinedOutput()
		if err2 == nil {
			cache.MemTouch(remote)
			cc = false
			lines := strings.Split(string(out), "\n")
			for _, i := range lines {
				ti := strings.TrimSpace(i)
				if found := createGitParseVersion.FindString(ti); found != "" {
					tg := strings.TrimPrefix(strings.TrimSuffix(found, "^{}"), "tags/")
					cache.MemPut(remote, tg)
					if d.Reference != "" && strings.HasPrefix(ti, d.Reference) {
						cache.MemSetCurrent(remote, tg)
					}
				}
			}
		}
	}

	if cc {
		cache.Lock(key)
		cache.MemTouch(remote)
		if _, err = os.Stat(local); os.IsNotExist(err) {
			repo.Get()
			branch := findCurrentBranch(repo)
			c := cache.RepoInfo{DefaultBranch: branch}
			err = cache.SaveRepoData(key, c)
			if err != nil {
				msg.Debug("Error saving cache repo details: %s", err)
			}
		} else {
			repo.Update()
		}
		tgs, err := repo.Tags()
		if err != nil {
			msg.Debug("Problem getting tags: %s", err)
		} else {
			for _, v := range tgs {
				cache.MemPut(remote, v)
			}
		}
		if d.Reference != "" && repo.IsReference(d.Reference) {
			tgs, err = repo.TagsFromCommit(d.Reference)
			if err != nil {
				msg.Debug("Problem getting tags for commit: %s", err)
			} else {
				if len(tgs) > 0 {
					for _, v := range tgs {
						if !(repo.Vcs() == vcs.Hg && v == "tip") {
							cache.MemSetCurrent(remote, v)
						}
					}
				}
			}
		}
		cache.Unlock(key)
	}
}

func findCurrentBranch(repo vcs.Repo) string {
	msg.Debug("Attempting to find current branch for %s", repo.Remote())
	// Svn and Bzr don't have default branches.
	if repo.Vcs() == vcs.Svn || repo.Vcs() == vcs.Bzr {
		return ""
	}

	if repo.Vcs() == vcs.Git || repo.Vcs() == vcs.Hg {
		ver, err := repo.Current()
		if err != nil {
			msg.Debug("Unable to find current branch for %s, error: %s", repo.Remote(), err)
			return ""
		}
		return ver
	}

	return ""
}
<commit_msg>Fix typo<commit_after>package action

import (
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/Masterminds/glide/cache"
	"github.com/Masterminds/glide/cfg"
	"github.com/Masterminds/glide/msg"
	gpath "github.com/Masterminds/glide/path"
	"github.com/Masterminds/semver"
	"github.com/Masterminds/vcs"
)

// ConfigWizard reads configuration from a glide.yaml file and attempts to suggest
// improvements. The wizard is interactive.
func ConfigWizard(base string) {
	_, err := gpath.Glide()
	glidefile := gpath.GlideFile
	if err != nil {
		msg.Info("Unable to find a glide.yaml file. Would you like to create one now? Yes (Y) or No (N)")
		bres := msg.PromptUntilYorN()
		if bres {
			// Guess deps
			conf := guessDeps(base, false)
			// Write YAML
			if err := conf.WriteFile(glidefile); err != nil {
				msg.Die("Could not save %s: %s", glidefile, err)
			}
		} else {
			msg.Err("Unable to find configuration file. Please create configuration information to continue.")
		}
	}

	conf := EnsureConfig()

	err = cache.Setup()
	if err != nil {
		msg.Die("Problem setting up cache: %s", err)
	}

	msg.Info("Looking for dependencies to make suggestions on")
	msg.Info("--> Scanning for dependencies not using version ranges")
	msg.Info("--> Scanning for dependencies using commit ids")
	var deps []*cfg.Dependency
	for _, dep := range conf.Imports {
		if wizardLookInto(dep) {
			deps = append(deps, dep)
		}
	}

	msg.Info("Gathering information on each dependency")
	msg.Info("--> This may take a moment. Especially on a codebase with many dependencies")
	msg.Info("--> Gathering release information for dependencies")
	msg.Info("--> Looking for dependency imports where versions are commit ids")
	for _, dep := range deps {
		wizardFindVersions(dep)
	}

	var changes int
	for _, dep := range deps {
		var remote string
		if dep.Repository != "" {
			remote = dep.Repository
		} else {
			remote = "https://" + dep.Name
		}

		// First check, ask if the tag should be used instead of the commit id for it.
		cur := cache.MemCurrent(remote)
		if cur != "" && cur != dep.Reference {
			wizardSugOnce()
			var dres bool
			asked, use, val := wizardOnce("current")
			if !use {
				dres = wizardAskCurrent(cur, dep)
			}
			if !asked {
				as := wizardRemember()
				wizardSetOnce("current", as, dres)
			}

			if asked && use {
				dres = val.(bool)
			}

			if dres {
				msg.Info("Updating %s to use the tag %s instead of commit id %s", dep.Name, cur, dep.Reference)
				dep.Reference = cur
				changes++
			}
		}

		// Second check, if no version is being used and there's a semver release ask about latest.
		memlatest := cache.MemLatest(remote)
		if dep.Reference == "" && memlatest != "" {
			wizardSugOnce()
			var dres bool
			asked, use, val := wizardOnce("latest")
			if !use {
				dres = wizardAskLatest(memlatest, dep)
			}
			if !asked {
				as := wizardRemember()
				wizardSetOnce("latest", as, dres)
			}

			if asked && use {
				dres = val.(bool)
			}

			if dres {
				msg.Info("Updating %s to use the release %s instead of no release", dep.Name, memlatest)
				dep.Reference = memlatest
				changes++
			}
		}

		// Third check, if the version is semver offer to use a range instead.
		sv, err := semver.NewVersion(dep.Reference)
		if err == nil {
			wizardSugOnce()
			var res string
			asked, use, val := wizardOnce("range")
			if !use {
				res = wizardAskRange(sv, dep)
			}
			if !asked {
				as := wizardRemember()
				wizardSetOnce("range", as, res)
			}

			if asked && use {
				res = val.(string)
			}

			if res == "m" {
				r := "^" + sv.String()
				msg.Info("Updating %s to use the range %s instead of commit id %s", dep.Name, r, dep.Reference)
				dep.Reference = r
				changes++
			} else if res == "p" {
				r := "~" + sv.String()
				msg.Info("Updating %s to use the range %s instead of commit id %s", dep.Name, r, dep.Reference)
				dep.Reference = r
				changes++
			}
		}
	}

	if changes > 0 {
		msg.Info("Configuration changes have been made. Would you like to write these")
		msg.Info("changes to your configuration file? Yes (Y) or No (N)")
		dres := msg.PromptUntilYorN()
		if dres {
			msg.Info("Writing updates to configuration file (%s)", glidefile)
			if err := conf.WriteFile(glidefile); err != nil {
				msg.Die("Could not save %s: %s", glidefile, err)
			}
			msg.Info("You can now edit the glide.yaml file.:")
			msg.Info("--> For more information on versions and ranges see https://glide.sh/docs/versions/")
			msg.Info("--> For details on additional metadata see https://glide.sh/docs/glide.yaml/")
		} else {
			msg.Warn("Change not written to configuration file")
		}
	} else {
		msg.Info("No proposed changes found. Have a nice day.")
	}
}

var wizardOnceVal = make(map[string]interface{})
var wizardOnceDo = make(map[string]bool)
var wizardOnceAsked = make(map[string]bool)

var wizardSuggeseOnce bool

func wizardSugOnce() {
	if !wizardSuggeseOnce {
		msg.Info("Here are some suggestions...")
	}
	wizardSuggeseOnce = true
}

// Returns if it's you should prompt, if not prompt if you should use stored value,
// and stored value if it has one.
func wizardOnce(name string) (bool, bool, interface{}) {
	return wizardOnceAsked[name], wizardOnceDo[name], wizardOnceVal[name]
}

func wizardSetOnce(name string, prompt bool, val interface{}) {
	wizardOnceAsked[name] = true
	wizardOnceDo[name] = prompt
	wizardOnceVal[name] = val
}

func wizardRemember() bool {
	msg.Info("Would you like to remember the previous decision and apply it to future")
	msg.Info("dependencies? Yes (Y) or No (N)")
	return msg.PromptUntilYorN()
}

func wizardAskRange(ver *semver.Version, d *cfg.Dependency) string {
	vstr := ver.String()
	msg.Info("The package %s appears to use semantic versions (http://semver.org).", d.Name)
	msg.Info("Would you like to track the latest minor or patch releases (major.minor.path)?")
	msg.Info("Tracking minor version releases would use '>= %s, < %d.0.0' ('^%s'). Tracking patch version", vstr, ver.Major()+1, vstr)
	msg.Info("releases would use '>= %s, < %d.%d.0' ('~%s'). For more information on Glide versions", vstr, ver.Major(), ver.Minor()+1, vstr)
	msg.Info("and ranges see https://glide.sh/docs/versions")
	msg.Info("Minor (M), Patch (P), or Skip Ranges (S)?")
	res, err := msg.PromptUntil([]string{"minor", "m", "patch", "p", "skip ranges", "s"})
	if err != nil {
		msg.Die("Error processing response: %s", err)
	}
	if res == "m" || res == "minor" {
		return "m"
	} else if res == "p" || res == "patch" {
		return "p"
	}

	return "s"
}

func wizardAskCurrent(cur string, d *cfg.Dependency) bool {
	msg.Info("The package %s is currently set to use the version %s.", d.Name, d.Reference)
	msg.Info("There is an equivalent semantic version (http://semver.org) release of %s. Would", cur)
	msg.Info("you like to use that instead? Yes (Y) or No (N)")
	return msg.PromptUntilYorN()
}

func wizardAskLatest(latest string, d *cfg.Dependency) bool {
	msg.Info("The package %s appears to have Semantic Version releases (http://semver.org). ", d.Name)
	msg.Info("The latestrelease is %s. You are currently not using a release. Would you like", latest)
	msg.Info("to use this release? Yes (Y) or No (N)")
	return msg.PromptUntilYorN()
}

func wizardLookInto(d *cfg.Dependency) bool {
	_, err := semver.NewConstraint(d.Reference)

	// The existing version is already a valid semver constraint so we skip suggestions.
	if err == nil {
		return false
	}

	return true
}

// Note, this really needs a simpler name.
var createGitParseVersion = regexp.MustCompile(`(?m-s)(?:tags)/(\S+)$`)

func wizardFindVersions(d *cfg.Dependency) {
	l, err := cache.Location()
	if err != nil {
		msg.Debug("Problem detecting cache location: %s", err)
		return
	}
	var remote string
	if d.Repository != "" {
		remote = d.Repository
	} else {
		remote = "https://" + d.Name
	}

	key, err := cache.Key(remote)
	if err != nil {
		msg.Debug("Problem generating cache key for %s: %s", remote, err)
		return
	}

	local := filepath.Join(l, "src", key)
	repo, err := vcs.NewRepo(remote, local)
	if err != nil {
		msg.Debug("Problem getting repo instance: %s", err)
		return
	}

	var useLocal bool
	if _, err = os.Stat(local); err == nil {
		useLocal = true
	}

	// Git endpoints allow for querying without fetching the codebase locally.
	// We try that first to avoid fetching right away. Is this premature
	// optimization?
	cc := true
	if !useLocal && repo.Vcs() == vcs.Git {
		out, err2 := exec.Command("git", "ls-remote", remote).CombinedOutput()
		if err2 == nil {
			cache.MemTouch(remote)
			cc = false
			lines := strings.Split(string(out), "\n")
			for _, i := range lines {
				ti := strings.TrimSpace(i)
				if found := createGitParseVersion.FindString(ti); found != "" {
					tg := strings.TrimPrefix(strings.TrimSuffix(found, "^{}"), "tags/")
					cache.MemPut(remote, tg)
					if d.Reference != "" && strings.HasPrefix(ti, d.Reference) {
						cache.MemSetCurrent(remote, tg)
					}
				}
			}
		}
	}

	if cc {
		cache.Lock(key)
		cache.MemTouch(remote)
		if _, err = os.Stat(local); os.IsNotExist(err) {
			repo.Get()
			branch := findCurrentBranch(repo)
			c := cache.RepoInfo{DefaultBranch: branch}
			err = cache.SaveRepoData(key, c)
			if err != nil {
				msg.Debug("Error saving cache repo details: %s", err)
			}
		} else {
			repo.Update()
		}
		tgs, err := repo.Tags()
		if err != nil {
			msg.Debug("Problem getting tags: %s", err)
		} else {
			for _, v := range tgs {
				cache.MemPut(remote, v)
			}
		}
		if d.Reference != "" && repo.IsReference(d.Reference) {
			tgs, err = repo.TagsFromCommit(d.Reference)
			if err != nil {
				msg.Debug("Problem getting tags for commit: %s", err)
			} else {
				if len(tgs) > 0 {
					for _, v := range tgs {
						if !(repo.Vcs() == vcs.Hg && v == "tip") {
							cache.MemSetCurrent(remote, v)
						}
					}
				}
			}
		}
		cache.Unlock(key)
	}
}

func findCurrentBranch(repo vcs.Repo) string {
	msg.Debug("Attempting to find current branch for %s", repo.Remote())
	// Svn and Bzr don't have default branches.
	if repo.Vcs() == vcs.Svn || repo.Vcs() == vcs.Bzr {
		return ""
	}

	if repo.Vcs() == vcs.Git || repo.Vcs() == vcs.Hg {
		ver, err := repo.Current()
		if err != nil {
			msg.Debug("Unable to find current branch for %s, error: %s", repo.Remote(), err)
			return ""
		}
		return ver
	}

	return ""
}
<|endoftext|>


<commit_before>package bfs

import (
	"os"
	"path/filepath"

	"github.com/itchio/wharf/state"
)

var debugGhostBusting = os.Getenv("BUTLER_LOUD_GHOSTS") == "1"

type BustGhostsParams struct {
	Consumer *state.Consumer
	Folder   string
	NewFiles []string
	Receipt  *Receipt
}

/**
 * A ghost busting is performed after performing an install using a method
 * that lets us know exactly what was written to disk.
 *
 * In this case, we:
 *   - Install in-place, directly into the destination
 *   - Compare the previous list of installed files with the list
 *     of files we just wrote to disk
 *   - Remove all the ghosts
 *
 * Ghosts are files that were in the previous install and aren't present
 * in the new install. Since we don't want to keep old, unnecessary files
 * (that aren't angels) around, we just remove them.
 *
 * See also: save angels.
 */
func BustGhosts(params *BustGhostsParams) error {
	if !params.Receipt.HasFiles() {
		// if we didn't have a receipt, we can't know for sure
		// which files are ghosts, so we just don't wipe anything
		params.Consumer.Infof("No receipt found, leaving potential ghosts alone")
		return nil
	}

	oldFiles := params.Receipt.Files

	ghostFiles := Difference(params.NewFiles, oldFiles)

	if len(ghostFiles) == 0 {
		params.Consumer.Infof("No ghosts there!")
		return nil
	}

	if debugGhostBusting {
		params.Consumer.Infof("== old files")
		for _, f := range oldFiles {
			params.Consumer.Infof("  %s", f)
		}
		params.Consumer.Infof("== new files")
		for _, f := range params.NewFiles {
			params.Consumer.Infof("  %s", f)
		}
		params.Consumer.Infof("== ghosts")
		for _, f := range ghostFiles {
			params.Consumer.Infof("  %s", f)
		}
		params.Consumer.Infof("=====================")
	}

	removeFoundGhosts(params, ghostFiles)
	return nil
}

func removeFoundGhosts(params *BustGhostsParams, ghostFiles []string) {
	for _, ghostFile := range ghostFiles {
		absolutePath := filepath.Join(params.Folder, ghostFile)

		err := os.Remove(absolutePath)
		if err != nil {
			params.Consumer.Infof("Leaving ghost file behind (%s): %s", absolutePath, err.Error())
		}
	}

	dt := NewDirTree(params.Folder)
	dt.CommitFiles(ghostFiles)
	for _, ghostDir := range dt.ListRelativeDirs() {
		if ghostDir == "." {
			continue
		}

		absolutePath := filepath.Join(params.Folder, ghostDir)

		err := os.Remove(absolutePath)
		if err != nil {
			params.Consumer.Infof("Leaving directory behind (%s): %s", absolutePath, err.Error())
		}
	}
}
<commit_msg>Closes https://github.com/itchio/itch/issues/1947<commit_after>package bfs

import (
	"os"
	"path/filepath"

	"github.com/itchio/wharf/state"
)

var debugGhostBusting = os.Getenv("BUTLER_LOUD_GHOSTS") == "1"

type BustGhostsParams struct {
	Consumer *state.Consumer
	Folder   string
	NewFiles []string
	Receipt  *Receipt
}

/**
 * A ghost busting is performed after performing an install using a method
 * that lets us know exactly what was written to disk.
 *
 * In this case, we:
 *   - Install in-place, directly into the destination
 *   - Compare the previous list of installed files with the list
 *     of files we just wrote to disk
 *   - Remove all the ghosts
 *
 * Ghosts are files that were in the previous install and aren't present
 * in the new install. Since we don't want to keep old, unnecessary files
 * (that aren't angels) around, we just remove them.
 *
 * See also: save angels.
 */
func BustGhosts(params *BustGhostsParams) error {
	if !params.Receipt.HasFiles() {
		// if we didn't have a receipt, we can't know for sure
		// which files are ghosts, so we just don't wipe anything
		params.Consumer.Infof("No receipt found, leaving potential ghosts alone")
		return nil
	}

	oldFiles := params.Receipt.Files

	ghostFiles := Difference(params.NewFiles, oldFiles)

	if len(ghostFiles) == 0 {
		params.Consumer.Infof("No ghosts there!")
		return nil
	}

	if debugGhostBusting {
		params.Consumer.Infof("== old files")
		for _, f := range oldFiles {
			params.Consumer.Infof("  %s", f)
		}
		params.Consumer.Infof("== new files")
		for _, f := range params.NewFiles {
			params.Consumer.Infof("  %s", f)
		}
		params.Consumer.Infof("== ghosts")
		for _, f := range ghostFiles {
			params.Consumer.Infof("  %s", f)
		}
		params.Consumer.Infof("=====================")
	}

	removeFoundGhosts(params, ghostFiles)
	return nil
}

func removeFoundGhosts(params *BustGhostsParams, ghostFiles []string) {
	for _, ghostFile := range ghostFiles {
		absolutePath := filepath.Join(params.Folder, ghostFile)

		err := os.Remove(absolutePath)
		if err != nil {
			params.Consumer.Infof("Leaving ghost file behind (%s): %s", absolutePath, err.Error())
		}
	}

	dt := NewDirTree(params.Folder)
	dt.CommitFiles(ghostFiles)
	for _, ghostDir := range dt.ListRelativeDirs() {
		if ghostDir == "." {
			continue
		}

		absolutePath := filepath.Join(params.Folder, ghostDir)

		// instead of doing readdir first, we just call Remove - if it fails, the
		// directory wasn't empty, which is fine.
		// the way DirTree works, all directories that could possibly be empty
		// now (due to removal of some files) will be listed. However, some of
		// them might still contain files.
		os.Remove(absolutePath)
	}
}
<|endoftext|>


<commit_before>package fire

import "gopkg.in/mgo.v2"

// A Store manages the usage of database connections.
type Store struct {
	session *mgo.Session
}

// MustCreateStore will dial the passed database and return a new store. It will
// panic if the initial connection failed.
func MustCreateStore(uri string) *Store {
	store, err := CreateStore(uri)
	if err != nil {
		panic(err)
	}

	return store
}

// CreateStore will dial the passed database and return a new store. It will
// return an error if the initial connection failed
func CreateStore(uri string) (*Store, error) {
	session, err := mgo.Dial(uri)
	if err != nil {
		return nil, err
	}

	return NewStore(session), nil
}

// NewStore returns a Store that uses the passed session and its default database.
func NewStore(session *mgo.Session) *Store {
	return &Store{
		session: session,
	}
}

// Copy will make a copy of the store and the underlying session. Copied stores
// that are not used anymore must be closed.
func (s *Store) Copy() *Store {
	return NewStore(s.session.Copy())
}

// Close will close the store and its associated session.
func (s *Store) Close() {
	s.session.Close()
}

// DB returns the database used by this store.
func (s *Store) DB() *mgo.Database {
	return s.session.DB("")
}

// C will return the collection associated to the passed model.
func (s *Store) C(model Model) *mgo.Collection {
	return s.DB().C(Init(model).Meta().Collection)
}
<commit_msg>simplify<commit_after>package fire

import "gopkg.in/mgo.v2"

// A Store manages the usage of database connections.
type Store struct {
	session *mgo.Session
}

// MustCreateStore will dial the passed database and return a new store. It will
// panic if the initial connection failed.
func MustCreateStore(uri string) *Store {
	store, err := CreateStore(uri)
	if err != nil {
		panic(err)
	}

	return store
}

// CreateStore will dial the passed database and return a new store. It will
// return an error if the initial connection failed
func CreateStore(uri string) (*Store, error) {
	session, err := mgo.Dial(uri)
	if err != nil {
		return nil, err
	}

	return NewStore(session), nil
}

// NewStore returns a Store that uses the passed session and its default database.
func NewStore(session *mgo.Session) *Store {
	return &Store{
		session: session,
	}
}

// Copy will make a copy of the store and the underlying session. Copied stores
// that are not used anymore must be closed.
func (s *Store) Copy() *Store {
	return NewStore(s.session.Copy())
}

// Close will close the store and its associated session.
func (s *Store) Close() {
	s.session.Close()
}

// DB returns the database used by this store.
func (s *Store) DB() *mgo.Database {
	return s.session.DB("")
}

// C will return the collection associated to the passed model.
func (s *Store) C(model Model) *mgo.Collection {
	return s.DB().C(C(model))
}
<|endoftext|>


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

package inode

import (
	"sync"

	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/gcloud/gcs"
	"golang.org/x/net/context"
)

// When this custom metadata key is present in an object record, it is to be
// treated as a symlink. For use in testing only; other users should detect
// this with IsSymlink.
const SymlinkMetadataKey = "gcsfuse_symlink_target"

// Does the supplied object represent a symlink inode?
func IsSymlink(o *gcs.Object) bool {
	_, ok := o.Metadata[SymlinkMetadataKey]
	return ok
}

type SymlinkInode struct {
	/////////////////////////
	// Constant data
	/////////////////////////

	id               fuseops.InodeID
	name             string
	sourceGeneration int64
	attrs            fuseops.InodeAttributes
	target           string

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

	mu sync.Mutex

	// GUARDED_BY(mu)
	lc lookupCount
}

var _ Inode = &SymlinkInode{}

// Create a symlink inode for the supplied object record.
//
// REQUIRES: IsSymlink(o)
func NewSymlinkInode(
	id fuseops.InodeID,
	o *gcs.Object,
	attrs fuseops.InodeAttributes) (s *SymlinkInode) {
	// Create the inode.
	s = &SymlinkInode{
		id:               id,
		name:             o.Name,
		sourceGeneration: o.Generation,
		attrs: fuseops.InodeAttributes{
			Nlink: 1,
			Uid:   attrs.Uid,
			Gid:   attrs.Gid,
			Mode:  attrs.Mode,
			Mtime: o.Updated,
		},
		target: o.Metadata[SymlinkMetadataKey],
	}

	// Set up lookup counting.
	s.lc.Init(id)

	return
}

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

func (s *SymlinkInode) Lock() {
	s.mu.Lock()
}

func (s *SymlinkInode) Unlock() {
	s.mu.Unlock()
}

func (s *SymlinkInode) ID() fuseops.InodeID {
	return s.id
}

func (s *SymlinkInode) Name() string {
	return s.name
}

// Return the object generation number from which this inode was branched.
//
// LOCKS_REQUIRED(s)
func (s *SymlinkInode) SourceGeneration() int64 {
	return s.sourceGeneration
}

// LOCKS_REQUIRED(s.mu)
func (s *SymlinkInode) IncrementLookupCount() {
	s.lc.Inc()
}

// LOCKS_REQUIRED(s.mu)
func (s *SymlinkInode) DecrementLookupCount(n uint64) (destroy bool) {
	destroy = s.lc.Dec(n)
	return
}

// LOCKS_REQUIRED(s.mu)
func (s *SymlinkInode) Destroy() (err error) {
	// Nothing to do.
	return
}

func (s *SymlinkInode) Attributes(
	ctx context.Context) (attrs fuseops.InodeAttributes, err error) {
	attrs = s.attrs
	return
}

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

package inode

import (
	"sync"

	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/gcloud/gcs"
	"golang.org/x/net/context"
)

// When this custom metadata key is present in an object record, it is to be
// treated as a symlink. For use in testing only; other users should detect
// this with IsSymlink.
const SymlinkMetadataKey = "gcsfuse_symlink_target"

// Does the supplied object represent a symlink inode?
func IsSymlink(o *gcs.Object) bool {
	_, ok := o.Metadata[SymlinkMetadataKey]
	return ok
}

type SymlinkInode struct {
	/////////////////////////
	// Constant data
	/////////////////////////

	id               fuseops.InodeID
	name             string
	sourceGeneration Generation
	attrs            fuseops.InodeAttributes
	target           string

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

	mu sync.Mutex

	// GUARDED_BY(mu)
	lc lookupCount
}

var _ Inode = &SymlinkInode{}

// Create a symlink inode for the supplied object record.
//
// REQUIRES: IsSymlink(o)
func NewSymlinkInode(
	id fuseops.InodeID,
	o *gcs.Object,
	attrs fuseops.InodeAttributes) (s *SymlinkInode) {
	// Create the inode.
	s = &SymlinkInode{
		id:   id,
		name: o.Name,
		sourceGeneration: Generation{
			Object:   o.Generation,
			Metadata: o.Metadata,
		},
		attrs: fuseops.InodeAttributes{
			Nlink: 1,
			Uid:   attrs.Uid,
			Gid:   attrs.Gid,
			Mode:  attrs.Mode,
			Mtime: o.Updated,
		},
		target: o.Metadata[SymlinkMetadataKey],
	}

	// Set up lookup counting.
	s.lc.Init(id)

	return
}

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

func (s *SymlinkInode) Lock() {
	s.mu.Lock()
}

func (s *SymlinkInode) Unlock() {
	s.mu.Unlock()
}

func (s *SymlinkInode) ID() fuseops.InodeID {
	return s.id
}

func (s *SymlinkInode) Name() string {
	return s.name
}

// Return the object generation from which this inode was branched.
//
// LOCKS_REQUIRED(s)
func (s *SymlinkInode) SourceGeneration() Generation {
	return s.sourceGeneration
}

// LOCKS_REQUIRED(s.mu)
func (s *SymlinkInode) IncrementLookupCount() {
	s.lc.Inc()
}

// LOCKS_REQUIRED(s.mu)
func (s *SymlinkInode) DecrementLookupCount(n uint64) (destroy bool) {
	destroy = s.lc.Dec(n)
	return
}

// LOCKS_REQUIRED(s.mu)
func (s *SymlinkInode) Destroy() (err error) {
	// Nothing to do.
	return
}

func (s *SymlinkInode) Attributes(
	ctx context.Context) (attrs fuseops.InodeAttributes, err error) {
	attrs = s.attrs
	return
}

// Return the target of the symlink.
func (s *SymlinkInode) Target() (target string) {
	target = s.target
	return
}
<|endoftext|>


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

package setup_test

import (
	"context"
	"io/ioutil"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/google/exposure-notifications-server/internal/authorizedapp"
	"github.com/google/exposure-notifications-server/internal/database"
	"github.com/google/exposure-notifications-server/internal/secrets"
	"github.com/google/exposure-notifications-server/internal/setup"
	"github.com/google/exposure-notifications-server/internal/signing"
	"github.com/google/exposure-notifications-server/internal/storage"
	"github.com/sethvargo/go-envconfig/pkg/envconfig"
)

var _ setup.AuthorizedAppConfigProvider = (*testConfig)(nil)
var _ setup.BlobstoreConfigProvider = (*testConfig)(nil)
var _ setup.DatabaseConfigProvider = (*testConfig)(nil)
var _ setup.KeyManagerConfigProvider = (*testConfig)(nil)
var _ setup.SecretManagerConfigProvider = (*testConfig)(nil)

type testConfig struct {
	Database   *database.Config
	Secret     string `env:"MY_SECRET"`
	SecretFile string `env:"MY_SECRET_FILE"`
}

func (t *testConfig) AuthorizedAppConfig() *authorizedapp.Config {
	return &authorizedapp.Config{
		// TODO: type
		CacheDuration: 10 * time.Minute,
	}
}

func (t *testConfig) BlobstoreConfig() *storage.Config {
	return &storage.Config{
		BlobstoreType: storage.BlobstoreType("NOOP"),
	}
}

func (t *testConfig) DatabaseConfig() *database.Config {
	return t.Database
}

func (t *testConfig) KeyManagerConfig() *signing.Config {
	return &signing.Config{
		KeyManagerType: signing.KeyManagerType("NOOP"),
	}
}

func (t *testConfig) SecretManagerConfig() *secrets.Config {
	return &secrets.Config{
		SecretManagerType: secrets.SecretManagerType("NOOP"),
		SecretCacheTTL:    10 * time.Minute,
	}
}

func TestSetupWith(t *testing.T) {
	t.Parallel()

	tmp, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmp)

	lookuper := envconfig.MapLookuper(map[string]string{
		"SECRETS_DIR":    tmp,
		"MY_SECRET":      "secret://foo",
		"MY_SECRET_FILE": "secret://foo?target=file",
	})

	ctx := context.Background()
	_, dbconfig := database.NewTestDatabaseWithConfig(t)

	t.Run("default", func(t *testing.T) {
		t.Parallel()

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		if got, want := config.Secret, "noop-secret"; got != want {
			t.Errorf("expected %v to be %v", got, want)
		}
	})

	t.Run("database", func(t *testing.T) {
		t.Parallel()

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		db := env.Database()
		if db == nil {
			t.Errorf("expected db to exist")
		}
	})

	t.Run("authorizedapp", func(t *testing.T) {
		t.Parallel()

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		ap := env.AuthorizedAppProvider()
		if ap == nil {
			t.Errorf("expected appprovider to exist")
		}

		if _, ok := ap.(*authorizedapp.DatabaseProvider); !ok {
			t.Errorf("expected %T to be DatabaseProvider", ap)
		}
	})

	t.Run("blobstore", func(t *testing.T) {
		t.Parallel()

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		bs := env.Blobstore()
		if bs == nil {
			t.Errorf("expected blobstore to exist")
		}

		if _, ok := bs.(*storage.Noop); !ok {
			t.Errorf("expected %T to be Noop", bs)
		}
	})

	t.Run("key_manager", func(t *testing.T) {
		t.Parallel()

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		km := env.KeyManager()
		if km == nil {
			t.Errorf("expected key manager to exist")
		}

		if _, ok := km.(*signing.Noop); !ok {
			t.Errorf("expected %T to be Noop", km)
		}
	})

	t.Run("secret_manager", func(t *testing.T) {
		t.Parallel()

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		sm := env.SecretManager()
		if sm == nil {
			t.Errorf("expected secret manager to exist")
		}

		if _, ok := sm.(*secrets.Cacher); !ok {
			t.Errorf("expected %T to be Cacher", sm)
		}

		if got, want := config.Secret, "noop-secret"; got != want {
			t.Errorf("expected %v to be %v", got, want)
		}

		if got, want := config.SecretFile, tmp; !strings.Contains(got, want) {
			t.Errorf("expected %v to contain %v", got, want)
		}
	})
}
<commit_msg>Fix data race in setup tests (#577)<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package setup_test

import (
	"context"
	"io/ioutil"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/google/exposure-notifications-server/internal/authorizedapp"
	"github.com/google/exposure-notifications-server/internal/database"
	"github.com/google/exposure-notifications-server/internal/secrets"
	"github.com/google/exposure-notifications-server/internal/setup"
	"github.com/google/exposure-notifications-server/internal/signing"
	"github.com/google/exposure-notifications-server/internal/storage"
	"github.com/sethvargo/go-envconfig/pkg/envconfig"
)

var _ setup.AuthorizedAppConfigProvider = (*testConfig)(nil)
var _ setup.BlobstoreConfigProvider = (*testConfig)(nil)
var _ setup.DatabaseConfigProvider = (*testConfig)(nil)
var _ setup.KeyManagerConfigProvider = (*testConfig)(nil)
var _ setup.SecretManagerConfigProvider = (*testConfig)(nil)

type testConfig struct {
	Database   *database.Config
	Secret     string `env:"MY_SECRET"`
	SecretFile string `env:"MY_SECRET_FILE"`
}

func (t *testConfig) AuthorizedAppConfig() *authorizedapp.Config {
	return &authorizedapp.Config{
		// TODO: type
		CacheDuration: 10 * time.Minute,
	}
}

func (t *testConfig) BlobstoreConfig() *storage.Config {
	return &storage.Config{
		BlobstoreType: storage.BlobstoreType("NOOP"),
	}
}

func (t *testConfig) DatabaseConfig() *database.Config {
	return t.Database
}

func (t *testConfig) KeyManagerConfig() *signing.Config {
	return &signing.Config{
		KeyManagerType: signing.KeyManagerType("NOOP"),
	}
}

func (t *testConfig) SecretManagerConfig() *secrets.Config {
	return &secrets.Config{
		SecretManagerType: secrets.SecretManagerType("NOOP"),
		SecretCacheTTL:    10 * time.Minute,
	}
}

func TestSetupWith(t *testing.T) {
	t.Parallel()

	tmp, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmp)

	lookuper := envconfig.MapLookuper(map[string]string{
		"SECRETS_DIR":    tmp,
		"MY_SECRET":      "secret://foo",
		"MY_SECRET_FILE": "secret://foo?target=file",
	})

	t.Run("default", func(t *testing.T) {
		t.Parallel()

		ctx := context.Background()
		_, dbconfig := database.NewTestDatabaseWithConfig(t)

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		if got, want := config.Secret, "noop-secret"; got != want {
			t.Errorf("expected %v to be %v", got, want)
		}
	})

	t.Run("database", func(t *testing.T) {
		t.Parallel()

		ctx := context.Background()
		_, dbconfig := database.NewTestDatabaseWithConfig(t)

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		db := env.Database()
		if db == nil {
			t.Errorf("expected db to exist")
		}
	})

	t.Run("authorizedapp", func(t *testing.T) {
		t.Parallel()

		ctx := context.Background()
		_, dbconfig := database.NewTestDatabaseWithConfig(t)

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		ap := env.AuthorizedAppProvider()
		if ap == nil {
			t.Errorf("expected appprovider to exist")
		}

		if _, ok := ap.(*authorizedapp.DatabaseProvider); !ok {
			t.Errorf("expected %T to be DatabaseProvider", ap)
		}
	})

	t.Run("blobstore", func(t *testing.T) {
		t.Parallel()

		ctx := context.Background()
		_, dbconfig := database.NewTestDatabaseWithConfig(t)

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		bs := env.Blobstore()
		if bs == nil {
			t.Errorf("expected blobstore to exist")
		}

		if _, ok := bs.(*storage.Noop); !ok {
			t.Errorf("expected %T to be Noop", bs)
		}
	})

	t.Run("key_manager", func(t *testing.T) {
		t.Parallel()

		ctx := context.Background()
		_, dbconfig := database.NewTestDatabaseWithConfig(t)

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		km := env.KeyManager()
		if km == nil {
			t.Errorf("expected key manager to exist")
		}

		if _, ok := km.(*signing.Noop); !ok {
			t.Errorf("expected %T to be Noop", km)
		}
	})

	t.Run("secret_manager", func(t *testing.T) {
		t.Parallel()

		ctx := context.Background()
		_, dbconfig := database.NewTestDatabaseWithConfig(t)

		config := &testConfig{Database: dbconfig}
		env, err := setup.SetupWith(ctx, config, lookuper)
		if err != nil {
			t.Fatal(err)
		}
		defer env.Close(ctx)

		sm := env.SecretManager()
		if sm == nil {
			t.Errorf("expected secret manager to exist")
		}

		if _, ok := sm.(*secrets.Cacher); !ok {
			t.Errorf("expected %T to be Cacher", sm)
		}

		if got, want := config.Secret, "noop-secret"; got != want {
			t.Errorf("expected %v to be %v", got, want)
		}

		if got, want := config.SecretFile, tmp; !strings.Contains(got, want) {
			t.Errorf("expected %v to contain %v", got, want)
		}
	})
}
<|endoftext|>