<commit_before>package main

import (
	"fmt"
	"log"
	"os"

	"gopkg.in/alecthomas/kingpin.v2"
)

// #cgo windows LDFLAGS: -Wl,--allow-multiple-definition -static
import "C"

var (
	version = "head" // set by command-line on CI release builds
	app     = kingpin.New("butler", "Your very own itch.io helper")

	dlCmd        = app.Command("dl", "Download a file (resumes if can, checks hashes)")
	pushCmd      = app.Command("push", "Upload a new version of something to itch.io")
	untarCmd     = app.Command("untar", "Extract a .tar file")
	wipeCmd      = app.Command("wipe", "Completely remove a directory (rm -rf)")
	dittoCmd     = app.Command("ditto", "Create a mirror (incl. symlinks) of a directory into another dir (rsync -az)")
	mkdirCmd     = app.Command("mkdir", "Create an empty directory and all required parent directories (mkdir -p)")
	megatestCmd  = app.Command("megatest", "Test megafile").Hidden()
	megadiffCmd  = app.Command("megadiff", "Generate a patch to turn 'target' into 'source'").Hidden()
	megapatchCmd = app.Command("megapatch", "Applies 'patch' to 'target' and writes it to 'output'").Hidden()
)

var appArgs = struct {
	json        *bool
	quiet       *bool
	verbose     *bool
	timestamps  *bool
	csv         *bool
	paranoid    *bool
	no_progress *bool
}{
	app.Flag("json", "Enable machine-readable JSON-lines output").Short('j').Bool(),
	app.Flag("quiet", "Hide progress indicators & other extra info").Short('q').Bool(),
	app.Flag("verbose", "Display as much extra info as possible").Short('v').Bool(),
	app.Flag("timestamps", "Prefix all output by timestamps (for logging purposes)").Bool(),
	app.Flag("csv", "Output stats in CSV format").Bool(),
	app.Flag("paranoid", "Insist on checking all available hashes, not just the fastest ones").Bool(),
	app.Flag("no-progress", "Doesn't show progress bars").Bool(),
}

var dlArgs = struct {
	url  *string
	dest *string
}{
	dlCmd.Arg("url", "Address to download from").Required().String(),
	dlCmd.Arg("dest", "File to write downloaded data to").Required().String(),
}

var pushArgs = struct {
	src      *string
	repo     *string
	identity *string
	address  *string
}{
	pushCmd.Arg("src", "Directory or zip archive to upload, e.g.").Required().ExistingFileOrDir(),
	pushCmd.Arg("repo", "Repository to push to, e.g. leafo/xmoon:win64").Required().String(),
	pushCmd.Flag("identity", "Path to the private key used for public key authentication.").Default(fmt.Sprintf("%s/%s", os.Getenv("HOME"), ".ssh/id_rsa")).Short('i').ExistingFile(),
	pushCmd.Flag("address", "Specify wharf address (advanced)").Default("wharf.itch.zone").Short('a').Hidden().String(),
}

var untarArgs = struct {
	file *string
	dir  *string
}{
	untarCmd.Arg("file", "Path of the .tar archive to extract").Required().String(),
	untarCmd.Flag("dir", "An optional directory to which to extract files (defaults to CWD)").Default(".").Short('d').String(),
}

var wipeArgs = struct {
	path *string
}{
	wipeCmd.Arg("path", "Path to completely remove, including its contents").Required().String(),
}

var mkdirArgs = struct {
	path *string
}{
	mkdirCmd.Arg("path", "Directory to create").Required().String(),
}

var dittoArgs = struct {
	src *string
	dst *string
}{
	dittoCmd.Arg("src", "Directory to mirror").Required().String(),
	dittoCmd.Arg("dst", "Path where to create a mirror").Required().String(),
}

var megatestArgs = struct {
	src *string
}{
	megatestCmd.Arg("src", "Directory to walk").Required().String(),
}

var megadiffArgs = struct {
	target  *string
	source  *string
	patch   *string
	verify  *bool
	quality *int
}{
	megadiffCmd.Arg("target", "Directory with older files").Required().String(),
	megadiffCmd.Arg("source", "Directory with newer files").Required().String(),
	megadiffCmd.Arg("patch", "Where to write the patch file").Default("patch.dat").String(),
	megadiffCmd.Flag("verify", "Verify that patch applies cleanly").Bool(),
	megadiffCmd.Flag("quality", "Brotli quality level").Hidden().Default("1").Int(),
}

var megapatchArgs = struct {
	patch  *string
	target *string
	output *string
}{
	megapatchCmd.Arg("patch", "Patch file").Required().String(),
	megapatchCmd.Arg("target", "Directory with older files").Required().String(),
	megapatchCmd.Arg("output", "Path to create directory with newer files").Required().String(),
}

func must(err error) {
	if err != nil {
		Die(err.Error())
	}
}

func main() {
	app.HelpFlag.Short('h')
	app.Version(version)
	app.VersionFlag.Short('V')

	cmd, err := app.Parse(os.Args[1:])
	if *appArgs.timestamps {
		log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
	} else {
		log.SetFlags(0)
	}

	if !*appArgs.quiet {
		*appArgs.no_progress = true
	}

	if *appArgs.csv {
		defer CsvFinish()
	}

	switch kingpin.MustParse(cmd, err) {
	case dlCmd.FullCommand():
		dl(*dlArgs.url, *dlArgs.dest)

	case pushCmd.FullCommand():
		push(*pushArgs.src, *pushArgs.repo)

	case untarCmd.FullCommand():
		untar(*untarArgs.file, *untarArgs.dir)

	case wipeCmd.FullCommand():
		wipe(*wipeArgs.path)

	case mkdirCmd.FullCommand():
		mkdir(*mkdirArgs.path)

	case dittoCmd.FullCommand():
		ditto(*dittoArgs.src, *dittoArgs.dst)

	case megatestCmd.FullCommand():
		megatest(*megatestArgs.src)

	case megadiffCmd.FullCommand():
		megadiff(*megadiffArgs.target, *megadiffArgs.source, *megadiffArgs.patch, *megadiffArgs.quality)

	case megapatchCmd.FullCommand():
		megapatch(*megapatchArgs.patch, *megapatchArgs.target, *megapatchArgs.output)
	}
}
<commit_msg>:bug: quiet option should disable progress bars<commit_after>package main

import (
	"fmt"
	"log"
	"os"

	"gopkg.in/alecthomas/kingpin.v2"
)

// #cgo windows LDFLAGS: -Wl,--allow-multiple-definition -static
import "C"

var (
	version = "head" // set by command-line on CI release builds
	app     = kingpin.New("butler", "Your very own itch.io helper")

	dlCmd        = app.Command("dl", "Download a file (resumes if can, checks hashes)")
	pushCmd      = app.Command("push", "Upload a new version of something to itch.io")
	untarCmd     = app.Command("untar", "Extract a .tar file")
	wipeCmd      = app.Command("wipe", "Completely remove a directory (rm -rf)")
	dittoCmd     = app.Command("ditto", "Create a mirror (incl. symlinks) of a directory into another dir (rsync -az)")
	mkdirCmd     = app.Command("mkdir", "Create an empty directory and all required parent directories (mkdir -p)")
	megatestCmd  = app.Command("megatest", "Test megafile").Hidden()
	megadiffCmd  = app.Command("megadiff", "Generate a patch to turn 'target' into 'source'").Hidden()
	megapatchCmd = app.Command("megapatch", "Applies 'patch' to 'target' and writes it to 'output'").Hidden()
)

var appArgs = struct {
	json        *bool
	quiet       *bool
	verbose     *bool
	timestamps  *bool
	csv         *bool
	paranoid    *bool
	no_progress *bool
}{
	app.Flag("json", "Enable machine-readable JSON-lines output").Short('j').Bool(),
	app.Flag("quiet", "Hide progress indicators & other extra info").Short('q').Bool(),
	app.Flag("verbose", "Display as much extra info as possible").Short('v').Bool(),
	app.Flag("timestamps", "Prefix all output by timestamps (for logging purposes)").Bool(),
	app.Flag("csv", "Output stats in CSV format").Bool(),
	app.Flag("paranoid", "Insist on checking all available hashes, not just the fastest ones").Bool(),
	app.Flag("no-progress", "Doesn't show progress bars").Bool(),
}

var dlArgs = struct {
	url  *string
	dest *string
}{
	dlCmd.Arg("url", "Address to download from").Required().String(),
	dlCmd.Arg("dest", "File to write downloaded data to").Required().String(),
}

var pushArgs = struct {
	src      *string
	repo     *string
	identity *string
	address  *string
}{
	pushCmd.Arg("src", "Directory or zip archive to upload, e.g.").Required().ExistingFileOrDir(),
	pushCmd.Arg("repo", "Repository to push to, e.g. leafo/xmoon:win64").Required().String(),
	pushCmd.Flag("identity", "Path to the private key used for public key authentication.").Default(fmt.Sprintf("%s/%s", os.Getenv("HOME"), ".ssh/id_rsa")).Short('i').ExistingFile(),
	pushCmd.Flag("address", "Specify wharf address (advanced)").Default("wharf.itch.zone").Short('a').Hidden().String(),
}

var untarArgs = struct {
	file *string
	dir  *string
}{
	untarCmd.Arg("file", "Path of the .tar archive to extract").Required().String(),
	untarCmd.Flag("dir", "An optional directory to which to extract files (defaults to CWD)").Default(".").Short('d').String(),
}

var wipeArgs = struct {
	path *string
}{
	wipeCmd.Arg("path", "Path to completely remove, including its contents").Required().String(),
}

var mkdirArgs = struct {
	path *string
}{
	mkdirCmd.Arg("path", "Directory to create").Required().String(),
}

var dittoArgs = struct {
	src *string
	dst *string
}{
	dittoCmd.Arg("src", "Directory to mirror").Required().String(),
	dittoCmd.Arg("dst", "Path where to create a mirror").Required().String(),
}

var megatestArgs = struct {
	src *string
}{
	megatestCmd.Arg("src", "Directory to walk").Required().String(),
}

var megadiffArgs = struct {
	target  *string
	source  *string
	patch   *string
	verify  *bool
	quality *int
}{
	megadiffCmd.Arg("target", "Directory with older files").Required().String(),
	megadiffCmd.Arg("source", "Directory with newer files").Required().String(),
	megadiffCmd.Arg("patch", "Where to write the patch file").Default("patch.dat").String(),
	megadiffCmd.Flag("verify", "Verify that patch applies cleanly").Bool(),
	megadiffCmd.Flag("quality", "Brotli quality level").Hidden().Default("1").Int(),
}

var megapatchArgs = struct {
	patch  *string
	target *string
	output *string
}{
	megapatchCmd.Arg("patch", "Patch file").Required().String(),
	megapatchCmd.Arg("target", "Directory with older files").Required().String(),
	megapatchCmd.Arg("output", "Path to create directory with newer files").Required().String(),
}

func must(err error) {
	if err != nil {
		Die(err.Error())
	}
}

func main() {
	app.HelpFlag.Short('h')
	app.Version(version)
	app.VersionFlag.Short('V')

	cmd, err := app.Parse(os.Args[1:])
	if *appArgs.timestamps {
		log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
	} else {
		log.SetFlags(0)
	}

	if *appArgs.quiet {
		*appArgs.no_progress = true
	}

	if *appArgs.csv {
		defer CsvFinish()
	}

	switch kingpin.MustParse(cmd, err) {
	case dlCmd.FullCommand():
		dl(*dlArgs.url, *dlArgs.dest)

	case pushCmd.FullCommand():
		push(*pushArgs.src, *pushArgs.repo)

	case untarCmd.FullCommand():
		untar(*untarArgs.file, *untarArgs.dir)

	case wipeCmd.FullCommand():
		wipe(*wipeArgs.path)

	case mkdirCmd.FullCommand():
		mkdir(*mkdirArgs.path)

	case dittoCmd.FullCommand():
		ditto(*dittoArgs.src, *dittoArgs.dst)

	case megatestCmd.FullCommand():
		megatest(*megatestArgs.src)

	case megadiffCmd.FullCommand():
		megadiff(*megadiffArgs.target, *megadiffArgs.source, *megadiffArgs.patch, *megadiffArgs.quality)

	case megapatchCmd.FullCommand():
		megapatch(*megapatchArgs.patch, *megapatchArgs.target, *megapatchArgs.output)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/julienschmidt/httprouter"
)

var (
	songs_root = "./songs"
	books_root = "./books"
)

func main() {

	//Follow symlinks if applicable
	link, _ := os.Readlink(songs_root)
	if link != "" {
		songs_root = link
	}
	link, _ = os.Readlink(books_root)
	if link != "" {
		books_root = link
	}

	//basic sanity check
	_, err := os.Stat(songs_root)
	if os.IsNotExist(err) {
		fmt.Printf("Error: Songs path (%s) does not exist.\n", songs_root)
		return
	}

	_, err = os.Stat(books_root)
	if os.IsNotExist(err) {
		fmt.Printf("Error: Books path (%s) does not exist.\n", books_root)
		return
	}

	filepath.Walk(songs_root, loadSongs)
	filepath.Walk(books_root, loadBooks)

	fmt.Printf("%d Songs loaded.\n", len(loadedSongs))
	fmt.Printf("%d Books loaded.\n", len(loadedBooks))

	//Sort lists by title
	sort.Sort(ByTitle(loadedSongs))
	sort.Sort(ByTitle(loadedBooks))

	r := httprouter.New()

	r.GET("/", indexHandler)
	r.GET("/index.php", indexHandler)
	r.GET("/index.html", indexHandler)
	r.GET("/song/:song", songHandler)
	r.GET("/pdf/song/:song", songPdfHandler)
	r.GET("/pdf/book/:book", bookPdfHandler)
	r.GET("/book/:book/index", bookIndexHandler)
	r.GET("/book/:book/song/:number", bookHandler)
	r.ServeFiles("/css/*filepath", http.Dir("css"))
	r.ServeFiles("/js/*filepath", http.Dir("js"))

	log.Fatal(http.ListenAndServe(":8090", r))

}

type DisplayList struct {
	Link  string
	Title string
}

// ByTitle implements sort.Interface for []DisplayList based on
// the Title field.
type ByTitle []DisplayList

func (a ByTitle) Len() int      { return len(a) }
func (a ByTitle) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByTitle) Less(i, j int) bool {
	num_regex := regexp.MustCompile("^[0-9]")

	i_pos := num_regex.FindAllStringIndex(a[i].Title, 1)
	j_pos := num_regex.FindAllStringIndex(a[j].Title, 1)

	i_num := (len(i_pos) > 0)
	j_num := (len(j_pos) > 0)

	//neither title begins with a number,
	//or both begin with a number, normal sort
	if i_num == j_num {
		return a[i].Title < a[j].Title
	}

	//I is a number, put it at the end
	if i_num {
		return false
	}

	return true
}

func (song DisplayList) MatchTitle(title string) bool {
	return song.Title == title
}

func (song Song) MatchNumber(num int) bool {
	return song.SongNumber == num
}

type IndexPage struct {
	Title        string
	Recent       []DisplayList
	Songs        []DisplayList
	Books        []DisplayList
	ShowIndigo   bool
	SelectedSong string
	SelectedBook string
	Error        string
}

func (i IndexPage) HasSong() bool {
	return len(i.SelectedSong) > 0
}

func (i IndexPage) HasBook() bool {
	return len(i.SelectedBook) > 0
}

type SongPage struct {
	Song     Song
	Songbook Songbook
	NextSong int
	PrevSong int
	Selected int
	IndexPage
}

type BookPage struct {
	Songbook Songbook
	Selected int
	IndexPage
}

var loadedSongs = make([]DisplayList, 0)
var loadedBooks = make([]DisplayList, 0)
var recent = make([]DisplayList, 0)
var last_err = ""

// indexHandler is an HTTP handler that serves the index page.
func indexHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	t, err := template.ParseFiles("templates/index.tmpl", "templates/_song_select.tmpl", "templates/_book_select.tmpl")
	if err != nil {
		panic(err)
	}

	data := getBasicIndexData()
	data.ShowIndigo = true

	if err := t.ExecuteTemplate(w, "index.tmpl", data); err != nil {
		log.Println(err)
	}
}

func loadSongFile(title string, transpose int) (*Song, error) {
	filename := songs_root + "/" + title + ".song"

	song, err := ParseSongFile(filename, transpose)

	if err != nil {
		return nil, err
	}

	return song, nil
}

func getBasicIndexData() IndexPage {
	index_data := IndexPage{
		Title:        "Indigo Song Book",
		Recent:       recent,
		Songs:        loadedSongs,
		Books:        loadedBooks,
		ShowIndigo:   false,
		SelectedSong: "",
		SelectedBook: "",
		Error:        last_err}

	last_err = ""

	return index_data
}

func bookIndexHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		fmt.Println(err)
	}

	index := getBasicIndexData()
	index.SelectedBook = sbook.Title

	book_data := &BookPage{
		Songbook:  *sbook,
		IndexPage: index}

	temp, err := template.ParseFiles(
		"templates/index.tmpl",
		"templates/_book_navigation.tmpl",
		"templates/book_index.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "book_index.tmpl", book_data); err != nil {
		log.Println(err)
	}
}

func bookHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	num := p.ByName("number")
	n, _ := strconv.Atoi(num)

	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		fmt.Println(err)
		http.NotFound(w, r)
	}

	keys := GetSongOrder(sbook)

	prev := n
	next := n
	for _, i := range keys {
		if i < n {
			prev = i
		}

		if i > n {
			next = i
			break
		}
	}

	if next == n {
		next = 0
	}

	if prev == n {
		prev = 0
	}

	song, ok := sbook.Songs[n]

	//Number does not exist in songbook
	if !ok {
		index := httprouter.CleanPath(r.URL.String() + "/../../index")
		last_err = "Song '" + strconv.Itoa(n) + "' not found in songbook."
		http.Redirect(w, r, index, 302)
		return
	}
	song.Transpose = t

	index := getBasicIndexData()
	index.SelectedSong = song.Title
	index.SelectedBook = sbook.Title

	updateRecent(song.Link(), song.Title)

	page_data := &SongPage{
		Song:      song,
		Songbook:  *sbook,
		PrevSong:  prev,
		NextSong:  next,
		Selected:  song.SongNumber,
		IndexPage: index}

	temp, err := template.ParseFiles(
		"templates/index.tmpl",
		"templates/_song_select.tmpl",
		"templates/_book_select.tmpl",
		"templates/_display_song.tmpl",
		"templates/_book_navigation.tmpl",
		"templates/book_song.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "book_song.tmpl", page_data); err != nil {
		log.Println(err)
	}
}

func updateRecent(link string, title string) {
	//if the song is already in the list, move it to the top
	for i, dl := range recent {
		if dl.Title == title {
			//remove element
			recent = append(recent[:i], recent[i+1:]...)
			break
		}
	}

	recent = append([]DisplayList{DisplayList{Link: link, Title: title}}, recent...)
	if len(recent) > 5 {
		recent = recent[0:5]
	}
}

func songHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	data, err := loadSongFile(p.ByName("song"), t)
	if err != nil {
		log.Println(err)

		home := httprouter.CleanPath(r.URL.String() + "/../..")
		last_err = "Song '" + p.ByName("song") + "' not found."
		http.Redirect(w, r, home, 302)
		return
	}

	updateRecent(p.ByName("song"), data.Title)

	index := getBasicIndexData()
	index.SelectedSong = data.Title

	page_data := &SongPage{
		Song:      *data,
		IndexPage: index}

	temp, err := template.ParseGlob("templates/*.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "song.tmpl", page_data); err != nil {
		log.Println(err)
	}
}

func songPdfHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	song, err := loadSongFile(p.ByName("song"), t)
	if err != nil {
		log.Println(err)
		http.NotFound(w, r)
		return
	}

	pdf, err := WriteSongPDF(song)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// stream straight to client(browser)
	w.Header().Set("Content-type", "application/pdf")

	if _, err := pdf.WriteTo(w); err != nil {
		fmt.Fprintf(w, "%s", err)
	}

	w.Write([]byte("PDF Generated"))
}

func bookPdfHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		log.Println(err)
		http.NotFound(w, r)
		return
	}

	pdf, err := WriteBookPDF(sbook)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if pdf == nil {
		return
	}

	// stream straight to client(browser)
	w.Header().Set("Content-type", "application/pdf")

	if _, err := pdf.WriteTo(w); err != nil {
		fmt.Fprintf(w, "%s", err)
	}

	w.Write([]byte("PDF Generated"))
}

func loadSongs(path string, f os.FileInfo, err error) error {
	if f.IsDir() {
		return nil
	}

	if strings.HasSuffix(strings.ToLower(f.Name()), ".song") {
		song, err := ParseSongFile(songs_root+"/"+f.Name(), 0)
		link := song.Filename[0 : len(song.Filename)-len(".song")]
		loadedSongs = append(loadedSongs, DisplayList{Link: link, Title: song.Title})

		return err
	}

	return nil
}

func loadBooks(path string, f os.FileInfo, err error) error {
	if f.IsDir() {
		return nil
	}

	if strings.HasSuffix(strings.ToLower(f.Name()), ".songlist") {
		book, err := ParseSongbookFile(books_root+"/"+f.Name(), songs_root)
		link := book.Filename[0:len(book.Filename)-len(".songlist")] + "/index"
		loadedBooks = append(loadedBooks, DisplayList{Link: link, Title: book.Title})

		return err
	}

	return nil
}

// readLines reads a whole file into memory
// and returns a slice of its lines.
func readLines(path string) ([]string, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var lines []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	return lines, scanner.Err()
}
<commit_msg>Load songs and books using ioutil.ReadDir instead of filepath.Walk to avoid subdirs<commit_after>package main

import (
	"bufio"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"io/ioutil"

	"github.com/julienschmidt/httprouter"
)

var (
	songs_root = "./songs"
	books_root = "./books"
)

func main() {

	//Follow symlinks if applicable
	link, _ := os.Readlink(songs_root)
	if link != "" {
		songs_root = link
	}
	link, _ = os.Readlink(books_root)
	if link != "" {
		books_root = link
	}

	//basic sanity check
	_, err := os.Stat(songs_root)
	if os.IsNotExist(err) {
		fmt.Printf("Error: Songs path (%s) does not exist.\n", songs_root)
		return
	}

	_, err = os.Stat(books_root)
	if os.IsNotExist(err) {
		fmt.Printf("Error: Books path (%s) does not exist.\n", books_root)
		return
	}

	//Load songs and books
	err = loadSongs(songs_root)
	if err != nil {
		fmt.Println("Error loading songs")
		fmt.Println(err)
		return
	}

	err = loadBooks(books_root)
	if err != nil {
		fmt.Println("Error loading books")
		fmt.Println(err)
		return
	}

	fmt.Printf("%d Songs loaded.\n", len(loadedSongs))
	fmt.Printf("%d Books loaded.\n", len(loadedBooks))

	//Sort lists by title
	sort.Sort(ByTitle(loadedSongs))
	sort.Sort(ByTitle(loadedBooks))

	r := httprouter.New()

	r.GET("/", indexHandler)
	r.GET("/index.php", indexHandler)
	r.GET("/index.html", indexHandler)
	r.GET("/song/:song", songHandler)
	r.GET("/pdf/song/:song", songPdfHandler)
	r.GET("/pdf/book/:book", bookPdfHandler)
	r.GET("/book/:book/index", bookIndexHandler)
	r.GET("/book/:book/song/:number", bookHandler)
	r.ServeFiles("/css/*filepath", http.Dir("css"))
	r.ServeFiles("/js/*filepath", http.Dir("js"))

	log.Fatal(http.ListenAndServe(":8090", r))

}

type DisplayList struct {
	Link  string
	Title string
}

// ByTitle implements sort.Interface for []DisplayList based on
// the Title field.
type ByTitle []DisplayList

func (a ByTitle) Len() int      { return len(a) }
func (a ByTitle) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByTitle) Less(i, j int) bool {
	num_regex := regexp.MustCompile("^[0-9]")

	i_pos := num_regex.FindAllStringIndex(a[i].Title, 1)
	j_pos := num_regex.FindAllStringIndex(a[j].Title, 1)

	i_num := (len(i_pos) > 0)
	j_num := (len(j_pos) > 0)

	//neither title begins with a number,
	//or both begin with a number, normal sort
	if i_num == j_num {
		return a[i].Title < a[j].Title
	}

	//I is a number, put it at the end
	if i_num {
		return false
	}

	return true
}

func (song DisplayList) MatchTitle(title string) bool {
	return song.Title == title
}

func (song Song) MatchNumber(num int) bool {
	return song.SongNumber == num
}

type IndexPage struct {
	Title        string
	Recent       []DisplayList
	Songs        []DisplayList
	Books        []DisplayList
	ShowIndigo   bool
	SelectedSong string
	SelectedBook string
	Error        string
}

func (i IndexPage) HasSong() bool {
	return len(i.SelectedSong) > 0
}

func (i IndexPage) HasBook() bool {
	return len(i.SelectedBook) > 0
}

type SongPage struct {
	Song     Song
	Songbook Songbook
	NextSong int
	PrevSong int
	Selected int
	IndexPage
}

type BookPage struct {
	Songbook Songbook
	Selected int
	IndexPage
}

var loadedSongs = make([]DisplayList, 0)
var loadedBooks = make([]DisplayList, 0)
var recent = make([]DisplayList, 0)
var last_err = ""

// indexHandler is an HTTP handler that serves the index page.
func indexHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	t, err := template.ParseFiles("templates/index.tmpl", "templates/_song_select.tmpl", "templates/_book_select.tmpl")
	if err != nil {
		panic(err)
	}

	data := getBasicIndexData()
	data.ShowIndigo = true

	if err := t.ExecuteTemplate(w, "index.tmpl", data); err != nil {
		log.Println(err)
	}
}

func loadSongFile(title string, transpose int) (*Song, error) {
	filename := songs_root + "/" + title + ".song"

	song, err := ParseSongFile(filename, transpose)

	if err != nil {
		return nil, err
	}

	return song, nil
}

func getBasicIndexData() IndexPage {
	index_data := IndexPage{
		Title:        "Indigo Song Book",
		Recent:       recent,
		Songs:        loadedSongs,
		Books:        loadedBooks,
		ShowIndigo:   false,
		SelectedSong: "",
		SelectedBook: "",
		Error:        last_err}

	last_err = ""

	return index_data
}

func bookIndexHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		fmt.Println(err)
	}

	index := getBasicIndexData()
	index.SelectedBook = sbook.Title

	book_data := &BookPage{
		Songbook:  *sbook,
		IndexPage: index}

	temp, err := template.ParseFiles(
		"templates/index.tmpl",
		"templates/_book_navigation.tmpl",
		"templates/book_index.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "book_index.tmpl", book_data); err != nil {
		log.Println(err)
	}
}

func bookHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	num := p.ByName("number")
	n, _ := strconv.Atoi(num)

	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		fmt.Println(err)
		http.NotFound(w, r)
	}

	keys := GetSongOrder(sbook)

	prev := n
	next := n
	for _, i := range keys {
		if i < n {
			prev = i
		}

		if i > n {
			next = i
			break
		}
	}

	if next == n {
		next = 0
	}

	if prev == n {
		prev = 0
	}

	song, ok := sbook.Songs[n]

	//Number does not exist in songbook
	if !ok {
		index := httprouter.CleanPath(r.URL.String() + "/../../index")
		last_err = "Song '" + strconv.Itoa(n) + "' not found in songbook."
		http.Redirect(w, r, index, 302)
		return
	}
	song.Transpose = t

	index := getBasicIndexData()
	index.SelectedSong = song.Title
	index.SelectedBook = sbook.Title

	updateRecent(song.Link(), song.Title)

	page_data := &SongPage{
		Song:      song,
		Songbook:  *sbook,
		PrevSong:  prev,
		NextSong:  next,
		Selected:  song.SongNumber,
		IndexPage: index}

	temp, err := template.ParseFiles(
		"templates/index.tmpl",
		"templates/_song_select.tmpl",
		"templates/_book_select.tmpl",
		"templates/_display_song.tmpl",
		"templates/_book_navigation.tmpl",
		"templates/book_song.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "book_song.tmpl", page_data); err != nil {
		log.Println(err)
	}
}

func updateRecent(link string, title string) {
	//if the song is already in the list, move it to the top
	for i, dl := range recent {
		if dl.Title == title {
			//remove element
			recent = append(recent[:i], recent[i+1:]...)
			break
		}
	}

	recent = append([]DisplayList{DisplayList{Link: link, Title: title}}, recent...)
	if len(recent) > 5 {
		recent = recent[0:5]
	}
}

func songHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	data, err := loadSongFile(p.ByName("song"), t)
	if err != nil {
		log.Println(err)

		home := httprouter.CleanPath(r.URL.String() + "/../..")
		last_err = "Song '" + p.ByName("song") + "' not found."
		http.Redirect(w, r, home, 302)
		return
	}

	updateRecent(p.ByName("song"), data.Title)

	index := getBasicIndexData()
	index.SelectedSong = data.Title

	page_data := &SongPage{
		Song:      *data,
		IndexPage: index}

	temp, err := template.ParseGlob("templates/*.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "song.tmpl", page_data); err != nil {
		log.Println(err)
	}
}

func songPdfHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	song, err := loadSongFile(p.ByName("song"), t)
	if err != nil {
		log.Println(err)
		http.NotFound(w, r)
		return
	}

	pdf, err := WriteSongPDF(song)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// stream straight to client(browser)
	w.Header().Set("Content-type", "application/pdf")

	if _, err := pdf.WriteTo(w); err != nil {
		fmt.Fprintf(w, "%s", err)
	}

	w.Write([]byte("PDF Generated"))
}

func bookPdfHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		log.Println(err)
		http.NotFound(w, r)
		return
	}

	pdf, err := WriteBookPDF(sbook)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if pdf == nil {
		return
	}

	// stream straight to client(browser)
	w.Header().Set("Content-type", "application/pdf")

	if _, err := pdf.WriteTo(w); err != nil {
		fmt.Fprintf(w, "%s", err)
	}

	w.Write([]byte("PDF Generated"))
}

func loadSongs(path string) error {
	song_files, err := ioutil.ReadDir(songs_root)
	if err != nil {
		return err
	}

	for _, f := range song_files {
		if !f.IsDir() && strings.HasSuffix(strings.ToLower(f.Name()), ".song") {
			song, err := ParseSongFile(songs_root+"/"+f.Name(), 0)
			if err != nil {
				return err
			}

			link := song.Filename[0 : len(song.Filename)-len(".song")]
			loadedSongs = append(loadedSongs, DisplayList{Link: link, Title: song.Title})
		}
	}

	return err
}

func loadBooks(path string) error {
	book_files, err := ioutil.ReadDir(books_root)
	if err != nil {
		return err
	}

	for _, f := range book_files {
		if !f.IsDir() && strings.HasSuffix(strings.ToLower(f.Name()), ".songlist") {
			book, err := ParseSongbookFile(books_root+"/"+f.Name(), songs_root)
			if err != nil {
				return err
			}
			link := book.Filename[0:len(book.Filename)-len(".songlist")] + "/index"
			loadedBooks = append(loadedBooks, DisplayList{Link: link, Title: book.Title})
		}
	}

	return err
}

// readLines reads a whole file into memory
// and returns a slice of its lines.
func readLines(path string) ([]string, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var lines []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	return lines, scanner.Err()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

// Input holds the user settable values.
type Input struct {
	Dir   string
	Dry   bool
	Force bool
}

// Link is a single symlink. A source and destination are required
type Link struct {
	Src  string
	Dest string
}

func (l Link) String() string {
	return fmt.Sprintf("%v -> %v", l.Src, l.Dest)
}

func (l Link) cmd(force bool) string {
	if force {
		return fmt.Sprintf("ls -sf %v %v", l.Src, l.Dest)
	}
	return fmt.Sprintf("ls -s %v %v", l.Src, l.Dest)
}

// Clean replaces the environment variables in the source and destination paths with the values.
func (l *Link) Clean() {
	l.Src = cleanPath(l.Src)
	l.Dest = cleanPath(l.Dest)
}

func cleanPath(path string) string {
	path = filepath.Clean(path)
	hasLeadingSlash := strings.HasPrefix(path, "/")
	var cleanPath []string
	for _, e := range strings.Split(path, "/") {
		if strings.HasPrefix(e, "$") {
			e = os.Getenv(strings.TrimPrefix(e, "$"))
		}
		cleanPath = append(cleanPath, e)
	}
	path = filepath.Join(cleanPath...)
	if hasLeadingSlash {
		path = "/" + path
	}
	return path
}

// Symlink creates a symlink using the Src and Dest. Dest will be removed if force is set.
func (l *Link) Symlink(force bool) error {
	if force {
		err := os.Remove(l.Dest)
		if err != nil {
			return err
		}
	}
	return os.Symlink(l.Src, l.Dest)
}

// DotDir is a directory containing a links file. The paths in the links file, if not absolute, will be relative to the Path attribute.
type DotDir struct {
	Path     string
	LinkFile string
}

// Links parses a list of links from the links file. The found links will be cleaned and returned. An error will be returned if parsing the links file fails.
func (d DotDir) Links() (links []Link, err error) {
	f, err := os.Open(d.LinkFile)
	if err != nil {
		log.Printf("Error openeing link file %v: %v", d.LinkFile, err)
		return nil, err
	}
	defer f.Close()

	var m map[string]string
	err = json.NewDecoder(f).Decode(&m)
	if err != nil {
		log.Printf("Error parsing link file %v: %v", d.LinkFile, err)
		return nil, err
	}
	for src, dest := range m {
		src = filepath.Join(d.Path, src)
		link := Link{Src: src, Dest: dest}
		link.Clean()
		links = append(links, link)
	}
	return
}

// Bootstrap manages a list of files that need to be symlinked.
type Bootstrap struct {
	DotDirs []DotDir
}

// AddDir adds a DotDir to the DotDirs given the directory path and path to the links file.
func (b *Bootstrap) AddDir(dir, links string) {
	b.DotDirs = append(b.DotDirs, DotDir{
		Path:     dir,
		LinkFile: links,
	})
}

// Walk traverses the specified directory. Any directories found containing a links file will be added to the DotDirs attribute. An error will be returned if the walking fails.
func (b *Bootstrap) Walk(dir string) error {
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		// Check for link file
		if info.Name() == LinkFile {
			d, _ := filepath.Split(path)
			b.AddDir(d, path)
		}
		return nil
	})
	if err != nil && err != io.EOF {
		return err
	}
	return nil
}

// Link adds the links from each of the DotDirs to the links chan. If an error occurs while getting a DotDirs links, the error will be added to the errors chan.
func (b *Bootstrap) Link(links chan Link, errors chan error) {
	toLinks := func(l Link) {
		if links != nil {
			links <- l
		}
	}
	toErrors := func(e error) {
		if errors != nil {
			errors <- e
		}
	}
	wg := &sync.WaitGroup{}
	for _, dotDir := range b.DotDirs {
		wg.Add(1)
		go func(dotDir DotDir) {
			defer wg.Done()
			links, err := dotDir.Links()
			if err != nil {
				toErrors(err)
			}
			for _, link := range links {
				toLinks(link)
			}
		}(dotDir)
	}
	wg.Wait()
}

// DotEnv is the name of the environment variable signifying the location of the dotfiles needing bootstrapping.
const DotEnv = "DOT"

// LinkFile is the name the file describing symlinks relative to the current directory.
const LinkFile = "links.json"

func main() {
	i := Input{
		Dir:   os.Getenv(DotEnv),
		Dry:   false,
		Force: false,
	}
	if i.Dir == "" {
		i.Dir = "../"
	}
	flag.StringVar(&i.Dir, "dir", i.Dir, "The dotfiles source.")
	flag.BoolVar(&i.Dry, "dry", i.Dry, "Only print out the changes.")
	flag.BoolVar(&i.Force, "force", i.Force, "Overwrite existing links.")
	flag.Parse()

	dir, err := filepath.Abs(i.Dir)
	if err != nil {
		log.Fatal(err)
	}

	// Create and populate the Bootstrap DotDirs
	b := &Bootstrap{}
	err = b.Walk(dir)
	if err != nil {
		log.Fatal(err)
	}

	// Create the needed chans
	links := make(chan Link)
	errors := make(chan error)

	wg := new(sync.WaitGroup)
	wg.Add(1) // Add 1 for the single go routine listening on the above chans
	messages := map[string][]string{}

	// Spawn a go routine to create the desired links
	go func(messages map[string][]string) {
		defer wg.Done()
		var linksDone, errorsDone bool
		for !linksDone || !errorsDone {
			select {
			case link, ok := <-links:
				if !ok {
					// The links chan has been closed.
					linksDone = true
					continue
				}

				if i.Dry {
					// Add the ln commands to the messages map.
					a := messages["Commands"]
					messages["Commands"] = append(a, link.cmd(i.Force))
					continue
				}

				// Write the symlink. Use the user specified force flag.
				err := link.Symlink(i.Force)
				if err != nil {
					if lerr, ok := err.(*os.LinkError); ok {
						// Grab the err causing the LinkError
						err = lerr.Err
					}
					// Add the Symlink error to the messages map.
					a := messages["Failures"]
					messages["Failures"] = append(a, fmt.Sprintf("%v: %v", err, link))
					continue
				}
				// Add the newly created Link string to the messages map.
				a := messages["Successes"]
				messages["Successes"] = append(a, link.String())
			case err, ok := <-errors:
				if !ok {
					// The errors chan has been closed
					errorsDone = true
					continue
				}
				// Add the bootstrap error to the messages map.
				a := messages["Errors"]
				messages["Errors"] = append(a, err.Error())
			}
		}
	}(messages)

	// Kick off the links method.
	b.Link(links, errors)

	// Links only returns once all the links or errors
	// have been added to the respective chan.We can
	// safley close the links and errors chans.
	close(links)
	close(errors)
	// Wait for all the symlinks to be created.
	wg.Wait()
	// Print out all the messages
	for header, msgs := range messages {
		if len(messages) > 1 {
			fmt.Println(header + ":")
		}
		fmt.Println(strings.Join(msgs, "\n"))
	}
	if len(messages) > 0 {
		fmt.Println("Changes will take effect after sourcing your .*shrc")
	}
}
<commit_msg>Added a short flags for each long flag and a custom usage.<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

// Input holds the user settable values.
type Input struct {
	Dir   string
	Dry   bool
	Force bool
}

// Link is a single symlink. A source and destination are required
type Link struct {
	Src  string
	Dest string
}

func (l Link) String() string {
	return fmt.Sprintf("%v -> %v", l.Src, l.Dest)
}

func (l Link) cmd(force bool) string {
	if force {
		return fmt.Sprintf("ls -sf %v %v", l.Src, l.Dest)
	}
	return fmt.Sprintf("ls -s %v %v", l.Src, l.Dest)
}

// Clean replaces the environment variables in the source and destination paths with the values.
func (l *Link) Clean() {
	l.Src = cleanPath(l.Src)
	l.Dest = cleanPath(l.Dest)
}

func cleanPath(path string) string {
	path = filepath.Clean(path)
	hasLeadingSlash := strings.HasPrefix(path, "/")
	var cleanPath []string
	for _, e := range strings.Split(path, "/") {
		if strings.HasPrefix(e, "$") {
			e = os.Getenv(strings.TrimPrefix(e, "$"))
		}
		cleanPath = append(cleanPath, e)
	}
	path = filepath.Join(cleanPath...)
	if hasLeadingSlash {
		path = "/" + path
	}
	return path
}

// Symlink creates a symlink using the Src and Dest. Dest will be removed if force is set.
func (l *Link) Symlink(force bool) error {
	if force {
		err := os.Remove(l.Dest)
		if err != nil {
			return err
		}
	}
	return os.Symlink(l.Src, l.Dest)
}

// DotDir is a directory containing a links file. The paths in the links file, if not absolute, will be relative to the Path attribute.
type DotDir struct {
	Path     string
	LinkFile string
}

// Links parses a list of links from the links file. The found links will be cleaned and returned. An error will be returned if parsing the links file fails.
func (d DotDir) Links() (links []Link, err error) {
	f, err := os.Open(d.LinkFile)
	if err != nil {
		log.Printf("Error openeing link file %v: %v", d.LinkFile, err)
		return nil, err
	}
	defer f.Close()

	var m map[string]string
	err = json.NewDecoder(f).Decode(&m)
	if err != nil {
		log.Printf("Error parsing link file %v: %v", d.LinkFile, err)
		return nil, err
	}
	for src, dest := range m {
		src = filepath.Join(d.Path, src)
		link := Link{Src: src, Dest: dest}
		link.Clean()
		links = append(links, link)
	}
	return
}

// Bootstrap manages a list of files that need to be symlinked.
type Bootstrap struct {
	DotDirs []DotDir
}

// AddDir adds a DotDir to the DotDirs given the directory path and path to the links file.
func (b *Bootstrap) AddDir(dir, links string) {
	b.DotDirs = append(b.DotDirs, DotDir{
		Path:     dir,
		LinkFile: links,
	})
}

// Walk traverses the specified directory. Any directories found containing a links file will be added to the DotDirs attribute. An error will be returned if the walking fails.
func (b *Bootstrap) Walk(dir string) error {
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		// Check for link file
		if info.Name() == LinkFile {
			d, _ := filepath.Split(path)
			b.AddDir(d, path)
		}
		return nil
	})
	if err != nil && err != io.EOF {
		return err
	}
	return nil
}

// Link adds the links from each of the DotDirs to the links chan. If an error occurs while getting a DotDirs links, the error will be added to the errors chan.
func (b *Bootstrap) Link(links chan Link, errors chan error) {
	toLinks := func(l Link) {
		if links != nil {
			links <- l
		}
	}
	toErrors := func(e error) {
		if errors != nil {
			errors <- e
		}
	}
	wg := &sync.WaitGroup{}
	for _, dotDir := range b.DotDirs {
		wg.Add(1)
		go func(dotDir DotDir) {
			defer wg.Done()
			links, err := dotDir.Links()
			if err != nil {
				toErrors(err)
			}
			for _, link := range links {
				toLinks(link)
			}
		}(dotDir)
	}
	wg.Wait()
}

// DotEnv is the name of the environment variable signifying the location of the dotfiles needing bootstrapping.
const DotEnv = "DOT"

// LinkFile is the name the file describing symlinks relative to the current directory.
const LinkFile = "links.json"

func main() {
	i := Input{
		Dir:   os.Getenv(DotEnv),
		Dry:   false,
		Force: false,
	}
	flag.Usage = func() {
		fmt.Print(`Bootstrap : github.com/dangerhuss/bootstrap

Blah blah blah blah bootstrap blah blah blah.

Options:
-d -dir    The dotfiles source directory that needs bootstrapping.
-n -dry    Print out the ln commands instead of creating the links.
-f -force  Overwrite existing links.

Source: github.com/dangerhuss/bootstrap/issues
License: MIT License Copyright (c) 2017 Andrew Huss

`)
	}
	flag.StringVar(&i.Dir, "dir", i.Dir, "")
	flag.StringVar(&i.Dir, "d", i.Dir, "")

	flag.BoolVar(&i.Dry, "dry", i.Dry, "")
	flag.BoolVar(&i.Dry, "n", i.Dry, "")

	flag.BoolVar(&i.Force, "force", i.Force, "")
	flag.BoolVar(&i.Force, "f", i.Force, "")
	flag.Parse()

	dir, err := filepath.Abs(i.Dir)
	if err != nil {
		log.Fatal(err)
	}

	// Create and populate the Bootstrap DotDirs
	b := &Bootstrap{}
	err = b.Walk(dir)
	if err != nil {
		log.Fatal(err)
	}

	// Create the needed chans
	links := make(chan Link)
	errors := make(chan error)

	wg := new(sync.WaitGroup)
	wg.Add(1) // Add 1 for the single go routine listening on the above chans
	messages := map[string][]string{}

	// Spawn a go routine to create the desired links
	go func(messages map[string][]string) {
		defer wg.Done()
		var linksDone, errorsDone bool
		for !linksDone || !errorsDone {
			select {
			case link, ok := <-links:
				if !ok {
					// The links chan has been closed.
					linksDone = true
					continue
				}

				if i.Dry {
					// Add the ln commands to the messages map.
					a := messages["Commands"]
					messages["Commands"] = append(a, link.cmd(i.Force))
					continue
				}

				// Write the symlink. Use the user specified force flag.
				err := link.Symlink(i.Force)
				if err != nil {
					if lerr, ok := err.(*os.LinkError); ok {
						// Grab the err causing the LinkError
						err = lerr.Err
					}
					// Add the Symlink error to the messages map.
					a := messages["Failures"]
					messages["Failures"] = append(a, fmt.Sprintf("%v: %v", err, link))
					continue
				}
				// Add the newly created Link string to the messages map.
				a := messages["Successes"]
				messages["Successes"] = append(a, link.String())
			case err, ok := <-errors:
				if !ok {
					// The errors chan has been closed
					errorsDone = true
					continue
				}
				// Add the bootstrap error to the messages map.
				a := messages["Errors"]
				messages["Errors"] = append(a, err.Error())
			}
		}
	}(messages)

	// Kick off the links method.
	b.Link(links, errors)

	// Links only returns once all the links or errors
	// have been added to the respective chan.We can
	// safley close the links and errors chans.
	close(links)
	close(errors)
	// Wait for all the symlinks to be created.
	wg.Wait()
	// Print out all the messages
	for header, msgs := range messages {
		if len(messages) > 1 {
			fmt.Println(header + ":")
		}
		fmt.Println(strings.Join(msgs, "\n"))
	}
	if len(messages) > 0 {
		fmt.Println("Changes will take effect after sourcing your .*shrc")
	}
}
<|endoftext|>


<commit_before>package main // import "github.com/christian-blades-cb/etcdbk"

import (
	log "github.com/Sirupsen/logrus"
	"github.com/coreos/go-etcd/etcd"
	"github.com/jessevdk/go-flags"

	"github.com/goamz/goamz/aws"
	"github.com/goamz/goamz/s3"

	"archive/tar"
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"os"
	"strings"
	"time"
)

func main() {
	var opts struct {
		EtcdMachines []string `long:"etcd-hosts" short:"e" required:"true" default:"http://127.0.0.1:2379" env:"ETCD_HOSTS" env-delim:"," description:"etcd machines"`
		ClusterName  string   `long:"cluster-name" short:"n" default:"etcd-cluster" env:"CLUSTER_NAME" description:"Cluster name to use in naming the file in the S3 Bucket"`

		OutFilePath string `long:"outfile" short:"o" env:"OUTFILE" description:"Where to write the resulting tarball. '-' for STDOUT"`

		AwsAccessKey  string `long:"aws-access" env:"AWS_ACCESS_KEY_ID"`
		AwsSecretKey  string `long:"aws-secret" env:"AWS_SECRET_ACCESS_KEY"`
		AwsS3Endpoint string `long:"s3-endpoint" env:"AWS_S3_ENDPOINT" default:"https://s3.amazonaws.com" description:"AWS S3 endpoint. See http://goo.gl/OG2Nkv"`
		AwsBucket     string `long:"aws-bucket" env:"AWS_S3_BUCKET"`
	}
	flags.Parse(&opts)

	log.SetLevel(log.DebugLevel)
	log.SetOutput(os.Stderr)

	log.Debug("connecting to etcd")
	client := etcd.NewClient(opts.EtcdMachines)
	defer client.Close()

	log.Debug("requesting root node")
	response, err := client.Get("/", false, true)
	if err != nil {
		log.WithField("error", err).Fatal("could not retrieve value for key")
	}

	// fill the buffer with tarball
	buffer := bytes.NewBuffer(nil)
	gzipWriter := gzip.NewWriter(buffer)
	w := tar.NewWriter(gzipWriter)

	writeNode(w, response.Node)

	w.Close()
	gzipWriter.Close()

	// write to file
	log.WithField("outpath", opts.OutFilePath).Debug("writing to destination")
	outWriter, err := getOutfileWriter(opts.OutFilePath)
	if err != nil {
		log.WithField("error", err).Fatal("error opening output file")
	}
	defer outWriter.Close()
	if _, err := buffer.WriteTo(outWriter); err != nil {
		log.WithField("error", err).Warn("Error writing to file")
	}

	log.WithField("bucket", opts.AwsBucket).Debug("saving to bucket")
	// write to S3
	s3Out := S3Writer{
		AccessKey:   opts.AwsAccessKey,
		SecretKey:   opts.AwsSecretKey,
		Endpoint:    opts.AwsS3Endpoint,
		Bucket:      opts.AwsBucket,
		ClusterName: opts.ClusterName,
	}
	s3Out.WriteToS3(buffer.Bytes())
}

func writeNode(w *tar.Writer, node *etcd.Node) { // I'm recursive!
	log.WithField("key", node.Key).Debug()
	if node.Dir {
		for _, subNode := range node.Nodes {
			writeNode(w, subNode) // see?
		}
		return
	}

	buf := bytes.NewBuffer([]byte(node.Value))
	expiration := func() string {
		if node.Expiration == nil {
			return "never"
		} else {
			return node.Expiration.Format(time.RFC3339)
		}
	}()

	w.WriteHeader(&tar.Header{
		Name: node.Key,
		Mode: 0444,
		Size: int64(buf.Len()),
		Xattrs: map[string]string{
			"ModifiedIndex": fmt.Sprintf("%d", node.ModifiedIndex),
			"CreatedIndex":  fmt.Sprintf("%d", node.CreatedIndex),
			"Expiration":    expiration,
		},
	})
	buf.WriteTo(w)
}

type DiscardCloser struct{}

func (d *DiscardCloser) Write(p []byte) (int, error) {
	return len(p), nil
}

func (d *DiscardCloser) Close() error {
	return nil
}

func getOutfileWriter(path string) (io.WriteCloser, error) {
	trimmedPath := strings.TrimSpace(path)
	switch trimmedPath {
	case "-":
		return os.Stdout, nil
	case "":
		return &DiscardCloser{}, nil
	}
	return os.Create(path)
}

type S3Writer struct {
	AccessKey, SecretKey, Endpoint, Bucket string

	ClusterName string
}

func (s3w S3Writer) WriteToS3(p []byte) error {
	auth := aws.Auth{
		AccessKey: s3w.AccessKey,
		SecretKey: s3w.SecretKey,
	}
	path := fmt.Sprintf("%s-%s", s3w.ClusterName, time.Now().UTC().Format(time.RFC3339))

	client := s3.New(auth, aws.Region{S3Endpoint: s3w.Endpoint})
	bucket := client.Bucket(s3w.Bucket)
	bucket.Put(path, p, "application/x-gzip", s3.Private, s3.Options{})
	return nil
}
<commit_msg>cli descriptions and added .tar.gz to s3 archive name<commit_after>package main // import "github.com/christian-blades-cb/etcdbk"

import (
	log "github.com/Sirupsen/logrus"
	"github.com/coreos/go-etcd/etcd"
	"github.com/jessevdk/go-flags"

	"github.com/goamz/goamz/aws"
	"github.com/goamz/goamz/s3"

	"archive/tar"
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"os"
	"strings"
	"time"
)

func main() {
	var opts struct {
		EtcdMachines []string `long:"etcd-hosts" short:"e" required:"true" default:"http://127.0.0.1:2379" env:"ETCD_HOSTS" env-delim:"," description:"etcd machines"`
		ClusterName  string   `long:"cluster-name" short:"n" default:"etcd-cluster" env:"CLUSTER_NAME" description:"Cluster name to use in naming the file in the S3 Bucket"`

		OutFilePath string `long:"outfile" short:"o" env:"OUTFILE" description:"Where to write the resulting tarball. '-' for STDOUT"`

		AwsAccessKey  string `long:"aws-access" env:"AWS_ACCESS_KEY_ID" description:"Access key of an IAM user with write access to the given bucket"`
		AwsSecretKey  string `long:"aws-secret" env:"AWS_SECRET_ACCESS_KEY" description:"Secret key of an IAM user with write access to the given bucket"`
		AwsS3Endpoint string `long:"s3-endpoint" env:"AWS_S3_ENDPOINT" default:"https://s3.amazonaws.com" description:"AWS S3 endpoint. See http://goo.gl/OG2Nkv"`
		AwsBucket     string `long:"aws-bucket" env:"AWS_S3_BUCKET" description:"Bucket in which to place the archive."`
	}
	flags.Parse(&opts)

	log.SetLevel(log.DebugLevel)
	log.SetOutput(os.Stderr)

	log.Debug("connecting to etcd")
	client := etcd.NewClient(opts.EtcdMachines)
	defer client.Close()

	log.Debug("requesting root node")
	response, err := client.Get("/", false, true)
	if err != nil {
		log.WithField("error", err).Fatal("could not retrieve value for key")
	}

	// fill the buffer with tarball
	buffer := bytes.NewBuffer(nil)
	gzipWriter := gzip.NewWriter(buffer)
	w := tar.NewWriter(gzipWriter)

	writeNode(w, response.Node)

	w.Close()
	gzipWriter.Close()

	// write to file
	log.WithField("outpath", opts.OutFilePath).Debug("writing to destination")
	outWriter, err := getOutfileWriter(opts.OutFilePath)
	if err != nil {
		log.WithField("error", err).Fatal("error opening output file")
	}
	defer outWriter.Close()
	if _, err := buffer.WriteTo(outWriter); err != nil {
		log.WithField("error", err).Warn("Error writing to file")
	}

	log.WithField("bucket", opts.AwsBucket).Debug("saving to bucket")
	// write to S3
	s3Out := S3Writer{
		AccessKey:   opts.AwsAccessKey,
		SecretKey:   opts.AwsSecretKey,
		Endpoint:    opts.AwsS3Endpoint,
		Bucket:      opts.AwsBucket,
		ClusterName: opts.ClusterName,
	}
	s3Out.WriteToS3(buffer.Bytes())
}

func writeNode(w *tar.Writer, node *etcd.Node) { // I'm recursive!
	log.WithField("key", node.Key).Debug()
	if node.Dir {
		for _, subNode := range node.Nodes {
			writeNode(w, subNode) // see?
		}
		return
	}

	buf := bytes.NewBuffer([]byte(node.Value))
	expiration := func() string {
		if node.Expiration == nil {
			return "never"
		} else {
			return node.Expiration.Format(time.RFC3339)
		}
	}()

	w.WriteHeader(&tar.Header{
		Name: node.Key,
		Mode: 0444,
		Size: int64(buf.Len()),
		Xattrs: map[string]string{
			"ModifiedIndex": fmt.Sprintf("%d", node.ModifiedIndex),
			"CreatedIndex":  fmt.Sprintf("%d", node.CreatedIndex),
			"Expiration":    expiration,
		},
	})
	buf.WriteTo(w)
}

type DiscardCloser struct{}

func (d *DiscardCloser) Write(p []byte) (int, error) {
	return len(p), nil
}

func (d *DiscardCloser) Close() error {
	return nil
}

func getOutfileWriter(path string) (io.WriteCloser, error) {
	trimmedPath := strings.TrimSpace(path)
	switch trimmedPath {
	case "-":
		return os.Stdout, nil
	case "":
		return &DiscardCloser{}, nil
	}
	return os.Create(path)
}

type S3Writer struct {
	AccessKey, SecretKey, Endpoint, Bucket string

	ClusterName string
}

func (s3w S3Writer) WriteToS3(p []byte) error {
	auth := aws.Auth{
		AccessKey: s3w.AccessKey,
		SecretKey: s3w.SecretKey,
	}
	path := fmt.Sprintf("%s-%s.tar.gz", s3w.ClusterName, time.Now().UTC().Format(time.RFC3339))

	client := s3.New(auth, aws.Region{S3Endpoint: s3w.Endpoint})
	bucket := client.Bucket(s3w.Bucket)
	return bucket.Put(path, p, "application/x-gzip", s3.Private, s3.Options{})
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"log"
	"os"
	"sphere2cubeGo/cache"
	"sphere2cubeGo/saver"
	"sphere2cubeGo/worker"
	"time"
)

var (
	tileNames = []string{
		worker.TileUp,
		worker.TileDown,
		worker.TileFront,
		worker.TileRight,
		worker.TileBack,
		worker.TileLeft,
	}

	tileSize          = 1024
	originalImagePath = ""
	outPutDir         = "./build"

	tileSizeCmd          = flag.Int("s", tileSize, "Size in px of final tile")
	originalImagePathCmd = flag.String("i", "", "Path to input equirectangular panorama")
	outPutDirCmd         = flag.String("o", outPutDir, "Path to output directory")
)

func main() {

	flag.Parse()
	tileSize = *tileSizeCmd
	originalImagePath = *originalImagePathCmd
	outPutDir = *outPutDirCmd

	if originalImagePath == "" {
		flag.PrintDefaults()
		os.Exit(2)
	}

	_, err := os.Stat(originalImagePath)
	if err != nil {
		if os.IsNotExist(err) {
			log.Fatalf("%v not found", originalImagePath)
		}
	}

	done := make(chan worker.TileResult)
	timeStart := time.Now()
	cacheResult := cache.CacheAnglesHandler(tileSize)
	for _, tileName := range tileNames {
		tile := worker.Tile{TileName: tileName, TileSize: tileSize}
		go worker.Worker(tile, cacheResult, originalImagePath, done)
	}

	for range tileNames {
		tileResult := <-done
		err = saver.SaveTile(tileResult, outPutDir)

		if err != nil {
			log.Fatal(err.Error())
			os.Exit(2)
		}

		log.Printf("Process for tile %v --> finished", tileResult.Tile.TileName)
	}

	timeFinish := time.Now()
	duration := timeFinish.Sub(timeStart)
	log.Printf("Time to render: %v seconds", duration.Seconds())
}
<commit_msg>some improvements<commit_after>package main

import (
	"flag"
	"log"
	"os"
	"sphere2cubeGo/cache"
	"sphere2cubeGo/saver"
	"sphere2cubeGo/worker"
	"time"
)

var (
	tileNames = []string{
		worker.TileUp,
		worker.TileDown,
		worker.TileFront,
		worker.TileRight,
		worker.TileBack,
		worker.TileLeft,
	}

	tileSize          = 1024
	originalImagePath = ""
	outPutDir         = "./build"

	tileSizeCmd          = flag.Int("s", tileSize, "Size in px of final tile")
	originalImagePathCmd = flag.String("i", "", "Path to input equirectangular panorama")
	outPutDirCmd         = flag.String("o", outPutDir, "Path to output directory")
)

func main() {

	flag.Parse()
	tileSize = *tileSizeCmd
	originalImagePath = *originalImagePathCmd
	outPutDir = *outPutDirCmd

	if originalImagePath == "" {
		flag.PrintDefaults()
		os.Exit(2)
	}

	_, err := os.Stat(originalImagePath)
	if err != nil {
		if os.IsNotExist(err) {
			log.Fatalf("%v not found", originalImagePath)
			os.Exit(2)
		}
	}

	done := make(chan worker.TileResult)
	timeStart := time.Now()
	cacheResult := cache.CacheAnglesHandler(tileSize)
	for _, tileName := range tileNames {
		tile := worker.Tile{TileName: tileName, TileSize: tileSize}
		go worker.Worker(tile, cacheResult, originalImagePath, done)
	}

	for range tileNames {
		tileResult := <-done
		err = saver.SaveTile(tileResult, outPutDir)

		if err != nil {
			log.Fatal(err.Error())
			os.Exit(2)
		}

		log.Printf("Process for tile %v --> finished", tileResult.Tile.TileName)
	}

	timeFinish := time.Now()
	duration := timeFinish.Sub(timeStart)
	log.Printf("Time to render: %v seconds", duration.Seconds())
}
<|endoftext|>


<commit_before>package pearl

import (
	"bufio"
	"encoding/binary"
	"io"

	"github.com/mmcloughlin/pearl/log"
	"github.com/pkg/errors"
)

// MaxPayloadLength is the longest allowable cell payload.
//
// Reference: https://github.com/torproject/torspec/blob/4074b891e53e8df951fc596ac6758d74da290c60/tor-spec.txt#L65
//
//	   PAYLOAD_LEN -- The longest allowable cell payload, in bytes. (509)
//
const MaxPayloadLength = 509

// ErrUnknownCommand is returned when a cell is seen with an unknown command.
var ErrUnknownCommand = errors.New("unknown command")

// CircID is a circuit ID.
type CircID uint32

// CellFormat represents a format for serializing cells.
type CellFormat interface {
	CircIDLen() int
	CircID([]byte) CircID
	PutCircID([]byte, CircID)
}

// CircID2Format is the (older) cell format with 2-byte circuit IDs.
type CircID2Format struct{}

// CircIDLen returns 2.
func (c CircID2Format) CircIDLen() int {
	return 2
}

// CircID extracts the circuit ID from cell bytes in x.
func (c CircID2Format) CircID(x []byte) CircID {
	return CircID(binary.BigEndian.Uint16(x))
}

// PutCircID inserts the CircID into x.
func (c CircID2Format) PutCircID(x []byte, id CircID) {
	// BUG(mmcloughlin): potential overflow in CircID2Format.PutCircID with a
	// 32-bit circ id value.
	binary.BigEndian.PutUint16(x, uint16(id))
}

// CircID4Format is the 4-byte cell ID format for link protocol versions 4 and newer.
type CircID4Format struct{}

// CircIDLen returns 4.
func (c CircID4Format) CircIDLen() int {
	return 4
}

// CircID extracts the circuit ID from cell bytes in x.
func (c CircID4Format) CircID(x []byte) CircID {
	return CircID(binary.BigEndian.Uint32(x))
}

// PutCircID inserts the CircID into x.
func (c CircID4Format) PutCircID(x []byte, id CircID) {
	binary.BigEndian.PutUint32(x, uint32(id))
}

// VersionsCellFormat is the cell format for VERSIONS cells.
var VersionsCellFormat CellFormat = CircID2Format{}

// IsCommandVariableLength determines whether a cell for the given command
// code is variable length.
func IsCommandVariableLength(c Command) bool {
	// Reference: https://github.com/torproject/torspec/blob/master/tor-spec.txt#L406-L409
	//
	//	   On a version 2 connection, variable-length cells are indicated by a
	//	   command byte equal to 7 ("VERSIONS").  On a version 3 or
	//	   higher connection, variable-length cells are indicated by a command
	//	   byte equal to 7 ("VERSIONS"), or greater than or equal to 128.
	//
	return (c == Versions) || (byte(c) >= 128)
}

// PayloadOffset computes the payload offset from the start of cell data for the
// given cell format and command.
func PayloadOffset(f CellFormat, cmd Command) int {
	offset := f.CircIDLen() + 1
	if IsCommandVariableLength(cmd) {
		offset += 2
	}
	return offset
}

// Cell represents a cell.
type Cell interface {
	CircID() CircID
	Command() Command
	Payload() []byte
	Bytes() []byte
}

// CellBuilder can build a cell in a given format.
type CellBuilder interface {
	Cell(CellFormat) (Cell, error)
}

type FixedCellBuilder struct {
	CircID  CircID
	Command Command
	Payload []byte
}

func (c FixedCellBuilder) Cell(f CellFormat) (Cell, error) {
	cell := NewCellEmptyPayload(f, c.CircID, c.Command, uint16(len(c.Payload)))
	copy(cell.Payload(), c.Payload)
	return cell, nil
}

// cell is a concrete implemenation of Cell.
type cell struct {
	format CellFormat
	data   []byte
}

var _ Cell = new(cell)

// NewCellFromBuffer builds a Cell from the given bytes.
func NewCellFromBuffer(f CellFormat, x []byte) Cell {
	return cell{
		format: f,
		data:   x,
	}
}

// NewCellEmptyPayload builds a variable-length Cell with an empty payload of
// size n bytes.
func NewCellEmptyPayload(f CellFormat, circID CircID, cmd Command, n uint16) Cell {
	// BUG(mmcloughlin): NewCellEmptyPayload should use sync.Pool to allocate
	// cell buffers.
	alloc := f.CircIDLen() + 1 + 2 + int(n)
	data := make([]byte, alloc) // assumes we need 2 bytes for length (but we may not)
	ptr := 0

	f.PutCircID(data[ptr:], circID)
	ptr += f.CircIDLen()

	data[f.CircIDLen()] = byte(cmd)
	ptr++

	if IsCommandVariableLength(cmd) {
		binary.BigEndian.PutUint16(data[ptr:], n)
	}

	return NewCellFromBuffer(f, data)
}

// NewFixedCell builds a fixed-size cell.
func NewFixedCell(f CellFormat, circID CircID, cmd Command) Cell {
	if IsCommandVariableLength(cmd) {
		panic("command is requires variable length cell")
	}

	// BUG(mmcloughlin): NewFixedCell should use sync.Pool to allocate
	// cell buffers.
	alloc := f.CircIDLen() + 1 + MaxPayloadLength
	data := make([]byte, alloc) // assumes we need 2 bytes for length (but we may not)

	f.PutCircID(data, circID)

	data[f.CircIDLen()] = byte(cmd)

	return NewCellFromBuffer(f, data)
}

// CircID returns the circuit ID from the cell.
func (c cell) CircID() CircID {
	return c.format.CircID(c.data)
}

// Command returns the cell command.
func (c cell) Command() Command {
	return Command(c.data[c.format.CircIDLen()])
}

// Payload returns the cell payload.
func (c cell) Payload() []byte {
	offset := PayloadOffset(c.format, c.Command())
	return c.data[offset:]
}

// Bytes returns the whole cell in bytes.
func (c cell) Bytes() []byte {
	return c.data
}

// CellReader can read cells. Parallel to the io.Reader interface.
type CellReader interface {
	ReadCell(CellFormat) (Cell, error)
}

// CellReaderFunc implements CellReader with a plain functions.
type CellReaderFunc func(CellFormat) (Cell, error)

// ReadCell calls r.
func (r CellReaderFunc) ReadCell(f CellFormat) (Cell, error) {
	return r(f)
}

// cellReader reads cells from an io.Reader.
type cellReader struct {
	reader io.Reader

	buf    *bufio.Reader
	logger log.Logger
}

var _ CellReader = new(cellReader)

// NewCellReader builds a CellReader reading from r.
func NewCellReader(r io.Reader, logger log.Logger) CellReader {
	return cellReader{
		reader: r,

		buf:    bufio.NewReader(r),
		logger: log.ForComponent(logger, "cellreader"),
	}
}

// ReadCell reads a cell of the given format.
func (r cellReader) ReadCell(format CellFormat) (Cell, error) {
	// Reference: https://github.com/torproject/torspec/blob/master/tor-spec.txt#L391-L404
	//
	//	   On a version 1 connection, each cell contains the following
	//	   fields:
	//
	//	        CircID                                [CIRCID_LEN bytes]
	//	        Command                               [1 byte]
	//	        Payload (padded with 0 bytes)         [PAYLOAD_LEN bytes]
	//
	//	   On a version 2 or higher connection, all cells are as in version 1
	//	   connections, except for variable-length cells, whose format is:
	//
	//	        CircID                                [CIRCID_LEN octets]
	//	        Command                               [1 octet]
	//	        Length                                [2 octets; big-endian integer]
	//	        Payload                               [Length bytes]
	//

	circIDLen := format.CircIDLen()

	// Read cell header
	n := circIDLen + 1 + 2
	hdr, err := r.buf.Peek(n)
	if err != nil {
		return nil, errors.Wrap(err, "could not peek cell header")
	}
	r.logger.With("hdr", hdr).Trace("peek cell header")

	// command byte
	cmdByte := hdr[circIDLen]
	if !IsCommand(cmdByte) {
		return nil, ErrUnknownCommand
	}
	cmd := Command(cmdByte)
	r.logger.With("command", cmd.String()).Trace("extracted command")

	// fixed vs. variable cell
	payloadLen := uint16(MaxPayloadLength)
	if IsCommandVariableLength(cmd) {
		payloadLen = binary.BigEndian.Uint16(hdr[circIDLen+1:])
	}
	payloadOffset := PayloadOffset(format, cmd)

	// actually read the cell
	cellLength := payloadOffset + int(payloadLen)
	r.logger.With("len", cellLength).Trace("reading cell")

	// BUG(mmcloughlin) cellReader.ReadCell allocates new buffer every time
	// (should use sync.Pool)
	cellBuf := make([]byte, cellLength)
	_, err = io.ReadFull(r.buf, cellBuf)
	if err != nil {
		return nil, errors.Wrap(err, "could not read full cell")
	}

	return NewCellFromBuffer(format, cellBuf), nil
}
<commit_msg>refactor<commit_after>package pearl

import (
	"bufio"
	"encoding/binary"
	"io"

	"github.com/mmcloughlin/pearl/log"
	"github.com/pkg/errors"
)

// MaxPayloadLength is the longest allowable cell payload.
//
// Reference: https://github.com/torproject/torspec/blob/4074b891e53e8df951fc596ac6758d74da290c60/tor-spec.txt#L65
//
//	   PAYLOAD_LEN -- The longest allowable cell payload, in bytes. (509)
//
const MaxPayloadLength = 509

// ErrUnknownCommand is returned when a cell is seen with an unknown command.
var ErrUnknownCommand = errors.New("unknown command")

// CircID is a circuit ID.
type CircID uint32

// IsCommandVariableLength determines whether a cell for the given command
// code is variable length.
func (c Command) IsVariableLength() bool {
	// Reference: https://github.com/torproject/torspec/blob/4074b891e53e8df951fc596ac6758d74da290c60/tor-spec.txt#L433-L436
	//
	//	   On a version 2 connection, variable-length cells are indicated by a
	//	   command byte equal to 7 ("VERSIONS").  On a version 3 or
	//	   higher connection, variable-length cells are indicated by a command
	//	   byte equal to 7 ("VERSIONS"), or greater than or equal to 128.
	//
	return (c == Versions) || (byte(c) >= 128)
}

// PayloadOffset computes the payload offset from the start of cell data for the
// given command.
func (c Command) PayloadOffset() int {
	if c.IsVariableLength() {
		return 7
	}
	return 5
}

// Cell represents a cell.
type Cell interface {
	CircID() CircID
	Command() Command
	Payload() []byte
	Bytes() []byte
}

// CellBuilder can build a cell.
type CellBuilder interface {
	Cell() (Cell, error)
}

// cell is an implemenation of Cell backed by a byte array.
type cell []byte

// NewCellFromBuffer builds a Cell from the given bytes.
func NewCellFromBuffer(x []byte) Cell {
	return cell(c)
}

// NewCellEmptyPayload builds a variable-length Cell with an empty payload of
// size n bytes.
func NewCellEmptyPayload(circID CircID, cmd Command, n uint16) Cell {
	if !cmd.IsVariableLength() {
		panic("cannot build fixed length cell")
	}

	// BUG(mmcloughlin): NewCellEmptyPayload should use sync.Pool to allocate
	// cell buffers.
	data := make([]byte, 7+int(n))

	binary.BigEndian.PutUint32(data, circID)
	data[4] = byte(cmd)
	binary.BigEndian.PutUint16(data[5:], n)

	return NewCellFromBuffer(data)
}

// NewFixedCell builds a fixed-size cell.
func NewFixedCell(circID CircID, cmd Command) Cell {
	if cmd.IsVariableLength() {
		panic("command is requires variable length cell")
	}

	// BUG(mmcloughlin): NewFixedCell should use sync.Pool to allocate
	// cell buffers.
	data := make([]byte, 5+MaxPayloadLength)

	binary.BigEndian.PutUint32(data, circID)
	data[4] = byte(cmd)

	return NewCellFromBuffer(f, data)
}

// CircID returns the circuit ID from the cell.
func (c cell) CircID() CircID {
	return CircID(binary.BigEndian.Uint32(c))
}

// Command returns the cell command.
func (c cell) Command() Command {
	return Command(c[4])
}

// Payload returns the cell payload.
func (c cell) Payload() []byte {
	return c[PayloadOffset(c.Command()):]
}

// Bytes returns the whole cell in bytes.
func (c cell) Bytes() []byte {
	return c.data
}

// cellReader reads cells from an io.Reader.
type cellReader struct {
	reader    io.Reader
	circIDLen int
	buf       *bufio.Reader
	logger    log.Logger
}

// NewCellReader builds a CellReceiver reading from r.
func NewCellReader(r io.Reader, logger log.Logger) CellReceiver {
	return newCellReader(r, 4, logger)
}

// NewLegacyCellReader builds an old-style CellReceiver (for length 2 CircIDs).
func NewLegacyCellReader(r io.Reader, logger log.Logger) CellReceiver {
	return newCellReader(r, 2, logger)
}

func newCellReader(r io.Reader, circIDLen int, logger log.Logger) CellReceiver {
	return cellReader{
		reader:    r,
		circIDLen: circIDLen,
		buf:       bufio.NewReader(r),
		logger:    log.ForComponent(logger, "cellreader"),
	}
}

// ReadCell reads a cell of the given format.
func (r cellReader) ReadCell() (Cell, error) {
	// Reference: https://github.com/torproject/torspec/blob/master/tor-spec.txt#L391-L404
	//
	//	   On a version 1 connection, each cell contains the following
	//	   fields:
	//
	//	        CircID                                [CIRCID_LEN bytes]
	//	        Command                               [1 byte]
	//	        Payload (padded with 0 bytes)         [PAYLOAD_LEN bytes]
	//
	//	   On a version 2 or higher connection, all cells are as in version 1
	//	   connections, except for variable-length cells, whose format is:
	//
	//	        CircID                                [CIRCID_LEN octets]
	//	        Command                               [1 octet]
	//	        Length                                [2 octets; big-endian integer]
	//	        Payload                               [Length bytes]
	//

	// Read cell header
	n := r.circIDLen + 1 + 2
	hdr, err := r.buf.Peek(7)
	if err != nil {
		return nil, errors.Wrap(err, "could not peek cell header")
	}
	r.logger.With("hdr", hdr).Trace("peek cell header")

	// command byte
	cmdByte := hdr[r.circIDLen]
	if !IsCommand(cmdByte) {
		return nil, ErrUnknownCommand
	}
	cmd := Command(cmdByte)
	r.logger.With("command", cmd.String()).Trace("extracted command")

	// fixed vs. variable cell
	payloadLen := uint16(MaxPayloadLength)
	if cmd.IsVariableLength() {
		payloadLen = binary.BigEndian.Uint16(hdr[r.circIDLen+1:])
	}
	payloadOffset := PayloadOffset(cmd)

	// actually read the cell
	cellLength := payloadOffset + int(payloadLen)
	r.logger.With("len", cellLength).Trace("reading cell")

	// BUG(mmcloughlin) cellReader.ReadCell allocates new buffer every time
	// (should use sync.Pool)
	cellBuf := make([]byte, cellLength)
	offset := 4 - r.circIDLen
	_, err = io.ReadFull(r.buf, cellBuf[offset:])
	if err != nil {
		return nil, errors.Wrap(err, "could not read full cell")
	}

	return NewCellFromBuffer(cellBuf), nil
}
<|endoftext|>


<commit_before>package dokugen

import (
	"log"
	"strconv"
	"strings"
)

const ALT_0 = "."

type Cell struct {
	grid *Grid
	//The number if it's explicitly set. Number() will return it if it's explicitly or implicitly set.
	number      int
	Row         int
	Col         int
	Block       int
	neighbors   []*Cell
	impossibles [DIM]int
}

func NewCell(grid *Grid, row int, col int) Cell {
	//TODO: we should not set the number until neighbors are initialized.
	return Cell{grid: grid, Row: row, Col: col, Block: grid.blockForCell(row, col)}
}

func (self *Cell) Load(data string) {
	//Format, for now, is just the number itself, or 0 if no number.
	data = strings.Replace(data, ALT_0, "0", -1)
	num, _ := strconv.Atoi(data)
	self.SetNumber(num)
}

func (self *Cell) Number() int {
	//A layer of indirection since number needs to be used from the Setter.
	return self.number
}

func (self *Cell) SetNumber(number int) {
	//Sets the explicit number. This will affect its neighbors possibles list.
	oldNumber := self.number
	self.number = number
	if oldNumber > 0 {
		for i := 1; i <= DIM; i++ {
			if i == oldNumber {
				continue
			}
			self.setPossible(i)
		}
		self.alertNeighbors(oldNumber, true)
	}
	if number > 0 {
		for i := 1; i <= DIM; i++ {
			if i == number {
				continue
			}
			self.setImpossible(i)
		}
		self.alertNeighbors(number, false)
	}
}

func (self *Cell) alertNeighbors(number int, possible bool) {
	for _, cell := range self.Neighbors() {
		if possible {
			cell.setPossible(number)
		} else {
			cell.setImpossible(number)
		}
	}
}

func (self *Cell) setPossible(number int) {
	//Number is 1 indexed, but we store it as 0-indexed
	number--
	if number < 0 || number >= DIM {
		return
	}
	if self.impossibles[number] == 0 {
		log.Println("We were told to mark something that was already possible to possible.")
		return
	}
	self.impossibles[number]--
	if self.impossibles[number] == 0 && self.grid != nil {
		//Our rank will have changed.
		self.grid.queue.Insert(self)
	}

}

func (self *Cell) setImpossible(number int) {
	//Number is 1 indexed, but we store it as 0-indexed
	number--
	if number < 0 || number >= DIM {
		return
	}
	self.impossibles[number]++
	if self.impossibles[number] == 1 && self.grid != nil {
		//Our rank will have changed.
		self.grid.queue.Insert(self)
	}
}

func (self *Cell) Possible(number int) bool {
	//Number is 1 indexed, but we store it as 0-indexed
	number--
	if number < 0 || number >= DIM {
		return false
	}
	return self.impossibles[number] == 0
}

func (self *Cell) Invalid() bool {
	//Returns true if no numbers are possible.
	//TODO: figure out a way to send this back up to the solver when it happens.
	for _, counter := range self.impossibles {
		if counter == 0 {
			return false
		}
	}
	return true
}

func (self *Cell) Rank() int {
	if self.number != 0 {
		return 0
	}
	count := 0
	for _, counter := range self.impossibles {
		if counter == 0 {
			count++
		}
	}
	return count
}

func (self *Cell) implicitNumber() int {
	//Impossibles is in 0-index space, but represents nubmers in 1-indexed space.
	result := -1
	for i, counter := range self.impossibles {
		if counter == 0 {
			//Is there someone else competing for this? If so there's no implicit number
			if result != -1 {
				return 0
			}
			result = i
		}
	}
	//convert from 0-indexed to 1-indexed
	return result + 1
}

func (self *Cell) Neighbors() []*Cell {
	if self.grid == nil || !self.grid.initalized {
		return nil
	}
	if self.neighbors == nil {
		//We don't want duplicates, so we will collect in a map (used as a set) and then reduce.
		neighborsMap := make(map[*Cell]bool)
		for _, cell := range self.grid.Row(self.Row) {
			if cell == self {
				continue
			}
			neighborsMap[cell] = true
		}
		for _, cell := range self.grid.Col(self.Col) {
			if cell == self {
				continue
			}
			neighborsMap[cell] = true
		}
		for _, cell := range self.grid.Block(self.Block) {
			if cell == self {
				continue
			}
			neighborsMap[cell] = true
		}
		self.neighbors = make([]*Cell, len(neighborsMap))
		i := 0
		for cell, _ := range neighborsMap {
			self.neighbors[i] = cell
			i++
		}
	}
	return self.neighbors

}

func (self *Cell) DataString() string {
	result := strconv.Itoa(self.Number())
	return strings.Replace(result, "0", ALT_0, -1)
}

func (self *Cell) String() string {
	return "Cell[" + strconv.Itoa(self.Row) + "][" + strconv.Itoa(self.Col) + "]:" + strconv.Itoa(self.Number()) + "\n"
}

func (self *Cell) positionInBlock() (top, right, bottom, left bool) {
	if self.grid == nil {
		return
	}
	topRow, topCol, bottomRow, bottomCol := self.grid.blockExtents(self.Block)
	top = self.Row == topRow
	right = self.Col == bottomCol
	bottom = self.Row == bottomRow
	left = self.Col == topCol
	return
}
<commit_msg>Implemented diagramRows and Diagram. It only works for cells that are not filled, and it doesn't draw extra thick borders. Not tested.<commit_after>package dokugen

import (
	"log"
	"strconv"
	"strings"
)

const ALT_0 = "."
const DIAGRAM_IMPOSSIBLE = " "
const DIAGRAM_RIGHT = "|"
const DIAGRAM_BOTTOM = "-"
const DIAGRAM_CORNER = "+"

type Cell struct {
	grid *Grid
	//The number if it's explicitly set. Number() will return it if it's explicitly or implicitly set.
	number      int
	Row         int
	Col         int
	Block       int
	neighbors   []*Cell
	impossibles [DIM]int
}

func NewCell(grid *Grid, row int, col int) Cell {
	//TODO: we should not set the number until neighbors are initialized.
	return Cell{grid: grid, Row: row, Col: col, Block: grid.blockForCell(row, col)}
}

func (self *Cell) Load(data string) {
	//Format, for now, is just the number itself, or 0 if no number.
	data = strings.Replace(data, ALT_0, "0", -1)
	num, _ := strconv.Atoi(data)
	self.SetNumber(num)
}

func (self *Cell) Number() int {
	//A layer of indirection since number needs to be used from the Setter.
	return self.number
}

func (self *Cell) SetNumber(number int) {
	//Sets the explicit number. This will affect its neighbors possibles list.
	oldNumber := self.number
	self.number = number
	if oldNumber > 0 {
		for i := 1; i <= DIM; i++ {
			if i == oldNumber {
				continue
			}
			self.setPossible(i)
		}
		self.alertNeighbors(oldNumber, true)
	}
	if number > 0 {
		for i := 1; i <= DIM; i++ {
			if i == number {
				continue
			}
			self.setImpossible(i)
		}
		self.alertNeighbors(number, false)
	}
}

func (self *Cell) alertNeighbors(number int, possible bool) {
	for _, cell := range self.Neighbors() {
		if possible {
			cell.setPossible(number)
		} else {
			cell.setImpossible(number)
		}
	}
}

func (self *Cell) setPossible(number int) {
	//Number is 1 indexed, but we store it as 0-indexed
	number--
	if number < 0 || number >= DIM {
		return
	}
	if self.impossibles[number] == 0 {
		log.Println("We were told to mark something that was already possible to possible.")
		return
	}
	self.impossibles[number]--
	if self.impossibles[number] == 0 && self.grid != nil {
		//Our rank will have changed.
		self.grid.queue.Insert(self)
	}

}

func (self *Cell) setImpossible(number int) {
	//Number is 1 indexed, but we store it as 0-indexed
	number--
	if number < 0 || number >= DIM {
		return
	}
	self.impossibles[number]++
	if self.impossibles[number] == 1 && self.grid != nil {
		//Our rank will have changed.
		self.grid.queue.Insert(self)
	}
}

func (self *Cell) Possible(number int) bool {
	//Number is 1 indexed, but we store it as 0-indexed
	number--
	if number < 0 || number >= DIM {
		return false
	}
	return self.impossibles[number] == 0
}

func (self *Cell) Invalid() bool {
	//Returns true if no numbers are possible.
	//TODO: figure out a way to send this back up to the solver when it happens.
	for _, counter := range self.impossibles {
		if counter == 0 {
			return false
		}
	}
	return true
}

func (self *Cell) Rank() int {
	if self.number != 0 {
		return 0
	}
	count := 0
	for _, counter := range self.impossibles {
		if counter == 0 {
			count++
		}
	}
	return count
}

func (self *Cell) implicitNumber() int {
	//Impossibles is in 0-index space, but represents nubmers in 1-indexed space.
	result := -1
	for i, counter := range self.impossibles {
		if counter == 0 {
			//Is there someone else competing for this? If so there's no implicit number
			if result != -1 {
				return 0
			}
			result = i
		}
	}
	//convert from 0-indexed to 1-indexed
	return result + 1
}

func (self *Cell) Neighbors() []*Cell {
	if self.grid == nil || !self.grid.initalized {
		return nil
	}
	if self.neighbors == nil {
		//We don't want duplicates, so we will collect in a map (used as a set) and then reduce.
		neighborsMap := make(map[*Cell]bool)
		for _, cell := range self.grid.Row(self.Row) {
			if cell == self {
				continue
			}
			neighborsMap[cell] = true
		}
		for _, cell := range self.grid.Col(self.Col) {
			if cell == self {
				continue
			}
			neighborsMap[cell] = true
		}
		for _, cell := range self.grid.Block(self.Block) {
			if cell == self {
				continue
			}
			neighborsMap[cell] = true
		}
		self.neighbors = make([]*Cell, len(neighborsMap))
		i := 0
		for cell, _ := range neighborsMap {
			self.neighbors[i] = cell
			i++
		}
	}
	return self.neighbors

}

func (self *Cell) DataString() string {
	result := strconv.Itoa(self.Number())
	return strings.Replace(result, "0", ALT_0, -1)
}

func (self *Cell) String() string {
	return "Cell[" + strconv.Itoa(self.Row) + "][" + strconv.Itoa(self.Col) + "]:" + strconv.Itoa(self.Number()) + "\n"
}

func (self *Cell) positionInBlock() (top, right, bottom, left bool) {
	if self.grid == nil {
		return
	}
	topRow, topCol, bottomRow, bottomCol := self.grid.blockExtents(self.Block)
	top = self.Row == topRow
	right = self.Col == bottomCol
	bottom = self.Row == bottomRow
	left = self.Col == topCol
	return
}

func (self *Cell) diagramRows() (rows []string) {
	//We'll only draw barriers at our bottom right edge.
	_, right, bottom, _ := self.positionInBlock()
	//TODO: vary printing if the block has neighbors or not.
	//TODO: print differently if the number is filled.
	current := 0
	for r := 0; r < BLOCK_DIM; r++ {
		row := ""
		for c := 0; c < BLOCK_DIM; c++ {
			if self.impossibles[current] == 0 {
				row += strconv.Itoa(current + 1)
			} else {
				row += DIAGRAM_IMPOSSIBLE
			}
			current++
		}
		rows = append(rows, row)
	}
	//Do we need to pad each row with | on the right?
	if !right {
		for i, data := range rows {
			rows[i] = data + DIAGRAM_RIGHT
		}
	}
	//Do we need an extra bottom row?
	if !bottom {
		rows = append(rows, strings.Repeat(DIAGRAM_BOTTOM, BLOCK_DIM))
		// Does it need a + at the end?
		if !right {
			rows[len(rows)-1] = rows[len(rows)-1] + DIAGRAM_CORNER
		}
	}

	return rows
}

func (self *Cell) Diagram() string {
	return strings.Join(self.diagramRows(), "\n")
}
<|endoftext|>


<commit_before>package main

import (
	"net"
	"io"
	"fmt"
	"strings"
	"time"
	"encoding/json"
	"os"
	"container/list"
	"log"
)

type Client struct {
	Name string
	Connection net.Conn
	Room *Room
	Rooms *list.List
	ChatLog *os.File
}

//Equals compares two clients to see if they're the same
func (cl Client) Equals (other *Client) bool {
	if cl.Name == other.Name && cl.Connection == other.Connection {
		return true
	} else {
		return false
	}
}

//Leave removes cl from current room and closes any rooms left empty
func (cl *Client) Leave () {
	if cl.Room != nil {
		cl.Room.Tell(fmt.Sprintf("%v leaves the room.",cl.Name))
		for entry := cl.Room.Clients.Front(); entry != nil; entry = entry.Next() { //Remove the client from room
			if cl.Equals(entry.Value.(*Client)) {
				cl.Room.Clients.Remove(entry)
			}
		}
		for entry := cl.Rooms.Front(); entry != nil; entry = entry.Next() {//Close any empty rooms
			if entry.Value.(*Room).Clients.Front() == nil {
				cl.Rooms.Remove(entry)
			}
		}
		cl.Room = nil
	}
	return
}

//Log writes the string to the chat log
func (cl Client) Log (s string) {
	_,err := io.WriteString(cl.ChatLog, strings.TrimSuffix(s,"\r"))
	if err != nil {
		log.Println(err)
	}
	return
}

//Send sends the message to the clients room
func (cl Client) Send (m Message) {
	if cl.Room == nil {
		cl.Tell("You're not in a room.  Type /join roomname to join a room or /help for other commands.")
		return
	}
	for i := cl.Room.Clients.Front();i != nil;i = i.Next() {
		_, err := io.WriteString(i.Value.(*Client).Connection,fmt.Sprint(m))
		if err != nil {
			log.Println(err)
			cl.Room.Clients.Remove(i)//remove the client that caused the error from the room
		}
	}
	cl.Log(fmt.Sprint(m))
	return
}

//Quit logs the client out, removes the client from all rooms, and closes the connection
func (cl *Client) Quit() {
	cl.Leave()
	err := cl.Connection.Close()
	if err != nil {
		log.Println(err)
	}
	return
}

//join adds a client to rm or creats a room if it doesn't exist
func (cl *Client) Join (rm string) {
	cl.Leave()//leave old room first
	exists := false
	for entry := cl.Rooms.Front(); entry != nil; entry = entry.Next() {
		if entry.Value.(*Room).Name == rm {//if the room exists add the client to the room
			exists = true
			cl.Room = entry.Value.(*Room)
			entry.Value.(*Room).Clients.PushBack(cl)
		}
	}
	if exists == false {
		newRoom := new(Room) //make a new room
		newRoom.Name = rm
		newRoom.Clients = list.New()
		cl.Room = newRoom //set the room as the clients room
		cl.Room.Clients.PushBack(cl)//add the client to the room
		cl.Rooms.PushBack(cl.Room)//add the room to the room list
	}
	cl.Room.Tell(fmt.Sprintf("%v has joined the room.",cl.Name))
	return
}

//Help tells the client a list of valid commands
func (cl Client) Help () {
	cl.Tell("/quit to quit")
	cl.Tell("/join roomname to join a room")
	cl.Tell("/leave to leave current room")
	cl.Tell("/help to see a list of commands")
}

//Tell sends a message to the client from the server
func (cl Client) Tell(msg string) {
	_,err := io.WriteString(cl.Connection, msg + "\r\n")
	if err != nil {
		log.Println(err)
	}
}

type Room struct {
	Name string
	Clients *list.List
}

//Tell sends a message to the room from the server
func (rm Room) Tell(msg string) {
	for i := rm.Clients.Front();i != nil;i = i.Next() {
		_, err := io.WriteString(i.Value.(*Client).Connection,fmt.Sprint(msg,"\r\n"))
		if err != nil {
			log.Println(err)
		}
	}
	return
}

type Message struct {
	text string
	time time.Time
	Sender Client
}
func (m Message) String() string {
	const layout = "3:04pm"
	return fmt.Sprintf("%s [%v]: %v\n\r",m.time.Format(layout),m.Sender.Name,m.text)
}

type config struct {
	ListeningIP string
	ListeningPort string
	LogFile string
}

//newMessage creates a new message
func newMessage(t string, s Client) Message {
	return Message{t,time.Now(),s}
}

//readString reads a string from the connection ending with a '\n'
func readString(conn net.Conn) (string, error) {
	r := make([]byte,1)
	var ip string
	var err error
	_, err = conn.Read(r)
	for r[0] != '\n'{
		ip = ip + string(r[0])
		_, err = conn.Read(r)
	}
	return strings.TrimSuffix(ip,"\r"), err
}

//handleConnection handles overall connection
func handleConnection(conn net.Conn, rooms *list.List,chl *os.File) {
	_, err := io.WriteString(conn, "What is your name? ")//set up the client
	if err != nil {
		log.Println("Error Writing",err)
	}
	name, err := readString(conn)
	if err != nil {
		log.Println("Error Reading",err)
	}
	cl := Client{name,conn,nil,rooms,chl}
	for{
		input, err := readString(cl.Connection)
		if err != nil {
			log.Println("Error Reading",err)
		}
		if strings.HasPrefix(input,"/") {// handle commands
			cmd := strings.Fields(input)
			switch cmd[0]{
			case "/quit":
				cl.Quit()
				return
			case "/join":
				if len(cmd) > 1 {
					rmName := strings.Join(cmd[1:], " ")
					cl.Join(rmName)
				} else {
					cl.Tell("Must enter channel to join")
				}
			case "/leave":
				cl.Leave()
			case "/help":
				cl.Help()
			default:
				cl.Tell("Invalid Command Type /help for list of commands")
			}
		}else {
			cl.Send(newMessage(input,cl))
		}
	}
}

//configure loads the config file
func configure (filename string) (c *config) {
	file, err := os.Open(filename)
	if err != nil {
		log.Panic("Error opening config file",err)
	}
	dec := json.NewDecoder(file)
	c = new(config)
	err = dec.Decode(c)
	if err !=nil {
		log.Panic("Error decoding config file",err)
	}
	return
}

//server listens for connections and sends them to handleConnection()
func server (c *config) {
	rooms := list.New()
	chl,err := os.Create(c.LogFile)
	defer chl.Close()
	if err != nil {
		log.Panic(err)
	}
	// go documentation code
	ln, err := net.Listen("tcp", net.JoinHostPort(c.ListeningIP,c.ListeningPort))
	if err != nil{
		log.Panic(err)
	}
	for{
		conn, err := ln.Accept()
		if err != nil {
			log.Println(err)
		}
		go handleConnection(conn,rooms,chl)
	}
	// end go documentation code
}

func main() {
	c := configure("Config")
	server(c)
}
<commit_msg>Added mutex for roomlist<commit_after>package main

import (
	"net"
	"io"
	"fmt"
	"strings"
	"time"
	"encoding/json"
	"os"
	"container/list"
	"log"
	"sync"
)

type Client struct {
	Name string
	Connection net.Conn
	Room *Room
	Rooms *RoomList
	ChatLog *os.File
}

//Equals compares two clients to see if they're the same
func (cl Client) Equals (other *Client) bool {
	if cl.Name == other.Name && cl.Connection == other.Connection {
		return true
	} else {
		return false
	}
}

//Leave removes cl from current room and closes any rooms left empty
func (cl *Client) Leave () {
	cl.Rooms.Lock()
	defer cl.Rooms.Unlock()
	if cl.Room != nil {
		cl.Room.Tell(fmt.Sprintf("%v leaves the room.",cl.Name))
		for entry := cl.Room.Clients.Front(); entry != nil; entry = entry.Next() { //Remove the client from room
			if cl.Equals(entry.Value.(*Client)) {
				cl.Room.Clients.Remove(entry)
			}
		}
		for entry := cl.Rooms.Front(); entry != nil; entry = entry.Next() {//Close any empty rooms
			if entry.Value.(*Room).Clients.Front() == nil {
				cl.Rooms.Remove(entry)
			}
		}
		cl.Room = nil
	}
}

//Log writes the string to the chat log
func (cl Client) Log (s string) {
	_,err := io.WriteString(cl.ChatLog, strings.TrimSuffix(s,"\r"))
	if err != nil {
		log.Println(err)
	}
	return
}

//Send sends the message to the clients room
func (cl Client) Send (m Message) {
	if cl.Room == nil {
		cl.Tell("You're not in a room.  Type /join roomname to join a room or /help for other commands.")
		return
	}
	for i := cl.Room.Clients.Front();i != nil;i = i.Next() {
		_, err := io.WriteString(i.Value.(*Client).Connection,fmt.Sprint(m))
		if err != nil {
			log.Println(err)
			i.Value.(*Client).Leave()
//			cl.Room.Clients.Remove(i)//remove the client that caused the error from the room
		}
	}
	cl.Log(fmt.Sprint(m))
	return
}

//Quit logs the client out, removes the client from all rooms, and closes the connection
func (cl *Client) Quit() {
	cl.Leave()
	err := cl.Connection.Close()
	if err != nil {
		log.Println(err)
	}
	return
}

//join adds a client to rm or creats a room if it doesn't exist
func (cl *Client) Join (rm string) {
	cl.Leave()//leave old room first
	cl.Rooms.Lock()
	defer cl.Rooms.Unlock()
	exists := false
	for entry := cl.Rooms.Front(); entry != nil; entry = entry.Next() {
		if entry.Value.(*Room).Name == rm {//if the room exists add the client to the room
			exists = true
			cl.Room = entry.Value.(*Room)
			entry.Value.(*Room).Clients.PushBack(cl)
		}
	}
	if exists == false {
		fmt.Print("Room not found making Room")
		newRoom := new(Room) //make a new room
		newRoom.Name = rm
		newRoom.Clients = list.New()
		cl.Room = newRoom //set the room as the clients room
		cl.Room.Clients.PushBack(cl)//add the client to the room
		cl.Rooms.PushBack(cl.Room)//add the room to the room list
	}
	cl.Room.Tell(fmt.Sprintf("%v has joined the room.",cl.Name))
}

//Help tells the client a list of valid commands
func (cl Client) Help () {
	cl.Tell("/quit to quit")
	cl.Tell("/join roomname to join a room")
	cl.Tell("/leave to leave current room")
	cl.Tell("/help to see a list of commands")
}

//Tell sends a message to the client from the server
func (cl Client) Tell(msg string) {
	_,err := io.WriteString(cl.Connection, msg + "\r\n")
	if err != nil {
		log.Println(err)
	}
}

type RoomList struct {
	*list.List
	*sync.Mutex
}

type Room struct {
	Name string
	Clients *list.List
}

//Tell sends a message to the room from the server
func (rm Room) Tell(msg string) {
	for i := rm.Clients.Front();i != nil;i = i.Next() {
		_, err := io.WriteString(i.Value.(*Client).Connection,fmt.Sprint(msg,"\r\n"))
		if err != nil {
			log.Println(err)
		}
	}
	return
}

type Message struct {
	text string
	time time.Time
	Sender Client
}
func (m Message) String() string {
	const layout = "3:04pm"
	return fmt.Sprintf("%s [%v]: %v\n\r",m.time.Format(layout),m.Sender.Name,m.text)
}

type config struct {
	ListeningIP string
	ListeningPort string
	LogFile string
}

//newMessage creates a new message
func newMessage(t string, s Client) Message {
	return Message{t,time.Now(),s}
}

//readString reads a string from the connection ending with a '\n'
func readString(conn net.Conn) (string, error) {
	r := make([]byte,1)
	var ip string
	var err error
	_, err = conn.Read(r)
	for r[0] != '\n'{
		ip = ip + string(r[0])
		_, err = conn.Read(r)
	}
	return strings.TrimSuffix(ip,"\r"), err
}

//handleConnection handles overall connection
func handleConnection(conn net.Conn, rooms *RoomList,chl *os.File) {
	_, err := io.WriteString(conn, "What is your name? ")//set up the client
	if err != nil {
		log.Println("Error Writing",err)
	}
	name, err := readString(conn)
	if err != nil {
		log.Println("Error Reading",err)
	}
	cl := Client{name,conn,nil,rooms,chl}
	for{
		input, err := readString(cl.Connection)
		if err != nil {
			log.Println("Error Reading",err)
		}
		if strings.HasPrefix(input,"/") {// handle commands
			cmd := strings.Fields(input)
			switch cmd[0]{
			case "/quit":
				cl.Quit()
				return
			case "/join":
				if len(cmd) > 1 {
					rmName := strings.Join(cmd[1:], " ")
					cl.Join(rmName)
				} else {
					cl.Tell("Must enter channel to join")
				}
			case "/leave":
				cl.Leave()
			case "/help":
				cl.Help()
			default:
				cl.Tell("Invalid Command Type /help for list of commands")
			}
		}else {
			cl.Send(newMessage(input,cl))
		}
	}
}

//configure loads the config file
func configure (filename string) (c *config) {
	file, err := os.Open(filename)
	if err != nil {
		log.Panic("Error opening config file",err)
	}
	dec := json.NewDecoder(file)
	c = new(config)
	err = dec.Decode(c)
	if err !=nil {
		log.Panic("Error decoding config file",err)
	}
	return
}

//server listens for connections and sends them to handleConnection()
func server (c *config) {
	rooms := &RoomList{list.New(),new(sync.Mutex)}
	chl,err := os.Create(c.LogFile)
	defer chl.Close()
	if err != nil {
		log.Panic(err)
	}
	// go documentation code
	ln, err := net.Listen("tcp", net.JoinHostPort(c.ListeningIP,c.ListeningPort))
	if err != nil{
		log.Panic(err)
	}
	for{
		conn, err := ln.Accept()
		if err != nil {
			log.Println(err)
		}
		go handleConnection(conn,rooms,chl)
	}
	// end go documentation code
}

func main() {
	c := configure("Config")
	server(c)
}
<|endoftext|>


<commit_before>package conf

import (
	"encoding/json"
	"flag"
	"io/ioutil"
	"os"
)

const (
	JSON     = "JSON"
	Flag     = "Flags"
	Env      = "Environment"
	Defaults = "Defaults"
)

type MultiLoader struct {
	JSONKey   string
	Mandatory []string
	Optional  []string
	Defaults  map[string]string
	Args      []string
}

func (l MultiLoader) Load() (config map[string]string, origin map[string]string, err error) {
	config = make(map[string]string)
	origin = make(map[string]string)

	flags := flag.NewFlagSet("", flag.ContinueOnError)
	flagVals := make(map[string]*string)
	for _, item := range l.Mandatory {
		flagVals[item] = flags.String(item, "", item)
	}
	for _, item := range l.Optional {
		flagVals[item] = flags.String(item, "", item)
	}

	var jsonFile *string
	if l.JSONKey != "" {
		jsonFile = flags.String(l.JSONKey, "", "JSON configuration file")
	}

	_ = flags.Parse(l.Args)
	for _, item := range l.Mandatory {
		if config[item] == "" {
			config[item] = *flagVals[item]
			origin[item] = Flag
		}
	}
	for _, item := range l.Optional {
		if config[item] == "" {
			config[item] = *flagVals[item]
			origin[item] = Flag
		}
	}

	var jsonConfig map[string]string
	if jsonFile != nil {
		jsonContent, _ := ioutil.ReadFile(*jsonFile)
		_ = json.Unmarshal(jsonContent, &jsonConfig)
	}

	for _, item := range l.Mandatory {
		if config[item] == "" {
			config[item] = jsonConfig[item]
			origin[item] = JSON
		}
	}
	for _, item := range l.Optional {
		if config[item] == "" {
			config[item] = jsonConfig[item]
			origin[item] = JSON
		}
	}

	for _, item := range l.Mandatory {
		if config[item] == "" {
			config[item] = os.Getenv(item)
			origin[item] = Env
		}
	}
	for _, item := range l.Optional {
		if config[item] == "" {
			config[item] = os.Getenv(item)
			origin[item] = Env
		}
	}

	for _, item := range l.Mandatory {
		if config[item] == "" {
			config[item] = l.Defaults[item]
			origin[item] = Defaults
		}
	}
	for _, item := range l.Optional {
		if config[item] == "" {
			config[item] = l.Defaults[item]
			origin[item] = Defaults
		}
	}

	return config, origin, err
}
<commit_msg>Refactor: pull out configurations in a common function<commit_after>package conf

import (
	"encoding/json"
	"flag"
	"io/ioutil"
	"os"
)

type MultiLoader struct {
	JSONKey   string
	Mandatory []string
	Optional  []string
	Defaults  map[string]string
	Args      []string
}

func (l MultiLoader) Load() (config map[string]string, origin map[string]string, err error) {
	config = make(map[string]string)
	origin = make(map[string]string)

	flags := flag.NewFlagSet("", flag.ContinueOnError)
	flagVals := make(map[string]*string)
	for _, item := range l.Mandatory {
		flagVals[item] = flags.String(item, "", item)
	}
	for _, item := range l.Optional {
		flagVals[item] = flags.String(item, "", item)
	}

	var jsonFile *string
	if l.JSONKey != "" {
		jsonFile = flags.String(l.JSONKey, "", "JSON configuration file")
	}

	_ = flags.Parse(l.Args)

	var jsonConfig map[string]string
	if jsonFile != nil {
		jsonContent, _ := ioutil.ReadFile(*jsonFile)
		_ = json.Unmarshal(jsonContent, &jsonConfig)
	}

	l.configure(config, origin, func(key string) string { return *flagVals[key] }, "Flags")
	l.configure(config, origin, func(key string) string { return jsonConfig[key] }, "JSON")
	l.configure(config, origin, func(key string) string { return os.Getenv(key) }, "Environment")
	l.configure(config, origin, func(key string) string { return l.Defaults[key] }, "Defaults")

	return config, origin, err
}

type mappingFunc func(key string) (value string)

func (l MultiLoader) configure(config map[string]string, origin map[string]string, mapping mappingFunc, from string) {
	for _, item := range l.Mandatory {
		if config[item] == "" {
			config[item] = mapping(item)
			origin[item] = from
		}
	}
	for _, item := range l.Optional {
		if config[item] == "" {
			config[item] = mapping(item)
			origin[item] = from
		}
	}
}
<|endoftext|>


<commit_before>package pool

import "net"

// PoolConn is a wrapper around net.Conn to modify the the behavior of
// net.Conn's Close() method.
type PoolConn struct {
	net.Conn
	c          *channelPool
	unusable bool
}

// Close() puts the given connects back to the pool instead of closing it.
func (p PoolConn) Close() error {
	if p.unusable {
		if p.Conn != nil {
			return p.Conn.Close()
		}
		return nil
	}
	return p.c.put(p.Conn)
}

// MarkUnreusable() marks the connection is not reusable.
func (p *PoolConn) MarkUnusable() {
	p.unusable = true
}

// newConn wraps a standard net.Conn to a poolConn net.Conn.
func (c *channelPool) wrapConn(conn net.Conn) net.Conn {
	p := &PoolConn{c: c}
	p.Conn = conn
	return p
}
<commit_msg>Update conn.go<commit_after>package pool

import "net"

// PoolConn is a wrapper around net.Conn to modify the the behavior of
// net.Conn's Close() method.
type PoolConn struct {
	net.Conn
	c          *channelPool
	unusable bool
}

// Close() puts the given connects back to the pool instead of closing it.
func (p PoolConn) Close() error {
	if p.unusable {
		if p.Conn != nil {
			return p.Conn.Close()
		}
		return nil
	}
	return p.c.put(p.Conn)
}

// MarkUnusable() marks the connection not usable any more, to let the pool close it instead of returning it to pool.
func (p *PoolConn) MarkUnusable() {
	p.unusable = true
}

// newConn wraps a standard net.Conn to a poolConn net.Conn.
func (c *channelPool) wrapConn(conn net.Conn) net.Conn {
	p := &PoolConn{c: c}
	p.Conn = conn
	return p
}
<|endoftext|>