<commit_before>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"sync"
	"sync/atomic"
	"time"

	"github.com/keybase/client/go/logger"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/kbfs/kbfsblock"
	"golang.org/x/net/context"
)

// ECQUCtxTagKey is the type for unique ECQU background opertaion IDs.
type ECQUCtxTagKey struct{}

// ECQUID is used in EventuallyConsistentQuotaUsage for only background RPCs.
// More specifically, when we need to spawn a background goroutine for
// GetUserQuotaInfo, a new context with this tag is created and used. This is
// also used as a prefix for the logger module name in
// EventuallyConsistentQuotaUsage.
const ECQUID = "ECQU"

type cachedQuotaUsage struct {
	timestamp  time.Time
	usageBytes int64
	limitBytes int64
}

// EventuallyConsistentQuotaUsage keeps tracks of quota usage, in a way user of
// which can choose to accept stale data to reduce calls into block servers.
type EventuallyConsistentQuotaUsage struct {
	config Config
	log    logger.Logger
	tid    keybase1.TeamID

	backgroundInProcess int32

	mu     sync.RWMutex
	cached cachedQuotaUsage
}

// NewEventuallyConsistentQuotaUsage creates a new
// EventuallyConsistentQuotaUsage object.
func NewEventuallyConsistentQuotaUsage(
	config Config, loggerSuffix string) *EventuallyConsistentQuotaUsage {
	return &EventuallyConsistentQuotaUsage{
		config: config,
		log:    config.MakeLogger(ECQUID + "-" + loggerSuffix),
	}
}

// NewEventuallyConsistentTeamQuotaUsage creates a new
// EventuallyConsistentQuotaUsage object.
func NewEventuallyConsistentTeamQuotaUsage(
	config Config, tid keybase1.TeamID,
	loggerSuffix string) *EventuallyConsistentQuotaUsage {
	return &EventuallyConsistentQuotaUsage{
		config: config,
		log:    config.MakeLogger(ECQUID + "-" + loggerSuffix),
		tid:    tid,
	}
}

func (q *EventuallyConsistentQuotaUsage) getAndCache(
	ctx context.Context) (usage cachedQuotaUsage, err error) {
	defer func() {
		q.log.CDebugf(ctx, "getAndCache: error=%v", err)
	}()
	var quotaInfo *kbfsblock.QuotaInfo
	if q.tid.IsNil() {
		quotaInfo, err = q.config.BlockServer().GetUserQuotaInfo(ctx)
	} else {
		quotaInfo, err = q.config.BlockServer().GetTeamQuotaInfo(ctx, q.tid)
	}
	if err != nil {
		return cachedQuotaUsage{}, err
	}

	usage.limitBytes = quotaInfo.Limit
	if quotaInfo.Total != nil {
		usage.usageBytes = quotaInfo.Total.Bytes[kbfsblock.UsageWrite]
	} else {
		usage.usageBytes = 0
	}
	usage.timestamp = q.config.Clock().Now()

	q.mu.Lock()
	defer q.mu.Unlock()
	q.cached = usage

	return usage, nil
}

// Get returns KBFS bytes used and limit for user. To help avoid having too
// frequent calls into bserver, caller can provide a positive tolerance, to
// accept stale LimitBytes and UsageBytes data. If tolerance is 0 or negative,
// this always makes a blocking RPC to bserver and return latest quota usage.
//
// 1) If the age of cached data is more than blockTolerance, a blocking RPC is
// issued and the function only returns after RPC finishes, with the newest
// data from RPC. The RPC causes cached data to be refreshed as well.
// 2) Otherwise, if the age of cached data is more than bgTolerance,
// a background RPC is spawned to refresh cached data, and the stale
// data is returned immediately.
// 3) Otherwise, the cached stale data is returned immediately.
func (q *EventuallyConsistentQuotaUsage) Get(
	ctx context.Context, bgTolerance, blockTolerance time.Duration) (
	timestamp time.Time, usageBytes, limitBytes int64, err error) {
	c := func() cachedQuotaUsage {
		q.mu.RLock()
		defer q.mu.RUnlock()
		return q.cached
	}()
	past := q.config.Clock().Now().Sub(c.timestamp)
	switch {
	case past > blockTolerance:
		q.log.CDebugf(ctx, "Blocking on getAndCache. Cached data is %s old.", past)
		// TODO: optimize this to make sure there's only one outstanding RPC. In
		// other words, wait for it to finish if one is already in progress.
		c, err = q.getAndCache(ctx)
		if err != nil {
			return time.Time{}, -1, -1, err
		}
	case past > bgTolerance:
		if atomic.CompareAndSwapInt32(&q.backgroundInProcess, 0, 1) {
			id, err := MakeRandomRequestID()
			if err != nil {
				q.log.Warning("Couldn't generate a random request ID: %v", err)
			}
			q.log.CDebugf(ctx, "Cached data is %s old. Spawning getAndCache in "+
				"background with tag:%s=%v.", past, ECQUID, id)
			go func() {
				// Make a new context so that it doesn't get canceled when returned.
				logTags := make(logger.CtxLogTags)
				logTags[ECQUCtxTagKey{}] = ECQUID
				bgCtx := logger.NewContextWithLogTags(context.Background(), logTags)
				bgCtx = context.WithValue(bgCtx, ECQUCtxTagKey{}, id)
				// Make sure a timeout is on the context, in case the RPC blocks
				// forever somehow, where we'd end up with never resetting
				// backgroundInProcess flag again.
				bgCtx, cancel := context.WithTimeout(bgCtx, 10*time.Second)
				defer cancel()
				// The error is igonred here without logging since getAndCache already
				// logs it.
				_, _ = q.getAndCache(bgCtx)
				atomic.StoreInt32(&q.backgroundInProcess, 0)
			}()
		} else {
			q.log.CDebugf(ctx,
				"Cached data is %s old, but background getAndCache is already running.", past)
		}
	default:
		q.log.CDebugf(ctx, "Returning cached data from %s ago.", past)
	}
	return c.timestamp, c.usageBytes, c.limitBytes, nil
}
<commit_msg>quota_usage: always block on the get on the first access<commit_after>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libkbfs

import (
	"sync"
	"sync/atomic"
	"time"

	"github.com/keybase/client/go/logger"
	"github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/kbfs/kbfsblock"
	"golang.org/x/net/context"
)

// ECQUCtxTagKey is the type for unique ECQU background opertaion IDs.
type ECQUCtxTagKey struct{}

// ECQUID is used in EventuallyConsistentQuotaUsage for only background RPCs.
// More specifically, when we need to spawn a background goroutine for
// GetUserQuotaInfo, a new context with this tag is created and used. This is
// also used as a prefix for the logger module name in
// EventuallyConsistentQuotaUsage.
const ECQUID = "ECQU"

type cachedQuotaUsage struct {
	timestamp  time.Time
	usageBytes int64
	limitBytes int64
}

// EventuallyConsistentQuotaUsage keeps tracks of quota usage, in a way user of
// which can choose to accept stale data to reduce calls into block servers.
type EventuallyConsistentQuotaUsage struct {
	config Config
	log    logger.Logger
	tid    keybase1.TeamID

	backgroundInProcess int32

	mu     sync.RWMutex
	cached cachedQuotaUsage
}

// NewEventuallyConsistentQuotaUsage creates a new
// EventuallyConsistentQuotaUsage object.
func NewEventuallyConsistentQuotaUsage(
	config Config, loggerSuffix string) *EventuallyConsistentQuotaUsage {
	return &EventuallyConsistentQuotaUsage{
		config: config,
		log:    config.MakeLogger(ECQUID + "-" + loggerSuffix),
	}
}

// NewEventuallyConsistentTeamQuotaUsage creates a new
// EventuallyConsistentQuotaUsage object.
func NewEventuallyConsistentTeamQuotaUsage(
	config Config, tid keybase1.TeamID,
	loggerSuffix string) *EventuallyConsistentQuotaUsage {
	return &EventuallyConsistentQuotaUsage{
		config: config,
		log:    config.MakeLogger(ECQUID + "-" + loggerSuffix),
		tid:    tid,
	}
}

func (q *EventuallyConsistentQuotaUsage) getAndCache(
	ctx context.Context) (usage cachedQuotaUsage, err error) {
	defer func() {
		q.log.CDebugf(ctx, "getAndCache: error=%v", err)
	}()
	var quotaInfo *kbfsblock.QuotaInfo
	if q.tid.IsNil() {
		quotaInfo, err = q.config.BlockServer().GetUserQuotaInfo(ctx)
	} else {
		quotaInfo, err = q.config.BlockServer().GetTeamQuotaInfo(ctx, q.tid)
	}
	if err != nil {
		return cachedQuotaUsage{}, err
	}

	usage.limitBytes = quotaInfo.Limit
	if quotaInfo.Total != nil {
		usage.usageBytes = quotaInfo.Total.Bytes[kbfsblock.UsageWrite]
	} else {
		usage.usageBytes = 0
	}
	usage.timestamp = q.config.Clock().Now()

	q.mu.Lock()
	defer q.mu.Unlock()
	q.cached = usage

	return usage, nil
}

// Get returns KBFS bytes used and limit for user. To help avoid having too
// frequent calls into bserver, caller can provide a positive tolerance, to
// accept stale LimitBytes and UsageBytes data. If tolerance is 0 or negative,
// this always makes a blocking RPC to bserver and return latest quota usage.
//
// 1) If the age of cached data is more than blockTolerance, a blocking RPC is
// issued and the function only returns after RPC finishes, with the newest
// data from RPC. The RPC causes cached data to be refreshed as well.
// 2) Otherwise, if the age of cached data is more than bgTolerance,
// a background RPC is spawned to refresh cached data, and the stale
// data is returned immediately.
// 3) Otherwise, the cached stale data is returned immediately.
func (q *EventuallyConsistentQuotaUsage) Get(
	ctx context.Context, bgTolerance, blockTolerance time.Duration) (
	timestamp time.Time, usageBytes, limitBytes int64, err error) {
	c := func() cachedQuotaUsage {
		q.mu.RLock()
		defer q.mu.RUnlock()
		return q.cached
	}()
	past := q.config.Clock().Now().Sub(c.timestamp)
	switch {
	case past > blockTolerance || c.timestamp.IsZero():
		q.log.CDebugf(ctx, "Blocking on getAndCache. Cached data is %s old.", past)
		// TODO: optimize this to make sure there's only one outstanding RPC. In
		// other words, wait for it to finish if one is already in progress.
		c, err = q.getAndCache(ctx)
		if err != nil {
			return time.Time{}, -1, -1, err
		}
	case past > bgTolerance:
		if atomic.CompareAndSwapInt32(&q.backgroundInProcess, 0, 1) {
			id, err := MakeRandomRequestID()
			if err != nil {
				q.log.Warning("Couldn't generate a random request ID: %v", err)
			}
			q.log.CDebugf(ctx, "Cached data is %s old. Spawning getAndCache in "+
				"background with tag:%s=%v.", past, ECQUID, id)
			go func() {
				// Make a new context so that it doesn't get canceled when returned.
				logTags := make(logger.CtxLogTags)
				logTags[ECQUCtxTagKey{}] = ECQUID
				bgCtx := logger.NewContextWithLogTags(context.Background(), logTags)
				bgCtx = context.WithValue(bgCtx, ECQUCtxTagKey{}, id)
				// Make sure a timeout is on the context, in case the RPC blocks
				// forever somehow, where we'd end up with never resetting
				// backgroundInProcess flag again.
				bgCtx, cancel := context.WithTimeout(bgCtx, 10*time.Second)
				defer cancel()
				// The error is igonred here without logging since getAndCache already
				// logs it.
				_, _ = q.getAndCache(bgCtx)
				atomic.StoreInt32(&q.backgroundInProcess, 0)
			}()
		} else {
			q.log.CDebugf(ctx,
				"Cached data is %s old, but background getAndCache is already running.", past)
		}
	default:
		q.log.CDebugf(ctx, "Returning cached data from %s ago.", past)
	}
	return c.timestamp, c.usageBytes, c.limitBytes, nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
)

type User struct {
	Name string
	Dir  string
}

func GetUsers(installationDir string) ([]User, error) {
	userdataDir := filepath.Join(installationDir, "userdata")
	files, err := ioutil.ReadDir(userdataDir)
	if err != nil {
		return nil, err
	}

	users := make([]User, 0)

	for _, userDir := range files {
		userId := userDir.Name()
		userDir := filepath.Join(userdataDir, userId)
		configFile := filepath.Join(userDir, "config", "localconfig.vdf")

		configBytes, err := ioutil.ReadFile(configFile)
		if err != nil {
			return nil, err
		}
		pattern := regexp.MustCompile(`"PersonaName"\s*"(.+?)"`)
		username := pattern.FindStringSubmatch(string(configBytes))[1]
		users = append(users, User{username, userDir})
	}

	return users, nil
}

const urlFormat = `http://steamcommunity.com/id/%v/games?tab=all`

func GetProfile(username string) (string, error) {
	response, err := http.Get(fmt.Sprintf(urlFormat, username))
	if err != nil {
		return "", err
	}

	contentBytes, err := ioutil.ReadAll(response.Body)
	response.Body.Close()
	if err != nil {
		return "", err
	}

	return string(contentBytes), nil
}

type Game struct {
	Id   string
	Name string
	Category string
}

const gamePattern = `\{"appid":\s*(\d+),\s*"name":\s*"(.+?)"`

func GetGames(user User) ([]Game, error) {
	profile, err := GetProfile(user.Name)
	if err != nil {
		return nil, err
	}

	pattern := regexp.MustCompile(gamePattern)
	games := make([]Game, 0)
	for _, groups := range pattern.FindAllStringSubmatch(profile, -1) {
		games = append(games, Game{groups[1], groups[2], ""})
	}

	sharedConfFile := filepath.Join(user.Dir, "7", "remote", "sharedconfig.vdf")
	sharedConfBytes, err := ioutil.ReadFile(sharedConfFile)

	sharedConf := string(sharedConfBytes)
	pattern = regexp.MustCompile(`"([0-9]+)"\s*{[^}]+?"tags"\s*{\s*"0"\s*"([^"]+)"`)
	for _, groups := range pattern.FindAllStringSubmatch(sharedConf, -1) {
		gameId := groups[1]
		category := groups[2]
		for _, game := range games {
			if game.Id == gameId {
				game.Category = category
				fmt.Println(game.Category, game.Name)
			}
		}
	}

	return games, nil
}

const imageUrlFormat = `https://steamcdn-a.akamaihd.net/steam/apps/%v/header.jpg`
const alternativeUrlFormat = `http://cdn.steampowered.com/v/gfx/apps/%v/header.jpg`

const googleSearchFormat = `https://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=`

func getGoogleImage(gameName string) (string, error) {
	url := googleSearchFormat + url.QueryEscape("steam grid OR header" + gameName)
	response, err := http.Get(url)
	if err != nil {
		return "", err
	}

	responseBytes, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return "", err
	}
	response.Body.Close()
	pattern := regexp.MustCompile(`"width":"460","height":"215",[^}]+"unescapedUrl":"(.+?)"`)
	imageUrl := pattern.FindStringSubmatch(string(responseBytes))[1]
	return imageUrl, nil
}

func tryDownload(url string) (*http.Response, error) {

	response, err := http.Get(url)
	if err != nil {
		return nil, err
	}

	if response.StatusCode == 404 {
		// Some apps don't have an image and there's nothing we can do.
		return nil, nil
	} else if response.StatusCode > 400 {
		// Other errors should be reported, though.
		return nil, errors.New("Failed to download image " + url + ": " + response.Status)
	}

	return response, nil
}

func getImageAlternatives(game Game) (response *http.Response, fromGoogle bool, err error) {
	response, err = tryDownload(fmt.Sprintf(imageUrlFormat, game.Id))
	if err == nil && response != nil {
		return
	}

	response, err = tryDownload(fmt.Sprintf(alternativeUrlFormat, game.Id))
	if err == nil && response != nil {
		return
	}

	fromGoogle = true
	url, err := getGoogleImage(game.Name)
	if err != nil {
		return
	}
	response, err = tryDownload(url)
	if err == nil && response != nil {
		return
	}

	return nil, false, nil
}

func DownloadImage(game Game, gridDir string) (found bool, fromGoogle bool, err error) {
	filename := filepath.Join(gridDir, game.Id+".jpg")
	if _, err := os.Stat(filename); err == nil {
		// File already exists, skip it.
		return true, false, nil
	}

	response, fromGoogle, err := getImageAlternatives(game)
	if response == nil || err != nil {
		return false, false, err
	}

	imageBytes, err := ioutil.ReadAll(response.Body)
	response.Body.Close()
	return true, fromGoogle, ioutil.WriteFile(filename, imageBytes, 0666)
}

func GetSteamInstallation() (path string, err error) {
	if len(os.Args) == 2 {
		argDir := os.Args[1]
		_, err := os.Stat(argDir)
		if err == nil {
			return argDir, nil
		} else {
			return "", errors.New("Argument must be a valid Steam directory, or empty for auto detection. Got: " + argDir)
		}
	}

	programFiles86Dir := filepath.Join(os.Getenv("ProgramFiles(x86)"), "Steam")
	if _, err = os.Stat(programFiles86Dir); err == nil {
		return programFiles86Dir, nil
	}

	programFilesDir := filepath.Join(os.Getenv("ProgramFiles"), "Steam")
	if _, err = os.Stat(programFilesDir); err == nil {
		return programFilesDir, nil
	}

	return "", errors.New("Could not find Steam installation folder.")
}

func PrintProgress(current int, total int) {
	fmt.Print("\r[")
	printedHead := false
	for i := 0; i < 40; i++ {
		part := int(float64(i) * (float64(total) / 40.0))
		if part < current {
			fmt.Print("=")
		} else if !printedHead {
			printedHead = true
			fmt.Print(">")
		} else {
			fmt.Print(" ")
		}
	}
	fmt.Printf("] (%v/%v)", current, total)
}

func errorAndExit(err error) {
	fmt.Println("An unexpected error occurred:")
	fmt.Println(err)
	os.Exit(1)
}

func main() {
	installationDir, err := GetSteamInstallation()
	if err != nil {
		errorAndExit(err)
	}

	users, err := GetUsers(installationDir)
	if err != nil {
		errorAndExit(err)
	}

	for _, user := range users {
		fmt.Printf("Found user %v. Fetching game list from profile...\n\n\n", user.Name)

		games, err := GetGames(user)
		if err != nil {
			errorAndExit(err)
		}

		notFounds := make([]Game, 0)
		googleFounds := make([]Game, 0)
		fmt.Printf("Found %v games. Downloading images...\n\n", len(games))
		continue
		for i, game := range games {
			PrintProgress(i+1, len(games))
			gridDir := filepath.Join(user.Dir, "config", "grid")
			found, fromGoogle, err := DownloadImage(game, gridDir)
			if err != nil {
				errorAndExit(err)
			}
			if !found {
				notFounds = append(notFounds, game)
			}
			if fromGoogle {
				googleFounds = append(googleFounds, game)
			}
		}
		fmt.Print("\n\n\n")

		if len(notFounds) == 0 && len(googleFounds) == 0 {
			fmt.Println("All grid images downloaded!")
		} else {
			if len(googleFounds) >= 1 {
				fmt.Printf("%v images were found with a Google search and may not be accurate:.\n", len(googleFounds))
				for _, game := range googleFounds {
					fmt.Printf("* %v (steam id %v)\n", game.Name, game.Id)
				}
			}

			fmt.Print("\n\n")

			if len(notFounds) >= 1 {
				fmt.Printf("%v images could not be found:\n", len(notFounds))
				for _, game := range notFounds {
					fmt.Printf("* %v (steam id %v)\n", game.Name, game.Id)
				}
			}
		}
	}

	fmt.Print("\n\n")
	fmt.Println("You can press enter to close this window.")
	os.Stdin.Read(make([]byte, 1))
}
<commit_msg>Add comments and docs<commit_after>// Automatically downloads and configures Steam grid images for all games in a
// given Steam installation.
package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
)

// User in the local steam installation.
type User struct {
	Name string
	Dir  string
}

// Given the Steam installation dir (NOT the library!), returns all users in
// this computer.
func GetUsers(installationDir string) ([]User, error) {
	userdataDir := filepath.Join(installationDir, "userdata")
	files, err := ioutil.ReadDir(userdataDir)
	if err != nil {
		return nil, err
	}

	users := make([]User, 0)

	for _, userDir := range files {
		userId := userDir.Name()
		userDir := filepath.Join(userdataDir, userId)

		configFile := filepath.Join(userDir, "config", "localconfig.vdf")
		configBytes, err := ioutil.ReadFile(configFile)
		if err != nil {
			return nil, err
		}

		pattern := regexp.MustCompile(`"PersonaName"\s*"(.+?)"`)
		username := pattern.FindStringSubmatch(string(configBytes))[1]
		users = append(users, User{username, userDir})
	}

	return users, nil
}

// Steam profile URL format.
const urlFormat = `http://steamcommunity.com/id/%v/games?tab=all`

// Returns the public Steam profile for a given user, in HTML.
func GetProfile(username string) (string, error) {
	response, err := http.Get(fmt.Sprintf(urlFormat, username))
	if err != nil {
		return "", err
	}

	contentBytes, err := ioutil.ReadAll(response.Body)
	response.Body.Close()
	if err != nil {
		return "", err
	}

	return string(contentBytes), nil
}

// A Steam game in a library. May or may not be installed.
type Game struct {
	// Official Steam id.
	Id   string
	// Warning, may contain Unicode characters.
	Name string
	// User created category. May be blank.
	Category string
}

// Pattern of game declarations in the public profile. It's actually JSON
// inside Javascript, but this way is easier to extract.
const profileGamePattern = `\{"appid":\s*(\d+),\s*"name":\s*"(.+?)"`

// Returns all games from a given user, using both the public profile and local
// files to gather the data.
func GetGames(user User) ([]Game, error) {
	profile, err := GetProfile(user.Name)
	if err != nil {
		return nil, err
	}

	// Fetch game list from public profile.
	pattern := regexp.MustCompile(profileGamePattern)
	games := make([]Game, 0)
	for _, groups := range pattern.FindAllStringSubmatch(profile, -1) {
		games = append(games, Game{groups[1], groups[2], ""})
	}

	// Fetch game categories from local file.
	sharedConfFile := filepath.Join(user.Dir, "7", "remote", "sharedconfig.vdf")
	sharedConfBytes, err := ioutil.ReadFile(sharedConfFile)

	sharedConf := string(sharedConfBytes)
	// VDF patterN: "steamid" { "tags { "0" "category" } }
	pattern = regexp.MustCompile(`"([0-9]+)"\s*{[^}]+?"tags"\s*{\s*"0"\s*"([^"]+)"`)
	for _, groups := range pattern.FindAllStringSubmatch(sharedConf, -1) {
		gameId := groups[1]
		category := groups[2]

		// Slow, could be made with a map, but there won't be enough items to
		// make a difference.
		for _, game := range games {
			if game.Id == gameId {
				game.Category = category
				fmt.Println(game.Category, game.Name)
				break
			}
		}
	}

	return games, nil
}

// When all else fails, Google it. Unfortunately this is a deprecated API and
// may go offline at any time. Because this is last resort the number of
// requests shouldn't trigger any punishment.
const googleSearchFormat = `https://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=`

// Returns the first steam grid image URL found by Google search of a given
// game name.
func getGoogleImage(gameName string) (string, error) {
	url := googleSearchFormat + url.QueryEscape("steam grid OR header" + gameName)
	response, err := http.Get(url)
	if err != nil {
		return "", err
	}

	responseBytes, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return "", err
	}
	response.Body.Close()
	// Again, we could parse JSON. This may be a little too lazy, the pattern
	// is very loose. The order could be wrong, for example.
	pattern := regexp.MustCompile(`"width":"460","height":"215",[^}]+"unescapedUrl":"(.+?)"`)
	imageUrl := pattern.FindStringSubmatch(string(responseBytes))[1]
	return imageUrl, nil
}

// Tries to fetch a URL, returning the response only if it was positive.
func tryDownload(url string) (*http.Response, error) {
	response, err := http.Get(url)
	if err != nil {
		return nil, err
	}

	if response.StatusCode == 404 {
		// Some apps don't have an image and there's nothing we can do.
		return nil, nil
	} else if response.StatusCode > 400 {
		// Other errors should be reported, though.
		return nil, errors.New("Failed to download image " + url + ": " + response.Status)
	}

	return response, nil
}

// Primary URL for downloading grid images.
const akamaiUrlFormat = `https://steamcdn-a.akamaihd.net/steam/apps/%v/header.jpg`
// The subreddit mentions this as primary, but I've found Akamai to contain
// more images and answer faster.
const steamCdnUrlFormat = `http://cdn.steampowered.com/v/gfx/apps/%v/header.jpg`

// Tries to load the grid image for a game from a number of alternative
// sources. Returns the final response received and a flag indicating if it was
// fro ma Google search (useful because we want to log the lower quality
// images).
func getImageAlternatives(game Game) (response *http.Response, fromSearch bool, err error) {
	response, err = tryDownload(fmt.Sprintf(akamaiUrlFormat, game.Id))
	if err == nil && response != nil {
		return
	}

	response, err = tryDownload(fmt.Sprintf(steamCdnUrlFormat, game.Id))
	if err == nil && response != nil {
		return
	}

	fromSearch = true
	url, err := getGoogleImage(game.Name)
	if err != nil {
		return
	}
	response, err = tryDownload(url)
	if err == nil && response != nil {
		return
	}

	return nil, false, nil
}

// Downloads the grid image for a game into the user's grid directory. Returns
// flags indicating if the operation succeeded and if the image downloaded was
// from a search.
func DownloadImage(game Game, user User) (found bool, fromSearch bool, err error) {
	gridDir := filepath.Join(user.Dir, "config", "grid")
	filename := filepath.Join(gridDir, game.Id+".jpg")
	if _, err := os.Stat(filename); err == nil {
		// File already exists, skip it.
		return true, false, nil
	}

	response, fromSearch, err := getImageAlternatives(game)
	if response == nil || err != nil {
		return false, false, err
	}

	imageBytes, err := ioutil.ReadAll(response.Body)
	response.Body.Close()
	return true, fromSearch, ioutil.WriteFile(filename, imageBytes, 0666)
}

// Returns the Steam installation directory in Windows. Should work for
// internationalized systems, 32 and 64 bits and users that moved their
// ProgramFiles folder. If a folder is given by program parameter, uses that.
func GetSteamInstallation() (path string, err error) {
	if len(os.Args) == 2 {
		argDir := os.Args[1]
		_, err := os.Stat(argDir)
		if err == nil {
			return argDir, nil
		} else {
			return "", errors.New("Argument must be a valid Steam directory, or empty for auto detection. Got: " + argDir)
		}
	}

	programFiles86Dir := filepath.Join(os.Getenv("ProgramFiles(x86)"), "Steam")
	if _, err = os.Stat(programFiles86Dir); err == nil {
		return programFiles86Dir, nil
	}

	programFilesDir := filepath.Join(os.Getenv("ProgramFiles"), "Steam")
	if _, err = os.Stat(programFilesDir); err == nil {
		return programFilesDir, nil
	}

	return "", errors.New("Could not find Steam installation folder.")
}

// Prints a progress bar, overriding the previous line. It looks like this:
// [=========>        ] (50/100)
func PrintProgress(current int, total int) {
	// \r moves the cursor back to the start of the line.
	fmt.Print("\r[")

	printedHead := false
	for i := 0; i < 40; i++ {
		part := int(float64(i) * (float64(total) / 40.0))
		if part < current {
			fmt.Print("=")
		} else if !printedHead {
			printedHead = true
			fmt.Print(">")
		} else {
			fmt.Print(" ")
		}
	}

	fmt.Printf("] (%v/%v)", current, total)
}

// Prints an error and quits.
func errorAndExit(err error) {
	fmt.Println("An unexpected error occurred:")
	fmt.Println(err)
	os.Exit(1)
}

func main() {
	installationDir, err := GetSteamInstallation()
	if err != nil {
		errorAndExit(err)
	}

	users, err := GetUsers(installationDir)
	if err != nil {
		errorAndExit(err)
	}

	for _, user := range users {
		fmt.Printf("Found user %v. Fetching game list from profile...\n\n\n", user.Name)

		games, err := GetGames(user)
		if err != nil {
			errorAndExit(err)
		}

		notFounds := make([]Game, 0)
		searchFounds := make([]Game, 0)
		fmt.Printf("Found %v games. Downloading images...\n\n", len(games))
		continue
		for i, game := range games {
			PrintProgress(i+1, len(games))
			found, fromSearch, err := DownloadImage(game, user)
			if err != nil {
				errorAndExit(err)
			}
			if !found {
				notFounds = append(notFounds, game)
			}
			if fromSearch {
				searchFounds = append(searchFounds, game)
			}
		}
		fmt.Print("\n\n\n")

		if len(notFounds) == 0 && len(searchFounds) == 0 {
			fmt.Println("All grid images downloaded!")
		} else {
			if len(searchFounds) >= 1 {
				fmt.Printf("%v images were found with a Google search and may not be accurate:.\n", len(searchFounds))
				for _, game := range searchFounds {
					fmt.Printf("* %v (steam id %v)\n", game.Name, game.Id)
				}
			}

			fmt.Print("\n\n")

			if len(notFounds) >= 1 {
				fmt.Printf("%v images could not be found:\n", len(notFounds))
				for _, game := range notFounds {
					fmt.Printf("* %v (steam id %v)\n", game.Name, game.Id)
				}
			}
		}
	}

	fmt.Print("\n\n")
	fmt.Println("You can press enter to close this window.")
	os.Stdin.Read(make([]byte, 1))
}
<|endoftext|>


<commit_before>// The MIT License (MIT)
// Copyright (c) 2014 Claes Mogren
// http://opensource.org/licenses/MIT

// Code to generate a weighted random identity based on weighted input files
// http://www.keithschwarz.com/darts-dice-coins/
// http://web.eecs.utk.edu/~vose/Publications/random.pdf
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
)

var langFlag string
var listLangFlag bool
var verbose bool

type RigLine struct {
	weight float64
	text   []string
}

type RigFile struct {
	tot      float64
	probList []float64
	texts    [][]string
	// Vose
}

type RigDict struct {
	fnames, mnames, lnames, streets, zipcodes RigFile
}

func init() {
	flag.StringVar(&langFlag, "lang", "en", "Select ISO 639-1 language code")
	flag.BoolVar(&listLangFlag, "l", false, "List available ISO language codes")
	flag.BoolVar(&verbose, "v", false, "Verbose output")
}

func main() {
	flag.Parse()
	if listLangFlag {
		listLangs()
		os.Exit(0)
	}
	loadData(langFlag)
}

func listLangs() {
	// for all dirs in data
	files, _ := ioutil.ReadDir("./data/")
	for _, f := range files {
		if f.IsDir() && !strings.HasPrefix(f.Name(), ",") {
			if validateDir(f.Name()) {
				fmt.Println(f.Name())
			}
		}
	}
}

func validateDir(iso string) bool {
	// Check for fnames, mnames, lnames, zipcodes and streets
	srcFileNames := []string{"fnames.grig", "lnames.grig", "mnames.grig", "streets.grig", "zipcodes.grig"}
	valid := true
	for _, srcFile := range srcFileNames {
		filename := "./data/" + iso + "/" + srcFile
		if _, err := os.Stat(filename); os.IsNotExist(err) {
			if verbose {
				fmt.Println("Data file", srcFile, "missing for", iso)
			}
			valid = false
		}
	}
	return valid
}

func loadData(iso string) RigDict {
	//{"fnames.grig", "lnames.grig", "mnames.grig", "streets.grig", "zipcodes.grig"}
	dict := RigDict{}
	dict.fnames = loadFile(iso, "fnames.grig")
	return dict
}

func loadFile(iso string, srcFile string) RigFile {
	file, err := os.Open("data/" + iso + "/" + srcFile)
	defer file.Close()
	if err != nil {
		fmt.Println(err)
		os.Exit(0)
	}
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		dataStr := strings.Split(scanner.Text(), "\t")
		// string to float
		i, err := strconv.ParseFloat(dataStr[0], 64)
		if err != nil {
			// handle error
			fmt.Println(err)
			os.Exit(2)
		}
		str := dataStr[1:]
		fmt.Println("P: ", i, str)
	}
	return RigFile{}
}
<commit_msg>Add sum of totals<commit_after>// The MIT License (MIT)
// Copyright (c) 2014 Claes Mogren
// http://opensource.org/licenses/MIT

// Code to generate a weighted random identity based on weighted input files
// http://www.keithschwarz.com/darts-dice-coins/
// http://web.eecs.utk.edu/~vose/Publications/random.pdf
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

var langFlag string
var listLangFlag bool
var verbose bool

type RigFile struct {
	tot     float64
	weights []float64
	texts   [][]string
	// Vose
}

type RigDict struct {
	fnames, mnames, lnames, streets, zipcodes RigFile
}

func init() {
	flag.StringVar(&langFlag, "lang", "en", "Select ISO 639-1 language code")
	flag.BoolVar(&listLangFlag, "l", false, "List available ISO language codes")
	flag.BoolVar(&verbose, "v", false, "Verbose output")
}

func main() {
	flag.Parse()
	if listLangFlag {
		listLangs()
		os.Exit(0)
	}
	rand.Seed(time.Now().UnixNano())
	dict := loadData(langFlag)
	printNext(dict)
}

func listLangs() {
	// for all dirs in data
	files, _ := ioutil.ReadDir("./data/")
	for _, f := range files {
		if f.IsDir() && !strings.HasPrefix(f.Name(), ",") {
			if validateDir(f.Name()) {
				fmt.Println(f.Name())
			}
		}
	}
}

func validateDir(iso string) bool {
	// Check for fnames, mnames, lnames, zipcodes and streets
	srcFileNames := []string{"fnames.grig", "lnames.grig", "mnames.grig", "streets.grig", "zipcodes.grig"}
	valid := true
	for _, srcFile := range srcFileNames {
		filename := "./data/" + iso + "/" + srcFile
		if _, err := os.Stat(filename); os.IsNotExist(err) {
			if verbose {
				fmt.Println("Data file", srcFile, "missing for", iso)
			}
			valid = false
		}
	}
	return valid
}

func loadData(iso string) RigDict {
	dict := RigDict{}
	dict.fnames = loadFile(iso, "fnames.grig")
	dict.mnames = loadFile(iso, "mnames.grig")
	dict.lnames = loadFile(iso, "lnames.grig")
	dict.streets = loadFile(iso, "streets.grig")
	dict.zipcodes = loadFile(iso, "zipcodes.grig")
	if verbose {
		fmt.Println("fname tot:", dict.fnames.tot, dict.fnames.texts[0])
		fmt.Println("mname tot:", dict.mnames.tot, dict.mnames.texts[0])
		fmt.Println("lname tot:", dict.lnames.tot, dict.lnames.texts[0])
	}
	return dict
}

func loadFile(iso string, srcFile string) RigFile {
	file, err := os.Open("data/" + iso + "/" + srcFile)
	rigFile := RigFile{}
	rigFile.weights = make([]float64, 0)
	rigFile.texts = make([][]string, 0)
	defer file.Close()
	if err != nil {
		fmt.Println(err)
		os.Exit(0)
	}
	scanner := bufio.NewScanner(file)
	sum := 0.0
	for scanner.Scan() {
		dataStr := strings.Split(scanner.Text(), "\t")
		// string to float
		f, err := strconv.ParseFloat(dataStr[0], 64)
		if err != nil {
			// Ignore error
			fmt.Println(err)
		}
		sum += f
		str := dataStr[1:]
		rigFile.weights = append(rigFile.weights, f)
		rigFile.texts = append(rigFile.texts, str)
		if verbose {
			fmt.Println("P: ", f, str)
		}
	}
	rigFile.tot = sum
	return rigFile
}

func printNext(dict RigDict) {
	if rand.Intn(2) == 0 {
		fmt.Print(dict.fnames.texts[rand.Intn(len(dict.fnames.texts))])
	} else {
		fmt.Print(dict.mnames.texts[rand.Intn(len(dict.mnames.texts))])
	}
	fmt.Println(dict.lnames.texts[rand.Intn(len(dict.lnames.texts))])
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"strings"
	"text/template"
)

var helpCommand = Command{
	Name:  "help",
	Short: "get help for a command",
	Usage: "help [command]",
	Long: `
Wait, why are you geting help for the help command?
`,
	GetTask: NewHelpTask,
}

var helpTemplate = `
Usage: goenv {{.Usage}}

{{.Long | trim}}
`

// HelpTask initializes a goenv.
type HelpTask struct {
	CommandName string              // the name of the command.
	Commands    map[string]*Command // the map of commands.
}

// NewHelpTask returns a new HelpTask created with the specified command-line args.
func NewHelpTask(args []string) (Task, error) {

	flags := flag.NewFlagSet("help", flag.ExitOnError)
	flags.Parse(args)
	args = flags.Args()

	if len(args) < 1 {
		return nil, errors.New("no command specified")
	}

	return &HelpTask{
		CommandName: args[0],
		Commands:    commands,
	}, nil
}

// Run exeuctes the HelpTask
func (task *HelpTask) Run() error {

	cmd, found := task.Commands[task.CommandName]

	if !found {
		return fmt.Errorf("no such command \"%s\"", cmd)
	}

	tmpl := template.New("help")
	tmpl.Funcs(template.FuncMap{
		"trim": strings.TrimSpace,
	})
	tmpl, err := tmpl.Parse(strings.TrimSpace(helpTemplate) + "\n\n")

	if err != nil {
		return err
	}

	err = tmpl.Execute(os.Stdout, cmd)

	return err
}
<commit_msg>help update<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"strings"
	"text/template"
)

var helpCommand = Command{
	Name:  "help",
	Short: "get help for a command",
	Usage: "help [command]",
	Long: `
Help gets detailed usage information for a command.
`,
	GetTask: NewHelpTask,
}

var helpTemplate = `
Usage: goenv {{.Usage}}

{{.Long | trim}}
`

// HelpTask initializes a goenv.
type HelpTask struct {
	CommandName string              // the name of the command.
	Commands    map[string]*Command // the map of commands.
}

// NewHelpTask returns a new HelpTask created with the specified command-line args.
func NewHelpTask(args []string) (Task, error) {

	flags := flag.NewFlagSet("help", flag.ExitOnError)
	flags.Parse(args)
	args = flags.Args()

	if len(args) < 1 {
		return nil, errors.New("no command specified")
	}

	return &HelpTask{
		CommandName: args[0],
		Commands:    commands,
	}, nil
}

// Run exeuctes the HelpTask
func (task *HelpTask) Run() error {

	cmd, found := task.Commands[task.CommandName]

	if !found {
		return fmt.Errorf("no such command \"%s\"", cmd)
	}

	tmpl := template.New("help")
	tmpl.Funcs(template.FuncMap{
		"trim": strings.TrimSpace,
	})
	tmpl, err := tmpl.Parse(strings.TrimSpace(helpTemplate) + "\n\n")

	if err != nil {
		return err
	}

	err = tmpl.Execute(os.Stdout, cmd)

	return err
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

type httpRequest struct {
	// TODO(CaptainHayashi): method, payload
	resource string
	resCh    chan<- interface{}
}

func initHTTP(connectors []*bfConnector, wspool *Wspool, log *log.Logger) http.Handler {
	r := mux.NewRouter()
	r.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			http.Error(w, "Method not allowed", 405)
			return
		}
		ws, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Println(err)
			return
		}
		c := &wsConn{send: make(chan []byte, 256), ws: ws}
		wspool.register <- c
		c.writeLoop()
	})
	r.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))

	for i := range connectors {
		installConnector(r, connectors[i])
	}

	return r
}

func installConnector(router *mux.Router, connector *bfConnector) {
	fn := func(w http.ResponseWriter, r *http.Request) {
		resCh := make(chan interface{})

		fmt.Printf("sending request to %s\n", connector.name)

		resource := r.URL.Path

		w.Header().Add("Content-Type", "application/json")
		connector.reqCh <- httpRequest{
			resource,
			resCh,
		}

		select {
		case res := <-resCh:
			j, err := json.Marshal(res)
			if err != nil {
				fmt.Println(err)
				break
			}
			_, err = w.Write(j)
			if err != nil {
				fmt.Println(err)
				break
			}
		}
	}

	router.HandleFunc("/"+connector.name, fn)
	router.PathPrefix("/" + connector.name + "/").HandlerFunc(fn)
}
<commit_msg>Move JSON dumping to a separate function.<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"log"
	"io"
	"net/http"

	"github.com/gorilla/mux"
)

type httpRequest struct {
	// TODO(CaptainHayashi): method, payload
	resource string
	resCh    chan<- interface{}
}

func initHTTP(connectors []*bfConnector, wspool *Wspool, log *log.Logger) http.Handler {
	r := mux.NewRouter()
	r.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "GET" {
			http.Error(w, "Method not allowed", 405)
			return
		}
		ws, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Println(err)
			return
		}
		c := &wsConn{send: make(chan []byte, 256), ws: ws}
		wspool.register <- c
		c.writeLoop()
	})
	r.PathPrefix("/").Handler(http.FileServer(http.Dir("./static/")))

	for i := range connectors {
		installConnector(r, connectors[i])
	}

	return r
}

func installConnector(router *mux.Router, connector *bfConnector) {
	fn := func(w http.ResponseWriter, r *http.Request) {
		resCh := make(chan interface{})

		fmt.Printf("sending request to %s\n", connector.name)

		resource := r.URL.Path

		w.Header().Add("Content-Type", "application/json")
		connector.reqCh <- httpRequest{
			resource,
			resCh,
		}

		select {
		case res := <-resCh:
			err := dumpJson(w, res)
			if err != nil {
				fmt.Println(err)
				break
			}
		}
	}

	router.HandleFunc("/"+connector.name, fn)
	router.PathPrefix("/" + connector.name + "/").HandlerFunc(fn)
}

// dumpJson dumps the JSON marshalling of res into w.
func dumpJson(w io.Writer, res interface{}) (err error) {
	j, err := json.Marshal(res)

	if err == nil {
		_, err = w.Write(j)
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/metal3d/idok/asserver"
	"github.com/metal3d/idok/tunnel"
	"github.com/metal3d/idok/utils"
	"log"
	"os"
	"path/filepath"
	"runtime"
)

const (
	VERSION = "v1-alpha1"
)

func main() {

	// flags
	xbmcaddr := flag.String("target", "", "xbmc/kodi ip (raspbmc address, ip or hostname)")
	username := flag.String("login", "", "jsonrpc login (configured in xbmc settings)")
	password := flag.String("password", "", "jsonrpc password (configured in xbmc settings)")
	viassh := flag.Bool("ssh", false, "Use SSH Tunnelling (need ssh user and password)")
	port := flag.Int("port", 8080, "local port (ignored if you use ssh option)")
	sshuser := flag.String("sshuser", "pi", "ssh login")
	sshpassword := flag.String("sshpass", "", "ssh password")
	sshport := flag.Int("sshport", 22, "target ssh port")
	version := flag.Bool("version", false, fmt.Sprintf("Print the current version (%s)", VERSION))
	xbmcport := flag.Int("targetport", 80, "XBMC/Kodi jsonrpc port")
	stdin := flag.Bool("stdin", false, "Read file from stdin")
	confexample := flag.Bool("conf-example", false, "Write a configuration example")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "\n\t%s [options] mediafile|youtubeurl\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "Opening  URL dosen't open local or remote port. Your media center will fetch data itself.\n\n")
		fmt.Fprintf(os.Stderr, "You may be able to stream stdout -> stdin:")
		fmt.Fprintf(os.Stderr, "\n\t%s [options] -stdin < file\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "Or:\n\tcommand | %s [options] -stdin \n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "Using ssh option is only managed for local files.\n")
		fmt.Fprintf(os.Stderr, "Default mode is HTTP mode, it opens :8080 port on your host and send message to Kodi to open that port.\n")
		fmt.Fprintf(os.Stderr, "You can use SSH with -ssh option, %s will try to use key pair authtification, then use -sshpass to try login/password auth. With -ssh, you should change -sshuser if your Kodi user is not \"pi\" (default on raspbmc)\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "To be able to authenticate without password, use the command:\n\n\tssh-copy-id USER@KODI_HOST\n\nwhere USER is the Kodi user (pi) and KODI_HOST the ip or hostname of Kodi host.")
		fmt.Fprintf(os.Stderr, "\n\nOptions:\n")
		flag.PrintDefaults()
	}

	flag.Parse()

	// print the current version
	if *version {
		fmt.Println(VERSION)
		fmt.Println("Compiled for", runtime.GOOS, runtime.GOARCH)
		os.Exit(0)
	}

	if *confexample {
		utils.PrintExampleConfig()
		os.Exit(0)
	}

	conf := &utils.Config{
		Target:      *xbmcaddr,
		Targetport:  *xbmcport,
		Localport:   *port,
		User:        *username,
		Password:    *password,
		Sshuser:     *sshuser,
		Sshpassword: *sshpassword,
		Sshport:     *sshport,
		Ssh:         *viassh,
	}

	// check if conf file exists
	if filename, found := utils.CheckLocalConfigFiles(); found {
		utils.LoadLocalConfig(filename, conf)
	}

	if conf.Target == "" {
		fmt.Println("\033[33mYou must provide the xbmc server address\033[0m")
		flag.Usage()
		os.Exit(1)
	}

	utils.SetTarget(conf)

	var dir, file string

	// we don't use stdin, so we should check if scheme is file, youtube or other...
	if !*stdin {
		if len(flag.Args()) < 1 {
			fmt.Println("\033[33mYou must provide a file to serve\033[0m")
			flag.Usage()
			os.Exit(2)
		}

		if youtube, vid := utils.IsYoutubeURL(flag.Arg(0)); youtube {
			log.Println("Youtube video, using youtube addon from XBMC/Kodi")
			utils.PlayYoutube(vid)
			os.Exit(0)
		}

		if ok, local := utils.IsOtherScheme(flag.Arg(0)); ok {
			log.Println("\033[33mWarning, other scheme could be not supported by you Kodi/XBMC installation. If doesn't work, check addons and stream\033[0m")
			utils.SendBasicStream(flag.Arg(0), local)
			os.Exit(0)
		}

		// find the good path
		toserve := flag.Arg(0)
		dir = "."
		toserve, _ = filepath.Abs(toserve)
		file = filepath.Base(toserve)
		dir = filepath.Dir(toserve)

	}

	if conf.Ssh {
		config := tunnel.NewConfig(*sshuser, *sshpassword)
		// serve ssh tunnel !
		if !*stdin {
			tunnel.SshForward(config, file, dir)
		} else {
			tunnel.SshForwardStdin(config)
		}
	} else {
		// serve local port !
		if !*stdin {
			asserver.HttpServe(file, dir, *port)
		} else {
			asserver.HttpServeStdin(*port)
		}
	}
}
<commit_msg>Change usage form<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/metal3d/idok/asserver"
	"github.com/metal3d/idok/tunnel"
	"github.com/metal3d/idok/utils"
	"log"
	"os"
	"path/filepath"
	"runtime"
)

const (
	VERSION = "v1-alpha1"
)

func main() {

	// flags
	xbmcaddr := flag.String("target", "", "xbmc/kodi ip (raspbmc address, ip or hostname)")
	username := flag.String("login", "", "jsonrpc login (configured in xbmc settings)")
	password := flag.String("password", "", "jsonrpc password (configured in xbmc settings)")
	viassh := flag.Bool("ssh", false, "Use SSH Tunnelling (need ssh user and password)")
	port := flag.Int("port", 8080, "local port (ignored if you use ssh option)")
	sshuser := flag.String("sshuser", "pi", "ssh login")
	sshpassword := flag.String("sshpass", "", "ssh password")
	sshport := flag.Int("sshport", 22, "target ssh port")
	version := flag.Bool("version", false, fmt.Sprintf("Print the current version (%s)", VERSION))
	xbmcport := flag.Int("targetport", 80, "XBMC/Kodi jsonrpc port")
	stdin := flag.Bool("stdin", false, "Read file from stdin")
	confexample := flag.Bool("conf-example", false, "Write a configuration example")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: ")
		fmt.Fprintf(os.Stderr, "%s [options] mediafile|youtubeurl|streamurl\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "Opening  URL dosen't open local or remote port. Your media center will fetch data itself.\n\n")
		fmt.Fprintf(os.Stderr, "You may be able to stream stdout -> stdin:")
		fmt.Fprintf(os.Stderr, "\n\t%s [options] -stdin < file\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "Or:\n\tcommand | %s [options] -stdin \n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "Using ssh option is only managed for local files.\n")
		fmt.Fprintf(os.Stderr, "Default mode is HTTP mode, it opens :8080 port on your host and send message to Kodi to open that port.\n")
		fmt.Fprintf(os.Stderr, "You can use SSH with -ssh option, %s will try to use key pair authtification, then use -sshpass to try login/password auth. With -ssh, you should change -sshuser if your Kodi user is not \"pi\" (default on raspbmc)\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "To be able to authenticate without password, use the command:\n\n\tssh-copy-id USER@KODI_HOST\n\nwhere USER is the Kodi user (pi) and KODI_HOST the ip or hostname of Kodi host.")
		fmt.Fprintf(os.Stderr, "\n\nOptions:\n")
		flag.PrintDefaults()
	}

	flag.Parse()

	// print the current version
	if *version {
		fmt.Println(VERSION)
		fmt.Println("Compiled for", runtime.GOOS, runtime.GOARCH)
		os.Exit(0)
	}

	if *confexample {
		utils.PrintExampleConfig()
		os.Exit(0)
	}

	conf := &utils.Config{
		Target:      *xbmcaddr,
		Targetport:  *xbmcport,
		Localport:   *port,
		User:        *username,
		Password:    *password,
		Sshuser:     *sshuser,
		Sshpassword: *sshpassword,
		Sshport:     *sshport,
		Ssh:         *viassh,
	}

	// check if conf file exists
	if filename, found := utils.CheckLocalConfigFiles(); found {
		utils.LoadLocalConfig(filename, conf)
	}

	if conf.Target == "" {
		fmt.Println("\033[33mYou must provide the xbmc server address\033[0m")
		flag.Usage()
		os.Exit(1)
	}

	utils.SetTarget(conf)

	var dir, file string

	// we don't use stdin, so we should check if scheme is file, youtube or other...
	if !*stdin {
		if len(flag.Args()) < 1 {
			fmt.Println("\033[33mYou must provide a file to serve\033[0m")
			flag.Usage()
			os.Exit(2)
		}

		if youtube, vid := utils.IsYoutubeURL(flag.Arg(0)); youtube {
			log.Println("Youtube video, using youtube addon from XBMC/Kodi")
			utils.PlayYoutube(vid)
			os.Exit(0)
		}

		if ok, local := utils.IsOtherScheme(flag.Arg(0)); ok {
			log.Println("\033[33mWarning, other scheme could be not supported by you Kodi/XBMC installation. If doesn't work, check addons and stream\033[0m")
			utils.SendBasicStream(flag.Arg(0), local)
			os.Exit(0)
		}

		// find the good path
		toserve := flag.Arg(0)
		dir = "."
		toserve, _ = filepath.Abs(toserve)
		file = filepath.Base(toserve)
		dir = filepath.Dir(toserve)

	}

	if conf.Ssh {
		config := tunnel.NewConfig(*sshuser, *sshpassword)
		// serve ssh tunnel !
		if !*stdin {
			tunnel.SshForward(config, file, dir)
		} else {
			tunnel.SshForwardStdin(config)
		}
	} else {
		// serve local port !
		if !*stdin {
			asserver.HttpServe(file, dir, *port)
		} else {
			asserver.HttpServeStdin(*port)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"code.google.com/p/go.crypto/ssh"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"
)

var PORT string
var HOST string
var LISTEN string
var RASPIP string
var SSHPORT int

// message to send to stop media
const stopbody = `{"id":1,"jsonrpc":"2.0","method":"Player.Stop","params":{"playerid": %d}}`

// get player id
const getplayer = `{"id":1, "jsonrpc":"2.0","method":"Player.GetActivePlayers"}`

// the message to lauch local media
const body = `{
	"id":1,"jsonrpc":"2.0",
	"method":"Player.Open",
	"params": {
		"item": {
		   "file": "%s"
		 }
	 }
 }`

// response of get players
type itemresp struct {
	Id      int
	Jsonrpc string
	Result  []map[string]interface{}
}

// return active player from XBMC
func getActivePlayer() *itemresp {
	r, _ := http.Post(HOST, "application/json", bytes.NewBufferString(getplayer))
	response, _ := ioutil.ReadAll(r.Body)
	resp := &itemresp{}
	resp.Result = make([]map[string]interface{}, 0)
	json.Unmarshal(response, resp)
	return resp
}

// test if media is playing, if not then quit
func checkPlaying() {

	tick := time.Tick(3 * time.Second)
	for _ = range tick {
		resp := getActivePlayer()
		if len(resp.Result) == 0 {
			os.Exit(0)
		}
	}

}

// when quiting (CTRL+C for example) - tell to XBMC to stop
func onQuit() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)

	select {
	case <-c:
		fmt.Println("Quiting")
		resp := getActivePlayer()
		var playerid int
		for _, result := range resp.Result {
			for key, val := range result {
				if key == "playerid" {
					playerid = int(val.(float64))
				}
			}
		}

		http.Post(HOST, "application/json", bytes.NewBufferString(fmt.Sprintf(stopbody, playerid)))
		os.Exit(0)
	}
}

// begin to locally listen http to serve media
func send(host, file string, port int) {

	u := url.URL{Path: file}
	file = u.String()
	//_body := fmt.Sprintf(body, "http://"+LISTEN+":"+PORT+"/"+file)
	addr := fmt.Sprintf("http://%s:%d/%s", host, port, file)
	_body := fmt.Sprintf(body, addr)

	r, err := http.Post(HOST, "application/json", bytes.NewBufferString(_body))
	if err != nil {
		log.Fatal(err)
	}
	response, _ := ioutil.ReadAll(r.Body)
	log.Println(string(response))
}

// return local ip that matches kodi network
// ignoring loopback and other net interfaces
func getLocalInterfaceIP() (string, error) {
	ips, _ := net.LookupIP(RASPIP)
	ifaces, err := net.Interfaces()
	if err != nil {
		log.Fatalf("Error while checking you interfaces: %v", err)
	}
	for _, ip := range ips {
		mask := ip.DefaultMask()
		for _, iface := range ifaces {
			if iface.Flags&net.FlagLoopback != 0 {
				continue
			}

			addrs, _ := iface.Addrs()
			for _, addr := range addrs {
				switch v := addr.(type) {
				case *net.IPNet:
					if v.Mask.String() == mask.String() {
						return v.IP.String(), nil
					}
				}

			}
		}
	}
	return "", errors.New("Unable to get local ip")
}

// open a port locally and tell to kodi to stream
// from this port
func httpserve(file, dir string, port int) {

	localip, err := getLocalInterfaceIP()
	log.Println(localip)
	if err != nil {
		log.Fatal(err)
	}

	// handle file http response
	fullpath := filepath.Join(dir, file)
	http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, fullpath)
	}))

	// send xbmc the file query
	go send(localip, file, port)

	// handle CTRL+C to stop
	go onQuit()

	// and wait media end
	go checkPlaying()

	http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), nil)
}

// Dig tunnel to kodi, open a port and bind socket to
// the local http server
func sshforward(config *ssh.ClientConfig, file, dir string) {

	// Setup sshClientConn (type *ssh.ClientConn)
	sshClientConn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", RASPIP, SSHPORT), config)
	if err != nil {
		log.Fatal(err)
	}

	// Setup sshConn (type net.Conn)
	sshConn, err := sshClientConn.Listen("tcp", "127.0.0.1:0")
	addr := sshConn.Addr()
	port := addr.(*net.TCPAddr).Port
	log.Println("Listening port on raspberry: ", port)

	// send xbmc the file query
	go send("localhost", file, port)
	// handle CTRL+C to stop
	go onQuit()
	// and wait media end
	go checkPlaying()

	// now serve file
	fullpath := filepath.Join(dir, file)
	http.Serve(sshConn, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, fullpath)
	}))
}

func main() {

	// flags
	xbmcaddr := flag.String("target", "", "xbmc/kodi ip (raspbmc address, ip or hostname)")
	username := flag.String("login", "", "jsonrpc login (configured in xbmc settings)")
	password := flag.String("password", "", "jsonrpc password (configured in xbmc settings)")
	viassh := flag.Bool("ssh", false, "Use SSH Tunnelling (need ssh user and password)")
	port := flag.Int("port", 8080, "local port (ignored if you use ssh option)")
	sshuser := flag.String("sshuser", "pi", "ssh login")
	sshpassword := flag.String("sshpass", "raspberry", "ssh password")
	sshport := flag.Int("sshport", 22, "target ssh port")

	flag.Parse()

	if *xbmcaddr == "" {
		fmt.Println("You must provide the xbmc server address")
		os.Exit(1)
	}

	HOST = *xbmcaddr
	RASPIP = *xbmcaddr
	SSHPORT = *sshport

	// XBMC can be configured to have username/password
	if *username != "" {
		HOST = *username + ":" + *password + "@" + HOST
	}
	HOST = "http://" + HOST + "/jsonrpc"

	if len(flag.Args()) < 1 {
		fmt.Println("You must provide a file to serve")
		os.Exit(2)
	}

	// find the good path
	toserve := flag.Arg(0)
	dir := "."
	toserve, _ = filepath.Abs(toserve)
	file := filepath.Base(toserve)
	dir = filepath.Dir(toserve)

	if *viassh {
		config := &ssh.ClientConfig{
			User: *sshuser,
			Auth: []ssh.AuthMethod{
				ssh.Password(*sshpassword),
			},
		}

		// serve !
		sshforward(config, file, dir)
	} else {
		httpserve(file, dir, *port)
	}
}
<commit_msg>Set randomisation port ourself<commit_after>package main

import (
	"bytes"
	"code.google.com/p/go.crypto/ssh"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"
)

var PORT string
var HOST string
var LISTEN string
var RASPIP string
var SSHPORT int

// message to send to stop media
const stopbody = `{"id":1,"jsonrpc":"2.0","method":"Player.Stop","params":{"playerid": %d}}`

// get player id
const getplayer = `{"id":1, "jsonrpc":"2.0","method":"Player.GetActivePlayers"}`

// the message to lauch local media
const body = `{
	"id":1,"jsonrpc":"2.0",
	"method":"Player.Open",
	"params": {
		"item": {
		   "file": "%s"
		 }
	 }
 }`

// response of get players
type itemresp struct {
	Id      int
	Jsonrpc string
	Result  []map[string]interface{}
}

// return active player from XBMC
func getActivePlayer() *itemresp {
	r, _ := http.Post(HOST, "application/json", bytes.NewBufferString(getplayer))
	response, _ := ioutil.ReadAll(r.Body)
	resp := &itemresp{}
	resp.Result = make([]map[string]interface{}, 0)
	json.Unmarshal(response, resp)
	return resp
}

// test if media is playing, if not then quit
func checkPlaying() {

	tick := time.Tick(3 * time.Second)
	for _ = range tick {
		resp := getActivePlayer()
		if len(resp.Result) == 0 {
			os.Exit(0)
		}
	}

}

// when quiting (CTRL+C for example) - tell to XBMC to stop
func onQuit() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)

	select {
	case <-c:
		fmt.Println("Quiting")
		resp := getActivePlayer()
		var playerid int
		for _, result := range resp.Result {
			for key, val := range result {
				if key == "playerid" {
					playerid = int(val.(float64))
				}
			}
		}

		http.Post(HOST, "application/json", bytes.NewBufferString(fmt.Sprintf(stopbody, playerid)))
		os.Exit(0)
	}
}

// begin to locally listen http to serve media
func send(host, file string, port int) {

	u := url.URL{Path: file}
	file = u.String()
	//_body := fmt.Sprintf(body, "http://"+LISTEN+":"+PORT+"/"+file)
	addr := fmt.Sprintf("http://%s:%d/%s", host, port, file)
	_body := fmt.Sprintf(body, addr)

	r, err := http.Post(HOST, "application/json", bytes.NewBufferString(_body))
	if err != nil {
		log.Fatal(err)
	}
	response, _ := ioutil.ReadAll(r.Body)
	log.Println(string(response))
}

// return local ip that matches kodi network
// ignoring loopback and other net interfaces
func getLocalInterfaceIP() (string, error) {
	ips, _ := net.LookupIP(RASPIP)
	ifaces, err := net.Interfaces()
	if err != nil {
		log.Fatalf("Error while checking you interfaces: %v", err)
	}
	for _, ip := range ips {
		mask := ip.DefaultMask()
		for _, iface := range ifaces {
			if iface.Flags&net.FlagLoopback != 0 {
				continue
			}

			addrs, _ := iface.Addrs()
			for _, addr := range addrs {
				switch v := addr.(type) {
				case *net.IPNet:
					if v.Mask.String() == mask.String() {
						return v.IP.String(), nil
					}
				}

			}
		}
	}
	return "", errors.New("Unable to get local ip")
}

// open a port locally and tell to kodi to stream
// from this port
func httpserve(file, dir string, port int) {

	localip, err := getLocalInterfaceIP()
	log.Println(localip)
	if err != nil {
		log.Fatal(err)
	}

	// handle file http response
	fullpath := filepath.Join(dir, file)
	http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, fullpath)
	}))

	// send xbmc the file query
	go send(localip, file, port)

	// handle CTRL+C to stop
	go onQuit()

	// and wait media end
	go checkPlaying()

	http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), nil)
}

// Dig tunnel to kodi, open a port and bind socket to
// the local http server
func sshforward(config *ssh.ClientConfig, file, dir string) {

	// Setup sshClientConn (type *ssh.ClientConn)
	sshClientConn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", RASPIP, SSHPORT), config)
	if err != nil {
		log.Fatal(err)
	}

	// Setup sshConn (type net.Conn)
	// Because dropbear doesn't accept :0 port to open random port
	// we do the randomisation ourself
	rand.Seed(int64(time.Now().Nanosecond()))
	port := 10000 + rand.Intn(9999)
	tries := 0
	sshConn, err := sshClientConn.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
	for err != nil && tries < 500 {
		port = 10000 + rand.Intn(9999)
		sshConn, err = sshClientConn.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
		tries++
	}
	log.Println("Listening port on raspberry: ", port)

	// send xbmc the file query
	go send("localhost", file, port)
	// handle CTRL+C to stop
	go onQuit()
	// and wait media end
	go checkPlaying()

	// now serve file
	fullpath := filepath.Join(dir, file)
	http.Serve(sshConn, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, fullpath)
	}))
}

func main() {

	// flags
	xbmcaddr := flag.String("target", "", "xbmc/kodi ip (raspbmc address, ip or hostname)")
	username := flag.String("login", "", "jsonrpc login (configured in xbmc settings)")
	password := flag.String("password", "", "jsonrpc password (configured in xbmc settings)")
	viassh := flag.Bool("ssh", false, "Use SSH Tunnelling (need ssh user and password)")
	port := flag.Int("port", 8080, "local port (ignored if you use ssh option)")
	sshuser := flag.String("sshuser", "pi", "ssh login")
	sshpassword := flag.String("sshpass", "raspberry", "ssh password")
	sshport := flag.Int("sshport", 22, "target ssh port")

	flag.Parse()

	if *xbmcaddr == "" {
		fmt.Println("You must provide the xbmc server address")
		os.Exit(1)
	}

	HOST = *xbmcaddr
	RASPIP = *xbmcaddr
	SSHPORT = *sshport

	// XBMC can be configured to have username/password
	if *username != "" {
		HOST = *username + ":" + *password + "@" + HOST
	}
	HOST = "http://" + HOST + "/jsonrpc"

	if len(flag.Args()) < 1 {
		fmt.Println("You must provide a file to serve")
		os.Exit(2)
	}

	// find the good path
	toserve := flag.Arg(0)
	dir := "."
	toserve, _ = filepath.Abs(toserve)
	file := filepath.Base(toserve)
	dir = filepath.Dir(toserve)

	if *viassh {
		config := &ssh.ClientConfig{
			User: *sshuser,
			Auth: []ssh.AuthMethod{
				ssh.Password(*sshpassword),
			},
		}

		// serve !
		sshforward(config, file, dir)
	} else {
		httpserve(file, dir, *port)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"./asserver"
	"./tunnel"
	"./utils"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
)

const (
	VERSION = "20140910-1"
)

func main() {

	// flags
	xbmcaddr := flag.String("target", "", "xbmc/kodi ip (raspbmc address, ip or hostname)")
	username := flag.String("login", "", "jsonrpc login (configured in xbmc settings)")
	password := flag.String("password", "", "jsonrpc password (configured in xbmc settings)")
	viassh := flag.Bool("ssh", false, "Use SSH Tunnelling (need ssh user and password)")
	port := flag.Int("port", 8080, "local port (ignored if you use ssh option)")
	sshuser := flag.String("sshuser", "pi", "ssh login")
	sshpassword := flag.String("sshpass", "", "ssh password")
	sshport := flag.Int("sshport", 22, "target ssh port")
	version := flag.Bool("version", false, fmt.Sprintf("Print the current version (%s)", VERSION))
	xbmcport := flag.Int("targetport", 80, "XBMC/Kodi jsonrpc port")
	stdin := flag.Bool("stdin", false, "Read file from stdin")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "\n%s [options] mediafile|youtubeurl\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "Opening youtube urls dosen't open local or remote port.\n")
		fmt.Fprintf(os.Stderr, "Using ssh option is only managed for local files.\n")
		fmt.Fprintf(os.Stderr, "Default mode is HTTP mode, it opens :8080 port on your host and send message to Kodi to open that port.\n")
		fmt.Fprintf(os.Stderr, "You can use SSH with -ssh option, %s will try to use key pair authtification, then use -sshpass to try login/password auth. With -ssh, you should change -sshuser if your Kodi user is not \"pi\" (default on raspbmc)\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "To be able to authenticate without password, use the command:\n\n\tssh-copy-id USER@KODI_HOST\n\nwhere USER is the Kodi user (pi) and KODI_HOST the ip or hostname of Kodi host.")
		fmt.Fprintf(os.Stderr, "\n\nOptions:\n")
		flag.PrintDefaults()
	}

	flag.Parse()

	// print the current version
	if *version {
		fmt.Println(VERSION)
		fmt.Println("Compiled for", runtime.GOOS, runtime.GOARCH)
		os.Exit(0)
	}

	if *xbmcaddr == "" {
		fmt.Println("\033[33mYou must provide the xbmc server address\033[0m")
		flag.Usage()
		os.Exit(1)
	}

	utils.SSHPORT = *sshport
	utils.SetTarget(*xbmcaddr, *xbmcport, username, password)

	var dir, file string

	// we don't use stdin, so we should check if scheme is file, youtube or other...
	if !*stdin {
		if len(flag.Args()) < 1 {
			fmt.Println("\033[33mYou must provide a file to serve\033[0m")
			flag.Usage()
			os.Exit(2)
		}

		if youtube, vid := utils.IsYoutubeURL(flag.Arg(0)); youtube {
			log.Println("Youtube video, using youtube addon from XBMC/Kodi")
			utils.PlayYoutube(vid)
			os.Exit(0)
		}

		if ok, local := utils.IsOtherScheme(flag.Arg(0)); ok {
			log.Println("\033[33mWarning, other scheme could be not supported by you Kodi/XBMC installation. If doesn't work, check addons and stream\033[0m")
			utils.SendBasicStream(flag.Arg(0), local)
			os.Exit(0)
		}

		// find the good path
		toserve := flag.Arg(0)
		dir = "."
		toserve, _ = filepath.Abs(toserve)
		file = filepath.Base(toserve)
		dir = filepath.Dir(toserve)

	}

	if *viassh {
		config := tunnel.NewConfig(*sshuser, *sshpassword)
		// serve ssh tunnel !
		if !*stdin {
			tunnel.SshForward(config, file, dir)
		} else {
			tunnel.SshForwardStdin(config)
		}
	} else {
		// serve local port !
		if !*stdin {
			asserver.HttpServe(file, dir, *port)
		} else {
			asserver.HttpServeStdin(*port)
		}
	}
}
<commit_msg>Changed version<commit_after>package main

import (
	"./asserver"
	"./tunnel"
	"./utils"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
)

const (
	VERSION = "20140914-1"
)

func main() {

	// flags
	xbmcaddr := flag.String("target", "", "xbmc/kodi ip (raspbmc address, ip or hostname)")
	username := flag.String("login", "", "jsonrpc login (configured in xbmc settings)")
	password := flag.String("password", "", "jsonrpc password (configured in xbmc settings)")
	viassh := flag.Bool("ssh", false, "Use SSH Tunnelling (need ssh user and password)")
	port := flag.Int("port", 8080, "local port (ignored if you use ssh option)")
	sshuser := flag.String("sshuser", "pi", "ssh login")
	sshpassword := flag.String("sshpass", "", "ssh password")
	sshport := flag.Int("sshport", 22, "target ssh port")
	version := flag.Bool("version", false, fmt.Sprintf("Print the current version (%s)", VERSION))
	xbmcport := flag.Int("targetport", 80, "XBMC/Kodi jsonrpc port")
	stdin := flag.Bool("stdin", false, "Read file from stdin")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "\n%s [options] mediafile|youtubeurl\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "Opening youtube urls dosen't open local or remote port.\n")
		fmt.Fprintf(os.Stderr, "Using ssh option is only managed for local files.\n")
		fmt.Fprintf(os.Stderr, "Default mode is HTTP mode, it opens :8080 port on your host and send message to Kodi to open that port.\n")
		fmt.Fprintf(os.Stderr, "You can use SSH with -ssh option, %s will try to use key pair authtification, then use -sshpass to try login/password auth. With -ssh, you should change -sshuser if your Kodi user is not \"pi\" (default on raspbmc)\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "To be able to authenticate without password, use the command:\n\n\tssh-copy-id USER@KODI_HOST\n\nwhere USER is the Kodi user (pi) and KODI_HOST the ip or hostname of Kodi host.")
		fmt.Fprintf(os.Stderr, "\n\nOptions:\n")
		flag.PrintDefaults()
	}

	flag.Parse()

	// print the current version
	if *version {
		fmt.Println(VERSION)
		fmt.Println("Compiled for", runtime.GOOS, runtime.GOARCH)
		os.Exit(0)
	}

	if *xbmcaddr == "" {
		fmt.Println("\033[33mYou must provide the xbmc server address\033[0m")
		flag.Usage()
		os.Exit(1)
	}

	utils.SSHPORT = *sshport
	utils.SetTarget(*xbmcaddr, *xbmcport, username, password)

	var dir, file string

	// we don't use stdin, so we should check if scheme is file, youtube or other...
	if !*stdin {
		if len(flag.Args()) < 1 {
			fmt.Println("\033[33mYou must provide a file to serve\033[0m")
			flag.Usage()
			os.Exit(2)
		}

		if youtube, vid := utils.IsYoutubeURL(flag.Arg(0)); youtube {
			log.Println("Youtube video, using youtube addon from XBMC/Kodi")
			utils.PlayYoutube(vid)
			os.Exit(0)
		}

		if ok, local := utils.IsOtherScheme(flag.Arg(0)); ok {
			log.Println("\033[33mWarning, other scheme could be not supported by you Kodi/XBMC installation. If doesn't work, check addons and stream\033[0m")
			utils.SendBasicStream(flag.Arg(0), local)
			os.Exit(0)
		}

		// find the good path
		toserve := flag.Arg(0)
		dir = "."
		toserve, _ = filepath.Abs(toserve)
		file = filepath.Base(toserve)
		dir = filepath.Dir(toserve)

	}

	if *viassh {
		config := tunnel.NewConfig(*sshuser, *sshpassword)
		// serve ssh tunnel !
		if !*stdin {
			tunnel.SshForward(config, file, dir)
		} else {
			tunnel.SshForwardStdin(config)
		}
	} else {
		// serve local port !
		if !*stdin {
			asserver.HttpServe(file, dir, *port)
		} else {
			asserver.HttpServeStdin(*port)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/gin-gonic/gin"
)

func infoHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"http":  "http://localhost" + port,
		"https": "https://localhost" + port,
		"ws":    "ws://localhost" + port,
		"wss":   "wss://localhost" + portSSL,
	})
}
<commit_msg>Give the correct port for upload<commit_after>package main

import (
	"github.com/gin-gonic/gin"
)

func infoHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"http":  "http://localhost" + port,
		"https": "https://localhost" + portSSL,
		"ws":    "ws://localhost" + port,
		"wss":   "wss://localhost" + portSSL,
	})
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/urfave/cli"
)

var cmdInit = cli.Command{
	Name:        "init",
	Usage:       "Initializes the shell for use of bronze",
	Description: `Put 'eval "$(bronze init)"' in your ~/.*shrc`,
	Action: func(ctx *cli.Context) error {
		switch shell {
		case "fish":
			// TODO
		case "zsh":
			fmt.Println(`unsetopt prompt_subst; precmd() { PROMPT="$(bronze print "${BRONZE[@]}") "; }`)
		case "bash":
			fmt.Println(`PS1='$(bronze print "${BRONZE[@]}") '`)
		default:
			fmt.Print(`echo "bronze: Unrecognized shell."`)
		}
		return nil
	},
}
<commit_msg>added support for fish<commit_after>package main

import (
	"fmt"

	"github.com/urfave/cli"
)

var cmdInit = cli.Command{
	Name:        "init",
	Usage:       "Initializes the shell for use of bronze",
	Description: `Put 'eval "$(bronze init)"' in your ~/.*shrc`,
	Action: func(ctx *cli.Context) error {
		switch shell {
		case "fish":
			fmt.Println(`function fish_prompt; bronze print $BRONZE; echo -n ' '; end`)
		case "zsh":
			fmt.Println(`unsetopt prompt_subst; precmd() { PROMPT="$(bronze print "${BRONZE[@]}") "; }`)
		case "bash":
			fmt.Println(`PS1='$(bronze print "${BRONZE[@]}") '`)
		default:
			fmt.Print(`echo "bronze: Unrecognized shell."`)
		}
		return nil
	},
}
<|endoftext|>


<commit_before>//: ----------------------------------------------------------------------------
//: Copyright (C) 2017 Verizon.  All Rights Reserved.
//: All Rights Reserved
//:
//: file:    netflow_v9.go
//: details: netflow decoders handler
//: author:  Mehrdad Arshad Rad
//: date:    04/21/2017
//:
//: Licensed under the Apache License, Version 2.0 (the "License");
//: you may not use this file except in compliance with the License.
//: You may obtain a copy of the License at
//:
//:     http://www.apache.org/licenses/LICENSE-2.0
//:
//: Unless required by applicable law or agreed to in writing, software
//: distributed under the License is distributed on an "AS IS" BASIS,
//: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//: See the License for the specific language governing permissions and
//: limitations under the License.
//: ----------------------------------------------------------------------------
package main

import (
	"net"
)

// NetflowV9 represents netflow v9 collector
type NetflowV9 struct {
	port    int
	addr    string
	workers int
	stop    bool
	stats   NetflowV9Stats
	pool    chan chan struct{}
}

// NetflowV9UDPMsg represents netflow v9 UDP data
type NetflowV9UDPMsg struct {
	raddr *net.UDPAddr
	body  []byte
}

// NetflowV9Stats represents netflow v9 stats
type NetflowV9Stats struct {
	UDPQueue     int
	MessageQueue int
	UDPCount     uint64
	DecodedCount uint64
	MQErrorCount uint64
	Workers      int32
}

// NewIPFIX constructs IPFIX
func NewNetflowV9() *NetflowV9 {
	return &NetflowV9{
		port:    opts.IPFIXPort,
		workers: opts.IPFIXWorkers,
		pool:    make(chan chan struct{}, maxWorkers),
	}
}

func (i *NetflowV9) run() {
	//TODO
}

func (i *NetflowV9) shutdown() {
	//TODO
}

func (i *NetflowV9) netflowWorker(wQuit chan struct{}) {
	// TODO
}

func (i *NetflowV9) status() *NetflowV9Stats {
	//TODO
	return &NetflowV9Stats{}
}

func (i *NetflowV9) dynWorkers() {
	//TODO
}
<commit_msg>fix comment issue<commit_after>//: ----------------------------------------------------------------------------
//: Copyright (C) 2017 Verizon.  All Rights Reserved.
//: All Rights Reserved
//:
//: file:    netflow_v9.go
//: details: netflow decoders handler
//: author:  Mehrdad Arshad Rad
//: date:    04/21/2017
//:
//: Licensed under the Apache License, Version 2.0 (the "License");
//: you may not use this file except in compliance with the License.
//: You may obtain a copy of the License at
//:
//:     http://www.apache.org/licenses/LICENSE-2.0
//:
//: Unless required by applicable law or agreed to in writing, software
//: distributed under the License is distributed on an "AS IS" BASIS,
//: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//: See the License for the specific language governing permissions and
//: limitations under the License.
//: ----------------------------------------------------------------------------
package main

import (
	"net"
)

// NetflowV9 represents netflow v9 collector
type NetflowV9 struct {
	port    int
	addr    string
	workers int
	stop    bool
	stats   NetflowV9Stats
	pool    chan chan struct{}
}

// NetflowV9UDPMsg represents netflow v9 UDP data
type NetflowV9UDPMsg struct {
	raddr *net.UDPAddr
	body  []byte
}

// NetflowV9Stats represents netflow v9 stats
type NetflowV9Stats struct {
	UDPQueue     int
	MessageQueue int
	UDPCount     uint64
	DecodedCount uint64
	MQErrorCount uint64
	Workers      int32
}

// NewNetflowV9 constructs NetflowV9
func NewNetflowV9() *NetflowV9 {
	return &NetflowV9{
		port:    opts.IPFIXPort,
		workers: opts.IPFIXWorkers,
		pool:    make(chan chan struct{}, maxWorkers),
	}
}

func (i *NetflowV9) run() {
	//TODO
}

func (i *NetflowV9) shutdown() {
	//TODO
}

func (i *NetflowV9) netflowWorker(wQuit chan struct{}) {
	// TODO
}

func (i *NetflowV9) status() *NetflowV9Stats {
	//TODO
	return &NetflowV9Stats{}
}

func (i *NetflowV9) dynWorkers() {
	//TODO
}
<|endoftext|>


<commit_before>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

// The veceps implemens the vg.Canvas interface using
// encapsulated postscript.
package veceps

import (
	"bufio"
	"bytes"
	"code.google.com/p/plotinum/vg"
	"fmt"
	"image/color"
	"math"
	"os"
	"time"
)

type Canvas struct {
	stk []ctx
	buf *bytes.Buffer
}

type ctx struct {
	color  color.Color
	width  vg.Length
	dashes []vg.Length
	offs   vg.Length
	font   string
	fsize  vg.Length
}

// pr is the amount of precision to use when outputting float64s.
const pr = 5

// New returns a new Canvas.
func New(w, h vg.Length, title string) *Canvas {
	c := &Canvas{stk: []ctx{ctx{}}, buf: new(bytes.Buffer)}
	c.buf.WriteString("%%!PS-Adobe-3.0 EPSF-3.0\n")
	c.buf.WriteString("%%Creator code.google.com/p/plotinum/vg/veceps\n")
	c.buf.WriteString("%%Title: " + title + "\n")
	c.buf.WriteString(fmt.Sprintf("%%%%BoundingBox 0 0 %.*g %.*g\n",
		pr, w.Dots(c),
		pr, h.Dots(c)))
	c.buf.WriteString(fmt.Sprintf("%%%%CreationDate: %s\n", time.Now()))
	c.buf.WriteString("%%Orientation: Portrait\n")
	c.buf.WriteString("%%EndComments\n")
	c.buf.WriteString("\n")
	vg.Initialize(c)
	return c
}

// cur returns the top context on the stack.
func (e *Canvas) cur() *ctx {
	return &e.stk[len(e.stk)-1]
}

func (e *Canvas) SetLineWidth(w vg.Length) {
	if e.cur().width != w {
		e.cur().width = w
		fmt.Fprintf(e.buf, "%.*g setlinewidth\n", pr, w.Dots(e))
	}
}

func (e *Canvas) SetLineDash(dashes []vg.Length, o vg.Length) {
	dashEq := true
	curDash := e.cur().dashes
	for i, d := range dashes {
		if d != curDash[i] {
			dashEq = false
			break
		}
	}
	if !dashEq || e.cur().offs != o {
		e.cur().dashes = dashes
		e.cur().offs = o
		e.buf.WriteString("[")
		for _, d := range dashes {
			fmt.Fprintf(e.buf, " %.*g", pr, d.Dots(e))
		}
		e.buf.WriteString(" ] ")
		fmt.Fprintf(e.buf, "%.*g setdash\n", pr, o.Dots(e))
	}
}

func (e *Canvas) SetColor(c color.Color) {
	if c == nil {
		c = color.Black
	}
	if e.cur().color != c {
		e.cur().color = c
		r, g, b, _ := c.RGBA()
		mx := float64(math.MaxUint16)
		fmt.Fprintf(e.buf, "%.*g %.*g %.*g setrgbcolor\n", pr, float64(r)/mx,
			pr, float64(g)/mx, pr, float64(b)/mx)
	}
}

func (e *Canvas) Rotate(r float64) {
	fmt.Fprintf(e.buf, "%.*g rotate\n", pr, r*180/math.Pi)
}

func (e *Canvas) Translate(x, y vg.Length) {
	fmt.Fprintf(e.buf, "%.*g %.*g translate\n",
		pr, x.Dots(e), pr, y.Dots(e))
}

func (e *Canvas) Scale(x, y float64) {
	fmt.Fprintf(e.buf, "%.*g %.*g scale\n", pr, x, pr, y)
}

func (e *Canvas) Push() {
	e.stk = append(e.stk, *e.cur())
	e.buf.WriteString("gsave\n")
}

func (e *Canvas) Pop() {
	e.stk = e.stk[:len(e.stk)-1]
	e.buf.WriteString("grestore\n")
}

func (e *Canvas) Stroke(path vg.Path) {
	if e.cur().width == 0 {
		return
	}
	e.trace(path)
	e.buf.WriteString("stroke\n")
}

func (e *Canvas) Fill(path vg.Path) {
	e.trace(path)
	e.buf.WriteString("fill\n")
}

func (e *Canvas) trace(path vg.Path) {
	e.buf.WriteString("newpath\n")
	for _, comp := range path {
		switch comp.Type {
		case vg.MoveComp:
			fmt.Fprintf(e.buf, "%.*g %.*g moveto\n", pr, comp.X, pr, comp.Y)
		case vg.LineComp:
			fmt.Fprintf(e.buf, "%.*g %.*g lineto\n", pr, comp.X, pr, comp.Y)
		case vg.ArcComp:
			end := comp.Start + comp.Angle
			arcOp := "arc"
			if comp.Angle < 0 {
				arcOp = "arcn"
			}
			fmt.Fprintf(e.buf, "%.*g %.*g %.*g %.*g %.*g %s\n", pr, comp.X, pr, comp.Y,
				pr, comp.Radius, pr, comp.Start*180/math.Pi, pr,
				end*180/math.Pi, arcOp)
		case vg.CloseComp:
			e.buf.WriteString("closepath\n")
		default:
			panic(fmt.Sprintf("Unknown path component type: %d\n", comp.Type))
		}
	}
}

func (e *Canvas) FillString(fnt vg.Font, x, y vg.Length, str string) {
	if e.cur().font != fnt.Name() || e.cur().fsize != fnt.Size {
		e.cur().font = fnt.Name()
		e.cur().fsize = fnt.Size
		fmt.Fprintf(e.buf, "/%s findfont %.*g scalefont setfont\n",
			fnt.Name(), pr, fnt.Size)
	}
	fmt.Fprintf(e.buf, "%.*g %.*g moveto\n", pr, x.Dots(e), pr, y.Dots(e))
	fmt.Fprintf(e.buf, "(%s) show\n", str)
}

func (e *Canvas) DPI() float64 {
	return 72
}

// Save saves the plot to the given path.
func (e *Canvas) Save(path string) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	b := bufio.NewWriter(f)
	_, err = e.buf.WriteTo(b)
	if err != nil {
		return err
	}
	_, err = fmt.Fprintln(b, "showpage")
	if err != nil {
		return err
	}
	return b.Flush()
}
<commit_msg>Fix dashed lines in veceps.<commit_after>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

// The veceps implemens the vg.Canvas interface using
// encapsulated postscript.
package veceps

import (
	"bufio"
	"bytes"
	"code.google.com/p/plotinum/vg"
	"fmt"
	"image/color"
	"math"
	"os"
	"time"
)

type Canvas struct {
	stk []ctx
	buf *bytes.Buffer
}

type ctx struct {
	color  color.Color
	width  vg.Length
	dashes []vg.Length
	offs   vg.Length
	font   string
	fsize  vg.Length
}

// pr is the amount of precision to use when outputting float64s.
const pr = 5

// New returns a new Canvas.
func New(w, h vg.Length, title string) *Canvas {
	c := &Canvas{stk: []ctx{ctx{}}, buf: new(bytes.Buffer)}
	c.buf.WriteString("%%!PS-Adobe-3.0 EPSF-3.0\n")
	c.buf.WriteString("%%Creator code.google.com/p/plotinum/vg/veceps\n")
	c.buf.WriteString("%%Title: " + title + "\n")
	c.buf.WriteString(fmt.Sprintf("%%%%BoundingBox 0 0 %.*g %.*g\n",
		pr, w.Dots(c),
		pr, h.Dots(c)))
	c.buf.WriteString(fmt.Sprintf("%%%%CreationDate: %s\n", time.Now()))
	c.buf.WriteString("%%Orientation: Portrait\n")
	c.buf.WriteString("%%EndComments\n")
	c.buf.WriteString("\n")
	vg.Initialize(c)
	return c
}

// cur returns the top context on the stack.
func (e *Canvas) cur() *ctx {
	return &e.stk[len(e.stk)-1]
}

func (e *Canvas) SetLineWidth(w vg.Length) {
	if e.cur().width != w {
		e.cur().width = w
		fmt.Fprintf(e.buf, "%.*g setlinewidth\n", pr, w.Dots(e))
	}
}

func (e *Canvas) SetLineDash(dashes []vg.Length, o vg.Length) {
	cur := e.cur().dashes
	dashEq := len(dashes) == len(cur)
	for i := 0; dashEq && i < len(dashes); i++ {
		if dashes[i] != cur[i] {
			dashEq = false
		}
	}
	if !dashEq || e.cur().offs != o {
		e.cur().dashes = dashes
		e.cur().offs = o
		e.buf.WriteString("[")
		for _, d := range dashes {
			fmt.Fprintf(e.buf, " %.*g", pr, d.Dots(e))
		}
		e.buf.WriteString(" ] ")
		fmt.Fprintf(e.buf, "%.*g setdash\n", pr, o.Dots(e))
	}
}

func (e *Canvas) SetColor(c color.Color) {
	if c == nil {
		c = color.Black
	}
	if e.cur().color != c {
		e.cur().color = c
		r, g, b, _ := c.RGBA()
		mx := float64(math.MaxUint16)
		fmt.Fprintf(e.buf, "%.*g %.*g %.*g setrgbcolor\n", pr, float64(r)/mx,
			pr, float64(g)/mx, pr, float64(b)/mx)
	}
}

func (e *Canvas) Rotate(r float64) {
	fmt.Fprintf(e.buf, "%.*g rotate\n", pr, r*180/math.Pi)
}

func (e *Canvas) Translate(x, y vg.Length) {
	fmt.Fprintf(e.buf, "%.*g %.*g translate\n",
		pr, x.Dots(e), pr, y.Dots(e))
}

func (e *Canvas) Scale(x, y float64) {
	fmt.Fprintf(e.buf, "%.*g %.*g scale\n", pr, x, pr, y)
}

func (e *Canvas) Push() {
	e.stk = append(e.stk, *e.cur())
	e.buf.WriteString("gsave\n")
}

func (e *Canvas) Pop() {
	e.stk = e.stk[:len(e.stk)-1]
	e.buf.WriteString("grestore\n")
}

func (e *Canvas) Stroke(path vg.Path) {
	if e.cur().width == 0 {
		return
	}
	e.trace(path)
	e.buf.WriteString("stroke\n")
}

func (e *Canvas) Fill(path vg.Path) {
	e.trace(path)
	e.buf.WriteString("fill\n")
}

func (e *Canvas) trace(path vg.Path) {
	e.buf.WriteString("newpath\n")
	for _, comp := range path {
		switch comp.Type {
		case vg.MoveComp:
			fmt.Fprintf(e.buf, "%.*g %.*g moveto\n", pr, comp.X, pr, comp.Y)
		case vg.LineComp:
			fmt.Fprintf(e.buf, "%.*g %.*g lineto\n", pr, comp.X, pr, comp.Y)
		case vg.ArcComp:
			end := comp.Start + comp.Angle
			arcOp := "arc"
			if comp.Angle < 0 {
				arcOp = "arcn"
			}
			fmt.Fprintf(e.buf, "%.*g %.*g %.*g %.*g %.*g %s\n", pr, comp.X, pr, comp.Y,
				pr, comp.Radius, pr, comp.Start*180/math.Pi, pr,
				end*180/math.Pi, arcOp)
		case vg.CloseComp:
			e.buf.WriteString("closepath\n")
		default:
			panic(fmt.Sprintf("Unknown path component type: %d\n", comp.Type))
		}
	}
}

func (e *Canvas) FillString(fnt vg.Font, x, y vg.Length, str string) {
	if e.cur().font != fnt.Name() || e.cur().fsize != fnt.Size {
		e.cur().font = fnt.Name()
		e.cur().fsize = fnt.Size
		fmt.Fprintf(e.buf, "/%s findfont %.*g scalefont setfont\n",
			fnt.Name(), pr, fnt.Size)
	}
	fmt.Fprintf(e.buf, "%.*g %.*g moveto\n", pr, x.Dots(e), pr, y.Dots(e))
	fmt.Fprintf(e.buf, "(%s) show\n", str)
}

func (e *Canvas) DPI() float64 {
	return 72
}

// Save saves the plot to the given path.
func (e *Canvas) Save(path string) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	b := bufio.NewWriter(f)
	_, err = e.buf.WriteTo(b)
	if err != nil {
		return err
	}
	_, err = fmt.Fprintln(b, "showpage")
	if err != nil {
		return err
	}
	return b.Flush()
}
<|endoftext|>


<commit_before>package libvirt

import (
	"fmt"
	"log"
	"net"
	"sync"
	"time"

	libvirt2 "github.com/digitalocean/go-libvirt"
	"github.com/hashicorp/terraform-plugin-sdk/helper/mutexkv"
	libvirtc "github.com/libvirt/libvirt-go"
)

// Config struct for the libvirt-provider
type Config struct {
	URI string
}

// Client libvirt
type Client struct {
	libvirt     *libvirt2.Libvirt
	libvirtc    *libvirtc.Connect
	poolMutexKV *mutexkv.MutexKV
	// define only one network at a time
	// https://gitlab.com/libvirt/libvirt/-/issues/78
	networkMutex sync.Mutex
}

// Client libvirt, generate libvirt client given URI
func (c *Config) Client() (*Client, error) {
	libvirtClient, err := libvirtc.NewConnect(c.URI)
	if err != nil {
		return nil, err
	}
	log.Println("[INFO] Created libvirt client")

	// TODO function url -> connection
	conn, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second)
	if err != nil {
		return nil, fmt.Errorf("failed to dial libvirt: %w", err)
	}

	l := libvirt2.New(conn)
	if err := l.Connect(); err != nil {
		return nil, fmt.Errorf("failed to connect: %w", err)
	}

	v, err := l.ConnectGetLibVersion()
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve libvirt version: %w", err)
	}
	log.Printf("[INFO] libvirt2 client libvirt version: %v\n", v)

	client := &Client{
		libvirt:     l,
		libvirtc:    libvirtClient,
		poolMutexKV: mutexkv.NewMutexKV(),
	}

	return client, nil
}
<commit_msg>Basic support for libvirt URIs<commit_after>package libvirt

import (
	"fmt"
	"log"
	"net"

	"net/url"
	"sync"
	"time"

	"github.com/digitalocean/go-libvirt"
	libvirt2 "github.com/digitalocean/go-libvirt"
	"github.com/hashicorp/terraform-plugin-sdk/helper/mutexkv"
	libvirtc "github.com/libvirt/libvirt-go"
)

const (
	defaultUnixSock = "/var/run/libvirt/libvirt-sock"
)

// Config struct for the libvirt-provider
type Config struct {
	URI string
}

// Client libvirt
type Client struct {
	libvirt     *libvirt2.Libvirt
	libvirtc    *libvirtc.Connect
	poolMutexKV *mutexkv.MutexKV
	// define only one network at a time
	// https://gitlab.com/libvirt/libvirt/-/issues/78
	networkMutex sync.Mutex
}

// Client libvirt, generate libvirt client given URI
func (c *Config) Client() (*Client, error) {

	u, err := url.Parse(c.URI)
	if err != nil {
		return nil, err
	}

	network := "unix"
	address := ""
	if u.Host != "" {
		network = "tcp"
		address = u.Host
		if u.Port() != "" {
			address = fmt.Sprintf("%s:%s", address, u.Port())
		}
	} else {
		q := u.Query()
		address = q.Get("socket")
		if address == "" {
			address = defaultUnixSock
		}
	}

	libvirtClient, err := libvirtc.NewConnect(c.URI)
	if err != nil {
		return nil, err
	}
	log.Println("[INFO] Created libvirt client")

	conn, err := net.DialTimeout(network, address, 2*time.Second)
	if err != nil {
		return nil, fmt.Errorf("failed to dial libvirt: %w", err)
	}
	log.Printf("[INFO] Set up libvirt transport: %v\n", conn)

	l := libvirt2.New(conn)
	if err := l.ConnectToURI(libvirt.ConnectURI(c.URI)); err != nil {
		return nil, fmt.Errorf("failed to connect: %w", err)
	}

	v, err := l.ConnectGetLibVersion()
	if err != nil {
		return nil, fmt.Errorf("failed to retrieve libvirt version: %w", err)
	}
	log.Printf("[INFO] libvirt2 client libvirt version: %v\n", v)

	client := &Client{
		libvirt:     l,
		libvirtc:    libvirtClient,
		poolMutexKV: mutexkv.NewMutexKV(),
	}

	return client, nil
}
<|endoftext|>