<commit_before>package main

import (
    "fmt"
    "net/http"
    "os"
    "regexp"
    "strconv"
    "strings"
    "time"

    "github.com/PuerkitoBio/goquery"
    "github.com/sfreiberg/gotwilio"
)

var rgx *regexp.Regexp

func Scrape(proj string, idx uint) int64 {
    doc, err := goquery.NewDocument("https://www.kickstarter.com/projects/" + proj)
    if err != nil {
        fmt.Println("Error getting doc")
        fmt.Println(err)
        return -1
    }

    var remaining int64 = -1
    parseErr := false
    doc.Find(".backers-wrap").EachWithBreak(func(i int, s *goquery.Selection) bool {
        if uint(i) != idx*2 {
            return true
        }

        span := s.Find(".limited-number").Text()                           // Limited (5093 left of 26000)
        raw := rgx.FindString(span)                                        // (5093  (trailing space)
        remaining, err = strconv.ParseInt(strings.Trim(raw, "( "), 10, 32) // 5093
        if err != nil {
            fmt.Println("Error parsing int")
            fmt.Println(err)
            parseErr = true
        }
        return false
    })

    if parseErr {
        return -1
    }

    return remaining
}

func doEvery(d time.Duration, f func()) {
    for {
        time.Sleep(d)
        f()
    }
}

func handler(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/html")
    w.Write([]byte("
Pebble availability
"))
}

// Given the last sent amount and the current remaining amount, determines if a new text should be sent
func sendMessage(lastSentAmount int64, currentAmount int64) bool {
    lastSentAmount-- // Adjust values since we want to send a new text at the diff multiple
    currentAmount--
    var factor int64 = 10 // Represents the multiple of 10 that is higher than the current amount
    for factor < currentAmount {
        factor *= 10
    }
    diff := factor / 10 // Threshold for sending a new text
    lastTier := (lastSentAmount - lastSentAmount%diff) / diff
    curTier := (currentAmount - currentAmount%diff) / diff
    isInitial := lastSentAmount < 0
    changed := curTier < lastTier
    return isInitial || changed
}

func main() {
    accountSid := os.Getenv("twilioSid")
    authToken := os.Getenv("twilioToken")
    twilio := gotwilio.NewTwilioClient(accountSid, authToken)
    from := os.Getenv("fromNum")
    to := os.Getenv("toNum")

    rgx = regexp.MustCompile("\\((.*?) ")
    var last int64 = -1

    go doEvery(10*60*time.Second, func() {
        var timeSteelIdx uint = 3
        fmt.Println("Starting scrape")
        remaining := Scrape("597507018/pebble-time-awesome-smartwatch-no-compromises", timeSteelIdx)
        if remaining < 0 {
            fmt.Println("Failed scrape")
            return
        }
        fmt.Println(remaining, "remaining")
        if !sendMessage(last, remaining) {
            return
        }

        last = remaining
        message := fmt.Sprintf("%d Pebble Time Steels of %d are remaining.", remaining, 20000)
        fmt.Printf("Sending message: %s\n", message)
        twilio.SendSMS(from, to, message, "", "")
    })

    http.HandleFunc("/", handler)
    err := http.ListenAndServe(":"+os.Getenv("PORT"), nil)
    if err != nil {
        panic(err)
    }
}
<commit_msg>Scrape before sleeping<commit_after>package main

import (
    "fmt"
    "net/http"
    "os"
    "regexp"
    "strconv"
    "strings"
    "time"

    "github.com/PuerkitoBio/goquery"
    "github.com/sfreiberg/gotwilio"
)

var rgx *regexp.Regexp

func Scrape(proj string, idx uint) int64 {
    doc, err := goquery.NewDocument("https://www.kickstarter.com/projects/" + proj)
    if err != nil {
        fmt.Println("Error getting doc")
        fmt.Println(err)
        return -1
    }

    var remaining int64 = -1
    parseErr := false
    doc.Find(".backers-wrap").EachWithBreak(func(i int, s *goquery.Selection) bool {
        if uint(i) != idx*2 {
            return true
        }

        span := s.Find(".limited-number").Text()                           // Limited (5093 left of 26000)
        raw := rgx.FindString(span)                                        // (5093  (trailing space)
        remaining, err = strconv.ParseInt(strings.Trim(raw, "( "), 10, 32) // 5093
        if err != nil {
            fmt.Println("Error parsing int")
            fmt.Println(err)
            parseErr = true
        }
        return false
    })

    if parseErr {
        return -1
    }

    return remaining
}

func doEvery(d time.Duration, f func()) {
    for {
        f()
        time.Sleep(d)
    }
}

func handler(w http.ResponseWriter, req *http.Request) {
    w.Header().Set("Content-Type", "text/html")
    w.Write([]byte("
Pebble availability
"))
}

// Given the last sent amount and the current remaining amount, determines if a new text should be sent
func sendMessage(lastSentAmount int64, currentAmount int64) bool {
    lastSentAmount-- // Adjust values since we want to send a new text at the diff multiple
    currentAmount--
    var factor int64 = 10 // Represents the multiple of 10 that is higher than the current amount
    for factor < currentAmount {
        factor *= 10
    }
    diff := factor / 10 // Threshold for sending a new text
    lastTier := (lastSentAmount - lastSentAmount%diff) / diff
    curTier := (currentAmount - currentAmount%diff) / diff
    isInitial := lastSentAmount < 0
    changed := curTier < lastTier
    return isInitial || changed
}

func main() {
    accountSid := os.Getenv("twilioSid")
    authToken := os.Getenv("twilioToken")
    twilio := gotwilio.NewTwilioClient(accountSid, authToken)
    from := os.Getenv("fromNum")
    to := os.Getenv("toNum")

    rgx = regexp.MustCompile("\\((.*?) ")
    var last int64 = -1

    go doEvery(10*60*time.Second, func() {
        var timeSteelIdx uint = 3
        fmt.Println("Starting scrape")
        remaining := Scrape("597507018/pebble-time-awesome-smartwatch-no-compromises", timeSteelIdx)
        if remaining < 0 {
            fmt.Println("Failed scrape")
            return
        }
        fmt.Println(remaining, "remaining")
        if !sendMessage(last, remaining) {
            return
        }

        last = remaining
        message := fmt.Sprintf("%d Pebble Time Steels of %d are remaining.", remaining, 20000)
        fmt.Printf("Sending message: %s\n", message)
        twilio.SendSMS(from, to, message, "", "")
    })

    http.HandleFunc("/", handler)
    err := http.ListenAndServe(":"+os.Getenv("PORT"), nil)
    if err != nil {
        panic(err)
    }
}
<|endoftext|>


<commit_before>// Server application for the futa-world text adventure engine
// Currently supports telnet connections and also TODO: provides a web interface.
// Please configure the variables in config.json for your server.
// The game world is read from world.json.
package main

// TODO: do I need to use an ampersand like &SomeStruct{} if I want to make a new one without using a newSomeStruct() function?

import (
	"flag"
	"fmt"
	"github.com/firstrow/tcp_server"
	"os"
	"strconv"
	"strings"
	"sync"
)

// metadata constants
// TODO: separate these into config.json
const ver = "0.0.1"
const telnetPort = 23
const webPort = 80

// String displayed on connect
// TODO: separate these into world.json
const welcomeString = "\r\nWelcome to futa.world! This is version " + ver + ", created by deltaryz.\r\n\r\nWARNING: EXPLICIT CONTENT\r\nYou must be at least 18 years of age to play this game. If you agree that you are 18 or older, please type any message and press enter.\r\n"
const introMessage = "You are PLACEHOLDER_NAME, a young mare from Ponyville. You have awoken to find yourself in an unknown location, and all you know is that you are REALLY itching to fuck something with your massive futa schlong.\r\n\r\n"

// locker utility to prevent map collision
var plock sync.Mutex
var tlock sync.Mutex

// Player map
var Players = make(map[string]*Player)
var tcpConnections = make(map[*tcp_server.Client]string)

// Gets a Player from the map, makes sure to wait until nothing else is accessing it
func getPlayer(username string) (*Player, bool) {
	plock.Lock()
	defer plock.Unlock()

	pl, exists := Players[username]

	return pl, exists
}

// Sets a Player in the map, ignoring whether it exists or not
func setPlayer(username string, p *Player) bool {
	plock.Lock()
	defer plock.Unlock()

	Players[username] = p

	return true
}

// Gets a TCP client's associated username
func getTCPPlayer(conn *tcp_server.Client) (string, bool) {
	tlock.Lock()
	defer tlock.Unlock()

	username, exists := tcpConnections[conn]

	return username, exists
}

// Sets a TCP client's associated username
func setTCPPlayer(conn *tcp_server.Client, username string) bool {
	tlock.Lock()
	defer tlock.Unlock()

	tcpConnections[conn] = username
	return true
}

// Player object
type Player struct {
	Name    string        `json:"name"`    // Username given by the Player
	Inv     ItemList      `json:"inv"`     // Array of Item objects currently owned by the Player
	Pos     pos           `json:"pos"`     // Room position in x/y coordinates on the map
	World   map[pos]*Room `json:"world"`   // map of position structs to room objects TODO: init world property of players upon acct creation
	Health  int64         `json:"health"`  // take a wild guess
	Arousal int64         `json:"arousal"` // she's a kinky fucker
}

// Stats returns a string with the Player's stats, used for game start & stats command
func (p *Player) Stats() string {
	info := ""

	info += "Health: " + strconv.FormatInt(p.Health, 10) + "\r\nArousal: " + strconv.FormatInt(p.Arousal, 10) // TODO: have a bool to enable/disable displaying of "nsfw" stats in world.json

	return info
}

// Inventory returns a string describing the contents of the Player's inventory
func (p *Player) Inventory() string {
	info := ""
	// TODO: finish inventory method
	return info
}

// Create new Player with beginning-game attributes
func newPlayer(username string) *Player {
	result := &Player{
		Name:    username,
		Inv:     ItemList{newDildo()},
		Pos:     pos{X: 0, Y: 0},
		Health:  10,
		Arousal: 10,
	}
	return result
}

// Slice of Items, such as an inventory or chest
type ItemList []*Item

// Position struct, for easy .pos.X
type pos struct {
	X int `json:"x"`
	Y int `json:"y"`
}

// generic Item struct, inherited by all Items
type Item struct {
	Name     string   `json:"name"`               // Name of the Item
	Desc     string   `json:"desc"`               // Short description of the Item
	Labels   []string `json:"labels"`             // Array of descriptive labels which apply to the Item (think booru tagging)
	Weight   float32  `json:"weight"`             // Weight of the Item
	Owned    bool     `json:"owned"`              // Is the Item currently in the Player's inventory?
	Location string   `json:"location,omitempty"` // States where in the room the Item is (ground, wall, "the pedestal", etc) - unused if the Item is currently owned
	Contents ItemList `json:"contents"`           // so i herd u liek items / Used for chests/containers to contain more items
}

// Pick up an Item from the room
func (i *Item) pickUp() string {
	if !i.Owned {
		// TODO: pick up Items
		return fmt.Sprintf("You pick up the %s.", i.Name)
	} else {
		return "You already have the " + i.Name + "!"
	}
}

// Drop an Item on the ground
func (i *Item) drop() string {
	if i.Owned {
		// TODO: drop Items on ground
		return fmt.Sprintf("You drop the %s onto the ground.", i.Name)
	} else {
		return "You can't drop that, you aren't holding it!"
	}
}

// Set the Item to an error Item
func (i *Item) makeError() error {
	i.Labels = []string{"err"}
	i.Location = ""
	i.Owned = false
	i.Desc = "Something went wrong with Item generation. Please contact the developer."
	i.Name = "Error Item"
	i.Weight = 696969

	return nil
}

// Returns the name and description of the Item
func (i *Item) getBasicInfo() []string {
	return []string{i.Name, i.Desc}
}

/*
// Instantiates a new empty Item.
// Please ensure that you initialize it with appropriate values before using.
func newEmptyItem() *Item {
	return &Item{}
}
*/

// Initializes an Item with Dildo properties
// TODO: remove this, replace with default items field in world.json
func newDildo() *Item {
	result := &Item{
		Desc: "A medium sized, unassuming dildo. It is purple.",
		Name: "Modest Dildo",
	}
	return result
}

// generic Room struct, is used to create each room
type Room struct {
	Pos   pos      `json:"pos"`   // Position in the world of the room.
	Name  string   `json:"name"`  // Name of the room.
	Desc  string   `json:"desc"`  // Description of the room, output of "look" command
	Items ItemList `json:"items"` // Items contained in the room.
	Exits Exits    `json:"exits"` // Valid exits of the room
}

// Which exits of a room are valid exits?
// This can be changed at runtime (unlocking doors)
type Exits struct {
	North bool `json:"north"`
	South bool `json:"south"`
	East  bool `json:"east"`
	West  bool `json:"west"`
}

// create a new room based on given coordinates from the given world json
func newRoom(coords pos) *Room { // TODO: receive json in argument
	room := &Room{
		Pos:   coords,
		Name:  "",         // TODO: grab name from world json
		Desc:  "",         // TODO: grab desc from world json
		Items: ItemList{}, // TODO: grab items from world json
		Exits: Exits{},    // TODO: grab exits from world json
	}

	return room
}

// Main function
func main() {

	// Command line flags for loading alternate config and world files
	configPath := flag.String("config", "config.json", "-config <path>") // TODO: enable/disable web/telnet in config
	worldPath := flag.String("world", "world.json", "-world <path>")

	flag.Parse()

	// Attempt to open these files
	configFile, configErr := os.Open(*configPath)
	worldFile, worldErr := os.Open(*worldPath)
	defer configFile.Close()
	defer worldFile.Close()

	if configErr != nil {
		fmt.Println("Error reading config file!\r\n" + configErr.Error())
	}

	if worldErr != nil {
		fmt.Println("Error reading world file!\r\n" + worldErr.Error())
	}

	// TODO: load master map json into variable

	// TCP setup
	server := tcp_server.New("localhost:" + strconv.FormatInt(telnetPort, 10))

	// new TCP client has connected
	server.OnNewClient(func(c *tcp_server.Client) {
		// new client connected
		// lets send some dank ass message
		c.Send(welcomeString)

		fmt.Println(c.Conn().LocalAddr())
	})

	// TCP client has sent a new message
	server.OnNewMessage(func(c *tcp_server.Client, message string) {

		// split string
		args := strings.Fields(message)

		if len(args) <= 0 {
			c.Send("\r\n\r\n>")
		} else {

			// TODO: proper intro message
			// check if user is logged in before doing anything
			username, exists := getTCPPlayer(c) // "username" is unusable if the connection has not logged in

			// give login message if the user is not logged in
			if !exists && args[0] != "login" {
				c.Send("\r\nSend \"quit\" to exit.\r\nPlease use the \"login\" command to start or resume your session.\r\nUsage: login <username>")
			}

			// make sure user actually sent something
			if len(args) > 0 {

				// user is quitting game
				if args[0] == "quit" || args[0] == "exit" {
					c.Send("Thank you for playing futa.world!\r\n\r\n") // TODO: have this use global item string
					c.Close()
				} else { // user is not quitting game

					// pass command to the generic interpreter
					msg, uname, needsMap := messageReceived(args, username)

					// if we need to map the connection to a username
					if needsMap {
						setTCPPlayer(c, uname)
					}

					// send to client, add newlines for readability
					c.Send(msg + "\r\n\r\n>")
				}

			}
		}
	})

	// TCP client has disconnected
	server.OnClientConnectionClosed(func(c *tcp_server.Client, err error) {
		// connection with client lost
		// TODO: clean up character data, save their shit
	})

	// start TCP server
	server.Listen()

	// TODO: web interface
}

// Generic message handler function
// Designed to be client/protocol-independent for maximum portability
// Returns a response string to print to the client, the string username of the client, and a bool stating whether a TCPMap needs to be created (only used for TCP).
func messageReceived(args []string, username string) (string, string, bool) {

	// start with empty string
	response := ""
	// only used for tcp connections, indicates whether a tcp mapping needs to be made
	needsTcpMap := false

	p, playerExists := getPlayer(username)

	// which command did the Player use?
	// just fyi this switch is gonna be FUCKING LONG
	switch args[0] {

	// User is attempting to log in
	case "login":
		if len(args) > 1 {
			// TODO: sanitize login string
			if playerExists {
				response += "You are already logged in!"
			} else {
				response += "Initializing game...\r\n"
				username = args[1] // username is not set because Player hasn't logged in yet
				needsTcpMap = true // in case the client is tcp, unused otherwise
				// Player already exists
				if playerExists {
					response += "User exists, attempting to log in...\r\n\r\n"
					fmt.Println("User " + p.Name + " succesfully logged in")
					response += fmt.Sprintf("%s%s", introMessage, p.Stats()) // TODO: don't use the intro string here, also describe room
				} else { // create new account
					response += "Username does not exist, creating new Player profile...\r\n\r\n"
					setPlayer(username, newPlayer(username)) // set Player in database
					tmpPlayer, ok2 := getPlayer(username)    // get Player back from database to ensure successful creation
					if ok2 {
						fmt.Println("User " + tmpPlayer.Name + " succesfully created account")

						// TODO: create world from json, store in player object

						response += fmt.Sprintf("%s%s", introMessage, tmpPlayer.Stats()) // TODO: describe current room
					} else {
						fmt.Println("Account creation derped")
						response += "An error occured with logging in."
						needsTcpMap = false
					}
				}
			}
		} else {
			response += "Error: not enough arguments"
		}
		break

	// display the Player's stats
	case "stats":
		if playerExists {
			response += p.Stats()
		}
		break

	// list contents of inventory
	case "inv":
	case "inventory":
		if playerExists {
			response += p.Inventory()
		}
		break

	default:
		if playerExists {
			response += "Error: invalid command"
		}
		break
	}

	return response, username, needsTcpMap

}
<commit_msg>Fixed login derpage<commit_after>// Server application for the futa-world text adventure engine
// Currently supports telnet connections and also TODO: provides a web interface.
// Please configure the variables in config.json for your server.
// The game world is read from world.json.
package main

// TODO: do I need to use an ampersand like &SomeStruct{} if I want to make a new one without using a newSomeStruct() function?

import (
	"flag"
	"fmt"
	"github.com/firstrow/tcp_server"
	"os"
	"strconv"
	"strings"
	"sync"
)

// metadata constants
// TODO: separate these into config.json
const ver = "0.0.1"
const telnetPort = 23
const webPort = 80

// String displayed on connect
// TODO: separate these into world.json
const welcomeString = "\r\nWelcome to futa.world! This is version " + ver + ", created by deltaryz.\r\n\r\nWARNING: EXPLICIT CONTENT\r\nYou must be at least 18 years of age to play this game. If you agree that you are 18 or older, please type any message and press enter.\r\n"
const introMessage = "You are PLACEHOLDER_NAME, a young mare from Ponyville. You have awoken to find yourself in an unknown location, and all you know is that you are REALLY itching to fuck something with your massive futa schlong.\r\n\r\n"

// locker utility to prevent map collision
var plock sync.Mutex
var tlock sync.Mutex

// Player map
var Players = make(map[string]*Player)
var tcpConnections = make(map[*tcp_server.Client]string)

// Gets a Player from the map, makes sure to wait until nothing else is accessing it
func getPlayer(username string) (*Player, bool) {
	plock.Lock()
	defer plock.Unlock()

	pl, exists := Players[username]

	return pl, exists
}

// Sets a Player in the map, ignoring whether it exists or not
func setPlayer(username string, p *Player) bool {
	plock.Lock()
	defer plock.Unlock()

	Players[username] = p

	return true
}

// Gets a TCP client's associated username
func getTCPPlayer(conn *tcp_server.Client) (string, bool) {
	tlock.Lock()
	defer tlock.Unlock()

	username, exists := tcpConnections[conn]

	return username, exists
}

// Sets a TCP client's associated username
func setTCPPlayer(conn *tcp_server.Client, username string) bool {
	tlock.Lock()
	defer tlock.Unlock()

	tcpConnections[conn] = username
	return true
}

// Player object
type Player struct {
	Name    string        `json:"name"`    // Username given by the Player
	Inv     ItemList      `json:"inv"`     // Array of Item objects currently owned by the Player
	Pos     pos           `json:"pos"`     // Room position in x/y coordinates on the map
	World   map[pos]*Room `json:"world"`   // map of position structs to room objects TODO: init world property of players upon acct creation
	Health  int64         `json:"health"`  // take a wild guess
	Arousal int64         `json:"arousal"` // she's a kinky fucker
}

// Stats returns a string with the Player's stats, used for game start & stats command
func (p *Player) Stats() string {
	info := ""

	info += "Health: " + strconv.FormatInt(p.Health, 10) + "\r\nArousal: " + strconv.FormatInt(p.Arousal, 10) // TODO: have a bool to enable/disable displaying of "nsfw" stats in world.json

	return info
}

// Inventory returns a string describing the contents of the Player's inventory
func (p *Player) Inventory() string {
	info := ""
	// TODO: finish inventory method
	return info
}

// Create new Player with beginning-game attributes
func newPlayer(username string) *Player {
	result := &Player{
		Name:    username,
		Inv:     ItemList{newDildo()},
		Pos:     pos{X: 0, Y: 0},
		Health:  10,
		Arousal: 10,
	}
	return result
}

// Slice of Items, such as an inventory or chest
type ItemList []*Item

// Position struct, for easy .pos.X
type pos struct {
	X int `json:"x"`
	Y int `json:"y"`
}

// generic Item struct, inherited by all Items
type Item struct {
	Name     string   `json:"name"`               // Name of the Item
	Desc     string   `json:"desc"`               // Short description of the Item
	Labels   []string `json:"labels"`             // Array of descriptive labels which apply to the Item (think booru tagging)
	Weight   float32  `json:"weight"`             // Weight of the Item
	Owned    bool     `json:"owned"`              // Is the Item currently in the Player's inventory?
	Location string   `json:"location,omitempty"` // States where in the room the Item is (ground, wall, "the pedestal", etc) - unused if the Item is currently owned
	Contents ItemList `json:"contents"`           // so i herd u liek items / Used for chests/containers to contain more items
}

// Pick up an Item from the room
func (i *Item) pickUp() string {
	if !i.Owned {
		// TODO: pick up Items
		return fmt.Sprintf("You pick up the %s.", i.Name)
	} else {
		return "You already have the " + i.Name + "!"
	}
}

// Drop an Item on the ground
func (i *Item) drop() string {
	if i.Owned {
		// TODO: drop Items on ground
		return fmt.Sprintf("You drop the %s onto the ground.", i.Name)
	} else {
		return "You can't drop that, you aren't holding it!"
	}
}

// Set the Item to an error Item
func (i *Item) makeError() error {
	i.Labels = []string{"err"}
	i.Location = ""
	i.Owned = false
	i.Desc = "Something went wrong with Item generation. Please contact the developer."
	i.Name = "Error Item"
	i.Weight = 696969

	return nil
}

// Returns the name and description of the Item
func (i *Item) getBasicInfo() []string {
	return []string{i.Name, i.Desc}
}

/*
// Instantiates a new empty Item.
// Please ensure that you initialize it with appropriate values before using.
func newEmptyItem() *Item {
	return &Item{}
}
*/

// Initializes an Item with Dildo properties
// TODO: remove this, replace with default items field in world.json
func newDildo() *Item {
	result := &Item{
		Desc: "A medium sized, unassuming dildo. It is purple.",
		Name: "Modest Dildo",
	}
	return result
}

// generic Room struct, is used to create each room
type Room struct {
	Pos   pos      `json:"pos"`   // Position in the world of the room.
	Name  string   `json:"name"`  // Name of the room.
	Desc  string   `json:"desc"`  // Description of the room, output of "look" command
	Items ItemList `json:"items"` // Items contained in the room.
	Exits Exits    `json:"exits"` // Valid exits of the room
}

// Which exits of a room are valid exits?
// This can be changed at runtime (unlocking doors)
type Exits struct {
	North bool `json:"north"`
	South bool `json:"south"`
	East  bool `json:"east"`
	West  bool `json:"west"`
}

// create a new room based on given coordinates from the given world json
func newRoom(coords pos) *Room { // TODO: receive json in argument
	room := &Room{
		Pos:   coords,
		Name:  "",         // TODO: grab name from world json
		Desc:  "",         // TODO: grab desc from world json
		Items: ItemList{}, // TODO: grab items from world json
		Exits: Exits{},    // TODO: grab exits from world json
	}

	return room
}

// Main function
func main() {

	// Command line flags for loading alternate config and world files
	configPath := flag.String("config", "config.json", "-config <path>") // TODO: enable/disable web/telnet in config
	worldPath := flag.String("world", "world.json", "-world <path>")

	flag.Parse()

	// Attempt to open these files
	configFile, configErr := os.Open(*configPath)
	worldFile, worldErr := os.Open(*worldPath)
	defer configFile.Close()
	defer worldFile.Close()

	if configErr != nil {
		fmt.Println("Error reading config file!\r\n" + configErr.Error())
	}

	if worldErr != nil {
		fmt.Println("Error reading world file!\r\n" + worldErr.Error())
	}

	if configErr != nil || worldErr != nil {
		os.Exit(69)
	}

	// TODO: load master map json into variable

	// TCP setup
	server := tcp_server.New("localhost:" + strconv.FormatInt(telnetPort, 10))

	// new TCP client has connected
	server.OnNewClient(func(c *tcp_server.Client) {
		// new client connected
		// lets send some dank ass message
		c.Send(welcomeString)

		fmt.Println(c.Conn().LocalAddr())
	})

	// TCP client has sent a new message
	server.OnNewMessage(func(c *tcp_server.Client, message string) {

		// split string
		args := strings.Fields(message)

		if len(args) <= 0 {
			c.Send("\r\n\r\n>")
		} else {

			// TODO: proper intro message
			// check if user is logged in before doing anything
			username, exists := getTCPPlayer(c) // "username" is unusable if the connection has not logged in

			// give login message if the user is not logged in
			if !exists && args[0] != "login" {
				c.Send("\r\nSend \"quit\" to exit.\r\nPlease use the \"login\" command to start or resume your session.\r\nUsage: login <username>")
			}

			// make sure user actually sent something
			if len(args) > 0 {

				// user is quitting game
				if args[0] == "quit" || args[0] == "exit" {
					c.Send("Thank you for playing futa.world!\r\n\r\n") // TODO: have this use global item string
					c.Close()
				} else { // user is not quitting game

					// pass command to the generic interpreter
					msg, uname, needsMap := messageReceived(args, username)

					// if we need to map the connection to a username
					if needsMap {
						setTCPPlayer(c, uname)
					}

					// send to client, add newlines for readability
					c.Send(msg + "\r\n\r\n>")
				}

			}
		}
	})

	// TCP client has disconnected
	server.OnClientConnectionClosed(func(c *tcp_server.Client, err error) {
		// connection with client lost
		// TODO: clean up character data, save their shit
	})

	// start TCP server
	server.Listen()

	// TODO: web interface
}

// Generic message handler function
// Designed to be client/protocol-independent for maximum portability
// Returns a response string to print to the client, the string username of the client, and a bool stating whether a TCPMap needs to be created (only used for TCP).
func messageReceived(args []string, username string) (string, string, bool) {

	// start with empty string
	response := ""
	// only used for tcp connections, indicates whether a tcp mapping needs to be made
	needsTcpMap := false

	p, playerExists := getPlayer(username)

	// which command did the Player use?
	// just fyi this switch is gonna be FUCKING LONG
	switch args[0] {

	// User is attempting to log in
	case "login":
		if len(args) > 1 {
			// TODO: sanitize login string
			p2, playerExistsFromArg := getPlayer(args[1])
			if playerExists {
				response += fmt.Sprintf("You are already logged in as player: %s!", p.Name)
			} else {
				response += "Initializing game...\r\n"
				username = args[1] // username is not set because Player hasn't logged in yet
				needsTcpMap = true // in case the client is tcp, unused otherwise
				// Player already exists
				if playerExistsFromArg {
					response += "User exists, attempting to log in...\r\n\r\n"
					fmt.Println("User " + p2.Name + " succesfully logged in")
					response += fmt.Sprintf("%s%s", introMessage, p2.Stats()) // TODO: don't use the intro string here, also describe room
				} else { // create new account
					response += "Username does not exist, creating new Player profile...\r\n\r\n"
					setPlayer(username, newPlayer(username)) // set Player in database
					tmpPlayer, ok2 := getPlayer(username)    // get Player back from database to ensure successful creation
					if ok2 {
						fmt.Println("User " + tmpPlayer.Name + " succesfully created account")

						// TODO: create world from json, store in player object

						response += fmt.Sprintf("%s%s", introMessage, tmpPlayer.Stats()) // TODO: describe current room
					} else {
						fmt.Println("Account creation derped")
						response += "An error occured with logging in."
						needsTcpMap = false
					}
				}
			}
		} else {
			response += "Error: not enough arguments"
		}
		break

	// display the Player's stats
	case "stats":
		if playerExists {
			response += p.Stats()
		}
		break

	// list contents of inventory
	case "inv":
	case "inventory":
		if playerExists {
			response += p.Inventory()
		}
		break

	default:
		if playerExists {
			response += "Error: invalid command"
		}
		break
	}

	return response, username, needsTcpMap

}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"html/template"
	"net/http"
	"os"
	"time"

	log "github.com/sirupsen/logrus"
	"github.com/skibish/ddns/conf"
	"github.com/skibish/ddns/do"
	"github.com/skibish/ddns/ipprovider"
	"github.com/skibish/ddns/notifier"
)

var (
	digio   do.DigitalOceanInterface
	cf      *conf.Configuration
	storage *conf.Configuration
	periodC <-chan time.Time
)

var (
	reqTimeouts = flag.Duration("req-timeout", 10*time.Second, "Request timeout to external resources")
	checkPeriod = flag.Duration("check-period", 5*time.Minute, "Check if IP has been changed period")
	confFile    = flag.String("conf-file", "$HOME/.ddns.yml", "Location of the configuration file")
)

// current remembered IP
var currentIP string

func init() {
	log.SetFormatter(&log.TextFormatter{
		DisableColors: true,
	})
	log.SetLevel(log.DebugLevel)
	log.SetOutput(os.Stdout)

	// initialize storage
	storage = &conf.Configuration{}
}

func main() {
	flag.Parse()

	// read configuration
	var errConf error
	cf, errConf = conf.NewConfiguration(*confFile)
	if errConf != nil {
		log.Fatal(errConf.Error())
	}

	// try to register all provided hooks
	for k, v := range cf.Notify {
		hook, errGet := notifier.GetHook(k, v)
		if errGet != nil {
			log.Debugf("Notifier %q not added: %s", k, errGet.Error())
			continue
		}
		log.AddHook(hook)
	}

	// setup http client
	hc := &http.Client{
		Timeout: *reqTimeouts,
	}

	// initialte digital ocean client
	digio = do.NewDigitalOcean(cf.Domain, cf.Token, hc)

	// register providers
	ipprovider.Register(hc)

	// get current IP
	currentIP = ipprovider.GetIP()
	if currentIP == "" {
		log.Fatal("IP can't be empty in the beginning... Do you have internet connection?")
	}
	log.Infof("Current IP is %q", currentIP)

	// do request to the digital ocean API for list of records
	allRecords, errGetDR := digio.GetDomainRecords()
	if errGetDR != nil {
		log.Fatal(errGetDR.Error())
	}

	// do initial sync of records
	var errSync error
	errSync = syncRecords(storage, cf, allRecords)
	if errSync != nil {
		log.Fatal(errSync.Error())
	}

	periodC = time.NewTicker(*checkPeriod).C

	// start main proceess
	go func(storage *conf.Configuration) {
		// for defined period of time, perform IP check
		for {
			select {
			case <-periodC:
				errCheck := checkAndUpdate(storage, cf, ipprovider.GetIP)
				if errCheck != nil {
					log.Errorf("Failed to update: %s", errCheck.Error())
				}
			}
		}

	}(storage)

	select {}
}

// syncRecords perform initial sync between what we provided
// in configuration and what already exist in DNS records
func syncRecords(storage *conf.Configuration, cf *conf.Configuration, allRecords []do.Record) error {
	cRec := len(storage.Records)
	cAllRec := len(allRecords)
	for i := 0; i < cRec; i++ {
		for j := 0; j < cAllRec; j++ {

			// we are only interested in those who have full match
			// by `type AND name`
			if storage.Records[i].Type == allRecords[j].Type &&
				storage.Records[i].Name == allRecords[j].Name {
				storage.Records[i] = allRecords[j]
				break
			}
		}

		// if there was no match, we should create new DNS record
		// and updatee current configuration
		if storage.Records[i].ID == 0 {
			// if there is not template in configuration, set current IP as data,
			// otherwise parse data and fill template with provided params
			if cf.Records[i].Data == "" {
				storage.Records[i].Data = currentIP
			} else {
				storage.Params["IP"] = currentIP
				t := template.Must(template.New("t1").Parse(cf.Records[i].Data))
				buf := new(bytes.Buffer)
				errExec := t.Execute(buf, storage.Params)
				if errExec != nil {
					return errExec
				}
				storage.Records[i].Data = buf.String()
			}

			newR, errCreate := digio.CreateRecord(storage.Records[i])
			if errCreate != nil {
				return errCreate
			}

			storage.Records[i] = *newR
		}

		// if IPs are different, update record
		if storage.Records[i].Data != currentIP {
			if cf.Records[i].Data == "" {
				storage.Records[i].Data = currentIP
			} else {
				storage.Params["IP"] = currentIP
				t := template.Must(template.New("t1").Parse(cf.Records[i].Data))
				buf := new(bytes.Buffer)
				errExec := t.Execute(buf, storage.Params)
				if errExec != nil {
					return errExec
				}
				storage.Records[i].Data = buf.String()
			}

			newR, errUpdate := digio.UpdateRecord(storage.Records[i])
			if errUpdate != nil {
				return errUpdate
			}

			storage.Records[i] = *newR
		}
	}

	return nil
}

// checkAndUpdate check for new IP and if it has been changed,
// trigger the update of the DNS records
func checkAndUpdate(storage *conf.Configuration, cf *conf.Configuration, getIP ipprovider.FGetIP) error {
	log.Debug("IP check")
	newIP := getIP()

	if currentIP != newIP {
		log.Infof("IP has changed from %q to %q", currentIP, newIP)
		currentIP = newIP

		cRec := len(storage.Records)
		for i := 0; i < cRec; i++ {
			if cf.Records[i].Data == "" {
				storage.Records[i].Data = currentIP
			} else {
				storage.Params["IP"] = currentIP
				t := template.Must(template.New("t1").Parse(cf.Records[i].Data))
				buf := new(bytes.Buffer)
				errExec := t.Execute(buf, storage.Params)
				if errExec != nil {
					return errExec
				}
				storage.Records[i].Data = buf.String()
			}

			newR, errUpdate := digio.UpdateRecord(storage.Records[i])
			if errUpdate != nil {
				return errUpdate
			}

			storage.Records[i] = *newR
		}
	}

	return nil
}
<commit_msg>copy structures<commit_after>package main

import (
	"bytes"
	"flag"
	"html/template"
	"net/http"
	"os"
	"time"

	log "github.com/sirupsen/logrus"
	"github.com/skibish/ddns/conf"
	"github.com/skibish/ddns/do"
	"github.com/skibish/ddns/ipprovider"
	"github.com/skibish/ddns/notifier"
)

var (
	digio   do.DigitalOceanInterface
	cf      *conf.Configuration
	storage *conf.Configuration
	periodC <-chan time.Time
)

var (
	reqTimeouts = flag.Duration("req-timeout", 10*time.Second, "Request timeout to external resources")
	checkPeriod = flag.Duration("check-period", 5*time.Minute, "Check if IP has been changed period")
	confFile    = flag.String("conf-file", "$HOME/.ddns.yml", "Location of the configuration file")
)

// current remembered IP
var currentIP string

func init() {
	log.SetFormatter(&log.TextFormatter{
		DisableColors: true,
	})
	log.SetLevel(log.DebugLevel)
	log.SetOutput(os.Stdout)

	// initialize storage
	storage = &conf.Configuration{}
}

func main() {
	flag.Parse()

	// read configuration
	var errConf error
	cf, errConf = conf.NewConfiguration(*confFile)
	if errConf != nil {
		log.Fatal(errConf.Error())
	}
	*storage = *cf

	// try to register all provided hooks
	for k, v := range cf.Notify {
		hook, errGet := notifier.GetHook(k, v)
		if errGet != nil {
			log.Debugf("Notifier %q not added: %s", k, errGet.Error())
			continue
		}
		log.AddHook(hook)
	}

	// setup http client
	hc := &http.Client{
		Timeout: *reqTimeouts,
	}

	// initialte digital ocean client
	digio = do.NewDigitalOcean(cf.Domain, cf.Token, hc)

	// register providers
	ipprovider.Register(hc)

	// get current IP
	currentIP = ipprovider.GetIP()
	if currentIP == "" {
		log.Fatal("IP can't be empty in the beginning... Do you have internet connection?")
	}
	log.Infof("Current IP is %q", currentIP)

	// do request to the digital ocean API for list of records
	allRecords, errGetDR := digio.GetDomainRecords()
	if errGetDR != nil {
		log.Fatal(errGetDR.Error())
	}

	// do initial sync of records
	var errSync error
	errSync = syncRecords(storage, cf, allRecords)
	if errSync != nil {
		log.Fatal(errSync.Error())
	}

	periodC = time.NewTicker(*checkPeriod).C

	// start main proceess
	go func(storage *conf.Configuration) {
		// for defined period of time, perform IP check
		for {
			select {
			case <-periodC:
				errCheck := checkAndUpdate(storage, cf, ipprovider.GetIP)
				if errCheck != nil {
					log.Errorf("Failed to update: %s", errCheck.Error())
				}
			}
		}

	}(storage)

	select {}
}

// syncRecords perform initial sync between what we provided
// in configuration and what already exist in DNS records
func syncRecords(storage *conf.Configuration, cf *conf.Configuration, allRecords []do.Record) error {
	cRec := len(storage.Records)
	cAllRec := len(allRecords)
	for i := 0; i < cRec; i++ {
		for j := 0; j < cAllRec; j++ {

			// we are only interested in those who have full match
			// by `type AND name`
			if storage.Records[i].Type == allRecords[j].Type &&
				storage.Records[i].Name == allRecords[j].Name {
				storage.Records[i] = allRecords[j]
				break
			}
		}

		// if there was no match, we should create new DNS record
		// and updatee current configuration
		if storage.Records[i].ID == 0 {
			// if there is not template in configuration, set current IP as data,
			// otherwise parse data and fill template with provided params
			if cf.Records[i].Data == "" {
				storage.Records[i].Data = currentIP
			} else {
				storage.Params["IP"] = currentIP
				t := template.Must(template.New("t1").Parse(cf.Records[i].Data))
				buf := new(bytes.Buffer)
				errExec := t.Execute(buf, storage.Params)
				if errExec != nil {
					return errExec
				}
				storage.Records[i].Data = buf.String()
			}

			newR, errCreate := digio.CreateRecord(storage.Records[i])
			if errCreate != nil {
				return errCreate
			}

			storage.Records[i] = *newR
		}

		// if IPs are different, update record
		if storage.Records[i].Data != currentIP {
			if cf.Records[i].Data == "" {
				storage.Records[i].Data = currentIP
			} else {
				storage.Params["IP"] = currentIP
				t := template.Must(template.New("t1").Parse(cf.Records[i].Data))
				buf := new(bytes.Buffer)
				errExec := t.Execute(buf, storage.Params)
				if errExec != nil {
					return errExec
				}
				storage.Records[i].Data = buf.String()
			}

			newR, errUpdate := digio.UpdateRecord(storage.Records[i])
			if errUpdate != nil {
				return errUpdate
			}

			storage.Records[i] = *newR
		}
	}

	return nil
}

// checkAndUpdate check for new IP and if it has been changed,
// trigger the update of the DNS records
func checkAndUpdate(storage *conf.Configuration, cf *conf.Configuration, getIP ipprovider.FGetIP) error {
	log.Debug("IP check")
	newIP := getIP()

	if currentIP != newIP {
		log.Infof("IP has changed from %q to %q", currentIP, newIP)
		currentIP = newIP

		cRec := len(storage.Records)
		for i := 0; i < cRec; i++ {
			if cf.Records[i].Data == "" {
				storage.Records[i].Data = currentIP
			} else {
				storage.Params["IP"] = currentIP
				t := template.Must(template.New("t1").Parse(cf.Records[i].Data))
				buf := new(bytes.Buffer)
				errExec := t.Execute(buf, storage.Params)
				if errExec != nil {
					return errExec
				}
				storage.Records[i].Data = buf.String()
			}

			newR, errUpdate := digio.UpdateRecord(storage.Records[i])
			if errUpdate != nil {
				return errUpdate
			}

			storage.Records[i] = *newR
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"room"
	"spotify"
	"time"

	"github.com/gorilla/mux"
	"github.com/gorilla/securecookie"
)

var s *securecookie.SecureCookie

var addr = flag.String("addr", ":8000", "http service address")
var env = flag.String("env", "development", "the environment to run in")
var dev bool

var templates RadioTemplate

var rooms = make(map[string]*room.Room)
var users = make(map[string]*room.User)

func main() {
	rand.Seed(time.Now().Unix())
	go h.run()

	flag.Parse()
	dev = *env == "development"

	r := mux.NewRouter()

	r.HandleFunc("/", withLogin(serveHome)).Methods("GET")
	r.HandleFunc("/rooms/{key}", withLogin(serveRoom)).Methods("GET")
	r.HandleFunc("/rooms/{key}/create", withLogin(createRoom)).Methods("POST")
	r.HandleFunc("/rooms/{key}/search", withLogin(serveSearch)).Methods("GET")
	r.HandleFunc("/rooms/{key}/queue", withLogin(serveQueue)).Methods("GET")
	r.HandleFunc("/rooms/{key}/add", withLogin(addToQueue)).Methods("POST")
	r.HandleFunc("/rooms/{key}/pop", withLogin(serveSong)).Methods("GET")
	r.HandleFunc("/rooms/{key}/ws", withLogin(serveData)).Methods("GET")

	http.Handle("/", r)

	var err error

	if err = servePaths(); err != nil {
		log.Fatal("Can't serve static assets", err)
	}

	if templates, err = loadTemplates(); err != nil {
		log.Fatal("Can't load templates, dying: ", err)
	}

	if err = loadKeys(); err != nil {
		log.Fatal("Can't load or generate keys, dying: ", err)
	}

	err = http.ListenAndServe(*addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func serveHome(c Context) {
	dat := map[string]interface{}{}

	if err := templates.ExecuteTemplate(c, "index.html", dat); err != nil {
		serveError(c.w, err)
	}
}

type QueueResponse struct {
	Error   bool
	Message string
}

type TrackListResponse struct {
	Error   bool
	Message string
	Tracks  []spotify.Track
}

type TrackResponse struct {
	Error   bool
	Message string
	Track   spotify.Track
}

func addToQueue(c Context) {
	c.w.Header().Set("Content-Type", "application/json")

	var err error
	track := spotify.GetTrack(c.r.FormValue("id"))
	if c.Queue.HasTrack(track) {
		err = c.Queue.RemoveTrack(track)
	} else {
		err = c.Queue.AddTrack(track)
	}

	data := QueueResponse{}
	if err != nil {
		data.Error = true
		data.Message = err.Error()
	}

	respString, _ := json.Marshal(data)
	fmt.Fprint(c.w, string(respString))
}

func serveQueue(c Context) {
	data := allData{
		"Queue": c.Queue,
		"Raw":   true,
	}
	err := templates.ExecuteTemplate(c, "queue.html", data)
	if err != nil {
		fmt.Println(err)
	}
}

func serveSong(c Context) {
	fmt.Println(c.Room.Name)
	fmt.Println(len(c.User.Queues))
	for x, q := range c.User.Queues {
		fmt.Println(x, ":", len(q.Tracks))
	}
	c.w.Header().Set("Content-Type", "application/json")

	data := TrackResponse{}
	if c.Room.HasTracks() {
		u, t := c.Room.PopTrack()
		data.Track = t
		if c, ok := h.userconns[u]; ok {
			c.send <- []byte{}
		}
		respString, _ := json.Marshal(data)
		fmt.Fprint(c.w, string(respString))
	} else {
		data.Error = true
		data.Message = "No tracks to choose from"
		respString, _ := json.Marshal(data)
		fmt.Fprint(c.w, string(respString))
	}
}

func createRoom(c Context) {
	vars := mux.Vars(c.r)
	roomName := vars["key"]

	if _, ok := rooms[roomName]; !ok {
		// Add the new room
		rooms[roomName] = room.New(roomName)
	}

	http.Redirect(c.w, c.r, "/rooms/"+roomName, 302)
}

func serveRoom(c Context) {
	if c.Room == nil {
		// Make the user create it first
		data := allData{
			"Room": &room.Room{Name: mux.Vars(c.r)["key"]},
		}

		err := templates.ExecuteTemplate(c, "new_room.html", data)
		if err != nil {
			serveError(c.w, err)
			return
		}
	} else {

		if c.Queue == nil {
			c.Room.AddUser(c.User)
		}

		data := allData{
			"Room":  c.Room,
			"Queue": c.Queue,
			"Host":  c.r.Host,
		}

		err := templates.ExecuteTemplate(c, "room.html", data)
		if err != nil {
			serveError(c.w, err)
		}
	}
}
<commit_msg>Woots, debugging kills apparently<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"room"
	"spotify"
	"time"

	"github.com/gorilla/mux"
	"github.com/gorilla/securecookie"
)

var s *securecookie.SecureCookie

var addr = flag.String("addr", ":8000", "http service address")
var env = flag.String("env", "development", "the environment to run in")
var dev bool

var templates RadioTemplate

var rooms = make(map[string]*room.Room)
var users = make(map[string]*room.User)

func main() {
	rand.Seed(time.Now().Unix())
	go h.run()

	flag.Parse()
	dev = *env == "development"

	r := mux.NewRouter()

	r.HandleFunc("/", withLogin(serveHome)).Methods("GET")
	r.HandleFunc("/rooms/{key}", withLogin(serveRoom)).Methods("GET")
	r.HandleFunc("/rooms/{key}/create", withLogin(createRoom)).Methods("POST")
	r.HandleFunc("/rooms/{key}/search", withLogin(serveSearch)).Methods("GET")
	r.HandleFunc("/rooms/{key}/queue", withLogin(serveQueue)).Methods("GET")
	r.HandleFunc("/rooms/{key}/add", withLogin(addToQueue)).Methods("POST")
	r.HandleFunc("/rooms/{key}/pop", withLogin(serveSong)).Methods("GET")
	r.HandleFunc("/rooms/{key}/ws", withLogin(serveData)).Methods("GET")

	http.Handle("/", r)

	var err error

	if err = servePaths(); err != nil {
		log.Fatal("Can't serve static assets", err)
	}

	if templates, err = loadTemplates(); err != nil {
		log.Fatal("Can't load templates, dying: ", err)
	}

	if err = loadKeys(); err != nil {
		log.Fatal("Can't load or generate keys, dying: ", err)
	}

	err = http.ListenAndServe(*addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func serveHome(c Context) {
	dat := map[string]interface{}{}

	if err := templates.ExecuteTemplate(c, "index.html", dat); err != nil {
		serveError(c.w, err)
	}
}

type QueueResponse struct {
	Error   bool
	Message string
}

type TrackListResponse struct {
	Error   bool
	Message string
	Tracks  []spotify.Track
}

type TrackResponse struct {
	Error   bool
	Message string
	Track   spotify.Track
}

func addToQueue(c Context) {
	c.w.Header().Set("Content-Type", "application/json")

	var err error
	track := spotify.GetTrack(c.r.FormValue("id"))
	if c.Queue.HasTrack(track) {
		err = c.Queue.RemoveTrack(track)
	} else {
		err = c.Queue.AddTrack(track)
	}

	data := QueueResponse{}
	if err != nil {
		data.Error = true
		data.Message = err.Error()
	}

	respString, _ := json.Marshal(data)
	fmt.Fprint(c.w, string(respString))
}

func serveQueue(c Context) {
	data := allData{
		"Queue": c.Queue,
		"Raw":   true,
	}
	err := templates.ExecuteTemplate(c, "queue.html", data)
	if err != nil {
		fmt.Println(err)
	}
}

func serveSong(c Context) {
	c.w.Header().Set("Content-Type", "application/json")

	data := TrackResponse{}
	if c.Room.HasTracks() {
		u, t := c.Room.PopTrack()
		data.Track = t
		if c, ok := h.userconns[u]; ok {
			c.send <- []byte{}
		}
		respString, _ := json.Marshal(data)
		fmt.Fprint(c.w, string(respString))
	} else {
		data.Error = true
		data.Message = "No tracks to choose from"
		respString, _ := json.Marshal(data)
		fmt.Fprint(c.w, string(respString))
	}
}

func createRoom(c Context) {
	vars := mux.Vars(c.r)
	roomName := vars["key"]

	if _, ok := rooms[roomName]; !ok {
		// Add the new room
		rooms[roomName] = room.New(roomName)
	}

	http.Redirect(c.w, c.r, "/rooms/"+roomName, 302)
}

func serveRoom(c Context) {
	if c.Room == nil {
		// Make the user create it first
		data := allData{
			"Room": &room.Room{Name: mux.Vars(c.r)["key"]},
		}

		err := templates.ExecuteTemplate(c, "new_room.html", data)
		if err != nil {
			serveError(c.w, err)
			return
		}
	} else {

		if c.Queue == nil {
			c.Room.AddUser(c.User)
		}

		data := allData{
			"Room":  c.Room,
			"Queue": c.Queue,
			"Host":  c.r.Host,
		}

		err := templates.ExecuteTemplate(c, "room.html", data)
		if err != nil {
			serveError(c.w, err)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	//"github.com/kisielk/whisper-go/whisper"
	whisper "github.com/robyoung/go-whisper"
	pickle "github.com/kisielk/og-rek"

	"net/http"
	"encoding/json"
	"path/filepath"
	"strings"
	"strconv"
	"math"
	"fmt"
)

var config = struct {
	WhisperData	string
}{
	//WhisperData: "/var/lib/carbon/whisper",
	WhisperData: "..",
}

type WhisperFetchResponse struct {
	Name        string      `json:"name"`
	StartTime   int         `json:"startTime"`
	StopTime    int         `json:"stopTime"`
	StepTime    int         `json:"stepTime"`
	Values      []float64   `json:"values"`
	IsAbsent    []bool      `json:"isAbsent"`
}

type WhisperGlobResponse struct {
	Name        string      `json:"name"`
	Paths       []string    `json:"paths"`
}

func findHandler(wr http.ResponseWriter, req *http.Request) {
//	GET /metrics/find/?local=1&format=pickle&query=general.hadoop.lhr4.ha201jobtracker-01.jobtracker.NonHeapMemoryUsage.committed HTTP/1.1
//	http://localhost:8080/metrics/find/?query=test
	req.ParseForm()
	glob := req.FormValue("query")
	format := req.FormValue("format")

	if format != "json" && format != "pickle" {
		fmt.Printf("dropping invalid uri (format=%s): %s\n",
				format, req.URL.RequestURI())
		return
	}

	/* things to glob:
	 * - carbon.relays  -> carbon.relays
	 * - carbon.re      -> carbon.relays, carbon.rewhatever
	 * - carbon.[rz]    -> carbon.relays, carbon.zipper
	 * - carbon.{re,zi} -> carbon.relays, carbon.zipper
	 * - implicit * at the end of each query
	 * - match is either dir or .wsp file
	 * unfortunately, filepath.Glob doesn't handle the curly brace
	 * expansion for us */
	lbrace := strings.Index(glob, "{")
	rbrace := -1
	if lbrace > -1 {
		rbrace = strings.Index(glob[lbrace:], "}")
		if rbrace > -1 {
			rbrace += lbrace
		}
	}
	files := make([]string, 0)
	if lbrace > -1 && rbrace > -1 {
		expansion := glob[lbrace + 1:rbrace]
		parts := strings.Split(expansion, ",")
		for _, sub := range parts {
			sglob := glob[:lbrace] + sub + glob[rbrace + 1:]
			path := config.WhisperData + "/" + strings.Replace(sglob, ".", "/", -1) + "*"
			nfiles, err := filepath.Glob(path)
			if err == nil {
				files = append(files, nfiles...)
			}
		}
	} else {
		path := config.WhisperData + "/" + strings.Replace(glob, ".", "/", -1) + "*"
		nfiles, err := filepath.Glob(path)
		if err != nil {
			files = append(files, nfiles...)
		}
	}

	leafs := make([]bool, len(files))
	for i, p := range files {
		p = p[len(config.WhisperData + "/"):]
		if strings.HasSuffix(p, ".wsp") {
			p = p[:len(p) - 4]
			leafs[i] = true
		} else {
			leafs[i] = false
		}
		files[i] = strings.Replace(p, "/", ".", -1)
	}

	if format == "json" {
		response := WhisperGlobResponse {
			Name:		glob,
			Paths:		make([]string, 0),
		}
		for _, p := range files {
			response.Paths = append(response.Paths, p)
		}
		b, err := json.Marshal(response)
		if err != nil {
			fmt.Printf("failed to create JSON data for %s: %s\n", glob, err)
			return
		}
		wr.Write(b)
	} else if format == "pickle" {
		// [{'metric_path': 'metric', 'intervals': [(x,y)], 'isLeaf': True},]
		var metrics []map[string]interface{}
		var m map[string]interface{}

		for i, p := range files {
			m = make(map[string]interface{})
			m["metric_path"] = p
			// m["intervals"] = dunno how to do a tuple here
			m["isLeaf"] = leafs[i]
			metrics = append(metrics, m)
		}

		wr.Header().Set("Content-Type", "application/pickle")
		pEnc := pickle.NewEncoder(wr)
		pEnc.Encode(metrics)
	}
	fmt.Printf("find: %d hits for %s\n", len(files), glob)
	return
}

func fetchHandler(wr http.ResponseWriter, req *http.Request) {
//	GET /render/?target=general.me.1.percent_time_active.pfnredis&format=pickle&from=1396008021&until=1396022421 HTTP/1.1
//	http://localhost:8080/render/?target=testmetric&format=json&from=1395961200&until=1395961800
	req.ParseForm()
	metric := req.FormValue("target")
	format := req.FormValue("format")
	from := req.FormValue("from")
	until := req.FormValue("until")

	if format != "json" && format != "pickle" {
		fmt.Printf("dropping invalid uri (format=%s): %s\n",
				format, req.URL.RequestURI())
		http.Error(wr, "Bad request (unsupported format)", http.StatusBadRequest)
		return
	}

	path := config.WhisperData + "/" + strings.Replace(metric, ".", "/", -1) + ".wsp"
	w, err := whisper.Open(path)
	if err != nil {
		// the FE/carbonzipper often requests metrics we don't have
		//fmt.Printf("failed to open %s: %s\n", path, err)
		w = nil
	}

	i, err := strconv.Atoi(from)
	if err != nil {
		fmt.Printf("fromTime (%s) invalid: %s\n", from, err)
		if w != nil {
			w.Close()
		}
		w = nil
	}
	fromTime := int(i)
	i, err = strconv.Atoi(until)
	if err != nil {
		fmt.Printf("untilTime (%s) invalid: %s\n", from, err)
		if w != nil {
			w.Close()
		}
		w = nil
	}
	untilTime := int(i)

	if (w != nil) {
		defer w.Close()
	} else {
		http.Error(wr, "Bad request (invalid from/until time)", http.StatusBadRequest)
		return
	}

	points, err := w.Fetch(fromTime, untilTime)
	if err != nil {
		fmt.Printf("failed to getch points from %s: %s\n", path, err)
		return
	}
	values := points.Values()

	if format == "json" {
		response := WhisperFetchResponse {
			Name:		metric,
			StartTime:	points.FromTime(),
			StopTime:	points.UntilTime(),
			StepTime:	points.Step(),
			Values:		make([]float64, len(values)),
			IsAbsent:	make([]bool, len(values)),
		}

		for i, p := range values {
			if math.IsNaN(p) {
				response.Values[i] = 0
				response.IsAbsent[i] = true
			} else {
				response.Values[i] = p
				response.IsAbsent[i] = false
			}
		}

		b, err := json.Marshal(response)
		if err != nil {
			fmt.Printf("failed to create JSON data for %s: %s\n", path, err)
			return
		}
		wr.Write(b)
	} else if format == "pickle" {
		//[{'start': 1396271100, 'step': 60, 'name': 'metric',
		//'values': [9.0, 19.0, None], 'end': 1396273140}
		var metrics []map[string]interface{}
		var m map[string]interface{}

		m = make(map[string]interface{})
		m["start"] = points.FromTime()
		m["step"] = points.Step()
		m["end"] = points.UntilTime()
		m["name"] = metric

		mv := make([]interface{}, len(values))
		for i, p := range values {
			if math.IsNaN(p) {
				mv[i] = nil
			} else {
				mv[i] = p
			}
		}

		m["values"] = mv
		metrics = append(metrics, m)

		wr.Header().Set("Content-Type", "application/pickle")
		pEnc := pickle.NewEncoder(wr)
		pEnc.Encode(metrics)
	}

	fmt.Printf("served %d points\n", len(values))
	return
}

func main() {
	http.HandleFunc("/metrics/find/", findHandler)
	http.HandleFunc("/render/", fetchHandler)

	http.ListenAndServe(":8080", nil)
}
<commit_msg>undo testing bit<commit_after>package main

import (
	//"github.com/kisielk/whisper-go/whisper"
	whisper "github.com/robyoung/go-whisper"
	pickle "github.com/kisielk/og-rek"

	"net/http"
	"encoding/json"
	"path/filepath"
	"strings"
	"strconv"
	"math"
	"fmt"
)

var config = struct {
	WhisperData	string
}{
	WhisperData: "/var/lib/carbon/whisper",
	//WhisperData: "..",
}

type WhisperFetchResponse struct {
	Name        string      `json:"name"`
	StartTime   int         `json:"startTime"`
	StopTime    int         `json:"stopTime"`
	StepTime    int         `json:"stepTime"`
	Values      []float64   `json:"values"`
	IsAbsent    []bool      `json:"isAbsent"`
}

type WhisperGlobResponse struct {
	Name        string      `json:"name"`
	Paths       []string    `json:"paths"`
}

func findHandler(wr http.ResponseWriter, req *http.Request) {
//	GET /metrics/find/?local=1&format=pickle&query=general.hadoop.lhr4.ha201jobtracker-01.jobtracker.NonHeapMemoryUsage.committed HTTP/1.1
//	http://localhost:8080/metrics/find/?query=test
	req.ParseForm()
	glob := req.FormValue("query")
	format := req.FormValue("format")

	if format != "json" && format != "pickle" {
		fmt.Printf("dropping invalid uri (format=%s): %s\n",
				format, req.URL.RequestURI())
		return
	}

	/* things to glob:
	 * - carbon.relays  -> carbon.relays
	 * - carbon.re      -> carbon.relays, carbon.rewhatever
	 * - carbon.[rz]    -> carbon.relays, carbon.zipper
	 * - carbon.{re,zi} -> carbon.relays, carbon.zipper
	 * - implicit * at the end of each query
	 * - match is either dir or .wsp file
	 * unfortunately, filepath.Glob doesn't handle the curly brace
	 * expansion for us */
	lbrace := strings.Index(glob, "{")
	rbrace := -1
	if lbrace > -1 {
		rbrace = strings.Index(glob[lbrace:], "}")
		if rbrace > -1 {
			rbrace += lbrace
		}
	}
	files := make([]string, 0)
	if lbrace > -1 && rbrace > -1 {
		expansion := glob[lbrace + 1:rbrace]
		parts := strings.Split(expansion, ",")
		for _, sub := range parts {
			sglob := glob[:lbrace] + sub + glob[rbrace + 1:]
			path := config.WhisperData + "/" + strings.Replace(sglob, ".", "/", -1) + "*"
			nfiles, err := filepath.Glob(path)
			if err == nil {
				files = append(files, nfiles...)
			}
		}
	} else {
		path := config.WhisperData + "/" + strings.Replace(glob, ".", "/", -1) + "*"
		nfiles, err := filepath.Glob(path)
		if err != nil {
			files = append(files, nfiles...)
		}
	}

	leafs := make([]bool, len(files))
	for i, p := range files {
		p = p[len(config.WhisperData + "/"):]
		if strings.HasSuffix(p, ".wsp") {
			p = p[:len(p) - 4]
			leafs[i] = true
		} else {
			leafs[i] = false
		}
		files[i] = strings.Replace(p, "/", ".", -1)
	}

	if format == "json" {
		response := WhisperGlobResponse {
			Name:		glob,
			Paths:		make([]string, 0),
		}
		for _, p := range files {
			response.Paths = append(response.Paths, p)
		}
		b, err := json.Marshal(response)
		if err != nil {
			fmt.Printf("failed to create JSON data for %s: %s\n", glob, err)
			return
		}
		wr.Write(b)
	} else if format == "pickle" {
		// [{'metric_path': 'metric', 'intervals': [(x,y)], 'isLeaf': True},]
		var metrics []map[string]interface{}
		var m map[string]interface{}

		for i, p := range files {
			m = make(map[string]interface{})
			m["metric_path"] = p
			// m["intervals"] = dunno how to do a tuple here
			m["isLeaf"] = leafs[i]
			metrics = append(metrics, m)
		}

		wr.Header().Set("Content-Type", "application/pickle")
		pEnc := pickle.NewEncoder(wr)
		pEnc.Encode(metrics)
	}
	fmt.Printf("find: %d hits for %s\n", len(files), glob)
	return
}

func fetchHandler(wr http.ResponseWriter, req *http.Request) {
//	GET /render/?target=general.me.1.percent_time_active.pfnredis&format=pickle&from=1396008021&until=1396022421 HTTP/1.1
//	http://localhost:8080/render/?target=testmetric&format=json&from=1395961200&until=1395961800
	req.ParseForm()
	metric := req.FormValue("target")
	format := req.FormValue("format")
	from := req.FormValue("from")
	until := req.FormValue("until")

	if format != "json" && format != "pickle" {
		fmt.Printf("dropping invalid uri (format=%s): %s\n",
				format, req.URL.RequestURI())
		http.Error(wr, "Bad request (unsupported format)", http.StatusBadRequest)
		return
	}

	path := config.WhisperData + "/" + strings.Replace(metric, ".", "/", -1) + ".wsp"
	w, err := whisper.Open(path)
	if err != nil {
		// the FE/carbonzipper often requests metrics we don't have
		//fmt.Printf("failed to open %s: %s\n", path, err)
		w = nil
	}

	i, err := strconv.Atoi(from)
	if err != nil {
		fmt.Printf("fromTime (%s) invalid: %s\n", from, err)
		if w != nil {
			w.Close()
		}
		w = nil
	}
	fromTime := int(i)
	i, err = strconv.Atoi(until)
	if err != nil {
		fmt.Printf("untilTime (%s) invalid: %s\n", from, err)
		if w != nil {
			w.Close()
		}
		w = nil
	}
	untilTime := int(i)

	if (w != nil) {
		defer w.Close()
	} else {
		http.Error(wr, "Bad request (invalid from/until time)", http.StatusBadRequest)
		return
	}

	points, err := w.Fetch(fromTime, untilTime)
	if err != nil {
		fmt.Printf("failed to getch points from %s: %s\n", path, err)
		return
	}
	values := points.Values()

	if format == "json" {
		response := WhisperFetchResponse {
			Name:		metric,
			StartTime:	points.FromTime(),
			StopTime:	points.UntilTime(),
			StepTime:	points.Step(),
			Values:		make([]float64, len(values)),
			IsAbsent:	make([]bool, len(values)),
		}

		for i, p := range values {
			if math.IsNaN(p) {
				response.Values[i] = 0
				response.IsAbsent[i] = true
			} else {
				response.Values[i] = p
				response.IsAbsent[i] = false
			}
		}

		b, err := json.Marshal(response)
		if err != nil {
			fmt.Printf("failed to create JSON data for %s: %s\n", path, err)
			return
		}
		wr.Write(b)
	} else if format == "pickle" {
		//[{'start': 1396271100, 'step': 60, 'name': 'metric',
		//'values': [9.0, 19.0, None], 'end': 1396273140}
		var metrics []map[string]interface{}
		var m map[string]interface{}

		m = make(map[string]interface{})
		m["start"] = points.FromTime()
		m["step"] = points.Step()
		m["end"] = points.UntilTime()
		m["name"] = metric

		mv := make([]interface{}, len(values))
		for i, p := range values {
			if math.IsNaN(p) {
				mv[i] = nil
			} else {
				mv[i] = p
			}
		}

		m["values"] = mv
		metrics = append(metrics, m)

		wr.Header().Set("Content-Type", "application/pickle")
		pEnc := pickle.NewEncoder(wr)
		pEnc.Encode(metrics)
	}

	fmt.Printf("served %d points\n", len(values))
	return
}

func main() {
	http.HandleFunc("/metrics/find/", findHandler)
	http.HandleFunc("/render/", fetchHandler)

	http.ListenAndServe(":8080", nil)
}
<|endoftext|>


<commit_before>package libkbfs

import (
	"fmt"

	"github.com/keybase/client/go/engine"
	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/protocol/go"
	"github.com/maxtaco/go-framed-msgpack-rpc/rpc2"
)

// KBPKIClient uses rpc calls to daemon
type KBPKIClient struct {
	client keybase1.GenericClient
}

// NewKBPKIClient creates a KBPKIClient.
func NewKBPKIClient(ctx *libkb.GlobalContext) (*KBPKIClient, error) {
	_, xp, err := ctx.GetSocket()
	if err != nil {
		return nil, err
	}

	srv := rpc2.NewServer(xp, libkb.WrapError)

	protocols := []rpc2.Protocol{
		newLogUIProtocol(),
		newIdentifyUIProtocol(),
	}

	for _, p := range protocols {
		if err := srv.Register(p); err != nil {
			if _, ok := err.(rpc2.AlreadyRegisteredError); !ok {
				return nil, err
			}
		}
	}

	client := rpc2.NewClient(xp, libkb.UnwrapError)
	return newKBPKIClientWithClient(client), nil
}

// For testing.
func newKBPKIClientWithClient(client keybase1.GenericClient) *KBPKIClient {
	return &KBPKIClient{client}
}

// ResolveAssertion finds a user via assertion.
func (k *KBPKIClient) ResolveAssertion(username string) (*libkb.User, error) {
	arg := &engine.IDEngineArg{UserAssertion: username}
	// TODO: Consider caching the returned public key info from
	// identify instead of dropping them.
	user, _, err := k.identify(arg)
	return user, err
}

// GetUser finds a user via UID.
func (k *KBPKIClient) GetUser(uid libkb.UID) (user *libkb.User, err error) {
	arg := &engine.IDEngineArg{UserAssertion: fmt.Sprintf("uid:%s", uid)}
	user, _, err = k.identify(arg)
	return user, err
}

// GetSession returns the current session.
func (k *KBPKIClient) GetSession() (*libkb.Session, error) {
	s, _, err := k.session()
	if err != nil {
		// XXX shouldn't ignore this...
		libkb.G.Log.Warning("error getting session: %q", err)
		return nil, err
	}
	return s, nil
}

// GetLoggedInUser returns the current logged in user.
func (k *KBPKIClient) GetLoggedInUser() (libkb.UID, error) {
	s, _, err := k.session()
	if err != nil {
		// TODO: something more intelligent; maybe just shut down
		// unless we want anonymous browsing of public data
		return libkb.UID{0}, err
	}
	uid := s.GetUID()
	if uid == nil {
		// TODO: something more intelligent; maybe just shut down
		// unless we want anonymous browsing of public data
		return libkb.UID{0}, &LoggedInUserError{}
	}
	libkb.G.Log.Info("logged in user uid = %s", *uid)
	return *uid, nil
}

func (k *KBPKIClient) GetDeviceSibKeys(user *libkb.User) (
	keys []Key, err error) {
	return k.getDeviceKeysHelper(user, true /* isSibkey */)
}

func (k *KBPKIClient) GetDeviceSubKeys(user *libkb.User) (
	keys []Key, err error) {
	return k.getDeviceKeysHelper(user, false /* isSibkey */)
}

func (k *KBPKIClient) getDeviceKeysHelper(user *libkb.User, isSibkey bool) (
	keys []Key, err error) {
	arg := &engine.IDEngineArg{UserAssertion: fmt.Sprintf("uid:%s", user.GetUid())}
	_, publicKeys, err := k.identify(arg)
	if err != nil {
		return nil, err
	}

	keys = make([]Key, 0, len(publicKeys))
	for _, publicKey := range publicKeys {
		if (publicKey.IsSibkey != isSibkey) || len(publicKey.PGPFingerprint) > 0 {
			continue
		}
		kid, err := libkb.ImportKID(publicKey.KID)
		if err != nil {
			return nil, err
		}
		key, err := libkb.ImportKeypairFromKID(kid, nil)
		if err != nil {
			return nil, err
		}
		var keyType string
		if isSibkey {
			keyType = "sibkey"
		} else {
			keyType = "subkey"
		}
		libkb.G.Log.Info("got %s %s for user %s", keyType, key.VerboseDescription(), user.GetName())
		keys = append(keys, key)
	}

	return keys, nil
}

func (k *KBPKIClient) GetPublicSigningKey(user *libkb.User) (Key, error) {
	return nil, nil
}

func (k *KBPKIClient) GetDeviceSubkeyKid() (KID, error) {
	_, deviceSubkeyKid, err := k.session()
	if err != nil {
		return KID{}, err
	}
	libkb.G.Log.Info("got device kid %s", libkb.KID(deviceSubkeyKid).ToShortIdString())
	return deviceSubkeyKid, nil
}

func (k *KBPKIClient) identify(arg *engine.IDEngineArg) (*libkb.User, []keybase1.PublicKey, error) {
	c := keybase1.IdentifyClient{k.client}

	res, err := c.Identify(arg.Export())
	if err != nil {
		return nil, nil, err
	}

	return libkb.NewUserThin(res.User.Username, libkb.UID(res.User.Uid)), res.User.PublicKeys, nil
}

func (k *KBPKIClient) session() (*libkb.Session, KID, error) {
	c := keybase1.SessionClient{k.client}
	res, err := c.CurrentSession()
	if err != nil {
		return nil, KID{}, err
	}

	return libkb.NewSessionThin(libkb.UID(res.Uid), res.Username, res.Token), KID(res.DeviceSubkeyKid), nil
}
<commit_msg>kbpki_client: downgrade common Info messages to Debug<commit_after>package libkbfs

import (
	"fmt"

	"github.com/keybase/client/go/engine"
	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/protocol/go"
	"github.com/maxtaco/go-framed-msgpack-rpc/rpc2"
)

// KBPKIClient uses rpc calls to daemon
type KBPKIClient struct {
	client keybase1.GenericClient
}

// NewKBPKIClient creates a KBPKIClient.
func NewKBPKIClient(ctx *libkb.GlobalContext) (*KBPKIClient, error) {
	_, xp, err := ctx.GetSocket()
	if err != nil {
		return nil, err
	}

	srv := rpc2.NewServer(xp, libkb.WrapError)

	protocols := []rpc2.Protocol{
		newLogUIProtocol(),
		newIdentifyUIProtocol(),
	}

	for _, p := range protocols {
		if err := srv.Register(p); err != nil {
			if _, ok := err.(rpc2.AlreadyRegisteredError); !ok {
				return nil, err
			}
		}
	}

	client := rpc2.NewClient(xp, libkb.UnwrapError)
	return newKBPKIClientWithClient(client), nil
}

// For testing.
func newKBPKIClientWithClient(client keybase1.GenericClient) *KBPKIClient {
	return &KBPKIClient{client}
}

// ResolveAssertion finds a user via assertion.
func (k *KBPKIClient) ResolveAssertion(username string) (*libkb.User, error) {
	arg := &engine.IDEngineArg{UserAssertion: username}
	// TODO: Consider caching the returned public key info from
	// identify instead of dropping them.
	user, _, err := k.identify(arg)
	return user, err
}

// GetUser finds a user via UID.
func (k *KBPKIClient) GetUser(uid libkb.UID) (user *libkb.User, err error) {
	arg := &engine.IDEngineArg{UserAssertion: fmt.Sprintf("uid:%s", uid)}
	user, _, err = k.identify(arg)
	return user, err
}

// GetSession returns the current session.
func (k *KBPKIClient) GetSession() (*libkb.Session, error) {
	s, _, err := k.session()
	if err != nil {
		// XXX shouldn't ignore this...
		libkb.G.Log.Warning("error getting session: %q", err)
		return nil, err
	}
	return s, nil
}

// GetLoggedInUser returns the current logged in user.
func (k *KBPKIClient) GetLoggedInUser() (libkb.UID, error) {
	s, _, err := k.session()
	if err != nil {
		// TODO: something more intelligent; maybe just shut down
		// unless we want anonymous browsing of public data
		return libkb.UID{0}, err
	}
	uid := s.GetUID()
	if uid == nil {
		// TODO: something more intelligent; maybe just shut down
		// unless we want anonymous browsing of public data
		return libkb.UID{0}, &LoggedInUserError{}
	}
	libkb.G.Log.Info("logged in user uid = %s", *uid)
	return *uid, nil
}

func (k *KBPKIClient) GetDeviceSibKeys(user *libkb.User) (
	keys []Key, err error) {
	return k.getDeviceKeysHelper(user, true /* isSibkey */)
}

func (k *KBPKIClient) GetDeviceSubKeys(user *libkb.User) (
	keys []Key, err error) {
	return k.getDeviceKeysHelper(user, false /* isSibkey */)
}

func (k *KBPKIClient) getDeviceKeysHelper(user *libkb.User, isSibkey bool) (
	keys []Key, err error) {
	arg := &engine.IDEngineArg{UserAssertion: fmt.Sprintf("uid:%s", user.GetUid())}
	_, publicKeys, err := k.identify(arg)
	if err != nil {
		return nil, err
	}

	keys = make([]Key, 0, len(publicKeys))
	for _, publicKey := range publicKeys {
		if (publicKey.IsSibkey != isSibkey) || len(publicKey.PGPFingerprint) > 0 {
			continue
		}
		kid, err := libkb.ImportKID(publicKey.KID)
		if err != nil {
			return nil, err
		}
		key, err := libkb.ImportKeypairFromKID(kid, nil)
		if err != nil {
			return nil, err
		}
		var keyType string
		if isSibkey {
			keyType = "sibkey"
		} else {
			keyType = "subkey"
		}
		libkb.G.Log.Debug("got %s %s for user %s", keyType, key.VerboseDescription(), user.GetName())
		keys = append(keys, key)
	}

	return keys, nil
}

func (k *KBPKIClient) GetPublicSigningKey(user *libkb.User) (Key, error) {
	return nil, nil
}

func (k *KBPKIClient) GetDeviceSubkeyKid() (KID, error) {
	_, deviceSubkeyKid, err := k.session()
	if err != nil {
		return KID{}, err
	}
	libkb.G.Log.Debug("got device kid %s", libkb.KID(deviceSubkeyKid).ToShortIdString())
	return deviceSubkeyKid, nil
}

func (k *KBPKIClient) identify(arg *engine.IDEngineArg) (*libkb.User, []keybase1.PublicKey, error) {
	c := keybase1.IdentifyClient{k.client}

	res, err := c.Identify(arg.Export())
	if err != nil {
		return nil, nil, err
	}

	return libkb.NewUserThin(res.User.Username, libkb.UID(res.User.Uid)), res.User.PublicKeys, nil
}

func (k *KBPKIClient) session() (*libkb.Session, KID, error) {
	c := keybase1.SessionClient{k.client}
	res, err := c.CurrentSession()
	if err != nil {
		return nil, KID{}, err
	}

	return libkb.NewSessionThin(libkb.UID(res.Uid), res.Username, res.Token), KID(res.DeviceSubkeyKid), nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"code.google.com/p/go.exp/go/exact"
	"code.google.com/p/go.exp/go/types"
	"errors"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

var (
	// allImports is a map of already-imported import paths to packages
	allImports map[string]*types.Package

	// ErrCheckErrors is returned by the checkFiles function if any errors were
	// encountered during checking.
	ErrCheckErrors = errors.New("found errors in checked files")
)

// Err prints an error to Stderr
func Err(s string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "error: "+s+"\n", args...)
}

// Fatal calls Err followed by os.Exit(2)
func Fatal(s string, args ...interface{}) {
	Err(s, args...)
	os.Exit(2)
}

// regexpFlag is a type that can be used with flag.Var for regular expression flags
type regexpFlag struct {
	re *regexp.Regexp
}

func (r regexpFlag) String() string {
	if r.re == nil {
		return ""
	}
	return r.re.String()
}

func (r *regexpFlag) Set(s string) error {
	if s == "" {
		r.re = nil
		return nil
	}

	re, err := regexp.Compile(s)
	if err != nil {
		return err
	}
	r.re = re
	return nil
}

// stringsFlag is a type that can be used with flag.Var for lists that are turned to a set
type stringsFlag struct {
	items map[string]bool
}

func (f stringsFlag) String() string {
	items := make([]string, 0, len(f.items))
	for k := range f.items {
		items = append(items, k)
	}
	return strings.Join(items, ",")
}

func (f *stringsFlag) Set(s string) error {
	f.items = make(map[string]bool)
	for _, item := range strings.Split(s, ",") {
		f.items[item] = true
	}
	return nil
}

func main() {
	allImports = make(map[string]*types.Package)

	var ignore regexpFlag
	flag.Var(&ignore, "ignore", "regular expression of function names to ignore")
	ignorePkg := &stringsFlag{}
	ignorePkg.Set("fmt")
	flag.Var(ignorePkg, "ignorepkg", "comma-separated list of package paths to ignore")
	flag.Parse()

	pkgName := flag.Arg(0)
	if pkgName == "" {
		flag.Usage()
		Fatal("you must specify a package")
	}
	pkg, err := findPackage(pkgName)
	if err != nil {
		Fatal("%s", err)
	}
	files := getFiles(pkg)

	if err := checkFiles(files, ignore.re, ignorePkg.items); err != nil {
		if err == ErrCheckErrors {
			os.Exit(1)
		}
		Fatal("failed to check package: %s", err)
	}
}

// findPackage finds a package.
// path is first tried as an import path and if the package is not found, as a filesystem path.
func findPackage(path string) (*build.Package, error) {
	var (
		err1, err2 error
		pkg        *build.Package
	)

	ctx := build.Default
	ctx.CgoEnabled = false

	// First try to treat path as import path...
	pkg, err1 = ctx.Import(path, ".", 0)
	if err1 != nil {
		// ... then attempt as file path
		pkg, err2 = ctx.ImportDir(path, 0)
	}

	if err2 != nil {
		// Print both errors so the user can see in what ways the
		// package lookup failed.
		return nil, fmt.Errorf("could not import %s: %s\n%s", path, err1, err2)
	}

	return pkg, nil
}

// getFiles returns all the Go files found in a package
func getFiles(pkg *build.Package) []string {
	files := make([]string, len(pkg.GoFiles))
	for i, fileName := range pkg.GoFiles {
		files[i] = filepath.Join(pkg.Dir, fileName)
	}
	return files
}

type file struct {
	fset  *token.FileSet
	name  string
	ast   *ast.File
	lines [][]byte
}

func parseFile(fset *token.FileSet, fileName string) (f file, err error) {
	rd, err := os.Open(fileName)
	if err != nil {
		return f, err
	}
	defer rd.Close()

	data, err := ioutil.ReadAll(rd)
	if err != nil {
		return f, err
	}

	astFile, err := parser.ParseFile(fset, fileName, bytes.NewReader(data), parser.ParseComments)
	if err != nil {
		return f, fmt.Errorf("could not parse: %s", err)
	}

	lines := bytes.Split(data, []byte("\n"))
	f = file{fset: fset, name: fileName, ast: astFile, lines: lines}
	return f, nil
}

func typeCheck(fset *token.FileSet, astFiles []*ast.File) (map[*ast.CallExpr]types.Type, map[*ast.Ident]types.Object, error) {
	callTypes := make(map[*ast.CallExpr]types.Type)
	identObjs := make(map[*ast.Ident]types.Object)

	exprFn := func(x ast.Expr, typ types.Type, val exact.Value) {
		call, ok := x.(*ast.CallExpr)
		if !ok {
			return
		}
		callTypes[call] = typ
	}
	identFn := func(id *ast.Ident, obj types.Object) {
		identObjs[id] = obj
	}
	context := types.Context{
		Expr:   exprFn,
		Ident:  identFn,
		Import: importer,
	}
	_, err := context.Check(fset, astFiles)
	return callTypes, identObjs, err
}

type checker struct {
	fset      *token.FileSet
	files     map[string]file
	callTypes map[*ast.CallExpr]types.Type
	identObjs map[*ast.Ident]types.Object
	ignore    *regexp.Regexp
	ignorePkg map[string]bool

	errors []error
}

type uncheckedErr struct {
	pos  token.Position
	line []byte
}

func (e uncheckedErr) Error() string {
	return fmt.Sprintf("%s %s", e.pos, e.line)
}

func (c *checker) Visit(node ast.Node) ast.Visitor {
	n, ok := node.(*ast.ExprStmt)
	if !ok {
		return c
	}

	// Check for a call expression
	call, ok := n.X.(*ast.CallExpr)
	if !ok {
		return c
	}

	// Try to get an identifier.
	// Currently only supports simple expressions:
	//     1. f()
	//     2. x.y.f()
	var id *ast.Ident
	switch exp := call.Fun.(type) {
	case (*ast.Ident):
		id = exp
	case (*ast.SelectorExpr):
		id = exp.Sel
	default:
		// eg: *ast.SliceExpr, *ast.IndexExpr
	}

	// If we got an identifier for the function, see if it is ignored
	if id != nil {
		// Ignore if in an ignored package
		if obj := c.identObjs[id]; obj != nil {
			if pkg := obj.GetPkg(); pkg != nil && c.ignorePkg[pkg.Path] {
				return c
			}
		}
		// Ignore if the name matches the regexp
		if c.ignore != nil && c.ignore.MatchString(id.Name) {
			return c
		}
	}

	unchecked := false
	switch t := c.callTypes[call].(type) {
	case *types.NamedType:
		// Single return
		if isErrorType(t.Obj) {
			unchecked = true
		}
	case *types.Result:
		// Multiple returns
		for _, v := range t.Values {
			nt, ok := v.Type.(*types.NamedType)
			if !ok {
				continue
			}
			if isErrorType(nt.Obj) {
				unchecked = true
				break
			}
		}
	}

	if unchecked {
		pos := c.fset.Position(call.Lparen)
		line := bytes.TrimSpace(c.files[pos.Filename].lines[pos.Line-1])
		line = append([]byte{'\t'}, line...)
		c.errors = append(c.errors, uncheckedErr{pos, line})
	}
	return c
}

func checkFiles(fileNames []string, ignore *regexp.Regexp, ignorePkg map[string]bool) error {
	fset := token.NewFileSet()
	astFiles := make([]*ast.File, len(fileNames))
	files := make(map[string]file, len(fileNames))

	for i, fileName := range fileNames {
		f, err := parseFile(fset, fileName)
		if err != nil {
			return fmt.Errorf("could not parse %s: %s", fileName, err)
		}
		files[fileName] = f
		astFiles[i] = f.ast
	}

	callTypes, identObjs, err := typeCheck(fset, astFiles)
	if err != nil {
		return fmt.Errorf("could not type check: %s", err)
	}

	visitor := &checker{fset, files, callTypes, identObjs, ignore, ignorePkg, []error{}}
	for _, astFile := range astFiles {
		ast.Walk(visitor, astFile)
	}

	for _, e := range visitor.errors {
		fmt.Println(e)
	}

	if len(visitor.errors) > 0 {
		return ErrCheckErrors
	}
	return nil
}

type obj interface {
	GetPkg() *types.Package
	GetName() string
}

func isErrorType(v obj) bool {
	return v.GetPkg() == nil && v.GetName() == "error"
}

func importer(imports map[string]*types.Package, path string) (pkg *types.Package, err error) {
	// types.Importer does not seem to be designed for recursive
	// parsing like we're doing here. Specifically, each nested import
	// will maintain its own imports map. This will lead to duplicate
	// imports and in turn packages, which will lead to funny errors
	// such as "cannot pass argument ip (variable of type net.IP) to
	// variable of type net.IP"
	//
	// To work around this, we keep a global imports map, allImports,
	// to which we add all nested imports, and which we use as the
	// cache, instead of imports.
	//
	// Since all nested imports will also use this importer, there
	// should be no way to end up with duplicate imports.

	// We first try to use GcImport directly. This has the downside of
	// using possibly out-of-date packages, but it has the upside of
	// not having to parse most of the Go standard library.

	buildPkg, buildErr := build.Import(path, ".", 0)

	// If we found no build dir, assume we're dealing with installed
	// but no source. If we found a build dir, only use GcImport if
	// it's in GOROOT. This way we always use up-to-date code for
	// normal packages but avoid parsing the standard library.
	if (buildErr == nil && buildPkg.Goroot) || buildErr != nil {
		pkg, err = types.GcImport(allImports, path)
		if err == nil {
			// We don't use imports, but per API we have to add the package.
			imports[pkg.Path] = pkg
			allImports[pkg.Path] = pkg
			return pkg, nil
		}
	}

	// See if we already imported this package
	if pkg = allImports[path]; pkg != nil && pkg.Complete {
		return pkg, nil
	}

	// allImports failed, try to use go/build
	if buildErr != nil {
		return nil, buildErr
	}

	fileSet := token.NewFileSet()

	isGoFile := func(d os.FileInfo) bool {
		allFiles := make([]string, 0, len(buildPkg.GoFiles)+len(buildPkg.CgoFiles))
		allFiles = append(allFiles, buildPkg.GoFiles...)
		allFiles = append(allFiles, buildPkg.CgoFiles...)

		for _, file := range allFiles {
			if file == d.Name() {
				return true
			}
		}
		return false
	}
	pkgs, err := parser.ParseDir(fileSet, buildPkg.Dir, isGoFile, 0)
	if err != nil {
		return nil, err
	}

	delete(pkgs, "documentation")
	var astPkg *ast.Package
	var name string
	for name, astPkg = range pkgs {
		// Use the first non-main package, or the only package we
		// found.
		//
		// NOTE(dh) I can't think of a reason why there should be
		// multiple packages in a single directory, but ParseDir
		// accommodates for that possibility.
		if len(pkgs) == 1 || name != "main" {
			break
		}
	}

	if astPkg == nil {
		return nil, fmt.Errorf("can't find import: %s", name)
	}

	var ff []*ast.File
	for _, f := range astPkg.Files {
		ff = append(ff, f)
	}

	context := types.Context{
		Import: importer,
	}

	pkg, err = context.Check(fileSet, ff)
	if err != nil {
		return pkg, err
	}

	// We don't use imports, but per API we have to add the package.
	imports[path] = pkg
	allImports[path] = pkg
	pkg.Complete = true
	return pkg, nil
}
<commit_msg>main.go: new import of go/exact and go/types<commit_after>package main

import (
	"bytes"
	"code.google.com/p/go.tools/go/exact"
	"code.google.com/p/go.tools/go/types"
	"errors"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

var (
	// allImports is a map of already-imported import paths to packages
	allImports map[string]*types.Package

	// ErrCheckErrors is returned by the checkFiles function if any errors were
	// encountered during checking.
	ErrCheckErrors = errors.New("found errors in checked files")
)

// Err prints an error to Stderr
func Err(s string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "error: "+s+"\n", args...)
}

// Fatal calls Err followed by os.Exit(2)
func Fatal(s string, args ...interface{}) {
	Err(s, args...)
	os.Exit(2)
}

// regexpFlag is a type that can be used with flag.Var for regular expression flags
type regexpFlag struct {
	re *regexp.Regexp
}

func (r regexpFlag) String() string {
	if r.re == nil {
		return ""
	}
	return r.re.String()
}

func (r *regexpFlag) Set(s string) error {
	if s == "" {
		r.re = nil
		return nil
	}

	re, err := regexp.Compile(s)
	if err != nil {
		return err
	}
	r.re = re
	return nil
}

// stringsFlag is a type that can be used with flag.Var for lists that are turned to a set
type stringsFlag struct {
	items map[string]bool
}

func (f stringsFlag) String() string {
	items := make([]string, 0, len(f.items))
	for k := range f.items {
		items = append(items, k)
	}
	return strings.Join(items, ",")
}

func (f *stringsFlag) Set(s string) error {
	f.items = make(map[string]bool)
	for _, item := range strings.Split(s, ",") {
		f.items[item] = true
	}
	return nil
}

func main() {
	allImports = make(map[string]*types.Package)

	var ignore regexpFlag
	flag.Var(&ignore, "ignore", "regular expression of function names to ignore")
	ignorePkg := &stringsFlag{}
	ignorePkg.Set("fmt")
	flag.Var(ignorePkg, "ignorepkg", "comma-separated list of package paths to ignore")
	flag.Parse()

	pkgName := flag.Arg(0)
	if pkgName == "" {
		flag.Usage()
		Fatal("you must specify a package")
	}
	pkg, err := findPackage(pkgName)
	if err != nil {
		Fatal("%s", err)
	}
	files := getFiles(pkg)

	if err := checkFiles(files, ignore.re, ignorePkg.items); err != nil {
		if err == ErrCheckErrors {
			os.Exit(1)
		}
		Fatal("failed to check package: %s", err)
	}
}

// findPackage finds a package.
// path is first tried as an import path and if the package is not found, as a filesystem path.
func findPackage(path string) (*build.Package, error) {
	var (
		err1, err2 error
		pkg        *build.Package
	)

	ctx := build.Default
	ctx.CgoEnabled = false

	// First try to treat path as import path...
	pkg, err1 = ctx.Import(path, ".", 0)
	if err1 != nil {
		// ... then attempt as file path
		pkg, err2 = ctx.ImportDir(path, 0)
	}

	if err2 != nil {
		// Print both errors so the user can see in what ways the
		// package lookup failed.
		return nil, fmt.Errorf("could not import %s: %s\n%s", path, err1, err2)
	}

	return pkg, nil
}

// getFiles returns all the Go files found in a package
func getFiles(pkg *build.Package) []string {
	files := make([]string, len(pkg.GoFiles))
	for i, fileName := range pkg.GoFiles {
		files[i] = filepath.Join(pkg.Dir, fileName)
	}
	return files
}

type file struct {
	fset  *token.FileSet
	name  string
	ast   *ast.File
	lines [][]byte
}

func parseFile(fset *token.FileSet, fileName string) (f file, err error) {
	rd, err := os.Open(fileName)
	if err != nil {
		return f, err
	}
	defer rd.Close()

	data, err := ioutil.ReadAll(rd)
	if err != nil {
		return f, err
	}

	astFile, err := parser.ParseFile(fset, fileName, bytes.NewReader(data), parser.ParseComments)
	if err != nil {
		return f, fmt.Errorf("could not parse: %s", err)
	}

	lines := bytes.Split(data, []byte("\n"))
	f = file{fset: fset, name: fileName, ast: astFile, lines: lines}
	return f, nil
}

func typeCheck(fset *token.FileSet, astFiles []*ast.File) (map[*ast.CallExpr]types.Type, map[*ast.Ident]types.Object, error) {
	callTypes := make(map[*ast.CallExpr]types.Type)
	identObjs := make(map[*ast.Ident]types.Object)

	exprFn := func(x ast.Expr, typ types.Type, val exact.Value) {
		call, ok := x.(*ast.CallExpr)
		if !ok {
			return
		}
		callTypes[call] = typ
	}
	identFn := func(id *ast.Ident, obj types.Object) {
		identObjs[id] = obj
	}
	context := types.Context{
		Expr:   exprFn,
		Ident:  identFn,
		Import: importer,
	}
	_, err := context.Check(fset, astFiles)
	return callTypes, identObjs, err
}

type checker struct {
	fset      *token.FileSet
	files     map[string]file
	callTypes map[*ast.CallExpr]types.Type
	identObjs map[*ast.Ident]types.Object
	ignore    *regexp.Regexp
	ignorePkg map[string]bool

	errors []error
}

type uncheckedErr struct {
	pos  token.Position
	line []byte
}

func (e uncheckedErr) Error() string {
	return fmt.Sprintf("%s %s", e.pos, e.line)
}

func (c *checker) Visit(node ast.Node) ast.Visitor {
	n, ok := node.(*ast.ExprStmt)
	if !ok {
		return c
	}

	// Check for a call expression
	call, ok := n.X.(*ast.CallExpr)
	if !ok {
		return c
	}

	// Try to get an identifier.
	// Currently only supports simple expressions:
	//     1. f()
	//     2. x.y.f()
	var id *ast.Ident
	switch exp := call.Fun.(type) {
	case (*ast.Ident):
		id = exp
	case (*ast.SelectorExpr):
		id = exp.Sel
	default:
		// eg: *ast.SliceExpr, *ast.IndexExpr
	}

	// If we got an identifier for the function, see if it is ignored
	if id != nil {
		// Ignore if in an ignored package
		if obj := c.identObjs[id]; obj != nil {
			if pkg := obj.GetPkg(); pkg != nil && c.ignorePkg[pkg.Path] {
				return c
			}
		}
		// Ignore if the name matches the regexp
		if c.ignore != nil && c.ignore.MatchString(id.Name) {
			return c
		}
	}

	unchecked := false
	switch t := c.callTypes[call].(type) {
	case *types.NamedType:
		// Single return
		if isErrorType(t.Obj) {
			unchecked = true
		}
	case *types.Result:
		// Multiple returns
		for _, v := range t.Values {
			nt, ok := v.Type.(*types.NamedType)
			if !ok {
				continue
			}
			if isErrorType(nt.Obj) {
				unchecked = true
				break
			}
		}
	}

	if unchecked {
		pos := c.fset.Position(call.Lparen)
		line := bytes.TrimSpace(c.files[pos.Filename].lines[pos.Line-1])
		line = append([]byte{'\t'}, line...)
		c.errors = append(c.errors, uncheckedErr{pos, line})
	}
	return c
}

func checkFiles(fileNames []string, ignore *regexp.Regexp, ignorePkg map[string]bool) error {
	fset := token.NewFileSet()
	astFiles := make([]*ast.File, len(fileNames))
	files := make(map[string]file, len(fileNames))

	for i, fileName := range fileNames {
		f, err := parseFile(fset, fileName)
		if err != nil {
			return fmt.Errorf("could not parse %s: %s", fileName, err)
		}
		files[fileName] = f
		astFiles[i] = f.ast
	}

	callTypes, identObjs, err := typeCheck(fset, astFiles)
	if err != nil {
		return fmt.Errorf("could not type check: %s", err)
	}

	visitor := &checker{fset, files, callTypes, identObjs, ignore, ignorePkg, []error{}}
	for _, astFile := range astFiles {
		ast.Walk(visitor, astFile)
	}

	for _, e := range visitor.errors {
		fmt.Println(e)
	}

	if len(visitor.errors) > 0 {
		return ErrCheckErrors
	}
	return nil
}

type obj interface {
	GetPkg() *types.Package
	GetName() string
}

func isErrorType(v obj) bool {
	return v.GetPkg() == nil && v.GetName() == "error"
}

func importer(imports map[string]*types.Package, path string) (pkg *types.Package, err error) {
	// types.Importer does not seem to be designed for recursive
	// parsing like we're doing here. Specifically, each nested import
	// will maintain its own imports map. This will lead to duplicate
	// imports and in turn packages, which will lead to funny errors
	// such as "cannot pass argument ip (variable of type net.IP) to
	// variable of type net.IP"
	//
	// To work around this, we keep a global imports map, allImports,
	// to which we add all nested imports, and which we use as the
	// cache, instead of imports.
	//
	// Since all nested imports will also use this importer, there
	// should be no way to end up with duplicate imports.

	// We first try to use GcImport directly. This has the downside of
	// using possibly out-of-date packages, but it has the upside of
	// not having to parse most of the Go standard library.

	buildPkg, buildErr := build.Import(path, ".", 0)

	// If we found no build dir, assume we're dealing with installed
	// but no source. If we found a build dir, only use GcImport if
	// it's in GOROOT. This way we always use up-to-date code for
	// normal packages but avoid parsing the standard library.
	if (buildErr == nil && buildPkg.Goroot) || buildErr != nil {
		pkg, err = types.GcImport(allImports, path)
		if err == nil {
			// We don't use imports, but per API we have to add the package.
			imports[pkg.Path] = pkg
			allImports[pkg.Path] = pkg
			return pkg, nil
		}
	}

	// See if we already imported this package
	if pkg = allImports[path]; pkg != nil && pkg.Complete {
		return pkg, nil
	}

	// allImports failed, try to use go/build
	if buildErr != nil {
		return nil, buildErr
	}

	fileSet := token.NewFileSet()

	isGoFile := func(d os.FileInfo) bool {
		allFiles := make([]string, 0, len(buildPkg.GoFiles)+len(buildPkg.CgoFiles))
		allFiles = append(allFiles, buildPkg.GoFiles...)
		allFiles = append(allFiles, buildPkg.CgoFiles...)

		for _, file := range allFiles {
			if file == d.Name() {
				return true
			}
		}
		return false
	}
	pkgs, err := parser.ParseDir(fileSet, buildPkg.Dir, isGoFile, 0)
	if err != nil {
		return nil, err
	}

	delete(pkgs, "documentation")
	var astPkg *ast.Package
	var name string
	for name, astPkg = range pkgs {
		// Use the first non-main package, or the only package we
		// found.
		//
		// NOTE(dh) I can't think of a reason why there should be
		// multiple packages in a single directory, but ParseDir
		// accommodates for that possibility.
		if len(pkgs) == 1 || name != "main" {
			break
		}
	}

	if astPkg == nil {
		return nil, fmt.Errorf("can't find import: %s", name)
	}

	var ff []*ast.File
	for _, f := range astPkg.Files {
		ff = append(ff, f)
	}

	context := types.Context{
		Import: importer,
	}

	pkg, err = context.Check(fileSet, ff)
	if err != nil {
		return pkg, err
	}

	// We don't use imports, but per API we have to add the package.
	imports[path] = pkg
	allImports[path] = pkg
	pkg.Complete = true
	return pkg, nil
}
<|endoftext|>