<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

//Structs used when parsing a song file
type Song struct {
	Filename          string
	Title             string
	Section           string
	StanzaCount       int
	SongNumber        int
	Stanzas           []Stanza
	ShowStanzaNumbers bool
	BeforeComments    []string
	AfterComments     []string
	transpose         int
}

func ParseSongFile(filename string, transpose int) (*Song, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	filename = filepath.Base(filename)

	var (
		//Song variables
		stanzas         []Stanza
		stanza_count    = 1
		title           = ""
		section         = ""
		scanner         = bufio.NewScanner(file)
		song_stanza_num = true
		//Stanza variables
		lines           []Line
		is_chorus       = false
		stanza_show_num = true
	)

	//We need to handle /r only as Mac OS <= 9 uses this as end-of-line marker
	//This is based on bufio/scan.go ScanLines function
	split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		if atEOF && len(data) == 0 {
			return 0, nil, nil
		}
		i := bytes.IndexByte(data, '\n')

		if i < 0 {
			i = bytes.IndexByte(data, '\r')
		}

		ind := 0
		if i > 0 && data[i-1] == '\r' {
			ind = -1
		}

		if i >= 0 {

			// We have a full newline-terminated line.
			return i + 1, data[0 : i+ind], nil
		}
		// If we're at EOF, we have a final, non-terminated line. Return it.
		if atEOF {
			return len(data), data[0 : len(data)+ind], nil
		}
		// Request more data.
		return 0, nil, nil
	}
	scanner.Split(split)

	stanza_before_comments := make([]string, 0)
	stanza_after_comments := make([]string, 0)
	song_before_comments := make([]string, 0)
	song_after_comments := make([]string, 0)

	chord_regex := regexp.MustCompile("\\[.*?\\]")
	bad_command_regex := regexp.MustCompile("\\{|\\}")
	song_started := false

	for scanner.Scan() {
		line := scanner.Text()
		echo := -1

		//is this a command
		if strings.HasPrefix(line, "{") {
			command := strings.ToLower(line)
			if strings.HasPrefix(command, "{start_of_chorus}") {
				is_chorus = true
				continue
			} else if strings.HasPrefix(command, "{end_of_chorus}") {
				continue
			} else if strings.HasPrefix(command, "{title:") {
				title = parseCommand(line)
				continue
			} else if strings.HasPrefix(command, "{section:") {
				section = parseCommand(line)
				continue
			} else if strings.HasPrefix(command, "{comments:") {
				if !song_started {
					song_before_comments = append(song_before_comments, parseCommand(line))
				} else {
					if len(lines) > 0 {
						stanza_after_comments = append(stanza_after_comments, parseCommand(line))
					} else {
						stanza_before_comments = append(stanza_before_comments, parseCommand(line))
					}
				}
				continue
			} else if strings.HasPrefix(command, "{no_number") {
				if !song_started {
					song_stanza_num = false
				} else {
					stanza_show_num = false
				}
				continue
			} else if i := strings.Index(command, "{echo"); i >= 0 {
				//fall through
			} else {
				fmt.Println(filename)
				fmt.Printf("Unknown tag: %s\n", line)
				continue
			}
		}

		//blank line separates stanzas
		if len(line) == 0 {
			song_started = true

			if len(lines) > 0 {
				stanzas = append(stanzas, *&Stanza{
					Lines:          lines,
					Number:         stanza_count,
					IsChorus:       is_chorus,
					ShowNumber:     stanza_show_num,
					BeforeComments: stanza_before_comments,
					AfterComments:  stanza_after_comments})

				//Choruses do not get stanza numbers
				if !is_chorus {
					stanza_count++
				}

				is_chorus = false
				stanza_show_num = true
				lines = make([]Line, 0)
				stanza_before_comments = make([]string, 0)
				stanza_after_comments = make([]string, 0)
			}
		} else {
			//check for echo marker
			if i := strings.Index(line, "{echo:"); i >= 0 {
				end := strings.Index(line, "}")

				if end < 1 {
					fmt.Printf("Bad echo tag: %s\n", line)
				} else {
					//to work out the index we have to remove the chords
					clean := chord_regex.ReplaceAllString(line, "")
					echo = strings.Index(clean, "{echo:")

					echo_txt := line[i+len("{echo:") : end]
					echo_txt = strings.TrimSpace(echo_txt)

					//remove command from text
					tmp := line[0:i]
					tmp += echo_txt

					line = tmp
				}
			}

			chords_pos := chord_regex.FindAllStringIndex(line, -1)
			chord_len := 0
			chords := make([]Chord, 0)

			for _, pos := range chords_pos {
				chord_text := line[pos[0]+1 : pos[1]-1]
				chord_len += pos[1] - pos[0]
				position := pos[1] - chord_len

				chords = append(chords, Chord{text: chord_text, Position: position, Transpose: transpose})
			}

			//remove all chord markers
			line = chord_regex.ReplaceAllString(line, "")
			lines = append(lines, Line{Text: line, Chords: chords, EchoIndex: echo})

			//check for bad commands
			for _, pos := range bad_command_regex.FindAllStringIndex(line, -1) {
				fmt.Println(filename)
				fmt.Println(line)
				for i := 0; i < pos[0]; i++ {
					fmt.Print(" ")
				}
				fmt.Println("^")
			}

			//Default title is first line text
			if len(title) == 0 {
				//Replace all quotation marks in title
				title = line
				re := regexp.MustCompile("[\"“”]")
				title = re.ReplaceAllString(title, "")
				title = strings.TrimSpace(title)

				//trim any trailing/leading puncutation
				start_reg := regexp.MustCompile("^[a-zA-Z0-9]")
				end_reg := regexp.MustCompile("[a-zA-Z0-9]$")
				start_done := false
				end_done := false

				for {
					if len(title) == 1 {
						break
					}

					pos := start_reg.FindAllStringIndex(title, 1)

					//No match, title has punctuation at the start
					if !start_done && len(pos) == 0 {
						title = title[1:]
					} else {
						start_done = true
					}

					pos = end_reg.FindAllStringIndex(title, 1)

					if !end_done && len(pos) == 0 {
						title = title[0 : len(title)-1]
					} else {
						end_done = true
					}

					if start_done && end_done {
						break
					}
				}
			}
		}
	}

	//check for last stanza
	if len(lines) > 0 {
		stanzas = append(stanzas, Stanza{
			Lines:          lines,
			Number:         stanza_count,
			IsChorus:       is_chorus,
			ShowNumber:     stanza_show_num,
			BeforeComments: stanza_before_comments,
			AfterComments:  stanza_after_comments})
	} else if len(stanza_before_comments) > 0 {
		song_after_comments = stanza_before_comments
	}

	return &Song{
			Filename:          filename,
			Title:             title,
			Section:           section,
			StanzaCount:       0,
			SongNumber:        -1,
			ShowStanzaNumbers: song_stanza_num,
			Stanzas:           stanzas,
			BeforeComments:    song_before_comments,
			AfterComments:     song_after_comments,
			transpose:         transpose},
		nil
}

func parseCommand(command string) string {
	return strings.TrimSpace(command[strings.Index(command, ":")+1 : strings.Index(command, "}")])
}

func (song Song) GetTranspose() int {
	return song.transpose
}

func (song *Song) Transpose(change_by int) {
	song.transpose = change_by

	for _, s := range song.Stanzas {
		for _, l := range s.Lines {
			//index range here because we are modifying the Chord
			for i := range l.Chords {
				l.Chords[i].Transpose = change_by
			}
		}
	}
}

func (song Song) String() string {
	var buffer bytes.Buffer

	if len(song.Section) > 0 {
		buffer.WriteString(fmt.Sprintf("Section: %s\n", song.Section))
	}

	if len(song.BeforeComments) > 0 {
		buffer.WriteString(fmt.Sprintf("/%s/\n", song.BeforeComments))
	}

	for _, s := range song.Stanzas {
		if s.IsChorus {
			buffer.WriteString("---CHORUS--\n")
		} else {
			buffer.WriteString(fmt.Sprintf("STANZA: %d\n", s.Number))
		}
		for _, l := range s.Lines {
			buffer.WriteString(fmt.Sprintf("%s\n", l.Text))
		}
		if s.IsChorus {
			buffer.WriteString(fmt.Sprintln("---END CHORUS--"))
		}

		buffer.WriteString("\n")
	}

	return buffer.String()
}

func (song Song) HasBeforeComments() bool {
	return len(song.BeforeComments) > 0
}

func (song Song) Link() string {
	return song.Filename[0 : len(song.Filename)-5]
}
<commit_msg>Fix song_started not being detected without leading blank line on stanza<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

//Structs used when parsing a song file
type Song struct {
	Filename          string
	Title             string
	Section           string
	StanzaCount       int
	SongNumber        int
	Stanzas           []Stanza
	ShowStanzaNumbers bool
	BeforeComments    []string
	AfterComments     []string
	transpose         int
}

func ParseSongFile(filename string, transpose int) (*Song, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	filename = filepath.Base(filename)

	var (
		//Song variables
		stanzas         []Stanza
		stanza_count    = 1
		title           = ""
		section         = ""
		scanner         = bufio.NewScanner(file)
		song_stanza_num = true
		//Stanza variables
		lines           []Line
		is_chorus       = false
		stanza_show_num = true
	)

	//We need to handle /r only as Mac OS <= 9 uses this as end-of-line marker
	//This is based on bufio/scan.go ScanLines function
	split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
		if atEOF && len(data) == 0 {
			return 0, nil, nil
		}
		i := bytes.IndexByte(data, '\n')

		if i < 0 {
			i = bytes.IndexByte(data, '\r')
		}

		ind := 0
		if i > 0 && data[i-1] == '\r' {
			ind = -1
		}

		if i >= 0 {

			// We have a full newline-terminated line.
			return i + 1, data[0 : i+ind], nil
		}
		// If we're at EOF, we have a final, non-terminated line. Return it.
		if atEOF {
			return len(data), data[0 : len(data)+ind], nil
		}
		// Request more data.
		return 0, nil, nil
	}
	scanner.Split(split)

	stanza_before_comments := make([]string, 0)
	stanza_after_comments := make([]string, 0)
	song_before_comments := make([]string, 0)
	song_after_comments := make([]string, 0)

	chord_regex := regexp.MustCompile("\\[.*?\\]")
	bad_command_regex := regexp.MustCompile("\\{|\\}")
	song_started := false

	for scanner.Scan() {
		line := scanner.Text()
		echo := -1

		//is this a command
		if strings.HasPrefix(line, "{") {
			command := strings.ToLower(line)
			if strings.HasPrefix(command, "{start_of_chorus}") {
				is_chorus = true
				continue
			} else if strings.HasPrefix(command, "{end_of_chorus}") {
				continue
			} else if strings.HasPrefix(command, "{title:") {
				title = parseCommand(line)
				continue
			} else if strings.HasPrefix(command, "{section:") {
				section = parseCommand(line)
				continue
			} else if strings.HasPrefix(command, "{comments:") {
				if !song_started {
					song_before_comments = append(song_before_comments, parseCommand(line))
				} else {
					if len(lines) > 0 {
						stanza_after_comments = append(stanza_after_comments, parseCommand(line))
					} else {
						stanza_before_comments = append(stanza_before_comments, parseCommand(line))
					}
				}
				continue
			} else if strings.HasPrefix(command, "{no_number") {
				if !song_started {
					song_stanza_num = false
				} else {
					stanza_show_num = false
				}
				continue
			} else if i := strings.Index(command, "{echo"); i >= 0 {
				//fall through
			} else {
				fmt.Println(filename)
				fmt.Printf("Unknown tag: %s\n", line)
				continue
			}
		}

		//blank line separates stanzas
		if len(line) == 0 {
			song_started = true

			if len(lines) > 0 {
				stanzas = append(stanzas, *&Stanza{
					Lines:          lines,
					Number:         stanza_count,
					IsChorus:       is_chorus,
					ShowNumber:     stanza_show_num,
					BeforeComments: stanza_before_comments,
					AfterComments:  stanza_after_comments})

				//Choruses do not get stanza numbers
				if !is_chorus {
					stanza_count++
				}

				is_chorus = false
				stanza_show_num = true
				lines = make([]Line, 0)
				stanza_before_comments = make([]string, 0)
				stanza_after_comments = make([]string, 0)
			}
		} else {
			song_started = true
			//check for echo marker
			if i := strings.Index(line, "{echo:"); i >= 0 {
				end := strings.Index(line, "}")

				if end < 1 {
					fmt.Printf("Bad echo tag: %s\n", line)
				} else {
					//to work out the index we have to remove the chords
					clean := chord_regex.ReplaceAllString(line, "")
					echo = strings.Index(clean, "{echo:")

					echo_txt := line[i+len("{echo:") : end]
					echo_txt = strings.TrimSpace(echo_txt)

					//remove command from text
					tmp := line[0:i]
					tmp += echo_txt

					line = tmp
				}
			}

			chords_pos := chord_regex.FindAllStringIndex(line, -1)
			chord_len := 0
			chords := make([]Chord, 0)

			for _, pos := range chords_pos {
				chord_text := line[pos[0]+1 : pos[1]-1]
				chord_len += pos[1] - pos[0]
				position := pos[1] - chord_len

				chords = append(chords, Chord{text: chord_text, Position: position, Transpose: transpose})
			}

			//remove all chord markers
			line = chord_regex.ReplaceAllString(line, "")
			lines = append(lines, Line{Text: line, Chords: chords, EchoIndex: echo})

			//check for bad commands
			for _, pos := range bad_command_regex.FindAllStringIndex(line, -1) {
				fmt.Println(filename)
				fmt.Println(line)
				for i := 0; i < pos[0]; i++ {
					fmt.Print(" ")
				}
				fmt.Println("^")
			}

			//Default title is first line text
			if len(title) == 0 {
				//Replace all quotation marks in title
				title = line
				re := regexp.MustCompile("[\"“”]")
				title = re.ReplaceAllString(title, "")
				title = strings.TrimSpace(title)

				//trim any trailing/leading puncutation
				start_reg := regexp.MustCompile("^[a-zA-Z0-9]")
				end_reg := regexp.MustCompile("[a-zA-Z0-9]$")
				start_done := false
				end_done := false

				for {
					if len(title) == 1 {
						break
					}

					pos := start_reg.FindAllStringIndex(title, 1)

					//No match, title has punctuation at the start
					if !start_done && len(pos) == 0 {
						title = title[1:]
					} else {
						start_done = true
					}

					pos = end_reg.FindAllStringIndex(title, 1)

					if !end_done && len(pos) == 0 {
						title = title[0 : len(title)-1]
					} else {
						end_done = true
					}

					if start_done && end_done {
						break
					}
				}
			}
		}
	}

	//check for last stanza
	if len(lines) > 0 {
		stanzas = append(stanzas, Stanza{
			Lines:          lines,
			Number:         stanza_count,
			IsChorus:       is_chorus,
			ShowNumber:     stanza_show_num,
			BeforeComments: stanza_before_comments,
			AfterComments:  stanza_after_comments})
	} else if len(stanza_before_comments) > 0 {
		song_after_comments = stanza_before_comments
	}

	return &Song{
			Filename:          filename,
			Title:             title,
			Section:           section,
			StanzaCount:       0,
			SongNumber:        -1,
			ShowStanzaNumbers: song_stanza_num,
			Stanzas:           stanzas,
			BeforeComments:    song_before_comments,
			AfterComments:     song_after_comments,
			transpose:         transpose},
		nil
}

func parseCommand(command string) string {
	return strings.TrimSpace(command[strings.Index(command, ":")+1 : strings.Index(command, "}")])
}

func (song Song) GetTranspose() int {
	return song.transpose
}

func (song *Song) Transpose(change_by int) {
	song.transpose = change_by

	for _, s := range song.Stanzas {
		for _, l := range s.Lines {
			//index range here because we are modifying the Chord
			for i := range l.Chords {
				l.Chords[i].Transpose = change_by
			}
		}
	}
}

func (song Song) String() string {
	var buffer bytes.Buffer

	if len(song.Section) > 0 {
		buffer.WriteString(fmt.Sprintf("Section: %s\n", song.Section))
	}

	if len(song.BeforeComments) > 0 {
		buffer.WriteString(fmt.Sprintf("/%s/\n", song.BeforeComments))
	}

	for _, s := range song.Stanzas {
		if s.IsChorus {
			buffer.WriteString("---CHORUS--\n")
		} else {
			buffer.WriteString(fmt.Sprintf("STANZA: %d\n", s.Number))
		}
		for _, l := range s.Lines {
			buffer.WriteString(fmt.Sprintf("%s\n", l.Text))
		}
		if s.IsChorus {
			buffer.WriteString(fmt.Sprintln("---END CHORUS--"))
		}

		buffer.WriteString("\n")
	}

	return buffer.String()
}

func (song Song) HasBeforeComments() bool {
	return len(song.BeforeComments) > 0
}

func (song Song) Link() string {
	return song.Filename[0 : len(song.Filename)-5]
}
<|endoftext|>


<commit_before>package elicit

import (
	"fmt"
	"os"
	"testing"
)

type spec struct {
	context   *Context
	path      string
	name      string
	scenarios []*scenario
	tables    []stringTable
	result    result
}

type result int

const (
	// Note: These are ordered by precedence
	passed result = iota
	skipped
	pending
	failed
	panicked
	numResultTypes
)

func (r result) shouldLog() bool {
	switch r {
	case skipped, passed:
		return false
	case pending, failed, panicked:
		return true
	default:
		panic(fmt.Errorf("unknown result: %d", r))
	}
}

func (r result) String() string {
	switch r {
	case pending:
		return "Pending"
	case skipped:
		return "Skipped"
	case failed:
		return "Failed"
	case panicked:
		return "Panicked"
	case passed:
		return "Passed"
	default:
		panic(fmt.Errorf("unknown result: %d", r))
	}
}

func (s *spec) run(specT *testing.T) {
	for _, scenario := range s.scenarios {

		var hookErr error
		for _, h := range s.context.beforeScenario {
			if hookErr = h.run(); hookErr != nil {
				scenario.result = panicked
				fmt.Fprintf(os.Stderr, "panic during before scenario hook: %s\n", hookErr)
				break
			}
		}

		specT.Run(scenario.name, func(scenarioT *testing.T) {
			if hookErr != nil {
				scenarioT.FailNow()
			}

			// Ensure the after scenario hooks are run regardless of the result
			func() {
				defer func() {
					if hookErr == nil {

						for _, h := range s.context.afterScenario {
							if hookErr = h.run(); hookErr != nil {
								scenario.result = panicked
								fmt.Fprintf(os.Stderr, "panic during after scenario hook: %s\n", hookErr)
								scenarioT.FailNow()
								break
							}
						}
					}
				}()

				scenario.run(scenarioT)
			}()
		})

		if scenario.result > s.result {
			s.result = scenario.result
		}
	}

	switch s.result {
	case panicked, failed:
		specT.Fail()
	case skipped, pending:
		specT.SkipNow()
	}
}

func (s *spec) skipAllScenarios() {
	for _, scenario := range s.scenarios {
		scenario.result = skipped
	}
}
<commit_msg>Simplify Decision to Log Result Based on Ordering<commit_after>package elicit

import (
	"fmt"
	"os"
	"testing"
)

type spec struct {
	context   *Context
	path      string
	name      string
	scenarios []*scenario
	tables    []stringTable
	result    result
}

type result int

const (
	// Note: These are ordered by precedence
	passed result = iota
	skipped
	pending
	failed
	panicked
	numResultTypes
)

func (r result) shouldLog() bool {
	return r > skipped
}

func (r result) String() string {
	switch r {
	case pending:
		return "Pending"
	case skipped:
		return "Skipped"
	case failed:
		return "Failed"
	case panicked:
		return "Panicked"
	case passed:
		return "Passed"
	default:
		panic(fmt.Errorf("unknown result: %d", r))
	}
}

func (s *spec) run(specT *testing.T) {
	for _, scenario := range s.scenarios {

		var hookErr error
		for _, h := range s.context.beforeScenario {
			if hookErr = h.run(); hookErr != nil {
				scenario.result = panicked
				fmt.Fprintf(os.Stderr, "panic during before scenario hook: %s\n", hookErr)
				break
			}
		}

		specT.Run(scenario.name, func(scenarioT *testing.T) {
			if hookErr != nil {
				scenarioT.FailNow()
			}

			// Ensure the after scenario hooks are run regardless of the result
			func() {
				defer func() {
					if hookErr == nil {

						for _, h := range s.context.afterScenario {
							if hookErr = h.run(); hookErr != nil {
								scenario.result = panicked
								fmt.Fprintf(os.Stderr, "panic during after scenario hook: %s\n", hookErr)
								scenarioT.FailNow()
								break
							}
						}
					}
				}()

				scenario.run(scenarioT)
			}()
		})

		if scenario.result > s.result {
			s.result = scenario.result
		}
	}

	switch s.result {
	case panicked, failed:
		specT.Fail()
	case skipped, pending:
		specT.SkipNow()
	}
}

func (s *spec) skipAllScenarios() {
	for _, scenario := range s.scenarios {
		scenario.result = skipped
	}
}
<|endoftext|>


<commit_before>package chat

import (
	"context"
	"sync"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/chat/utils"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/client/go/protocol/keybase1"
)

type NotifyRouterActivityRouter struct {
	utils.DebugLabeler
	globals.Contextified
	sync.Mutex

	notifyCh   chan func()
	shutdownCh chan struct{}
}

func NewNotifyRouterActivityRouter(g *globals.Context) *NotifyRouterActivityRouter {
	n := &NotifyRouterActivityRouter{
		Contextified: globals.NewContextified(g),
		DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "NotifyRouterActivityRouter", false),
		notifyCh:     make(chan func(), 5000),
		shutdownCh:   make(chan struct{}),
	}
	go n.notifyLoop()
	g.PushShutdownHook(func(mctx libkb.MetaContext) error {
		close(n.shutdownCh)
		return nil
	})
	return n
}

func (n *NotifyRouterActivityRouter) notifyLoop() {
	for {
		select {
		case f := <-n.notifyCh:
			f()
		case <-n.shutdownCh:
			return
		}
	}
}

func (n *NotifyRouterActivityRouter) kuid(uid gregor1.UID) keybase1.UID {
	return keybase1.UID(uid.String())
}

func (n *NotifyRouterActivityRouter) Activity(ctx context.Context, uid gregor1.UID,
	topicType chat1.TopicType, activity *chat1.ChatActivity, source chat1.ChatActivitySource) {
	defer n.Trace(ctx, nil, "Activity(%v,%v)", topicType, source)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	if activity == nil {
		return
	}
	typ, err := activity.ActivityType()
	if err != nil {
		n.Debug(ctx, "invalid activity type: %v", err)
		return
	}

	var canSkip bool
	// If the conversation is not being actively viewed, we can optionally skip
	// notifications to the UI.
	if typ == chat1.ChatActivityType_INCOMING_MESSAGE {
		act := activity.IncomingMessage()
		if !act.DisplayDesktopNotification && act.Conv != nil &&
			act.Conv.TeamType == chat1.TeamType_COMPLEX &&
			!n.G().Syncer.IsSelectedConversation(act.ConvID) &&
			act.Conv.ReadMsgID+100 < act.Conv.MaxVisibleMsgID {
			n.Debug(ctx, "canSkip UI notification %v for %v", typ, act.ConvID)
			canSkip = true
		}
	}
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleNewChatActivity(ctx, n.kuid(uid), topicType, activity, source, canSkip)
	}
}

func (n *NotifyRouterActivityRouter) TypingUpdate(ctx context.Context, updates []chat1.ConvTypingUpdate) {
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatTypingUpdate(ctx, updates)
	}
}

func (n *NotifyRouterActivityRouter) JoinedConversation(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "JoinedConversation(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatJoinedConversation(ctx, n.kuid(uid), convID, topicType, conv)
	}
}

func (n *NotifyRouterActivityRouter) LeftConversation(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType) {
	defer n.Trace(ctx, nil, "LeftConversation(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatLeftConversation(ctx, n.kuid(uid), convID, topicType)
	}
}

func (n *NotifyRouterActivityRouter) ResetConversation(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType) {
	defer n.Trace(ctx, nil, "ResetConversation(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatResetConversation(ctx, n.kuid(uid), convID, topicType)
	}
}

func (n *NotifyRouterActivityRouter) KBFSToImpteamUpgrade(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType) {
	defer n.Trace(ctx, nil, "KBFSToImpteamUpgrade(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatKBFSToImpteamUpgrade(ctx, n.kuid(uid), convID, topicType)
	}
}

func (n *NotifyRouterActivityRouter) SetConvRetention(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "SetConvRetention(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatSetConvRetention(ctx, n.kuid(uid), convID, topicType, conv)
	}
}

func (n *NotifyRouterActivityRouter) SetTeamRetention(ctx context.Context, uid gregor1.UID,
	teamID keybase1.TeamID, topicType chat1.TopicType, convs []chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "SetTeamRetention(%s,%v)", teamID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatSetTeamRetention(ctx, n.kuid(uid), teamID, topicType, convs)
	}
}

func (n *NotifyRouterActivityRouter) SetConvSettings(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "SetConvSettings(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatSetConvSettings(ctx, n.kuid(uid), convID, topicType, conv)
	}
}

func (n *NotifyRouterActivityRouter) SubteamRename(ctx context.Context, uid gregor1.UID,
	convIDs []chat1.ConversationID, topicType chat1.TopicType, convs []chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "SubteamRename(%v,%d convs)", topicType, len(convs))()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatSubteamRename(ctx, n.kuid(uid), convIDs, topicType, convs)
	}
}

func (n *NotifyRouterActivityRouter) InboxSyncStarted(ctx context.Context, uid gregor1.UID) {
	defer n.Trace(ctx, nil, "InboxSyncStarted")()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatInboxSyncStarted(ctx, n.kuid(uid))
	}
}

func (n *NotifyRouterActivityRouter) InboxSynced(ctx context.Context, uid gregor1.UID,
	topicType chat1.TopicType, syncRes chat1.ChatSyncResult) {
	defer n.Trace(ctx, nil, "InboxSynced(%v)", topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatInboxSynced(ctx, n.kuid(uid), topicType, syncRes)
	}
}

func (n *NotifyRouterActivityRouter) InboxStale(ctx context.Context, uid gregor1.UID) {
	defer n.Trace(ctx, nil, "InboxStale")()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatInboxStale(ctx, n.kuid(uid))
	}
}

func (n *NotifyRouterActivityRouter) ThreadsStale(ctx context.Context, uid gregor1.UID,
	updates []chat1.ConversationStaleUpdate) {
	defer n.Trace(ctx, nil, "ThreadsStale")()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatThreadsStale(ctx, n.kuid(uid), updates)
	}
}

func (n *NotifyRouterActivityRouter) TLFFinalize(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, finalizeInfo chat1.ConversationFinalizeInfo, conv *chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "TLFFinalize(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatTLFFinalize(ctx, n.kuid(uid), convID, topicType, finalizeInfo, conv)
	}
}

func (n *NotifyRouterActivityRouter) TLFResolve(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, resolveInfo chat1.ConversationResolveInfo) {
	defer n.Trace(ctx, nil, "TLFResolve(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatTLFResolve(ctx, n.kuid(uid), convID, topicType, resolveInfo)
	}
}

func (n *NotifyRouterActivityRouter) AttachmentUploadStart(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, outboxID chat1.OutboxID) {
	defer n.Trace(ctx, nil, "AttachmentUploadStart(%s,%s)", convID, outboxID)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatAttachmentUploadStart(ctx, n.kuid(uid), convID, outboxID)
	}
}

func (n *NotifyRouterActivityRouter) AttachmentUploadProgress(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, outboxID chat1.OutboxID, bytesComplete, bytesTotal int64) {
	defer n.Trace(ctx, nil, "AttachmentUploadProgress(%s,%s)", convID, outboxID)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatAttachmentUploadProgress(ctx, n.kuid(uid), convID, outboxID,
			bytesComplete, bytesTotal)
	}
}

func (n *NotifyRouterActivityRouter) PromptUnfurl(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, msgID chat1.MessageID, domain string) {
	defer n.Trace(ctx, nil, "PromptUnfurl(%s,%s)", convID, msgID)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatPromptUnfurl(ctx, n.kuid(uid), convID, msgID, domain)
	}
}

func (n *NotifyRouterActivityRouter) ConvUpdate(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "ConvUpdate(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatConvUpdate(ctx, n.kuid(uid), convID, topicType, conv)
	}
}
<commit_msg>squash notifications on mobile (#23974)<commit_after>package chat

import (
	"context"
	"sync"

	"github.com/keybase/client/go/chat/globals"
	"github.com/keybase/client/go/chat/utils"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/client/go/protocol/keybase1"
)

type NotifyRouterActivityRouter struct {
	utils.DebugLabeler
	globals.Contextified
	sync.Mutex

	notifyCh   chan func()
	shutdownCh chan struct{}
}

func NewNotifyRouterActivityRouter(g *globals.Context) *NotifyRouterActivityRouter {
	n := &NotifyRouterActivityRouter{
		Contextified: globals.NewContextified(g),
		DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "NotifyRouterActivityRouter", false),
		notifyCh:     make(chan func(), 5000),
		shutdownCh:   make(chan struct{}),
	}
	go n.notifyLoop()
	g.PushShutdownHook(func(mctx libkb.MetaContext) error {
		close(n.shutdownCh)
		return nil
	})
	return n
}

func (n *NotifyRouterActivityRouter) notifyLoop() {
	for {
		select {
		case f := <-n.notifyCh:
			f()
		case <-n.shutdownCh:
			return
		}
	}
}

func (n *NotifyRouterActivityRouter) kuid(uid gregor1.UID) keybase1.UID {
	return keybase1.UID(uid.String())
}

func (n *NotifyRouterActivityRouter) Activity(ctx context.Context, uid gregor1.UID,
	topicType chat1.TopicType, activity *chat1.ChatActivity, source chat1.ChatActivitySource) {
	defer n.Trace(ctx, nil, "Activity(%v,%v)", topicType, source)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	if activity == nil {
		return
	}
	switch topicType {
	case chat1.TopicType_KBFSFILEEDIT:
		if libkb.IsMobilePlatform() {
			n.Debug(ctx, "skipping file edit notify on mobile")
			return
		}
	default:
	}
	typ, err := activity.ActivityType()
	if err != nil {
		n.Debug(ctx, "invalid activity type: %v", err)
		return
	}

	var canSkip bool
	// If the conversation is not being actively viewed, we can optionally skip
	// notifications to the UI.
	if typ == chat1.ChatActivityType_INCOMING_MESSAGE {
		act := activity.IncomingMessage()
		if !act.DisplayDesktopNotification && act.Conv != nil &&
			act.Conv.TeamType == chat1.TeamType_COMPLEX &&
			!n.G().Syncer.IsSelectedConversation(act.ConvID) &&
			act.Conv.ReadMsgID+100 < act.Conv.MaxVisibleMsgID {
			n.Debug(ctx, "canSkip UI notification %v for %v", typ, act.ConvID)
			canSkip = true
		}
	}
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleNewChatActivity(ctx, n.kuid(uid), topicType, activity, source, canSkip)
	}
}

func (n *NotifyRouterActivityRouter) TypingUpdate(ctx context.Context, updates []chat1.ConvTypingUpdate) {
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatTypingUpdate(ctx, updates)
	}
}

func (n *NotifyRouterActivityRouter) JoinedConversation(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "JoinedConversation(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatJoinedConversation(ctx, n.kuid(uid), convID, topicType, conv)
	}
}

func (n *NotifyRouterActivityRouter) LeftConversation(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType) {
	defer n.Trace(ctx, nil, "LeftConversation(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatLeftConversation(ctx, n.kuid(uid), convID, topicType)
	}
}

func (n *NotifyRouterActivityRouter) ResetConversation(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType) {
	defer n.Trace(ctx, nil, "ResetConversation(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatResetConversation(ctx, n.kuid(uid), convID, topicType)
	}
}

func (n *NotifyRouterActivityRouter) KBFSToImpteamUpgrade(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType) {
	defer n.Trace(ctx, nil, "KBFSToImpteamUpgrade(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatKBFSToImpteamUpgrade(ctx, n.kuid(uid), convID, topicType)
	}
}

func (n *NotifyRouterActivityRouter) SetConvRetention(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "SetConvRetention(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatSetConvRetention(ctx, n.kuid(uid), convID, topicType, conv)
	}
}

func (n *NotifyRouterActivityRouter) SetTeamRetention(ctx context.Context, uid gregor1.UID,
	teamID keybase1.TeamID, topicType chat1.TopicType, convs []chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "SetTeamRetention(%s,%v)", teamID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatSetTeamRetention(ctx, n.kuid(uid), teamID, topicType, convs)
	}
}

func (n *NotifyRouterActivityRouter) SetConvSettings(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "SetConvSettings(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatSetConvSettings(ctx, n.kuid(uid), convID, topicType, conv)
	}
}

func (n *NotifyRouterActivityRouter) SubteamRename(ctx context.Context, uid gregor1.UID,
	convIDs []chat1.ConversationID, topicType chat1.TopicType, convs []chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "SubteamRename(%v,%d convs)", topicType, len(convs))()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatSubteamRename(ctx, n.kuid(uid), convIDs, topicType, convs)
	}
}

func (n *NotifyRouterActivityRouter) InboxSyncStarted(ctx context.Context, uid gregor1.UID) {
	defer n.Trace(ctx, nil, "InboxSyncStarted")()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatInboxSyncStarted(ctx, n.kuid(uid))
	}
}

func (n *NotifyRouterActivityRouter) InboxSynced(ctx context.Context, uid gregor1.UID,
	topicType chat1.TopicType, syncRes chat1.ChatSyncResult) {
	defer n.Trace(ctx, nil, "InboxSynced(%v)", topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatInboxSynced(ctx, n.kuid(uid), topicType, syncRes)
	}
}

func (n *NotifyRouterActivityRouter) InboxStale(ctx context.Context, uid gregor1.UID) {
	defer n.Trace(ctx, nil, "InboxStale")()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatInboxStale(ctx, n.kuid(uid))
	}
}

func (n *NotifyRouterActivityRouter) ThreadsStale(ctx context.Context, uid gregor1.UID,
	updates []chat1.ConversationStaleUpdate) {
	defer n.Trace(ctx, nil, "ThreadsStale")()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatThreadsStale(ctx, n.kuid(uid), updates)
	}
}

func (n *NotifyRouterActivityRouter) TLFFinalize(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, finalizeInfo chat1.ConversationFinalizeInfo, conv *chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "TLFFinalize(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatTLFFinalize(ctx, n.kuid(uid), convID, topicType, finalizeInfo, conv)
	}
}

func (n *NotifyRouterActivityRouter) TLFResolve(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, resolveInfo chat1.ConversationResolveInfo) {
	defer n.Trace(ctx, nil, "TLFResolve(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatTLFResolve(ctx, n.kuid(uid), convID, topicType, resolveInfo)
	}
}

func (n *NotifyRouterActivityRouter) AttachmentUploadStart(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, outboxID chat1.OutboxID) {
	defer n.Trace(ctx, nil, "AttachmentUploadStart(%s,%s)", convID, outboxID)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatAttachmentUploadStart(ctx, n.kuid(uid), convID, outboxID)
	}
}

func (n *NotifyRouterActivityRouter) AttachmentUploadProgress(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, outboxID chat1.OutboxID, bytesComplete, bytesTotal int64) {
	defer n.Trace(ctx, nil, "AttachmentUploadProgress(%s,%s)", convID, outboxID)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatAttachmentUploadProgress(ctx, n.kuid(uid), convID, outboxID,
			bytesComplete, bytesTotal)
	}
}

func (n *NotifyRouterActivityRouter) PromptUnfurl(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, msgID chat1.MessageID, domain string) {
	defer n.Trace(ctx, nil, "PromptUnfurl(%s,%s)", convID, msgID)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatPromptUnfurl(ctx, n.kuid(uid), convID, msgID, domain)
	}
}

func (n *NotifyRouterActivityRouter) ConvUpdate(ctx context.Context, uid gregor1.UID,
	convID chat1.ConversationID, topicType chat1.TopicType, conv *chat1.InboxUIItem) {
	defer n.Trace(ctx, nil, "ConvUpdate(%s,%v)", convID, topicType)()
	ctx = globals.BackgroundChatCtx(ctx, n.G())
	n.notifyCh <- func() {
		n.G().NotifyRouter.HandleChatConvUpdate(ctx, n.kuid(uid), convID, topicType, conv)
	}
}
<|endoftext|>


<commit_before>// vttestserver is a native Go implementation of `run_local_server.py`.
// It allows users to spawn a self-contained Vitess server for local testing/CI
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/golang/protobuf/proto"
	"vitess.io/vitess/go/vt/log"
	vttestpb "vitess.io/vitess/go/vt/proto/vttest"
	"vitess.io/vitess/go/vt/vttest"
)

type topoFlags struct {
	cells     string
	keyspaces string
	shards    string
	replicas  int
	rdonly    int
}

func (t *topoFlags) buildTopology() (*vttestpb.VTTestTopology, error) {
	topo := &vttestpb.VTTestTopology{}
	topo.Cells = strings.Split(t.cells, ",")

	keyspaces := strings.Split(t.keyspaces, ",")
	shardCounts := strings.Split(t.shards, ",")
	if len(keyspaces) != len(shardCounts) {
		return nil, fmt.Errorf("--keyspaces must be same length as --shards")
	}

	for i := range keyspaces {
		name := keyspaces[i]
		numshards, err := strconv.ParseInt(shardCounts[i], 10, 32)
		if err != nil {
			return nil, err
		}

		ks := &vttestpb.Keyspace{
			Name:         name,
			ReplicaCount: int32(t.replicas),
			RdonlyCount:  int32(t.rdonly),
		}

		for _, shardname := range vttest.GetShardNames(int(numshards)) {
			ks.Shards = append(ks.Shards, &vttestpb.Shard{
				Name: shardname,
			})
		}

		topo.Keyspaces = append(topo.Keyspaces, ks)
	}

	return topo, nil
}

func parseFlags() (config vttest.Config, env vttest.Environment, err error) {
	var seed vttest.SeedConfig
	var topo topoFlags

	var basePort int
	var protoTopo string
	var doSeed bool
	var mycnf string

	flag.IntVar(&basePort, "port", 0,
		"Port to use for vtcombo. If this is 0, a random port will be chosen.")

	flag.StringVar(&protoTopo, "proto_topo", "",
		"Define the fake cluster topology as a compact text format encoded"+
			" vttest proto. See vttest.proto for more information.")

	flag.StringVar(&config.SchemaDir, "schema_dir", "",
		"Directory for initial schema files. Within this dir,"+
			" there should be a subdir for each keyspace. Within"+
			" each keyspace dir, each file is executed as SQL"+
			" after the database is created on each shard."+
			" If the directory contains a vschema.json file, it"+
			" will be used as the vschema for the V3 API.")

	flag.StringVar(&config.DefaultSchemaDir, "default_schema_dir", "",
		"Default directory for initial schema files. If no schema is found"+
			" in schema_dir, default to this location.")

	flag.BoolVar(&config.OnlyMySQL, "mysql_only", false,
		"If this flag is set only mysql is initialized."+
			" The rest of the vitess components are not started."+
			" Also, the output specifies the mysql unix socket"+
			" instead of the vtgate port.")

	flag.BoolVar(&doSeed, "initialize_with_random_data", false,
		"If this flag is each table-shard will be initialized"+
			" with random data. See also the 'rng_seed' and 'min_shard_size'"+
			" and 'max_shard_size' flags.")

	flag.IntVar(&seed.RngSeed, "rng_seed", 123,
		"The random number generator seed to use when initializing"+
			" with random data (see also --initialize_with_random_data)."+
			" Multiple runs with the same seed will result with the same"+
			" initial data.")

	flag.IntVar(&seed.MinSize, "min_table_shard_size", 1000,
		"The minimum number of initial rows in a table shard. Ignored if"+
			"--initialize_with_random_data is false. The actual number is chosen"+
			" randomly.")

	flag.IntVar(&seed.MaxSize, "max_table_shard_size", 10000,
		"The maximum number of initial rows in a table shard. Ignored if"+
			"--initialize_with_random_data is false. The actual number is chosen"+
			" randomly")

	flag.Float64Var(&seed.NullProbability, "null_probability", 0.1,
		"The probability to initialize a field with 'NULL' "+
			" if --initialize_with_random_data is true. Only applies to fields"+
			" that can contain NULL values.")

	flag.StringVar(&config.WebDir, "web_dir", "",
		"location of the vtctld web server files.")

	flag.StringVar(&config.WebDir2, "web_dir2", "",
		"location of the vtctld2 web server files.")

	flag.StringVar(&mycnf, "extra_my_cnf", "",
		"extra files to add to the config, separated by ':'")

	flag.StringVar(&topo.cells, "cells", "test", "Comma separated list of cells")
	flag.StringVar(&topo.keyspaces, "keyspaces", "test_keyspace",
		"Comma separated list of keyspaces")
	flag.StringVar(&topo.shards, "num_shards", "2",
		"Comma separated shard count (one per keyspace)")
	flag.IntVar(&topo.replicas, "replica_count", 2,
		"Replica tablets per shard (includes master)")
	flag.IntVar(&topo.rdonly, "rdonly_count", 1,
		"Rdonly tablets per shard")

	flag.StringVar(&config.Charset, "charset", "utf8", "MySQL charset")
	flag.StringVar(&config.SnapshotFile, "snapshot_file", "",
		"A MySQL DB snapshot file")

	flag.Parse()

	if basePort != 0 {
		env, err = vttest.NewLocalTestEnv("", basePort)
		if err != nil {
			return
		}
	}

	if protoTopo == "" {
		config.Topology, err = topo.buildTopology()
		if err != nil {
			return
		}
	} else {
		var topology vttestpb.VTTestTopology
		err = proto.UnmarshalText(protoTopo, &topology)
		if err != nil {
			return
		}
		if len(topology.Cells) == 0 {
			topology.Cells = append(topology.Cells, "test")
		}
		config.Topology = &topology
	}

	if doSeed {
		config.Seed = &seed
	}

	if mycnf != "" {
		config.ExtraMyCnf = strings.Split(mycnf, ":")
	}

	return
}

func main() {
	config, env, err := parseFlags()
	if err != nil {
		log.Fatal(err)
	}

	log.Infof("Starting local cluster...")
	log.Infof("config: %#v", config)

	cluster := vttest.LocalCluster{
		Config: config,
		Env:    env,
	}

	err = cluster.Setup()
	defer cluster.TearDown()

	if err != nil {
		log.Fatal(err)
	}

	kvconf := cluster.JSONConfig()
	if err := json.NewEncoder(os.Stdout).Encode(kvconf); err != nil {
		log.Fatal(err)
	}

	log.Info("Local cluster started. Waiting for stdin input...")

	_, err = fmt.Scanln()
	if err != nil {
		log.Fatal(err)
	}

	log.Info("Shutting down cleanly")
}
<commit_msg>No stdin for vttestserver<commit_after>// vttestserver is a native Go implementation of `run_local_server.py`.
// It allows users to spawn a self-contained Vitess server for local testing/CI
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/golang/protobuf/proto"
	"vitess.io/vitess/go/vt/log"
	vttestpb "vitess.io/vitess/go/vt/proto/vttest"
	"vitess.io/vitess/go/vt/vttest"
)

type topoFlags struct {
	cells     string
	keyspaces string
	shards    string
	replicas  int
	rdonly    int
}

func (t *topoFlags) buildTopology() (*vttestpb.VTTestTopology, error) {
	topo := &vttestpb.VTTestTopology{}
	topo.Cells = strings.Split(t.cells, ",")

	keyspaces := strings.Split(t.keyspaces, ",")
	shardCounts := strings.Split(t.shards, ",")
	if len(keyspaces) != len(shardCounts) {
		return nil, fmt.Errorf("--keyspaces must be same length as --shards")
	}

	for i := range keyspaces {
		name := keyspaces[i]
		numshards, err := strconv.ParseInt(shardCounts[i], 10, 32)
		if err != nil {
			return nil, err
		}

		ks := &vttestpb.Keyspace{
			Name:         name,
			ReplicaCount: int32(t.replicas),
			RdonlyCount:  int32(t.rdonly),
		}

		for _, shardname := range vttest.GetShardNames(int(numshards)) {
			ks.Shards = append(ks.Shards, &vttestpb.Shard{
				Name: shardname,
			})
		}

		topo.Keyspaces = append(topo.Keyspaces, ks)
	}

	return topo, nil
}

func parseFlags() (config vttest.Config, env vttest.Environment, err error) {
	var seed vttest.SeedConfig
	var topo topoFlags

	var basePort int
	var protoTopo string
	var doSeed bool
	var mycnf string

	flag.IntVar(&basePort, "port", 0,
		"Port to use for vtcombo. If this is 0, a random port will be chosen.")

	flag.StringVar(&protoTopo, "proto_topo", "",
		"Define the fake cluster topology as a compact text format encoded"+
			" vttest proto. See vttest.proto for more information.")

	flag.StringVar(&config.SchemaDir, "schema_dir", "",
		"Directory for initial schema files. Within this dir,"+
			" there should be a subdir for each keyspace. Within"+
			" each keyspace dir, each file is executed as SQL"+
			" after the database is created on each shard."+
			" If the directory contains a vschema.json file, it"+
			" will be used as the vschema for the V3 API.")

	flag.StringVar(&config.DefaultSchemaDir, "default_schema_dir", "",
		"Default directory for initial schema files. If no schema is found"+
			" in schema_dir, default to this location.")

	flag.BoolVar(&config.OnlyMySQL, "mysql_only", false,
		"If this flag is set only mysql is initialized."+
			" The rest of the vitess components are not started."+
			" Also, the output specifies the mysql unix socket"+
			" instead of the vtgate port.")

	flag.BoolVar(&doSeed, "initialize_with_random_data", false,
		"If this flag is each table-shard will be initialized"+
			" with random data. See also the 'rng_seed' and 'min_shard_size'"+
			" and 'max_shard_size' flags.")

	flag.IntVar(&seed.RngSeed, "rng_seed", 123,
		"The random number generator seed to use when initializing"+
			" with random data (see also --initialize_with_random_data)."+
			" Multiple runs with the same seed will result with the same"+
			" initial data.")

	flag.IntVar(&seed.MinSize, "min_table_shard_size", 1000,
		"The minimum number of initial rows in a table shard. Ignored if"+
			"--initialize_with_random_data is false. The actual number is chosen"+
			" randomly.")

	flag.IntVar(&seed.MaxSize, "max_table_shard_size", 10000,
		"The maximum number of initial rows in a table shard. Ignored if"+
			"--initialize_with_random_data is false. The actual number is chosen"+
			" randomly")

	flag.Float64Var(&seed.NullProbability, "null_probability", 0.1,
		"The probability to initialize a field with 'NULL' "+
			" if --initialize_with_random_data is true. Only applies to fields"+
			" that can contain NULL values.")

	flag.StringVar(&config.WebDir, "web_dir", "",
		"location of the vtctld web server files.")

	flag.StringVar(&config.WebDir2, "web_dir2", "",
		"location of the vtctld2 web server files.")

	flag.StringVar(&mycnf, "extra_my_cnf", "",
		"extra files to add to the config, separated by ':'")

	flag.StringVar(&topo.cells, "cells", "test", "Comma separated list of cells")
	flag.StringVar(&topo.keyspaces, "keyspaces", "test_keyspace",
		"Comma separated list of keyspaces")
	flag.StringVar(&topo.shards, "num_shards", "2",
		"Comma separated shard count (one per keyspace)")
	flag.IntVar(&topo.replicas, "replica_count", 2,
		"Replica tablets per shard (includes master)")
	flag.IntVar(&topo.rdonly, "rdonly_count", 1,
		"Rdonly tablets per shard")

	flag.StringVar(&config.Charset, "charset", "utf8", "MySQL charset")
	flag.StringVar(&config.SnapshotFile, "snapshot_file", "",
		"A MySQL DB snapshot file")

	flag.Parse()

	if basePort != 0 {
		env, err = vttest.NewLocalTestEnv("", basePort)
		if err != nil {
			return
		}
	}

	if protoTopo == "" {
		config.Topology, err = topo.buildTopology()
		if err != nil {
			return
		}
	} else {
		var topology vttestpb.VTTestTopology
		err = proto.UnmarshalText(protoTopo, &topology)
		if err != nil {
			return
		}
		if len(topology.Cells) == 0 {
			topology.Cells = append(topology.Cells, "test")
		}
		config.Topology = &topology
	}

	if doSeed {
		config.Seed = &seed
	}

	if mycnf != "" {
		config.ExtraMyCnf = strings.Split(mycnf, ":")
	}

	return
}

func main() {
	config, env, err := parseFlags()
	if err != nil {
		log.Fatal(err)
	}

	log.Infof("Starting local cluster...")
	log.Infof("config: %#v", config)

	cluster := vttest.LocalCluster{
		Config: config,
		Env:    env,
	}

	err = cluster.Setup()
	defer cluster.TearDown()

	if err != nil {
		log.Fatal(err)
	}

	kvconf := cluster.JSONConfig()
	if err := json.NewEncoder(os.Stdout).Encode(kvconf); err != nil {
		log.Fatal(err)
	}

	log.Info("Local cluster started.")

	select {}
}
<|endoftext|>


<commit_before>package common

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/eris-ltd/common/go/log" // so we can flush logs on exit/ifexit
	"github.com/mitchellh/go-homedir"
)

var (
	// Convenience Directories
	GoPath  = os.Getenv("GOPATH")
	ErisLtd = filepath.Join(GoPath, "src", "github.com", "eris-ltd")
	ErisGH  = "https://github.com/eris-ltd/"
	// usr, _   = user.Current() // error?!
	ErisRoot          = ResolveErisRoot()
	ErisContainerRoot = "/home/eris/.eris" // XXX: this is used as root in the `eris/base` image

	// Major Directories
	AppsPath      = filepath.Join(ErisRoot, "apps") // previously "dapps"
	ActionsPath   = filepath.Join(ErisRoot, "actions")
	ChainsPath    = filepath.Join(ErisRoot, "chains") // previously "blockchains"
	KeysPath      = filepath.Join(ErisRoot, "keys")
	LanguagesPath = filepath.Join(ErisRoot, "languages")
	ServicesPath  = filepath.Join(ErisRoot, "services")
	ScratchPath   = filepath.Join(ErisRoot, "scratch")

	//Deprecated Directories
	BlockchainsPath = filepath.Join(ErisRoot, "blockchains")
	DappsPath       = filepath.Join(ErisRoot, "dapps")

	// Keys
	KeysDataPath = filepath.Join(KeysPath, "data")
	KeyNamesPath = filepath.Join(KeysPath, "names")

	// Scratch Directories (globally coordinated)
	EpmScratchPath     = filepath.Join(ScratchPath, "epm")
	LllcScratchPath    = filepath.Join(ScratchPath, "lllc")
	SolcScratchPath    = filepath.Join(ScratchPath, "sol")
	SerpScratchPath    = filepath.Join(ScratchPath, "ser")
	DataContainersPath = filepath.Join(ScratchPath, "data")

	// Blockchains stuff
	HEAD            = filepath.Join(ChainsPath, "HEAD")
	Refs            = filepath.Join(ChainsPath, "refs")
	DefaultChainDir = filepath.Join(ChainsPath, "default")
)

var MajorDirs = []string{
	ErisRoot, ActionsPath, ChainsPath, DefaultChainDir, DataContainersPath, AppsPath, KeysPath, LanguagesPath, ServicesPath, KeysDataPath, KeyNamesPath, ScratchPath, EpmScratchPath, LllcScratchPath, SolcScratchPath, SerpScratchPath,
}

//eris update checks if old dirs exist & migrates them
var DirsToMigrate = map[string]string{
	BlockchainsPath: ChainsPath,
	DappsPath:       AppsPath,
}

//---------------------------------------------
// user and process

func Usr() string {
	u, _ := homedir.Dir()
	return u
}

func Exit(err error) {
	status := 0
	if err != nil {
		log.Flush()
		fmt.Println(err)
		status = 1
	}
	os.Exit(status)
}

func IfExit(err error) {
	if err != nil {
		log.Flush()
		fmt.Println(err)
		os.Exit(1)
	}
}

// user and process
//---------------------------------------------------------------------------
// filesystem

func AbsolutePath(Datadir string, filename string) string {
	if filepath.IsAbs(filename) {
		return filename
	}
	return filepath.Join(Datadir, filename)
}

func InitDataDir(Datadir string) error {
	if _, err := os.Stat(Datadir); err != nil {
		if os.IsNotExist(err) {
			if err := os.MkdirAll(Datadir, 0777); err != nil {
				return err
			}
		}
	}
	return nil
}

func ResolveErisRoot() string {
	var eris string
	if os.Getenv("ERIS") != "" {
		eris = os.Getenv("ERIS")
	} else {
		if runtime.GOOS == "windows" {
			home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
			if home == "" {
				home = os.Getenv("USERPROFILE")
			}
			eris = filepath.Join(home, ".eris")
		} else {
			eris = filepath.Join(Usr(), ".eris")
		}
	}
	return eris
}

// Create the default eris tree
func InitErisDir() (err error) {
	for _, d := range MajorDirs {
		err := InitDataDir(d)
		if err != nil {
			return err
		}
	}
	if _, err = os.Stat(HEAD); err != nil {
		_, err = os.Create(HEAD)
	}
	return
}

func ClearDir(dir string) error {
	fs, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}
	for _, f := range fs {
		n := f.Name()
		if f.IsDir() {
			if err := os.RemoveAll(filepath.Join(dir, f.Name())); err != nil {
				return err
			}
		} else {
			if err := os.Remove(filepath.Join(dir, n)); err != nil {
				return err
			}
		}
	}
	return nil
}

func Copy(src, dst string) error {
	f, err := os.Stat(src)
	if err != nil {
		return err
	}
	if f.IsDir() {
		tmpDir, err := ioutil.TempDir(os.TempDir(), "eris_copy")
		if err != nil {
			return err
		}
		if err := copyDir(src, tmpDir); err != nil {
			return err
		}
		if err := copyDir(tmpDir, dst); err != nil {
			return err
		}
		// fi, err := os.Stat(src)
		// if err := os.MkdirAll(dst, fi.Mode()); err != nil {
		// 	return err
		// }
		// return os.Rename(tmpDir, dst)
		return nil
	}
	return copyFile(src, dst)
}

// assumes we've done our checking
func copyDir(src, dst string) error {
	fi, err := os.Stat(src)
	if err := os.MkdirAll(dst, fi.Mode()); err != nil {
		return err
	}
	fs, err := ioutil.ReadDir(src)
	if err != nil {
		return err
	}

	for _, f := range fs {
		s := filepath.Join(src, f.Name())
		d := filepath.Join(dst, f.Name())
		if f.IsDir() {
			if err := copyDir(s, d); err != nil {
				return err
			}
		} else {
			if err := copyFile(s, d); err != nil {
				return err
			}
		}
	}
	return nil
}

// common golang, really?
func copyFile(src, dst string) error {
	r, err := os.Open(src)
	if err != nil {
		return err
	}
	defer r.Close()

	w, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer w.Close()

	_, err = io.Copy(w, r)
	if err != nil {
		return err
	}
	return nil
}

func WriteFile(data, path string) error {
	if err := os.MkdirAll(filepath.Dir(path), 0775); err != nil {
		return err
	}
	writer, err := os.Create(filepath.Join(path))
	defer writer.Close()
	if err != nil {
		return err
	}
	writer.Write([]byte(data))
	return nil
}

// filesystem
//-------------------------------------------------------
// open text editors

func Editor(file string) error {
	editr := os.Getenv("EDITOR")
	if strings.Contains(editr, "/") {
		editr = filepath.Base(editr)
	}
	switch editr {
	case "", "vim", "vi":
		return vi(file)
	case "emacs":
		return emacs(file)
	default:
		return editor(file)
	}
	// return fmt.Errorf("Unknown editor %s", editr)
}

func emacs(file string) error {
	cmd := exec.Command("emacs", file)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func vi(file string) error {
	cmd := exec.Command("vim", file)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func editor(file string) error {
	cmd := exec.Command(os.Getenv("EDITOR"), file)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}
<commit_msg>remove path<commit_after>package common

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/eris-ltd/common/go/log" // so we can flush logs on exit/ifexit
	"github.com/mitchellh/go-homedir"
)

var (
	// Convenience Directories
	GoPath  = os.Getenv("GOPATH")
	ErisLtd = filepath.Join(GoPath, "src", "github.com", "eris-ltd")
	ErisGH  = "https://github.com/eris-ltd/"
	// usr, _   = user.Current() // error?!
	ErisRoot          = ResolveErisRoot()
	ErisContainerRoot = "/home/eris/.eris" // XXX: this is used as root in the `eris/base` image

	// Major Directories
	AppsPath      = filepath.Join(ErisRoot, "apps") // previously "dapps"
	ActionsPath   = filepath.Join(ErisRoot, "actions")
	ChainsPath    = filepath.Join(ErisRoot, "chains") // previously "blockchains"
	KeysPath      = filepath.Join(ErisRoot, "keys")
	LanguagesPath = filepath.Join(ErisRoot, "languages")
	ServicesPath  = filepath.Join(ErisRoot, "services")
	ScratchPath   = filepath.Join(ErisRoot, "scratch")

	//Deprecated Directories
	BlockchainsPath = filepath.Join(ErisRoot, "blockchains")
	DappsPath       = filepath.Join(ErisRoot, "dapps")

	// Keys
	KeysDataPath = filepath.Join(KeysPath, "data")
	KeyNamesPath = filepath.Join(KeysPath, "names")

	// Scratch Directories (globally coordinated)
	EpmScratchPath     = filepath.Join(ScratchPath, "epm")
	LllcScratchPath    = filepath.Join(ScratchPath, "lllc")
	SolcScratchPath    = filepath.Join(ScratchPath, "sol")
	SerpScratchPath    = filepath.Join(ScratchPath, "ser")
	DataContainersPath = filepath.Join(ScratchPath, "data")

	// Blockchains stuff
	HEAD            = filepath.Join(ChainsPath, "HEAD")
	Refs            = filepath.Join(ChainsPath, "refs")
	DefaultChainDir = filepath.Join(ChainsPath, "default")
)

var MajorDirs = []string{
	ErisRoot, ActionsPath, ChainsPath, DefaultChainDir, DataContainersPath, AppsPath, KeysPath, LanguagesPath, ServicesPath, KeysDataPath, KeyNamesPath, ScratchPath, EpmScratchPath, LllcScratchPath, SolcScratchPath, SerpScratchPath,
}

//eris update checks if old dirs exist & migrates them
var DirsToMigrate = map[string]string{
	BlockchainsPath: ChainsPath,
	DappsPath:       AppsPath,
}

//---------------------------------------------
// user and process

func Usr() string {
	u, _ := homedir.Dir()
	return u
}

func Exit(err error) {
	status := 0
	if err != nil {
		log.Flush()
		fmt.Println(err)
		status = 1
	}
	os.Exit(status)
}

func IfExit(err error) {
	if err != nil {
		log.Flush()
		fmt.Println(err)
		os.Exit(1)
	}
}

// user and process
//---------------------------------------------------------------------------
// filesystem

func AbsolutePath(Datadir string, filename string) string {
	if filepath.IsAbs(filename) {
		return filename
	}
	return filepath.Join(Datadir, filename)
}

func InitDataDir(Datadir string) error {
	if _, err := os.Stat(Datadir); err != nil {
		if os.IsNotExist(err) {
			if err := os.MkdirAll(Datadir, 0777); err != nil {
				return err
			}
		}
	}
	return nil
}

func ResolveErisRoot() string {
	var eris string
	if os.Getenv("ERIS") != "" {
		eris = os.Getenv("ERIS")
	} else {
		if runtime.GOOS == "windows" {
			home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
			if home == "" {
				home = os.Getenv("USERPROFILE")
			}
			eris = filepath.Join(home, ".eris")
		} else {
			eris = filepath.Join(Usr(), ".eris")
		}
	}
	return eris
}

// Create the default eris tree
func InitErisDir() (err error) {
	for _, d := range MajorDirs {
		err := InitDataDir(d)
		if err != nil {
			return err
		}
	}
	if _, err = os.Stat(HEAD); err != nil {
		_, err = os.Create(HEAD)
	}
	return
}

func ClearDir(dir string) error {
	fs, err := ioutil.ReadDir(dir)
	if err != nil {
		return err
	}
	for _, f := range fs {
		n := f.Name()
		if f.IsDir() {
			if err := os.RemoveAll(filepath.Join(dir, f.Name())); err != nil {
				return err
			}
		} else {
			if err := os.Remove(filepath.Join(dir, n)); err != nil {
				return err
			}
		}
	}
	return nil
}

func Copy(src, dst string) error {
	f, err := os.Stat(src)
	if err != nil {
		return err
	}
	if f.IsDir() {
		tmpDir, err := ioutil.TempDir(os.TempDir(), "eris_copy")
		if err != nil {
			return err
		}
		if err := copyDir(src, tmpDir); err != nil {
			return err
		}
		if err := copyDir(tmpDir, dst); err != nil {
			return err
		}
		// fi, err := os.Stat(src)
		// if err := os.MkdirAll(dst, fi.Mode()); err != nil {
		// 	return err
		// }
		// return os.Rename(tmpDir, dst)
		return nil
	}
	return copyFile(src, dst)
}

// assumes we've done our checking
func copyDir(src, dst string) error {
	fi, err := os.Stat(src)
	if err := os.MkdirAll(dst, fi.Mode()); err != nil {
		return err
	}
	fs, err := ioutil.ReadDir(src)
	if err != nil {
		return err
	}

	for _, f := range fs {
		s := filepath.Join(src, f.Name())
		d := filepath.Join(dst, f.Name())
		if f.IsDir() {
			if err := copyDir(s, d); err != nil {
				return err
			}
		} else {
			if err := copyFile(s, d); err != nil {
				return err
			}
		}
	}
	return nil
}

// common golang, really?
func copyFile(src, dst string) error {
	r, err := os.Open(src)
	if err != nil {
		return err
	}
	defer r.Close()

	w, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer w.Close()

	_, err = io.Copy(w, r)
	if err != nil {
		return err
	}
	return nil
}

func WriteFile(data, path string) error {
	if err := os.MkdirAll(filepath.Dir(path), 0775); err != nil {
		return err
	}
	writer, err := os.Create(filepath.Join(path))
	defer writer.Close()
	if err != nil {
		return err
	}
	writer.Write([]byte(data))
	return nil
}

// filesystem
//-------------------------------------------------------
// open text editors

func Editor(file string) error {
	editr := os.Getenv("EDITOR")
	if strings.Contains(editr, "/") {
		editr = filepath.Base(editr)
	}
	switch editr {
	case "", "vim", "vi":
		return vi(file)
	case "emacs":
		return emacs(file)
	default:
		return editor(file)
	}
	// return fmt.Errorf("Unknown editor %s", editr)
}

func emacs(file string) error {
	cmd := exec.Command("emacs", file)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func vi(file string) error {
	cmd := exec.Command("vim", file)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func editor(file string) error {
	cmd := exec.Command(os.Getenv("EDITOR"), file)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd.Run()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	//"log"
	"mime/multipart"
	"os"
	"strconv"
	"strings"
	//"path/filepath"
	"time"
)

type ErrorMessage struct {
	Message string `json:"message"`
}

type Device struct {
	Id         int    `json:"id"`
	Name       string `json:"name"`
	Os_version string `json:"os_version"`
}

type DevicePool struct {
	Id       int      `json:"id"`
	Name     string   `json:"name"`
	Readonly string   `json:"readonly"`
	Devices  []Device `json:"devices"`
}

type Project struct {
	Id              int    `json:"id"`
	Url             string `json:"url"`
	Name            string `json:"name"`
	Project_type_id int    `json:"project_type_id"`
}

type UploadAppParams struct {
	Name string `json:"name"`
	File string `json:"file"`
	Save string `json:"save"`
	Type string `json:type`
}

type UploadedFile struct {
	Id int `json:"file_id"`
}

type runId struct {
	Id int `json:"run_id"`
}
type status struct {
	Status string `json:"status"`
}

type DevicePoolList []DevicePool
type ProjectList []Project

//type UploadedFileList []UploadedFile

func createUploadAppPayloadParam(name string, fileName string, save string, fileType string) (UploadAppParams, error) {
	uploadParams := UploadAppParams{
		Name: name,
		File: fileName,
		Save: save,
		Type: fileType,
	}

	return uploadParams, nil
}

func sendGetRequest(requestURL string, apiKey string) string {
	req, err := http.NewRequest("GET", requestURL, nil)
	req.SetBasicAuth(apiKey, "")
	cli := &http.Client{}
	resp, err := cli.Do(req)
	if err != nil {
		//TODO

	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		//TODO

	}

	resp.Body.Close()
	bodyStr := string(body)
	if resp.StatusCode != 200 {
		var msg ErrorMessage
		json.Unmarshal([]byte(bodyStr), &msg)
		fmt.Println(msg.Message)
		fmt.Printf("REQUEST ERROR: %d", resp.StatusCode)
		switch resp.StatusCode {
		case 400:
			fmt.Println(" BAD REQUEST")
		case 401:
			fmt.Println(" UNAUTHORIZED")
		case 402:
			fmt.Println(" REQUEST FAILED")
		case 404:
			fmt.Println(" NOT FOUND")
		case 500:
			fmt.Println(" SERVER ERROR")
		case 501:
			fmt.Println(" NOT IMPLEMENTED")

		}
		os.Exit(1)
	}
	return bodyStr
}

func sendPostRequestWithFileUpload(apiKey, requestURL string, params UploadAppParams) (str string, err error) {
	// Prepare a form that you will submit to that URL.
	var b bytes.Buffer
	w := multipart.NewWriter(&b)
	// Add file
	f, err := os.Open(params.File)
	if err != nil {
		fmt.Println("Failed to open file", params.File, err)
		os.Exit(1)
	}
	fw, err := w.CreateFormFile("file", params.File)
	if err != nil {
		fmt.Println("Failed to create form", err)
		os.Exit(1)
	}
	if _, err = io.Copy(fw, f); err != nil {
		return "", err
	}
	// Add the other fields
	if fw, err = w.CreateFormField("name"); err != nil {
		fmt.Println("Failed to create form", err)
		os.Exit(1)
	}
	if _, err = fw.Write([]byte(params.Name)); err != nil {
		fmt.Println("Failed to create form", err)
		os.Exit(1)
	}
	if fw, err = w.CreateFormField("type"); err != nil {
		fmt.Println("Failed to create form", err)
		os.Exit(1)
	}
	if _, err = fw.Write([]byte(params.Type)); err != nil {
		fmt.Println("Failed to create form", err)
		os.Exit(1)
	}
	if fw, err = w.CreateFormField("save"); err != nil {
		fmt.Println("Failed to create form", err)
		os.Exit(1)
	}
	if _, err = fw.Write([]byte(params.Save)); err != nil {
		fmt.Println("Failed to create form", err)
		os.Exit(1)
	}

	// Don't forget to close the multipart writer.
	// If you don't close it, your request will be missing the terminating boundary.
	w.Close()

	// Now that you have a form, you can submit it to your handler.
	req, err := http.NewRequest("POST", requestURL, &b)
	if err != nil {
		fmt.Println("Failed to create request", err)
		os.Exit(1)
	}
	//It's needed for http authentication.
	req.SetBasicAuth(apiKey, "")
	// Don't forget to set the content type, this will contain the boundary.
	req.Header.Set("Content-Type", w.FormDataContentType())

	// Submit the request
	client := &http.Client{}
	res, err := client.Do(req)
	if err != nil {
		fmt.Printf("Failed to send the request", err)
		os.Exit(1)
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		//TODO

	}
	res.Body.Close()
	bodyStr := string(body)

	if res.StatusCode != 200 {
		var msg ErrorMessage
		json.Unmarshal([]byte(bodyStr), &msg)
		fmt.Println(msg.Message)
		fmt.Printf("REQUEST ERROR: %d", res.StatusCode)
		switch res.StatusCode {
		case 400:
			fmt.Println(" BAD REQUEST")
		case 401:
			fmt.Println(" UNAUTHORIZED")
		case 402:
			fmt.Println(" REQUEST FAILED")
		case 404:
			fmt.Println(" NOT FOUND")
		case 500:
			fmt.Println(" SERVER ERROR")
		case 501:
			fmt.Println(" NOT IMPLEMENTED")

		}
		os.Exit(1)
	}
	return bodyStr, err
}

func searchProjectIdByName(l ProjectList, name string) (id int) {
	for _, v := range l {
		if v.Name == name {
			return v.Id
		}
	}
	return -1
}

func searchPoolIdByName(l DevicePoolList, name string) (id int) {
	for _, v := range l {
		if v.Name == name {
			return v.Id
		}
	}
	return -1
}

func scheduleTest(apiKey string, projectId int, projectName string, fileId int, poolId int, requestURL string, testType string, testFileId int) (str string, err error) {
	// project, name, app, pool
	// s := []string{`"project":`, strconv.Itoa(projectId), `,"name":"`, projectName, `","app":`, strconv.Itoa(fileId), `,"pool":`, strconv.Itoa(poolId)}
	data := url.Values{}
	data.Set("project", strconv.Itoa(projectId))
	data.Set("name", projectName)
	data.Set("app", strconv.Itoa(fileId))
	data.Set("pool", strconv.Itoa(poolId))
	if testType != "built-in" {
		if testType == "kif" {
			data.Set(testType, "")
		} else {
			data.Set(testType, strconv.Itoa(testFileId))
		}
	}
	req, err := http.NewRequest("POST", requestURL, strings.NewReader(data.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	req.SetBasicAuth(apiKey, "")
	cli := &http.Client{}
	resp, err := cli.Do(req)
	if err != nil {
		fmt.Printf("Failed to send the request", err)
		os.Exit(1)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		//TODO

	}
	resp.Body.Close()
	bodyStr := string(body)
	if resp.StatusCode != 200 {
		var msg ErrorMessage
		json.Unmarshal([]byte(bodyStr), &msg)
		fmt.Println(msg.Message)
		fmt.Printf("REQUEST ERROR: %d", resp.StatusCode)
		switch resp.StatusCode {
		case 400:
			fmt.Println(" BAD REQUEST")
		case 401:
			fmt.Println(" UNAUTHORIZED")
		case 402:
			fmt.Println(" REQUEST FAILED")
		case 404:
			fmt.Println(" NOT FOUND")
		case 500:
			fmt.Println(" SERVER ERROR")
		case 501:
			fmt.Println(" NOT IMPLEMENTED")

		}
		os.Exit(1)
	}
	return bodyStr, err

}

func retrieveResults(apiKey string, pid int, rid int, done chan string) {
	requestURL := "https://appthwack.com/api/run/" + strconv.Itoa(pid) + "/" + strconv.Itoa(rid) + "/status"
	stringifiedJSON := sendGetRequest(requestURL, apiKey)

	var stat status

	json.Unmarshal([]byte(stringifiedJSON), &stat)
	if stat.Status == "completed" {
		done <- stat.Status
	}

}

func main() {

	apiKey := os.Getenv("APPTHWACK_API_KEY")
	if apiKey == "" {
		fmt.Println("$APPTHWACK_API_KEY is not provided!")
		os.Exit(1)
	}

	projectName := os.Getenv("APPTHWACK_PROJECT_NAME")
	devicePoolName := os.Getenv("APPTHWACK_DEVICE_POOL_NAME")
	if projectName == "" {
		fmt.Println("$APPTHWACK_PROJECT_NAME is not provided!")
		os.Exit(1)
	}
	if devicePoolName == "" {
		fmt.Println("$APPTHWACK_DEVICE_POOL_NAME is not provided!")
		os.Exit(1)
	}

	uploadName := os.Getenv("APPTHWACK_UPLOAD_NAME")
	uploadFile := os.Getenv("APPTHWACK_UPLOAD_FILE")
	uploadSave := os.Getenv("APPTHWACK_UPLOAD_SAVE")
	uploadType := os.Getenv("APPTHWACK_UPLOAD_TYPE")
	if uploadName == "" {
		fmt.Println("$APPTHWACK_UPLOAD_NAME is not provided!")
		os.Exit(1)
	}
	if uploadFile == "" {
		fmt.Println("$APPTHWACK_UPLOAD_FILE is not provided!")
		os.Exit(1)
	}
	if uploadSave == "" {
		fmt.Println("$APPTHWACK_UPLOAD_SAVE is not provided!")
		os.Exit(1)
	}
	if uploadType == "" {
		fmt.Println("$APPTHWACK_UPLOAD_TYPE is not provided!")
		os.Exit(1)
	}

	testUploadName := ""
	testUploadFile := ""
	testUploadSave := ""
	testUploadType := os.Getenv("APPTHWACK_TEST_UPLOAD_TYPE")
	if uploadName == "" {
		fmt.Println("$APPTHWACK_TEST_UPLOAD_TYPE is not provided!")
		os.Exit(1)
	}

	if testUploadType != "built-in" {
		testUploadName = os.Getenv("APPTHWACK_TEST_UPLOAD_NAME")
		testUploadFile = os.Getenv("APPTHWACK_TEST_UPLOAD_FILE")
		testUploadSave = os.Getenv("APPTHWACK_TEST_UPLOAD_SAVE")

		if uploadName == "" {
			fmt.Println("$APPTHWACK_TEST_UPLOAD_NAME is not provided!")
			os.Exit(1)
		}
		if uploadName == "" {
			fmt.Println("$APPTHWACK_TEST_UPLOAD_FILE is not provided!")
			os.Exit(1)
		}
		if uploadName == "" {
			fmt.Println("$APPTHWACK_TEST_UPLOAD_SAVE is not provided!")
			os.Exit(1)
		}
	}

	urlDevice := "https://appthwack.com/api/devicepool"
	urlProject := "https://appthwack.com/api/project"
	urlUpload := "https://appthwack.com/api/file"
	urlSchedule := "https://appthwack.com/api/run"

	stringifiedJSON := sendGetRequest(urlProject, apiKey)
	//getting projects
	var resProjList ProjectList

	json.Unmarshal([]byte(stringifiedJSON), &resProjList)
	//fmt.Printf("%#v\n", resProjList)

	//getting device pools
	stringifiedJSON = sendGetRequest(urlDevice, apiKey)

	var resDev DevicePoolList

	json.Unmarshal([]byte(stringifiedJSON), &resDev)
	//fmt.Printf("%#v\n", resDev)

	//getting Id-s
	projectId := (searchProjectIdByName(resProjList, projectName))
	if projectId == -1 {
		//TODO
		fmt.Println("Invalid project name:", projectName)
		os.Exit(1)
	}
	poolId := (searchPoolIdByName(resDev, devicePoolName))
	if poolId == -1 {
		//TODO
		fmt.Println("Invalid device pool name:", devicePoolName)
		os.Exit(1)
	}

	//uploading app
	fileUpload, err := createUploadAppPayloadParam(uploadName, uploadFile, uploadSave, uploadType)
	if err != nil {
		//TODO
	}
	stringifiedJSON, err = sendPostRequestWithFileUpload(apiKey, urlUpload, fileUpload)

	var uploadedFiles UploadedFile

	json.Unmarshal([]byte(stringifiedJSON), &uploadedFiles)

	var uploadedTestFiles UploadedFile

	if testUploadType != "built-in" {
		fileUpload, err := createUploadAppPayloadParam(testUploadName, testUploadFile, testUploadSave, testUploadType)
		if err != nil {
			//TODO
		}
		stringifiedJSON, err = sendPostRequestWithFileUpload(apiKey, urlUpload, fileUpload)

		json.Unmarshal([]byte(stringifiedJSON), &uploadedTestFiles)
	}

	//schedule Test

	fmt.Printf("%#v\n", uploadedFiles)
	stringifiedJSON, err = scheduleTest(apiKey, projectId, projectName, uploadedFiles.Id, poolId, urlSchedule, testUploadType, uploadedTestFiles.Id)
	fmt.Println(stringifiedJSON)

	var runIds runId

	json.Unmarshal([]byte(stringifiedJSON), &runIds)

	done := make(chan string, 1)
	ticker := time.NewTicker(time.Millisecond * 2000).C
	fmt.Printf("Waiting for test results")
	for {
		select {
		case <-ticker:
			go retrieveResults(apiKey, projectId, runIds.Id, done)
			fmt.Printf(".")
		case msg := <-done:
			fmt.Println("status:", msg)
			return
		}
	}

}
<commit_msg>fmt.println replaced with log.fatal<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	//"path/filepath"
	"time"
)

type ErrorMessage struct {
	Message string `json:"message"`
}

type Device struct {
	Id         int    `json:"id"`
	Name       string `json:"name"`
	Os_version string `json:"os_version"`
}

type DevicePool struct {
	Id       int      `json:"id"`
	Name     string   `json:"name"`
	Readonly string   `json:"readonly"`
	Devices  []Device `json:"devices"`
}

type Project struct {
	Id              int    `json:"id"`
	Url             string `json:"url"`
	Name            string `json:"name"`
	Project_type_id int    `json:"project_type_id"`
}

type UploadAppParams struct {
	Name string `json:"name"`
	File string `json:"file"`
	Save string `json:"save"`
	Type string `json:type`
}

type UploadedFile struct {
	Id int `json:"file_id"`
}

type runId struct {
	Id int `json:"run_id"`
}
type status struct {
	Status string `json:"status"`
}

type DevicePoolList []DevicePool
type ProjectList []Project

//type UploadedFileList []UploadedFile

func createUploadAppPayloadParam(name string, fileName string, save string, fileType string) (UploadAppParams, error) {
	uploadParams := UploadAppParams{
		Name: name,
		File: fileName,
		Save: save,
		Type: fileType,
	}

	return uploadParams, nil
}

func sendGetRequest(requestURL string, apiKey string) string {
	req, err := http.NewRequest("GET", requestURL, nil)
	req.SetBasicAuth(apiKey, "")
	cli := &http.Client{}
	resp, err := cli.Do(req)
	if err != nil {
		//TODO
		log.Fatal(err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		//TODO
		log.Fatal(err)

	}

	resp.Body.Close()
	bodyStr := string(body)
	if resp.StatusCode != 200 {
		var msg ErrorMessage
		json.Unmarshal([]byte(bodyStr), &msg)
		fmt.Println(msg.Message)
		fmt.Printf("REQUEST ERROR: %d", resp.StatusCode)
		switch resp.StatusCode {
		case 400:
			log.Fatal(" BAD REQUEST")
		case 401:
			log.Fatal(" UNAUTHORIZED")
		case 402:
			log.Fatal(" REQUEST FAILED")
		case 404:
			log.Fatal(" NOT FOUND")
		case 500:
			log.Fatal(" SERVER ERROR")
		case 501:
			log.Fatal(" NOT IMPLEMENTED")

		}

	}
	return bodyStr
}

func sendPostRequestWithFileUpload(apiKey, requestURL string, params UploadAppParams) (str string, err error) {
	// Prepare a form that you will submit to that URL.
	var b bytes.Buffer
	w := multipart.NewWriter(&b)
	// Add file
	f, err := os.Open(params.File)
	if err != nil {
		log.Fatal("Failed to open file", params.File, err)
	}
	fw, err := w.CreateFormFile("file", params.File)
	if err != nil {
		log.Fatal("Failed to create form", err)
	}
	if _, err = io.Copy(fw, f); err != nil {
		return "", err
	}
	// Add the other fields
	if fw, err = w.CreateFormField("name"); err != nil {
		log.Fatal("Failed to create form", err)
	}
	if _, err = fw.Write([]byte(params.Name)); err != nil {
		log.Fatal("Failed to create form", err)
	}
	if fw, err = w.CreateFormField("type"); err != nil {
		log.Fatal("Failed to create form", err)
	}
	if _, err = fw.Write([]byte(params.Type)); err != nil {
		log.Fatal("Failed to create form", err)
	}
	if fw, err = w.CreateFormField("save"); err != nil {
		log.Fatal("Failed to create form", err)
	}
	if _, err = fw.Write([]byte(params.Save)); err != nil {
		log.Fatal("Failed to create form", err)
	}

	// Don't forget to close the multipart writer.
	// If you don't close it, your request will be missing the terminating boundary.
	w.Close()

	// Now that you have a form, you can submit it to your handler.
	req, err := http.NewRequest("POST", requestURL, &b)
	if err != nil {
		log.Fatal("Failed to create request", err)
	}
	//It's needed for http authentication.
	req.SetBasicAuth(apiKey, "")
	// Don't forget to set the content type, this will contain the boundary.
	req.Header.Set("Content-Type", w.FormDataContentType())

	// Submit the request
	client := &http.Client{}
	res, err := client.Do(req)
	if err != nil {
		fmt.Printf("Failed to send the request", err)
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		//TODO
		log.Fatal(err)
	}
	res.Body.Close()
	bodyStr := string(body)

	if res.StatusCode != 200 {
		var msg ErrorMessage
		json.Unmarshal([]byte(bodyStr), &msg)
		fmt.Println(msg.Message)
		fmt.Printf("REQUEST ERROR: %d", res.StatusCode)
		switch res.StatusCode {
		case 400:
			log.Fatal(" BAD REQUEST")
		case 401:
			log.Fatal(" UNAUTHORIZED")
		case 402:
			log.Fatal(" REQUEST FAILED")
		case 404:
			log.Fatal(" NOT FOUND")
		case 500:
			log.Fatal(" SERVER ERROR")
		case 501:
			log.Fatal(" NOT IMPLEMENTED")

		}
	}
	return bodyStr, err
}

func searchProjectIdByName(l ProjectList, name string) (id int) {
	for _, v := range l {
		if v.Name == name {
			return v.Id
		}
	}
	return -1
}

func searchPoolIdByName(l DevicePoolList, name string) (id int) {
	for _, v := range l {
		if v.Name == name {
			return v.Id
		}
	}
	return -1
}

func scheduleTest(apiKey string, projectId int, projectName string, fileId int, poolId int, requestURL string, testType string, testFileId int) (str string, err error) {
	// project, name, app, pool
	// s := []string{`"project":`, strconv.Itoa(projectId), `,"name":"`, projectName, `","app":`, strconv.Itoa(fileId), `,"pool":`, strconv.Itoa(poolId)}
	data := url.Values{}
	data.Set("project", strconv.Itoa(projectId))
	data.Set("name", projectName)
	data.Set("app", strconv.Itoa(fileId))
	data.Set("pool", strconv.Itoa(poolId))
	if testType != "built-in" {
		if testType == "kif" {
			data.Set(testType, "")
		} else {
			data.Set(testType, strconv.Itoa(testFileId))
		}
	}
	req, err := http.NewRequest("POST", requestURL, strings.NewReader(data.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	req.SetBasicAuth(apiKey, "")
	cli := &http.Client{}
	resp, err := cli.Do(req)
	if err != nil {
		log.Fatal("Failed to send the request", err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		//TODO
		log.Fatal(err)

	}
	resp.Body.Close()
	bodyStr := string(body)
	if resp.StatusCode != 200 {
		var msg ErrorMessage
		json.Unmarshal([]byte(bodyStr), &msg)
		fmt.Println(msg.Message)
		fmt.Printf("REQUEST ERROR: %d", resp.StatusCode)
		switch resp.StatusCode {
		case 400:
			log.Fatal(" BAD REQUEST")
		case 401:
			log.Fatal(" UNAUTHORIZED")
		case 402:
			log.Fatal(" REQUEST FAILED")
		case 404:
			log.Fatal(" NOT FOUND")
		case 500:
			log.Fatal(" SERVER ERROR")
		case 501:
			log.Fatal(" NOT IMPLEMENTED")

		}
	}
	return bodyStr, err

}

func retrieveResults(apiKey string, pid int, rid int, done chan string) {
	requestURL := "https://appthwack.com/api/run/" + strconv.Itoa(pid) + "/" + strconv.Itoa(rid) + "/status"
	stringifiedJSON := sendGetRequest(requestURL, apiKey)

	var stat status

	json.Unmarshal([]byte(stringifiedJSON), &stat)
	if stat.Status == "completed" {
		done <- stat.Status
	}

}

func main() {

	apiKey := os.Getenv("APPTHWACK_API_KEY")
	if apiKey == "" {
		log.Fatal("$APPTHWACK_API_KEY is not provided!")
	}

	projectName := os.Getenv("APPTHWACK_PROJECT_NAME")
	devicePoolName := os.Getenv("APPTHWACK_DEVICE_POOL_NAME")
	if projectName == "" {
		log.Fatal("$APPTHWACK_PROJECT_NAME is not provided!")
	}
	if devicePoolName == "" {
		log.Fatal("$APPTHWACK_DEVICE_POOL_NAME is not provided!")
	}

	uploadName := os.Getenv("APPTHWACK_UPLOAD_NAME")
	uploadFile := os.Getenv("APPTHWACK_UPLOAD_FILE")
	uploadSave := os.Getenv("APPTHWACK_UPLOAD_SAVE")
	uploadType := os.Getenv("APPTHWACK_UPLOAD_TYPE")
	if uploadName == "" {
		log.Fatal("$APPTHWACK_UPLOAD_NAME is not provided!")
	}
	if uploadFile == "" {
		log.Fatal("$APPTHWACK_UPLOAD_FILE is not provided!")
	}
	if uploadSave == "" {
		log.Fatal("$APPTHWACK_UPLOAD_SAVE is not provided!")
	}
	if uploadType == "" {
		log.Fatal("$APPTHWACK_UPLOAD_TYPE is not provided!")
	}

	testUploadName := ""
	testUploadFile := ""
	testUploadSave := ""
	testUploadType := os.Getenv("APPTHWACK_TEST_UPLOAD_TYPE")
	if uploadName == "" {
		log.Fatal("$APPTHWACK_TEST_UPLOAD_TYPE is not provided!")
	}

	if testUploadType != "built-in" {
		testUploadName = os.Getenv("APPTHWACK_TEST_UPLOAD_NAME")
		testUploadFile = os.Getenv("APPTHWACK_TEST_UPLOAD_FILE")
		testUploadSave = os.Getenv("APPTHWACK_TEST_UPLOAD_SAVE")

		if uploadName == "" {
			log.Fatal("$APPTHWACK_TEST_UPLOAD_NAME is not provided!")

		}
		if uploadName == "" {
			log.Fatal("$APPTHWACK_TEST_UPLOAD_FILE is not provided!")

		}
		if uploadName == "" {
			log.Fatal("$APPTHWACK_TEST_UPLOAD_SAVE is not provided!")

		}
	}

	urlDevice := "https://appthwack.com/api/devicepool"
	urlProject := "https://appthwack.com/api/project"
	urlUpload := "https://appthwack.com/api/file"
	urlSchedule := "https://appthwack.com/api/run"

	stringifiedJSON := sendGetRequest(urlProject, apiKey)
	//getting projects
	var resProjList ProjectList

	json.Unmarshal([]byte(stringifiedJSON), &resProjList)
	//fmt.Printf("%#v\n", resProjList)

	//getting device pools
	stringifiedJSON = sendGetRequest(urlDevice, apiKey)

	var resDev DevicePoolList

	json.Unmarshal([]byte(stringifiedJSON), &resDev)
	//fmt.Printf("%#v\n", resDev)

	//getting Id-s
	projectId := (searchProjectIdByName(resProjList, projectName))
	if projectId == -1 {
		//TODO
		log.Fatal("Invalid project name:", projectName)
	}
	poolId := (searchPoolIdByName(resDev, devicePoolName))
	if poolId == -1 {
		//TODO
		log.Fatal("Invalid device pool name:", devicePoolName)
	}

	//uploading app
	fileUpload, err := createUploadAppPayloadParam(uploadName, uploadFile, uploadSave, uploadType)
	if err != nil {
		//TODO
		log.Fatal(err)
	}
	stringifiedJSON, err = sendPostRequestWithFileUpload(apiKey, urlUpload, fileUpload)

	var uploadedFiles UploadedFile

	json.Unmarshal([]byte(stringifiedJSON), &uploadedFiles)

	var uploadedTestFiles UploadedFile

	if testUploadType != "built-in" {
		fileUpload, err := createUploadAppPayloadParam(testUploadName, testUploadFile, testUploadSave, testUploadType)
		if err != nil {
			//TODO
			log.Fatal(err)
		}
		stringifiedJSON, err = sendPostRequestWithFileUpload(apiKey, urlUpload, fileUpload)

		json.Unmarshal([]byte(stringifiedJSON), &uploadedTestFiles)
	}

	//schedule Test

	stringifiedJSON, err = scheduleTest(apiKey, projectId, projectName, uploadedFiles.Id, poolId, urlSchedule, testUploadType, uploadedTestFiles.Id)

	var runIds runId

	json.Unmarshal([]byte(stringifiedJSON), &runIds)

	done := make(chan string, 1)
	ticker := time.NewTicker(time.Millisecond * 2000).C
	fmt.Printf("Waiting for test results")
	counter := 0
	for {
		select {
		case <-ticker:
			go retrieveResults(apiKey, projectId, runIds.Id, done)
			fmt.Printf(".")
			if counter == 9 {
				counter = 0
				fmt.Printf("\n")

			}
		case msg := <-done:
			fmt.Println("\n", "status:", msg)
			return
		}
	}

}
<|endoftext|>


<commit_before>package zeusclient

import (
	"io"
	"net"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"sync"
	"syscall"

	"github.com/burke/ttyutils"
	"github.com/burke/zeus/go/messages"
	slog "github.com/burke/zeus/go/shinylog"
	"github.com/burke/zeus/go/unixsocket"
	"github.com/burke/zeus/go/zerror"
	"github.com/kr/pty"
)

const (
	sigInt  = 3
	sigQuit = 28
	sigTstp = 26
)

// man signal | grep 'terminate process' | awk '{print $2}' | xargs -I '{}' echo -n "syscall.{}, "
var terminatingSignals = []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGKILL, syscall.SIGPIPE, syscall.SIGALRM, syscall.SIGTERM, syscall.SIGXCPU, syscall.SIGXFSZ, syscall.SIGVTALRM, syscall.SIGPROF, syscall.SIGUSR1, syscall.SIGUSR2}

func Run(args []string, input io.Reader, output *os.File, stderr *os.File) int {
	if os.Getenv("RAILS_ENV") != "" {
		println("Warning: Specifying a Rails environment via RAILS_ENV has no effect for commands run with zeus.")
		println("As a safety precaution to protect you from nuking your development database,")
		println("Zeus will now cowardly refuse to proceed. Please unset RAILS_ENV and try again.")
		return 1
	}

	// setup stdout
	localStdout, remoteStdout, outputIsTerminal, err := socketsForOutput(output)
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}
	defer localStdout.Close()
	defer remoteStdout.Close()

	// setup stderr
	localStderr, remoteStderr, _, err := socketsForOutput(stderr)
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}
	defer localStderr.Close()
	defer remoteStderr.Close()

	addr, err := net.ResolveUnixAddr("unixgram", unixsocket.ZeusSockName())
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	conn, err := net.DialUnix("unix", nil, addr)
	if err != nil {
		zerror.ErrorCantConnectToMaster()
		return 1
	}
	usock := unixsocket.New(conn)

	msg := messages.CreateCommandAndArgumentsMessage(args, os.Getpid())
	usock.WriteMessage(msg)
	err = sendCommandLineArguments(usock, args)
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	usock.WriteFD(int(remoteStdout.Fd()))
	usock.WriteFD(int(remoteStderr.Fd()))

	msg, err = usock.ReadMessage()
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	parts := strings.Split(msg, "\000")
	commandPid, err := strconv.Atoi(parts[0])
	defer func() {
		if commandPid > 0 {
			// Just in case.
			syscall.Kill(commandPid, 9)
		}
	}()

	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	if outputIsTerminal {
		c := make(chan os.Signal, 1)
		handledSignals := append(append(terminatingSignals, syscall.SIGWINCH), syscall.SIGCONT)
		signal.Notify(c, handledSignals...)
		go func() {
			for sig := range c {
				if sig == syscall.SIGCONT {
					syscall.Kill(commandPid, syscall.SIGCONT)
				} else if sig == syscall.SIGWINCH {
					ttyutils.MirrorWinsize(output, localStdout)
					syscall.Kill(commandPid, syscall.SIGWINCH)
				} else { // member of terminatingSignals
					print("\r")
					syscall.Kill(commandPid, sig.(syscall.Signal))
					os.Exit(1)
				}
			}
		}()
	}

	var exitStatus int = -1
	if len(parts) > 2 {
		exitStatus, err = strconv.Atoi(parts[0])
		if err != nil {
			slog.ErrorString(err.Error() + "\r")
			return 1
		}
	}

	var endOfIO sync.WaitGroup

	err = forwardOutput(localStdout, output, endOfIO)
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	err = forwardOutput(localStderr, stderr, endOfIO)
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	go func() {
		endOfIO.Add(1)
		buf := make([]byte, 8192)
		for {
			n, err := input.Read(buf)
			if err != nil {
				endOfIO.Done()
				break
			}
			if outputIsTerminal {
				for i := 0; i < n; i++ {
					switch buf[i] {
					case sigInt:
						syscall.Kill(commandPid, syscall.SIGINT)
					case sigQuit:
						syscall.Kill(commandPid, syscall.SIGQUIT)
					case sigTstp:
						syscall.Kill(commandPid, syscall.SIGTSTP)
						syscall.Kill(os.Getpid(), syscall.SIGTSTP)
					}
				}
			}
			localStdout.Write(buf[:n])
		}
	}()

	endOfIO.Wait()

	if exitStatus == -1 {
		msg, err = usock.ReadMessage()
		if err != nil {
			slog.ErrorString(err.Error() + "\r")
			return 1
		}
		parts := strings.Split(msg, "\000")
		exitStatus, err = strconv.Atoi(parts[0])
		if err != nil {
			slog.ErrorString(err.Error() + "\r")
			return 1
		}
	}

	return exitStatus
}

func sendCommandLineArguments(usock *unixsocket.Usock, args []string) error {
	master, slave, err := unixsocket.Socketpair(syscall.SOCK_STREAM)
	if err != nil {
		return err
	}
	usock.WriteFD(int(slave.Fd()))
	if err != nil {
		return err
	}
	slave.Close()

	go func() {
		defer master.Close()
		argAsBytes := []byte{}
		for _, arg := range args[1:] {
			argAsBytes = append(argAsBytes, []byte(arg)...)
			argAsBytes = append(argAsBytes, byte(0))
		}
		_, err = master.Write(argAsBytes)
		if err != nil {
			slog.ErrorString("Could not send arguments across: " +
				err.Error() + "\r")
			return
		}
	}()

	return nil
}

func socketsForOutput(out *os.File) (local, remote *os.File, outIsTerminal bool, err error) {
	outIsTerminal = ttyutils.IsTerminal(out.Fd())

	if outIsTerminal {
		local, remote, err = pty.Open()
	} else {
		local, remote, err = unixsocket.Socketpair(syscall.SOCK_STREAM)
	}

	return
}

func forwardOutput(from, to *os.File, signalEnd sync.WaitGroup) (err error) {
	var oldToState *ttyutils.Termios
	ttyutils.MirrorWinsize(to, from)

	if ttyutils.IsTerminal(to.Fd()) {
		oldToState, err = ttyutils.MakeTerminalRaw(to.Fd())
		if err != nil {
			return
		}
	}

	go func() {
		defer ttyutils.RestoreTerminalState(to.Fd(), oldToState)

		signalEnd.Add(1)
		for {
			buf := make([]byte, 1024)
			n, err := from.Read(buf)

			if err == nil || (err == io.EOF && n > 0) {
				to.Write(buf[:n])
			} else {
				signalEnd.Done()
				break
			}
		}
	}()

	return
}
<commit_msg>restore terminal state after exit<commit_after>package zeusclient

import (
	"io"
	"net"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"sync"
	"syscall"

	"github.com/burke/ttyutils"
	"github.com/burke/zeus/go/messages"
	slog "github.com/burke/zeus/go/shinylog"
	"github.com/burke/zeus/go/unixsocket"
	"github.com/burke/zeus/go/zerror"
	"github.com/kr/pty"
)

const (
	sigInt  = 3
	sigQuit = 28
	sigTstp = 26
)

// man signal | grep 'terminate process' | awk '{print $2}' | xargs -I '{}' echo -n "syscall.{}, "
var terminatingSignals = []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGKILL, syscall.SIGPIPE, syscall.SIGALRM, syscall.SIGTERM, syscall.SIGXCPU, syscall.SIGXFSZ, syscall.SIGVTALRM, syscall.SIGPROF, syscall.SIGUSR1, syscall.SIGUSR2}

func Run(args []string, input io.Reader, output *os.File, stderr *os.File) int {
	if os.Getenv("RAILS_ENV") != "" {
		println("Warning: Specifying a Rails environment via RAILS_ENV has no effect for commands run with zeus.")
		println("As a safety precaution to protect you from nuking your development database,")
		println("Zeus will now cowardly refuse to proceed. Please unset RAILS_ENV and try again.")
		return 1
	}

	// setup stdout
	localStdout, remoteStdout, outputIsTerminal, err := socketsForOutput(output)
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}
	defer localStdout.Close()
	defer remoteStdout.Close()

	// setup stderr
	localStderr, remoteStderr, _, err := socketsForOutput(stderr)
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}
	defer localStderr.Close()
	defer remoteStderr.Close()

	addr, err := net.ResolveUnixAddr("unixgram", unixsocket.ZeusSockName())
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	conn, err := net.DialUnix("unix", nil, addr)
	if err != nil {
		zerror.ErrorCantConnectToMaster()
		return 1
	}
	usock := unixsocket.New(conn)

	msg := messages.CreateCommandAndArgumentsMessage(args, os.Getpid())
	usock.WriteMessage(msg)
	err = sendCommandLineArguments(usock, args)
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	usock.WriteFD(int(remoteStdout.Fd()))
	usock.WriteFD(int(remoteStderr.Fd()))

	msg, err = usock.ReadMessage()
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	parts := strings.Split(msg, "\000")
	commandPid, err := strconv.Atoi(parts[0])
	defer func() {
		if commandPid > 0 {
			// Just in case.
			syscall.Kill(commandPid, 9)
		}
	}()

	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	if outputIsTerminal {
		c := make(chan os.Signal, 1)
		handledSignals := append(append(terminatingSignals, syscall.SIGWINCH), syscall.SIGCONT)
		signal.Notify(c, handledSignals...)
		go func() {
			for sig := range c {
				if sig == syscall.SIGCONT {
					syscall.Kill(commandPid, syscall.SIGCONT)
				} else if sig == syscall.SIGWINCH {
					ttyutils.MirrorWinsize(output, localStdout)
					syscall.Kill(commandPid, syscall.SIGWINCH)
				} else { // member of terminatingSignals
					print("\r")
					syscall.Kill(commandPid, sig.(syscall.Signal))
					os.Exit(1)
				}
			}
		}()
	}

	var exitStatus int = -1
	if len(parts) > 2 {
		exitStatus, err = strconv.Atoi(parts[0])
		if err != nil {
			slog.ErrorString(err.Error() + "\r")
			return 1
		}
	}

	var endOfIO sync.WaitGroup

	var oldTerminalStateStdout *ttyutils.Termios
	err, oldTerminalStateStdout = forwardOutput(localStdout, output, endOfIO)
	defer ttyutils.RestoreTerminalState(output.Fd(), oldTerminalStateStdout)
	// TODO restore terminal state
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	var oldTerminalStateStderr *ttyutils.Termios
	err, oldTerminalStateStderr = forwardOutput(localStderr, stderr, endOfIO)
	defer ttyutils.RestoreTerminalState(stderr.Fd(), oldTerminalStateStderr)
	if err != nil {
		slog.ErrorString(err.Error() + "\r")
		return 1
	}

	go func() {
		endOfIO.Add(1)
		buf := make([]byte, 8192)
		for {
			n, err := input.Read(buf)
			if err != nil {
				endOfIO.Done()
				break
			}
			if outputIsTerminal {
				for i := 0; i < n; i++ {
					switch buf[i] {
					case sigInt:
						syscall.Kill(commandPid, syscall.SIGINT)
					case sigQuit:
						syscall.Kill(commandPid, syscall.SIGQUIT)
					case sigTstp:
						syscall.Kill(commandPid, syscall.SIGTSTP)
						syscall.Kill(os.Getpid(), syscall.SIGTSTP)
					}
				}
			}
			localStdout.Write(buf[:n])
		}
	}()

	endOfIO.Wait()

	if exitStatus == -1 {
		msg, err = usock.ReadMessage()
		if err != nil {
			slog.ErrorString(err.Error() + "\r")
			return 1
		}
		parts := strings.Split(msg, "\000")
		exitStatus, err = strconv.Atoi(parts[0])
		if err != nil {
			slog.ErrorString(err.Error() + "\r")
			return 1
		}
	}

	return exitStatus
}

func sendCommandLineArguments(usock *unixsocket.Usock, args []string) error {
	master, slave, err := unixsocket.Socketpair(syscall.SOCK_STREAM)
	if err != nil {
		return err
	}
	usock.WriteFD(int(slave.Fd()))
	if err != nil {
		return err
	}
	slave.Close()

	go func() {
		defer master.Close()
		argAsBytes := []byte{}
		for _, arg := range args[1:] {
			argAsBytes = append(argAsBytes, []byte(arg)...)
			argAsBytes = append(argAsBytes, byte(0))
		}
		_, err = master.Write(argAsBytes)
		if err != nil {
			slog.ErrorString("Could not send arguments across: " +
				err.Error() + "\r")
			return
		}
	}()

	return nil
}

func socketsForOutput(out *os.File) (local, remote *os.File, outIsTerminal bool, err error) {
	outIsTerminal = ttyutils.IsTerminal(out.Fd())

	if outIsTerminal {
		local, remote, err = pty.Open()
	} else {
		local, remote, err = unixsocket.Socketpair(syscall.SOCK_STREAM)
	}

	return
}

func forwardOutput(from, to *os.File, signalEnd sync.WaitGroup) (err error, oldTerminalState *ttyutils.Termios) {
	ttyutils.MirrorWinsize(to, from)

	if ttyutils.IsTerminal(to.Fd()) {
		oldTerminalState, err = ttyutils.MakeTerminalRaw(to.Fd())
		if err != nil {
			return
		}
	}

	go func() {
		defer ttyutils.RestoreTerminalState(to.Fd(), oldTerminalState)

		signalEnd.Add(1)
		for {
			buf := make([]byte, 1024)
			n, err := from.Read(buf)

			if err == nil || (err == io.EOF && n > 0) {
				to.Write(buf[:n])
			} else {
				signalEnd.Done()
				break
			}
		}
	}()

	return
}
<|endoftext|>