<commit_before>package main

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

	"github.com/cryptix/exp/git"

	"gopkg.in/errgo.v1"
)

// "fetch $sha1 $ref" method 1 - unpacking loose objects
//   - look for it in ".git/objects/substr($sha1, 0, 2)/substr($sha, 2)"
//   - if found, download it and put it in place. (there may be a command for this)
//   - done \o/
func fetchObject(sha1 string) error {
	p := filepath.Join(ipfsRepoPath, "objects", sha1[:2], sha1[2:])
	objF, err := ipfsShell.Cat(p)
	if err != nil {
		return errgo.Notef(err, "shell.Cat(%q) failed", p)
	}
	obj, err := git.DecodeObject(objF)
	if err != nil {
		return errgo.Notef(err, "git.DecodeObject() failed")
	}
	log.WithField("obj", obj).Warning("TODO: assert(obj.type==commit)")
	// assert(typ=commit)
	// >recurese parent?
	// >recurse tree & store blobs

	return errgo.Newf("TODO: unsupported - please see issue #1")
}

// "fetch $sha1 $ref" method 2 - unpacking packed objects
//   - look for it in packfiles by fetching ".git/objects/pack/*.idx"
//     and looking at each idx with cat <idx> | git show-index  (alternatively can learn to read the format in go)
//   - if found in an <idx>, download the relevant .pack file,
//     and feed it into `git index-pack --stdin --fix-thin` which will put it into place.
//   - done \o/
func fetchPackedObject(sha1 string) error {
	// search for all index files
	packPath := filepath.Join(ipfsRepoPath, "objects", "pack")
	lsobj, err := ipfsShell.FileList(packPath)
	if err != nil {
		return errgo.Notef(err, "shell FileList(%q) failed", packPath)
	}
	var indexes []string
	for _, lnk := range lsobj.Links {
		if lnk.Type == "File" && strings.HasSuffix(lnk.Name, ".idx") {
			indexes = append(indexes, filepath.Join(packPath, lnk.Name))
		}
	}
	if len(indexes) == 0 {
		return errgo.New("fetchPackedObject: no idx files found")
	}
	for _, idx := range indexes {
		idxF, err := ipfsShell.Cat(idx)
		if err != nil {
			return errgo.Notef(err, "fetchPackedObject: idx<%s> cat(%s) failed", sha1, idx)
		}

		// using external git show-index < idxF for now
		// TODO: parse index file in go to make this portable
		var b bytes.Buffer
		showIdx := exec.Command("git", "show-index")
		showIdx.Stdin = idxF
		showIdx.Stdout = &b
		showIdx.Stderr = &b
		if err := showIdx.Run(); err != nil {
			return errgo.Notef(err, "fetchPackedObject: idx<%s> show-index start failed", sha1)
		}
		cmdOut := b.String()
		if !strings.Contains(cmdOut, sha1) {
			log.WithField("idx", filepath.Base(idx)).Debug("git show-index: sha1 not in index, next idx file")
			continue
		}
		//log.Debug("git show-index:", cmdOut)

		// we found an index with our hash inside
		pack := strings.Replace(idx, ".idx", ".pack", 1)
		log.Debug("unpacking:", pack)
		packF, err := ipfsShell.Cat(pack)
		if err != nil {
			return errgo.Notef(err, "fetchPackedObject: pack<%s> open() failed", sha1)
		}
		b.Reset()
		unpackIdx := exec.Command("git", "unpack-objects")
		unpackIdx.Dir = thisGitRepo // GIT_DIR
		unpackIdx.Stdin = packF
		unpackIdx.Stdout = &b
		unpackIdx.Stderr = &b
		if err := unpackIdx.Run(); err != nil {
			return errgo.Notef(err, "fetchPackedObject: pack<%s> 'git unpack-objects' failed\nOutput: %s", sha1, b.String())
		}
		log.Debug("git unpack-objects ...:", b.String())
		// found and unpacked - done
		// TODO(cryptix): somehow git doesnt checkout now..?
		// 'warning: remote HEAD refers to nonexistent ref, unable to checkout.'
		//b.Reset()
		//symRef := exec.Command("git", "symbolic-ref", "HEAD", "ref/heads/master")
		//symRef.Dir = thisGitRepo // GIT_DIR
		//symRef.Stdout = &b
		//symRef.Stderr = &b
		//if err := symRef.Run(); err != nil {
		//	return errgo.Notef(err, "fetchPackedObject: 'git symbolic-ref HEAD ref/heads/master' failed\nOutput: %s", b.String())
		//}
		return nil
	}
	return errgo.Newf("did not find sha1<%s> in %d index files", sha1, len(indexes))
}
<commit_msg>fetchObject: begin decoding git objects<commit_after>package main

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

	"github.com/cryptix/exp/git"

	"gopkg.in/errgo.v1"
)

// "fetch $sha1 $ref" method 1 - unpacking loose objects
//   - look for it in ".git/objects/substr($sha1, 0, 2)/substr($sha, 2)"
//   - if found, download it and put it in place. (there may be a command for this)
//   - done \o/
func fetchObject(sha1 string) error {
	p := filepath.Join(ipfsRepoPath, "objects", sha1[:2], sha1[2:])
	objF, err := ipfsShell.Cat(p)
	if err != nil {
		return errgo.Notef(err, "shell.Cat(%q) commit failed", p)
	}
	obj, err := git.DecodeObject(objF)
	if err != nil {
		return errgo.Notef(err, "git.DecodeObject(commit) failed")
	}
	commit, ok := obj.Commit()
	if !ok {
		return errgo.Newf("sha1 is not a git commit object")
	}
	// >recurese parent?

	// >recurse tree & store blobs
	p = filepath.Join(ipfsRepoPath, "objects", commit.Tree[:2], commit.Tree[2:])
	objF, err = ipfsShell.Cat(p)
	if err != nil {
		return errgo.Notef(err, "shell.Cat(%q) tree failed", p)
	}
	obj, err = git.DecodeObject(objF)
	if err != nil {
		return errgo.Notef(err, "git.DecodeObject(tree) failed")
	}

	tree, ok := obj.Tree()
	if !ok {
		return errgo.Newf("sha1 is not a git tree object")
	}

	log.Warning("Trees!")
	log.Warning(tree)
	return errgo.Newf("TODO: unsupported - please see issue #1")
}

// "fetch $sha1 $ref" method 2 - unpacking packed objects
//   - look for it in packfiles by fetching ".git/objects/pack/*.idx"
//     and looking at each idx with cat <idx> | git show-index  (alternatively can learn to read the format in go)
//   - if found in an <idx>, download the relevant .pack file,
//     and feed it into `git index-pack --stdin --fix-thin` which will put it into place.
//   - done \o/
func fetchPackedObject(sha1 string) error {
	// search for all index files
	packPath := filepath.Join(ipfsRepoPath, "objects", "pack")
	lsobj, err := ipfsShell.FileList(packPath)
	if err != nil {
		return errgo.Notef(err, "shell FileList(%q) failed", packPath)
	}
	var indexes []string
	for _, lnk := range lsobj.Links {
		if lnk.Type == "File" && strings.HasSuffix(lnk.Name, ".idx") {
			indexes = append(indexes, filepath.Join(packPath, lnk.Name))
		}
	}
	if len(indexes) == 0 {
		return errgo.New("fetchPackedObject: no idx files found")
	}
	for _, idx := range indexes {
		idxF, err := ipfsShell.Cat(idx)
		if err != nil {
			return errgo.Notef(err, "fetchPackedObject: idx<%s> cat(%s) failed", sha1, idx)
		}

		// using external git show-index < idxF for now
		// TODO: parse index file in go to make this portable
		var b bytes.Buffer
		showIdx := exec.Command("git", "show-index")
		showIdx.Stdin = idxF
		showIdx.Stdout = &b
		showIdx.Stderr = &b
		if err := showIdx.Run(); err != nil {
			return errgo.Notef(err, "fetchPackedObject: idx<%s> show-index start failed", sha1)
		}
		cmdOut := b.String()
		if !strings.Contains(cmdOut, sha1) {
			log.WithField("idx", filepath.Base(idx)).Debug("git show-index: sha1 not in index, next idx file")
			continue
		}
		//log.Debug("git show-index:", cmdOut)

		// we found an index with our hash inside
		pack := strings.Replace(idx, ".idx", ".pack", 1)
		log.Debug("unpacking:", pack)
		packF, err := ipfsShell.Cat(pack)
		if err != nil {
			return errgo.Notef(err, "fetchPackedObject: pack<%s> open() failed", sha1)
		}
		b.Reset()
		unpackIdx := exec.Command("git", "unpack-objects")
		unpackIdx.Dir = thisGitRepo // GIT_DIR
		unpackIdx.Stdin = packF
		unpackIdx.Stdout = &b
		unpackIdx.Stderr = &b
		if err := unpackIdx.Run(); err != nil {
			return errgo.Notef(err, "fetchPackedObject: pack<%s> 'git unpack-objects' failed\nOutput: %s", sha1, b.String())
		}
		log.Debug("git unpack-objects ...:", b.String())
		return nil
	}
	return errgo.Newf("did not find sha1<%s> in %d index files", sha1, len(indexes))
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
	//"errors"
	"container/list"
	"sync"

	"github.com/reiver/go-porterstemmer"
)

var seperators []byte = []byte{10, 32, 44, 46}
var stopWords map[string]bool

//var dictionary map[string]*list.List
var groupLen int = 50

var dictionary = struct {
	sync.RWMutex
	m map[string]*list.List
}{m: make(map[string]*list.List)}

func main() {
	var corpusPath string
	flag.StringVar(&corpusPath, "corpus", "data/corpus", "File path of the corpus.")
	flag.Parse()
	dispatcher(corpusPath)
}

func dispatcher(corpusPath string) {
	file, err := os.Open(corpusPath)
	defer file.Close()
	if err != nil {
		panic(err)
	}
	collectStopWords()
	//dictionary = make(map[string]*list.List)
	scanner := bufio.NewScanner(io.Reader(file))

	scanner.Split(splitTokens)

	var (
		token string
		pos   uint32 = 0
		wg    sync.WaitGroup
	)
	for scanner.Scan() {
		token = scanner.Text()
		if len(token) > 0 {
			wg.Add(1)
			go addToken(token, pos, &wg)
			pos += 1
		}
	}
	if err := scanner.Err(); err != nil {
		panic(err)
	}
	wg.Wait()

	outFile, err := os.Create("res")
	defer outFile.Close()
	if err != nil {
		panic(err)
	}
	writer := bufio.NewWriter(io.Writer(outFile))
	writeIndex(writer)
}

func addToken(token string, pos uint32, wg *sync.WaitGroup) {
	defer wg.Done()

	// Check to see if token is a stop word
	_, ok := stopWords[token]
	if !ok {
		// Stem the token
		token = porterstemmer.StemString(token)
		dictionary.RLock()
		postingsList, ok := dictionary.m[token]
		dictionary.RUnlock()
		if ok {
			lastEl := postingsList.Back()
			lastGroup := lastEl.Value.([]uint32)
			if len(lastGroup) == groupLen {
				newGroup := []uint32{pos}
				postingsList.PushBack(newGroup)
			} else {
				lastGroup = append(lastGroup, pos)
				postingsList.Remove(lastEl)
				postingsList.PushBack(lastGroup)
			}
		} else {
			l := list.New()
			newGroup := []uint32{pos}
			l.PushBack(newGroup)
			dictionary.Lock()
			dictionary.m[token] = l
			dictionary.Unlock()
		}
	}
}

func writeIndex(writer io.Writer) {
	for k, v := range dictionary.m {
		fmt.Println(k, v.Len(), len(v.Front().Value.([]uint32)))
	}
}

func splitTokens(data []byte, atEOF bool) (advance int, token []byte, err error) {
	var (
		found   = false
		started = false
		offset  = 0
	)
	for i := 0; i < len(data); i++ {
		found = false
		for s := 0; s < len(seperators); s++ {
			if data[i] == seperators[s] {
				advance = i + 1
				token = data[offset:i]
				if !started {
					offset += 1
				}
				found = true
				break
			}
		}
		if found {
			if started {
				break
			} else {
				continue
			}
		}
		if !started {
			started = true
		}
	}
	return
}

func collectStopWords() {
	file, err := os.Open("data/stopwords")
	defer file.Close()
	if err != nil {
		panic(err)
	}
	stopWords = make(map[string]bool)
	scanner := bufio.NewScanner(io.Reader(file))

	var word string
	for scanner.Scan() {
		word = scanner.Text()
		if len(word) > 0 {
			stopWords[word] = true
		}
	}
	if err := scanner.Err(); err != nil {
		panic(err)
	}
}
<commit_msg>Actually write the index to disk.<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
	//"errors"
	"container/list"
	"strconv"
	"sync"

	"github.com/reiver/go-porterstemmer"
)

var seperators []byte = []byte{10, 32, 44, 46}
var stopWords map[string]bool

//var dictionary map[string]*list.List
var groupLen int = 50

var dictionary = struct {
	sync.RWMutex
	m map[string]*list.List
}{m: make(map[string]*list.List)}

func main() {
	var corpusPath string
	flag.StringVar(&corpusPath, "corpus", "data/corpus", "File path of the corpus.")
	flag.Parse()
	dispatcher(corpusPath)
}

func dispatcher(corpusPath string) {
	file, err := os.Open(corpusPath)
	defer file.Close()
	if err != nil {
		panic(err)
	}
	collectStopWords()
	//dictionary = make(map[string]*list.List)
	scanner := bufio.NewScanner(io.Reader(file))

	scanner.Split(splitTokens)

	var (
		token string
		pos   uint32 = 0
		wg    sync.WaitGroup
	)
	for scanner.Scan() {
		token = scanner.Text()
		if len(token) > 0 {
			wg.Add(1)
			go addToken(token, pos, &wg)
			pos += 1
		}
	}
	if err := scanner.Err(); err != nil {
		panic(err)
	}
	wg.Wait()

	outFile, err := os.Create("res")
	defer outFile.Close()
	if err != nil {
		panic(err)
	}
	writer := bufio.NewWriter(io.Writer(outFile))
	writeIndex(writer)
}

func addToken(token string, pos uint32, wg *sync.WaitGroup) {
	defer wg.Done()

	// Check to see if token is a stop word
	_, ok := stopWords[token]
	if !ok {
		// Stem the token
		token = porterstemmer.StemString(token)
		dictionary.RLock()
		postingsList, ok := dictionary.m[token]
		dictionary.RUnlock()
		if ok {
			lastEl := postingsList.Back()
			lastGroup := lastEl.Value.([]uint32)
			if len(lastGroup) == groupLen {
				newGroup := []uint32{pos}
				postingsList.PushBack(newGroup)
			} else {
				lastGroup = append(lastGroup, pos)
				postingsList.Remove(lastEl)
				postingsList.PushBack(lastGroup)
			}
		} else {
			l := list.New()
			newGroup := []uint32{pos}
			l.PushBack(newGroup)
			dictionary.Lock()
			dictionary.m[token] = l
			dictionary.Unlock()
		}
	}
}

func writeIndex(writer *bufio.Writer) {
	for k, v := range dictionary.m {
		fmt.Println(k, v.Len(), len(v.Front().Value.([]uint32)))
		writer.WriteString("#" + k)
		var group []uint32
		for el := v.Front(); el != nil; el = el.Next() {
			group = el.Value.([]uint32)
			for _, posting := range group {
				val := strconv.FormatUint(uint64(posting), 10)
				writer.WriteString("," + string(val))
			}
		}
	}
	writer.Flush()
}

func splitTokens(data []byte, atEOF bool) (advance int, token []byte, err error) {
	var (
		found   = false
		started = false
		offset  = 0
	)
	for i := 0; i < len(data); i++ {
		found = false
		for s := 0; s < len(seperators); s++ {
			if data[i] == seperators[s] {
				advance = i + 1
				token = data[offset:i]
				if !started {
					offset += 1
				}
				found = true
				break
			}
		}
		if found {
			if started {
				break
			} else {
				continue
			}
		}
		if !started {
			started = true
		}
	}
	return
}

func collectStopWords() {
	file, err := os.Open("data/stopwords")
	defer file.Close()
	if err != nil {
		panic(err)
	}
	stopWords = make(map[string]bool)
	scanner := bufio.NewScanner(io.Reader(file))

	var word string
	for scanner.Scan() {
		word = scanner.Text()
		if len(word) > 0 {
			stopWords[word] = true
		}
	}
	if err := scanner.Err(); err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>/*
Asciidocgo implements an AsciiDoc renderer in Go.

Methods for parsing Asciidoc input files and rendering documents using eRuby
templates.

Asciidoc documents comprise a header followed by zero or more sections.
Sections are composed of blocks of content.  For example:

  = Doc Title

  == Section 1

  This is a paragraph block in the first section.

  == Section 2

  This section has a paragraph block and an olist block.

  . Item 1
  . Item 2

Examples:

Use built-in templates:

  lines = File.readlines("your_file.asc")
  doc = Asciidoctor::Document.new(lines)
  html = doc.render
  File.open("your_file.html", "w+") do |file|
    file.puts html
  end

Use custom (Tilt-supported) templates:

  lines = File.readlines("your_file.asc")
  doc = Asciidoctor::Document.new(lines, :template_dir => 'templates')
  html = doc.render
  File.open("your_file.html", "w+") do |file|
    file.puts html
  end

*/
package asciidocgo

import "io"

// Accepts input as a string
func LoadString(input string) *Document {
	return nil
}

// Accepts input as an array of strings
func LoadStrings(inputs ...string) *Document {
	return nil
}

// Accepts input as an IO.
// If the input is a File, information about the file is stored in attributes on
// the Document object.
func Load(input io.Reader) *Document {
	return nil
}
<commit_msg>Add REGEXP on Asciidocgo.<commit_after>/*
Asciidocgo implements an AsciiDoc renderer in Go.

Methods for parsing Asciidoc input files and rendering documents using eRuby
templates.

Asciidoc documents comprise a header followed by zero or more sections.
Sections are composed of blocks of content.  For example:

  = Doc Title

  == Section 1

  This is a paragraph block in the first section.

  == Section 2

  This section has a paragraph block and an olist block.

  . Item 1
  . Item 2

Examples:

Use built-in templates:

  lines = File.readlines("your_file.asc")
  doc = Asciidoctor::Document.new(lines)
  html = doc.render
  File.open("your_file.html", "w+") do |file|
    file.puts html
  end

Use custom (Tilt-supported) templates:

  lines = File.readlines("your_file.asc")
  doc = Asciidoctor::Document.new(lines, :template_dir => 'templates')
  html = doc.render
  File.open("your_file.html", "w+") do |file|
    file.puts html
  end

*/
package asciidocgo

import (
	"fmt"
	"io"
	"regexp"

	"github.com/VonC/asciidocgo/utils"
)

// Accepts input as a string
func LoadString(input string) *Document {
	return nil
}

// Accepts input as an array of strings
func LoadStrings(inputs ...string) *Document {
	return nil
}

// Accepts input as an IO.
// If the input is a File, information about the file is stored in attributes on
// the Document object.
func Load(input io.Reader) *Document {
	return nil
}

const (
	CC_ALPHA = `a-zA-Z`
	CC_ALNUM = `a-zA-Z0-9`
	CC_BLANK = `[ \t]`
	// non-blank character
	CC_GRAPH = `[\x21-\x7E]`
	CC_EOL   = `(?=\n|$)`
)

var ADMONITION_STYLES utils.Arr = []string{"NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"}

/* The following pattern, which appears frequently, captures the contents
between square brackets, ignoring escaped closing brackets
(closing brackets prefixed with a backslash '\' character)

	Pattern:
	(?:\[((?:\\\]|[^\]])*?)\])
	Matches:
	[enclosed text here] or [enclosed [text\] here]
*/
var REGEXP_STRING = map[string]string{
	//:strip_line_wise => /\A(?:\s*\n)?(.*?)\s*\z/m,

	// # NOTE: this is a inline admonition note
	//	:admonition_inline => /^(#{ADMONITION_STYLES.to_a * '|'}):#{CC_BLANK}/,
	":admonition_inline": fmt.Sprintf("^(%v):%v", ADMONITION_STYLES.Mult("|"), CC_BLANK),

	//	http://domain
	//	https://domain
	//	data:info
	//	:uri_sniff        => %r{^[#{CC_ALPHA}][#{CC_ALNUM}.+-]*:/{0,2}},
	":uri_sniff": fmt.Sprintf("^[%v][%v.+-]*:/{0,2}.*", CC_ALPHA, CC_ALNUM),
}

func iniREGEXP() map[string]*regexp.Regexp {
	res := map[string]*regexp.Regexp{}
	for key, regexpString := range REGEXP_STRING {
		regexp, err := regexp.Compile(regexpString)
		if err != nil {
			panic(fmt.Sprintf("iniREGEXP should compile all REGEXP_STRING like %v: %v", regexpString, err))
		}
		res[key] = regexp
	}
	return res
}

/* The following pattern, which appears frequently, captures the contents
between square brackets, ignoring escaped closing brackets
(closing brackets prefixed with a backslash '\' character)

	Pattern:
	(?:\[((?:\\\]|[^\]])*?)\])
	Matches:
	[enclosed text here] or [enclosed [text\] here]
*/
var REGEXP = iniREGEXP()
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/spf13/viper"
)

// global variables
var (
	region    string
	project   string
	stackname string
	stacks    map[string]*stack
	cfvars    map[string]interface{}
)

// fetchContent - checks the source type, url/s3/file and calls the corresponding function
func fetchContent(source string) (string, error) {
	switch strings.Split(strings.ToLower(source), ":")[0] {
	case "http":
		Log(fmt.Sprintln("Source Type: [http] Detected, Fetching Source: ", source), level.debug)
		resp, err := Get(source)
		if err != nil {
			return "", err
		}
		return resp, nil
	case "https":
		Log(fmt.Sprintln("Source Type: [https] Detected, Fetching Source: ", source), level.debug)
		resp, err := Get(source)
		if err != nil {
			return "", err
		}
		return resp, nil
	case "s3":
		Log(fmt.Sprintln("Source Type: [s3] Detected, Fetching Source: ", source), level.debug)
		resp, err := S3Read(source)
		if err != nil {
			return "", err
		}
		return resp, nil
	default:
		Log(fmt.Sprintln("Source Type: [file] Detected, Fetching Source: ", source), level.debug)
		b, err := ioutil.ReadFile(source)
		if err != nil {
			return "", err
		}
		return string(b), nil
	}
}

// getName  - Checks if arg is url or file and returns stack name and filepath/url
func getSource(s string) (string, string, error) {
	if strings.Contains(s, "::") {
		vals := strings.Split(s, "::")
		if len(vals) < 2 {
			return "", "", errors.New(`Error, invalid url format --> Example: stackname::http://someurl OR stackname::s3://bucket/key`)
		}

		return vals[0], vals[1], nil

	}

	name := filepath.Base(strings.Replace(s, filepath.Ext(s), "", -1))
	return name, s, nil

}

// configReader parses the config YAML file with Viper
func configReader(conf string) error {
	viper.SetConfigType("yaml")

	cfg, err := fetchContent(conf)

	if err != nil {
		return err
	}

	Log(fmt.Sprintln("Reading Config String into viper:", cfg), level.debug)
	err = viper.ReadConfig(bytes.NewBuffer([]byte(cfg)))

	if err != nil {
		return err
	}

	// Get basic settings
	region = viper.GetString("region")
	project = viper.GetString("project")

	// Get all the values of variables under cloudformation and put them into a map
	cfvars = make(map[string]interface{})
	stacks = make(map[string]*stack)

	// Get Global values
	cfvars["global"] = viper.Get("global")

	Log(fmt.Sprintln("Keys identified in Config:", viper.AllKeys()), level.debug)

	// if stacks is 0 at this point, All Stacks are assumed.
	if len(job.stacks) == 0 {
		job.stacks = make(map[string]string)
		for _, stk := range viper.Sub("stacks").AllKeys() {
			job.stacks[strings.Split(stk, ".")[0]] = ""
		}
	}

	for s := range job.stacks {
		stacks[s] = &stack{}
		key := "stacks." + s
		Log(fmt.Sprintf("Evaluating: [%s] in config"+"\n", s), level.debug)

		Log(fmt.Sprintf("Checking if [%s] exists in [%s]", key, strings.Join(viper.AllKeys(), ", ")), level.debug)
		if !strings.Contains(strings.Join(viper.AllKeys(), ""), key) {
			return fmt.Errorf("Key not found in config: %s", key)
		}

		for _, v := range viper.Sub(key).AllKeys() {
			Log(fmt.Sprintln("Processing: ", v), level.debug)
			// Using case statement for setting keyword values, added for scalability later.
			switch v {
			case "depends_on":
				dept := viper.Get(fmt.Sprintf("stacks.%s.%s", s, v)).([]interface{})
				Log(fmt.Sprintf("Found Dependency for [%s]: %s", s, dept), level.debug)
				stacks[s].dependsOn = dept
			default:
				// TODO: Nothing for now - more built-in values to come... maybe
			}

			// Get Cloudformation values
			cfvars[s] = viper.Get(fmt.Sprintf("stacks.%s.cf", s))
		}

		stacks[s].name = s
		stacks[s].setStackName()
	}

	return nil
}

// genTimeParser - Parses templates before deploying them...
func genTimeParser(source string) (string, error) {

	templ, err := fetchContent(source)
	if err != nil {
		return "", err
	}

	// Create template
	t, err := template.New("template").Funcs(genTimeFunctions).Parse(templ)
	if err != nil {
		return "", err
	}

	// so that we can write to string
	var doc bytes.Buffer
	t.Execute(&doc, cfvars)
	return doc.String(), nil
}

// deployTimeParser - Parses templates during deployment to resolve specfic Dependency functions like stackout...
func (s *stack) deployTimeParser() error {

	// Create template
	t, err := template.New("template").Delims("%", "%").Funcs(deployTimeFunctions).Parse(s.template)
	if err != nil {
		return err
	}

	// so that we can write to string
	var doc bytes.Buffer
	t.Execute(&doc, cfvars)
	s.template = doc.String()
	Log(fmt.Sprintf("Deploy Time Template Generate:\n%s", s.template), level.debug)

	return nil
}
<commit_msg>updated DeployTime function delimiter<commit_after>package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/spf13/viper"
)

// global variables
var (
	region    string
	project   string
	stackname string
	stacks    map[string]*stack
	cfvars    map[string]interface{}
)

// fetchContent - checks the source type, url/s3/file and calls the corresponding function
func fetchContent(source string) (string, error) {
	switch strings.Split(strings.ToLower(source), ":")[0] {
	case "http":
		Log(fmt.Sprintln("Source Type: [http] Detected, Fetching Source: ", source), level.debug)
		resp, err := Get(source)
		if err != nil {
			return "", err
		}
		return resp, nil
	case "https":
		Log(fmt.Sprintln("Source Type: [https] Detected, Fetching Source: ", source), level.debug)
		resp, err := Get(source)
		if err != nil {
			return "", err
		}
		return resp, nil
	case "s3":
		Log(fmt.Sprintln("Source Type: [s3] Detected, Fetching Source: ", source), level.debug)
		resp, err := S3Read(source)
		if err != nil {
			return "", err
		}
		return resp, nil
	default:
		Log(fmt.Sprintln("Source Type: [file] Detected, Fetching Source: ", source), level.debug)
		b, err := ioutil.ReadFile(source)
		if err != nil {
			return "", err
		}
		return string(b), nil
	}
}

// getName  - Checks if arg is url or file and returns stack name and filepath/url
func getSource(s string) (string, string, error) {
	if strings.Contains(s, "::") {
		vals := strings.Split(s, "::")
		if len(vals) < 2 {
			return "", "", errors.New(`Error, invalid url format --> Example: stackname::http://someurl OR stackname::s3://bucket/key`)
		}

		return vals[0], vals[1], nil

	}

	name := filepath.Base(strings.Replace(s, filepath.Ext(s), "", -1))
	return name, s, nil

}

// configReader parses the config YAML file with Viper
func configReader(conf string) error {
	viper.SetConfigType("yaml")

	cfg, err := fetchContent(conf)

	if err != nil {
		return err
	}

	Log(fmt.Sprintln("Reading Config String into viper:", cfg), level.debug)
	err = viper.ReadConfig(bytes.NewBuffer([]byte(cfg)))

	if err != nil {
		return err
	}

	// Get basic settings
	region = viper.GetString("region")
	project = viper.GetString("project")

	// Get all the values of variables under cloudformation and put them into a map
	cfvars = make(map[string]interface{})
	stacks = make(map[string]*stack)

	// Get Global values
	cfvars["global"] = viper.Get("global")

	Log(fmt.Sprintln("Keys identified in Config:", viper.AllKeys()), level.debug)

	for _, val := range viper.Sub("stacks").AllKeys() {
		s := strings.Split(val, ".")[0]
		stacks[s] = &stack{}
		key := "stacks." + s
		Log(fmt.Sprintf("Evaluating: [%s] in config"+"\n", s), level.debug)

		Log(fmt.Sprintf("Checking if [%s] exists in [%s]", key, strings.Join(viper.AllKeys(), ", ")), level.debug)
		if !strings.Contains(strings.Join(viper.AllKeys(), ""), key) {
			return fmt.Errorf("Key not found in config: %s", key)
		}

		for _, v := range viper.Sub(key).AllKeys() {
			Log(fmt.Sprintln("Processing: ", v), level.debug)
			// Using case statement for setting keyword values, added for scalability later.
			switch v {
			case "depends_on":
				dept := viper.Get(fmt.Sprintf("stacks.%s.%s", s, v)).([]interface{})
				Log(fmt.Sprintf("Found Dependency for [%s]: %s", s, dept), level.debug)
				stacks[s].dependsOn = dept
			default:
				// TODO: Nothing for now - more built-in values to come... maybe
			}

			// Get Cloudformation values
			cfvars[s] = viper.Get(fmt.Sprintf("stacks.%s.cf", s))
		}

		stacks[s].name = s
		stacks[s].setStackName()
	}

	return nil
}

// genTimeParser - Parses templates before deploying them...
func genTimeParser(source string) (string, error) {

	templ, err := fetchContent(source)
	if err != nil {
		return "", err
	}

	// Create template
	t, err := template.New("template").Funcs(genTimeFunctions).Parse(templ)
	if err != nil {
		return "", err
	}

	// so that we can write to string
	var doc bytes.Buffer
	t.Execute(&doc, cfvars)
	return doc.String(), nil
}

// deployTimeParser - Parses templates during deployment to resolve specfic Dependency functions like stackout...
func (s *stack) deployTimeParser() error {

	// Create template
	t, err := template.New("template").Delims("<<", ">>").Funcs(deployTimeFunctions).Parse(s.template)
	if err != nil {
		return err
	}

	// so that we can write to string
	var doc bytes.Buffer
	t.Execute(&doc, cfvars)
	s.template = doc.String()
	Log(fmt.Sprintf("Deploy Time Template Generate:\n%s", s.template), level.debug)

	return nil
}
<|endoftext|>


<commit_before>// +build go1.2,!go1.4

// Copyright 2015 Robert S. Gerus. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package config

import (
	"fmt"
	"io/ioutil"
	"log"
	"reflect"
	"testing"
)

var emptyContext map[string]string

// func Lookup(context map[string]string, key string) interface{}
var testsLookup = []struct {
	key           string
	expectedValue string
}{
	{
		key:           "TestLookup",
		expectedValue: "this can be anything you can imagine, but now it's just a string",
	},
	{
		key:           "NotExisting",
		expectedValue: "<nil>",
	},
}

func TestLookup(t *testing.T) {
	for i, e := range testsLookup {
		v := Lookup(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupString(context map[string]string, key string) string
var testsLookupString = []struct {
	key           string
	expectedValue string
}{
	{
		key:           "TestLookupString",
		expectedValue: "this should be a string",
	},
	{
		key:           "NotExisting",
		expectedValue: "",
	},
}

func TestLookupString(t *testing.T) {
	for i, e := range testsLookupString {
		v := LookupString(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupInt(context map[string]string, key string) int
var testsLookupInt = []struct {
	key           string
	expectedValue int
}{
	{
		key:           "ThisWillBeAnInt",
		expectedValue: 42,
	},
	{
		key:           "NotExisting",
		expectedValue: -1,
	},
}

func TestLookupInt(t *testing.T) {
	for i, e := range testsLookupInt {
		v := LookupInt(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupStringSlice(context map[string]string, key string) []string
var testsLookupStringSlice = []struct {
	key           string
	expectedValue []string
}{
	{
		// TestLookupStringSlice return slice is ordered
		key:           "TestLookupStringSlice",
		expectedValue: []string{"a", "am", "be", "going", "i", "slice", "to"},
	},
	{
		key:           "NotExisting",
		expectedValue: []string{""},
	},
}

func TestLookupStringSlice(t *testing.T) {
	for i, e := range testsLookupStringSlice {
		v := LookupStringSlice(emptyContext, e.key)
		if fmt.Sprintf("%+v", v) != fmt.Sprintf("%+v", e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

// func LookupStringMap(context map[string]string, key string) map[string]bool
var testsLookupStringMap = []struct {
	key           string
	expectedValue map[string]bool
}{
	{
		key: "TestLookupStringMap",
		expectedValue: map[string]bool{
			"to":              true,
			"be":              true,
			"a":               true,
			"map[string]bool": true,
			"I":               true,
			"want":            true,
		},
	},
	{
		key:           "NotExisting",
		expectedValue: map[string]bool{},
	},
}

func TestLookupStringMap(t *testing.T) {
	for i, e := range testsLookupStringMap {
		v := LookupStringMap(emptyContext, e.key)
		if !reflect.DeepEqual(v, e.expectedValue) {
			t.Log("test#", i+1, "Lookup key", e.key)
			t.Logf("expected: %+v\n", e.expectedValue)
			t.Logf("result  : %+v\n", v)
			t.Fail()
		}
	}
}

func configLookupHelper(map[string]string) []string {
	return []string{"this/thing/does/not/exist.json", ".testconfig.json"}
}

func init() {
	SetFileListBuilder(configLookupHelper)
	log.SetOutput(ioutil.Discard)
}
<commit_msg>We only use go1.5 anyway now.<commit_after><|endoftext|>


<commit_before>// Package authcookie implements creation and verification of signed
// authentication cookies.
//
// Cookie is a Base64 encoded (using URLEncoding, from RFC 4648) string, which
// consists of concatenation of expiration time, login, and signature:
//
// 	expiration time || login || signature
//
// where expiration time is the number of seconds since Unix epoch UTC
// indicating when this cookie must expire (4 bytes, big-endian, uint32), login
// is a byte string of arbitrary length (at least 1 byte, not null-terminated),
// and signature is 32 bytes of HMAC-SHA256(expiration_time || login, k), where
// k = HMAC-SHA256(expiration_time || login, secret key).
//
// Example:
//
//	secret := []byte("my secret key")
//
//	// Generate cookie valid for 24 hours for user "bender"
//	cookie := authcookie.NewSinceNow("bender", 24 * time.Hour, secret)
//
//	// cookie is now:
//	// Tajh02JlbmRlcskYMxowgwPj5QZ94jaxhDoh3n0Yp4hgGtUpeO0YbMTY
//	// send it to user's browser..
//
//	// To authenticate a user later, receive cookie and:
//	login := authcookie.Login(cookie, secret)
//	if login != "" {
//		// access for login granted
//	} else {
//		// access denied
//	}
//
// Note that login and expiration time are not encrypted, they are only signed
// and Base64 encoded.
//
// For safety, the maximum length of base64-decoded cookie is limited to 1024
// bytes.
package authcookie

import (
	"crypto/hmac"
	"crypto/subtle"
	"encoding/base64"
	"encoding/binary"
	"errors"
	"time"
)

const (
	decodedMinLength = 4 /*expiration*/ + 1 /*login*/ + 32 /*signature*/
	decodedMaxLength = 1024 /* maximum decoded length, for safety */
)

// MinLength is the minimum allowed length of cookie string.
//
// It is useful for avoiding DoS attacks with too long cookies: before passing
// a cookie to Parse or Login functions, check that it has length less than the
// [maximum login length allowed in your application] + MinLength.
var MinLength = base64.URLEncoding.EncodedLen(decodedMinLength)

func getSignature(b []byte, secret []byte) []byte {
	keym := hmac.NewSHA256(secret)
	keym.Write(b)
	m := hmac.NewSHA256(keym.Sum(nil))
	m.Write(b)
	return m.Sum(nil)
}

var (
	ErrMalformedCookie = errors.New("malformed cookie")
	ErrWrongSignature  = errors.New("wrong cookie signature")
)

// New returns a signed authentication cookie for the given login,
// expiration time, and secret key.
// If the login is empty, the function returns an empty string.
func New(login string, expires time.Time, secret []byte) string {
	if login == "" {
		return ""
	}
	llen := len(login)
	b := make([]byte, llen+4+32)
	// Put expiration time.
	binary.BigEndian.PutUint32(b, uint32(expires.Unix()))
	// Put login.
	copy(b[4:], []byte(login))
	// Calculate and put signature.
	sig := getSignature([]byte(b[:4+llen]), secret)
	copy(b[4+llen:], sig)
	// Base64-encode.
	return base64.URLEncoding.EncodeToString(b)
}

// NewSinceNow returns a signed authetication cookie for the given login,
// duration since current time, and secret key.
func NewSinceNow(login string, dur time.Duration, secret []byte) string {
	return New(login, time.Now().Add(dur), secret)
}

// Parse verifies the given cookie with the secret key and returns login and
// expiration time extracted from the cookie. If the cookie fails verification
// or is not well-formed, the function returns an error.
//
// Callers must:
//
// 1. Check for the returned error and deny access if it's present.
//
// 2. Check the returned expiration time and deny access if it's in the past.
//
func Parse(cookie string, secret []byte) (login string, expires time.Time, err error) {
	blen := base64.URLEncoding.DecodedLen(len(cookie))
	// Avoid allocation if cookie is too short or too long.
	if blen < decodedMinLength || blen > decodedMaxLength {
		err = ErrMalformedCookie
		return
	}
	b, err := base64.URLEncoding.DecodeString(cookie)
	if err != nil {
		return
	}
	// Decoded length may be different from max length, which
	// we allocated, so check it, and set new length for b.
	blen = len(b)
	if blen < decodedMinLength {
		err = ErrMalformedCookie
		return
	}
	b = b[:blen]

	sig := b[blen-32:]
	data := b[:blen-32]

	realSig := getSignature(data, secret)
	if subtle.ConstantTimeCompare(realSig, sig) != 1 {
		err = ErrWrongSignature
		return
	}
	expires = time.Unix(int64(binary.BigEndian.Uint32(data[:4])), 0)
	login = string(data[4:])
	return
}

// Login returns a valid login extracted from the given cookie and verified
// using the given secret key.  If verification fails or the cookie expired,
// the function returns an empty string.
func Login(cookie string, secret []byte) string {
	l, exp, err := Parse(cookie, secret)
	if err != nil || exp.Before(time.Now()) {
		return ""
	}
	return l
}
<commit_msg>Pass hash to hmac.New instead of using NewSHA256.<commit_after>// Package authcookie implements creation and verification of signed
// authentication cookies.
//
// Cookie is a Base64 encoded (using URLEncoding, from RFC 4648) string, which
// consists of concatenation of expiration time, login, and signature:
//
// 	expiration time || login || signature
//
// where expiration time is the number of seconds since Unix epoch UTC
// indicating when this cookie must expire (4 bytes, big-endian, uint32), login
// is a byte string of arbitrary length (at least 1 byte, not null-terminated),
// and signature is 32 bytes of HMAC-SHA256(expiration_time || login, k), where
// k = HMAC-SHA256(expiration_time || login, secret key).
//
// Example:
//
//	secret := []byte("my secret key")
//
//	// Generate cookie valid for 24 hours for user "bender"
//	cookie := authcookie.NewSinceNow("bender", 24 * time.Hour, secret)
//
//	// cookie is now:
//	// Tajh02JlbmRlcskYMxowgwPj5QZ94jaxhDoh3n0Yp4hgGtUpeO0YbMTY
//	// send it to user's browser..
//
//	// To authenticate a user later, receive cookie and:
//	login := authcookie.Login(cookie, secret)
//	if login != "" {
//		// access for login granted
//	} else {
//		// access denied
//	}
//
// Note that login and expiration time are not encrypted, they are only signed
// and Base64 encoded.
//
// For safety, the maximum length of base64-decoded cookie is limited to 1024
// bytes.
package authcookie

import (
	"crypto/hmac"
	"crypto/sha256"
	"crypto/subtle"
	"encoding/base64"
	"encoding/binary"
	"errors"
	"time"
)

const (
	decodedMinLength = 4 /*expiration*/ + 1 /*login*/ + 32 /*signature*/
	decodedMaxLength = 1024                                /* maximum decoded length, for safety */
)

// MinLength is the minimum allowed length of cookie string.
//
// It is useful for avoiding DoS attacks with too long cookies: before passing
// a cookie to Parse or Login functions, check that it has length less than the
// [maximum login length allowed in your application] + MinLength.
var MinLength = base64.URLEncoding.EncodedLen(decodedMinLength)

func getSignature(b []byte, secret []byte) []byte {
	keym := hmac.New(sha256.New, secret)
	keym.Write(b)
	m := hmac.New(sha256.New, keym.Sum(nil))
	m.Write(b)
	return m.Sum(nil)
}

var (
	ErrMalformedCookie = errors.New("malformed cookie")
	ErrWrongSignature  = errors.New("wrong cookie signature")
)

// New returns a signed authentication cookie for the given login,
// expiration time, and secret key.
// If the login is empty, the function returns an empty string.
func New(login string, expires time.Time, secret []byte) string {
	if login == "" {
		return ""
	}
	llen := len(login)
	b := make([]byte, llen+4+32)
	// Put expiration time.
	binary.BigEndian.PutUint32(b, uint32(expires.Unix()))
	// Put login.
	copy(b[4:], []byte(login))
	// Calculate and put signature.
	sig := getSignature([]byte(b[:4+llen]), secret)
	copy(b[4+llen:], sig)
	// Base64-encode.
	return base64.URLEncoding.EncodeToString(b)
}

// NewSinceNow returns a signed authetication cookie for the given login,
// duration since current time, and secret key.
func NewSinceNow(login string, dur time.Duration, secret []byte) string {
	return New(login, time.Now().Add(dur), secret)
}

// Parse verifies the given cookie with the secret key and returns login and
// expiration time extracted from the cookie. If the cookie fails verification
// or is not well-formed, the function returns an error.
//
// Callers must:
//
// 1. Check for the returned error and deny access if it's present.
//
// 2. Check the returned expiration time and deny access if it's in the past.
//
func Parse(cookie string, secret []byte) (login string, expires time.Time, err error) {
	blen := base64.URLEncoding.DecodedLen(len(cookie))
	// Avoid allocation if cookie is too short or too long.
	if blen < decodedMinLength || blen > decodedMaxLength {
		err = ErrMalformedCookie
		return
	}
	b, err := base64.URLEncoding.DecodeString(cookie)
	if err != nil {
		return
	}
	// Decoded length may be different from max length, which
	// we allocated, so check it, and set new length for b.
	blen = len(b)
	if blen < decodedMinLength {
		err = ErrMalformedCookie
		return
	}
	b = b[:blen]

	sig := b[blen-32:]
	data := b[:blen-32]

	realSig := getSignature(data, secret)
	if subtle.ConstantTimeCompare(realSig, sig) != 1 {
		err = ErrWrongSignature
		return
	}
	expires = time.Unix(int64(binary.BigEndian.Uint32(data[:4])), 0)
	login = string(data[4:])
	return
}

// Login returns a valid login extracted from the given cookie and verified
// using the given secret key.  If verification fails or the cookie expired,
// the function returns an empty string.
func Login(cookie string, secret []byte) string {
	l, exp, err := Parse(cookie, secret)
	if err != nil || exp.Before(time.Now()) {
		return ""
	}
	return l
}
<|endoftext|>


<commit_before>// Copyright 2019 The go-python Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package bind

import (
	"fmt"
)

func (g *pyGen) genConst(c *Const) {
	if isPyCompatVar(c.sym) != nil {
		return
	}
	if c.sym.isSignature() {
		return
	}
	g.genConstValue(c)
}

func (g *pyGen) genVar(v *Var) {
	if isPyCompatVar(v.sym) != nil {
		return
	}
	if v.sym.isSignature() {
		return
	}
	g.genVarGetter(v)
	if !v.sym.isArray() {
		g.genVarSetter(v)
	}
}

func (g *pyGen) genVarGetter(v *Var) {
	gopkg := g.pkg.Name()
	pkgname := g.outname
	cgoFn := v.Name() // plain name is the getter
	qCgoFn := gopkg + "_" + cgoFn
	qFn := "_" + pkgname + "." + qCgoFn
	qVn := gopkg + "." + v.Name()

	g.pywrap.Printf("def %s():\n", cgoFn)
	g.pywrap.Indent()
	g.pywrap.Printf("%s\n%s Gets Go Variable: %s\n%s\n%s\n", `"""`, cgoFn, qVn, v.doc, `"""`)
	if v.sym.hasHandle() {
		cvnm := v.sym.pyPkgId(g.pkg.pkg)
		g.pywrap.Printf("return %s(handle=%s())\n", cvnm, qFn)
	} else {
		g.pywrap.Printf("return %s()\n", qFn)
	}
	g.pywrap.Outdent()
	g.pywrap.Printf("\n")

	g.gofile.Printf("//export %s\n", qCgoFn)
	g.gofile.Printf("func %s() %s {\n", qCgoFn, v.sym.cgoname)
	g.gofile.Indent()
	g.gofile.Printf("return ")
	if v.sym.go2py != "" {
		if v.sym.hasHandle() && !v.sym.isPtrOrIface() {
			g.gofile.Printf("%s(&%s)%s", v.sym.go2py, qVn, v.sym.go2pyParenEx)
		} else {
			g.gofile.Printf("%s(%s)%s", v.sym.go2py, qVn, v.sym.go2pyParenEx)
		}
	} else {
		g.gofile.Printf("%s", qVn)
	}
	g.gofile.Printf("\n")
	g.gofile.Outdent()
	g.gofile.Printf("}\n\n")

	g.pybuild.Printf("mod.add_function('%s', retval('%s'), [])\n", qCgoFn, v.sym.cpyname)
}

func (g *pyGen) genVarSetter(v *Var) {
	gopkg := g.pkg.Name()
	pkgname := g.outname
	cgoFn := fmt.Sprintf("Set_%s", v.Name())
	qCgoFn := gopkg + "_" + cgoFn
	qFn := "_" + pkgname + "." + qCgoFn
	qVn := gopkg + "." + v.Name()

	g.pywrap.Printf("def %s(value):\n", cgoFn)
	g.pywrap.Indent()
	g.pywrap.Printf("%s\n%s Sets Go Variable: %s\n%s\n%s\n", `"""`, cgoFn, qVn, v.doc, `"""`)
	g.pywrap.Printf("if isinstance(value, go.GoClass):\n")
	g.pywrap.Indent()
	g.pywrap.Printf("%s(value.handle)\n", qFn)
	g.pywrap.Outdent()
	g.pywrap.Printf("else:\n")
	g.pywrap.Indent()
	g.pywrap.Printf("%s(value)\n", qFn)
	g.pywrap.Outdent()
	g.pywrap.Outdent()
	g.pywrap.Printf("\n")

	g.gofile.Printf("//export %s\n", qCgoFn)
	g.gofile.Printf("func %s(val %s) {\n", qCgoFn, v.sym.cgoname)
	g.gofile.Indent()
	if v.sym.py2go != "" {
		g.gofile.Printf("%s = %s(val)%s", qVn, v.sym.py2go, v.sym.py2goParenEx)
	} else {
		g.gofile.Printf("%s = val", qVn)
	}
	g.gofile.Printf("\n")
	g.gofile.Outdent()
	g.gofile.Printf("}\n\n")

	g.pybuild.Printf("mod.add_function('%s', None, [param('%s', 'val')])\n", qCgoFn, v.sym.cpyname)
}

func (g *pyGen) genConstValue(c *Const) {
	// constants go directly into wrapper as-is
	g.pywrap.Printf("%s = %s\n", c.GoName(), c.obj.Val().ExactString())
}
<commit_msg>bind: use python case for booleans<commit_after>// Copyright 2019 The go-python Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package bind

import (
	"fmt"
)

func (g *pyGen) genConst(c *Const) {
	if isPyCompatVar(c.sym) != nil {
		return
	}
	if c.sym.isSignature() {
		return
	}
	g.genConstValue(c)
}

func (g *pyGen) genVar(v *Var) {
	if isPyCompatVar(v.sym) != nil {
		return
	}
	if v.sym.isSignature() {
		return
	}
	g.genVarGetter(v)
	if !v.sym.isArray() {
		g.genVarSetter(v)
	}
}

func (g *pyGen) genVarGetter(v *Var) {
	gopkg := g.pkg.Name()
	pkgname := g.outname
	cgoFn := v.Name() // plain name is the getter
	qCgoFn := gopkg + "_" + cgoFn
	qFn := "_" + pkgname + "." + qCgoFn
	qVn := gopkg + "." + v.Name()

	g.pywrap.Printf("def %s():\n", cgoFn)
	g.pywrap.Indent()
	g.pywrap.Printf("%s\n%s Gets Go Variable: %s\n%s\n%s\n", `"""`, cgoFn, qVn, v.doc, `"""`)
	if v.sym.hasHandle() {
		cvnm := v.sym.pyPkgId(g.pkg.pkg)
		g.pywrap.Printf("return %s(handle=%s())\n", cvnm, qFn)
	} else {
		g.pywrap.Printf("return %s()\n", qFn)
	}
	g.pywrap.Outdent()
	g.pywrap.Printf("\n")

	g.gofile.Printf("//export %s\n", qCgoFn)
	g.gofile.Printf("func %s() %s {\n", qCgoFn, v.sym.cgoname)
	g.gofile.Indent()
	g.gofile.Printf("return ")
	if v.sym.go2py != "" {
		if v.sym.hasHandle() && !v.sym.isPtrOrIface() {
			g.gofile.Printf("%s(&%s)%s", v.sym.go2py, qVn, v.sym.go2pyParenEx)
		} else {
			g.gofile.Printf("%s(%s)%s", v.sym.go2py, qVn, v.sym.go2pyParenEx)
		}
	} else {
		g.gofile.Printf("%s", qVn)
	}
	g.gofile.Printf("\n")
	g.gofile.Outdent()
	g.gofile.Printf("}\n\n")

	g.pybuild.Printf("mod.add_function('%s', retval('%s'), [])\n", qCgoFn, v.sym.cpyname)
}

func (g *pyGen) genVarSetter(v *Var) {
	gopkg := g.pkg.Name()
	pkgname := g.outname
	cgoFn := fmt.Sprintf("Set_%s", v.Name())
	qCgoFn := gopkg + "_" + cgoFn
	qFn := "_" + pkgname + "." + qCgoFn
	qVn := gopkg + "." + v.Name()

	g.pywrap.Printf("def %s(value):\n", cgoFn)
	g.pywrap.Indent()
	g.pywrap.Printf("%s\n%s Sets Go Variable: %s\n%s\n%s\n", `"""`, cgoFn, qVn, v.doc, `"""`)
	g.pywrap.Printf("if isinstance(value, go.GoClass):\n")
	g.pywrap.Indent()
	g.pywrap.Printf("%s(value.handle)\n", qFn)
	g.pywrap.Outdent()
	g.pywrap.Printf("else:\n")
	g.pywrap.Indent()
	g.pywrap.Printf("%s(value)\n", qFn)
	g.pywrap.Outdent()
	g.pywrap.Outdent()
	g.pywrap.Printf("\n")

	g.gofile.Printf("//export %s\n", qCgoFn)
	g.gofile.Printf("func %s(val %s) {\n", qCgoFn, v.sym.cgoname)
	g.gofile.Indent()
	if v.sym.py2go != "" {
		g.gofile.Printf("%s = %s(val)%s", qVn, v.sym.py2go, v.sym.py2goParenEx)
	} else {
		g.gofile.Printf("%s = val", qVn)
	}
	g.gofile.Printf("\n")
	g.gofile.Outdent()
	g.gofile.Printf("}\n\n")

	g.pybuild.Printf("mod.add_function('%s', None, [param('%s', 'val')])\n", qCgoFn, v.sym.cpyname)
}

func (g *pyGen) genConstValue(c *Const) {
	// constants go directly into wrapper as-is
	val := c.obj.Val().ExactString()
	switch val {
	case "true":
		val = "True"
	case "false":
		val = "False"
	}
	g.pywrap.Printf("%s = %s\n", c.GoName(), val)
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ses"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsSesTemplate() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSesTemplateCreate,
		Read:   resourceAwsSesTemplateRead,
		Update: resourceAwsSesTemplateUpdate,
		Delete: resourceAwsSesTemplateDelete,

		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateSesTemplateName,
			},
			"html": {
				Type:         schema.TypeString,
				Optional:     true,
				ValidateFunc: validateSesTemplateHtml,
			},
			"subject": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"text": {
				Type:         schema.TypeString,
				Optional:     true,
				ValidateFunc: validateSesTemplateText,
			},
		},
	}
}
func resourceAwsSesTemplateCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).sesConn

	templateName := d.Get("name").(string)

	template := ses.Template{
		TemplateName: aws.String(templateName),
	}

	if v, ok := d.GetOk("html"); ok {
		template.HtmlPart = aws.String(v.(string))
	}

	if v, ok := d.GetOk("subject"); ok {
		template.SubjectPart = aws.String(v.(string))
	}

	if v, ok := d.GetOk("text"); ok {
		template.TextPart = aws.String(v.(string))
	}

	input := ses.CreateTemplateInput{
		Template: &template,
	}

	log.Printf("[DEBUG] Creating SES template: %#v", input)
	_, err := conn.CreateTemplate(&input)
	if err != nil {
		return fmt.Errorf("Creating SES template failed: %s", err.Error())
	}
	d.SetId(templateName)

	return resourceAwsSesTemplateRead(d, meta)
}

func resourceAwsSesTemplateRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).sesConn
	input := ses.GetTemplateInput{
		TemplateName: aws.String(d.Id()),
	}

	log.Printf("[DEBUG] Reading SES template: %#v", input)
	gto, err := conn.GetTemplate(&input)
	if err != nil {
		if isAWSErr(err, "TemplateDoesNotExist", "") {
			log.Printf("[WARN] SES template %q not found, removing from state", d.Id())
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Reading SES template '%s' failed: %s", *input.TemplateName, err.Error())
	}

	d.Set("html", gto.Template.HtmlPart)
	d.Set("name", gto.Template.TemplateName)
	d.Set("subject", gto.Template.SubjectPart)
	d.Set("text", gto.Template.TextPart)

	return nil
}

func resourceAwsSesTemplateUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).sesConn

	templateName := d.Id()

	template := ses.Template{
		HtmlPart:     aws.String(d.Get("html").(string)),
		TemplateName: aws.String(templateName),
		SubjectPart:  aws.String(d.Get("subject").(string)),
		TextPart:     aws.String(d.Get("text").(string)),
	}

	input := ses.UpdateTemplateInput{
		Template: &template,
	}

	log.Printf("[DEBUG] Update SES template: %#v", input)
	_, err := conn.UpdateTemplate(&input)
	if err != nil {
		return fmt.Errorf("Updating SES template '%s' failed: %s", templateName, err.Error())
	}

	return resourceAwsSesTemplateRead(d, meta)
}

func resourceAwsSesTemplateDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).sesConn
	input := ses.DeleteTemplateInput{
		TemplateName: aws.String(d.Id()),
	}

	log.Printf("[DEBUG] Delete SES template: %#v", input)
	_, err := conn.DeleteTemplate(&input)
	if err != nil {
		return fmt.Errorf("Deleting SES template '%s' failed: %s", *input.TemplateName, err.Error())
	}
	return nil
}
<commit_msg>resource/aws_ses_template: Send only specified attributes for update<commit_after>package aws

import (
	"fmt"
	"log"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ses"
	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsSesTemplate() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsSesTemplateCreate,
		Read:   resourceAwsSesTemplateRead,
		Update: resourceAwsSesTemplateUpdate,
		Delete: resourceAwsSesTemplateDelete,

		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"name": {
				Type:         schema.TypeString,
				Required:     true,
				ForceNew:     true,
				ValidateFunc: validateSesTemplateName,
			},
			"html": {
				Type:         schema.TypeString,
				Optional:     true,
				ValidateFunc: validateSesTemplateHtml,
			},
			"subject": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"text": {
				Type:         schema.TypeString,
				Optional:     true,
				ValidateFunc: validateSesTemplateText,
			},
		},
	}
}
func resourceAwsSesTemplateCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).sesConn

	templateName := d.Get("name").(string)

	template := ses.Template{
		TemplateName: aws.String(templateName),
	}

	if v, ok := d.GetOk("html"); ok {
		template.HtmlPart = aws.String(v.(string))
	}

	if v, ok := d.GetOk("subject"); ok {
		template.SubjectPart = aws.String(v.(string))
	}

	if v, ok := d.GetOk("text"); ok {
		template.TextPart = aws.String(v.(string))
	}

	input := ses.CreateTemplateInput{
		Template: &template,
	}

	log.Printf("[DEBUG] Creating SES template: %#v", input)
	_, err := conn.CreateTemplate(&input)
	if err != nil {
		return fmt.Errorf("Creating SES template failed: %s", err.Error())
	}
	d.SetId(templateName)

	return resourceAwsSesTemplateRead(d, meta)
}

func resourceAwsSesTemplateRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).sesConn
	input := ses.GetTemplateInput{
		TemplateName: aws.String(d.Id()),
	}

	log.Printf("[DEBUG] Reading SES template: %#v", input)
	gto, err := conn.GetTemplate(&input)
	if err != nil {
		if isAWSErr(err, "TemplateDoesNotExist", "") {
			log.Printf("[WARN] SES template %q not found, removing from state", d.Id())
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Reading SES template '%s' failed: %s", *input.TemplateName, err.Error())
	}

	d.Set("html", gto.Template.HtmlPart)
	d.Set("name", gto.Template.TemplateName)
	d.Set("subject", gto.Template.SubjectPart)
	d.Set("text", gto.Template.TextPart)

	return nil
}

func resourceAwsSesTemplateUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).sesConn

	templateName := d.Id()

	template := ses.Template{
		TemplateName: aws.String(templateName),
	}

	if v, ok := d.GetOk("html"); ok {
		template.HtmlPart = aws.String(v.(string))
	}

	if v, ok := d.GetOk("subject"); ok {
		template.SubjectPart = aws.String(v.(string))
	}

	if v, ok := d.GetOk("text"); ok {
		template.TextPart = aws.String(v.(string))
	}

	input := ses.UpdateTemplateInput{
		Template: &template,
	}

	log.Printf("[DEBUG] Update SES template: %#v", input)
	_, err := conn.UpdateTemplate(&input)
	if err != nil {
		return fmt.Errorf("Updating SES template '%s' failed: %s", templateName, err.Error())
	}

	return resourceAwsSesTemplateRead(d, meta)
}

func resourceAwsSesTemplateDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).sesConn
	input := ses.DeleteTemplateInput{
		TemplateName: aws.String(d.Id()),
	}

	log.Printf("[DEBUG] Delete SES template: %#v", input)
	_, err := conn.DeleteTemplate(&input)
	if err != nil {
		return fmt.Errorf("Deleting SES template '%s' failed: %s", *input.TemplateName, err.Error())
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	zmq "github.com/pebbe/zmq4"
	"log"
	"time"
)

func main() {
	// create connection socket
	context, err := zmq.NewContext()

	socket, err := context.NewSocket(zmq.ROUTER)
	defer socket.Close()

	location := "tcp://127.0.0.1:7171"

	socket.Bind(location)

	poller := zmq.NewPoller()
	poller.Add(socket, zmq.POLLIN)

	if err != nil {
		log.Printf("Could not start broker: %v\n", err)
		return
	}

	log.Println("Now listening at: " + location)

	workers := map[string]string{}

	for {
		polled, err := poller.Poll(time.Second * 10)

		if err != nil {
			log.Println("E: Interrupted")
			break //  Interrupted
		}

		if len(polled) > 0 {
			msg, err := socket.RecvMessage(0)

			if err != nil {
				log.Println("E: Interrupted")
				break //  Interrupted
			}

			sender := msg[0]
			protocol := msg[1]
			command := msg[2]
			data := msg[3:]

			if protocol == "MDPW02" && command == "1" {
				log.Printf("I: %s is a worker\n", sender)
				workers[protocol] = sender
				log.Printf("I: workers - %q\n", workers)
			}

			if protocol == "MDPW02" && command == "2" {
				log.Printf("I: %s replying\n", sender)
				correlationID := data[1]
				response := data[2:]
				socket.SendMessage(data[0], correlationID, "SUCCESS", response)
			}

			if protocol == "MDPC02" && command == "1" {
				log.Printf("I: %s is a client\n", sender)
				if len(workers) > 0 {
					// send message to worker, don't care about services yet
					log.Printf("I: sending data to worker %s", workers["MDPW02"])
					msgCount, err := socket.SendMessage(workers["MDPW02"], sender, data)
					if err != nil {
						log.Printf("! %x", err)
					} else {
						log.Printf("I: sent %i bytes", msgCount)
					}
				}
			}

			log.Printf("I: received message: %q\n", msg)
			log.Printf("I: sender: %q\n", sender)
			log.Printf("I: protocol: %q\n", protocol)
			log.Printf("I: command: %q\n", command)
			log.Printf("I: data: %q\n", data)
		}
	}
}
<commit_msg>Add service directory to broker.<commit_after>package main

import (
	"errors"
	"fmt"
	zmq "github.com/pebbe/zmq4"
	"log"
	"time"
)

type ServiceDirectory struct {
	services map[string]*ServiceType
	index    map[string][]string
	workers  map[string]*Worker
}

type ServiceType struct {
	name      string
	instances map[string]*Worker
}

type Worker struct {
	name     string // human readable id
	identity string // routing frame
}

func (serviceDirectory *ServiceDirectory) AddWorker(identity string, serviceTypeName string, serviceInstanceName string) {
	// create or return worker
	worker, exists := serviceDirectory.workers[identity]

	if exists == false {
		name := fmt.Sprintf("%q", identity)
		worker = &Worker{
			identity: identity,
			name:     name,
		}

		serviceDirectory.workers[identity] = worker
	}

	log.Printf("?: Worker - %q", worker)

	// create or return serviceType
	serviceType, exists := serviceDirectory.services[serviceTypeName]

	if exists == false {
		serviceType = &ServiceType{
			name:      serviceTypeName,
			instances: make(map[string]*Worker),
		}

		serviceDirectory.services[serviceTypeName] = serviceType
	}

	// register serviceInstance, or err if already found
	_, exists = serviceType.instances[serviceInstanceName]

	if exists == false {
		log.Printf("Adding worker %q to %s.%s", worker, serviceTypeName, serviceInstanceName)
		serviceType.instances[serviceInstanceName] = worker
	}

	// add to index
	serviceDirectory.index[serviceTypeName] = append(serviceDirectory.index[serviceTypeName], serviceInstanceName)
}

func (serviceDirectory *ServiceDirectory) WorkerForService(serviceTypeName string, serviceInstanceName string) (serviceWorker *Worker, err error) {
	// check for serviceType
	serviceType, exists := serviceDirectory.index[serviceTypeName]

	if !exists {
		err = errors.New("Unknown serviceType")
		return
	}

	log.Printf("Found serviceType %q", serviceType)

	for _, serviceInstance := range serviceType {
		if serviceInstance == serviceInstanceName {
			log.Printf("Match: %q", serviceDirectory.services[serviceTypeName])
			serviceWorker, exists = serviceDirectory.services[serviceTypeName].instances[serviceInstanceName]
			if exists {
				log.Printf("Found serviceWorker: %q", serviceWorker)
				return
			}
		}
	}

	err = errors.New("Unknown serviceInstance")
	return
}

func main() {
	// create connection socket
	context, err := zmq.NewContext()

	socket, err := context.NewSocket(zmq.ROUTER)
	defer socket.Close()

	location := "tcp://127.0.0.1:7171"

	socket.Bind(location)

	poller := zmq.NewPoller()
	poller.Add(socket, zmq.POLLIN)

	if err != nil {
		log.Printf("Could not start broker: %v\n", err)
		return
	}

	log.Println("Now listening at: " + location)

	serviceDirectory := ServiceDirectory{
		services: make(map[string]*ServiceType),
		workers:  make(map[string]*Worker),
		index:    make(map[string][]string),
	}

	for {
		polled, err := poller.Poll(time.Second * 10)

		if err != nil {
			log.Println("E: Interrupted")
			break //  Interrupted
		}

		if len(polled) > 0 {
			msg, err := socket.RecvMessage(0)

			if err != nil {
				log.Println("E: Interrupted")
				break //  Interrupted
			}

			sender := msg[0]
			protocol := msg[1]
			command := msg[2]
			data := msg[3:]

			if protocol == "MDPW02" && command == "1" {
				log.Printf("I: %s is a worker\n", sender)
				serviceType := data[0]
				for _, serviceInstance := range data[1:] {
					log.Printf("?: %q\n", serviceInstance)
					serviceDirectory.AddWorker(sender, serviceType, serviceInstance)
				}
				log.Printf("I: services - %q\n", serviceDirectory.index)
			}

			if protocol == "MDPW02" && command == "2" {
				log.Printf("I: %s replying\n", sender)
				correlationID := data[1]
				response := data[2:]
				socket.SendMessage(data[0], correlationID, "SUCCESS", response)
			}

			if protocol == "MDPC02" && command == "1" {
				log.Printf("I: %s is a client\n", sender)
				log.Printf("I: data - %q", data)

				//correlationId := data[0]
				serviceType := data[1]
				serviceInstance := data[2]
				//msg := data[3:]

				worker, err := serviceDirectory.WorkerForService(serviceType, serviceInstance)

				if err != nil {
					log.Printf("I: No worker for %s.%s", serviceType, serviceInstance)
					return
				}
				log.Printf("I: sending data to worker %s", worker.name)
				msgCount, err := socket.SendMessage(worker.identity, sender, data)
				if err != nil {
					log.Printf("! %x", err)
				} else {
					log.Printf("I: sent %i bytes", msgCount)
				}
			}

			//log.Printf("I: received message: %q\n", msg)
			//log.Printf("I: sender: %q\n", sender)
			//log.Printf("I: protocol: %q\n", protocol)
			//log.Printf("I: command: %q\n", command)
			//log.Printf("I: data: %q\n", data)
		}
	}
}
<|endoftext|>


<commit_before>//
// go-sonos
// ========
//
// Copyright (c) 2012, Ian T. Richards <ianr@panix.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in the
//     documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

package sonos

import (
	"github.com/ianr0bkny/go-sonos/model"
	"github.com/ianr0bkny/go-sonos/upnp"
	_ "log"
	"strings"
)

const (
	ObjectID_Attributes    = "A:"
	ObjectID_MusicShares   = "S:"
	ObjectID_Queues        = "Q:"
	ObjectID_SavedQueues   = "SQ:"
	ObjectID_InternetRadio = "R:"
	ObjectID_EntireNetwork = "EN:"
	//
	ObjectID_Queue_AVT_Instance_0 = "Q:0"
	//
	ObjectID_Attribute_Genres = "A:GENRE"
	ObjectID_Attribute_Album  = "A:ALBUM"
	ObjectID_Attribute_Artist = "A:ARTIST"
)

func (this *Sonos) GetRootLevelChildren() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		upnp.BrowseObjectID_Root,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListQueues() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_Queues,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListSavedQueues() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_SavedQueues,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListInternetRadio() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_InternetRadio,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListAttributes() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_Attributes,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListMusicShares() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_MusicShares,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetAllGenres() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_Attribute_Genres,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func objectIDForGenre(genre string) string {
	return strings.Join([]string{ObjectID_Attribute_Genres, genre}, "/")
}

func objectIDForAlbum(album string) string {
	return strings.Join([]string{ObjectID_Attribute_Album, album}, "/")
}

func objectIDForArtist(artist string) string {
	return strings.Join([]string{ObjectID_Attribute_Artist, artist}, "/")
}

func (this *Sonos) GetGenreArtists(genre string) (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		objectIDForGenre(genre),
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListChildren(objectId string) (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		objectId,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetMetadata(objectId string) (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		objectId,
		upnp.BrowseFlag_BrowseMetadata,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetDirectChildren(objectId string) (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		objectId,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetQueueContents() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_Queue_AVT_Instance_0,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetAlbumTracks(album string) (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		objectIDForAlbum(album),
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetTrackFromAlbum(album, track string) ([]model.Object, error) {
	if tracks, err := this.GetAlbumTracks(album); nil != err {
		return nil, err
	} else {
		var track_objs []model.Object
		for _, track_obj := range tracks {
			if track_obj.Title() == track {
				track_objs = append(track_objs, track_obj)
			}
		}
		return track_objs, nil
	}
	panic("unreachable")
}

func (this *Sonos) GetArtistAlbums(artist string) (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		objectIDForArtist(artist),
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}
<commit_msg>Checkpoint<commit_after>//
// go-sonos
// ========
//
// Copyright (c) 2012, Ian T. Richards <ianr@panix.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright
//     notice, this list of conditions and the following disclaimer in the
//     documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

package sonos

import (
	"github.com/ianr0bkny/go-sonos/model"
	"github.com/ianr0bkny/go-sonos/upnp"
	"log"
	"strings"
)

const (
	ObjectID_Attributes    = "A:"
	ObjectID_MusicShares   = "S:"
	ObjectID_Queues        = "Q:"
	ObjectID_SavedQueues   = "SQ:"
	ObjectID_InternetRadio = "R:"
	ObjectID_EntireNetwork = "EN:"
	//
	ObjectID_Queue_AVT_Instance_0 = "Q:0"
	//
	ObjectID_Attribute_Genres = "A:GENRE"
	ObjectID_Attribute_Album  = "A:ALBUM"
	ObjectID_Attribute_Artist = "A:ARTIST"
)

func (this *Sonos) GetRootLevelChildren() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		upnp.BrowseObjectID_Root,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListQueues() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_Queues,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListSavedQueues() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_SavedQueues,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListInternetRadio() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_InternetRadio,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListAttributes() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_Attributes,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) ListMusicShares() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_MusicShares,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetAllGenres() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_Attribute_Genres,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func objectIDForGenre(genre string) string {
	return strings.Join([]string{ObjectID_Attribute_Genres, genre}, "/")
}

func objectIDForAlbum(album string) string {
	return strings.Join([]string{ObjectID_Attribute_Album, album}, "/")
}

func objectIDForArtist(artist string) string {
	return strings.Join([]string{ObjectID_Attribute_Artist, artist}, "/")
}

func (this *Sonos) GetGenreArtists(genre string) ([]model.Object, error) {
	req := &upnp.BrowseRequest{
		objectIDForGenre(genre),
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err := this.Browse(req); nil != err {
		log.Printf("Could not browse artists for genre `%s': %v", genre, err)
		return nil, err
	} else {
		return model.ObjectStream(result.Doc), nil
	}
	panic("unreachable")
}

func (this *Sonos) ListChildren(objectId string) (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		objectId,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetMetadata(objectId string) (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		objectId,
		upnp.BrowseFlag_BrowseMetadata,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetDirectChildren(objectId string) (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		objectId,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetQueueContents() (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		ObjectID_Queue_AVT_Instance_0,
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}

func (this *Sonos) GetAlbumTracks(album string) ([]model.Object, error) {
	req := &upnp.BrowseRequest{
		objectIDForAlbum(album),
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	log.Printf("Browsing tracks for album `%s'", album)
	if result, err := this.Browse(req); nil != err {
		log.Printf("Could not browse tracks for album `%s': %v", album, err)
		return nil, err
	} else {
		return model.ObjectStream(result.Doc), nil
	}
	panic("unreachable")
}

func (this *Sonos) GetTrackFromAlbum(album, track string) ([]model.Object, error) {
	if tracks, err := this.GetAlbumTracks(album); nil != err {
		return nil, err
	} else {
		var track_objs []model.Object
		for _, track_obj := range tracks {
			if track_obj.Title() == track {
				track_objs = append(track_objs, track_obj)
			}
		}
		return track_objs, nil
	}
	panic("unreachable")
}

func (this *Sonos) GetArtistAlbums(artist string) (objects []model.Object, err error) {
	var result *upnp.BrowseResult
	req := &upnp.BrowseRequest{
		objectIDForArtist(artist),
		upnp.BrowseFlag_BrowseDirectChildren,
		upnp.BrowseFilter_All,
		0, /*StartingIndex*/
		0, /*RequestCount*/
		upnp.BrowseSortCriteria_None,
	}
	if result, err = this.Browse(req); nil != err {
		return
	} else {
		objects = model.ObjectStream(result.Doc)
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"github.com/gorilla/mux"
)

var ROOT = ""

type FileTypeMapJSON struct {
	MIME        string `json:"mime"`
	IsDirectory bool   `json:"is_directory"`
	IsHidden    bool   `json:"is_hidden"`
	IsAudio     bool   `json:"is_audio"`
	IsImage     bool   `json:"is_image"`
	IsVideo     bool   `json:"is_video"`
}

type FileInfoJSON struct {
	Name       string          `json:"name"`
	Size       int64           `json:"size"`
	ModifiedAt string          `json:"modified_at"`
	Type       FileTypeMapJSON `json:"type"`
}

// returns a pair of (filename, MIME type) strings given a `file` output line
func parseMIMEType(fileOutputLine string) (string, string, error) {
	// parse the file program output into a bare MIME type
	mimeString := strings.TrimSpace(fileOutputLine)
	splitIndex := strings.LastIndex(mimeString, ":")

	if len(fileOutputLine) <= 1 || splitIndex <= 0 {
		return "", "", fmt.Errorf("Invalid MIME string: '%s'", fileOutputLine)
	}

	return mimeString[0:splitIndex], strings.TrimSpace(mimeString[splitIndex+1:]), nil
}

func writeJSONResponse(w http.ResponseWriter, data interface{}) {
	w.Header().Add("Content-Type", "application/json")
	w.Header().Add("Cache-Control", "no-cache")

	json, err := json.Marshal(data)
	if err != nil {
		http.Error(w, "Failed to generate JSON response", 500)
		return
	}
	w.Write(json)
}

// given a path, returns a map of child name to MIME type
func getChildMIMETypes(parentPath string) map[string]string {
	result := make(map[string]string)

	// get all the children in the given directory
	children, err := filepath.Glob(path.Join(parentPath, "*"))
	if err != nil {
		return result
	}

	args := []string{"--mime-type", "--dereference", "--preserve-date"}
	args = append(args, children...)

	// call `file` for a newline-delimited list of "filename: MIME-type" pairs
	fileOutput, err := exec.Command("file", args...).Output()

	if err != nil {
		return result
	}

	for _, line := range strings.Split(string(fileOutput), "\n") {
		fileName, mimeType, err := parseMIMEType(line)
		if err == nil {
			result[fileName] = mimeType
		}
	}

	return result
}

func getMIMEType(filePath string) string {
	fileOutput, err := exec.Command(
		"file",
		"--mime-type",
		"--dereference",
		"--preserve-date",
		"--brief",
		filePath,
	).Output()

	if err != nil {
		return ""
	}

	return strings.TrimSpace(string(fileOutput))
}

// normalizes the path using a root, and returns it. if the path exits the root
// or is otherwise invalid, returns an error.
func normalizePathToRoot(root, child string) (string, error) {
	// clean the path, resolving any ".."s in it
	requestPath := path.Clean(path.Join(root, child))

	// if the path exited the root directory, fail
	relPath, err := filepath.Rel(root, requestPath)
	if err != nil || strings.Index(relPath, "..") >= 0 {
		// keep things vague since someone's probably trying to be sneaky anyway
		return "", fmt.Errorf("Invalid path")
	}

	return requestPath, nil
}

// this returns the info for the specified files _or_ directory, not just files
func getInfo(w http.ResponseWriter, r *http.Request) {
	// make sure our path is valid
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathToRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	// stat the file so we can return its info
	fileInfo, err := os.Stat(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	mimeType := getMIMEType(normalizedPath)

	writeJSONResponse(w, FileInfoJSON{
		fileInfo.Name(),
		fileInfo.Size(),
		fileInfo.ModTime().Format("2006-01-02T15:04:05Z"), // ISO 8601
		FileTypeMapJSON{
			mimeType,
			fileInfo.IsDir(),
			strings.HasPrefix(fileInfo.Name(), "."),
			strings.HasPrefix("audio/", mimeType),
			strings.HasPrefix("image/", mimeType),
			strings.HasPrefix("video/", mimeType),
		},
	})
}

func download(w http.ResponseWriter, r *http.Request) {
	// make sure our path is valid
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathToRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	// stat the file so we can set appropriate response headers, and so we can
	// ensure it's a regular file and not a directory.
	file, err := os.Stat(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	// return different responses depending on file type
	if file.IsDir() {
		// TODO: zip up the directory contents and serve it up
	} else {
		downloadFile(w, r, normalizedPath, file)
	}
}

func downloadFile(w http.ResponseWriter, r *http.Request, filePath string, file os.FileInfo) {
	mimeType := getMIMEType(filePath)
	w.Header().Add("Content-Type", mimeType)
	w.Header().Add("Content-Disposition", file.Name())
	w.Header().Add("Cache-Control", "no-cache")

	http.ServeFile(w, r, filePath)
}

func getDirectory(w http.ResponseWriter, r *http.Request) {
	// ensure the directory actually exists
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathToRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	children, err := ioutil.ReadDir(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	// get a map of all the MIME types for the directory
	mimeTypes := getChildMIMETypes(normalizedPath)

	// list the directory to a JSON response
	var files []FileInfoJSON
	for _, file := range children {
		fileName := file.Name()
		mimeType := mimeTypes[path.Join(normalizedPath, fileName)]

		files = append(files, FileInfoJSON{
			fileName,
			file.Size(),
			file.ModTime().Format("2006-01-02T15:04:05Z"), // ISO 8601
			FileTypeMapJSON{
				mimeType,
				file.IsDir(),
				strings.HasPrefix(fileName, "."), // hidden?
				strings.HasPrefix("audio/", mimeType), // audio?
				strings.HasPrefix("image/", mimeType), // image?
				strings.HasPrefix("video/", mimeType), // video?
			},
		})
	}

	writeJSONResponse(w, files)
}

func downloadDirectory(w http.ResponseWriter, r *http.Request) {
	// TODO: zip up the whole directory and offer it for download
}

// retrieves/caches/updates a thumbnail file given a path, or returns an error
// if no thumbnail could be geneated.
func getThumbnail(w http.ResponseWriter, r *http.Request) {
	// TODO:
	// * look up the existing file to get its modtime and ensure it exists
	// * see if we have a cached file with the same modtime
	// ** if so, use it
	// ** otherwise, generate a preview
	// *** use graphicsmagick/ffmpeg to generate a preview thumbnail
	// *** store the new file to a mirroed path with the filename plus the modtime
	// * read the cached file and return its contents

	// cache preview thumbnails for a good while to lower load on this tiny
	// server, even if we are caching the preview thumbnails on-disk too.
	w.Header().Add("Cache-Control", "max-age=3600")
	w.Header().Add("Content-Type", "image/jpeg")
}

func main() {
	// ensure we have all the binaries we need
	requiredBinaries := []string{"file"}
	for _, binary := range requiredBinaries {
		if _, err := exec.LookPath(binary); err != nil {
			log.Panicf("'%s' must be installed and in the PATH\n", binary)
		}
	}

	if len(os.Args) <= 1 {
		panic("A root directory argument is required")
	}

	ROOT = os.Args[1]

	router := mux.NewRouter()

	// /files
	// anything with a trailing `/` indicates a directory; anything that ends
	// without a trailing slash indicates a file.
	filesJSON := router.Headers("Content-Type", "application/json").Subrouter()
	filesJSON.HandleFunc("/files/{path:.*[^/]$}", getInfo).
		Headers("Content-Type", "application/json").
		Methods("GET")
	filesJSON.HandleFunc("/files{path:.*}/", getDirectory).
		Headers("Content-Type", "application/json").
		Methods("GET")

	router.HandleFunc("/files/{path:.*}", download).
		Methods("GET")

	// /thumbnails
	router.HandleFunc("/thumbnails/{path:.*[^/]$}", getThumbnail).
		Methods("GET")

	http.ListenAndServe(":3000", router)
}
<commit_msg>Refactor response format, misc.<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

	"github.com/gorilla/mux"
)

var ROOT = ""

type FileInfoJSON struct {
	Name        string `json:"name"`
	Size        int64  `json:"size"`
	ModifiedAt  string `json:"modified_at"`
	MIMEType    string `json:"mime_type"`
	IsDirectory bool   `json:"is_directory"`
	IsHidden    bool   `json:"is_hidden"`
}

// returns a pair of (filename, MIME type) strings given a `file` output line
func parseMIMEType(fileOutputLine string) (string, string, error) {
	// parse the file program output into a bare MIME type
	mimeString := strings.TrimSpace(fileOutputLine)
	splitIndex := strings.LastIndex(mimeString, ":")

	if len(fileOutputLine) <= 1 || splitIndex <= 0 {
		return "", "", fmt.Errorf("Invalid MIME string: '%s'", fileOutputLine)
	}

	return mimeString[0:splitIndex], strings.TrimSpace(mimeString[splitIndex+1:]), nil
}

func writeJSONResponse(w http.ResponseWriter, data interface{}) {
	w.Header().Add("Content-Type", "application/json")
	w.Header().Add("Cache-Control", "no-cache")

	json, err := json.Marshal(data)
	if err != nil {
		http.Error(w, "Failed to generate JSON response", 500)
		return
	}
	w.Write(json)
}

// given a path, returns a map of child name to MIME type
func getChildMIMETypes(parentPath string) map[string]string {
	result := make(map[string]string)

	// get all the children in the given directory
	children, err := filepath.Glob(path.Join(parentPath, "*"))
	if err != nil {
		return result
	}

	args := []string{"--mime-type", "--dereference", "--preserve-date"}
	args = append(args, children...)

	// call `file` for a newline-delimited list of "filename: MIME-type" pairs
	fileOutput, err := exec.Command("file", args...).Output()

	if err != nil {
		return result
	}

	for _, line := range strings.Split(string(fileOutput), "\n") {
		fileName, mimeType, err := parseMIMEType(line)
		if err == nil {
			result[fileName] = mimeType
		}
	}

	return result
}

func getMIMEType(filePath string) string {
	fileOutput, err := exec.Command(
		"file",
		"--mime-type",
		"--dereference",
		"--preserve-date",
		"--brief",
		filePath,
	).Output()

	if err != nil {
		return ""
	}

	return strings.TrimSpace(string(fileOutput))
}

// given a root and a relative child path, returns the normalized, absolute path
// of the child. if the path is not a child of the root or is otherwise invalid,
// returns an error.
func normalizePathUnderRoot(root, child string) (string, error) {
	// clean the path, resolving any ".."s in it
	requestPath := path.Clean(path.Join(root, child))

	// if the path exited the root directory, fail
	relPath, err := filepath.Rel(root, requestPath)
	if err != nil || strings.Index(relPath, "..") >= 0 {
		// keep things vague since someone's probably trying to be sneaky anyway
		return "", fmt.Errorf("Invalid path")
	}

	return requestPath, nil
}

// this returns the info for the specified files _or_ directory, not just files
func getInfo(w http.ResponseWriter, r *http.Request) {
	// make sure our path is valid
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathUnderRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	// stat the file so we can return its info
	fileInfo, err := os.Stat(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	mimeType := getMIMEType(normalizedPath)

	writeJSONResponse(w, FileInfoJSON{
		fileInfo.Name(),
		fileInfo.Size(),
		fileInfo.ModTime().Format("2006-01-02T15:04:05Z"), // ISO 8601
		mimeType,
		fileInfo.IsDir(),
		strings.HasPrefix(fileInfo.Name(), "."),
	})
}

func download(w http.ResponseWriter, r *http.Request) {
	// make sure our path is valid
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathUnderRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	// stat the file so we can set appropriate response headers, and so we can
	// ensure it's a regular file and not a directory.
	file, err := os.Stat(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	// return different responses depending on file type
	if file.IsDir() {
		// TODO: zip up the directory contents and serve it up
	} else {
		downloadFile(w, r, normalizedPath, file)
	}
}

func downloadFile(w http.ResponseWriter, r *http.Request, filePath string, file os.FileInfo) {
	mimeType := getMIMEType(filePath)
	w.Header().Add("Content-Type", mimeType)
	w.Header().Add("Content-Disposition", file.Name())
	w.Header().Add("Cache-Control", "no-cache")

	http.ServeFile(w, r, filePath)
}

func getDirectory(w http.ResponseWriter, r *http.Request) {
	// ensure the directory actually exists
	rawPath := mux.Vars(r)["path"]
	normalizedPath, err := normalizePathUnderRoot(ROOT, rawPath)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	children, err := ioutil.ReadDir(normalizedPath)
	if err != nil {
		// don't report the raw error in case we leak server directory information
		http.Error(w, "", 404)
		return
	}

	// get a map of all the MIME types for the directory
	mimeTypes := getChildMIMETypes(normalizedPath)

	// list the directory to a JSON response
	var files []FileInfoJSON
	for _, file := range children {
		fileName := file.Name()
		mimeType := mimeTypes[path.Join(normalizedPath, fileName)]

		files = append(files, FileInfoJSON{
			fileName,
			file.Size(),
			file.ModTime().Format("2006-01-02T15:04:05Z"), // ISO 8601
			FileTypeMapJSON{
				mimeType,
				file.IsDir(),
				strings.HasPrefix(fileName, "."), // hidden?
				strings.HasPrefix("audio/", mimeType), // audio?
				strings.HasPrefix("image/", mimeType), // image?
				strings.HasPrefix("video/", mimeType), // video?
			},
		})
	}

	writeJSONResponse(w, files)
}

func downloadDirectory(w http.ResponseWriter, r *http.Request) {
	// TODO: zip up the whole directory and offer it for download
}

// retrieves/caches/updates a thumbnail file given a path, or returns an error
// if no thumbnail could be geneated.
func getThumbnail(w http.ResponseWriter, r *http.Request) {
	// TODO:
	// * look up the existing file to get its modtime and ensure it exists
	// * see if we have a cached file with the same modtime
	// ** if so, use it
	// ** otherwise, generate a preview
	// *** use graphicsmagick/ffmpeg to generate a preview thumbnail
	// *** store the new file to a mirroed path with the filename plus the modtime
	// * read the cached file and return its contents

	// cache preview thumbnails for a good while to lower load on this tiny
	// server, even if we are caching the preview thumbnails on-disk too.
	w.Header().Add("Cache-Control", "max-age=3600")
	w.Header().Add("Content-Type", "image/jpeg")
}

func main() {
	// ensure we have all the binaries we need
	requiredBinaries := []string{"file"}
	for _, binary := range requiredBinaries {
		if _, err := exec.LookPath(binary); err != nil {
			log.Panicf("'%s' must be installed and in the PATH\n", binary)
		}
	}

	if len(os.Args) <= 1 {
		panic("A root directory argument is required")
	}

	ROOT = path.Clean(os.Args[1])

	router := mux.NewRouter()

	// /files
	// anything with a trailing `/` indicates a directory; anything that ends
	// without a trailing slash indicates a file.
	filesJSON := router.Headers("Content-Type", "application/json").Subrouter()
	filesJSON.HandleFunc("/files/{path:.*[^/]$}", getInfo).
		Headers("Content-Type", "application/json").
		Methods("GET")
	filesJSON.HandleFunc("/files{path:.*}/", getDirectory).
		Headers("Content-Type", "application/json").
		Methods("GET")

	router.HandleFunc("/files/{path:.*}", download).
		Methods("GET")

	// /thumbnails
	router.HandleFunc("/thumbnails/{path:.*[^/]$}", getThumbnail).
		Methods("GET")

	addr := "127.0.0.1:3000"
	fmt.Printf("Serving %s to %s...\n", ROOT, addr)
	http.ListenAndServe(addr, router)
}
<|endoftext|>