<commit_before>package main

import (
	"fmt"
	"log"
	"os"

	"github.com/udhos/acigo/aci"
)

func main() {

	debug := os.Getenv("DEBUG") != ""

	if len(os.Args) < 2 {
		log.Fatalf("usage: %s add|del|list|run args", os.Args[0])
	}

	a, errLogin := login(debug)
	if errLogin != nil {
		log.Printf("exiting: %v", errLogin)
		return
	}

	defer logout(a)

	execute(a, os.Args[1], os.Args[2:])

	// display existing

	list, errList := a.ExportConfigurationList()
	if errList != nil {
		log.Printf("could not list: %v", errList)
		return
	}

	for _, t := range list {
		config := t["name"]
		dn := t["dn"]
		adminSt := t["adminSt"]
		format := t["format"]
		descr := t["descr"]

		log.Printf("FOUND export config: config=%s dn=%s adminSt=%s format=%s descr=%s", config, dn, adminSt, format, descr)

		conf, isStr := config.(string)
		if !isStr {
			log.Printf("  config=%s not a string", config)
			continue
		}

		loc, errLoc := a.ExportConfigurationRemoteLocationGet(conf)
		if errLoc == nil {
			name := loc["tnFileRemotePathName"]
			log.Printf("  config=%s remote location: name=[%s]", conf, name)
		}

		sched, errSched := a.ExportConfigurationSchedulerGet(conf)
		if errSched == nil {
			name := sched["tnTrigSchedPName"]
			log.Printf("  config=%s scheduler: name=[%s]", conf, name)
		}
	}
}

func execute(a *aci.Client, cmd string, args []string) {
	switch cmd {
	case "add":
		if len(args) < 3 {
			log.Fatalf("usage: %s add config scheduler remote-location [descr]", os.Args[0])
		}
		config := args[0]
		scheduler := args[1]
		remoteLocation := args[2]
		var descr string
		if len(args) > 3 {
			descr = args[3]
		}
		errAdd := a.ExportConfigurationAdd(config, scheduler, remoteLocation, descr)
		if errAdd != nil {
			log.Printf("FAILURE: add error: %v", errAdd)
			return
		}
		log.Printf("SUCCESS: add: %s %s %s %s", config, scheduler, remoteLocation, descr)
	case "del":
		if len(args) < 1 {
			log.Fatalf("usage: %s del config", os.Args[0])
		}
		config := args[0]
		errDel := a.ExportConfigurationDel(config)
		if errDel != nil {
			log.Printf("FAILURE: del error: %v", errDel)
			return
		}
		log.Printf("SUCCESS: del: %s", config)
	case "run":
		if len(args) < 1 {
			log.Fatalf("usage: %s run config", os.Args[0])
		}
		config := args[0]
		errRun := a.ExportConfigurationRun(config)
		if errRun != nil {
			log.Printf("FAILURE: run error: %v", errRun)
			return
		}
		log.Printf("SUCCESS: rn: %s", config)
	case "list":
	default:
		log.Printf("unknown command: %s", cmd)
	}
}

func login(debug bool) (*aci.Client, error) {

	a, errNew := aci.New(aci.ClientOptions{Debug: debug})
	if errNew != nil {
		return nil, fmt.Errorf("login new client error: %v", errNew)
	}

	errLogin := a.Login()
	if errLogin != nil {
		return nil, fmt.Errorf("login error: %v", errLogin)
	}

	return a, nil
}

func logout(a *aci.Client) {
	errLogout := a.Logout()
	if errLogout != nil {
		log.Printf("logout error: %v", errLogout)
		return
	}
}
<commit_msg>Typo.<commit_after>package main

import (
	"fmt"
	"log"
	"os"

	"github.com/udhos/acigo/aci"
)

func main() {

	debug := os.Getenv("DEBUG") != ""

	if len(os.Args) < 2 {
		log.Fatalf("usage: %s add|del|list|run args", os.Args[0])
	}

	a, errLogin := login(debug)
	if errLogin != nil {
		log.Printf("exiting: %v", errLogin)
		return
	}

	defer logout(a)

	execute(a, os.Args[1], os.Args[2:])

	// display existing

	list, errList := a.ExportConfigurationList()
	if errList != nil {
		log.Printf("could not list: %v", errList)
		return
	}

	for _, t := range list {
		config := t["name"]
		dn := t["dn"]
		adminSt := t["adminSt"]
		format := t["format"]
		descr := t["descr"]

		log.Printf("FOUND export config: config=%s dn=%s adminSt=%s format=%s descr=%s", config, dn, adminSt, format, descr)

		conf, isStr := config.(string)
		if !isStr {
			log.Printf("  config=%s not a string", config)
			continue
		}

		loc, errLoc := a.ExportConfigurationRemoteLocationGet(conf)
		if errLoc == nil {
			name := loc["tnFileRemotePathName"]
			log.Printf("  config=%s remote location: name=[%s]", conf, name)
		}

		sched, errSched := a.ExportConfigurationSchedulerGet(conf)
		if errSched == nil {
			name := sched["tnTrigSchedPName"]
			log.Printf("  config=%s scheduler: name=[%s]", conf, name)
		}
	}
}

func execute(a *aci.Client, cmd string, args []string) {
	switch cmd {
	case "add":
		if len(args) < 3 {
			log.Fatalf("usage: %s add config scheduler remote-location [descr]", os.Args[0])
		}
		config := args[0]
		scheduler := args[1]
		remoteLocation := args[2]
		var descr string
		if len(args) > 3 {
			descr = args[3]
		}
		errAdd := a.ExportConfigurationAdd(config, scheduler, remoteLocation, descr)
		if errAdd != nil {
			log.Printf("FAILURE: add error: %v", errAdd)
			return
		}
		log.Printf("SUCCESS: add: %s %s %s %s", config, scheduler, remoteLocation, descr)
	case "del":
		if len(args) < 1 {
			log.Fatalf("usage: %s del config", os.Args[0])
		}
		config := args[0]
		errDel := a.ExportConfigurationDel(config)
		if errDel != nil {
			log.Printf("FAILURE: del error: %v", errDel)
			return
		}
		log.Printf("SUCCESS: del: %s", config)
	case "run":
		if len(args) < 1 {
			log.Fatalf("usage: %s run config", os.Args[0])
		}
		config := args[0]
		errRun := a.ExportConfigurationRun(config)
		if errRun != nil {
			log.Printf("FAILURE: run error: %v", errRun)
			return
		}
		log.Printf("SUCCESS: run: %s", config)
	case "list":
	default:
		log.Printf("unknown command: %s", cmd)
	}
}

func login(debug bool) (*aci.Client, error) {

	a, errNew := aci.New(aci.ClientOptions{Debug: debug})
	if errNew != nil {
		return nil, fmt.Errorf("login new client error: %v", errNew)
	}

	errLogin := a.Login()
	if errLogin != nil {
		return nil, fmt.Errorf("login error: %v", errLogin)
	}

	return a, nil
}

func logout(a *aci.Client) {
	errLogout := a.Logout()
	if errLogout != nil {
		log.Printf("logout error: %v", errLogout)
		return
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
)

// Actually processses a file that's in the new folder.
func processTV(folder string, file string, paths Paths, config Config) error {
	inPath := filepath.Join(folder, file)
	log.Println("Processing", file)

	// Parse the title.
	showTitleFromFile, season, episode, err := showSeasonEpisodeFromFile(file)
	if err != nil {
		log.Println("Failed to parse season/episode for", file)
		failedPath := filepath.Join(paths.Failed, file) // Move to 'failed'.
		os.Rename(inPath, failedPath)
		return err
	}

	// Get and save the show data. This has to happen for every episode so we can get the proper title name.
	omdbSeries, omdbErr := omdbRequestTVSeries(showTitleFromFile)
	if omdbErr != nil {
		log.Println("Could not get OMDB metadata for", showTitleFromFile)
		failedPath := filepath.Join(paths.Failed, file) // Move it to 'failed'.
		os.Rename(inPath, failedPath)
		return omdbErr
	}
	showOutputFolder := filepath.Join(paths.TV, sanitiseForFilesystem(omdbSeries.Title))
	os.MkdirAll(showOutputFolder, os.ModePerm)
	seriesMetadata, _ := json.Marshal(omdbSeries)
	seriesMetadataPath := filepath.Join(showOutputFolder, metadataFilename)
	ioutil.WriteFile(seriesMetadataPath, seriesMetadata, os.ModePerm)

	// Get show pic if needed.
	seriesImagePath := filepath.Join(showOutputFolder, imageFilename)
	if _, err := os.Stat(seriesImagePath); os.IsNotExist(err) {
		log.Println("Fetching show image for", omdbSeries.Title)
		image, imageErr := imageForPosterLink(omdbSeries.Poster)
		if imageErr == nil {
			log.Println("Downloaded show image")
			ioutil.WriteFile(seriesImagePath, image, os.ModePerm)
		} else {
			log.Println("Couldn't download image:", imageErr)
		}
	}

	// Make the temporary output folder.
	stagingOutputFolder := filepath.Join(paths.Staging, file)
	os.MkdirAll(stagingOutputFolder, os.ModePerm)

	// Get the episode metadata.
	omdbEpisode, omdbEpisodeErr := omdbRequestTVEpisode(omdbSeries.Title, season, episode)
	if omdbEpisodeErr != nil {
		log.Println("Failed to find OMDB episode data, error:", omdbEpisodeErr)
		failedPath := filepath.Join(paths.Failed, file) // Move to 'failed'.
		os.Rename(inPath, failedPath)
		os.RemoveAll(stagingOutputFolder) // Tidy up.
		return omdbEpisodeErr
	} else {
		// Save the OMDB metadata.
		metadata, _ := json.Marshal(omdbEpisode)
		metadataPath := filepath.Join(stagingOutputFolder, metadataFilename)
		ioutil.WriteFile(metadataPath, metadata, os.ModePerm)
	}

	// Get the episode image.
	if omdbEpisode.Poster != "" {
		log.Println("Downloading an episode image")
		imageData, imageErr := imageForPosterLink(omdbEpisode.Poster)
		if imageErr != nil {
			log.Println("Couldn't download the image", omdbEpisode.Title, imageErr)
		} else {
			// Save the image.
			imagePath := filepath.Join(stagingOutputFolder, imageFilename)
			ioutil.WriteFile(imagePath, imageData, os.ModePerm)
		}
	}

	// Convert it.
	outPath := filepath.Join(stagingOutputFolder, hlsFilename)
	convertErr := convertToHLSAppropriately(inPath, outPath, config)

	// Fail! Move it to the failed folder.
	if convertErr != nil {
		log.Println("Failed to convert", file, "; moving to the Failed folder, err:", convertErr)
		failedPath := filepath.Join(paths.Failed, file) // Move it to 'failed'.
		os.Rename(inPath, failedPath)
		os.RemoveAll(stagingOutputFolder) // Tidy up.
		return errors.New("Couldn't convert " + file)
	}

	// Success!
	log.Println("Success! Removing original.")
	goodTitle := fmt.Sprintf("S%02dE%02d %s", season, episode, sanitiseForFilesystem(omdbEpisode.Title))
	goodFolder := filepath.Join(showOutputFolder, goodTitle)
	os.Rename(stagingOutputFolder, goodFolder) // Move the HLS across.
	os.Remove(inPath)                          // Remove the original file.
	// Assumption is that the user ripped their original from their DVD so doesn't care to lose it.

	return nil
}
<commit_msg>Continue if it cannot find tv metadata<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
)

// Actually processses a file that's in the new folder.
func processTV(folder string, file string, paths Paths, config Config) error {
	inPath := filepath.Join(folder, file)
	log.Println("Processing", file)

	// Parse the title.
	showTitleFromFile, season, episode, err := showSeasonEpisodeFromFile(file)
	if err != nil {
		log.Println("Failed to parse season/episode for", file)
		failedPath := filepath.Join(paths.Failed, file) // Move to 'failed'.
		os.Rename(inPath, failedPath)
		return err
	}

	// Get and save the show data. This has to happen for every episode so we can get the proper title name.
	omdbSeries, omdbErr := omdbRequestTVSeries(showTitleFromFile)
	if omdbErr != nil {
		log.Println("Could not get OMDB metadata for", showTitleFromFile)
		failedPath := filepath.Join(paths.Failed, file) // Move it to 'failed'.
		os.Rename(inPath, failedPath)
		return omdbErr
	}
	showOutputFolder := filepath.Join(paths.TV, sanitiseForFilesystem(omdbSeries.Title))
	os.MkdirAll(showOutputFolder, os.ModePerm)
	seriesMetadata, _ := json.Marshal(omdbSeries)
	seriesMetadataPath := filepath.Join(showOutputFolder, metadataFilename)
	ioutil.WriteFile(seriesMetadataPath, seriesMetadata, os.ModePerm)

	// Get show pic if needed.
	seriesImagePath := filepath.Join(showOutputFolder, imageFilename)
	if _, err := os.Stat(seriesImagePath); os.IsNotExist(err) {
		log.Println("Fetching show image for", omdbSeries.Title)
		image, imageErr := imageForPosterLink(omdbSeries.Poster)
		if imageErr == nil {
			log.Println("Downloaded show image")
			ioutil.WriteFile(seriesImagePath, image, os.ModePerm)
		} else {
			log.Println("Couldn't download image:", imageErr)
		}
	}

	// Make the temporary output folder.
	stagingOutputFolder := filepath.Join(paths.Staging, file)
	os.MkdirAll(stagingOutputFolder, os.ModePerm)

	// Get the episode metadata.
	// This can fail if OMDB isn't up to date, which happens, in which case carry on.
	omdbEpisode, omdbEpisodeErr := omdbRequestTVEpisode(omdbSeries.Title, season, episode)
	if omdbEpisodeErr != nil {
		log.Println("Failed to find OMDB episode data, error:", omdbEpisodeErr)
		// Don't return, carry on.
	} else {
		// Save the OMDB metadata.
		metadata, _ := json.Marshal(omdbEpisode)
		metadataPath := filepath.Join(stagingOutputFolder, metadataFilename)
		ioutil.WriteFile(metadataPath, metadata, os.ModePerm)
	}

	// Get the episode image.
	if omdbEpisode.Poster != "" {
		log.Println("Downloading an episode image")
		imageData, imageErr := imageForPosterLink(omdbEpisode.Poster)
		if imageErr != nil {
			log.Println("Couldn't download the image", omdbEpisode.Title, imageErr)
		} else {
			// Save the image.
			imagePath := filepath.Join(stagingOutputFolder, imageFilename)
			ioutil.WriteFile(imagePath, imageData, os.ModePerm)
		}
	}

	// Convert it.
	outPath := filepath.Join(stagingOutputFolder, hlsFilename)
	convertErr := convertToHLSAppropriately(inPath, outPath, config)

	// Fail! Move it to the failed folder.
	if convertErr != nil {
		log.Println("Failed to convert", file, "; moving to the Failed folder, err:", convertErr)
		failedPath := filepath.Join(paths.Failed, file) // Move it to 'failed'.
		os.Rename(inPath, failedPath)
		os.RemoveAll(stagingOutputFolder) // Tidy up.
		return errors.New("Couldn't convert " + file)
	}

	// Success!
	log.Println("Success! Removing original.")
	goodTitle := tvFolderNameFor(season, episode, omdbEpisode.Title)
	goodFolder := filepath.Join(showOutputFolder, goodTitle)
	os.Rename(stagingOutputFolder, goodFolder) // Move the HLS across.
	os.Remove(inPath)                          // Remove the original file.
	// Assumption is that the user ripped their original from their DVD so doesn't care to lose it.

	return nil
}

// Makes the folder name for the given show.
func tvFolderNameFor(season int, episode int, title string) string {
	if title == "" {
		return fmt.Sprintf("S%02dE%02d", season, episode)
	} else {
		return fmt.Sprintf("S%02dE%02d %s", season, episode, sanitiseForFilesystem(title))
	}
}
<|endoftext|>


<commit_before>package garchive

import (
	"bufio"
	"errors"
	"fmt"
	"log"
	"net"
	"os"
	"os/user"
	"strings"
	"time"
)

type IRCConnection struct {
	Location   string
	Nick       string
	Connection net.Conn
	Listeners  []func(*IRCCommand)
	Finished   chan bool
}

type IRCCommand struct {
	Source string
	Type   string
	Args   []string
}

type IRCListener func(*IRCCommand)

func MakeConnection(uri string, nick string) *IRCConnection {
	var connection IRCConnection

	connection.Location = uri
	connection.Nick = nick

	return &connection
}

/* go from a raw string form of a command to an IRCCommand */
func RawToIRCCommand(raw string) (*IRCCommand, error) {
	var command IRCCommand

	split_ver := strings.Split(raw, " ")
	/* first as a sanity check make sure that our array has at least
	   two entries, any less is not a valid command */
	if len(split_ver) < 2 {
		return &command, errors.New("invalid command (less than two entries in command)")
	}
	args_start := 2
	if strings.HasPrefix(split_ver[0], ":") {
		command.Source = strings.TrimPrefix(split_ver[0], ":")
		command.Type = split_ver[1]
	} else {
		command.Type = split_ver[0]
		args_start = 1
	}

	/* iterate over every element after the first two */
	multi_word_index := -1
	for index, arg := range split_ver[args_start:] {
		if strings.HasPrefix(arg, ":") {
			multi_word_index = index
			break
		}

		command.Args = append(command.Args, arg)
	}

	if multi_word_index != -1 {
		words := []string{}
		words = append(words, split_ver[args_start:][multi_word_index][1:len(split_ver[args_start:][multi_word_index])])
		words = append(words, split_ver[args_start:][multi_word_index+1:]...)
		command.Args = append(command.Args, strings.Join(words, " "))
	}

	command.Args[len(command.Args)-1] = strings.TrimSuffix(command.Args[len(command.Args)-1], "\r\n")

	return &command, nil
}

func MakeIRCCommand(cmdtype string, args ...string) *IRCCommand {
	var command IRCCommand

	command.Type = cmdtype
	command.Args = args

	return &command
}

func (command *IRCCommand) ToRaw() (string, error) {
	out := []string{}
	if command.Source != "" {
		out = append(out, command.Source)
	}
	out = append(out, command.Type)
	for _, arg := range command.Args[0 : len(command.Args)-1] {
		if strings.Contains(arg, " ") {
			return "", errors.New("nonfinal argument contains space")
		}
		out = append(out, arg)
	}

	if strings.Contains(command.Args[len(command.Args)-1], " ") {
		out = append(out, fmt.Sprint(":", command.Args[len(command.Args)-1]))
	} else {
		out = append(out, command.Args[len(command.Args)-1])
	}

	return fmt.Sprintf("%s\r\n", strings.Join(out, " ")), nil
}

func (connection *IRCConnection) Connect() error {
	log.Print("Connecting...")
	conn, err := net.Dial("tcp", connection.Location)
	connection.Connection = conn

	if err != nil {
		return err
	}

	log.Print("Connected!")

	go func() {
		//bio := bufio.NewReader(conn)
		for {
			line, err := bufio.NewReader(conn).ReadString('\n')
			log.Printf("Got line: %s\n", line)
			if err != nil {
				connection.Finished <- true
				log.Fatal(err)
			}
			command, err := RawToIRCCommand(line)
			if err != nil {
				log.Print(err)
			}
			for _, fn := range connection.Listeners {
				go fn(command)
			}
		}
	}()

	/* create a goroutine to send PONGs back when we receive PINGs */
	connection.AddListener(func(command *IRCCommand) {
		if command.Type == "PING" {
			fmt.Fprintf(conn, "PONG %s\r\n", command.Args[0])
		}
	})

	err = connection.SendCommand(MakeIRCCommand("NICK", connection.Nick))
	if err != nil {
		return err
	}
	/* query the local system for a username. This isn't *really* necessary,
	 * but it really isn't that big of a deal to do it aywa */
	user, err := user.Current()
	if err != nil {
		log.Print(err)
		user.Username = "unknown"
	}
	err = connection.SendCommand(MakeIRCCommand("USER", user.Username, "0", "*", "Garchive: An IRC archiver bot"))
	if err != nil {
		return err
	}

	return nil
}

func (connection *IRCConnection) AddListener(fn IRCListener) {
	connection.Listeners = append(connection.Listeners, fn)
}

func (connection *IRCConnection) SendCommand(command *IRCCommand) error {
	log.Printf("Sending command %v\n", command)
	raw_form, err := command.ToRaw()
	log.Printf("raw form: %v\n", raw_form)
	if err != nil {
		return err
	}

	fmt.Fprint(connection.Connection, raw_form)

	return nil
}

func MakeChannelListener(channel string, filename string) (func(*IRCCommand), error) {
	file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		return nil, err
	}

	fn := func(command *IRCCommand) {
		line := ""
		switch command.Type {
		case "PRIVMSG":
			if command.Args[0] == channel {
				line = fmt.Sprintf("%s: %s\n", command.Source, command.Args[1])
			}
		case "JOIN":
			if command.Args[0] == channel {
				line = fmt.Sprintf("%s has joined %s\n", command.Source, channel)
			}
		case "PART":
			if command.Args[0] == channel {
				line = fmt.Sprintf("%s has left %s\n", command.Source, channel)
			}
		case "TOPIC":
			if command.Args[0] == channel {
				line = fmt.Sprintf("%s has set the topic to %s\n", command.Source, command.Args[1])
			}
		}

		if line != "" {
			fmt.Fprintf(file, "[%v] %s", time.Now(), line)
		}
	}

	return fn, nil
}

func Main() {
	if len(os.Args) < 4 {
		fmt.Printf("Usage: %s irc_uri nick channel [other_channels...]\n", os.Args[0])
		os.Exit(1)
	}
	uri := os.Args[1]
	nick := os.Args[2]
	channels := os.Args[3:len(os.Args)]
	connection := MakeConnection(uri, nick)

	connection.AddListener(func(command *IRCCommand) {
		fmt.Printf("%v\n", command)
		fmt.Printf("Args:\n")
		for index, arg := range command.Args {
			fmt.Printf("%d.  %s\n", index, arg)
		}
	})

	for _, c := range channels {
		fmt.Printf("Adding channel %v\n", c)
	}

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

	for _, c := range channels {
		/* allow the user to specify the channel without the #
		 * since bash requries it to be escaped */
		if !strings.HasPrefix(c, "#") {
			c = fmt.Sprintf("#%s", c)
		}
		err = connection.SendCommand(MakeIRCCommand("JOIN", c))
		if err != nil {
			log.Print(err)
		}
		channelListener, err := MakeChannelListener(c, strings.TrimPrefix(c, "#"))
		if err != nil {
			log.Print(err)
		}
		connection.AddListener(channelListener)
	}

	<-connection.Finished
}
<commit_msg>added archive message for quits<commit_after>package garchive

import (
	"bufio"
	"errors"
	"fmt"
	"log"
	"net"
	"os"
	"os/user"
	"strings"
	"time"
)

type IRCConnection struct {
	Location   string
	Nick       string
	Connection net.Conn
	Listeners  []func(*IRCCommand)
	Finished   chan bool
}

type IRCCommand struct {
	Source string
	Type   string
	Args   []string
}

type IRCListener func(*IRCCommand)

func MakeConnection(uri string, nick string) *IRCConnection {
	var connection IRCConnection

	connection.Location = uri
	connection.Nick = nick

	return &connection
}

/* go from a raw string form of a command to an IRCCommand */
func RawToIRCCommand(raw string) (*IRCCommand, error) {
	var command IRCCommand

	split_ver := strings.Split(raw, " ")
	/* first as a sanity check make sure that our array has at least
	   two entries, any less is not a valid command */
	if len(split_ver) < 2 {
		return &command, errors.New("invalid command (less than two entries in command)")
	}
	args_start := 2
	if strings.HasPrefix(split_ver[0], ":") {
		command.Source = strings.TrimPrefix(split_ver[0], ":")
		command.Type = split_ver[1]
	} else {
		command.Type = split_ver[0]
		args_start = 1
	}

	/* iterate over every element after the first two */
	multi_word_index := -1
	for index, arg := range split_ver[args_start:] {
		if strings.HasPrefix(arg, ":") {
			multi_word_index = index
			break
		}

		command.Args = append(command.Args, arg)
	}

	if multi_word_index != -1 {
		words := []string{}
		words = append(words, split_ver[args_start:][multi_word_index][1:len(split_ver[args_start:][multi_word_index])])
		words = append(words, split_ver[args_start:][multi_word_index+1:]...)
		command.Args = append(command.Args, strings.Join(words, " "))
	}

	command.Args[len(command.Args)-1] = strings.TrimSuffix(command.Args[len(command.Args)-1], "\r\n")

	return &command, nil
}

func MakeIRCCommand(cmdtype string, args ...string) *IRCCommand {
	var command IRCCommand

	command.Type = cmdtype
	command.Args = args

	return &command
}

func (command *IRCCommand) ToRaw() (string, error) {
	out := []string{}
	if command.Source != "" {
		out = append(out, command.Source)
	}
	out = append(out, command.Type)
	for _, arg := range command.Args[0 : len(command.Args)-1] {
		if strings.Contains(arg, " ") {
			return "", errors.New("nonfinal argument contains space")
		}
		out = append(out, arg)
	}

	if strings.Contains(command.Args[len(command.Args)-1], " ") {
		out = append(out, fmt.Sprint(":", command.Args[len(command.Args)-1]))
	} else {
		out = append(out, command.Args[len(command.Args)-1])
	}

	return fmt.Sprintf("%s\r\n", strings.Join(out, " ")), nil
}

func (connection *IRCConnection) Connect() error {
	log.Print("Connecting...")
	conn, err := net.Dial("tcp", connection.Location)
	connection.Connection = conn

	if err != nil {
		return err
	}

	log.Print("Connected!")

	go func() {
		//bio := bufio.NewReader(conn)
		for {
			line, err := bufio.NewReader(conn).ReadString('\n')
			log.Printf("Got line: %s\n", line)
			if err != nil {
				connection.Finished <- true
				log.Fatal(err)
			}
			command, err := RawToIRCCommand(line)
			if err != nil {
				log.Print(err)
			}
			for _, fn := range connection.Listeners {
				go fn(command)
			}
		}
	}()

	/* create a goroutine to send PONGs back when we receive PINGs */
	connection.AddListener(func(command *IRCCommand) {
		if command.Type == "PING" {
			fmt.Fprintf(conn, "PONG %s\r\n", command.Args[0])
		}
	})

	err = connection.SendCommand(MakeIRCCommand("NICK", connection.Nick))
	if err != nil {
		return err
	}
	/* query the local system for a username. This isn't *really* necessary,
	 * but it really isn't that big of a deal to do it aywa */
	user, err := user.Current()
	if err != nil {
		log.Print(err)
		user.Username = "unknown"
	}
	err = connection.SendCommand(MakeIRCCommand("USER", user.Username, "0", "*", "Garchive: An IRC archiver bot"))
	if err != nil {
		return err
	}

	return nil
}

func (connection *IRCConnection) AddListener(fn IRCListener) {
	connection.Listeners = append(connection.Listeners, fn)
}

func (connection *IRCConnection) SendCommand(command *IRCCommand) error {
	log.Printf("Sending command %v\n", command)
	raw_form, err := command.ToRaw()
	log.Printf("raw form: %v\n", raw_form)
	if err != nil {
		return err
	}

	fmt.Fprint(connection.Connection, raw_form)

	return nil
}

func MakeChannelListener(channel string, filename string) (func(*IRCCommand), error) {
	file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		return nil, err
	}

	fn := func(command *IRCCommand) {
		line := ""
		switch command.Type {
		case "PRIVMSG":
			if command.Args[0] == channel {
				line = fmt.Sprintf("%s: %s\n", command.Source, command.Args[1])
			}
		case "JOIN":
			if command.Args[0] == channel {
				line = fmt.Sprintf("%s has joined %s\n", command.Source, channel)
			}
		case "PART":
			if command.Args[0] == channel {
				line = fmt.Sprintf("%s has left %s\n", command.Source, channel)
			}
		case "TOPIC":
			if command.Args[0] == channel {
				line = fmt.Sprintf("%s has set the topic to %s\n", command.Source, command.Args[1])
			}
                case "QUIT":
                        line = fmt.Sprintf("%s has quit: %s\n", command.Source, command.Args[0])
		}

		if line != "" {
			fmt.Fprintf(file, "[%v] %s", time.Now(), line)
		}
	}

	return fn, nil
}

func Main() {
	if len(os.Args) < 4 {
		fmt.Printf("Usage: %s irc_uri nick channel [other_channels...]\n", os.Args[0])
		os.Exit(1)
	}
	uri := os.Args[1]
	nick := os.Args[2]
	channels := os.Args[3:len(os.Args)]
	connection := MakeConnection(uri, nick)

	connection.AddListener(func(command *IRCCommand) {
		fmt.Printf("%v\n", command)
		fmt.Printf("Args:\n")
		for index, arg := range command.Args {
			fmt.Printf("%d.  %s\n", index, arg)
		}
	})

	for _, c := range channels {
		fmt.Printf("Adding channel %v\n", c)
	}

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

	for _, c := range channels {
		/* allow the user to specify the channel without the #
		 * since bash requries it to be escaped */
		if !strings.HasPrefix(c, "#") {
			c = fmt.Sprintf("#%s", c)
		}
		err = connection.SendCommand(MakeIRCCommand("JOIN", c))
		if err != nil {
			log.Print(err)
		}
		channelListener, err := MakeChannelListener(c, strings.TrimPrefix(c, "#"))
		if err != nil {
			log.Print(err)
		}
		connection.AddListener(channelListener)
	}

	<-connection.Finished
}
<|endoftext|>


<commit_before>package main

import (
	getopt "code.google.com/p/getopt"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	http "net/http"
	url "net/url"
	"os"
	"strconv"
)

const FPASTE_URL = "http://fpaste.org"

func main() {
	opts := initConfig(os.Args)
	if *opts.help {
		getopt.PrintUsage(os.Stdout)
	} else {
		/*Passing stdin for test mocking*/
		files, errs := handleArgs(os.Stdin, getopt.CommandLine)
		for _, file := range files {
			if len(file) != 0 {
				if err := copyPaste(file, opts); err != nil {
					errs = append(errs, err)
				}
			}
		}
		for _, err := range errs {
			log.Print(err)
		}
		if len(errs) > 0 {
			os.Exit(-1)
		}
	}
}

type config struct {
	help   *bool
	priv   *bool
	user   *string
	pass   *string
	lang   *string
	expire *int
}

func initConfig(args []string) *config {
	getopt.CommandLine = getopt.New()
	var flags config
	flags.help = getopt.BoolLong("help", 'h', "Display this help")
	flags.priv = getopt.BoolLong("private", 'P', "Private paste flag")
	flags.user = getopt.StringLong("user", 'u', "", "An alphanumeric username of the paste author")
	flags.pass = getopt.StringLong("pass", 'p', "", "Add a password")
	flags.lang = getopt.StringLong("lang", 'l', "Text", "The development language used")
	flags.expire = getopt.IntLong("expire", 'e', 0, "Seconds after which paste will be deleted from server")
	getopt.SetParameters("[FILE...]")
	getopt.CommandLine.Parse(args)
	return &flags
}

func handleArgs(stdin io.Reader, commandLine *getopt.Set) (files [][]byte, errs []error) {
	if commandLine.NArgs() > 0 {
		for _, x := range commandLine.Args() {
			file, err := os.Open(x)
			if err != nil {
				errs = append(errs, fmt.Errorf("Skipping [FILE: %s] since it cannot be opened (%s)", x, err))
				continue
			}
			defer file.Close()
			data, erread := ioutil.ReadAll(file)
			if erread != nil {
				errs = append(errs, fmt.Errorf("Skipping [FILE: %s] since it cannot be read (%s)", x, erread))
			} else {
				files = append(files, data)
			}
		}
	} else {
		data, erread := ioutil.ReadAll(stdin)
		if erread != nil {
			errs = append(errs, erread)
		} else {
			files = append(files, data)
		}
	}
	return files, errs
}

/*
Handling API errors so we can know why the request did not went as expetected.
The resquest will not be resend to prevent the user from being banned.
*/

func handleAPIError(error string) error {
	errorStr := make(map[string]string)
	errorStr["err_nothing_to_do"] = "No POST request was received by the create API"
	errorStr["err_author_numeric"] = "The paste author's alias should be alphanumeric"
	errorStr["err_save_error"] = "An error occurred while saving the paste"
	errorStr["err_spamguard_ipban"] = "Poster's IP address is banned"
	errorStr["err_spamguard_stealth"] = "The paste triggered the spam filter"
	errorStr["err_spamguard_noflood"] = "Poster is trying the flood"
	errorStr["err_spamguard_php"] = "Poster's IP address is listed as malicious"
	if err, ok := errorStr[error]; ok {
		return fmt.Errorf("API error: %s", err)
	}
	return fmt.Errorf("API error: Unknown [%s]", error)
}

func copyPaste(src []byte, opts *config) error {
	values := url.Values{
		"paste_data":     {string(src)},
		"paste_lang":     {*opts.lang},
		"api_submit":     {"true"},
		"mode":           {"json"},
		"paste_user":     {*opts.user},
		"paste_password": {*opts.pass},
		"paste_expire":   {strconv.Itoa(*opts.expire)},
	}
	if *opts.priv {
		values.Add("paste_private", "yes")
	}
	resp, erreq := http.PostForm(FPASTE_URL, values)
	if erreq != nil {
		return erreq
	}
	defer resp.Body.Close()
	type res struct {
		Id    string `json:"id"`
		Hash  string `json:"hash"`
		Error string `json:"error"`
	}
	type pasteUrls struct {
		Result res `json:"result"`
	}
	var m pasteUrls
	slice, err := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(slice, &m)
	if err != nil {
		return err
	}
	if m.Result.Error != "" {
		return handleAPIError(m.Result.Error)
	}
	fmt.Fprintf(os.Stdout, "%s/%s/%s\n", FPASTE_URL, m.Result.Id, m.Result.Hash)
	return nil
}
<commit_msg>Add support for other time formats<commit_after>package main

import (
	getopt "code.google.com/p/getopt"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	http "net/http"
	url "net/url"
	"os"
	"strconv"
	"time"
)

const FPASTE_URL = "http://fpaste.org"

func main() {
	opts := initConfig(os.Args)
	if *opts.help {
		getopt.PrintUsage(os.Stdout)
	} else {
		/*Passing stdin for test mocking*/
		files, errs := handleArgs(os.Stdin, getopt.CommandLine)
		for _, file := range files {
			if len(file) != 0 {
				if err := copyPaste(file, opts); err != nil {
					errs = append(errs, err)
				}
			}
		}
		for _, err := range errs {
			log.Print(err)
		}
		if len(errs) > 0 {
			os.Exit(-1)
		}
	}
}

type config struct {
	help   *bool
	priv   *bool
	user   *string
	pass   *string
	lang   *string
	expire *string
}

func initConfig(args []string) *config {
	getopt.CommandLine = getopt.New()
	var flags config
	flags.help = getopt.BoolLong("help", 'h', "Display this help")
	flags.priv = getopt.BoolLong("private", 'P', "Private paste flag")
	flags.user = getopt.StringLong("user", 'u', "", "An alphanumeric username of the paste author")
	flags.pass = getopt.StringLong("pass", 'p', "", "Add a password")
	flags.lang = getopt.StringLong("lang", 'l', "Text", "The development language used")
	flags.expire = getopt.StringLong("expire", 'e', "", "Seconds after which paste will be deleted from server")
	getopt.SetParameters("[FILE...]")
	getopt.CommandLine.Parse(args)
	return &flags
}

func handleArgs(stdin io.Reader, commandLine *getopt.Set) (files [][]byte, errs []error) {
	if commandLine.NArgs() > 0 {
		for _, x := range commandLine.Args() {
			file, err := os.Open(x)
			if err != nil {
				errs = append(errs, fmt.Errorf("Skipping [FILE: %s] since it cannot be opened (%s)", x, err))
				continue
			}
			defer file.Close()
			data, erread := ioutil.ReadAll(file)
			if erread != nil {
				errs = append(errs, fmt.Errorf("Skipping [FILE: %s] since it cannot be read (%s)", x, erread))
			} else {
				files = append(files, data)
			}
		}
	} else {
		data, erread := ioutil.ReadAll(stdin)
		if erread != nil {
			errs = append(errs, erread)
		} else {
			files = append(files, data)
		}
	}
	return files, errs
}

/*
Handling API errors so we can know why the request did not went as expetected.
The resquest will not be resend to prevent the user from being banned.
*/

func handleAPIError(error string) error {
	errorStr := make(map[string]string)
	errorStr["err_nothing_to_do"] = "No POST request was received by the create API"
	errorStr["err_author_numeric"] = "The paste author's alias should be alphanumeric"
	errorStr["err_save_error"] = "An error occurred while saving the paste"
	errorStr["err_spamguard_ipban"] = "Poster's IP address is banned"
	errorStr["err_spamguard_stealth"] = "The paste triggered the spam filter"
	errorStr["err_spamguard_noflood"] = "Poster is trying the flood"
	errorStr["err_spamguard_php"] = "Poster's IP address is listed as malicious"
	if err, ok := errorStr[error]; ok {
		return fmt.Errorf("API error: %s", err)
	}
	return fmt.Errorf("API error: Unknown [%s]", error)
}

func copyPaste(src []byte, opts *config) error {
	values := url.Values{
		"paste_data":     {string(src)},
		"paste_lang":     {*opts.lang},
		"api_submit":     {"true"},
		"mode":           {"json"},
		"paste_user":     {*opts.user},
		"paste_password": {*opts.pass},
	}
	if duration, err := time.ParseDuration(*opts.expire); err != nil {
		return err
	} else if secs := duration.Seconds(); secs >= 1 {
		values.Add("paste_expire", strconv.FormatFloat(secs, 'f', -1, 64))
	}
	if *opts.priv {
		values.Add("paste_private", "yes")
	}
	resp, erreq := http.PostForm(FPASTE_URL, values)
	if erreq != nil {
		return erreq
	}
	defer resp.Body.Close()
	type res struct {
		Id    string `json:"id"`
		Hash  string `json:"hash"`
		Error string `json:"error"`
	}
	type pasteUrls struct {
		Result res `json:"result"`
	}
	var m pasteUrls
	slice, err := ioutil.ReadAll(resp.Body)
	err = json.Unmarshal(slice, &m)
	if err != nil {
		return err
	}
	if m.Result.Error != "" {
		return handleAPIError(m.Result.Error)
	}
	fmt.Fprintf(os.Stdout, "%s/%s/%s\n", FPASTE_URL, m.Result.Id, m.Result.Hash)
	return nil
}
<|endoftext|>


<commit_before>package vm

import (
	"fmt"
)

// Instr describes all bytecode instructions
type Instr interface {
	String() string
	isInstr()
}

// InstrHalt signals that the VM can stop executing
type InstrHalt struct{}

func (ih InstrHalt) String() string { return "halt" }
func (ih InstrHalt) isInstr()       {}

// InstrNOP is a non-operation instruction that does nothing
type InstrNOP struct{}

func (nop InstrNOP) String() string { return "nop" }
func (nop InstrNOP) isInstr()       {}

// InstrJump is a non-conditional jump
type InstrJump struct {
	IP uint32
}

func (ij InstrJump) String() string { return fmt.Sprintf("%-8s%04d", "jmp", ij.IP) }
func (ij InstrJump) isInstr()       {}

// InstrJumpTrue will jump if the top value on the stack is true
type InstrJumpTrue struct {
	IP uint32
}

func (ijc InstrJumpTrue) String() string { return fmt.Sprintf("%-8s%04d", "jmpt", ijc.IP) }
func (ijc InstrJumpTrue) isInstr()       {}

// InstrJumpFalse will jump if the top value on the stack is false
type InstrJumpFalse struct {
	IP uint32
}

func (ijc InstrJumpFalse) String() string { return fmt.Sprintf("%-8s%04d", "jmpf", ijc.IP) }
func (ijc InstrJumpFalse) isInstr()       {}

// InstrPush adds its argument to the top of the VM expression stack
type InstrPush struct {
	Val Object
}

func (ip InstrPush) String() string { return fmt.Sprintf("%-8s%s", "push", ip.Val) }
func (ip InstrPush) isInstr()       {}

// InstrPop remove the top value from the stack and discard the value
type InstrPop struct{}

func (ip InstrPop) String() string { return "pop" }
func (ip InstrPop) isInstr()       {}

// InstrCopy duplicates the top value from the stack and pushes it onto the stack
type InstrCopy struct{}

func (ic InstrCopy) String() string { return "copy" }
func (ic InstrCopy) isInstr()       {}

// InstrReserve allocates registers for local variables
type InstrReserve struct {
	Template *CellTemplate
}

func (ir InstrReserve) String() string { return fmt.Sprintf("%-8s%s", "alloc", ir.Template) }
func (ir InstrReserve) isInstr()       {}

// InstrStore remove the top value from the stack and store it in a register
type InstrStore struct {
	Template *CellTemplate
}

func (is InstrStore) String() string { return fmt.Sprintf("%-8s%s", "store", is.Template) }
func (is InstrStore) isInstr()       {}

type InstrLoadSelf struct{}

func (ils InstrLoadSelf) String() string { return "self" }
func (ils InstrLoadSelf) isInstr()       {}

// InstrLoad reads a register and pushes its contents onto the stack
type InstrLoad struct {
	Template *CellTemplate
}

func (il InstrLoad) String() string { return fmt.Sprintf("%-8s%s", "ld", il.Template) }
func (il InstrLoad) isInstr()       {}

// InstrDispatch reads arguments from the stack and passes them to the callee
type InstrDispatch struct {
	NumArgs int
}

func (id InstrDispatch) String() string { return fmt.Sprintf("%-8s%d", "call", id.NumArgs) }
func (id InstrDispatch) isInstr()       {}

// InstrNone adds a nothing object to the stack to help handling void
// functions that return no values
type InstrNone struct{}

func (in InstrNone) String() string { return "none" }
func (in InstrNone) isInstr()       {}

// InstrReturn exits the current function
type InstrReturn struct{}

func (ir InstrReturn) String() string { return "ret" }
func (ir InstrReturn) isInstr()       {}

// InstrAdd pops top 2 values from stack, adds them, pushes sum back onto stack
type InstrAdd struct{}

func (ia InstrAdd) String() string { return "add" }
func (ia InstrAdd) isInstr()       {}

// InstrSub pops top 2 values from stack, subtracts them, pushes difference back onto stack
type InstrSub struct{}

func (is InstrSub) String() string { return "sub" }
func (is InstrSub) isInstr()       {}

// InstrLT pops top 2 values from stack, pushes true if first is greater than second
type InstrLT struct{}

func (ilt InstrLT) String() string { return "cmplt" }
func (ilt InstrLT) isInstr()       {}

// InstrLTEquals pops top 2 values from stack, pushes true if first is greater than second
type InstrLTEquals struct{}

func (ilte InstrLTEquals) String() string { return "cmplte" }
func (ilte InstrLTEquals) isInstr()       {}

// InstrGT pops top 2 values from stack, pushes true if first is greater than second
type InstrGT struct{}

func (igt InstrGT) String() string { return "cmpgt" }
func (igt InstrGT) isInstr()       {}

// InstrGTEquals pops top 2 values from stack, pushes true if first is greater than second
type InstrGTEquals struct{}

func (igte InstrGTEquals) String() string { return "cmpgte" }
func (igte InstrGTEquals) isInstr()       {}
<commit_msg>describe InstrLoadSelf instruction<commit_after>package vm

import (
	"fmt"
)

// Instr describes all bytecode instructions
type Instr interface {
	String() string
	isInstr()
}

// InstrHalt signals that the VM can stop executing
type InstrHalt struct{}

func (ih InstrHalt) String() string { return "halt" }
func (ih InstrHalt) isInstr()       {}

// InstrNOP is a non-operation instruction that does nothing
type InstrNOP struct{}

func (nop InstrNOP) String() string { return "nop" }
func (nop InstrNOP) isInstr()       {}

// InstrJump is a non-conditional jump
type InstrJump struct {
	IP uint32
}

func (ij InstrJump) String() string { return fmt.Sprintf("%-8s%04d", "jmp", ij.IP) }
func (ij InstrJump) isInstr()       {}

// InstrJumpTrue will jump if the top value on the stack is true
type InstrJumpTrue struct {
	IP uint32
}

func (ijc InstrJumpTrue) String() string { return fmt.Sprintf("%-8s%04d", "jmpt", ijc.IP) }
func (ijc InstrJumpTrue) isInstr()       {}

// InstrJumpFalse will jump if the top value on the stack is false
type InstrJumpFalse struct {
	IP uint32
}

func (ijc InstrJumpFalse) String() string { return fmt.Sprintf("%-8s%04d", "jmpf", ijc.IP) }
func (ijc InstrJumpFalse) isInstr()       {}

// InstrPush adds its argument to the top of the VM expression stack
type InstrPush struct {
	Val Object
}

func (ip InstrPush) String() string { return fmt.Sprintf("%-8s%s", "push", ip.Val) }
func (ip InstrPush) isInstr()       {}

// InstrPop remove the top value from the stack and discard the value
type InstrPop struct{}

func (ip InstrPop) String() string { return "pop" }
func (ip InstrPop) isInstr()       {}

// InstrCopy duplicates the top value from the stack and pushes it onto the stack
type InstrCopy struct{}

func (ic InstrCopy) String() string { return "copy" }
func (ic InstrCopy) isInstr()       {}

// InstrReserve allocates registers for local variables
type InstrReserve struct {
	Template *CellTemplate
}

func (ir InstrReserve) String() string { return fmt.Sprintf("%-8s%s", "alloc", ir.Template) }
func (ir InstrReserve) isInstr()       {}

// InstrStore remove the top value from the stack and store it in a register
type InstrStore struct {
	Template *CellTemplate
}

func (is InstrStore) String() string { return fmt.Sprintf("%-8s%s", "store", is.Template) }
func (is InstrStore) isInstr()       {}

// InstrLoadSelf pushes a copy of the current closure onto the stack so that
// it can be recursively called
type InstrLoadSelf struct{}

func (ils InstrLoadSelf) String() string { return "self" }
func (ils InstrLoadSelf) isInstr()       {}

// InstrLoad reads a register and pushes its contents onto the stack
type InstrLoad struct {
	Template *CellTemplate
}

func (il InstrLoad) String() string { return fmt.Sprintf("%-8s%s", "ld", il.Template) }
func (il InstrLoad) isInstr()       {}

// InstrDispatch reads arguments from the stack and passes them to the callee
type InstrDispatch struct {
	NumArgs int
}

func (id InstrDispatch) String() string { return fmt.Sprintf("%-8s%d", "call", id.NumArgs) }
func (id InstrDispatch) isInstr()       {}

// InstrNone adds a nothing object to the stack to help handling void
// functions that return no values
type InstrNone struct{}

func (in InstrNone) String() string { return "none" }
func (in InstrNone) isInstr()       {}

// InstrReturn exits the current function
type InstrReturn struct{}

func (ir InstrReturn) String() string { return "ret" }
func (ir InstrReturn) isInstr()       {}

// InstrAdd pops top 2 values from stack, adds them, pushes sum back onto stack
type InstrAdd struct{}

func (ia InstrAdd) String() string { return "add" }
func (ia InstrAdd) isInstr()       {}

// InstrSub pops top 2 values from stack, subtracts them, pushes difference back onto stack
type InstrSub struct{}

func (is InstrSub) String() string { return "sub" }
func (is InstrSub) isInstr()       {}

// InstrLT pops top 2 values from stack, pushes true if first is greater than second
type InstrLT struct{}

func (ilt InstrLT) String() string { return "cmplt" }
func (ilt InstrLT) isInstr()       {}

// InstrLTEquals pops top 2 values from stack, pushes true if first is greater than second
type InstrLTEquals struct{}

func (ilte InstrLTEquals) String() string { return "cmplte" }
func (ilte InstrLTEquals) isInstr()       {}

// InstrGT pops top 2 values from stack, pushes true if first is greater than second
type InstrGT struct{}

func (igt InstrGT) String() string { return "cmpgt" }
func (igt InstrGT) isInstr()       {}

// InstrGTEquals pops top 2 values from stack, pushes true if first is greater than second
type InstrGTEquals struct{}

func (igte InstrGTEquals) String() string { return "cmpgte" }
func (igte InstrGTEquals) isInstr()       {}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"os"

	aws_pkg "github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/portworx/torpedo/drivers/node"
)

const (
	// DriverName is the name of the aws driver
	DriverName = "aws"
)

type aws struct {
	node.Driver
	session     *session.Session
	credentials *credentials.Credentials
	config      *aws_pkg.Config
	region      string
	svc         *ec2.EC2
	instances   []*ec2.Instance
}

func (a *aws) String() string {
	return DriverName
}

func (a *aws) Init(sched string) error {
	var err error
	sess := session.Must(session.NewSession())
	a.session = sess
	creds := credentials.NewEnvCredentials()
	a.credentials = creds
	a.region = os.Getenv("AWS_REGION")
	if a.region == "" {
		return fmt.Errorf("Env AWS_REGION not found")
	}
	config := &aws_pkg.Config{Region: aws_pkg.String(a.region)}
	config.WithCredentials(creds)
	a.config = config
	svc := ec2.New(sess, config)
	a.svc = svc
	instances, err := a.getAllInstances()
	if err != nil {
		return err
	}
	a.instances = instances
	return nil
}

func (a *aws) TestConnection(n node.Node, options node.ConnectionOpts) error {
	return nil
}

func (a *aws) RebootNode(n node.Node, options node.RebootNodeOpts) error {
	var err error
	instanceID, err := a.getNodeIDByPrivAddr(n)
	if err != nil {
		return &node.ErrFailedToRebootNode{
			Node:  n,
			Cause: fmt.Sprintf("failed to get instance ID due to: %v", err),
		}
	}
	//Reboot the instance by its InstanceID
	rebootInput := &ec2.RebootInstancesInput{
		InstanceIds: []*string{
			aws_pkg.String(instanceID),
		},
	}
	_, err = a.svc.RebootInstances(rebootInput)
	if err != nil {
		return &node.ErrFailedToRebootNode{
			Node:  n,
			Cause: fmt.Sprintf("failed to reboot instance due to: %v", err),
		}
	}
	return nil
}

func (a *aws) ShutdownNode(n node.Node, options node.ShutdownNodeOpts) error {
	return nil
}

func (a *aws) getAllInstances() ([]*ec2.Instance, error) {
	instances := []*ec2.Instance{}
	params := &ec2.DescribeInstancesInput{}
	resp, err := a.svc.DescribeInstances(params)
	if err != nil {
		return instances, fmt.Errorf("there was an error listing instances in %s. Error: %q", a.region, err.Error())
	}
	reservations := resp.Reservations
	for _, resv := range reservations {
		for _, ins := range resv.Instances {
			instances = append(instances, ins)
		}
	}
	return instances, err
}

func (a *aws) getNodeIDByPrivAddr(n node.Node) (string, error) {
	for _, i := range a.instances {
		for _, addr := range n.Addresses {
			if aws_pkg.StringValue(i.PrivateIpAddress) == addr {
				return aws_pkg.StringValue(i.InstanceId), nil
			}
		}
	}
	return "", fmt.Errorf("Failed to get instanceID of %s by privateIP", n.Name)
}

func init() {
	a := &aws{
		Driver: node.NotSupportedDriver,
	}
	node.Register(DriverName, a)
}
<commit_msg>Add TestConnection implementation (#41)<commit_after>package aws

import (
	"fmt"
	"os"
	"strings"
	"time"

	aws_pkg "github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/ssm"
	"github.com/portworx/torpedo/drivers/node"
	"github.com/portworx/torpedo/drivers/scheduler"
	"github.com/portworx/torpedo/pkg/task"
)

const (
	// DriverName is the name of the aws driver
	DriverName = "aws"
)

type aws struct {
	node.Driver
	session     *session.Session
	credentials *credentials.Credentials
	config      *aws_pkg.Config
	region      string
	schedDriver scheduler.Driver
	svc         *ec2.EC2
	svcSsm      *ssm.SSM
	instances   []*ec2.Instance
}

func (a *aws) String() string {
	return DriverName
}

func (a *aws) Init(sched string) error {
	var err error
	sess := session.Must(session.NewSessionWithOptions(session.Options{
		SharedConfigState: session.SharedConfigEnable,
	}))
	creds := credentials.NewEnvCredentials()
	a.credentials = creds
	a.region = os.Getenv("AWS_REGION")
	if a.region == "" {
		return fmt.Errorf("Env AWS_REGION not found")
	}
	config := &aws_pkg.Config{Region: aws_pkg.String(a.region)}
	config.WithCredentials(creds)
	a.config = config
	svc := ec2.New(sess, config)
	a.svc = svc
	a.schedDriver, err = scheduler.Get(sched)
	if err != nil {
		return err
	}
	a.svcSsm = ssm.New(sess, aws_pkg.NewConfig().WithRegion(a.region))
	a.session = sess
	instances, err := a.getAllInstances()
	if err != nil {
		return err
	}
	a.instances = instances
	nodes := a.schedDriver.GetNodes()
	for _, n := range nodes {
		if n.Type == node.TypeWorker {
			if err := a.TestConnection(n, node.ConnectionOpts{
				Timeout:         1 * time.Minute,
				TimeBeforeRetry: 10 * time.Second,
			}); err != nil {
				return &node.ErrFailedToTestConnection{
					Node:  n,
					Cause: err.Error(),
				}
			}
		}
	}
	return nil
}

func (a *aws) TestConnection(n node.Node, options node.ConnectionOpts) error {
	var err error
	instanceID, err := a.getNodeIDByPrivAddr(n)
	if err != nil {
		return &node.ErrFailedToTestConnection{
			Node:  n,
			Cause: fmt.Sprintf("failed to get instance ID for connection due to: %v", err),
		}
	}
	command := "uptime"
	param := make(map[string][]*string)
	param["commands"] = []*string{
		aws_pkg.String(command),
	}
	sendCommandInput := &ssm.SendCommandInput{
		Comment:      aws_pkg.String(command),
		DocumentName: aws_pkg.String("AWS-RunShellScript"),
		Parameters:   param,
		InstanceIds: []*string{
			aws_pkg.String(instanceID),
		},
	}
	sendCommandOutput, err := a.svcSsm.SendCommand(sendCommandInput)
	if err != nil {
		return &node.ErrFailedToTestConnection{
			Node:  n,
			Cause: fmt.Sprintf("failed to send command to instance %s: %v", instanceID, err),
		}
	}
	if sendCommandOutput.Command == nil || sendCommandOutput.Command.CommandId == nil {
		return fmt.Errorf("No command returned after sending command to %s", instanceID)
	}
	listCmdsInput := &ssm.ListCommandInvocationsInput{
		CommandId: sendCommandOutput.Command.CommandId,
	}
	t := func() (interface{}, error) {
		return "", a.connect(listCmdsInput)
	}

	if _, err := task.DoRetryWithTimeout(t, options.Timeout, options.TimeBeforeRetry); err != nil {
		return &node.ErrFailedToTestConnection{
			Node:  n,
			Cause: err.Error(),
		}
	}
	return err
}

func (a *aws) connect(listCmdsInput *ssm.ListCommandInvocationsInput) error {
	var status string
	listCmdInvsOutput, _ := a.svcSsm.ListCommandInvocations(listCmdsInput)
	for _, cmd := range listCmdInvsOutput.CommandInvocations {
		status = strings.TrimSpace(*cmd.StatusDetails)
		if status == "Success" {
			return nil
		}
	}
	return &node.ErrFailedToTestConnection{
		Cause: fmt.Sprintf("Failed to connect. Command status is %s", status),
	}
}

func (a *aws) RebootNode(n node.Node, options node.RebootNodeOpts) error {
	var err error
	instanceID, err := a.getNodeIDByPrivAddr(n)
	if err != nil {
		return &node.ErrFailedToRebootNode{
			Node:  n,
			Cause: fmt.Sprintf("failed to get instance ID due to: %v", err),
		}
	}
	//Reboot the instance by its InstanceID
	rebootInput := &ec2.RebootInstancesInput{
		InstanceIds: []*string{
			aws_pkg.String(instanceID),
		},
	}
	_, err = a.svc.RebootInstances(rebootInput)
	if err != nil {
		return &node.ErrFailedToRebootNode{
			Node:  n,
			Cause: fmt.Sprintf("failed to reboot instance due to: %v", err),
		}
	}
	//TestConnection after node reboot
	err = a.TestConnection(n, node.ConnectionOpts{Timeout: 1 * time.Minute, TimeBeforeRetry: 10 * time.Second})
	if err != nil {
		return &node.ErrFailedToRebootNode{
			Node:  n,
			Cause: fmt.Sprintf("failed to connect instance after reboot due to: %v", err),
		}
	}
	return nil
}

func (a *aws) ShutdownNode(n node.Node, options node.ShutdownNodeOpts) error {
	return nil
}

func (a *aws) getAllInstances() ([]*ec2.Instance, error) {
	instances := []*ec2.Instance{}
	params := &ec2.DescribeInstancesInput{}
	resp, err := a.svc.DescribeInstances(params)
	if err != nil {
		return instances, fmt.Errorf("there was an error listing instances in %s. Error: %q", a.region, err.Error())
	}
	reservations := resp.Reservations
	for _, resv := range reservations {
		for _, ins := range resv.Instances {
			instances = append(instances, ins)
		}
	}
	return instances, err
}

func (a *aws) getNodeIDByPrivAddr(n node.Node) (string, error) {
	for _, i := range a.instances {
		for _, addr := range n.Addresses {
			if aws_pkg.StringValue(i.PrivateIpAddress) == addr {
				return aws_pkg.StringValue(i.InstanceId), nil
			}
		}
	}
	return "", fmt.Errorf("Failed to get instanceID of %s by privateIP", n.Name)
}

func init() {
	a := &aws{
		Driver: node.NotSupportedDriver,
	}
	node.Register(DriverName, a)
}
<|endoftext|>


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

package path

import (
	"bytes"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/corestoreio/csfw/store/scope"
	"github.com/corestoreio/csfw/util/bufferpool"
	"github.com/juju/errgo"
)

// Levels defines how many parts are at least in a path.
// Like a/b/c for 3 parts. And 5 for a fully qualified path.
const Levels int = 3

// Separator used in the database table core_config_data and in config.Service
// to separate the path parts.
const Separator = "/"

const rSeparator = '/'
const strDefaultID = "0"

// Path represents a configuration path.
type Path struct {
	// Parts either one short path or three path parts
	Parts []string
	Scope scope.Scope
	// ID represents a website, group or store ID
	ID int64
}

// New creates a new validated Path. Argument can either be a path like
// a/b/c or path parts like "a","b","c". Scope is assigned to Default.
func New(paths ...string) (Path, error) {
	p := Path{
		Parts: paths,
		Scope: scope.DefaultID,
	}
	if err := p.IsValid(); err != nil {
		return Path{}, err
	}
	return p, nil
}

// NewSplit takes a path argument like a/b/c or path parts like "a","b","c".
// If a path has been provided it gets split into its parts.
// Scope is assigned to Default.
func NewSplit(paths ...string) (Path, error) {
	p := Path{
		Scope: scope.DefaultID,
	}
	switch {
	case len(paths) >= Levels:
		p.Parts = paths
	case len(paths) == 1 && paths[0] != "":
		p.Parts = Split(paths[0])
	default:
		return Path{}, errgo.Newf("Incorrect number of paths elements: want %d, have %d, Path: %v", Levels, len(paths), paths)
	}

	if err := p.IsValid(); err != nil {
		return Path{}, err
	}
	return p, nil
}

// MustNew same as New but panics on error.
func MustNew(paths ...string) Path {
	p, err := New(paths...)
	if err != nil {
		panic(err)
	}
	return p
}

// BindStr binds a path to a new scope with its scope ID.
// The scope gets extracted from the StrScope.
func (p Path) BindStr(s scope.StrScope, id int64) Path {
	p.Scope = s.Scope()
	p.ID = id
	return p
}

// Bind binds a path to a new scope with its scope ID.
// Group Scope is not supported and falls back to default.
func (p Path) Bind(s scope.Scope, id int64) Path {
	p.Scope = s
	p.ID = id
	return p
}

// StrScope wrapper function. Converts the Path.Scope to a StrScope.
func (p Path) StrScope() string {
	return scope.FromScope(p.Scope).String()
}

// String returns a fully qualified path. Errors get logged if debug mode
// is enabled.
func (p Path) String() string {
	s, err := p.FQ()
	if PkgLog.IsDebug() {
		PkgLog.Debug("path.Path.FQ.String", "err", err, "path", p)
	}
	return s
}

// FQ returns the fully qualified path.
func (p Path) FQ() (string, error) {
	if err := p.IsValid(); err != nil {
		return "", err
	}

	idStr := "0"
	if p.ID > 0 {
		if p.ID <= int64CacheLen {
			idStr = int64Cache[p.ID]
		} else {
			idStr = strconv.FormatInt(p.ID, 10)
		}
	}

	scopeStr := scope.FromScope(p.Scope)
	if scopeStr == scope.StrDefault && idStr != strDefaultID {
		idStr = strDefaultID // default scope is always 0
	}
	buf := bufferpool.Get()
	defer bufferpool.Put(buf)
	buf.WriteString(scopeStr.String())
	buf.WriteString(Separator)
	buf.WriteString(idStr)
	buf.WriteString(Separator)
	join(buf, p.Parts)
	return buf.String(), nil
}

// this "cache" should cover ~80% of all store setups
var int64Cache = [...]string{
	"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
}
var int64CacheLen = int64(len(int64Cache))

// Split splits a configuration path by the path separator PS.
func Split(path string) []string {
	if len(path) > 0 && path[:1] == Separator {
		path = path[1:] // trim first PS
	}
	return strings.Split(path, Separator)
}

func join(buf *bytes.Buffer, paths []string) {
	for i, p := range paths {
		buf.WriteString(p)
		if i < (len(paths) - 1) {
			buf.WriteString(Separator)
		}
	}
}

// Level joins a configuration path parts by the path separator PS.
// The level argument defines the depth of the path parts to join.
// Level 1 will return the first part like "a", Level 2 returns "a/b"
// Level 3 returns "a/b/c" and so on. Level -1 joins all available path parts.
// Does not generate a fully qualified path.
func (p Path) Level(level int) string {
	lp := len(p.Parts)
	if level <= 0 || level >= lp {
		level = lp
	}
	if lp == 1 {
		return p.Parts[0]
	}

	buf := bufferpool.Get()
	join(buf, p.Parts[:level])
	s := buf.String()
	bufferpool.Put(buf)
	return s
}

// SplitFQPath takes a fully qualified path and splits it into its parts.
// 	Input: stores/5/catalog/frontend/list_allow_all
//	=>
//		scope: 		stores
//		scopeID: 	5
//		path: 		catalog/frontend/list_allow_all
// Zero allocations to memory. Err may contain an ErrUnsupportedScope or
// failed to parse a string into an int64 or invalid fqPath.
func SplitFQ(fqPath string) (Path, error) {
	if false == isFQ(fqPath) {
		return Path{}, errgo.Newf("Incorrect fully qualified path: %q", fqPath)
	}

	fi := strings.Index(fqPath, Separator)
	scopeStr := fqPath[:fi]

	if false == scope.Valid(scopeStr) {
		return Path{}, scope.ErrUnsupportedScope
	}

	fqPath = fqPath[fi+1:]

	fi = strings.Index(fqPath, Separator)
	scopeID, err := strconv.ParseInt(fqPath[:fi], 10, 64)
	path := fqPath[fi+1:]
	return Path{
		Parts: []string{path},
		Scope: scope.FromString(scopeStr),
		ID:    scopeID,
	}, err
}

func isFQ(fqPath string) bool {
	return strings.Count(fqPath, Separator) >= Levels+1 // like stores/1/a/b/c
}

// ErrPartsEmpty path parts are empty
var ErrPartsEmpty = errors.New("Parts are empty")

// ErrIncorrectPath a path is missing a path separator or is too short
var ErrIncorrectPath = errors.New("Incorrect Path. Either to short or missing path separator.")

// IsValid checks for valid configuration path. Returns nil on success.
// Configuration path attribute can have only three groups of [a-zA-Z0-9_] characters split by '/'.
// Minimal length per part 2 characters. Case sensitive.
//
// IsValid can return ErrPartsEmpty or ErrIncorrectPath or a custom error.
func (p Path) IsValid() error {
	lp := len(p.Parts)
	if lp < 1 {
		return ErrPartsEmpty
	}

	// first argument only without a slash
	if lp == 1 && (strings.Count(p.Parts[0], Separator) != Levels-1 || len(p.Parts[0]) < 8) { // must contain at least two slashes
		return ErrIncorrectPath
	}

	valid := 0
	for _, part := range p.Parts {
		if len(part) < 2 {
			return fmt.Errorf("This path part %q is too short. Parts: %#v", part, p.Parts)
		}

		for _, r := range part {
			ok := false
			switch {
			case '0' <= r && r <= '9':
				ok = true
			case 'a' <= r && r <= 'z':
				ok = true
			case 'A' <= r && r <= 'Z':
				ok = true
			case r == '_', r == rSeparator:
				ok = true
			}
			if !ok {
				return fmt.Errorf("This character %q is not allowed in Parts %#v", string(r), p.Parts)
			}
		}
		valid++
	}

	if lp > 1 && valid < Levels { // if more than one arg has been provided all 3 must be valid
		return fmt.Errorf("All arguments must be valid! Min want: %d. Have: %d. Parts %#v", Levels, valid, p.Parts)
	}

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

package path

import (
	"bytes"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/corestoreio/csfw/store/scope"
	"github.com/corestoreio/csfw/util/bufferpool"
)

// Levels defines how many parts are at least in a path.
// Like a/b/c for 3 parts. And 5 for a fully qualified path.
const Levels int = 3

// Separator used in the database table core_config_data and in config.Service
// to separate the path parts.
const Separator = "/"

const rSeparator = '/'
const strDefaultID = "0"

// ErrPartsEmpty path parts are empty
var ErrPartsEmpty = errors.New("Parts are empty")

// ErrIncorrectPath a path is missing a path separator or is too short
var ErrIncorrectPath = errors.New("Incorrect Path. Either to short or missing path separator.")

// Path represents a configuration path.
type Path struct {
	// Parts either one short path or three path parts
	Parts []string
	Scope scope.Scope
	// ID represents a website, group or store ID
	ID int64
}

// New creates a new validated Path. Argument can either be a path like
// a/b/c or path parts like "a","b","c". Scope is assigned to Default.
func New(paths ...string) (Path, error) {
	p := Path{
		Parts: paths,
		Scope: scope.DefaultID,
	}
	if err := p.IsValid(); err != nil {
		return Path{}, err
	}
	return p, nil
}

// NewSplit takes a path argument like a/b/c or path parts like "a","b","c".
// If a path has been provided it gets split into its parts.
// Scope is assigned to Default.
func NewSplit(paths ...string) (Path, error) {
	p := Path{
		Scope: scope.DefaultID,
	}
	switch {
	case len(paths) >= Levels:
		p.Parts = paths
	case len(paths) == 1 && paths[0] != "":
		p.Parts = Split(paths[0])
	default:
		return Path{}, fmt.Errorf("Incorrect number of paths elements: want %d, have %d, Path: %v", Levels, len(paths), paths)
	}

	if err := p.IsValid(); err != nil {
		return Path{}, err
	}
	return p, nil
}

// MustNew same as New but panics on error.
func MustNew(paths ...string) Path {
	p, err := New(paths...)
	if err != nil {
		panic(err)
	}
	return p
}

// BindStr binds a path to a new scope with its scope ID.
// The scope gets extracted from the StrScope.
func (p Path) BindStr(s scope.StrScope, id int64) Path {
	p.Scope = s.Scope()
	p.ID = id
	return p
}

// Bind binds a path to a new scope with its scope ID.
// Group Scope is not supported and falls back to default.
func (p Path) Bind(s scope.Scope, id int64) Path {
	p.Scope = s
	p.ID = id
	return p
}

// StrScope wrapper function. Converts the Path.Scope to a StrScope.
func (p Path) StrScope() string {
	return scope.FromScope(p.Scope).String()
}

// String returns a fully qualified path. Errors get logged if debug mode
// is enabled.
func (p Path) String() string {
	s, err := p.FQ()
	if PkgLog.IsDebug() {
		PkgLog.Debug("path.Path.FQ.String", "err", err, "path", p)
	}
	return s
}

// FQ returns the fully qualified path.
func (p Path) FQ() (string, error) {
	if err := p.IsValid(); err != nil {
		return "", err
	}

	idStr := "0"
	if p.ID > 0 {
		if p.ID <= int64CacheLen {
			idStr = int64Cache[p.ID]
		} else {
			idStr = strconv.FormatInt(p.ID, 10)
		}
	}

	scopeStr := scope.FromScope(p.Scope)
	if scopeStr == scope.StrDefault && idStr != strDefaultID {
		idStr = strDefaultID // default scope is always 0
	}
	buf := bufferpool.Get()
	defer bufferpool.Put(buf)
	buf.WriteString(scopeStr.String())
	buf.WriteString(Separator)
	buf.WriteString(idStr)
	buf.WriteString(Separator)
	join(buf, p.Parts)
	return buf.String(), nil
}

// this "cache" should cover ~80% of all store setups
var int64Cache = [...]string{
	"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
}
var int64CacheLen = int64(len(int64Cache))

// Split splits a configuration path by the path separator PS.
func Split(path string) []string {
	if len(path) > 0 && path[:1] == Separator {
		path = path[1:] // trim first PS
	}
	return strings.Split(path, Separator)
}

func join(buf *bytes.Buffer, paths []string) {
	for i, p := range paths {
		buf.WriteString(p)
		if i < (len(paths) - 1) {
			buf.WriteString(Separator)
		}
	}
}

// Level joins a configuration path parts by the path separator PS.
// The level argument defines the depth of the path parts to join.
// Level 1 will return the first part like "a", Level 2 returns "a/b"
// Level 3 returns "a/b/c" and so on. Level -1 joins all available path parts.
// Does not generate a fully qualified path.
func (p Path) Level(level int) string {
	lp := len(p.Parts)
	if level <= 0 || level >= lp {
		level = lp
	}
	if lp == 1 {
		return p.Parts[0]
	}

	buf := bufferpool.Get()
	join(buf, p.Parts[:level])
	s := buf.String()
	bufferpool.Put(buf)
	return s
}

// SplitFQPath takes a fully qualified path and splits it into its parts.
// 	Input: stores/5/catalog/frontend/list_allow_all
//	=>
//		scope: 		stores
//		scopeID: 	5
//		path: 		catalog/frontend/list_allow_all
// Zero allocations to memory. Err may contain an ErrUnsupportedScope or
// failed to parse a string into an int64 or invalid fqPath.
func SplitFQ(fqPath string) (Path, error) {
	if false == isFQ(fqPath) {
		return Path{}, fmt.Errorf("Incorrect fully qualified path: %q", fqPath)
	}

	fi := strings.Index(fqPath, Separator)
	scopeStr := fqPath[:fi]

	if false == scope.Valid(scopeStr) {
		return Path{}, scope.ErrUnsupportedScope
	}

	fqPath = fqPath[fi+1:]

	fi = strings.Index(fqPath, Separator)
	scopeID, err := strconv.ParseInt(fqPath[:fi], 10, 64)
	path := fqPath[fi+1:]
	return Path{
		Parts: []string{path},
		Scope: scope.FromString(scopeStr),
		ID:    scopeID,
	}, err
}

func isFQ(fqPath string) bool {
	return strings.Count(fqPath, Separator) >= Levels+1 // like stores/1/a/b/c
}

// IsValid checks for valid configuration path. Returns nil on success.
// Configuration path attribute can have only three groups of [a-zA-Z0-9_] characters split by '/'.
// Minimal length per part 2 characters. Case sensitive.
//
// IsValid can return ErrPartsEmpty or ErrIncorrectPath or a custom error.
func (p Path) IsValid() error {
	lp := len(p.Parts)
	if lp < 1 {
		return ErrPartsEmpty
	}

	// first argument only without a slash
	if lp == 1 && (strings.Count(p.Parts[0], Separator) != Levels-1 || len(p.Parts[0]) < 8) { // must contain at least two slashes
		return ErrIncorrectPath
	}

	valid := 0
	for _, part := range p.Parts {
		if len(part) < 2 {
			return fmt.Errorf("This path part %q is too short. Parts: %#v", part, p.Parts)
		}

		for _, r := range part {
			ok := false
			switch {
			case '0' <= r && r <= '9':
				ok = true
			case 'a' <= r && r <= 'z':
				ok = true
			case 'A' <= r && r <= 'Z':
				ok = true
			case r == '_', r == rSeparator:
				ok = true
			}
			if !ok {
				return fmt.Errorf("This character %q is not allowed in Parts %#v", string(r), p.Parts)
			}
		}
		valid++
	}

	if lp > 1 && valid < Levels { // if more than one arg has been provided all 3 must be valid
		return fmt.Errorf("All arguments must be valid! Min want: %d. Have: %d. Parts %#v", Levels, valid, p.Parts)
	}

	return nil
}
<|endoftext|>


<commit_before>package eforth

// following tutorial at http://www.offete.com/files/zeneForth.htm

import (
	"encoding/binary"
	"errors"
	"fmt"
	"strings"
	"strconv"
)

/*

Memory used in eForth is separated into the following areas:

Cold boot         100H-17FH         Cold start and variable initial values
Code dictionary   180H-1344H        Code dictionary growing upward
Free space        1346H-33E4H       Shared by code and name dictionaries
Name/word         33E6H-3BFFH       Name dictionary growing downward
Data stack        3C00H-3E7FH       Growing downward
TIB               3E80H-            Growing upward
Return stack      -3F7FH            Growing downward
User variables    3F80H-3FFFH

*/

const (
	CELLL = 2              // size of cell
	EM    = 0x04000        // top of memory
	COLDD = 0x00100        // cold start vector
	US    = 64 * CELLL     // user area size in cells
	RTS   = 64 * CELLL     // return stack/TIB size
	RPP   = EM - 8*CELLL   // start of return stack (RP0)
	TIBB  = RPP - RTS      // terminal input buffer (TIB)
	SPP   = TIBB - 8*CELLL // start of data stack (SP0)
	UPP   = EM - 256*CELLL // start of user area (UP0)
	NAMEE = UPP - 8*CELLL  //name dictionary
	CODEE = COLDD + US     // code dictionary
)

// 76 13 e8 35 3 62 61 72 == *bar where bar is the last field and the others are the code reference and prev word reference
type Forth struct {
	/*

	   Forth Register 8086 Register               Function

	   IP  SI                         Interpreter Pointer
	   SP  SP                         Data Stack Pointer
	   RP  BP                         Return Stack Pointer
	   WP  AX                         Word or Work Pointer
	   UP  (in memory )               User Area Pointer

	*/

	IP uint16
	SP uint16
	RP uint16
	WP uint16

	input  interface{}
	output interface{}

	Memory [EM]byte

	/*
	   primitive words or code words are as follows:

	   System interface:       BYE, ?rx, tx!, !io
	   Inner interpreters:     doLIT, doLIST, next, ?branch,  branch, EXECUTE, EXIT
	   Memory access:          ! , @,  C!,  C@
	   Return stack:           RP@,  RP!,  R>, R@,  R>
	   Data stack:             SP@,  SP!,  DROP, DUP,  SWAP,  OVER
	   Logic:                  0<,  AND,  OR,  XOR
	   Arithmetic:             UM+

	*/

	/*
	   For setting up the primitive words in memory
	   and interpretting pcode.
	*/

	prims      uint16
	prim2addr  map[string]uint16
	prim2func  map[string]fn
	pcode2word map[uint16]string

	LAST uint16 // last name in name dictionary
	NP   uint16 // bottom of name dictionary
}

type fn func()

func wordptr(mem []byte, reg uint16) (res uint16) {
	res = binary.LittleEndian.Uint16(mem[reg:])
	return
}

func setwordptr(mem []byte, reg, value uint16) {
	binary.LittleEndian.PutUint16(mem[reg:], value)
}

func NewForth() *Forth {
	f := &Forth{SP: SPP, RP: RPP,
		prim2addr:  make(map[string]uint16),
		prim2func:  make(map[string]fn),
		pcode2word: make(map[uint16]string),
		NP:         NAMEE,
		LAST:       0}
	fmt.Printf("NAMEE is %x\n", NAMEE)
	f.prim2addr["UPP"] = UPP
	f.AddPrimitives()

	f.ColonDefs()
	return f
}

func (f *Forth) AddName(word string, addr uint16) {
	fmt.Println("AddName(", word, ", ", addr, ")")
	_len := uint16(len(word) / CELLL)  // rounded down cell count
	f.NP = f.NP - ((_len + 3) * CELLL) // new header on cell boundary
	i := f.NP
	fmt.Printf("writing to memory address %x\n", i)
	f.SetWordPtr(i, addr)
	f.SetWordPtr(i+2, f.LAST)
	f.LAST = uint16(i + 4)
	f.Memory[f.LAST] = byte(len(word))
	for j, c := range word {
		f.Memory[int(i)+5+j] = byte(c)
	}

}

func (f *Forth) AddPrim(word string, m fn) {
	f.prims = f.prims + 1
	addr := CODEE + (2 * (f.prims - 1))
	f.prim2addr[word] = addr
	f.prim2func[word] = m
	f.pcode2word[f.prims] = word
	fmt.Printf("%x is \"%s\"\n", f.prims, word)
	f.SetWordPtr(addr, f.prims)
	f.AddName(word, addr)
}

func (f *Forth) RemoveComments(a string) (b string) {
	b = a
	i := strings.Index(a, "(")
	if i == -1 {
		return
	} else {
		j := strings.Index(a, ")")
		b = a[:i] + a[j+1:]
	}
	return
}

func doTHEN(f *Forth, ifs []uint16, addr uint16) {
	li := len(ifs) -1
	ifaddr := ifs[li]
	ifs = ifs[:li]
	f.SetWordPtr(ifaddr, addr)
}

func doIF(f *Forth, addr uint16, ifs *[]uint16, word string) {
	*ifs = append(*ifs, addr+4)
	w, _ := f.Addr(word)
	f.SetWordPtr(addr+2, w)
}

func (f *Forth) AddWord(cdef string) (e error) {
	prims := f.prims + 1
	e = nil
	all := strings.Fields(f.RemoveComments(cdef))
	addr := CODEE + (2 * (prims - 1))
	name := all[1]
	all[1] = ":"
	f.prim2addr[name] = addr
	f.AddName(name, addr)
	iwords := all[1:]
	f.SetWordPtr(addr, 2) // CALL is 2
	ifs := []uint16{}
	begins := []uint16{}
	for j, word := range iwords {
		fmt.Println("word is ", word)
		switch(word) {
			case "BEGIN":
				begins = append(begins, addr+2)
				// get rid of +2 makes no sense
			case "AGAIN":
				i := len(begins) -1
				beginaddr := begins[i]
				begins = begins[:i]
				branch, _ := f.Addr("BRANCH")
				f.SetWordPtr(addr+2, branch)
				f.SetWordPtr(addr+4, beginaddr)
				prims = prims + 2
				addr = addr + 4
			case "UNTIL":
				i := len(begins) -1
				beginaddr := begins[i]
				begins = begins[:i]
				branch, _ := f.Addr("?BRANCH")
				f.SetWordPtr(addr+2, branch)
				f.SetWordPtr(addr+4, beginaddr)
				prims = prims + 2
				addr = addr + 4
			case "IF":
				doIF(f, addr, &ifs, "?BRANCH")
				prims = prims + 2
				addr = addr + 4
			case "THEN":
				/*
				CALL addr addr IF addr addr THEN addrA
				CALL addr addr QBRAN p_addrA addr addr addrA

				Also things could be nested
				*/
				doTHEN(f, ifs, addr+2)
				// +2 is because addr points to the previous word addr
			case "ELSE":
				/*
				CALL addr addr IF addr ELSE addrA addr THEN addrB
				CALL addr addr QBRAN p_addrA addr BRAN p_addrB addrA addr addrB
				*/
				doTHEN(f, ifs, addr+6)
				doIF(f, addr, &ifs, "BRANCH")
				prims = prims + 2
				addr = addr + 4
			default:
				var wa uint16
				if j > 1 && iwords[j-1] == "doLIT" {
					x, err := strconv.Atoi(word)
					if err != nil {
						e = err
						return
					}
					wa = uint16(x)
				} else {
					x, err := f.Addr(word)
					if err != nil {
						e = err
						return
					}
					wa = uint16(x)

				}
				addr = addr + 2
				f.SetWordPtr(addr, wa)
				prims = prims + 1
				fmt.Printf("%x: %x %s\n", addr, wa, word)
		}
		fmt.Printf("addr is %x\n", addr)
	}

	f.prims = prims
	return
}

func (f *Forth) Addr(word string) (res uint16, err error) {
	err = nil
	res, ok := f.prim2addr[word]
	if !ok {
		err = errors.New(fmt.Sprintf(`Address for word "%s" not found`, word))
	}
	return
}

func (f *Forth) CallFn(word string) error {
	m, ok := f.prim2func[word]
	fmt.Printf("CallFn %v \"%s\"\n", m, word)
	if !ok {
		return errors.New(fmt.Sprintf("No method found for \"%s\"", word))
	}
	m()
	return nil
}

func (f *Forth) Frompcode(pcode uint16) (res string) {
	res = f.pcode2word[pcode]
	return
}

// this simulates the von neuman machine or processor
func (f *Forth) Main() {
	f.B_IO()
	fmt.Println("---------Main----------")
	var pcode uint16
	var word string
inf:
	for {
		// simulate JMP to f.WP
		pcode = f.WordPtr(f.WP)
		word = f.Frompcode(pcode)
		fmt.Printf("WP %x IP %x pcode %x word \"%s\"\n", f.WP, f.IP, pcode, word)
		err := f.CallFn(word)
		if err != nil {
			fmt.Println(err)
			break inf
		}
		if f.IP == 0xffff { // for BYE
			break inf
		}
	}
}

func (f *Forth) WordPtr(reg uint16) (res uint16) {
	return wordptr(f.Memory[0:], reg)
}

func (f *Forth) SetWordPtr(reg, value uint16) {
	setwordptr(f.Memory[0:], reg, value)
}

func (f *Forth) RegLower(w uint16) (res byte) {
	res = byte(0x00ff & w)
	return
}

func (f *Forth) SetBytePtr(i uint16, v byte) {
	f.Memory[i] = v
}

/*
lodsw
jmp ax
*/
func (f *Forth) _next() {
	f.WP = f.WordPtr(f.IP)
	f.IP += 2
}

// swap register values
func XCHG(a, b *uint16) {
	olda := *a
	*a = *b
	*b = olda
}

// PUSH is
// SP = SP -2
// [SP] = operand
func (f *Forth) Push(v uint16) {
	f.SP = f.SP - 2
	binary.LittleEndian.PutUint16(f.Memory[f.SP:], v)
}

// POP is
// operand = [SP]
// SP = SP + 2
func (f *Forth) Pop() uint16 {
	res := binary.LittleEndian.Uint16(f.Memory[f.SP:])
	f.SP = f.SP + 2
	return res
}
<commit_msg>nicer error messages when a colon definition fails to compile<commit_after>package eforth

// following tutorial at http://www.offete.com/files/zeneForth.htm

import (
	"encoding/binary"
	"errors"
	"fmt"
	"strings"
	"strconv"
)

/*

Memory used in eForth is separated into the following areas:

Cold boot         100H-17FH         Cold start and variable initial values
Code dictionary   180H-1344H        Code dictionary growing upward
Free space        1346H-33E4H       Shared by code and name dictionaries
Name/word         33E6H-3BFFH       Name dictionary growing downward
Data stack        3C00H-3E7FH       Growing downward
TIB               3E80H-            Growing upward
Return stack      -3F7FH            Growing downward
User variables    3F80H-3FFFH

*/

const (
	CELLL = 2              // size of cell
	EM    = 0x04000        // top of memory
	COLDD = 0x00100        // cold start vector
	US    = 64 * CELLL     // user area size in cells
	RTS   = 64 * CELLL     // return stack/TIB size
	RPP   = EM - 8*CELLL   // start of return stack (RP0)
	TIBB  = RPP - RTS      // terminal input buffer (TIB)
	SPP   = TIBB - 8*CELLL // start of data stack (SP0)
	UPP   = EM - 256*CELLL // start of user area (UP0)
	NAMEE = UPP - 8*CELLL  //name dictionary
	CODEE = COLDD + US     // code dictionary
)

// 76 13 e8 35 3 62 61 72 == *bar where bar is the last field and the others are the code reference and prev word reference
type Forth struct {
	/*

	   Forth Register 8086 Register               Function

	   IP  SI                         Interpreter Pointer
	   SP  SP                         Data Stack Pointer
	   RP  BP                         Return Stack Pointer
	   WP  AX                         Word or Work Pointer
	   UP  (in memory )               User Area Pointer

	*/

	IP uint16
	SP uint16
	RP uint16
	WP uint16

	input  interface{}
	output interface{}

	Memory [EM]byte

	/*
	   primitive words or code words are as follows:

	   System interface:       BYE, ?rx, tx!, !io
	   Inner interpreters:     doLIT, doLIST, next, ?branch,  branch, EXECUTE, EXIT
	   Memory access:          ! , @,  C!,  C@
	   Return stack:           RP@,  RP!,  R>, R@,  R>
	   Data stack:             SP@,  SP!,  DROP, DUP,  SWAP,  OVER
	   Logic:                  0<,  AND,  OR,  XOR
	   Arithmetic:             UM+

	*/

	/*
	   For setting up the primitive words in memory
	   and interpretting pcode.
	*/

	prims      uint16
	prim2addr  map[string]uint16
	prim2func  map[string]fn
	pcode2word map[uint16]string

	LAST uint16 // last name in name dictionary
	NP   uint16 // bottom of name dictionary
}

type fn func()

func wordptr(mem []byte, reg uint16) (res uint16) {
	res = binary.LittleEndian.Uint16(mem[reg:])
	return
}

func setwordptr(mem []byte, reg, value uint16) {
	binary.LittleEndian.PutUint16(mem[reg:], value)
}

func NewForth() *Forth {
	f := &Forth{SP: SPP, RP: RPP,
		prim2addr:  make(map[string]uint16),
		prim2func:  make(map[string]fn),
		pcode2word: make(map[uint16]string),
		NP:         NAMEE,
		LAST:       0}
	fmt.Printf("NAMEE is %x\n", NAMEE)
	f.prim2addr["UPP"] = UPP
	f.AddPrimitives()

	f.ColonDefs()
	return f
}

func (f *Forth) AddName(word string, addr uint16) {
	fmt.Println("AddName(", word, ", ", addr, ")")
	_len := uint16(len(word) / CELLL)  // rounded down cell count
	f.NP = f.NP - ((_len + 3) * CELLL) // new header on cell boundary
	i := f.NP
	fmt.Printf("writing to memory address %x\n", i)
	f.SetWordPtr(i, addr)
	f.SetWordPtr(i+2, f.LAST)
	f.LAST = uint16(i + 4)
	f.Memory[f.LAST] = byte(len(word))
	for j, c := range word {
		f.Memory[int(i)+5+j] = byte(c)
	}

}

func (f *Forth) AddPrim(word string, m fn) {
	f.prims = f.prims + 1
	addr := CODEE + (2 * (f.prims - 1))
	f.prim2addr[word] = addr
	f.prim2func[word] = m
	f.pcode2word[f.prims] = word
	fmt.Printf("%x is \"%s\"\n", f.prims, word)
	f.SetWordPtr(addr, f.prims)
	f.AddName(word, addr)
}

func (f *Forth) RemoveComments(a string) (b string) {
	b = a
	i := strings.Index(a, "(")
	if i == -1 {
		return
	} else {
		j := strings.Index(a, ")")
		b = a[:i] + a[j+1:]
	}
	return
}

func doTHEN(f *Forth, ifs []uint16, addr uint16) {
	li := len(ifs) -1
	ifaddr := ifs[li]
	ifs = ifs[:li]
	f.SetWordPtr(ifaddr, addr)
}

func doIF(f *Forth, addr uint16, ifs *[]uint16, word string) {
	*ifs = append(*ifs, addr+4)
	w, _ := f.Addr(word)
	f.SetWordPtr(addr+2, w)
}

func (f *Forth) AddWord(cdef string) (e error) {
	prims := f.prims + 1
	e = nil
	all := strings.Fields(f.RemoveComments(cdef))
	startaddr := CODEE + (2 * (prims - 1))
	addr := startaddr
	name := all[1]
	all[1] = ":"
	iwords := all[1:]
	ifs := []uint16{}
	begins := []uint16{}
	for j, word := range iwords {
		fmt.Println("word is ", word)
		switch(word) {
			case "BEGIN":
				begins = append(begins, addr+2)
				// get rid of +2 makes no sense
			case "AGAIN":
				i := len(begins) -1
				beginaddr := begins[i]
				begins = begins[:i]
				branch, _ := f.Addr("BRANCH")
				f.SetWordPtr(addr+2, branch)
				f.SetWordPtr(addr+4, beginaddr)
				prims = prims + 2
				addr = addr + 4
			case "UNTIL":
				i := len(begins) -1
				beginaddr := begins[i]
				begins = begins[:i]
				branch, _ := f.Addr("?BRANCH")
				f.SetWordPtr(addr+2, branch)
				f.SetWordPtr(addr+4, beginaddr)
				prims = prims + 2
				addr = addr + 4
			case "IF":
				doIF(f, addr, &ifs, "?BRANCH")
				prims = prims + 2
				addr = addr + 4
			case "THEN":
				/*
				CALL addr addr IF addr addr THEN addrA
				CALL addr addr QBRAN p_addrA addr addr addrA

				Also things could be nested
				*/
				doTHEN(f, ifs, addr+2)
				// +2 is because addr points to the previous word addr
			case "ELSE":
				/*
				CALL addr addr IF addr ELSE addrA addr THEN addrB
				CALL addr addr QBRAN p_addrA addr BRAN p_addrB addrA addr addrB
				*/
				doTHEN(f, ifs, addr+6)
				doIF(f, addr, &ifs, "BRANCH")
				prims = prims + 2
				addr = addr + 4
			default:
				var wa uint16
				if j > 1 && iwords[j-1] == "doLIT" {
					x, err := strconv.Atoi(word)
					if err != nil {
						e = err
						return
					}
					wa = uint16(x)
				} else {
					x, err := f.Addr(word)
					if err != nil {
						e = err
						fmt.Printf("ERROR: not adding \"%v\" because %v\n", name, err)
						return
					}
					wa = uint16(x)

				}
				addr = addr + 2
				f.SetWordPtr(addr, wa)
				prims = prims + 1
				fmt.Printf("%x: %x %s\n", addr, wa, word)
		}
		fmt.Printf("addr is %x\n", addr)
	}

	f.SetWordPtr(startaddr, 2) // CALL is 2
	f.prim2addr[name] = startaddr
	f.AddName(name, startaddr)
	f.prims = prims
	return
}

func (f *Forth) Addr(word string) (res uint16, err error) {
	err = nil
	res, ok := f.prim2addr[word]
	if !ok {
		err = errors.New(fmt.Sprintf(`Address for word "%s" not found`, word))
	}
	return
}

func (f *Forth) CallFn(word string) error {
	m, ok := f.prim2func[word]
	fmt.Printf("CallFn %v \"%s\"\n", m, word)
	if !ok {
		return errors.New(fmt.Sprintf("No method found for \"%s\"", word))
	}
	m()
	return nil
}

func (f *Forth) Frompcode(pcode uint16) (res string) {
	res = f.pcode2word[pcode]
	return
}

// this simulates the von neuman machine or processor
func (f *Forth) Main() {
	f.B_IO()
	fmt.Println("---------Main----------")
	var pcode uint16
	var word string
inf:
	for {
		// simulate JMP to f.WP
		pcode = f.WordPtr(f.WP)
		word = f.Frompcode(pcode)
		fmt.Printf("WP %x IP %x pcode %x word \"%s\"\n", f.WP, f.IP, pcode, word)
		err := f.CallFn(word)
		if err != nil {
			fmt.Println(err)
			break inf
		}
		if f.IP == 0xffff { // for BYE
			break inf
		}
	}
}

func (f *Forth) WordPtr(reg uint16) (res uint16) {
	return wordptr(f.Memory[0:], reg)
}

func (f *Forth) SetWordPtr(reg, value uint16) {
	setwordptr(f.Memory[0:], reg, value)
}

func (f *Forth) RegLower(w uint16) (res byte) {
	res = byte(0x00ff & w)
	return
}

func (f *Forth) SetBytePtr(i uint16, v byte) {
	f.Memory[i] = v
}

/*
lodsw
jmp ax
*/
func (f *Forth) _next() {
	f.WP = f.WordPtr(f.IP)
	f.IP += 2
}

// swap register values
func XCHG(a, b *uint16) {
	olda := *a
	*a = *b
	*b = olda
}

// PUSH is
// SP = SP -2
// [SP] = operand
func (f *Forth) Push(v uint16) {
	f.SP = f.SP - 2
	binary.LittleEndian.PutUint16(f.Memory[f.SP:], v)
}

// POP is
// operand = [SP]
// SP = SP + 2
func (f *Forth) Pop() uint16 {
	res := binary.LittleEndian.Uint16(f.Memory[f.SP:])
	f.SP = f.SP + 2
	return res
}
<|endoftext|>


<commit_before>package watchdog

import (
	"sync"
	"time"
)

type Task struct {
	Schedule time.Duration
	Command func(time.Time) error
	Timeout time.Duration
}

type Execution struct {
	Task *Task
	StartedAt time.Time
	FinishedAt time.Time
	Error error
}

type Stall struct {
	Task *Task
	StartedAt time.Time
	StalledAt time.Time
}

type Watchdog struct {
	tasks []*Task

	done chan bool
	sync sync.WaitGroup

	executions chan *Execution
	stalls chan *Stall
}

func Watch(tasks ...*Task) *Watchdog {
	w := &Watchdog{
		tasks: tasks,
		done: make(chan bool),
		executions: make(chan *Execution, 10),
		stalls: make(chan *Stall, 10),
	}
	go w.run()
	return w
}

func (w *Watchdog) Executions() <- chan *Execution {
	return w.executions
}

func (w *Watchdog) Stalls() <- chan *Stall {
	return w.stalls
}

func (w *Watchdog) run() {
	for _, task := range w.tasks {
		go w.runTask(task)
	}
	w.sync.Add(len(w.tasks))
}

func (w *Watchdog) runTask(task *Task) {
	ticker := time.NewTicker(task.Schedule)
	schedule := make(chan time.Time, 1)
	stallTimer := time.NewTimer(task.Schedule + 1 * time.Millisecond)
	stallTimer.Stop()
	go func () {
		for startedAt := range schedule {
			stallTimer.Reset(task.Timeout)
			err := task.Command(startedAt)
			stallTimer.Reset(task.Schedule)
			finishedAt := time.Now()
			select {
			case <- w.done:
				return
			default:
				w.executions <- &Execution{task, startedAt, finishedAt, err}
			}
		}
	}()
	monitor: for {
		var startedAt time.Time
		select {
		case <- w.done:
			ticker.Stop()
			stallTimer.Stop()
			break monitor
		case startedAt = <- ticker.C:
			select {
			case schedule <- startedAt:
			default:
			}
		case stalledAt := <- stallTimer.C:
			w.stalls <- &Stall{task, startedAt, stalledAt}
		}
	}
	close(schedule)
	stallTimer.Stop()
	w.sync.Done()
}

func (w *Watchdog) Stop() {
	close(w.done)
	w.sync.Wait()
	close(w.executions)
	close(w.stalls)
}
<commit_msg>Ensure Stop() waits for in-flight tasks<commit_after>package watchdog

import (
	"sync"
	"time"
)

type Task struct {
	Schedule time.Duration
	Command func(time.Time) error
	Timeout time.Duration
}

type Execution struct {
	Task *Task
	StartedAt time.Time
	FinishedAt time.Time
	Error error
}

type Stall struct {
	Task *Task
	StartedAt time.Time
	StalledAt time.Time
}

type Watchdog struct {
	tasks []*Task

	done chan bool
	sync sync.WaitGroup

	executions chan *Execution
	stalls chan *Stall
}

func Watch(tasks ...*Task) *Watchdog {
	w := &Watchdog{
		tasks: tasks,
		done: make(chan bool),
		executions: make(chan *Execution, 10),
		stalls: make(chan *Stall, 10),
	}
	go w.run()
	return w
}

func (w *Watchdog) Executions() <- chan *Execution {
	return w.executions
}

func (w *Watchdog) Stalls() <- chan *Stall {
	return w.stalls
}

func (w *Watchdog) run() {
	for _, task := range w.tasks {
		go w.runTask(task)
	}
	w.sync.Add(len(w.tasks))
}

func (w *Watchdog) runTask(task *Task) {
	ticker := time.NewTicker(task.Schedule)
	schedule := make(chan time.Time, 1)
	stallTimer := time.NewTimer(task.Schedule + 1 * time.Millisecond)
	stallTimer.Stop()
	taskDone := make(chan bool, 1)
	go func () {
		work: for startedAt := range schedule {
			stallTimer.Reset(task.Timeout)
			err := task.Command(startedAt)
			stallTimer.Reset(task.Schedule)
			finishedAt := time.Now()
			select {
			case <- w.done:
				break work
			default:
				w.executions <- &Execution{task, startedAt, finishedAt, err}
			}
		}
		taskDone <- true
	}()
	monitor: for {
		var startedAt time.Time
		select {
		case <- w.done:
			ticker.Stop()
			stallTimer.Stop()
			break monitor
		case startedAt = <- ticker.C:
			select {
			case schedule <- startedAt:
			default:
			}
		case stalledAt := <- stallTimer.C:
			w.stalls <- &Stall{task, startedAt, stalledAt}
		}
	}
	close(schedule)
	stallTimer.Stop()
	<- taskDone
	w.sync.Done()
}

func (w *Watchdog) Stop() {
	close(w.done)
	w.sync.Wait()
	close(w.executions)
	close(w.stalls)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	"net/http"
	"os"

	"github.com/julienschmidt/httprouter"
)

var (
	apiServerBaseURL       = flag.String("api-server-base-url", "https://kubernetes.default", "Kubernetes API server base URL")
	apiServerUser          = flag.String("api-server-username", "admin", "Kubernetes API server username to use if no service acccount API token is present.")
	apiServerPassword      = flag.String("api-server-password", "admin123", "Kubernetes API server password to use if no service acccount API token is present.")
	awsKey                 = flag.String("aws-access-key", "", "Default decap AWS access key.  /etc/secrets/aws-key in the cluster overrides this.")
	awsSecret              = flag.String("aws-secret-key", "", "Default decap AWS access secret.  /etc/secrets/aws-secret in the cluster overrides this.")
	awsRegion              = flag.String("aws-region", "us-west-1", "Default decap AWS region.  /etc/secrets/aws-region in the cluster overrides this.")
	githubClientID         = flag.String("github-client-id", "", "Default Github ClientID for quering Github repos.  /etc/secrets/github-client-id in the cluster overrides this.")
	githubClientSecret     = flag.String("github-client-secret", "", "Default Github Client Secret for quering Github repos.  /etc/secrets/github-client-secret in the cluster overrides this.")
	buildScriptsRepo       = flag.String("build-scripts-repo", "https://github.com/ae6rt/decap-build-scripts.git", "Git repo where userland build scripts are held.")
	buildScriptsRepoBranch = flag.String("build-scripts-repo-branch", "master", "Branch or revision to use on git repo where userland build scripts are held.")
	noWebsocket            = flag.Bool("no-websocket", false, "Do not start websocket client that watches pods.")
	versionFlag            = flag.Bool("version", false, "Print version info and exit.")

	Log *log.Logger = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)

	buildVersion string
	buildCommit  string
	buildDate    string
	buildGoSDK   string
)

func init() {
	flag.Parse()
	Log.Printf("Version: %s, Commit: %s, Date: %s, Go SDK: %s\n", buildVersion, buildCommit, buildDate, buildGoSDK)
	if *versionFlag {
		os.Exit(0)
	}

	*awsKey = kubeSecret("/etc/secrets/aws-key", *awsKey)
	*awsSecret = kubeSecret("/etc/secrets/aws-secret", *awsSecret)
	*awsRegion = kubeSecret("/etc/secrets/aws-region", *awsRegion)
	*githubClientID = kubeSecret("/etc/secrets/github-client-id", *githubClientID)
	*githubClientSecret = kubeSecret("/etc/secrets/github-client-secret", *githubClientSecret)
}

func main() {
	locker := NewDefaultLock([]string{"http://localhost:2379"})
	buildLauncher := NewBuilder(*apiServerBaseURL, *apiServerUser, *apiServerPassword, *awsKey, *awsSecret, *awsRegion, locker, *buildScriptsRepo, *buildScriptsRepoBranch)
	awsStorageService := NewAWSStorageService(*awsKey, *awsSecret, *awsRegion)
	scmManagers := map[string]SCMClient{
		"github": NewGithubClient("https://api.github.com", *githubClientID, *githubClientSecret),
	}

	router := httprouter.New()
	router.ServeFiles("/decap/*filepath", http.Dir("./static"))
	router.GET("/api/v1/version", VersionHandler)
	router.GET("/api/v1/projects", ProjectsHandler)
	router.GET("/api/v1/projects/:team/:project/refs", ProjectRefsHandler(scmManagers))
	router.GET("/api/v1/builds/:team/:project", BuildsHandler(awsStorageService))
	router.DELETE("/api/v1/builds/:id", StopBuildHandler(buildLauncher))
	router.POST("/api/v1/builds/:team/:project", ExecuteBuildHandler(buildLauncher))
	router.GET("/api/v1/teams", TeamsHandler)
	router.GET("/api/v1/logs/:id", LogHandler(awsStorageService))
	router.GET("/api/v1/artifacts/:id", ArtifactsHandler(awsStorageService))
	router.GET("/api/v1/shutdown", ShutdownHandler)
	router.POST("/api/v1/shutdown/:state", ShutdownHandler)
	router.POST("/hooks/:repomanager", HooksHandler(*buildScriptsRepo, *buildScriptsRepoBranch, buildLauncher))
	router.OPTIONS("/api/v1/*filepath", HandleOptions)

	projects, err := assembleProjects(*buildScriptsRepo, *buildScriptsRepoBranch)
	if err != nil {
		Log.Printf("Cannot clone build scripts repository: %v\n", err)
	}

	go projectMux(projects)
	go buildLauncher.LaunchDeferred()
	go shutdownMux(OPEN)
	if !*noWebsocket {
		go buildLauncher.Websock()
	}

	Log.Println("decap ready on port 9090...")
	http.ListenAndServe(":9090", corsWrapper(router))
}
<commit_msg>rename variable for storage service in main()<commit_after>package main

import (
	"flag"
	"log"
	"net/http"
	"os"

	"github.com/julienschmidt/httprouter"
)

var (
	apiServerBaseURL       = flag.String("api-server-base-url", "https://kubernetes.default", "Kubernetes API server base URL")
	apiServerUser          = flag.String("api-server-username", "admin", "Kubernetes API server username to use if no service acccount API token is present.")
	apiServerPassword      = flag.String("api-server-password", "admin123", "Kubernetes API server password to use if no service acccount API token is present.")
	awsKey                 = flag.String("aws-access-key", "", "Default decap AWS access key.  /etc/secrets/aws-key in the cluster overrides this.")
	awsSecret              = flag.String("aws-secret-key", "", "Default decap AWS access secret.  /etc/secrets/aws-secret in the cluster overrides this.")
	awsRegion              = flag.String("aws-region", "us-west-1", "Default decap AWS region.  /etc/secrets/aws-region in the cluster overrides this.")
	githubClientID         = flag.String("github-client-id", "", "Default Github ClientID for quering Github repos.  /etc/secrets/github-client-id in the cluster overrides this.")
	githubClientSecret     = flag.String("github-client-secret", "", "Default Github Client Secret for quering Github repos.  /etc/secrets/github-client-secret in the cluster overrides this.")
	buildScriptsRepo       = flag.String("build-scripts-repo", "https://github.com/ae6rt/decap-build-scripts.git", "Git repo where userland build scripts are held.")
	buildScriptsRepoBranch = flag.String("build-scripts-repo-branch", "master", "Branch or revision to use on git repo where userland build scripts are held.")
	noWebsocket            = flag.Bool("no-websocket", false, "Do not start websocket client that watches pods.")
	versionFlag            = flag.Bool("version", false, "Print version info and exit.")

	Log *log.Logger = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)

	buildVersion string
	buildCommit  string
	buildDate    string
	buildGoSDK   string
)

func init() {
	flag.Parse()
	Log.Printf("Version: %s, Commit: %s, Date: %s, Go SDK: %s\n", buildVersion, buildCommit, buildDate, buildGoSDK)
	if *versionFlag {
		os.Exit(0)
	}

	*awsKey = kubeSecret("/etc/secrets/aws-key", *awsKey)
	*awsSecret = kubeSecret("/etc/secrets/aws-secret", *awsSecret)
	*awsRegion = kubeSecret("/etc/secrets/aws-region", *awsRegion)
	*githubClientID = kubeSecret("/etc/secrets/github-client-id", *githubClientID)
	*githubClientSecret = kubeSecret("/etc/secrets/github-client-secret", *githubClientSecret)
}

func main() {
	locker := NewDefaultLock([]string{"http://localhost:2379"})
	buildLauncher := NewBuilder(*apiServerBaseURL, *apiServerUser, *apiServerPassword, *awsKey, *awsSecret, *awsRegion, locker, *buildScriptsRepo, *buildScriptsRepoBranch)
	storageService := NewAWSStorageService(*awsKey, *awsSecret, *awsRegion)
	scmManagers := map[string]SCMClient{
		"github": NewGithubClient("https://api.github.com", *githubClientID, *githubClientSecret),
	}

	router := httprouter.New()
	router.ServeFiles("/decap/*filepath", http.Dir("./static"))
	router.GET("/api/v1/version", VersionHandler)
	router.GET("/api/v1/projects", ProjectsHandler)
	router.GET("/api/v1/projects/:team/:project/refs", ProjectRefsHandler(scmManagers))
	router.GET("/api/v1/builds/:team/:project", BuildsHandler(storageService))
	router.DELETE("/api/v1/builds/:id", StopBuildHandler(buildLauncher))
	router.POST("/api/v1/builds/:team/:project", ExecuteBuildHandler(buildLauncher))
	router.GET("/api/v1/teams", TeamsHandler)
	router.GET("/api/v1/logs/:id", LogHandler(storageService))
	router.GET("/api/v1/artifacts/:id", ArtifactsHandler(storageService))
	router.GET("/api/v1/shutdown", ShutdownHandler)
	router.POST("/api/v1/shutdown/:state", ShutdownHandler)
	router.POST("/hooks/:repomanager", HooksHandler(*buildScriptsRepo, *buildScriptsRepoBranch, buildLauncher))
	router.OPTIONS("/api/v1/*filepath", HandleOptions)

	projects, err := assembleProjects(*buildScriptsRepo, *buildScriptsRepoBranch)
	if err != nil {
		Log.Printf("Cannot clone build scripts repository: %v\n", err)
	}

	go projectMux(projects)
	go buildLauncher.LaunchDeferred()
	go shutdownMux(OPEN)
	if !*noWebsocket {
		go buildLauncher.Websock()
	}

	Log.Println("decap ready on port 9090...")
	http.ListenAndServe(":9090", corsWrapper(router))
}
<|endoftext|>