<commit_before>// Copyright 2017 the gousb Authors.  All rights reserved.
//
// 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 usb

import (
	"testing"
	"time"
)

func TestEndpoint(t *testing.T) {
	defer func(i libusbIntf) { libusb = i }(libusb)
	for _, epData := range []struct {
		ei   EndpointInfo
		intf InterfaceSetting
	}{
		{
			ei: EndpointInfo{
				Number:        2,
				Direction:     EndpointDirectionIn,
				MaxPacketSize: 512,
				TransferType:  TransferTypeBulk,
			},
			intf: InterfaceSetting{
				Number:    0,
				Alternate: 0,
				Class:     ClassVendorSpec,
			},
		},
		{
			ei: EndpointInfo{
				Number:        6,
				MaxPacketSize: 3 * 1024,
				TransferType:  TransferTypeIsochronous,
				PollInterval:  125 * time.Microsecond,
				UsageType:     IsoUsageTypeData,
			},
			intf: InterfaceSetting{
				Number:    0,
				Alternate: 0,
				Class:     ClassVendorSpec,
			},
		},
	} {
		epData.intf.Endpoints = []EndpointInfo{epData.ei}
		for _, tc := range []struct {
			desc    string
			buf     []byte
			ret     int
			status  TransferStatus
			want    int
			wantErr bool
		}{
			{
				desc: "empty buffer",
				buf:  nil,
				ret:  10,
				want: 0,
			},
			{
				desc: "128B buffer, 60 transferred",
				buf:  make([]byte, 128),
				ret:  60,
				want: 60,
			},
			{
				desc:    "128B buffer, 10 transferred and then error",
				buf:     make([]byte, 128),
				ret:     10,
				status:  TransferError,
				want:    10,
				wantErr: true,
			},
		} {
			lib := newFakeLibusb()
			libusb = lib

			ep := newEndpoint(nil, epData.intf, epData.ei)
			go func() {
				fakeT := lib.waitForSubmitted()
				fakeT.length = tc.ret
				fakeT.status = tc.status
				close(fakeT.done)
			}()
			got, err := ep.transfer(tc.buf)
			if (err != nil) != tc.wantErr {
				t.Errorf("%s, %s: ep.transfer(...): got err: %v, err != nil is %v, want %v", epData.ei, tc.desc, err, err != nil, tc.wantErr)
				continue
			}
			if got != tc.want {
				t.Errorf("%s, %s: ep.transfer(...): got %d bytes, want %d", epData.ei, tc.desc, got, tc.want)
			}
		}
	}
}

func TestEndpointInfo(t *testing.T) {
	for _, tc := range []struct {
		ep   EndpointInfo
		want string
	}{
		{
			ep: EndpointInfo{
				Number:        6,
				Direction:     EndpointDirectionIn,
				TransferType:  TransferTypeBulk,
				MaxPacketSize: 512,
			},
			want: "Endpoint #6 IN (address 0x86) bulk [512 bytes]",
		},
		{
			ep: EndpointInfo{
				Number:        2,
				Direction:     EndpointDirectionOut,
				TransferType:  TransferTypeIsochronous,
				MaxPacketSize: 512,
				IsoSyncType:   IsoSyncTypeAsync,
				UsageType:     IsoUsageTypeData,
			},
			want: "Endpoint #2 OUT (address 0x02) isochronous - asynchronous data [512 bytes]",
		},
		{
			ep: EndpointInfo{
				Number:        3,
				Direction:     EndpointDirectionIn,
				TransferType:  TransferTypeInterrupt,
				MaxPacketSize: 16,
				UsageType:     InterruptUsageTypePeriodic,
			},
			want: "Endpoint #3 IN (address 0x83) interrupt - periodic [16 bytes]",
		},
	} {
		if got := tc.ep.String(); got != tc.want {
			t.Errorf("%#v.String(): got %q, want %q", tc.ep, got, tc.want)
		}
	}
}

func TestEndpointIn(t *testing.T) {
	defer func(i libusbIntf) { libusb = i }(libusb)

	lib := newFakeLibusb()
	libusb = lib

	ctx := NewContext()
	d, err := ctx.OpenDeviceWithVidPid(0x9999, 0x0001)
	if err != nil {
		t.Fatalf("OpenDeviceWithVidPid(0x9999, 0x0001): got error %v, want nil", err)
	}
	ep, err := d.InEndpoint(1, 0, 0, 2)
	if err != nil {
		t.Fatalf("InEndpoint(1, 0, 0, 2): got error %v, want nil", err)
	}
	dataTransferred := 100
	go func() {
		fakeT := lib.waitForSubmitted()
		fakeT.length = dataTransferred
		fakeT.status = TransferCompleted
		close(fakeT.done)
	}()
	buf := make([]byte, 512)
	got, err := ep.Read(buf)
	if err != nil {
		t.Errorf("ep.Read: got error %v, want nil", err)
	} else if got != dataTransferred {
		t.Errorf("ep.Read: got %d, want %d", got, dataTransferred)
	}

	_, err = d.InEndpoint(1, 0, 0, 1)
	if err == nil {
		t.Error("InEndpoint(1, 0, 0, 1): got nil, want error")
	}
}

func TestEndpointOut(t *testing.T) {
	defer func(i libusbIntf) { libusb = i }(libusb)

	lib := newFakeLibusb()
	libusb = lib

	ctx := NewContext()
	d, err := ctx.OpenDeviceWithVidPid(0x9999, 0x0001)
	if err != nil {
		t.Fatalf("OpenDeviceWithVidPid(0x9999, 0x0001): got error %v, want nil", err)
	}
	ep, err := d.OutEndpoint(1, 0, 0, 1)
	if err != nil {
		t.Fatalf("OutEndpoint(1, 0, 0, 1): got error %v, want nil", err)
	}
	dataTransferred := 100
	go func() {
		fakeT := lib.waitForSubmitted()
		fakeT.length = dataTransferred
		fakeT.status = TransferCompleted
		close(fakeT.done)
	}()
	buf := make([]byte, 512)
	got, err := ep.Write(buf)
	if err != nil {
		t.Errorf("ep.Write: got error %v, want nil", err)
	} else if got != dataTransferred {
		t.Errorf("ep.Write: got %d, want %d", got, dataTransferred)
	}

	_, err = d.OutEndpoint(1, 0, 0, 2)
	if err == nil {
		t.Error("OutEndpoint(1, 0, 0, 2): got nil, want error")
	}
}
<commit_msg>Close the Context<commit_after>// Copyright 2017 the gousb Authors.  All rights reserved.
//
// 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 usb

import (
	"testing"
	"time"
)

func TestEndpoint(t *testing.T) {
	defer func(i libusbIntf) { libusb = i }(libusb)
	for _, epData := range []struct {
		ei   EndpointInfo
		intf InterfaceSetting
	}{
		{
			ei: EndpointInfo{
				Number:        2,
				Direction:     EndpointDirectionIn,
				MaxPacketSize: 512,
				TransferType:  TransferTypeBulk,
			},
			intf: InterfaceSetting{
				Number:    0,
				Alternate: 0,
				Class:     ClassVendorSpec,
			},
		},
		{
			ei: EndpointInfo{
				Number:        6,
				MaxPacketSize: 3 * 1024,
				TransferType:  TransferTypeIsochronous,
				PollInterval:  125 * time.Microsecond,
				UsageType:     IsoUsageTypeData,
			},
			intf: InterfaceSetting{
				Number:    0,
				Alternate: 0,
				Class:     ClassVendorSpec,
			},
		},
	} {
		epData.intf.Endpoints = []EndpointInfo{epData.ei}
		for _, tc := range []struct {
			desc    string
			buf     []byte
			ret     int
			status  TransferStatus
			want    int
			wantErr bool
		}{
			{
				desc: "empty buffer",
				buf:  nil,
				ret:  10,
				want: 0,
			},
			{
				desc: "128B buffer, 60 transferred",
				buf:  make([]byte, 128),
				ret:  60,
				want: 60,
			},
			{
				desc:    "128B buffer, 10 transferred and then error",
				buf:     make([]byte, 128),
				ret:     10,
				status:  TransferError,
				want:    10,
				wantErr: true,
			},
		} {
			lib := newFakeLibusb()
			libusb = lib

			ep := newEndpoint(nil, epData.intf, epData.ei)
			go func() {
				fakeT := lib.waitForSubmitted()
				fakeT.length = tc.ret
				fakeT.status = tc.status
				close(fakeT.done)
			}()
			got, err := ep.transfer(tc.buf)
			if (err != nil) != tc.wantErr {
				t.Errorf("%s, %s: ep.transfer(...): got err: %v, err != nil is %v, want %v", epData.ei, tc.desc, err, err != nil, tc.wantErr)
				continue
			}
			if got != tc.want {
				t.Errorf("%s, %s: ep.transfer(...): got %d bytes, want %d", epData.ei, tc.desc, got, tc.want)
			}
		}
	}
}

func TestEndpointInfo(t *testing.T) {
	for _, tc := range []struct {
		ep   EndpointInfo
		want string
	}{
		{
			ep: EndpointInfo{
				Number:        6,
				Direction:     EndpointDirectionIn,
				TransferType:  TransferTypeBulk,
				MaxPacketSize: 512,
			},
			want: "Endpoint #6 IN (address 0x86) bulk [512 bytes]",
		},
		{
			ep: EndpointInfo{
				Number:        2,
				Direction:     EndpointDirectionOut,
				TransferType:  TransferTypeIsochronous,
				MaxPacketSize: 512,
				IsoSyncType:   IsoSyncTypeAsync,
				UsageType:     IsoUsageTypeData,
			},
			want: "Endpoint #2 OUT (address 0x02) isochronous - asynchronous data [512 bytes]",
		},
		{
			ep: EndpointInfo{
				Number:        3,
				Direction:     EndpointDirectionIn,
				TransferType:  TransferTypeInterrupt,
				MaxPacketSize: 16,
				UsageType:     InterruptUsageTypePeriodic,
			},
			want: "Endpoint #3 IN (address 0x83) interrupt - periodic [16 bytes]",
		},
	} {
		if got := tc.ep.String(); got != tc.want {
			t.Errorf("%#v.String(): got %q, want %q", tc.ep, got, tc.want)
		}
	}
}

func TestEndpointIn(t *testing.T) {
	defer func(i libusbIntf) { libusb = i }(libusb)

	lib := newFakeLibusb()
	libusb = lib

	ctx := NewContext()
	defer ctx.Close()
	d, err := ctx.OpenDeviceWithVidPid(0x9999, 0x0001)
	if err != nil {
		t.Fatalf("OpenDeviceWithVidPid(0x9999, 0x0001): got error %v, want nil", err)
	}
	ep, err := d.InEndpoint(1, 0, 0, 2)
	if err != nil {
		t.Fatalf("InEndpoint(1, 0, 0, 2): got error %v, want nil", err)
	}
	dataTransferred := 100
	go func() {
		fakeT := lib.waitForSubmitted()
		fakeT.length = dataTransferred
		fakeT.status = TransferCompleted
		close(fakeT.done)
	}()
	buf := make([]byte, 512)
	got, err := ep.Read(buf)
	if err != nil {
		t.Errorf("ep.Read: got error %v, want nil", err)
	} else if got != dataTransferred {
		t.Errorf("ep.Read: got %d, want %d", got, dataTransferred)
	}

	_, err = d.InEndpoint(1, 0, 0, 1)
	if err == nil {
		t.Error("InEndpoint(1, 0, 0, 1): got nil, want error")
	}
}

func TestEndpointOut(t *testing.T) {
	defer func(i libusbIntf) { libusb = i }(libusb)

	lib := newFakeLibusb()
	libusb = lib

	ctx := NewContext()
	defer ctx.Close()
	d, err := ctx.OpenDeviceWithVidPid(0x9999, 0x0001)
	if err != nil {
		t.Fatalf("OpenDeviceWithVidPid(0x9999, 0x0001): got error %v, want nil", err)
	}
	ep, err := d.OutEndpoint(1, 0, 0, 1)
	if err != nil {
		t.Fatalf("OutEndpoint(1, 0, 0, 1): got error %v, want nil", err)
	}
	dataTransferred := 100
	go func() {
		fakeT := lib.waitForSubmitted()
		fakeT.length = dataTransferred
		fakeT.status = TransferCompleted
		close(fakeT.done)
	}()
	buf := make([]byte, 512)
	got, err := ep.Write(buf)
	if err != nil {
		t.Errorf("ep.Write: got error %v, want nil", err)
	} else if got != dataTransferred {
		t.Errorf("ep.Write: got %d, want %d", got, dataTransferred)
	}

	_, err = d.OutEndpoint(1, 0, 0, 2)
	if err == nil {
		t.Error("OutEndpoint(1, 0, 0, 2): got nil, want error")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"image"
	"io/ioutil"
	"net/http"
	"net/url"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

// When all else fails, Google it. Uses the regular web interface. There are
// two image search APIs, but one is deprecated and doesn't support exact size
// matching, and the other requires an API key limited to 100 searches a day.
const googleSearchFormat = `https://www.google.com.br/search?tbs=isz%3Aex%2Ciszw%3A%v%2Ciszh%3A%v&tbm=isch&num=5&q=`

// Possible Google result formats
var googleSearchResultPatterns = []string{`imgurl=(.+?\.(jpeg|jpg|png))&imgrefurl=`, `\"ou\":\"(.+?)\",\"`}

// Returns the first steam grid image URL found by Google search of a given
// game name.
func getGoogleImage(gameName string, artStyleExtensions []string) (string, error) {
	if gameName == "" {
		return "", nil
	}

	url := fmt.Sprintf(googleSearchFormat, artStyleExtensions[3], artStyleExtensions[4]) + url.QueryEscape(gameName)

	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	// If we don't set an user agent, Google will block us because we are a
	// bot. If we set something like "SteamGrid Image Search" it'll work, but
	// Google will serve a simple HTML page without direct image links.
	// So we have to lie.
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36")
	response, err := client.Do(req)
	if err != nil {
		return "", err
	}

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

	for _, googleSearchResultPattern := range googleSearchResultPatterns {
		pattern := regexp.MustCompile(googleSearchResultPattern)
		matches := pattern.FindStringSubmatch(string(responseBytes))

		if len(matches) >= 1 {
			return matches[1], nil
		}
	}
	return "", nil
}

// https://www.steamgriddb.com/api/v2
type SteamGridDBResponse struct {
	Success bool
	Data []struct {
		Id int
		Score int
		Style string
		Url string
		Thumb string
		Tags []string
		Author struct {
			Name string
			Steam64 string
			Avatar string
		}
	}
}

type SteamGridDBSearchResponse struct {
	Success bool
	Data []struct {
		Id int
		Name string
		Types []string
		Verified bool
	}
}

// Search SteamGridDB for cover image
const SteamGridDBBaseURL = "https://www.steamgriddb.com/api/v2"

func SteamGridDBGetRequest(url string, steamGridDBApiKey string) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer " + steamGridDBApiKey)
	if err != nil {
		return nil, err
	}

	response, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	if response.StatusCode == 401 {
		// Authorization token is missing or invalid
		return nil, errors.New("401")
	} else if response.StatusCode == 404 {
		// Could not find game with that id
		return nil, errors.New("404")
	}

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

	return responseBytes, nil
}

func getSteamGridDBImage(game *Game, artStyleExtensions []string, steamGridDBApiKey string) (string, error) {
	// Specify artType:
	// "alternate" "blurred" "white_logo" "material" "no_logo"
	artTypes := []string{"alternate"}
	filter := "?styles=" + strings.Join(artTypes, ",")

	// Try for HQ, then for LQ
	// It's possible to request both dimensions in one go but that'll give us scrambled results with no indicator which result has which size.
	for i := 0; i < 3; i += 2 {
		dimensions := filter + "&dimensions=" + artStyleExtensions[3 + i] + "x" + artStyleExtensions[4 + i]

		// Try with game.ID which is probably steams appID
		url := SteamGridDBBaseURL + "/grids/steam/" + game.ID + dimensions
		responseBytes, err := SteamGridDBGetRequest(url, steamGridDBApiKey)
		var jsonResponse SteamGridDBResponse

		// Authorization token is missing or invalid
	 	if err != nil && err.Error() == "401" {
			return "", errors.New("SteamGridDB authorization token is missing or invalid")
		// Could not find game with that id
		} else if err != nil && err.Error() == "404" {
			// Try searching for the name…
			url = SteamGridDBBaseURL + "/search/autocomplete/" + game.Name + dimensions
			responseBytes, err = SteamGridDBGetRequest(url, steamGridDBApiKey)
			if err != nil {
				return "", err
			}

			var jsonSearchResponse SteamGridDBSearchResponse
			err = json.Unmarshal(responseBytes, &jsonSearchResponse)
			if err != nil {
				return "", errors.New("Best search match doesn't has a " + strings.Join(artTypes, ",") + " type")
			}

			SteamGridDBGameId := -1
			if jsonSearchResponse.Success && len(jsonSearchResponse.Data) >= 1 {
				for _, n := range jsonSearchResponse.Data[0].Types {
					for _, m := range artTypes {
						if n == m {
							// This game has at least one of our requested artTypes
							SteamGridDBGameId = jsonSearchResponse.Data[0].Id
							break
						}
					}

					if SteamGridDBGameId != -1 {
						break
					}
				}
			}

			if SteamGridDBGameId == -1 {
				return "", nil
			}

			// …and get the url of the top result.
			url = SteamGridDBBaseURL + "/grids/game/" + strconv.Itoa(SteamGridDBGameId) + dimensions
			responseBytes, err = SteamGridDBGetRequest(url, steamGridDBApiKey)
			if err != nil {
				return "", err
			}
		} else if err != nil {
			return "", err
		}

		err = json.Unmarshal(responseBytes, &jsonResponse)
		if err != nil {
			return "", err
		}

		if jsonResponse.Success && len(jsonResponse.Data) >= 1 {
			return jsonResponse.Data[0].Url, nil
		}
	}

	return "", nil
}

const IGDBImageURL = "https://images.igdb.com/igdb/image/upload/t_720p/%v.jpg"
const IGDBGameURL = "https://api-v3.igdb.com/games"
const IGDBCoverURL = "https://api-v3.igdb.com/covers"
const IGDBGameBody = `fields name,cover; search "%v";`
const IGDBCoverBody = `fields image_id; where id = %v;`

type IGDBGame struct {
	Id int
	Cover int
	Name string
}

type IGDBCover struct {
	Id int
	Image_id string
}

func IGDBPostRequest(url string, body string, IGDBApiKey string) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, strings.NewReader(body))
	req.Header.Add("user-key", IGDBApiKey)
	req.Header.Add("Accept", "application/json")
	if err != nil {
		return nil, err
	}

	response, err := client.Do(req)
	if err != nil {
		return nil, err
	}

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

	return responseBytes, nil
}

func getIGDBImage(gameName string, IGDBApiKey string) (string, error) {
	responseBytes, err := IGDBPostRequest(IGDBGameURL, fmt.Sprintf(IGDBGameBody, gameName), IGDBApiKey)
	if err != nil {
		return "", err
	}

	var jsonGameResponse []IGDBGame
	err = json.Unmarshal(responseBytes, &jsonGameResponse)
	if err != nil {
		return "", nil
	}

	if len(jsonGameResponse) < 1 || jsonGameResponse[0].Cover == 0 {
		return "", nil
	}

	responseBytes, err = IGDBPostRequest(IGDBCoverURL, fmt.Sprintf(IGDBCoverBody, jsonGameResponse[0].Cover), IGDBApiKey)
	if err != nil {
		return "", err
	}

	var jsonCoverResponse []IGDBCover
	err = json.Unmarshal(responseBytes, &jsonCoverResponse)
	if err != nil {
		return "", nil
	}

	if len(jsonCoverResponse) >= 1 {
		return fmt.Sprintf(IGDBImageURL, jsonCoverResponse[0].Image_id), nil
	}

	return "", 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/`

// The subreddit mentions this as primary, but I've found Akamai to contain
// more images and answer faster.
const steamCdnURLFormat = `cdn.akamai.steamstatic.com/steam/apps/%v/`

// 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
// from a Google search (useful because we want to log the lower quality
// images).
func getImageAlternatives(game *Game, artStyle string, artStyleExtensions []string, steamGridDBApiKey string, IGDBApiKey string) (response *http.Response, from string, err error) {
	from = "steam server"
	response, err = tryDownload(fmt.Sprintf(akamaiURLFormat + artStyleExtensions[2], game.ID))
	if err == nil && response != nil {
		return
	}

	response, err = tryDownload(fmt.Sprintf(steamCdnURLFormat + artStyleExtensions[2], game.ID))
	if err == nil && response != nil {
		return
	}

	url := ""
	if (artStyle == "Cover" || artStyle == "Banner") && steamGridDBApiKey != "" && url == "" {
		from = "SteamGridDB"
		url, err = getSteamGridDBImage(game, artStyleExtensions, steamGridDBApiKey)
		if err != nil {
			return
		}
	}

	// IGDB has mostly cover styles
	if artStyle == "Cover" && IGDBApiKey != "" && url == "" {
		from = "IGDB"
		url, err = getIGDBImage(game.Name, IGDBApiKey)
		if err != nil {
			return
		}
	}

	// Skip for Covers, bad results
	if artStyle == "Banner" && url == "" {
		from = "search"
		url, err = getGoogleImage(game.Name, artStyleExtensions)
		if err != nil {
			return
		}
	}

	response, err = tryDownload(url)
	if err == nil && response != nil {
		return
	}

	return nil, "", nil
}

// DownloadImage tries to download the game images, saving it in game.ImageBytes. Returns
// flags indicating if the operation succeeded and if the image downloaded was
// from a search.
func DownloadImage(gridDir string, game *Game, artStyle string, artStyleExtensions []string, steamGridDBApiKey string, IGDBApiKey string) (string, error) {
	response, from, err := getImageAlternatives(game, artStyle, artStyleExtensions, steamGridDBApiKey, IGDBApiKey)
	if response == nil || err != nil {
		return "", err
	}

	contentType := response.Header.Get("Content-Type")
	urlExt := filepath.Ext(response.Request.URL.Path)
	if contentType != "" {
		game.ImageExt = "." + strings.Split(contentType, "/")[1]
	} else if urlExt != "" {
		game.ImageExt = urlExt
	} else {
		// Steam is forgiving on image extensions.
		game.ImageExt = "jpg"
	}

	if game.ImageExt == ".jpeg" {
		// The new library ignores .jpeg
		game.ImageExt = ".jpg"
	} else if game.ImageExt == ".octet-stream" {
		// Amazonaws (steamgriddb) gives us an .octet-stream
		game.ImageExt = ".png"
	}

	imageBytes, err := ioutil.ReadAll(response.Body)
	response.Body.Close()

	// catch false aspect ratios
	image, _, err := image.Decode(bytes.NewBuffer(imageBytes))
	if err != nil {
		return "", err
	}
	imageSize := image.Bounds().Max
	if (artStyle == "Banner" && imageSize.X < imageSize.Y) {
		return "", nil
	} else if (artStyle == "Cover" && imageSize.X > imageSize.Y) {
		return "", nil
	}

	game.ImageSource = from;

	game.CleanImageBytes = imageBytes
	return from, nil
}

// Get game name from SteamDB as last resort.
const steamDBFormat = `https://steamdb.info/app/%v`

func GetGameName(gameId string) string {
	response, err := tryDownload(fmt.Sprintf(steamDBFormat, gameId))
	if err != nil || response == nil {
		return ""
	}
	page, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return ""
	}
	response.Body.Close()

	pattern := regexp.MustCompile("
\nName	\\s*(.*?)	")
	match := pattern.FindStringSubmatch(string(page))
	if match == nil || len(match) == 0 {
		return ""
	} else {
		return match[1]
	}
}
<commit_msg>Use the older LQ variant for googles search<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"image"
	"io/ioutil"
	"net/http"
	"net/url"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

// When all else fails, Google it. Uses the regular web interface. There are
// two image search APIs, but one is deprecated and doesn't support exact size
// matching, and the other requires an API key limited to 100 searches a day.
const googleSearchFormat = `https://www.google.com.br/search?tbs=isz%3Aex%2Ciszw%3A%v%2Ciszh%3A%v&tbm=isch&num=5&q=`

// Possible Google result formats
var googleSearchResultPatterns = []string{`imgurl=(.+?\.(jpeg|jpg|png))&imgrefurl=`, `\"ou\":\"(.+?)\",\"`}

// Returns the first steam grid image URL found by Google search of a given
// game name.
func getGoogleImage(gameName string, artStyleExtensions []string) (string, error) {
	if gameName == "" {
		return "", nil
	}

	url := fmt.Sprintf(googleSearchFormat, artStyleExtensions[5], artStyleExtensions[6]) + url.QueryEscape(gameName)

	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	// If we don't set an user agent, Google will block us because we are a
	// bot. If we set something like "SteamGrid Image Search" it'll work, but
	// Google will serve a simple HTML page without direct image links.
	// So we have to lie.
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36")
	response, err := client.Do(req)
	if err != nil {
		return "", err
	}

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

	for _, googleSearchResultPattern := range googleSearchResultPatterns {
		pattern := regexp.MustCompile(googleSearchResultPattern)
		matches := pattern.FindStringSubmatch(string(responseBytes))

		if len(matches) >= 1 {
			return matches[1], nil
		}
	}
	return "", nil
}

// https://www.steamgriddb.com/api/v2
type SteamGridDBResponse struct {
	Success bool
	Data []struct {
		Id int
		Score int
		Style string
		Url string
		Thumb string
		Tags []string
		Author struct {
			Name string
			Steam64 string
			Avatar string
		}
	}
}

type SteamGridDBSearchResponse struct {
	Success bool
	Data []struct {
		Id int
		Name string
		Types []string
		Verified bool
	}
}

// Search SteamGridDB for cover image
const SteamGridDBBaseURL = "https://www.steamgriddb.com/api/v2"

func SteamGridDBGetRequest(url string, steamGridDBApiKey string) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer " + steamGridDBApiKey)
	if err != nil {
		return nil, err
	}

	response, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	if response.StatusCode == 401 {
		// Authorization token is missing or invalid
		return nil, errors.New("401")
	} else if response.StatusCode == 404 {
		// Could not find game with that id
		return nil, errors.New("404")
	}

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

	return responseBytes, nil
}

func getSteamGridDBImage(game *Game, artStyleExtensions []string, steamGridDBApiKey string) (string, error) {
	// Specify artType:
	// "alternate" "blurred" "white_logo" "material" "no_logo"
	artTypes := []string{"alternate"}
	filter := "?styles=" + strings.Join(artTypes, ",")

	// Try for HQ, then for LQ
	// It's possible to request both dimensions in one go but that'll give us scrambled results with no indicator which result has which size.
	for i := 0; i < 3; i += 2 {
		dimensions := filter + "&dimensions=" + artStyleExtensions[3 + i] + "x" + artStyleExtensions[4 + i]

		// Try with game.ID which is probably steams appID
		url := SteamGridDBBaseURL + "/grids/steam/" + game.ID + dimensions
		responseBytes, err := SteamGridDBGetRequest(url, steamGridDBApiKey)
		var jsonResponse SteamGridDBResponse

		// Authorization token is missing or invalid
	 	if err != nil && err.Error() == "401" {
			return "", errors.New("SteamGridDB authorization token is missing or invalid")
		// Could not find game with that id
		} else if err != nil && err.Error() == "404" {
			// Try searching for the name…
			url = SteamGridDBBaseURL + "/search/autocomplete/" + game.Name + dimensions
			responseBytes, err = SteamGridDBGetRequest(url, steamGridDBApiKey)
			if err != nil {
				return "", err
			}

			var jsonSearchResponse SteamGridDBSearchResponse
			err = json.Unmarshal(responseBytes, &jsonSearchResponse)
			if err != nil {
				return "", errors.New("Best search match doesn't has a " + strings.Join(artTypes, ",") + " type")
			}

			SteamGridDBGameId := -1
			if jsonSearchResponse.Success && len(jsonSearchResponse.Data) >= 1 {
				for _, n := range jsonSearchResponse.Data[0].Types {
					for _, m := range artTypes {
						if n == m {
							// This game has at least one of our requested artTypes
							SteamGridDBGameId = jsonSearchResponse.Data[0].Id
							break
						}
					}

					if SteamGridDBGameId != -1 {
						break
					}
				}
			}

			if SteamGridDBGameId == -1 {
				return "", nil
			}

			// …and get the url of the top result.
			url = SteamGridDBBaseURL + "/grids/game/" + strconv.Itoa(SteamGridDBGameId) + dimensions
			responseBytes, err = SteamGridDBGetRequest(url, steamGridDBApiKey)
			if err != nil {
				return "", err
			}
		} else if err != nil {
			return "", err
		}

		err = json.Unmarshal(responseBytes, &jsonResponse)
		if err != nil {
			return "", err
		}

		if jsonResponse.Success && len(jsonResponse.Data) >= 1 {
			return jsonResponse.Data[0].Url, nil
		}
	}

	return "", nil
}

const IGDBImageURL = "https://images.igdb.com/igdb/image/upload/t_720p/%v.jpg"
const IGDBGameURL = "https://api-v3.igdb.com/games"
const IGDBCoverURL = "https://api-v3.igdb.com/covers"
const IGDBGameBody = `fields name,cover; search "%v";`
const IGDBCoverBody = `fields image_id; where id = %v;`

type IGDBGame struct {
	Id int
	Cover int
	Name string
}

type IGDBCover struct {
	Id int
	Image_id string
}

func IGDBPostRequest(url string, body string, IGDBApiKey string) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, strings.NewReader(body))
	req.Header.Add("user-key", IGDBApiKey)
	req.Header.Add("Accept", "application/json")
	if err != nil {
		return nil, err
	}

	response, err := client.Do(req)
	if err != nil {
		return nil, err
	}

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

	return responseBytes, nil
}

func getIGDBImage(gameName string, IGDBApiKey string) (string, error) {
	responseBytes, err := IGDBPostRequest(IGDBGameURL, fmt.Sprintf(IGDBGameBody, gameName), IGDBApiKey)
	if err != nil {
		return "", err
	}

	var jsonGameResponse []IGDBGame
	err = json.Unmarshal(responseBytes, &jsonGameResponse)
	if err != nil {
		return "", nil
	}

	if len(jsonGameResponse) < 1 || jsonGameResponse[0].Cover == 0 {
		return "", nil
	}

	responseBytes, err = IGDBPostRequest(IGDBCoverURL, fmt.Sprintf(IGDBCoverBody, jsonGameResponse[0].Cover), IGDBApiKey)
	if err != nil {
		return "", err
	}

	var jsonCoverResponse []IGDBCover
	err = json.Unmarshal(responseBytes, &jsonCoverResponse)
	if err != nil {
		return "", nil
	}

	if len(jsonCoverResponse) >= 1 {
		return fmt.Sprintf(IGDBImageURL, jsonCoverResponse[0].Image_id), nil
	}

	return "", 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/`

// The subreddit mentions this as primary, but I've found Akamai to contain
// more images and answer faster.
const steamCdnURLFormat = `cdn.akamai.steamstatic.com/steam/apps/%v/`

// 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
// from a Google search (useful because we want to log the lower quality
// images).
func getImageAlternatives(game *Game, artStyle string, artStyleExtensions []string, steamGridDBApiKey string, IGDBApiKey string) (response *http.Response, from string, err error) {
	from = "steam server"
	response, err = tryDownload(fmt.Sprintf(akamaiURLFormat + artStyleExtensions[2], game.ID))
	if err == nil && response != nil {
		return
	}

	response, err = tryDownload(fmt.Sprintf(steamCdnURLFormat + artStyleExtensions[2], game.ID))
	if err == nil && response != nil {
		return
	}

	url := ""
	if (artStyle == "Cover" || artStyle == "Banner") && steamGridDBApiKey != "" && url == "" {
		from = "SteamGridDB"
		url, err = getSteamGridDBImage(game, artStyleExtensions, steamGridDBApiKey)
		if err != nil {
			return
		}
	}

	// IGDB has mostly cover styles
	if artStyle == "Cover" && IGDBApiKey != "" && url == "" {
		from = "IGDB"
		url, err = getIGDBImage(game.Name, IGDBApiKey)
		if err != nil {
			return
		}
	}

	// Skip for Covers, bad results
	if artStyle == "Banner" && url == "" {
		from = "search"
		url, err = getGoogleImage(game.Name, artStyleExtensions)
		if err != nil {
			return
		}
	}

	response, err = tryDownload(url)
	if err == nil && response != nil {
		return
	}

	return nil, "", nil
}

// DownloadImage tries to download the game images, saving it in game.ImageBytes. Returns
// flags indicating if the operation succeeded and if the image downloaded was
// from a search.
func DownloadImage(gridDir string, game *Game, artStyle string, artStyleExtensions []string, steamGridDBApiKey string, IGDBApiKey string) (string, error) {
	response, from, err := getImageAlternatives(game, artStyle, artStyleExtensions, steamGridDBApiKey, IGDBApiKey)
	if response == nil || err != nil {
		return "", err
	}

	contentType := response.Header.Get("Content-Type")
	urlExt := filepath.Ext(response.Request.URL.Path)
	if contentType != "" {
		game.ImageExt = "." + strings.Split(contentType, "/")[1]
	} else if urlExt != "" {
		game.ImageExt = urlExt
	} else {
		// Steam is forgiving on image extensions.
		game.ImageExt = "jpg"
	}

	if game.ImageExt == ".jpeg" {
		// The new library ignores .jpeg
		game.ImageExt = ".jpg"
	} else if game.ImageExt == ".octet-stream" {
		// Amazonaws (steamgriddb) gives us an .octet-stream
		game.ImageExt = ".png"
	}

	imageBytes, err := ioutil.ReadAll(response.Body)
	response.Body.Close()

	// catch false aspect ratios
	image, _, err := image.Decode(bytes.NewBuffer(imageBytes))
	if err != nil {
		return "", err
	}
	imageSize := image.Bounds().Max
	if (artStyle == "Banner" && imageSize.X < imageSize.Y) {
		return "", nil
	} else if (artStyle == "Cover" && imageSize.X > imageSize.Y) {
		return "", nil
	}

	game.ImageSource = from;

	game.CleanImageBytes = imageBytes
	return from, nil
}

// Get game name from SteamDB as last resort.
const steamDBFormat = `https://steamdb.info/app/%v`

func GetGameName(gameId string) string {
	response, err := tryDownload(fmt.Sprintf(steamDBFormat, gameId))
	if err != nil || response == nil {
		return ""
	}
	page, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return ""
	}
	response.Body.Close()

	pattern := regexp.MustCompile("
\nName	\\s*(.*?)	")
	match := pattern.FindStringSubmatch(string(page))
	if match == nil || len(match) == 0 {
		return ""
	} else {
		return match[1]
	}
}
<|endoftext|>


<commit_before>/*
Get all colors for 255-colors terminal:
	gommand 'for i := 0; i < 256; i++ {fmt.Println(i, ansi.ColorCode(strconv.Itoa(i)) + "String" + ansi.ColorCode("reset"))}'
*/
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/mgutz/ansi"
	"golang.org/x/tools/cover"
)

const usageMessage = `go-carpet - show coverage for Go source files

usage: go-carpet [dirs]`

func getDirsWithTests(roots ...string) []string {
	if len(roots) == 0 {
		roots = []string{"."}
	}

	dirs := map[string]struct{}{}
	for _, root := range roots {
		filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
			if strings.HasSuffix(path, "_test.go") {
				dirs[filepath.Dir(path)] = struct{}{}
			}
			return nil
		})
	}

	result := make([]string, 0, len(dirs))
	for dir := range dirs {
		result = append(result, "./"+dir)
	}
	return result
}

func readFile(fileName string) (result []byte, err error) {
	fileReader, err := os.Open(fileName)
	if err != nil {
		return result, err
	}
	defer fileReader.Close()

	result, err = ioutil.ReadAll(fileReader)
	return result, err
}

func getCoverForDir(path string, coverFileName string) (result []byte, err error) {
	osExec := exec.Command("go", "test", "-coverprofile="+coverFileName, "-covermode=count", path)
	osExec.Stderr = os.Stderr
	err = osExec.Run()
	if err != nil {
		return result, err
	}

	coverProfile, err := cover.ParseProfiles(coverFileName)
	if err != nil {
		return result, err
	}

	for _, fileProfile := range coverProfile {
		fileName := ""
		if strings.HasPrefix(fileProfile.FileName, "_") {
			// absolute path
			fileName = strings.TrimLeft(fileProfile.FileName, "_")
		} else {
			// file in GOPATH
			fileName = os.Getenv("GOPATH") + "/src/" + fileProfile.FileName
		}
		if _, err := os.Stat(fileName); os.IsNotExist(err) {
			fmt.Printf("File '%s' is not exists\n", fileName)
			continue
		}

		fileBytes, err := readFile(fileName)
		if err != nil {
			return result, err
		}

		fileNameDisplay := fileProfile.FileName

		result = append(result, []byte(ansi.ColorCode("yellow")+fileNameDisplay+ansi.ColorCode("reset")+"\n"+
			ansi.ColorCode("black+h")+strings.Repeat("~", len(fileNameDisplay))+ansi.ColorCode("reset")+"\n")...)

		boundaries := fileProfile.Boundaries(fileBytes)
		curOffset := 0
		for _, boundary := range boundaries {
			if boundary.Offset > curOffset {
				result = append(result, fileBytes[curOffset:boundary.Offset]...)
			}
			switch {
			case boundary.Start && boundary.Count > 0:
				result = append(result, []byte(ansi.ColorCode("green"))...)
			case boundary.Start && boundary.Count == 0:
				result = append(result, []byte(ansi.ColorCode("red"))...)
			case !boundary.Start:
				result = append(result, []byte(ansi.ColorCode("reset"))...)
			}

			curOffset = boundary.Offset
		}
		if curOffset < len(fileBytes) {
			result = append(result, fileBytes[curOffset:len(fileBytes)]...)
		}
		result = append(result, []byte("\n")...)
	}

	return result, err
}

func getTempFileName() string {
	tmpFile, err := ioutil.TempFile(".", "go-carpet-coverage-out-")
	if err != nil {
		log.Fatal(err)
	}
	tmpFile.Close()

	return tmpFile.Name()
}

func main() {
	flag.Usage = func() {
		fmt.Println(usageMessage)
		flag.PrintDefaults()
		os.Exit(0)
	}
	flag.Parse()
	testDirs := flag.Args()

	coverFileName := getTempFileName()
	defer os.RemoveAll(coverFileName)
	stdOut := getColorWriter()

	if len(testDirs) > 0 {
		testDirs = getDirsWithTests(testDirs...)
	} else {
		testDirs = getDirsWithTests(".")
	}
	for _, path := range testDirs {
		coverInBytes, err := getCoverForDir(path, coverFileName)
		if err != nil {
			log.Print(err)
		}
		stdOut.Write(coverInBytes)
	}
}
<commit_msg>Reformat code<commit_after>/*
Get all colors for 255-colors terminal:
	gommand 'for i := 0; i < 256; i++ {fmt.Println(i, ansi.ColorCode(strconv.Itoa(i)) + "String" + ansi.ColorCode("reset"))}'
*/
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/mgutz/ansi"
	"golang.org/x/tools/cover"
)

const usageMessage = `go-carpet - show coverage for Go source files

usage: go-carpet [dirs]`

func getDirsWithTests(roots ...string) []string {
	if len(roots) == 0 {
		roots = []string{"."}
	}

	dirs := map[string]struct{}{}
	for _, root := range roots {
		filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
			if strings.HasSuffix(path, "_test.go") {
				dirs[filepath.Dir(path)] = struct{}{}
			}
			return nil
		})
	}

	result := make([]string, 0, len(dirs))
	for dir := range dirs {
		result = append(result, "./"+dir)
	}
	return result
}

func readFile(fileName string) (result []byte, err error) {
	fileReader, err := os.Open(fileName)
	if err != nil {
		return result, err
	}
	defer fileReader.Close()

	result, err = ioutil.ReadAll(fileReader)
	return result, err
}

func getCoverForDir(path string, coverFileName string) (result []byte, err error) {
	osExec := exec.Command("go", "test", "-coverprofile="+coverFileName, "-covermode=count", path)
	osExec.Stderr = os.Stderr
	err = osExec.Run()
	if err != nil {
		return result, err
	}

	coverProfile, err := cover.ParseProfiles(coverFileName)
	if err != nil {
		return result, err
	}

	for _, fileProfile := range coverProfile {
		fileName := ""
		if strings.HasPrefix(fileProfile.FileName, "_") {
			// absolute path
			fileName = strings.TrimLeft(fileProfile.FileName, "_")
		} else {
			// file in GOPATH
			fileName = os.Getenv("GOPATH") + "/src/" + fileProfile.FileName
		}
		if _, err := os.Stat(fileName); os.IsNotExist(err) {
			fmt.Printf("File '%s' is not exists\n", fileName)
			continue
		}

		fileBytes, err := readFile(fileName)
		if err != nil {
			return result, err
		}

		fileNameDisplay := fileProfile.FileName

		result = append(result,
			[]byte(ansi.ColorCode("yellow")+
				fileNameDisplay+ansi.ColorCode("reset")+"\n"+
				ansi.ColorCode("black+h")+
				strings.Repeat("~", len(fileNameDisplay))+
				ansi.ColorCode("reset")+"\n",
			)...,
		)

		boundaries := fileProfile.Boundaries(fileBytes)
		curOffset := 0
		for _, boundary := range boundaries {
			if boundary.Offset > curOffset {
				result = append(result, fileBytes[curOffset:boundary.Offset]...)
			}
			switch {
			case boundary.Start && boundary.Count > 0:
				result = append(result, []byte(ansi.ColorCode("green"))...)
			case boundary.Start && boundary.Count == 0:
				result = append(result, []byte(ansi.ColorCode("red"))...)
			case !boundary.Start:
				result = append(result, []byte(ansi.ColorCode("reset"))...)
			}

			curOffset = boundary.Offset
		}
		if curOffset < len(fileBytes) {
			result = append(result, fileBytes[curOffset:len(fileBytes)]...)
		}
		result = append(result, []byte("\n")...)
	}

	return result, err
}

func getTempFileName() string {
	tmpFile, err := ioutil.TempFile(".", "go-carpet-coverage-out-")
	if err != nil {
		log.Fatal(err)
	}
	tmpFile.Close()

	return tmpFile.Name()
}

func main() {
	flag.Usage = func() {
		fmt.Println(usageMessage)
		flag.PrintDefaults()
		os.Exit(0)
	}
	flag.Parse()
	testDirs := flag.Args()

	coverFileName := getTempFileName()
	defer os.RemoveAll(coverFileName)
	stdOut := getColorWriter()

	if len(testDirs) > 0 {
		testDirs = getDirsWithTests(testDirs...)
	} else {
		testDirs = getDirsWithTests(".")
	}
	for _, path := range testDirs {
		coverInBytes, err := getCoverForDir(path, coverFileName)
		if err != nil {
			log.Print(err)
		}
		stdOut.Write(coverInBytes)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"image"
	"io/ioutil"
	"net/http"
	"net/url"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

// When all else fails, Google it. Uses the regular web interface. There are
// two image search APIs, but one is deprecated and doesn't support exact size
// matching, and the other requires an API key limited to 100 searches a day.
const googleSearchFormat = `https://www.google.com.br/search?tbs=isz%%3Aex%%2Ciszw%%3A%v%%2Ciszh%%3A%v&tbm=isch&num=5&q=`

// Possible Google result formats
var googleSearchResultPatterns = []string{`imgurl=(.+?\.(jpeg|jpg|png))&imgrefurl=`, `\"ou\":\"(.+?)\",\"`}

// Returns the first steam grid image URL found by Google search of a given
// game name.
func getGoogleImage(gameName string, artStyleExtensions []string) (string, error) {
	if gameName == "" {
		return "", nil
	}

	url := fmt.Sprintf(googleSearchFormat, artStyleExtensions[5], artStyleExtensions[6]) + url.QueryEscape(gameName)

	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	// If we don't set an user agent, Google will block us because we are a
	// bot. If we set something like "SteamGrid Image Search" it'll work, but
	// Google will serve a simple HTML page without direct image links.
	// So we have to lie.
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36")
	response, err := client.Do(req)
	if err != nil {
		return "", err
	}

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

	for _, googleSearchResultPattern := range googleSearchResultPatterns {
		pattern := regexp.MustCompile(googleSearchResultPattern)
		matches := pattern.FindStringSubmatch(string(responseBytes))

		if len(matches) >= 1 {
			return matches[1], nil
		}
	}
	return "", nil
}

// https://www.steamgriddb.com/api/v2
type SteamGridDBResponse struct {
	Success bool
	Data []struct {
		Id int
		Score int
		Style string
		Url string
		Thumb string
		Tags []string
		Author struct {
			Name string
			Steam64 string
			Avatar string
		}
	}
}

type SteamGridDBSearchResponse struct {
	Success bool
	Data []struct {
		Id int
		Name string
		Types []string
		Verified bool
	}
}

// Search SteamGridDB for cover image
const SteamGridDBBaseURL = "https://www.steamgriddb.com/api/v2"

func SteamGridDBGetRequest(url string, steamGridDBApiKey string) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Authorization", "Bearer " + steamGridDBApiKey)

	response, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	if response.StatusCode == 401 {
		// Authorization token is missing or invalid
		return nil, errors.New("401")
	} else if response.StatusCode == 404 {
		// Could not find game with that id
		return nil, errors.New("404")
	}

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

	return responseBytes, nil
}

func getSteamGridDBImage(game *Game, artStyleExtensions []string, steamGridDBApiKey string, steamGridFilter string) (string, error) {
	// Try for HQ, then for LQ
	// It's possible to request both dimensions in one go but that'll give us scrambled results with no indicator which result has which size.
	for i := 0; i < 3; i += 2 {
		filter := steamGridFilter + "&dimensions=" + artStyleExtensions[3 + i] + "x" + artStyleExtensions[4 + i]

		// Try with game.ID which is probably steams appID
		var baseUrl string
		switch artStyleExtensions[1] {
		case ".banner":
			baseUrl = SteamGridDBBaseURL + "/grids"
		case ".cover":
			baseUrl = SteamGridDBBaseURL + "/grids"
		case ".hero":
			baseUrl = SteamGridDBBaseURL + "/heroes"
		case ".logo":
			// not yet supported
			return "", nil
		}
		url := baseUrl + "/steam/" + game.ID + filter

		var jsonResponse SteamGridDBResponse
		var responseBytes []byte
		var err error

		// Skip requests with appID for custom games
		if !game.Custom {
			responseBytes, err = SteamGridDBGetRequest(url, steamGridDBApiKey)
		} else {
			err = errors.New("404")
		}

		// Authorization token is missing or invalid
	 	if err != nil && err.Error() == "401" {
			return "", errors.New("SteamGridDB authorization token is missing or invalid")
		// Could not find game with that id
		} else if err != nil && err.Error() == "404" {
			// Try searching for the name…
			url = SteamGridDBBaseURL + "/search/autocomplete/" + game.Name + filter
			responseBytes, err = SteamGridDBGetRequest(url, steamGridDBApiKey)
			if err != nil && err.Error() == "401" {
				return "", errors.New("SteamGridDB authorization token is missing or invalid")
			} else if err != nil {
				return "", err
			}

			var jsonSearchResponse SteamGridDBSearchResponse
			err = json.Unmarshal(responseBytes, &jsonSearchResponse)
			if err != nil {
				return "", errors.New("Best search match doesn't has a requested type or style")
			}

			SteamGridDBGameId := -1
			if jsonSearchResponse.Success && len(jsonSearchResponse.Data) >= 1 {
				// First match should be the best one
				SteamGridDBGameId = jsonSearchResponse.Data[0].Id
			}

			if SteamGridDBGameId == -1 {
				return "", nil
			}

			// …and get the url of the top result.
			url = baseUrl + "/game/" + strconv.Itoa(SteamGridDBGameId) + filter
			responseBytes, err = SteamGridDBGetRequest(url, steamGridDBApiKey)
			if err != nil {
				return "", err
			}
		} else if err != nil {
			return "", err
		}

		err = json.Unmarshal(responseBytes, &jsonResponse)
		if err != nil {
			return "", err
		}

		if jsonResponse.Success && len(jsonResponse.Data) >= 1 {
			return jsonResponse.Data[0].Url, nil
		}
	}

	return "", nil
}

const IGDBImageURL = "https://images.igdb.com/igdb/image/upload/t_720p/%v.jpg"
const IGDBGameURL = "https://api-v3.igdb.com/games"
const IGDBCoverURL = "https://api-v3.igdb.com/covers"
const IGDBGameBody = `fields name,cover; search "%v";`
const IGDBCoverBody = `fields image_id; where id = %v;`

type IGDBGame struct {
	Id int
	Cover int
	Name string
}

type IGDBCover struct {
	Id int
	Image_id string
}

func IGDBPostRequest(url string, body string, IGDBApiKey string) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, strings.NewReader(body))
	req.Header.Add("user-key", IGDBApiKey)
	req.Header.Add("Accept", "application/json")
	if err != nil {
		return nil, err
	}

	response, err := client.Do(req)
	if err != nil {
		return nil, err
	}

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

	return responseBytes, nil
}

func getIGDBImage(gameName string, IGDBApiKey string) (string, error) {
	responseBytes, err := IGDBPostRequest(IGDBGameURL, fmt.Sprintf(IGDBGameBody, gameName), IGDBApiKey)
	if err != nil {
		return "", err
	}

	var jsonGameResponse []IGDBGame
	err = json.Unmarshal(responseBytes, &jsonGameResponse)
	if err != nil {
		return "", nil
	}

	if len(jsonGameResponse) < 1 || jsonGameResponse[0].Cover == 0 {
		return "", nil
	}

	responseBytes, err = IGDBPostRequest(IGDBCoverURL, fmt.Sprintf(IGDBCoverBody, jsonGameResponse[0].Cover), IGDBApiKey)
	if err != nil {
		return "", err
	}

	var jsonCoverResponse []IGDBCover
	err = json.Unmarshal(responseBytes, &jsonCoverResponse)
	if err != nil {
		return "", nil
	}

	if len(jsonCoverResponse) >= 1 {
		return fmt.Sprintf(IGDBImageURL, jsonCoverResponse[0].Image_id), nil
	}

	return "", 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/`

// The subreddit mentions this as primary, but I've found Akamai to contain
// more images and answer faster.
const steamCdnURLFormat = `cdn.akamai.steamstatic.com/steam/apps/%v/`

// 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
// from a Google search (useful because we want to log the lower quality
// images).
func getImageAlternatives(game *Game, artStyle string, artStyleExtensions []string, skipSteam bool, steamGridDBApiKey string, steamGridFilter string, IGDBApiKey string, skipGoogle bool) (response *http.Response, from string, err error) {
	from = "steam server"
	if !skipSteam {
		response, err = tryDownload(fmt.Sprintf(akamaiURLFormat + artStyleExtensions[2], game.ID))
		if err == nil && response != nil {
			return
		}

		response, err = tryDownload(fmt.Sprintf(steamCdnURLFormat + artStyleExtensions[2], game.ID))
		if err == nil && response != nil {
			return
		}
	}

	url := ""
	if steamGridDBApiKey != "" && url == "" {
		from = "SteamGridDB"
		url, err = getSteamGridDBImage(game, artStyleExtensions, steamGridDBApiKey, steamGridFilter)
		if err != nil {
			return
		}
	}

	// IGDB has mostly cover styles
	if artStyle == "Cover" && IGDBApiKey != "" && url == "" {
		from = "IGDB"
		url, err = getIGDBImage(game.Name, IGDBApiKey)
		if err != nil {
			return
		}
	}

	// Skip for Covers, bad results
	if !skipGoogle && artStyle == "Banner" && url == "" {
		from = "search"
		url, err = getGoogleImage(game.Name, artStyleExtensions)
		if err != nil {
			return
		}
	}

	response, err = tryDownload(url)
	if err == nil && response != nil {
		return
	}

	return nil, "", nil
}

// DownloadImage tries to download the game images, saving it in game.ImageBytes. Returns
// flags indicating if the operation succeeded and if the image downloaded was
// from a search.
func DownloadImage(gridDir string, game *Game, artStyle string, artStyleExtensions []string, skipSteam bool, steamGridDBApiKey string, steamGridFilter string, IGDBApiKey string, skipGoogle bool) (string, error) {
	response, from, err := getImageAlternatives(game, artStyle, artStyleExtensions, skipSteam, steamGridDBApiKey, steamGridFilter, IGDBApiKey, skipGoogle)
	if response == nil || err != nil {
		return "", err
	}

	contentType := response.Header.Get("Content-Type")
	urlExt := filepath.Ext(response.Request.URL.Path)
	if contentType != "" {
		game.ImageExt = "." + strings.Split(contentType, "/")[1]
	} else if urlExt != "" {
		game.ImageExt = urlExt
	} else {
		// Steam is forgiving on image extensions.
		game.ImageExt = "jpg"
	}

	if game.ImageExt == ".jpeg" {
		// The new library ignores .jpeg
		game.ImageExt = ".jpg"
	} else if game.ImageExt == ".octet-stream" {
		// Amazonaws (steamgriddb) gives us an .octet-stream
		game.ImageExt = ".png"
	}

	imageBytes, err := ioutil.ReadAll(response.Body)
	response.Body.Close()

	// catch false aspect ratios
	image, _, err := image.Decode(bytes.NewBuffer(imageBytes))
	if err != nil {
		return "", err
	}
	imageSize := image.Bounds().Max
	if (artStyle == "Banner" && imageSize.X < imageSize.Y) {
		return "", nil
	} else if (artStyle == "Cover" && imageSize.X > imageSize.Y) {
		return "", nil
	}

	game.ImageSource = from;

	game.CleanImageBytes = imageBytes
	return from, nil
}

// Get game name from SteamDB as last resort.
const steamDBFormat = `https://steamdb.info/app/%v`

func GetGameName(gameId string) string {
	response, err := tryDownload(fmt.Sprintf(steamDBFormat, gameId))
	if err != nil || response == nil {
		return ""
	}
	page, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return ""
	}
	response.Body.Close()

	pattern := regexp.MustCompile("
\nName	\\s*(.*?)	")
	match := pattern.FindStringSubmatch(string(page))
	if match == nil || len(match) == 0 {
		return ""
	} else {
		return match[1]
	}
}
<commit_msg>SteamGridDB now supports logos<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"image"
	"io/ioutil"
	"net/http"
	"net/url"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

// When all else fails, Google it. Uses the regular web interface. There are
// two image search APIs, but one is deprecated and doesn't support exact size
// matching, and the other requires an API key limited to 100 searches a day.
const googleSearchFormat = `https://www.google.com.br/search?tbs=isz%%3Aex%%2Ciszw%%3A%v%%2Ciszh%%3A%v&tbm=isch&num=5&q=`

// Possible Google result formats
var googleSearchResultPatterns = []string{`imgurl=(.+?\.(jpeg|jpg|png))&imgrefurl=`, `\"ou\":\"(.+?)\",\"`}

// Returns the first steam grid image URL found by Google search of a given
// game name.
func getGoogleImage(gameName string, artStyleExtensions []string) (string, error) {
	if gameName == "" {
		return "", nil
	}

	url := fmt.Sprintf(googleSearchFormat, artStyleExtensions[5], artStyleExtensions[6]) + url.QueryEscape(gameName)

	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return "", err
	}

	// If we don't set an user agent, Google will block us because we are a
	// bot. If we set something like "SteamGrid Image Search" it'll work, but
	// Google will serve a simple HTML page without direct image links.
	// So we have to lie.
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36")
	response, err := client.Do(req)
	if err != nil {
		return "", err
	}

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

	for _, googleSearchResultPattern := range googleSearchResultPatterns {
		pattern := regexp.MustCompile(googleSearchResultPattern)
		matches := pattern.FindStringSubmatch(string(responseBytes))

		if len(matches) >= 1 {
			return matches[1], nil
		}
	}
	return "", nil
}

// https://www.steamgriddb.com/api/v2
type SteamGridDBResponse struct {
	Success bool
	Data []struct {
		Id int
		Score int
		Style string
		Url string
		Thumb string
		Tags []string
		Author struct {
			Name string
			Steam64 string
			Avatar string
		}
	}
}

type SteamGridDBSearchResponse struct {
	Success bool
	Data []struct {
		Id int
		Name string
		Types []string
		Verified bool
	}
}

// Search SteamGridDB for cover image
const SteamGridDBBaseURL = "https://www.steamgriddb.com/api/v2"

func SteamGridDBGetRequest(url string, steamGridDBApiKey string) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("Authorization", "Bearer " + steamGridDBApiKey)

	response, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	if response.StatusCode == 401 {
		// Authorization token is missing or invalid
		return nil, errors.New("401")
	} else if response.StatusCode == 404 {
		// Could not find game with that id
		return nil, errors.New("404")
	}

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

	return responseBytes, nil
}

func getSteamGridDBImage(game *Game, artStyleExtensions []string, steamGridDBApiKey string, steamGridFilter string) (string, error) {
	// Try for HQ, then for LQ
	// It's possible to request both dimensions in one go but that'll give us scrambled results with no indicator which result has which size.
	for i := 0; i < 3; i += 2 {
		filter := steamGridFilter + "&dimensions=" + artStyleExtensions[3 + i] + "x" + artStyleExtensions[4 + i]

		// Try with game.ID which is probably steams appID
		var baseUrl string
		switch artStyleExtensions[1] {
		case ".banner":
			baseUrl = SteamGridDBBaseURL + "/grids"
		case ".cover":
			baseUrl = SteamGridDBBaseURL + "/grids"
		case ".hero":
			baseUrl = SteamGridDBBaseURL + "/heroes"
		case ".logo":
			baseUrl = SteamGridDBBaseURL + "/logos"
		}
		url := baseUrl + "/steam/" + game.ID + filter

		var jsonResponse SteamGridDBResponse
		var responseBytes []byte
		var err error

		// Skip requests with appID for custom games
		if !game.Custom {
			responseBytes, err = SteamGridDBGetRequest(url, steamGridDBApiKey)
		} else {
			err = errors.New("404")
		}

		// Authorization token is missing or invalid
	 	if err != nil && err.Error() == "401" {
			return "", errors.New("SteamGridDB authorization token is missing or invalid")
		// Could not find game with that id
		} else if err != nil && err.Error() == "404" {
			// Try searching for the name…
			url = SteamGridDBBaseURL + "/search/autocomplete/" + game.Name + filter
			responseBytes, err = SteamGridDBGetRequest(url, steamGridDBApiKey)
			if err != nil && err.Error() == "401" {
				return "", errors.New("SteamGridDB authorization token is missing or invalid")
			} else if err != nil {
				return "", err
			}

			var jsonSearchResponse SteamGridDBSearchResponse
			err = json.Unmarshal(responseBytes, &jsonSearchResponse)
			if err != nil {
				return "", errors.New("Best search match doesn't has a requested type or style")
			}

			SteamGridDBGameId := -1
			if jsonSearchResponse.Success && len(jsonSearchResponse.Data) >= 1 {
				// First match should be the best one
				SteamGridDBGameId = jsonSearchResponse.Data[0].Id
			}

			if SteamGridDBGameId == -1 {
				return "", nil
			}

			// …and get the url of the top result.
			url = baseUrl + "/game/" + strconv.Itoa(SteamGridDBGameId) + filter
			responseBytes, err = SteamGridDBGetRequest(url, steamGridDBApiKey)
			if err != nil {
				return "", err
			}
		} else if err != nil {
			return "", err
		}

		err = json.Unmarshal(responseBytes, &jsonResponse)
		if err != nil {
			return "", err
		}

		if jsonResponse.Success && len(jsonResponse.Data) >= 1 {
			return jsonResponse.Data[0].Url, nil
		}
	}

	return "", nil
}

const IGDBImageURL = "https://images.igdb.com/igdb/image/upload/t_720p/%v.jpg"
const IGDBGameURL = "https://api-v3.igdb.com/games"
const IGDBCoverURL = "https://api-v3.igdb.com/covers"
const IGDBGameBody = `fields name,cover; search "%v";`
const IGDBCoverBody = `fields image_id; where id = %v;`

type IGDBGame struct {
	Id int
	Cover int
	Name string
}

type IGDBCover struct {
	Id int
	Image_id string
}

func IGDBPostRequest(url string, body string, IGDBApiKey string) ([]byte, error) {
	client := &http.Client{}
	req, err := http.NewRequest("POST", url, strings.NewReader(body))
	req.Header.Add("user-key", IGDBApiKey)
	req.Header.Add("Accept", "application/json")
	if err != nil {
		return nil, err
	}

	response, err := client.Do(req)
	if err != nil {
		return nil, err
	}

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

	return responseBytes, nil
}

func getIGDBImage(gameName string, IGDBApiKey string) (string, error) {
	responseBytes, err := IGDBPostRequest(IGDBGameURL, fmt.Sprintf(IGDBGameBody, gameName), IGDBApiKey)
	if err != nil {
		return "", err
	}

	var jsonGameResponse []IGDBGame
	err = json.Unmarshal(responseBytes, &jsonGameResponse)
	if err != nil {
		return "", nil
	}

	if len(jsonGameResponse) < 1 || jsonGameResponse[0].Cover == 0 {
		return "", nil
	}

	responseBytes, err = IGDBPostRequest(IGDBCoverURL, fmt.Sprintf(IGDBCoverBody, jsonGameResponse[0].Cover), IGDBApiKey)
	if err != nil {
		return "", err
	}

	var jsonCoverResponse []IGDBCover
	err = json.Unmarshal(responseBytes, &jsonCoverResponse)
	if err != nil {
		return "", nil
	}

	if len(jsonCoverResponse) >= 1 {
		return fmt.Sprintf(IGDBImageURL, jsonCoverResponse[0].Image_id), nil
	}

	return "", 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/`

// The subreddit mentions this as primary, but I've found Akamai to contain
// more images and answer faster.
const steamCdnURLFormat = `cdn.akamai.steamstatic.com/steam/apps/%v/`

// 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
// from a Google search (useful because we want to log the lower quality
// images).
func getImageAlternatives(game *Game, artStyle string, artStyleExtensions []string, skipSteam bool, steamGridDBApiKey string, steamGridFilter string, IGDBApiKey string, skipGoogle bool) (response *http.Response, from string, err error) {
	from = "steam server"
	if !skipSteam {
		response, err = tryDownload(fmt.Sprintf(akamaiURLFormat + artStyleExtensions[2], game.ID))
		if err == nil && response != nil {
			return
		}

		response, err = tryDownload(fmt.Sprintf(steamCdnURLFormat + artStyleExtensions[2], game.ID))
		if err == nil && response != nil {
			return
		}
	}

	url := ""
	if steamGridDBApiKey != "" && url == "" {
		from = "SteamGridDB"
		url, err = getSteamGridDBImage(game, artStyleExtensions, steamGridDBApiKey, steamGridFilter)
		if err != nil {
			return
		}
	}

	// IGDB has mostly cover styles
	if artStyle == "Cover" && IGDBApiKey != "" && url == "" {
		from = "IGDB"
		url, err = getIGDBImage(game.Name, IGDBApiKey)
		if err != nil {
			return
		}
	}

	// Skip for Covers, bad results
	if !skipGoogle && artStyle == "Banner" && url == "" {
		from = "search"
		url, err = getGoogleImage(game.Name, artStyleExtensions)
		if err != nil {
			return
		}
	}

	response, err = tryDownload(url)
	if err == nil && response != nil {
		return
	}

	return nil, "", nil
}

// DownloadImage tries to download the game images, saving it in game.ImageBytes. Returns
// flags indicating if the operation succeeded and if the image downloaded was
// from a search.
func DownloadImage(gridDir string, game *Game, artStyle string, artStyleExtensions []string, skipSteam bool, steamGridDBApiKey string, steamGridFilter string, IGDBApiKey string, skipGoogle bool) (string, error) {
	response, from, err := getImageAlternatives(game, artStyle, artStyleExtensions, skipSteam, steamGridDBApiKey, steamGridFilter, IGDBApiKey, skipGoogle)
	if response == nil || err != nil {
		return "", err
	}

	contentType := response.Header.Get("Content-Type")
	urlExt := filepath.Ext(response.Request.URL.Path)
	if contentType != "" {
		game.ImageExt = "." + strings.Split(contentType, "/")[1]
	} else if urlExt != "" {
		game.ImageExt = urlExt
	} else {
		// Steam is forgiving on image extensions.
		game.ImageExt = "jpg"
	}

	if game.ImageExt == ".jpeg" {
		// The new library ignores .jpeg
		game.ImageExt = ".jpg"
	} else if game.ImageExt == ".octet-stream" {
		// Amazonaws (steamgriddb) gives us an .octet-stream
		game.ImageExt = ".png"
	}

	imageBytes, err := ioutil.ReadAll(response.Body)
	response.Body.Close()

	// catch false aspect ratios
	image, _, err := image.Decode(bytes.NewBuffer(imageBytes))
	if err != nil {
		return "", err
	}
	imageSize := image.Bounds().Max
	if (artStyle == "Banner" && imageSize.X < imageSize.Y) {
		return "", nil
	} else if (artStyle == "Cover" && imageSize.X > imageSize.Y) {
		return "", nil
	}

	game.ImageSource = from;

	game.CleanImageBytes = imageBytes
	return from, nil
}

// Get game name from SteamDB as last resort.
const steamDBFormat = `https://steamdb.info/app/%v`

func GetGameName(gameId string) string {
	response, err := tryDownload(fmt.Sprintf(steamDBFormat, gameId))
	if err != nil || response == nil {
		return ""
	}
	page, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return ""
	}
	response.Body.Close()

	pattern := regexp.MustCompile("
\nName	\\s*(.*?)	")
	match := pattern.FindStringSubmatch(string(page))
	if match == nil || len(match) == 0 {
		return ""
	} else {
		return match[1]
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"strings"
	"sync"

	"git.astuart.co/andrew/nzb"
	"git.astuart.co/andrew/yenc"
)

func Download(nz *nzb.NZB, dir string) error {
	files := &sync.WaitGroup{}
	files.Add(len(nz.Files))

	var err error

	for n := range nz.Files {
		file := nz.Files[n]
		err = use.JoinGroup(file.Groups[0])
		if err != nil {
			return fmt.Errorf("Error joining group: %v", err)
		}

		fileSegs := &sync.WaitGroup{}
		fileSegs.Add(len(file.Segments))

		fileBufs := make([]*bytes.Buffer, len(file.Segments))

		//Write to disk
		go func() {
			fileSegs.Wait()

			nameParts := strings.Split(file.Subject, "\"")
			fName := strings.Replace(nameParts[1], "/", "-", -1)

			fName = path.Clean(fmt.Sprintf("%s/%s/%s", dir, nz.Meta["name"], fName))

			os.MkdirAll(path.Dir(fName), 0775)

			if err != nil {
				files.Done()
				return
			}

			var toFile *os.File
			toFile, err = os.Create(fName)

			if err != nil {
				files.Done()
				return
			}

			for i := range fileBufs {
				_, err = io.Copy(toFile, fileBufs[i])
			}

			files.Done()
		}()

		//Get from network
		for i := range file.Segments {
			fileBufs[i] = &bytes.Buffer{}

			go func(i int) {
				seg := file.Segments[i]
				art, err := use.GetArticle(seg.Id)

				if err != nil {
					log.Printf("error getting file: %v", err)
					fileSegs.Done()
					return
				}

				var r io.Reader

				if strings.Contains(file.Subject, "yEnc") {
					r = yenc.NewReader(art.Body)
				} else {
					r = art.Body
				}

				_, err = io.Copy(fileBufs[i], r)

				if err != nil {
					log.Printf("There was an error: %v\n", err)
				}

				fileSegs.Done()
			}(i)
		}
	}

	files.Wait()

	return err
}
<commit_msg>Add rateio limiter<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"strings"
	"sync"
	"time"

	"git.astuart.co/andrew/nzb"
	"git.astuart.co/andrew/yenc"
	"github.com/shazow/rateio"
)

func Download(nz *nzb.NZB, dir string) error {
	files := &sync.WaitGroup{}
	files.Add(len(nz.Files))

	var err error

	for n := range nz.Files {
		file := nz.Files[n]
		err = use.JoinGroup(file.Groups[0])
		if err != nil {
			return fmt.Errorf("Error joining group: %v", err)
		}

		fileSegs := &sync.WaitGroup{}
		fileSegs.Add(len(file.Segments))

		fileBufs := make([]*bytes.Buffer, len(file.Segments))

		//Write to disk
		go func() {
			fileSegs.Wait()

			nameParts := strings.Split(file.Subject, "\"")
			fName := strings.Replace(nameParts[1], "/", "-", -1)

			fName = path.Clean(fmt.Sprintf("%s/%s/%s", dir, nz.Meta["name"], fName))

			os.MkdirAll(path.Dir(fName), 0775)

			if err != nil {
				files.Done()
				return
			}

			var toFile *os.File
			toFile, err = os.Create(fName)

			if err != nil {
				files.Done()
				return
			}

			for i := range fileBufs {
				_, err = io.Copy(toFile, fileBufs[i])
			}

			files.Done()
		}()

		//Get from network
		for i := range file.Segments {
			fileBufs[i] = &bytes.Buffer{}

			go func(i int) {
				seg := file.Segments[i]
				art, err := use.GetArticle(seg.Id)

				if err != nil {
					log.Printf("error getting file: %v", err)
					fileSegs.Done()
					return
				}

				r := rateio.NewReader(art.Body, rateio.NewSimpleLimiter(1<<20, 1*time.Second))

				if strings.Contains(file.Subject, "yEnc") {
					r = yenc.NewReader(r)
				}

				_, err = io.Copy(fileBufs[i], r)

				if err != nil {
					log.Printf("There was an error: %v\n", err)
				}

				fileSegs.Done()
			}(i)
		}
	}

	files.Wait()

	return err
}
<|endoftext|>


<commit_before>package gode

import (
	"io/ioutil"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
)

const registry = "https://cli-npm.heroku.com"

var rootPath string
var lockPath string
var nodePath string
var npmPath string

func SetRootPath(root string) {
	rootPath = root
	base := filepath.Join(root, getLatestInstalledNode())
	nodePath = nodePathFromBase(base)
	npmPath = npmPathFromBase(base)
	lockPath = filepath.Join(rootPath, "node.lock")
}

func getLatestInstalledNode() string {
	nodes := getNodeInstalls()
	if len(nodes) == 0 {
		return ""
	}
	return nodes[len(nodes)-1]
}

func getNodeInstalls() []string {
	nodes := []string{}
	files, _ := ioutil.ReadDir(rootPath)
	for _, f := range files {
		name := f.Name()
		if strings.HasPrefix(name, "node-v") || strings.HasPrefix(name, "iojs-v") {
			nodes = append(nodes, name)
		}
	}
	sort.Strings(nodes)
	return nodes
}

func nodePathFromBase(base string) string {
	path := filepath.Join(base, "bin", "node")
	if runtime.GOOS == "windows" {
		return path + ".exe"
	}
	return path
}

func npmPathFromBase(base string) string {
	return filepath.Join(base, "lib", "node_modules", "npm", "cli.js")
}
<commit_msg>do not use ancient versions of node<commit_after>package gode

import (
	"io/ioutil"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
)

const registry = "https://cli-npm.heroku.com"

var rootPath string
var lockPath string
var nodePath string
var npmPath string

// SetRootPath sets the root for gode
func SetRootPath(root string) {
	rootPath = root
	base := filepath.Join(root, getLatestInstalledNode())
	nodePath = nodePathFromBase(base)
	npmPath = npmPathFromBase(base)
	lockPath = filepath.Join(rootPath, "node.lock")
}

func getLatestInstalledNode() string {
	nodes := getNodeInstalls()
	if len(nodes) == 0 {
		return ""
	}
	latest := nodes[len(nodes)-1]
	// ignore ancient versions
	if strings.HasPrefix(latest, "node-v0") || strings.HasPrefix(latest, "iojs-v") {
		return ""
	}
	return latest
}

func getNodeInstalls() []string {
	nodes := []string{}
	files, _ := ioutil.ReadDir(rootPath)
	for _, f := range files {
		name := f.Name()
		if f.IsDir() && (strings.HasPrefix(name, "node-v") || strings.HasPrefix(name, "iojs-v")) {
			nodes = append(nodes, name)
		}
	}
	sort.Strings(nodes)
	return nodes
}

func nodePathFromBase(base string) string {
	path := filepath.Join(base, "bin", "node")
	if runtime.GOOS == "windows" {
		return path + ".exe"
	}
	return path
}

func npmPathFromBase(base string) string {
	return filepath.Join(base, "lib", "node_modules", "npm", "cli.js")
}
<|endoftext|>


<commit_before>package ethui

import (
	"bytes"
	"fmt"
	"github.com/ethereum/eth-go"
	"github.com/ethereum/eth-go/ethchain"
	"github.com/ethereum/eth-go/ethdb"
	"github.com/ethereum/eth-go/ethpub"
	"github.com/ethereum/eth-go/ethutil"
	"github.com/ethereum/go-ethereum/utils"
	"github.com/go-qml/qml"
	"math/big"
	"strings"
)

type Gui struct {
	// The main application window
	win *qml.Window
	// QML Engine
	engine    *qml.Engine
	component *qml.Common
	// The ethereum interface
	eth *eth.Ethereum

	// The public Ethereum library
	lib   *EthLib
	uiLib *UiLib

	txDb *ethdb.LDBDatabase

	addr []byte

	pub *ethpub.PEthereum
}

// Create GUI, but doesn't start it
func New(ethereum *eth.Ethereum) *Gui {
	lib := &EthLib{stateManager: ethereum.StateManager(), blockChain: ethereum.BlockChain(), txPool: ethereum.TxPool()}
	db, err := ethdb.NewLDBDatabase("tx_database")
	if err != nil {
		panic(err)
	}

	// On first run we won't have any keys yet, so this would crash.
	// Therefor we check if we are ready to actually start this process
	var addr []byte
	if ethutil.GetKeyRing().Len() != 0 {
		addr = ethutil.GetKeyRing().Get(0).Address()
	}

	pub := ethpub.NewPEthereum(ethereum)

	return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr, pub: pub}
}

func (gui *Gui) Start(assetPath string) {
	const version = "0.5.0 RC11"

	defer gui.txDb.Close()

	// Register ethereum functions
	qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{
		Init: func(p *ethpub.PBlock, obj qml.Object) { p.Number = 0; p.Hash = "" },
	}, {
		Init: func(p *ethpub.PTx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" },
	}})

	ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", version))

	// Create a new QML engine
	gui.engine = qml.NewEngine()
	context := gui.engine.Context()

	// Expose the eth library and the ui library to QML
	context.SetVar("eth", gui)
	context.SetVar("pub", gui.pub)
	gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath)
	context.SetVar("ui", gui.uiLib)

	// Load the main QML interface
	data, _ := ethutil.Config.Db.Get([]byte("KeyRing"))

	var win *qml.Window
	var err error
	if len(data) == 0 {
		win, err = gui.showKeyImport(context)
	} else {
		win, err = gui.showWallet(context)
	}
	if err != nil {
		ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'")

		panic(err)
	}

	win.Show()

	ethutil.Config.Log.AddLogSystem(gui)
	ethutil.Config.Log.Infoln("[GUI] Starting GUI")

	win.Wait()

	gui.eth.Stop()
}

func (gui *Gui) ToggleMining() {
	var txt string
	if gui.eth.Mining {
		utils.StopMining(gui.eth)
		txt = "Start mining"
	} else {
		utils.StartMining(gui.eth)
		txt = "Stop mining"
	}

	gui.win.Root().Set("miningButtonText", txt)
}

func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) {
	component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/wallet.qml"))
	if err != nil {
		return nil, err
	}

	win := gui.createWindow(component)

	gui.setInitialBlockChain()
	gui.loadAddressBook()
	gui.readPreviousTransactions()
	gui.setPeerInfo()

	go gui.update()

	return win, nil
}

func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) {
	context.SetVar("lib", gui.lib)
	component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/first_run.qml"))
	if err != nil {
		return nil, err
	}

	return gui.createWindow(component), nil
}

func (gui *Gui) createWindow(comp qml.Object) *qml.Window {
	win := comp.CreateWindow(nil)

	gui.win = win
	gui.uiLib.win = win

	db := &Debugger{gui.win, make(chan bool), make(chan bool), true}
	gui.lib.Db = db
	gui.uiLib.Db = db

	return gui.win
}
func (gui *Gui) setInitialBlockChain() {
	sBlk := gui.eth.BlockChain().LastBlockHash
	blk := gui.eth.BlockChain().GetBlock(sBlk)
	for ; blk != nil; blk = gui.eth.BlockChain().GetBlock(sBlk) {
		sBlk = blk.PrevHash
		gui.processBlock(blk, true)
	}
}

type address struct {
	Name, Address string
}

var namereg = ethutil.FromHex("bb5f186604d057c1c5240ca2ae0f6430138ac010")

func (gui *Gui) loadAddressBook() {
	gui.win.Root().Call("clearAddress")
	gui.eth.StateManager().CurrentState().GetStateObject(namereg).State().EachStorage(func(name string, value *ethutil.Value) {
		gui.win.Root().Call("addAddress", struct{ Name, Address string }{name, ethutil.Hex(value.Bytes())})
	})
}

func (gui *Gui) readPreviousTransactions() {
	it := gui.txDb.Db().NewIterator(nil, nil)
	for it.Next() {
		tx := ethchain.NewTransactionFromBytes(it.Value())

		var inout string
		if bytes.Compare(tx.Sender(), gui.addr) == 0 {
			inout = "send"
		} else {
			inout = "recv"
		}

		gui.win.Root().Call("addTx", ethpub.NewPTx(tx), inout)

	}
	it.Release()
}

func (gui *Gui) processBlock(block *ethchain.Block, initial bool) {
	gui.win.Root().Call("addBlock", ethpub.NewPBlock(block), initial)
}

func (gui *Gui) setWalletValue(amount, unconfirmedFunds *big.Int) {
	var str string
	if unconfirmedFunds != nil {
		pos := "+"
		if unconfirmedFunds.Cmp(big.NewInt(0)) < 0 {
			pos = "-"
		}
		val := ethutil.CurrencyToString(new(big.Int).Abs(ethutil.BigCopy(unconfirmedFunds)))
		str = fmt.Sprintf("%v (%s %v)", ethutil.CurrencyToString(amount), pos, val)
	} else {
		str = fmt.Sprintf("%v", ethutil.CurrencyToString(amount))
	}

	gui.win.Root().Call("setWalletValue", str)
}

// Simple go routine function that updates the list of peers in the GUI
func (gui *Gui) update() {
	reactor := gui.eth.Reactor()

	blockChan := make(chan ethutil.React, 1)
	txChan := make(chan ethutil.React, 1)
	objectChan := make(chan ethutil.React, 1)
	peerChan := make(chan ethutil.React, 1)

	reactor.Subscribe("newBlock", blockChan)
	reactor.Subscribe("newTx:pre", txChan)
	reactor.Subscribe("newTx:post", txChan)
	reactor.Subscribe("object:"+string(namereg), objectChan)
	reactor.Subscribe("peerList", peerChan)

	state := gui.eth.StateManager().TransState()

	unconfirmedFunds := new(big.Int)
	gui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(state.GetAccount(gui.addr).Amount)))

	for {
		select {
		case b := <-blockChan:
			block := b.Resource.(*ethchain.Block)
			gui.processBlock(block, false)
			if bytes.Compare(block.Coinbase, gui.addr) == 0 {
				gui.setWalletValue(gui.eth.StateManager().CurrentState().GetAccount(gui.addr).Amount, nil)
			}

		case txMsg := <-txChan:
			tx := txMsg.Resource.(*ethchain.Transaction)

			if txMsg.Event == "newTx:pre" {
				object := state.GetAccount(gui.addr)

				if bytes.Compare(tx.Sender(), gui.addr) == 0 {
					gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "send")
					gui.txDb.Put(tx.Hash(), tx.RlpEncode())

					unconfirmedFunds.Sub(unconfirmedFunds, tx.Value)
				} else if bytes.Compare(tx.Recipient, gui.addr) == 0 {
					gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "recv")
					gui.txDb.Put(tx.Hash(), tx.RlpEncode())

					unconfirmedFunds.Add(unconfirmedFunds, tx.Value)
				}

				gui.setWalletValue(object.Amount, unconfirmedFunds)
			} else {
				object := state.GetAccount(gui.addr)
				if bytes.Compare(tx.Sender(), gui.addr) == 0 {
					object.SubAmount(tx.Value)
				} else if bytes.Compare(tx.Recipient, gui.addr) == 0 {
					object.AddAmount(tx.Value)
				}

				gui.setWalletValue(object.Amount, nil)

				state.UpdateStateObject(object)
			}
		case <-objectChan:
			gui.loadAddressBook()
		case <-peerChan:
			gui.setPeerInfo()
		}
	}
}

func (gui *Gui) setPeerInfo() {
	gui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", gui.eth.PeerCount(), gui.eth.MaxPeers))
}

// Logging functions that log directly to the GUI interface
func (gui *Gui) Println(v ...interface{}) {
	str := strings.TrimRight(fmt.Sprintln(v...), "\n")
	lines := strings.Split(str, "\n")
	for _, line := range lines {
		gui.win.Root().Call("addLog", line)
	}
}

func (gui *Gui) Printf(format string, v ...interface{}) {
	str := strings.TrimRight(fmt.Sprintf(format, v...), "\n")
	lines := strings.Split(str, "\n")
	for _, line := range lines {
		gui.win.Root().Call("addLog", line)
	}
}

func (gui *Gui) Transact(recipient, value, gas, gasPrice, data string) (*ethpub.PReceipt, error) {
	keyPair := ethutil.GetKeyRing().Get(0)

	return gui.pub.Transact(ethutil.Hex(keyPair.PrivateKey), recipient, value, gas, gasPrice, data)
}

func (gui *Gui) Create(recipient, value, gas, gasPrice, data string) (*ethpub.PReceipt, error) {
	keyPair := ethutil.GetKeyRing().Get(0)

	return gui.pub.Transact(ethutil.Hex(keyPair.PrivateKey), recipient, value, gas, gasPrice, data)
}

func (gui *Gui) ChangeClientId(id string) {
	ethutil.Config.SetIdentifier(id)
}

func (gui *Gui) ClientId() string {
	return ethutil.Config.Identifier
}
<commit_msg>fixed logging issue that would otherwise crash the client. Fixes #68<commit_after>package ethui

import (
	"bytes"
	"fmt"
	"github.com/ethereum/eth-go"
	"github.com/ethereum/eth-go/ethchain"
	"github.com/ethereum/eth-go/ethdb"
	"github.com/ethereum/eth-go/ethpub"
	"github.com/ethereum/eth-go/ethutil"
	"github.com/ethereum/go-ethereum/utils"
	"github.com/go-qml/qml"
	"math/big"
	"strings"
)

type Gui struct {
	// The main application window
	win *qml.Window
	// QML Engine
	engine    *qml.Engine
	component *qml.Common
	// The ethereum interface
	eth *eth.Ethereum

	// The public Ethereum library
	lib   *EthLib
	uiLib *UiLib

	txDb *ethdb.LDBDatabase

	addr []byte

	pub *ethpub.PEthereum
}

// Create GUI, but doesn't start it
func New(ethereum *eth.Ethereum) *Gui {
	lib := &EthLib{stateManager: ethereum.StateManager(), blockChain: ethereum.BlockChain(), txPool: ethereum.TxPool()}
	db, err := ethdb.NewLDBDatabase("tx_database")
	if err != nil {
		panic(err)
	}

	// On first run we won't have any keys yet, so this would crash.
	// Therefor we check if we are ready to actually start this process
	var addr []byte
	if ethutil.GetKeyRing().Len() != 0 {
		addr = ethutil.GetKeyRing().Get(0).Address()
	}

	pub := ethpub.NewPEthereum(ethereum)

	return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr, pub: pub}
}

func (gui *Gui) Start(assetPath string) {
	const version = "0.5.0 RC11"

	defer gui.txDb.Close()

	// Register ethereum functions
	qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{
		Init: func(p *ethpub.PBlock, obj qml.Object) { p.Number = 0; p.Hash = "" },
	}, {
		Init: func(p *ethpub.PTx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" },
	}})

	ethutil.Config.SetClientString(fmt.Sprintf("/Ethereal v%s", version))

	// Create a new QML engine
	gui.engine = qml.NewEngine()
	context := gui.engine.Context()

	// Expose the eth library and the ui library to QML
	context.SetVar("eth", gui)
	context.SetVar("pub", gui.pub)
	gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath)
	context.SetVar("ui", gui.uiLib)

	// Load the main QML interface
	data, _ := ethutil.Config.Db.Get([]byte("KeyRing"))

	var win *qml.Window
	var err error
	if len(data) == 0 {
		win, err = gui.showKeyImport(context)
	} else {
		win, err = gui.showWallet(context)

		ethutil.Config.Log.AddLogSystem(gui)
	}
	if err != nil {
		ethutil.Config.Log.Infoln("FATAL: asset not found: you can set an alternative asset path on on the command line using option 'asset_path'")

		panic(err)
	}

	ethutil.Config.Log.Infoln("[GUI] Starting GUI")

	win.Show()
	win.Wait()

	gui.eth.Stop()
}

func (gui *Gui) ToggleMining() {
	var txt string
	if gui.eth.Mining {
		utils.StopMining(gui.eth)
		txt = "Start mining"
	} else {
		utils.StartMining(gui.eth)
		txt = "Stop mining"
	}

	gui.win.Root().Set("miningButtonText", txt)
}

func (gui *Gui) showWallet(context *qml.Context) (*qml.Window, error) {
	component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/wallet.qml"))
	if err != nil {
		return nil, err
	}

	win := gui.createWindow(component)

	gui.setInitialBlockChain()
	gui.loadAddressBook()
	gui.readPreviousTransactions()
	gui.setPeerInfo()

	go gui.update()

	return win, nil
}

func (gui *Gui) showKeyImport(context *qml.Context) (*qml.Window, error) {
	context.SetVar("lib", gui.lib)
	component, err := gui.engine.LoadFile(gui.uiLib.AssetPath("qml/first_run.qml"))
	if err != nil {
		return nil, err
	}

	return gui.createWindow(component), nil
}

func (gui *Gui) createWindow(comp qml.Object) *qml.Window {
	win := comp.CreateWindow(nil)

	gui.win = win
	gui.uiLib.win = win

	db := &Debugger{gui.win, make(chan bool), make(chan bool), true}
	gui.lib.Db = db
	gui.uiLib.Db = db

	return gui.win
}
func (gui *Gui) setInitialBlockChain() {
	sBlk := gui.eth.BlockChain().LastBlockHash
	blk := gui.eth.BlockChain().GetBlock(sBlk)
	for ; blk != nil; blk = gui.eth.BlockChain().GetBlock(sBlk) {
		sBlk = blk.PrevHash
		gui.processBlock(blk, true)
	}
}

type address struct {
	Name, Address string
}

var namereg = ethutil.FromHex("bb5f186604d057c1c5240ca2ae0f6430138ac010")

func (gui *Gui) loadAddressBook() {
	gui.win.Root().Call("clearAddress")
	stateObject := gui.eth.StateManager().CurrentState().GetStateObject(namereg)
	if stateObject != nil {
		stateObject.State().EachStorage(func(name string, value *ethutil.Value) {
			gui.win.Root().Call("addAddress", struct{ Name, Address string }{name, ethutil.Hex(value.Bytes())})
		})
	}
}

func (gui *Gui) readPreviousTransactions() {
	it := gui.txDb.Db().NewIterator(nil, nil)
	for it.Next() {
		tx := ethchain.NewTransactionFromBytes(it.Value())

		var inout string
		if bytes.Compare(tx.Sender(), gui.addr) == 0 {
			inout = "send"
		} else {
			inout = "recv"
		}

		gui.win.Root().Call("addTx", ethpub.NewPTx(tx), inout)

	}
	it.Release()
}

func (gui *Gui) processBlock(block *ethchain.Block, initial bool) {
	gui.win.Root().Call("addBlock", ethpub.NewPBlock(block), initial)
}

func (gui *Gui) setWalletValue(amount, unconfirmedFunds *big.Int) {
	var str string
	if unconfirmedFunds != nil {
		pos := "+"
		if unconfirmedFunds.Cmp(big.NewInt(0)) < 0 {
			pos = "-"
		}
		val := ethutil.CurrencyToString(new(big.Int).Abs(ethutil.BigCopy(unconfirmedFunds)))
		str = fmt.Sprintf("%v (%s %v)", ethutil.CurrencyToString(amount), pos, val)
	} else {
		str = fmt.Sprintf("%v", ethutil.CurrencyToString(amount))
	}

	gui.win.Root().Call("setWalletValue", str)
}

// Simple go routine function that updates the list of peers in the GUI
func (gui *Gui) update() {
	reactor := gui.eth.Reactor()

	blockChan := make(chan ethutil.React, 1)
	txChan := make(chan ethutil.React, 1)
	objectChan := make(chan ethutil.React, 1)
	peerChan := make(chan ethutil.React, 1)

	reactor.Subscribe("newBlock", blockChan)
	reactor.Subscribe("newTx:pre", txChan)
	reactor.Subscribe("newTx:post", txChan)
	reactor.Subscribe("object:"+string(namereg), objectChan)
	reactor.Subscribe("peerList", peerChan)

	state := gui.eth.StateManager().TransState()

	unconfirmedFunds := new(big.Int)
	gui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(state.GetAccount(gui.addr).Amount)))

	for {
		select {
		case b := <-blockChan:
			block := b.Resource.(*ethchain.Block)
			gui.processBlock(block, false)
			if bytes.Compare(block.Coinbase, gui.addr) == 0 {
				gui.setWalletValue(gui.eth.StateManager().CurrentState().GetAccount(gui.addr).Amount, nil)
			}

		case txMsg := <-txChan:
			tx := txMsg.Resource.(*ethchain.Transaction)

			if txMsg.Event == "newTx:pre" {
				object := state.GetAccount(gui.addr)

				if bytes.Compare(tx.Sender(), gui.addr) == 0 {
					gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "send")
					gui.txDb.Put(tx.Hash(), tx.RlpEncode())

					unconfirmedFunds.Sub(unconfirmedFunds, tx.Value)
				} else if bytes.Compare(tx.Recipient, gui.addr) == 0 {
					gui.win.Root().Call("addTx", ethpub.NewPTx(tx), "recv")
					gui.txDb.Put(tx.Hash(), tx.RlpEncode())

					unconfirmedFunds.Add(unconfirmedFunds, tx.Value)
				}

				gui.setWalletValue(object.Amount, unconfirmedFunds)
			} else {
				object := state.GetAccount(gui.addr)
				if bytes.Compare(tx.Sender(), gui.addr) == 0 {
					object.SubAmount(tx.Value)
				} else if bytes.Compare(tx.Recipient, gui.addr) == 0 {
					object.AddAmount(tx.Value)
				}

				gui.setWalletValue(object.Amount, nil)

				state.UpdateStateObject(object)
			}
		case <-objectChan:
			gui.loadAddressBook()
		case <-peerChan:
			gui.setPeerInfo()
		}
	}
}

func (gui *Gui) setPeerInfo() {
	gui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", gui.eth.PeerCount(), gui.eth.MaxPeers))
}

// Logging functions that log directly to the GUI interface
func (gui *Gui) Println(v ...interface{}) {
	str := strings.TrimRight(fmt.Sprintln(v...), "\n")
	lines := strings.Split(str, "\n")
	for _, line := range lines {
		gui.win.Root().Call("addLog", line)
	}
}

func (gui *Gui) Printf(format string, v ...interface{}) {
	str := strings.TrimRight(fmt.Sprintf(format, v...), "\n")
	lines := strings.Split(str, "\n")
	for _, line := range lines {
		gui.win.Root().Call("addLog", line)
	}
}

func (gui *Gui) Transact(recipient, value, gas, gasPrice, data string) (*ethpub.PReceipt, error) {
	keyPair := ethutil.GetKeyRing().Get(0)

	return gui.pub.Transact(ethutil.Hex(keyPair.PrivateKey), recipient, value, gas, gasPrice, data)
}

func (gui *Gui) Create(recipient, value, gas, gasPrice, data string) (*ethpub.PReceipt, error) {
	keyPair := ethutil.GetKeyRing().Get(0)

	return gui.pub.Transact(ethutil.Hex(keyPair.PrivateKey), recipient, value, gas, gasPrice, data)
}

func (gui *Gui) ChangeClientId(id string) {
	ethutil.Config.SetIdentifier(id)
}

func (gui *Gui) ClientId() string {
	return ethutil.Config.Identifier
}
<|endoftext|>


<commit_before>/**
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * 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 (
	"os"

	commands "github.com/fabric8io/gofabric8/cmds"
	"github.com/fabric8io/gofabric8/util"
	"github.com/jimmidyson/minishift/pkg/minikube/update"
	"github.com/kubernetes/minikube/pkg/minikube/config"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

const (
	batchFlag = "batch"

	githubOrg  = "fabric8io"
	githubRepo = "gofabric8"
	binaryName = githubRepo
)

func runHelp(cmd *cobra.Command, args []string) {
	cmd.Help()
}

func main() {
	cmds := &cobra.Command{
		Use:   "gofabric8",
		Short: "gofabric8 is used to validate & deploy fabric8 components on to your Kubernetes or OpenShift environment",
		Long: `gofabric8 is used to validate & deploy fabric8 components on to your Kubernetes or OpenShift environment
								Find more information at http://fabric8.io.`,
		Run: runHelp,
	}

	cmds.PersistentFlags().String("fabric8-version", "latest", "fabric8 version")
	cmds.PersistentFlags().BoolP("yes", "y", false, "assume yes")
	cmds.PersistentFlags().BoolP(batchFlag, "b", false, "Run in batch mode to avoid prompts")

	f := cmdutil.NewFactory(nil)
	f.BindFlags(cmds.PersistentFlags())

	flag := cmds.Flags().Lookup(batchFlag)
	util.Infof("batch comand %v", flag)
	batch := false
	if flag != nil {
		batch = flag.Value.String() == "true"
	}

	if !batch {
		viper.SetDefault(config.WantUpdateNotification, true)
		update.MaybeUpdate(os.Stdout, githubOrg, githubRepo, binaryName, "")
	}

	cmds.AddCommand(commands.NewCmdValidate(f))
	cmds.AddCommand(commands.NewCmdDeploy(f))
	cmds.AddCommand(commands.NewCmdRun(f))
	cmds.AddCommand(commands.NewCmdPull(f))
	cmds.AddCommand(commands.NewCmdIngress(f))
	cmds.AddCommand(commands.NewCmdRoutes(f))
	cmds.AddCommand(commands.NewCmdSecrets(f))
	cmds.AddCommand(commands.NewCmdService(f))
	cmds.AddCommand(commands.NewCmdVolumes(f))
	cmds.AddCommand(commands.NewCmdVersion())

	cmds.Execute()
}
<commit_msg>fix up the handling of loading the batch flag<commit_after>/**
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * 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 (
	"os"

	commands "github.com/fabric8io/gofabric8/cmds"
	"github.com/fabric8io/gofabric8/util"
	"github.com/jimmidyson/minishift/pkg/minikube/update"
	"github.com/kubernetes/minikube/pkg/minikube/config"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

const (
	batchFlag = "batch"

	githubOrg  = "fabric8io"
	githubRepo = "gofabric8"
	binaryName = githubRepo
)

func runHelp(cmd *cobra.Command, args []string) {
	cmd.Help()
}

func main() {
	cmds := &cobra.Command{
		Use:   "gofabric8",
		Short: "gofabric8 is used to validate & deploy fabric8 components on to your Kubernetes or OpenShift environment",
		Long: `gofabric8 is used to validate & deploy fabric8 components on to your Kubernetes or OpenShift environment
								Find more information at http://fabric8.io.`,
		Run: runHelp,
	}

	cmds.PersistentFlags().String("fabric8-version", "latest", "fabric8 version")
	cmds.PersistentFlags().BoolP("yes", "y", false, "assume yes")
	cmds.PersistentFlags().BoolP(batchFlag, "b", false, "Run in batch mode to avoid prompts")

	f := cmdutil.NewFactory(nil)
	f.BindFlags(cmds.PersistentFlags())

	oldHandler := cmds.PersistentPreRun
	cmds.PersistentPreRun = func(cmd *cobra.Command, args []string) {
		flag := cmds.Flags().Lookup(batchFlag)
		batch := false
		if flag != nil {
			batch = flag.Value.String() == "true"
		}

		if !batch {
			viper.SetDefault(config.WantUpdateNotification, true)
			update.MaybeUpdate(os.Stdout, githubOrg, githubRepo, binaryName, "")

		}
		if oldHandler != nil {
			oldHandler(cmd, args)
		}
	}

	cmds.AddCommand(commands.NewCmdValidate(f))
	cmds.AddCommand(commands.NewCmdDeploy(f))
	cmds.AddCommand(commands.NewCmdRun(f))
	cmds.AddCommand(commands.NewCmdPull(f))
	cmds.AddCommand(commands.NewCmdIngress(f))
	cmds.AddCommand(commands.NewCmdRoutes(f))
	cmds.AddCommand(commands.NewCmdSecrets(f))
	cmds.AddCommand(commands.NewCmdService(f))
	cmds.AddCommand(commands.NewCmdVolumes(f))
	cmds.AddCommand(commands.NewCmdVersion())

	cmds.Execute()
}
<|endoftext|>