<commit_before>package dirble

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// Version is the library version
const Version = "0.1"

// APIBase is the root URL of the API
const APIBase = "http://api.dirble.com/v2/"

type Dirble struct {
	client *http.Client
	token  string
}

type Timestamped struct {
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

type Thumb struct {
	URL *string
}

type Image struct {
	URL   *string
	Thumb Thumb
}

type StationItem struct {
	ID                 int
	Name               string
	Description        string
	Country            string
	Accepted           int
	Added              time.Time
	Website            string
	CurrentSongReverse *string `json:"currentsong_reverse"`
	Image              Image
	Slug               string
	DisableSongChecks  bool
	Timestamped
}

type Stations []StationItem

type Stream struct {
	ID           int
	Stream       string
	Bitrate      int
	ContenType   string
	Status       int
	StationId    int `json:"Station_id"`
	Timedout     bool
	EmptyCounter int
	Timestamped
}

type Category struct {
	ID          int
	Title       string
	Description string
	URLId       interface{}
	Slug        string
	Ancestry    string
	Position    interface{}
	Timestamped
}

type StationSong struct {
	ID        string
	Name      string
	Title     string
	Week      int
	Year      int
	StationID int `json:"station_id"`
	Info      *string
	Date      time.Time
}

type StationSongs []StationSong
type Categories []Category

type Station struct {
	ID           int
	Name         string
	Accepted     int
	Added        time.Time
	Country      string
	Description  string
	Image        Image
	Slug         string
	Website      string
	Streams      []Stream
	Categories   Categories
	StationSongs StationSongs `json:"station_songs"`
}

type CategoryTree struct {
	Children Categories
	Category
}

type CategoryStations []Station

type Country struct {
	ID          int
	CountryCode string `json:"country_code"`
	ContinentID int    `json:"Continent_id"`
	Timestamped
}

type Countries []Country

type Continent struct {
	ID       int
	Name     string
	Slug     string
	LegacyID int `json:"legacy_id"`
	Timestamped
}

type Continents []Continent

type SearchResults []Station

func New(rt http.RoundTripper, token string) *Dirble {
	return &Dirble{
		client: &http.Client{
			Transport: rt,
		},
		token: token,
	}
}

func (d *Dirble) fetchURL(url string) ([]byte, error) {
	var err error
	var resp *http.Response
	if resp, err = d.client.Get(url); err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP Error: %d", resp.StatusCode)
	}
	return ioutil.ReadAll(resp.Body)
}

func (d *Dirble) getStations(url string) (*Stations, error) {
	var err error
	var content []byte
	if content, err = d.fetchURL(url); err != nil {
		return nil, err
	}
	var s Stations
	if err = json.Unmarshal(content, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

func (d *Dirble) Stations(page, perPage, offset *int) (*Stations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "stations"
	q := u.Query()
	q.Set("token", d.token)
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	return d.getStations(u.String())
}

func (d *Dirble) Station(id int) (*Station, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "station/" + strconv.Itoa(id)
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var s Station
	if err = json.Unmarshal(content, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

func (d *Dirble) StationSongHistory(id int) (*StationSongs, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "stations/" + strconv.Itoa(id) + "/song_history"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var ss StationSongs
	if err = json.Unmarshal(content, &ss); err != nil {
		return nil, err
	}
	return &ss, nil
}

func (d *Dirble) StationSimilar(id int) (*Stations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "stations/" + strconv.Itoa(id) + "/similar"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	return d.getStations(u.String())
}

func (d *Dirble) Categories() (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CategoriesPrimary() (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories/primary"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CategoriesTree() (*CategoryTree, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories/tree"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var ct CategoryTree
	if err = json.Unmarshal(content, &ct); err != nil {
		return nil, err
	}
	return &ct, nil
}

func (d *Dirble) CategoryStations(id int, all bool, page, perPage, offset *int) (*CategoryStations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "category/" + strconv.Itoa(id) + "/stations"
	q := u.Query()
	q.Set("token", d.token)
	if all {
		q.Set("all", "1")
	}
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var cs CategoryStations
	if err = json.Unmarshal(content, &cs); err != nil {
		return nil, err
	}
	return &cs, nil
}

func (d *Dirble) CategoryChilds(id int) (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "category/" + strconv.Itoa(id) + "/childs"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) Countries() (*Countries, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "countries"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Countries
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CountriesStations(code string, id int, all bool, page, perPage, offset *int) (*CategoryStations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "countries/" + code + "/stations"
	q := u.Query()
	q.Set("token", d.token)
	if all {
		q.Set("all", "1")
	}
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var cs CategoryStations
	if err = json.Unmarshal(content, &cs); err != nil {
		return nil, err
	}
	return &cs, nil
}

func (d *Dirble) Continents() (*Continents, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "continents"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Continents
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) ContinentsCountries(continentId int) (*Countries, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "continents/" + strconv.Itoa(continentId) + "/countries"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Countries
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) Search(query string, page *int) (*SearchResults, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "search/" + query
	q := u.Query()
	q.Set("token", d.token)

	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var sr SearchResults
	if err = json.Unmarshal(content, &sr); err != nil {
		return nil, err
	}
	return &sr, nil
}
<commit_msg>Removed extra param<commit_after>package dirble

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// Version is the library version
const Version = "0.1"

// APIBase is the root URL of the API
const APIBase = "http://api.dirble.com/v2/"

type Dirble struct {
	client *http.Client
	token  string
}

type Timestamped struct {
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

type Thumb struct {
	URL *string
}

type Image struct {
	URL   *string
	Thumb Thumb
}

type StationItem struct {
	ID                 int
	Name               string
	Description        string
	Country            string
	Accepted           int
	Added              time.Time
	Website            string
	CurrentSongReverse *string `json:"currentsong_reverse"`
	Image              Image
	Slug               string
	DisableSongChecks  bool
	Timestamped
}

type Stations []StationItem

type Stream struct {
	ID           int
	Stream       string
	Bitrate      int
	ContenType   string
	Status       int
	StationId    int `json:"Station_id"`
	Timedout     bool
	EmptyCounter int
	Timestamped
}

type Category struct {
	ID          int
	Title       string
	Description string
	URLId       interface{}
	Slug        string
	Ancestry    string
	Position    interface{}
	Timestamped
}

type StationSong struct {
	ID        string
	Name      string
	Title     string
	Week      int
	Year      int
	StationID int `json:"station_id"`
	Info      *string
	Date      time.Time
}

type StationSongs []StationSong
type Categories []Category

type Station struct {
	ID           int
	Name         string
	Accepted     int
	Added        time.Time
	Country      string
	Description  string
	Image        Image
	Slug         string
	Website      string
	Streams      []Stream
	Categories   Categories
	StationSongs StationSongs `json:"station_songs"`
}

type CategoryTree struct {
	Children Categories
	Category
}

type CategoryStations []Station

type Country struct {
	ID          int
	CountryCode string `json:"country_code"`
	ContinentID int    `json:"Continent_id"`
	Timestamped
}

type Countries []Country

type Continent struct {
	ID       int
	Name     string
	Slug     string
	LegacyID int `json:"legacy_id"`
	Timestamped
}

type Continents []Continent

type SearchResults []Station

func New(rt http.RoundTripper, token string) *Dirble {
	return &Dirble{
		client: &http.Client{
			Transport: rt,
		},
		token: token,
	}
}

func (d *Dirble) fetchURL(url string) ([]byte, error) {
	var err error
	var resp *http.Response
	if resp, err = d.client.Get(url); err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP Error: %d", resp.StatusCode)
	}
	return ioutil.ReadAll(resp.Body)
}

func (d *Dirble) getStations(url string) (*Stations, error) {
	var err error
	var content []byte
	if content, err = d.fetchURL(url); err != nil {
		return nil, err
	}
	var s Stations
	if err = json.Unmarshal(content, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

func (d *Dirble) Stations(page, perPage, offset *int) (*Stations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "stations"
	q := u.Query()
	q.Set("token", d.token)
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	return d.getStations(u.String())
}

func (d *Dirble) Station(id int) (*Station, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "station/" + strconv.Itoa(id)
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var s Station
	if err = json.Unmarshal(content, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

func (d *Dirble) StationSongHistory(id int) (*StationSongs, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "stations/" + strconv.Itoa(id) + "/song_history"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var ss StationSongs
	if err = json.Unmarshal(content, &ss); err != nil {
		return nil, err
	}
	return &ss, nil
}

func (d *Dirble) StationSimilar(id int) (*Stations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "stations/" + strconv.Itoa(id) + "/similar"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	return d.getStations(u.String())
}

func (d *Dirble) Categories() (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CategoriesPrimary() (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories/primary"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CategoriesTree() (*CategoryTree, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "categories/tree"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var ct CategoryTree
	if err = json.Unmarshal(content, &ct); err != nil {
		return nil, err
	}
	return &ct, nil
}

func (d *Dirble) CategoryStations(id int, all bool, page, perPage, offset *int) (*CategoryStations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "category/" + strconv.Itoa(id) + "/stations"
	q := u.Query()
	q.Set("token", d.token)
	if all {
		q.Set("all", "1")
	}
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var cs CategoryStations
	if err = json.Unmarshal(content, &cs); err != nil {
		return nil, err
	}
	return &cs, nil
}

func (d *Dirble) CategoryChilds(id int) (*Categories, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "category/" + strconv.Itoa(id) + "/childs"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Categories
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) Countries() (*Countries, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "countries"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Countries
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) CountriesStations(code string, all bool, page, perPage, offset *int) (*CategoryStations, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "countries/" + code + "/stations"
	q := u.Query()
	q.Set("token", d.token)
	if all {
		q.Set("all", "1")
	}
	if page != nil {
		q.Set("page", strconv.Itoa(*page))
	}
	if perPage != nil {
		q.Set("per_page", strconv.Itoa(*perPage))
	}
	if offset != nil {
		q.Set("offset", strconv.Itoa(*offset))
	}
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var cs CategoryStations
	if err = json.Unmarshal(content, &cs); err != nil {
		return nil, err
	}
	return &cs, nil
}

func (d *Dirble) Continents() (*Continents, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "continents"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Continents
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) ContinentsCountries(continentId int) (*Countries, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "continents/" + strconv.Itoa(continentId) + "/countries"
	q := u.Query()
	q.Set("token", d.token)
	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var c Countries
	if err = json.Unmarshal(content, &c); err != nil {
		return nil, err
	}
	return &c, nil
}

func (d *Dirble) Search(query string, page *int) (*SearchResults, error) {
	var err error
	var u *url.URL
	if u, err = url.Parse(APIBase); err != nil {
		return nil, err
	}
	u.Path += "search/" + query
	q := u.Query()
	q.Set("token", d.token)

	u.RawQuery = q.Encode()
	var content []byte
	if content, err = d.fetchURL(u.String()); err != nil {
		return nil, err
	}
	var sr SearchResults
	if err = json.Unmarshal(content, &sr); err != nil {
		return nil, err
	}
	return &sr, nil
}
<|endoftext|>


<commit_before>package dcpu

import (
	"errors"
	"github.com/kballard/dcpu16/dcpu/core"
	"github.com/kballard/termbox-go"
	"os"
	"strings"
)

// The display is 32x12 (128x96 pixels) surrounded by a
// 16 pixel border / background.
//
// We can't handle pixels, so use a 32x12 character display, with a border
// of one character.
const (
	windowWidth            = 32
	windowHeight           = 12
	characterRangeStart    = 0x0180
	miscRangeStart         = 0x0280
	backgroundColorAddress = 0x0280
)

const DefaultScreenRefreshRate ClockRate = 60 // 60Hz

var supportsXterm256 bool

// colorToAnsi maps the 4-bit DCPU-16 colors to xterm-256 colors
// We can't do an exact match, but we can get pretty close.
// Note: color spec says +red, +green, -highlight puts the green channel
// at 0xFF instead of 0xAA. After reading comments on the 0x10cwiki, this
// is likely a bug, it should probably be dropped to 0x55. Also note that
// this only holds if blue is off.
var colorToAnsi [16]byte = [...]byte{
	/* 0000 */ 16 /* 0001 */, 19 /* 0010 */, 34 /* 0011 */, 37,
	/* 0100 */ 124 /* 0101 */, 127 /* 0110 */, 130 /* 0111 */, 145,
	/* 1000 */ 59  /* 1001 */, 63  /* 1010 */, 71  /* 1011 */, 87,
	/* 1100 */ 203 /* 1101 */, 207 /* 1110 */, 227 /* 1111 */, 231,
}

type Video struct {
	RefreshRate ClockRate // the refresh rate of the screen
	words       [0x400]core.Word
	mapped      bool
}

func (v *Video) Init() error {
	if err := termbox.Init(); err != nil {
		return err
	}
	// Default the background to cyan, for the heck of it
	v.words[0x0280] = 3

	v.clearDisplay()
	v.drawBorder()

	return nil
}

func (v *Video) Close() {
	termbox.Close()
}

func (v *Video) handleChange(offset core.Word) {
	if offset < characterRangeStart {
		row := int(offset / windowWidth)
		column := int(offset % windowWidth)
		v.updateCell(row, column, v.words[offset])
	} else if offset < miscRangeStart {
		// we can't handle font stuff with the terminal
	} else if offset == backgroundColorAddress {
		v.drawBorder()
	}
}

func (v *Video) updateCell(row, column int, word core.Word) {
	// account for the border
	row++
	column++

	ch := rune(word & 0x7F)
	if ch == 0 {
		// replace 0000 with space
		ch = 0x20
	}
	// color seems to be in the top 2 nibbles, MSB being FG and LSB are BG
	// Within each nibble, from LSB to MSB, is blue, green, red, highlight
	// Lastly, the bit at 0x80 is blink.
	flag := (word & 0x80) != 0
	colors := byte((word & 0xFF00) >> 8)
	fgNibble := (colors & 0xF0) >> 4
	bgNibble := colors & 0x0F
	fg, bg := colorToAttr(fgNibble), colorToAttr(bgNibble)
	if flag {
		fg |= termbox.AttrBlink
	}
	termbox.SetCell(column, row, ch, fg, bg)
}

func colorToAttr(color byte) termbox.Attribute {
	var attr termbox.Attribute
	if supportsXterm256 {
		// We need to use xterm-256 colors to work properly here.
		// Luckily, we built a table!
		attr = termbox.ColorXterm256
		ansi := colorToAnsi[color]
		attr |= termbox.Attribute(ansi) << termbox.XtermColorShift
	} else {
		// We don't seem to support xterm-256 colors, so fall back on
		// trying to use the normal ANSI colors
		attr = termbox.ColorDefault
		// bold
		if color&0x8 != 0 {
			attr |= termbox.AttrBold
		}
		// cheat a bit here. We know the termbox color attributes go in the
		// same order as the ANSI colors, and they're monotomically-incrementing.
		// Just figure out the ANSI code and add ColorBlack
		ansi := termbox.Attribute(0)
		if color&0x1 != 0 {
			// blue
			ansi |= 0x4
		}
		if color&0x2 != 0 {
			// green
			ansi |= 0x2
		}
		if color&0x4 != 0 {
			// red
			ansi |= 0x1
		}
		attr |= ansi + termbox.ColorBlack
		return attr
	}
	return attr
}

func (v *Video) drawBorder() {
	// we have no good information on the background color lookup at the moment
	// So instead just treat the low 4 bits
	color := byte(v.words[backgroundColorAddress] & 0xf)
	attr := colorToAttr(color)

	// draw top/bottom
	for _, row := range [2]int{0, windowHeight + 1} {
		for col := 0; col < windowWidth+2; col++ {
			termbox.SetCell(col, row, ' ', termbox.ColorDefault, attr)
		}
	}
	// draw left/right
	for _, col := range [2]int{0, windowWidth + 1} {
		for row := 1; row < windowHeight+1; row++ {
			termbox.SetCell(col, row, ' ', termbox.ColorDefault, attr)
		}
	}
}

func (v *Video) clearDisplay() {
	// clear all cells inside of the border
	attr := termbox.ColorBlack

	for row := 1; row <= windowHeight; row++ {
		for col := 1; col <= windowWidth; col++ {
			termbox.SetCell(col, row, ' ', termbox.ColorDefault, attr)
		}
	}
}

func (v *Video) Flush() {
	termbox.Flush()
}

func (v *Video) UpdateStats(state *core.State, cycleCount uint) {
	// draw stats below the display
	// Cycles: ###########  PC: 0x####
	// A: 0x####  B: 0x####  C: 0x####  I: 0x####
	// X: 0x####  Y: 0x####  Z: 0x####  J: 0x####
	// O: 0x#### SP: 0x####

	row := windowHeight + 2 /* border */ + 1 /* spacing */
	fg, bg := termbox.ColorDefault, termbox.ColorDefault
	termbox.DrawStringf(1, row, fg, bg, "Cycles: %-11d  PC: %#04x", cycleCount, state.PC())
	row++
	termbox.DrawStringf(1, row, fg, bg, "A: %#04x  B: %#04X  C: %#04x  I: %#04x", state.A(), state.B(), state.C(), state.I())
	row++
	termbox.DrawStringf(1, row, fg, bg, "X: %#04x  Y: %#04x  Z: %#04x  J: %#04x", state.X(), state.Y(), state.Z(), state.J())
	row++
	termbox.DrawStringf(1, row, fg, bg, "O: %#04x SP: %#04x", state.O(), state.SP())
}

func (v *Video) MapToMachine(offset core.Word, m *Machine) error {
	if v.mapped {
		return errors.New("Video is already mapped to a machine")
	}
	get := func(offset core.Word) core.Word {
		return v.words[offset]
	}
	set := func(offset, val core.Word) error {
		v.words[offset] = val
		v.handleChange(offset)
		return nil
	}
	if err := m.State.Ram.MapRegion(offset, core.Word(len(v.words)), get, set); err != nil {
		return err
	}
	v.mapped = true
	return nil
}

func (v *Video) UnmapFromMachine(offset core.Word, m *Machine) error {
	if !v.mapped {
		return errors.New("Video is not mapped to a machine")
	}
	if err := m.State.Ram.UnmapRegion(offset, core.Word(len(v.words))); err != nil {
		return err
	}
	v.mapped = false
	return nil
}

// test for xterm-256 color support
func init() {
	// Check $TERM for the -256color suffix
	supportsXterm256 = strings.HasSuffix(os.ExpandEnv("$TERM"), "-256color")
}
<commit_msg>Switch from termbox.DrawStringf() to termbox.DrawString()<commit_after>package dcpu

import (
	"errors"
	"fmt"
	"github.com/kballard/dcpu16/dcpu/core"
	"github.com/kballard/termbox-go"
	"os"
	"strings"
)

// The display is 32x12 (128x96 pixels) surrounded by a
// 16 pixel border / background.
//
// We can't handle pixels, so use a 32x12 character display, with a border
// of one character.
const (
	windowWidth            = 32
	windowHeight           = 12
	characterRangeStart    = 0x0180
	miscRangeStart         = 0x0280
	backgroundColorAddress = 0x0280
)

const DefaultScreenRefreshRate ClockRate = 60 // 60Hz

var supportsXterm256 bool

// colorToAnsi maps the 4-bit DCPU-16 colors to xterm-256 colors
// We can't do an exact match, but we can get pretty close.
// Note: color spec says +red, +green, -highlight puts the green channel
// at 0xFF instead of 0xAA. After reading comments on the 0x10cwiki, this
// is likely a bug, it should probably be dropped to 0x55. Also note that
// this only holds if blue is off.
var colorToAnsi [16]byte = [...]byte{
	/* 0000 */ 16 /* 0001 */, 19 /* 0010 */, 34 /* 0011 */, 37,
	/* 0100 */ 124 /* 0101 */, 127 /* 0110 */, 130 /* 0111 */, 145,
	/* 1000 */ 59 /* 1001 */, 63 /* 1010 */, 71 /* 1011 */, 87,
	/* 1100 */ 203 /* 1101 */, 207 /* 1110 */, 227 /* 1111 */, 231,
}

type Video struct {
	RefreshRate ClockRate // the refresh rate of the screen
	words       [0x400]core.Word
	mapped      bool
}

func (v *Video) Init() error {
	if err := termbox.Init(); err != nil {
		return err
	}
	// Default the background to cyan, for the heck of it
	v.words[0x0280] = 3

	v.clearDisplay()
	v.drawBorder()

	return nil
}

func (v *Video) Close() {
	termbox.Close()
}

func (v *Video) handleChange(offset core.Word) {
	if offset < characterRangeStart {
		row := int(offset / windowWidth)
		column := int(offset % windowWidth)
		v.updateCell(row, column, v.words[offset])
	} else if offset < miscRangeStart {
		// we can't handle font stuff with the terminal
	} else if offset == backgroundColorAddress {
		v.drawBorder()
	}
}

func (v *Video) updateCell(row, column int, word core.Word) {
	// account for the border
	row++
	column++

	ch := rune(word & 0x7F)
	if ch == 0 {
		// replace 0000 with space
		ch = 0x20
	}
	// color seems to be in the top 2 nibbles, MSB being FG and LSB are BG
	// Within each nibble, from LSB to MSB, is blue, green, red, highlight
	// Lastly, the bit at 0x80 is blink.
	flag := (word & 0x80) != 0
	colors := byte((word & 0xFF00) >> 8)
	fgNibble := (colors & 0xF0) >> 4
	bgNibble := colors & 0x0F
	fg, bg := colorToAttr(fgNibble), colorToAttr(bgNibble)
	if flag {
		fg |= termbox.AttrBlink
	}
	termbox.SetCell(column, row, ch, fg, bg)
}

func colorToAttr(color byte) termbox.Attribute {
	var attr termbox.Attribute
	if supportsXterm256 {
		// We need to use xterm-256 colors to work properly here.
		// Luckily, we built a table!
		attr = termbox.ColorXterm256
		ansi := colorToAnsi[color]
		attr |= termbox.Attribute(ansi) << termbox.XtermColorShift
	} else {
		// We don't seem to support xterm-256 colors, so fall back on
		// trying to use the normal ANSI colors
		attr = termbox.ColorDefault
		// bold
		if color&0x8 != 0 {
			attr |= termbox.AttrBold
		}
		// cheat a bit here. We know the termbox color attributes go in the
		// same order as the ANSI colors, and they're monotomically-incrementing.
		// Just figure out the ANSI code and add ColorBlack
		ansi := termbox.Attribute(0)
		if color&0x1 != 0 {
			// blue
			ansi |= 0x4
		}
		if color&0x2 != 0 {
			// green
			ansi |= 0x2
		}
		if color&0x4 != 0 {
			// red
			ansi |= 0x1
		}
		attr |= ansi + termbox.ColorBlack
		return attr
	}
	return attr
}

func (v *Video) drawBorder() {
	// we have no good information on the background color lookup at the moment
	// So instead just treat the low 4 bits
	color := byte(v.words[backgroundColorAddress] & 0xf)
	attr := colorToAttr(color)

	// draw top/bottom
	for _, row := range [2]int{0, windowHeight + 1} {
		for col := 0; col < windowWidth+2; col++ {
			termbox.SetCell(col, row, ' ', termbox.ColorDefault, attr)
		}
	}
	// draw left/right
	for _, col := range [2]int{0, windowWidth + 1} {
		for row := 1; row < windowHeight+1; row++ {
			termbox.SetCell(col, row, ' ', termbox.ColorDefault, attr)
		}
	}
}

func (v *Video) clearDisplay() {
	// clear all cells inside of the border
	attr := termbox.ColorBlack

	for row := 1; row <= windowHeight; row++ {
		for col := 1; col <= windowWidth; col++ {
			termbox.SetCell(col, row, ' ', termbox.ColorDefault, attr)
		}
	}
}

func (v *Video) Flush() {
	termbox.Flush()
}

func (v *Video) UpdateStats(state *core.State, cycleCount uint) {
	// draw stats below the display
	// Cycles: ###########  PC: 0x####
	// A: 0x####  B: 0x####  C: 0x####  I: 0x####
	// X: 0x####  Y: 0x####  Z: 0x####  J: 0x####
	// O: 0x#### SP: 0x####

	row := windowHeight + 2 /* border */ + 1 /* spacing */
	fg, bg := termbox.ColorDefault, termbox.ColorDefault
	termbox.DrawString(1, row, fg, bg, fmt.Sprintf("Cycles: %-11d  PC: %#04x", cycleCount, state.PC()))
	row++
	termbox.DrawString(1, row, fg, bg, fmt.Sprintf("A: %#04x  B: %#04X  C: %#04x  I: %#04x", state.A(), state.B(), state.C(), state.I()))
	row++
	termbox.DrawString(1, row, fg, bg, fmt.Sprintf("X: %#04x  Y: %#04x  Z: %#04x  J: %#04x", state.X(), state.Y(), state.Z(), state.J()))
	row++
	termbox.DrawString(1, row, fg, bg, fmt.Sprintf("O: %#04x SP: %#04x", state.O(), state.SP()))
}

func (v *Video) MapToMachine(offset core.Word, m *Machine) error {
	if v.mapped {
		return errors.New("Video is already mapped to a machine")
	}
	get := func(offset core.Word) core.Word {
		return v.words[offset]
	}
	set := func(offset, val core.Word) error {
		v.words[offset] = val
		v.handleChange(offset)
		return nil
	}
	if err := m.State.Ram.MapRegion(offset, core.Word(len(v.words)), get, set); err != nil {
		return err
	}
	v.mapped = true
	return nil
}

func (v *Video) UnmapFromMachine(offset core.Word, m *Machine) error {
	if !v.mapped {
		return errors.New("Video is not mapped to a machine")
	}
	if err := m.State.Ram.UnmapRegion(offset, core.Word(len(v.words))); err != nil {
		return err
	}
	v.mapped = false
	return nil
}

// test for xterm-256 color support
func init() {
	// Check $TERM for the -256color suffix
	supportsXterm256 = strings.HasSuffix(os.ExpandEnv("$TERM"), "-256color")
}
<|endoftext|>


<commit_before>// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package dcrutil

import (
	"bytes"
	"fmt"
	"io"

	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/wire"
)

// assertTransactionImmutability throws a panic when a transaction has been
// mutated.
var assertTransactionImmutability = false

// TxIndexUnknown is the value returned for a transaction index that is unknown.
// This is typically because the transaction has not been inserted into a block
// yet.
const TxIndexUnknown = -1

// Tx defines a transaction that provides easier and more efficient manipulation
// of raw transactions.  It also memoizes the hash for the transaction on its
// first access so subsequent accesses don't have to repeat the relatively
// expensive hashing operations.
type Tx struct {
	hash    chainhash.Hash // Cached transaction hash
	msgTx   *wire.MsgTx    // Underlying MsgTx
	txTree  int8           // Indicates which tx tree the tx is found in
	txIndex int            // Position within a block or TxIndexUnknown
}

// MsgTx returns the underlying wire.MsgTx for the transaction.
func (t *Tx) MsgTx() *wire.MsgTx {
	// Return the cached transaction.
	return t.msgTx
}

// Hash returns the hash of the transaction.  This is equivalent to
// calling TxHash on the underlying wire.MsgTx, however it caches the
// result so subsequent calls are more efficient.
func (t *Tx) Hash() *chainhash.Hash {
	if assertTransactionImmutability {
		hash := t.msgTx.TxHash()
		if !hash.IsEqual(&t.hash) {
			str := fmt.Sprintf("ASSERT: mutated util.tx detected, old hash %v, "+
				"new hash %v",
				t.hash,
				hash)
			panic(str)
		}
	}
	return &t.hash
}

// Index returns the saved index of the transaction within a block.  This value
// will be TxIndexUnknown if it hasn't already explicitly been set.
func (t *Tx) Index() int {
	return t.txIndex
}

// SetIndex sets the index of the transaction in within a block.
func (t *Tx) SetIndex(index int) {
	t.txIndex = index
}

// Tree returns the saved tree of the transaction within a block.  This value
// will be TxTreeUnknown if it hasn't already explicitly been set.
func (t *Tx) Tree() int8 {
	return t.txTree
}

// SetTree sets the tree of the transaction in within a block.
func (t *Tx) SetTree(tree int8) {
	t.txTree = tree
}

// NewTx returns a new instance of a transaction given an underlying
// wire.MsgTx.  See Tx.
func NewTx(msgTx *wire.MsgTx) *Tx {
	return &Tx{
		hash:    msgTx.TxHash(),
		msgTx:   msgTx,
		txTree:  wire.TxTreeUnknown,
		txIndex: TxIndexUnknown,
	}
}

// NewTxDeep returns a new instance of a transaction given an underlying
// wire.MsgTx.  Until NewTx, it completely copies the data in the msgTx
// so that there are new memory allocations, in case you were to somewhere
// else modify the data assigned to these pointers.
func NewTxDeep(msgTx *wire.MsgTx) *Tx {
	txIns := make([]*wire.TxIn, len(msgTx.TxIn))
	txOuts := make([]*wire.TxOut, len(msgTx.TxOut))

	for i, txin := range msgTx.TxIn {
		sigScript := make([]byte, len(txin.SignatureScript))
		copy(sigScript[:], txin.SignatureScript[:])

		txIns[i] = &wire.TxIn{
			PreviousOutPoint: wire.OutPoint{
				Hash:  txin.PreviousOutPoint.Hash,
				Index: txin.PreviousOutPoint.Index,
				Tree:  txin.PreviousOutPoint.Tree,
			},
			Sequence:        txin.Sequence,
			ValueIn:         txin.ValueIn,
			BlockHeight:     txin.BlockHeight,
			BlockIndex:      txin.BlockIndex,
			SignatureScript: sigScript,
		}
	}

	for i, txout := range msgTx.TxOut {
		pkScript := make([]byte, len(txout.PkScript))
		copy(pkScript[:], txout.PkScript[:])

		txOuts[i] = &wire.TxOut{
			Value:    txout.Value,
			Version:  txout.Version,
			PkScript: pkScript,
		}
	}

	mtx := &wire.MsgTx{
		CachedHash: nil,
		SerType:    msgTx.SerType,
		Version:    msgTx.Version,
		TxIn:       txIns,
		TxOut:      txOuts,
		LockTime:   msgTx.LockTime,
		Expiry:     msgTx.Expiry,
	}

	return &Tx{
		hash:    mtx.TxHash(),
		msgTx:   mtx,
		txTree:  wire.TxTreeUnknown,
		txIndex: TxIndexUnknown,
	}
}

// NewTxDeepTxIns is used to deep copy a transaction, maintaining the old
// pointers to the TxOuts while replacing the old pointers to the TxIns with
// deep copies. This is to prevent races when the fraud proofs for the
// transactions are set by the miner.
func NewTxDeepTxIns(msgTx *wire.MsgTx) *Tx {
	if msgTx == nil {
		return nil
	}

	newMsgTx := new(wire.MsgTx)

	// Copy the fixed fields.
	newMsgTx.Version = msgTx.Version
	newMsgTx.LockTime = msgTx.LockTime
	newMsgTx.Expiry = msgTx.Expiry

	// Copy the TxIns deeply.
	for _, txIn := range msgTx.TxIn {
		sigScrLen := len(txIn.SignatureScript)
		sigScrCopy := make([]byte, sigScrLen)

		txInCopy := new(wire.TxIn)
		txInCopy.PreviousOutPoint.Hash = txIn.PreviousOutPoint.Hash
		txInCopy.PreviousOutPoint.Index = txIn.PreviousOutPoint.Index
		txInCopy.PreviousOutPoint.Tree = txIn.PreviousOutPoint.Tree

		txInCopy.Sequence = txIn.Sequence
		txInCopy.ValueIn = txIn.ValueIn
		txInCopy.BlockHeight = txIn.BlockHeight
		txInCopy.BlockIndex = txIn.BlockIndex

		txInCopy.SignatureScript = sigScrCopy

		newMsgTx.AddTxIn(txIn)
	}

	// Shallow copy the TxOuts.
	for _, txOut := range msgTx.TxOut {
		newMsgTx.AddTxOut(txOut)
	}

	return &Tx{
		hash:    msgTx.TxHash(),
		msgTx:   msgTx,
		txTree:  wire.TxTreeUnknown,
		txIndex: TxIndexUnknown,
	}
}

// NewTxFromBytes returns a new instance of a transaction given the
// serialized bytes.  See Tx.
func NewTxFromBytes(serializedTx []byte) (*Tx, error) {
	br := bytes.NewReader(serializedTx)
	return NewTxFromReader(br)
}

// NewTxFromReader returns a new instance of a transaction given a
// Reader to deserialize the transaction.  See Tx.
func NewTxFromReader(r io.Reader) (*Tx, error) {
	// Deserialize the bytes into a MsgTx.
	var msgTx wire.MsgTx
	err := msgTx.Deserialize(r)
	if err != nil {
		return nil, err
	}

	t := Tx{
		hash:    msgTx.TxHash(),
		msgTx:   &msgTx,
		txTree:  wire.TxTreeUnknown,
		txIndex: TxIndexUnknown,
	}

	return &t, nil
}
<commit_msg>dcrutil: Return deep copied tx in NewTxDeepTxIns. <commit_after>// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package dcrutil

import (
	"bytes"
	"fmt"
	"io"

	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/wire"
)

// assertTransactionImmutability throws a panic when a transaction has been
// mutated.
var assertTransactionImmutability = false

// TxIndexUnknown is the value returned for a transaction index that is unknown.
// This is typically because the transaction has not been inserted into a block
// yet.
const TxIndexUnknown = -1

// Tx defines a transaction that provides easier and more efficient manipulation
// of raw transactions.  It also memoizes the hash for the transaction on its
// first access so subsequent accesses don't have to repeat the relatively
// expensive hashing operations.
type Tx struct {
	hash    chainhash.Hash // Cached transaction hash
	msgTx   *wire.MsgTx    // Underlying MsgTx
	txTree  int8           // Indicates which tx tree the tx is found in
	txIndex int            // Position within a block or TxIndexUnknown
}

// MsgTx returns the underlying wire.MsgTx for the transaction.
func (t *Tx) MsgTx() *wire.MsgTx {
	// Return the cached transaction.
	return t.msgTx
}

// Hash returns the hash of the transaction.  This is equivalent to
// calling TxHash on the underlying wire.MsgTx, however it caches the
// result so subsequent calls are more efficient.
func (t *Tx) Hash() *chainhash.Hash {
	if assertTransactionImmutability {
		hash := t.msgTx.TxHash()
		if !hash.IsEqual(&t.hash) {
			str := fmt.Sprintf("ASSERT: mutated util.tx detected, old hash %v, "+
				"new hash %v",
				t.hash,
				hash)
			panic(str)
		}
	}
	return &t.hash
}

// Index returns the saved index of the transaction within a block.  This value
// will be TxIndexUnknown if it hasn't already explicitly been set.
func (t *Tx) Index() int {
	return t.txIndex
}

// SetIndex sets the index of the transaction in within a block.
func (t *Tx) SetIndex(index int) {
	t.txIndex = index
}

// Tree returns the saved tree of the transaction within a block.  This value
// will be TxTreeUnknown if it hasn't already explicitly been set.
func (t *Tx) Tree() int8 {
	return t.txTree
}

// SetTree sets the tree of the transaction in within a block.
func (t *Tx) SetTree(tree int8) {
	t.txTree = tree
}

// NewTx returns a new instance of a transaction given an underlying
// wire.MsgTx.  See Tx.
func NewTx(msgTx *wire.MsgTx) *Tx {
	return &Tx{
		hash:    msgTx.TxHash(),
		msgTx:   msgTx,
		txTree:  wire.TxTreeUnknown,
		txIndex: TxIndexUnknown,
	}
}

// NewTxDeep returns a new instance of a transaction given an underlying
// wire.MsgTx.  Until NewTx, it completely copies the data in the msgTx
// so that there are new memory allocations, in case you were to somewhere
// else modify the data assigned to these pointers.
func NewTxDeep(msgTx *wire.MsgTx) *Tx {
	txIns := make([]*wire.TxIn, len(msgTx.TxIn))
	txOuts := make([]*wire.TxOut, len(msgTx.TxOut))

	for i, txin := range msgTx.TxIn {
		sigScript := make([]byte, len(txin.SignatureScript))
		copy(sigScript[:], txin.SignatureScript[:])

		txIns[i] = &wire.TxIn{
			PreviousOutPoint: wire.OutPoint{
				Hash:  txin.PreviousOutPoint.Hash,
				Index: txin.PreviousOutPoint.Index,
				Tree:  txin.PreviousOutPoint.Tree,
			},
			Sequence:        txin.Sequence,
			ValueIn:         txin.ValueIn,
			BlockHeight:     txin.BlockHeight,
			BlockIndex:      txin.BlockIndex,
			SignatureScript: sigScript,
		}
	}

	for i, txout := range msgTx.TxOut {
		pkScript := make([]byte, len(txout.PkScript))
		copy(pkScript[:], txout.PkScript[:])

		txOuts[i] = &wire.TxOut{
			Value:    txout.Value,
			Version:  txout.Version,
			PkScript: pkScript,
		}
	}

	mtx := &wire.MsgTx{
		CachedHash: nil,
		SerType:    msgTx.SerType,
		Version:    msgTx.Version,
		TxIn:       txIns,
		TxOut:      txOuts,
		LockTime:   msgTx.LockTime,
		Expiry:     msgTx.Expiry,
	}

	return &Tx{
		hash:    mtx.TxHash(),
		msgTx:   mtx,
		txTree:  wire.TxTreeUnknown,
		txIndex: TxIndexUnknown,
	}
}

// NewTxDeepTxIns is used to deep copy a transaction, maintaining the old
// pointers to the TxOuts while replacing the old pointers to the TxIns with
// deep copies. This is to prevent races when the fraud proofs for the
// transactions are set by the miner.
func NewTxDeepTxIns(msgTx *wire.MsgTx) *Tx {
	if msgTx == nil {
		return nil
	}

	newMsgTx := new(wire.MsgTx)

	// Copy the fixed fields.
	newMsgTx.Version = msgTx.Version
	newMsgTx.LockTime = msgTx.LockTime
	newMsgTx.Expiry = msgTx.Expiry

	// Copy the TxIns deeply.
	for _, txIn := range msgTx.TxIn {
		sigScrLen := len(txIn.SignatureScript)
		sigScrCopy := make([]byte, sigScrLen)

		txInCopy := new(wire.TxIn)
		txInCopy.PreviousOutPoint.Hash = txIn.PreviousOutPoint.Hash
		txInCopy.PreviousOutPoint.Index = txIn.PreviousOutPoint.Index
		txInCopy.PreviousOutPoint.Tree = txIn.PreviousOutPoint.Tree

		txInCopy.Sequence = txIn.Sequence
		txInCopy.ValueIn = txIn.ValueIn
		txInCopy.BlockHeight = txIn.BlockHeight
		txInCopy.BlockIndex = txIn.BlockIndex

		txInCopy.SignatureScript = sigScrCopy

		newMsgTx.AddTxIn(txIn)
	}

	// Shallow copy the TxOuts.
	for _, txOut := range msgTx.TxOut {
		newMsgTx.AddTxOut(txOut)
	}

	return &Tx{
		hash:    newMsgTx.TxHash(),
		msgTx:   newMsgTx,
		txTree:  wire.TxTreeUnknown,
		txIndex: TxIndexUnknown,
	}
}

// NewTxFromBytes returns a new instance of a transaction given the
// serialized bytes.  See Tx.
func NewTxFromBytes(serializedTx []byte) (*Tx, error) {
	br := bytes.NewReader(serializedTx)
	return NewTxFromReader(br)
}

// NewTxFromReader returns a new instance of a transaction given a
// Reader to deserialize the transaction.  See Tx.
func NewTxFromReader(r io.Reader) (*Tx, error) {
	// Deserialize the bytes into a MsgTx.
	var msgTx wire.MsgTx
	err := msgTx.Deserialize(r)
	if err != nil {
		return nil, err
	}

	t := Tx{
		hash:    msgTx.TxHash(),
		msgTx:   &msgTx,
		txTree:  wire.TxTreeUnknown,
		txIndex: TxIndexUnknown,
	}

	return &t, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Google 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 sqlparser

// analyzer.go contains utility analysis functions.

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"unicode"

	"github.com/xwb1989/sqlparser/dependency/sqltypes"
)

// These constants are used to identify the SQL statement type.
const (
	StmtSelect = iota
	StmtInsert
	StmtReplace
	StmtUpdate
	StmtDelete
	StmtDDL
	StmtBegin
	StmtCommit
	StmtRollback
	StmtSet
	StmtShow
	StmtUse
	StmtOther
	StmtUnknown
	StmtComment
)

// Preview analyzes the beginning of the query using a simpler and faster
// textual comparison to identify the statement type.
func Preview(sql string) int {
	trimmed := StripLeadingComments(sql)

	firstWord := trimmed
	if end := strings.IndexFunc(trimmed, unicode.IsSpace); end != -1 {
		firstWord = trimmed[:end]
	}

	// Comparison is done in order of priority.
	loweredFirstWord := strings.ToLower(firstWord)
	switch loweredFirstWord {
	case "select":
		return StmtSelect
	case "insert":
		return StmtInsert
	case "replace":
		return StmtReplace
	case "update":
		return StmtUpdate
	case "delete":
		return StmtDelete
	}
	// For the following statements is not enough to just check
	// loweredFirstWord. This is because they are not statements
	// in the grammar and we are relying in Preview to parse them.
	// For instance, we don't want: "BEGIN ..." to be parsed
	// as StmtBegin.
	trimmedNoComments, _ := SplitTrailingComments(trimmed)
	switch strings.ToLower(trimmedNoComments) {
	case "begin", "start transaction":
		return StmtBegin
	case "commit":
		return StmtCommit
	case "rollback":
		return StmtRollback
	}
	switch loweredFirstWord {
	case "create", "alter", "rename", "drop", "truncate":
		return StmtDDL
	case "set":
		return StmtSet
	case "show":
		return StmtShow
	case "use":
		return StmtUse
	case "analyze", "describe", "desc", "explain", "repair", "optimize":
		return StmtOther
	}
	if strings.Index(trimmed, "/*!") == 0 {
		return StmtComment
	}
	return StmtUnknown
}

// StmtType returns the statement type as a string
func StmtType(stmtType int) string {
	switch stmtType {
	case StmtSelect:
		return "SELECT"
	case StmtInsert:
		return "INSERT"
	case StmtReplace:
		return "REPLACE"
	case StmtUpdate:
		return "UPDATE"
	case StmtDelete:
		return "DELETE"
	case StmtDDL:
		return "DDL"
	case StmtBegin:
		return "BEGIN"
	case StmtCommit:
		return "COMMIT"
	case StmtRollback:
		return "ROLLBACK"
	case StmtSet:
		return "SET"
	case StmtShow:
		return "SHOW"
	case StmtUse:
		return "USE"
	case StmtOther:
		return "OTHER"
	default:
		return "UNKNOWN"
	}
}

// IsDML returns true if the query is an INSERT, UPDATE or DELETE statement.
func IsDML(sql string) bool {
	switch Preview(sql) {
	case StmtInsert, StmtReplace, StmtUpdate, StmtDelete:
		return true
	}
	return false
}

// GetTableName returns the table name from the SimpleTableExpr
// only if it's a simple expression. Otherwise, it returns "".
func GetTableName(node SimpleTableExpr) TableIdent {
	if n, ok := node.(TableName); ok && n.Qualifier.IsEmpty() {
		return n.Name
	}
	// sub-select or '.' expression
	return NewTableIdent("")
}

// IsColName returns true if the Expr is a *ColName.
func IsColName(node Expr) bool {
	_, ok := node.(*ColName)
	return ok
}

// IsValue returns true if the Expr is a string, integral or value arg.
// NULL is not considered to be a value.
func IsValue(node Expr) bool {
	switch v := node.(type) {
	case *SQLVal:
		switch v.Type {
		case StrVal, HexVal, IntVal, ValArg:
			return true
		}
	}
	return false
}

// IsNull returns true if the Expr is SQL NULL
func IsNull(node Expr) bool {
	switch node.(type) {
	case *NullVal:
		return true
	}
	return false
}

// IsSimpleTuple returns true if the Expr is a ValTuple that
// contains simple values or if it's a list arg.
func IsSimpleTuple(node Expr) bool {
	switch vals := node.(type) {
	case ValTuple:
		for _, n := range vals {
			if !IsValue(n) {
				return false
			}
		}
		return true
	case ListArg:
		return true
	}
	// It's a subquery
	return false
}

// NewPlanValue builds a sqltypes.PlanValue from an Expr.
func NewPlanValue(node Expr) (sqltypes.PlanValue, error) {
	switch node := node.(type) {
	case *SQLVal:
		switch node.Type {
		case ValArg:
			return sqltypes.PlanValue{Key: string(node.Val[1:])}, nil
		case IntVal:
			n, err := sqltypes.NewIntegral(string(node.Val))
			if err != nil {
				return sqltypes.PlanValue{}, fmt.Errorf("%v", err)
			}
			return sqltypes.PlanValue{Value: n}, nil
		case StrVal:
			return sqltypes.PlanValue{Value: sqltypes.MakeTrusted(sqltypes.VarBinary, node.Val)}, nil
		case HexVal:
			v, err := node.HexDecode()
			if err != nil {
				return sqltypes.PlanValue{}, fmt.Errorf("%v", err)
			}
			return sqltypes.PlanValue{Value: sqltypes.MakeTrusted(sqltypes.VarBinary, v)}, nil
		}
	case ListArg:
		return sqltypes.PlanValue{ListKey: string(node[2:])}, nil
	case ValTuple:
		pv := sqltypes.PlanValue{
			Values: make([]sqltypes.PlanValue, 0, len(node)),
		}
		for _, val := range node {
			innerpv, err := NewPlanValue(val)
			if err != nil {
				return sqltypes.PlanValue{}, err
			}
			if innerpv.ListKey != "" || innerpv.Values != nil {
				return sqltypes.PlanValue{}, errors.New("unsupported: nested lists")
			}
			pv.Values = append(pv.Values, innerpv)
		}
		return pv, nil
	case *NullVal:
		return sqltypes.PlanValue{}, nil
	}
	return sqltypes.PlanValue{}, fmt.Errorf("expression is too complex '%v'", String(node))
}

// StringIn is a convenience function that returns
// true if str matches any of the values.
func StringIn(str string, values ...string) bool {
	for _, val := range values {
		if str == val {
			return true
		}
	}
	return false
}

// ExtractSetValues returns a map of key-value pairs
// if the query is a SET statement. Values can be int64 or string.
// Since set variable names are case insensitive, all keys are returned
// as lower case.
func ExtractSetValues(sql string) (keyValues map[string]interface{}, charset string, scope string, err error) {
	stmt, err := Parse(sql)
	if err != nil {
		return nil, "", "", err
	}
	setStmt, ok := stmt.(*Set)
	if !ok {
		return nil, "", "", fmt.Errorf("ast did not yield *sqlparser.Set: %T", stmt)
	}
	result := make(map[string]interface{})
	for _, expr := range setStmt.Exprs {
		if !expr.Name.Qualifier.IsEmpty() {
			return nil, "", "", fmt.Errorf("invalid syntax: %v", String(expr.Name))
		}
		key := expr.Name.Name.Lowered()

		switch expr := expr.Expr.(type) {
		case *SQLVal:
			switch expr.Type {
			case StrVal:
				result[key] = string(expr.Val)
			case IntVal:
				num, err := strconv.ParseInt(string(expr.Val), 0, 64)
				if err != nil {
					return nil, "", "", err
				}
				result[key] = num
			default:
				return nil, "", "", fmt.Errorf("invalid value type: %v", String(expr))
			}
		case *NullVal:
			result[key] = nil
		case *Default:
			result[key] = "default"
		default:
			return nil, "", "", fmt.Errorf("invalid syntax: %s", String(expr))
		}
	}
	return result, setStmt.Charset.Lowered(), strings.ToLower(setStmt.Scope), nil
}
<commit_msg>Rewrote comment to make it clearer<commit_after>/*
Copyright 2017 Google 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 sqlparser

// analyzer.go contains utility analysis functions.

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"unicode"

	"github.com/xwb1989/sqlparser/dependency/sqltypes"
)

// These constants are used to identify the SQL statement type.
const (
	StmtSelect = iota
	StmtInsert
	StmtReplace
	StmtUpdate
	StmtDelete
	StmtDDL
	StmtBegin
	StmtCommit
	StmtRollback
	StmtSet
	StmtShow
	StmtUse
	StmtOther
	StmtUnknown
	StmtComment
)

// Preview analyzes the beginning of the query using a simpler and faster
// textual comparison to identify the statement type.
func Preview(sql string) int {
	trimmed := StripLeadingComments(sql)

	firstWord := trimmed
	if end := strings.IndexFunc(trimmed, unicode.IsSpace); end != -1 {
		firstWord = trimmed[:end]
	}

	// Comparison is done in order of priority.
	loweredFirstWord := strings.ToLower(firstWord)
	switch loweredFirstWord {
	case "select":
		return StmtSelect
	case "insert":
		return StmtInsert
	case "replace":
		return StmtReplace
	case "update":
		return StmtUpdate
	case "delete":
		return StmtDelete
	}
	// For the following statements it is not sufficient to rely
	// on loweredFirstWord. This is because they are not statements
	// in the grammar and we are relying on Preview to parse them.
	// For instance, we don't want: "BEGIN JUNK" to be parsed
	// as StmtBegin.
	trimmedNoComments, _ := SplitTrailingComments(trimmed)
	switch strings.ToLower(trimmedNoComments) {
	case "begin", "start transaction":
		return StmtBegin
	case "commit":
		return StmtCommit
	case "rollback":
		return StmtRollback
	}
	switch loweredFirstWord {
	case "create", "alter", "rename", "drop", "truncate":
		return StmtDDL
	case "set":
		return StmtSet
	case "show":
		return StmtShow
	case "use":
		return StmtUse
	case "analyze", "describe", "desc", "explain", "repair", "optimize":
		return StmtOther
	}
	if strings.Index(trimmed, "/*!") == 0 {
		return StmtComment
	}
	return StmtUnknown
}

// StmtType returns the statement type as a string
func StmtType(stmtType int) string {
	switch stmtType {
	case StmtSelect:
		return "SELECT"
	case StmtInsert:
		return "INSERT"
	case StmtReplace:
		return "REPLACE"
	case StmtUpdate:
		return "UPDATE"
	case StmtDelete:
		return "DELETE"
	case StmtDDL:
		return "DDL"
	case StmtBegin:
		return "BEGIN"
	case StmtCommit:
		return "COMMIT"
	case StmtRollback:
		return "ROLLBACK"
	case StmtSet:
		return "SET"
	case StmtShow:
		return "SHOW"
	case StmtUse:
		return "USE"
	case StmtOther:
		return "OTHER"
	default:
		return "UNKNOWN"
	}
}

// IsDML returns true if the query is an INSERT, UPDATE or DELETE statement.
func IsDML(sql string) bool {
	switch Preview(sql) {
	case StmtInsert, StmtReplace, StmtUpdate, StmtDelete:
		return true
	}
	return false
}

// GetTableName returns the table name from the SimpleTableExpr
// only if it's a simple expression. Otherwise, it returns "".
func GetTableName(node SimpleTableExpr) TableIdent {
	if n, ok := node.(TableName); ok && n.Qualifier.IsEmpty() {
		return n.Name
	}
	// sub-select or '.' expression
	return NewTableIdent("")
}

// IsColName returns true if the Expr is a *ColName.
func IsColName(node Expr) bool {
	_, ok := node.(*ColName)
	return ok
}

// IsValue returns true if the Expr is a string, integral or value arg.
// NULL is not considered to be a value.
func IsValue(node Expr) bool {
	switch v := node.(type) {
	case *SQLVal:
		switch v.Type {
		case StrVal, HexVal, IntVal, ValArg:
			return true
		}
	}
	return false
}

// IsNull returns true if the Expr is SQL NULL
func IsNull(node Expr) bool {
	switch node.(type) {
	case *NullVal:
		return true
	}
	return false
}

// IsSimpleTuple returns true if the Expr is a ValTuple that
// contains simple values or if it's a list arg.
func IsSimpleTuple(node Expr) bool {
	switch vals := node.(type) {
	case ValTuple:
		for _, n := range vals {
			if !IsValue(n) {
				return false
			}
		}
		return true
	case ListArg:
		return true
	}
	// It's a subquery
	return false
}

// NewPlanValue builds a sqltypes.PlanValue from an Expr.
func NewPlanValue(node Expr) (sqltypes.PlanValue, error) {
	switch node := node.(type) {
	case *SQLVal:
		switch node.Type {
		case ValArg:
			return sqltypes.PlanValue{Key: string(node.Val[1:])}, nil
		case IntVal:
			n, err := sqltypes.NewIntegral(string(node.Val))
			if err != nil {
				return sqltypes.PlanValue{}, fmt.Errorf("%v", err)
			}
			return sqltypes.PlanValue{Value: n}, nil
		case StrVal:
			return sqltypes.PlanValue{Value: sqltypes.MakeTrusted(sqltypes.VarBinary, node.Val)}, nil
		case HexVal:
			v, err := node.HexDecode()
			if err != nil {
				return sqltypes.PlanValue{}, fmt.Errorf("%v", err)
			}
			return sqltypes.PlanValue{Value: sqltypes.MakeTrusted(sqltypes.VarBinary, v)}, nil
		}
	case ListArg:
		return sqltypes.PlanValue{ListKey: string(node[2:])}, nil
	case ValTuple:
		pv := sqltypes.PlanValue{
			Values: make([]sqltypes.PlanValue, 0, len(node)),
		}
		for _, val := range node {
			innerpv, err := NewPlanValue(val)
			if err != nil {
				return sqltypes.PlanValue{}, err
			}
			if innerpv.ListKey != "" || innerpv.Values != nil {
				return sqltypes.PlanValue{}, errors.New("unsupported: nested lists")
			}
			pv.Values = append(pv.Values, innerpv)
		}
		return pv, nil
	case *NullVal:
		return sqltypes.PlanValue{}, nil
	}
	return sqltypes.PlanValue{}, fmt.Errorf("expression is too complex '%v'", String(node))
}

// StringIn is a convenience function that returns
// true if str matches any of the values.
func StringIn(str string, values ...string) bool {
	for _, val := range values {
		if str == val {
			return true
		}
	}
	return false
}

// ExtractSetValues returns a map of key-value pairs
// if the query is a SET statement. Values can be int64 or string.
// Since set variable names are case insensitive, all keys are returned
// as lower case.
func ExtractSetValues(sql string) (keyValues map[string]interface{}, charset string, scope string, err error) {
	stmt, err := Parse(sql)
	if err != nil {
		return nil, "", "", err
	}
	setStmt, ok := stmt.(*Set)
	if !ok {
		return nil, "", "", fmt.Errorf("ast did not yield *sqlparser.Set: %T", stmt)
	}
	result := make(map[string]interface{})
	for _, expr := range setStmt.Exprs {
		if !expr.Name.Qualifier.IsEmpty() {
			return nil, "", "", fmt.Errorf("invalid syntax: %v", String(expr.Name))
		}
		key := expr.Name.Name.Lowered()

		switch expr := expr.Expr.(type) {
		case *SQLVal:
			switch expr.Type {
			case StrVal:
				result[key] = string(expr.Val)
			case IntVal:
				num, err := strconv.ParseInt(string(expr.Val), 0, 64)
				if err != nil {
					return nil, "", "", err
				}
				result[key] = num
			default:
				return nil, "", "", fmt.Errorf("invalid value type: %v", String(expr))
			}
		case *NullVal:
			result[key] = nil
		case *Default:
			result[key] = "default"
		default:
			return nil, "", "", fmt.Errorf("invalid syntax: %s", String(expr))
		}
	}
	return result, setStmt.Charset.Lowered(), strings.ToLower(setStmt.Scope), nil
}
<|endoftext|>


<commit_before>package task

import (
	"fmt"
	"io"
	"net/http"
	"octlink/rstore/modules/blobs"
	"octlink/rstore/modules/blobsmanifest"
	"octlink/rstore/modules/manifest"
	"octlink/rstore/utils"
	"octlink/rstore/utils/configuration"
	"octlink/rstore/utils/merrors"
	"octlink/rstore/utils/octlog"
	"octlink/rstore/utils/uuid"
	"os"
	"strings"
)

const (
	// TaskStatusNew to new status task
	TaskStatusNew = "new"

	// TaskStatusRunning for running status task
	TaskStatusRunning = "running"

	// TaskStatusFinished for finished status task
	TaskStatusFinished = "finished"

	// TaskStatusError for error status task
	TaskStatusError = "error"

	// TaskStatusStopped for stopped status task
	TaskStatusStopped = "stopped"
)

// Task for downloading and importing task management
type Task struct {
	ID         string `json:"id"`
	URL        string `josn:"url"`
	Status     string `json:"status"`
	CreateTime string `json:"createTime"`
	FinishTime string `json:"finishTime"`
	FilePath   string `json:"filePath"`
	FileName   string `json:"fileName"`
	FileLength int64  `json:"fileLength"`
	ImageName  string `json:"imageName"`
	callback   ImageCallBack
}

var logger *octlog.LogConfig

// InitLog for tasks
func InitLog(level int) {
	logger = octlog.InitLogConfig("task.log", level)
}

// GTasks all tasks map management
var GTasks = make(map[string]*Task, 0)

// GetTasks from web api
func GetTasks() map[string]*Task {
	return GTasks
}

// GetTaskByImage will get task by image id
func GetTaskByImage(imageID string) *Task {
	return nil
}

// ImageCallBack for image callback
type ImageCallBack func(string, int64, int64, string, string) error

// AddAndRun will add a new task to GTasks and run it
func (t *Task) AddAndRun(callback ImageCallBack) {
	t.callback = callback
	t.Status = TaskStatusRunning

	GTasks[t.ID] = t
	go t.Download()

	logger.Warnf("task of %s start to run, %s\n", t.ID, t.URL)
}

// GetTask by taskid
func GetTask(id string) *Task {
	return GTasks[id]
}

// UpdateFilePath update and write file path
func (t *Task) UpdateFilePath() {

	if !utils.IsLocalFile(t.URL) {
		segs := strings.Split(t.URL, "/")
		t.FileName = segs[len(segs)-1]
		t.FilePath = configuration.GetConfig().RootDirectory + "/registry/temp/" +
				uuid.Generate().Simple() + "EEEEE" + t.FileName
	} else { // for local file import
		segs := strings.Split(t.URL, "/")
		t.FileName = segs[len(segs)-1]
		t.FilePath = t.URL
	}

	logger.Debugf("update file dst path %s\n", t.FilePath)
}

// ImportBlobs and then write blobs-manifest config
func (t *Task) ImportBlobs() (*blobsmanifest.BlobsManifest, error) {

	var hashes []string
	var len int64
	var err error

	hugeBlob := configuration.GetConfig().HugeBlob
	if hugeBlob {
		hashes, len, err = blobs.ImportHugeBlob(t.FilePath)
	} else {
		hashes, len, err = blobs.ImportBlobs(t.FilePath)
	}
	if err != nil {
		logger.Errorf("got file hashlist error\n")
		return nil, err
	}

	if len != t.FileLength {
		logger.Errorf("filelen of blobs and http contentlen not match %d:%d\n", len, t.FileLength)
		return nil, fmt.Errorf("filelen %d not match imported len of %d", t.FileLength, len)
	}

	blobsum := blobsmanifest.CalcBlobSum(hashes)
	bm := blobsmanifest.GetBlobsManifest(blobsum)
	if bm != nil {
		logger.Warnf("blobs-manifest %s already exist, just return it\n", bm.BlobSum)
		return bm, nil
	}

	// write blobs-manifest config
	bm = new(blobsmanifest.BlobsManifest)
	bm.Size = t.FileLength
	bm.Chunks = hashes
	bm.BlobSum = blobsum
	err = bm.Write()
	if err != nil {
		logger.Errorf("write blobs-manifest %s error\n", bm.BlobSum)
		return nil, err
	}

	return bm, nil
}

// WriteManifest to write manifest file
func (t *Task) WriteManifest(blobsum string) (*manifest.Manifest, error) {

	m := manifest.GetManifest(t.ImageName, blobsum)
	if m != nil {
		logger.Warnf("manifest of %s:%s already exist\n", t.ImageName, blobsum)
		return m, nil
	}

	// write manifest config
	m = new(manifest.Manifest)
	m.Name = t.ImageName
	m.DiskSize = t.FileLength
	m.VirtualSize = utils.GetVirtualSize(t.FilePath)
	m.CreateTime = utils.CurrentTimeStr()
	m.BlobSum = blobsum

	err := m.Write()
	if err != nil {
		logger.Errorf("Create manifest error[%s]\n", err)
		return nil, err
	}

	logger.Debugf("Write manifest of %s OK\n", blobsum)

	return m, nil
}

func importImage(t *Task) {
	bm, err := t.ImportBlobs()
	if err != nil {
		logger.Errorf("import blobs error %s for %s\n", err, t.FileName)
		t.Error()

		// Remove temp file when failed to add image
		if !utils.IsLocalFile(t.URL) {
			utils.Remove(t.FilePath)
		}
		return
	}

	m, err := t.WriteManifest(bm.BlobSum)
	if err != nil {
		logger.Errorf("write manifest error, imageid:%s, blobsum:%s\n", t.ImageName, bm.BlobSum)
		t.Error()
		// Remove temp file when failed to add image
		if !utils.IsLocalFile(t.URL) {
			utils.Remove(t.FilePath)
		}
		return
	}

	hugeBlob := configuration.GetConfig().HugeBlob
	if hugeBlob {

		imageFilePath := t.FilePath
        destFileDir := configuration.RootDirectory() + manifest.ManifestDir + "/" + m.BlobSum
        destFilePath := destFileDir + "/" + "image"

        logger.Infof("LocalFile %s,destFile %s\n", imageFilePath, destFilePath)

        if utils.IsFileExist(imageFilePath) {
            utils.CreateDir(destFileDir)
            if !utils.IsFileExist(destFilePath) {
				if !utils.IsLocalFile(t.URL) {
					os.Rename(imageFilePath, destFilePath)
				} else {
					utils.CopyFile(imageFilePath, destFilePath)
				}
            } else {
				logger.Infof("image file of %s already exist, no need copy\n", destFilePath)
            }
        } else {
			logger.Errorf("local file of %s from url %s not exist, import manifest error\n",
				t.FilePath, t.URL)
			t.Error()
			return
		}
	}

	// remove temp file
	if !utils.IsLocalFile(t.URL) {
		utils.Remove(t.FilePath)
	}

	// update task status and image info
	t.Finish(m.DiskSize, m.VirtualSize, m.BlobSum)

	logger.Debugf("got file length of %d, and wroted %s\n", t.FileLength, t.FilePath)
}

// Download Image from URL
func (t *Task) Download() {

	if utils.IsLocalFile(t.URL) {

		t.UpdateFilePath()

		// import image from local
		if !utils.IsFileExist(t.URL) {
			logger.Errorf("local url to import of %s not exist", t.URL)
			t.Error()
			return
		}
		t.FileLength = utils.GetFileSize(t.URL)

		importImage(t)
		return
	}

	r, err := http.Get(t.URL)
	if err != nil {
		logger.Errorf("create URL response error on url %s\n", t.URL)
		t.Error()
		return
	}
	defer r.Body.Close()

	t.UpdateFilePath()
	fd, err := os.Create(t.FilePath)
	if err != nil {
		logger.Errorf("create temp file %s error\n", t.FilePath)
		t.Error()
		return
	}

	defer fd.Close()

	buf := make([]byte, configuration.BlobSize)
	for {

		n, err := r.Body.Read(buf)
		if err == io.EOF {
			if n > 0 {
				t.FileLength += int64(n)
				wlen, err := fd.Write(buf[:n])
				if err != nil {
					logger.Warnf("got len %d and wroted %d, total %d\n", n, wlen, t.FileLength)
					t.Error()
					return
				}
			}
			break
		}

		if err != nil {
			logger.Errorf("read file error %s", err)
			// TBD clear
			t.Error()
			return
		}

		t.FileLength += int64(n)
		wlen, err := fd.Write(buf[:n])
		if err != nil {
			logger.Errorf("got len %d and wroted %d, total %d\n", n, wlen, t.FileLength)
			t.Error()
			return
		}
	}

	importImage(t)

	return
}

// Stop this task
func (t *Task) Stop() int {
	t.callback(t.ImageName, 0, 0, "", TaskStatusError)
	return merrors.ErrSuccess
}

// Delete this task
func (t *Task) Delete() int {
	return merrors.ErrSuccess
}

// Finish task here
func (t *Task) Finish(diskSize int64, virtualSize int64, blobsum string) {
	t.Status = TaskStatusFinished
	t.FinishTime = utils.CurrentTimeStr()
	t.callback(t.ImageName, diskSize, virtualSize, blobsum, TaskStatusFinished)
}

func (t *Task) Error() {
	t.Status = TaskStatusError
	t.FinishTime = utils.CurrentTimeStr()
	t.callback(t.ImageName, 0, 0, "", TaskStatusError)
}
<commit_msg>update image downloading task<commit_after>package task

import (
	"fmt"
	"io"
	"net/http"
	"octlink/rstore/modules/blobs"
	"octlink/rstore/modules/blobsmanifest"
	"octlink/rstore/modules/manifest"
	"octlink/rstore/utils"
	"octlink/rstore/utils/configuration"
	"octlink/rstore/utils/merrors"
	"octlink/rstore/utils/octlog"
	"octlink/rstore/utils/uuid"
	"os"
	"strings"
)

const (
	// TaskStatusNew to new status task
	TaskStatusNew = "new"

	// TaskStatusRunning for running status task
	TaskStatusRunning = "running"

	// TaskStatusFinished for finished status task
	TaskStatusFinished = "finished"

	// TaskStatusError for error status task
	TaskStatusError = "error"

	// TaskStatusStopped for stopped status task
	TaskStatusStopped = "stopped"
)

// Task for downloading and importing task management
type Task struct {
	ID         string `json:"id"`
	URL        string `josn:"url"`
	Status     string `json:"status"`
	CreateTime string `json:"createTime"`
	FinishTime string `json:"finishTime"`
	FilePath   string `json:"filePath"`
	FileName   string `json:"fileName"`
	FileLength int64  `json:"fileLength"`
	ImageName  string `json:"imageName"`
	callback   ImageCallBack
}

var logger *octlog.LogConfig

// InitLog for tasks
func InitLog(level int) {
	logger = octlog.InitLogConfig("task.log", level)
}

// GTasks all tasks map management
var GTasks = make(map[string]*Task, 0)

// GetTasks from web api
func GetTasks() map[string]*Task {
	return GTasks
}

// GetTaskByImage will get task by image id
func GetTaskByImage(imageID string) *Task {
	return nil
}

// ImageCallBack for image callback
type ImageCallBack func(string, int64, int64, string, string) error

// AddAndRun will add a new task to GTasks and run it
func (t *Task) AddAndRun(callback ImageCallBack) {
	t.callback = callback
	t.Status = TaskStatusRunning

	GTasks[t.ID] = t
	go t.Download()

	logger.Warnf("task of %s start to run, URL:%s\n", t.ID, t.URL)
}

// GetTask by taskid
func GetTask(id string) *Task {
	return GTasks[id]
}

// UpdateFilePath update and write file path
func (t *Task) UpdateFilePath() {

	if !utils.IsLocalFile(t.URL) {
		segs := strings.Split(t.URL, "/")
		t.FileName = segs[len(segs)-1]
		t.FilePath = configuration.GetConfig().RootDirectory + "/registry/temp/" +
				uuid.Generate().Simple() + "EEEEE" + t.FileName
	} else { // for local file import
		segs := strings.Split(t.URL, "/")
		t.FileName = segs[len(segs)-1]
		t.FilePath = t.URL
	}

	logger.Warnf("Updated image dst path to %s\n", t.FilePath)
}

// ImportBlobs and then write blobs-manifest config
func (t *Task) ImportBlobs() (*blobsmanifest.BlobsManifest, error) {

	var hashes []string
	var len int64
	var err error

	hugeBlob := configuration.GetConfig().HugeBlob
	if hugeBlob {
		hashes, len, err = blobs.ImportHugeBlob(t.FilePath)
	} else {
		hashes, len, err = blobs.ImportBlobs(t.FilePath)
	}
	if err != nil {
		logger.Errorf("got file hashlist error\n")
		return nil, err
	}

	if len != t.FileLength {
		logger.Errorf("filelen of blobs and http contentlen not match %d:%d\n", len, t.FileLength)
		return nil, fmt.Errorf("filelen %d not match imported len of %d", t.FileLength, len)
	}

	blobsum := blobsmanifest.CalcBlobSum(hashes)
	bm := blobsmanifest.GetBlobsManifest(blobsum)
	if bm != nil {
		logger.Warnf("blobs-manifest %s already exist, just return it\n", bm.BlobSum)
		return bm, nil
	}

	// write blobs-manifest config
	bm = new(blobsmanifest.BlobsManifest)
	bm.Size = t.FileLength
	bm.Chunks = hashes
	bm.BlobSum = blobsum
	err = bm.Write()
	if err != nil {
		logger.Errorf("write blobs-manifest %s error\n", bm.BlobSum)
		return nil, err
	}

	logger.Warnf("Import blobs for URL:%s blobsum %s OK\n", t.URL, bm.BlobSum)

	return bm, nil
}

// WriteManifest to write manifest file
func (t *Task) WriteManifest(blobsum string) (*manifest.Manifest, error) {

	m := manifest.GetManifest(t.ImageName, blobsum)
	if m != nil {
		logger.Warnf("manifest of %s:%s already exist\n", t.ImageName, blobsum)
		return m, nil
	}

	// write manifest config
	m = new(manifest.Manifest)
	m.Name = t.ImageName
	m.DiskSize = t.FileLength
	m.VirtualSize = utils.GetVirtualSize(t.FilePath)
	m.CreateTime = utils.CurrentTimeStr()
	m.BlobSum = blobsum

	err := m.Write()
	if err != nil {
		logger.Errorf("Create and Write manifest error, name[%s], blobsum %s, err %s\n",
			m.Name, m.BlobSum, err)
		return nil, err
	}

	logger.Warnf("Write manifest of %s/%s OK\n", blobsum, m.Name)

	return m, nil
}

func importImage(t *Task) {

	bm, err := t.ImportBlobs()
	if err != nil {
		logger.Errorf("import blobs error %s for %s\n", err, t.FileName)
		t.Error()

		// Remove temp file when failed to add image
		if !utils.IsLocalFile(t.URL) {
			utils.Remove(t.FilePath)
		}
		return
	}

	m, err := t.WriteManifest(bm.BlobSum)
	if err != nil {
		logger.Errorf("write manifest error, imageid:%s, blobsum:%s\n", t.ImageName, bm.BlobSum)
		t.Error()
		// Remove temp file when failed to add image
		if !utils.IsLocalFile(t.URL) {
			utils.Remove(t.FilePath)
		}
		return
	}

	hugeBlob := configuration.GetConfig().HugeBlob
	if hugeBlob {

		imageFilePath := t.FilePath
        destFileDir := configuration.RootDirectory() + manifest.ManifestDir + "/" + m.BlobSum
        destFilePath := destFileDir + "/" + "image"

        logger.Infof("LocalFile %s,destFile %s\n", imageFilePath, destFilePath)

        if utils.IsFileExist(imageFilePath) {
            utils.CreateDir(destFileDir)
            if !utils.IsFileExist(destFilePath) {
				if !utils.IsLocalFile(t.URL) {
					os.Rename(imageFilePath, destFilePath)
				} else {
					utils.CopyFile(imageFilePath, destFilePath)
				}
            } else {
				logger.Infof("image file of %s already exist, no need copy\n", destFilePath)
            }
        } else {
			logger.Errorf("local file of %s from url %s not exist, import manifest error\n",
				t.FilePath, t.URL)
			t.Error()
			return
		}
	}

	// remove temp file
	if !utils.IsLocalFile(t.URL) {
		utils.Remove(t.FilePath)
	}

	// update task status and image info
	t.Finish(m.DiskSize, m.VirtualSize, m.BlobSum)

	logger.Warnf("Import Image From Internet OK,length %d, and wroted to %s, with blobsum %s\n",
		t.FileLength, t.FilePath, m.BlobSum)
}

// Download Image from URL
func (t *Task) Download() {

	if utils.IsLocalFile(t.URL) {

		t.UpdateFilePath()

		logger.Infof("Fetch image from Local URL %s, no need downloading\n", t.URL)

		// import image from local
		if !utils.IsFileExist(t.URL) {
			logger.Errorf("local url to import of %s not exist", t.URL)
			t.Error()
			return
		}
		t.FileLength = utils.GetFileSize(t.URL)

		importImage(t)
		return
	}

	logger.Infof("Fetch image from Internet URL %s\n", t.URL)

	r, err := http.Get(t.URL)
	if err != nil {
		logger.Errorf("create URL response error on url %s\n", t.URL)
		t.Error()
		return
	}
	defer r.Body.Close()

	t.UpdateFilePath()
	fd, err := os.Create(t.FilePath)
	if err != nil {
		logger.Errorf("create temp file %s error\n", t.FilePath)
		t.Error()
		return
	}

	defer fd.Close()

	buf := make([]byte, configuration.BlobSize)
	for {

		n, err := r.Body.Read(buf)
		if err == io.EOF {
			if n > 0 {
				t.FileLength += int64(n)
				wlen, err := fd.Write(buf[:n])
				if err != nil {
					logger.Warnf("got len %d and wroted %d, total %d\n", n, wlen, t.FileLength)
					t.Error()
					return
				}
			}
			break
		}

		if err != nil {
			logger.Errorf("read file error %s. with Url %s", err, t.URL)
			// TBD clear
			t.Error()
			return
		}

		t.FileLength += int64(n)
		wlen, err := fd.Write(buf[:n])
		if err != nil {
			logger.Errorf("got len %d and wroted %d, total %d\n", n, wlen, t.FileLength)
			t.Error()
			return
		}
	}

	importImage(t)

	return
}

// Stop this task
func (t *Task) Stop() int {
	t.callback(t.ImageName, 0, 0, "", TaskStatusError)
	return merrors.ErrSuccess
}

// Delete this task
func (t *Task) Delete() int {
	return merrors.ErrSuccess
}

// Finish task here
func (t *Task) Finish(diskSize int64, virtualSize int64, blobsum string) {
	t.Status = TaskStatusFinished
	t.FinishTime = utils.CurrentTimeStr()
	t.callback(t.ImageName, diskSize, virtualSize, blobsum, TaskStatusFinished)
}

func (t *Task) Error() {
	t.Status = TaskStatusError
	t.FinishTime = utils.CurrentTimeStr()
	t.callback(t.ImageName, 0, 0, "", TaskStatusError)
}
<|endoftext|>


<commit_before>package deepcopier

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
)

const (
	// TagName is struct field tag name.
	TagName = "deepcopier"
	// FieldOptionName is the from field option name for struct tag.
	FieldOptionName = "field"
	// ContextOptionName is the context option name for struct tag.
	ContextOptionName = "context"
	// SkipOptionName is the skip option name for struct tag.
	SkipOptionName = "skip"
)

// ----------------------------------------------------------------------------
// Struct tag options
// ----------------------------------------------------------------------------

// TagOptions are struct tag options.
type TagOptions map[string]string

// ----------------------------------------------------------------------------
// Options
// ----------------------------------------------------------------------------

// Options are copier options.
type Options struct {
	// Context given to WithContext() method.
	Context map[string]interface{}
	// Reversed reverses struct tag checkings.
	Reversed bool
}

// ----------------------------------------------------------------------------
// Deepcopier
// ----------------------------------------------------------------------------

// DeepCopier deep copies a struct to/from a struct.
type DeepCopier struct {
	dst interface{}
	src interface{}
	ctx map[string]interface{}
}

// Copy sets source or destination.
func Copy(src interface{}) *DeepCopier {
	return &DeepCopier{src: src}
}

// WithContext injects the given context into the builder instance.
func (dc *DeepCopier) WithContext(ctx map[string]interface{}) *DeepCopier {
	dc.ctx = ctx
	return dc
}

// To sets the given the destination.
func (dc *DeepCopier) To(dst interface{}) error {
	dc.dst = dst
	return cp(dc.dst, dc.src, Options{
		Context: dc.ctx,
	})
}

// From sets the given the source as destination and destination as source.
func (dc *DeepCopier) From(src interface{}) error {
	dc.dst = dc.src
	dc.src = src
	return cp(dc.dst, dc.src, Options{
		Context:  dc.ctx,
		Reversed: true,
	})
}

// cp is the brand new way to process copy.
func cp(dst interface{}, src interface{}, args ...Options) error {
	var (
		options        = Options{}
		srcValue       = reflect.Indirect(reflect.ValueOf(src))
		srcFieldNames  = getFieldNames(src)
		srcMethodNames = getMethodNames(src)
		dstValue       = reflect.Indirect(reflect.ValueOf(dst))
	)

	// Options are given
	if len(args) > 0 {
		options = args[0]
	}

	// Pointer only for receiver
	if !dstValue.CanAddr() {
		return errors.New("dst value is unaddressable")
	}

	//
	// Methods
	//

	for _, m := range srcMethodNames {
		name, opts := getRelatedField(dst, m)
		if name == "" {
			continue
		}

		method := reflect.ValueOf(src).MethodByName(m)
		if !method.IsValid() {
			return fmt.Errorf("method %s is invalid", m)
		}

		var (
			dstFieldType, _ = dstValue.Type().FieldByName(name)
			dstFieldValue   = dstValue.FieldByName(name)
			withContext     = false
		)

		if _, ok := opts[ContextOptionName]; ok {
			withContext = true
		}

		var args []reflect.Value
		if withContext {
			args = []reflect.Value{reflect.ValueOf(options.Context)}
		}

		result := method.Call(args)[0]
		if result.Type().AssignableTo(dstFieldType.Type) {
			dstFieldValue.Set(result)
		}
	}

	//
	// Fields
	//

	for _, f := range srcFieldNames {
		var (
			srcFieldValue                = srcValue.FieldByName(f)
			srcFieldType, srcFieldTypeOK = srcValue.Type().FieldByName(f)
			srcFieldName                 = srcFieldType.Name
			dstFieldName                 = srcFieldName
			tagOptions                   TagOptions
		)

		if options.Reversed {
			tagOptions = getTagOptions(srcFieldType.Tag.Get(TagName))

			if v, ok := tagOptions[FieldOptionName]; ok && v != "" {
				dstFieldName = v
			}
		} else {
			if name, opts := getRelatedField(dst, srcFieldName); name != "" {
				tagOptions = opts
				dstFieldName = name
			}
		}

		// Struct tag -- deepcopier:"skip"
		if _, ok := tagOptions[SkipOptionName]; ok {
			continue
		}

		var (
			dstFieldType, dstFieldTypeOK = dstValue.Type().FieldByName(dstFieldName)
			dstFieldValue                = dstValue.FieldByName(dstFieldName)
		)

		// Ptr -> Value
		if srcFieldType.Type.Kind() == reflect.Ptr && !srcFieldValue.IsNil() && dstFieldType.Type.Kind() != reflect.Ptr {
			dstFieldValue.Set(reflect.Indirect(srcFieldValue))
			continue
		}

		if srcFieldTypeOK && dstFieldTypeOK && srcFieldType.Type.AssignableTo(dstFieldType.Type) {
			dstFieldValue.Set(srcFieldValue)
		}
	}

	return nil
}

// ----------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------

// getTagOptions parses deepcopier tag field and returns options.
func getTagOptions(value string) TagOptions {
	options := TagOptions{}

	for _, opt := range strings.Split(value, ";") {
		o := strings.Split(opt, ":")

		// deepcopier:"keyword; without; value;"
		if len(o) == 1 {
			k := o[0]
			options[k] = ""
		}

		// deepcopier:"key:value; anotherkey:anothervalue"
		if len(o) == 2 {
			k, v := o[0], o[1]
			k = strings.TrimSpace(k)
			v = strings.TrimSpace(v)
			options[k] = v
		}
	}

	return options
}

// getRelatedField returns first matching field.
func getRelatedField(instance interface{}, name string) (string, TagOptions) {
	var (
		value      = reflect.Indirect(reflect.ValueOf(instance))
		fieldName  string
		tagOptions TagOptions
	)

	for i := 0; i < value.NumField(); i++ {
		var (
			v          = value.Field(i)
			t          = value.Type().Field(i)
			tagOptions = getTagOptions(t.Tag.Get(TagName))
		)

		if t.Type.Kind() == reflect.Struct && t.Anonymous {
			if n, o := getRelatedField(v.Interface(), name); n != "" {
				return n, o
			}
		}

		if v, ok := tagOptions[FieldOptionName]; ok && v == name {
			return t.Name, tagOptions
		}

		if t.Name == name {
			return t.Name, tagOptions
		}
	}

	return fieldName, tagOptions
}

// getMethodNames returns instance's method names.
func getMethodNames(instance interface{}) []string {
	var (
		t       = reflect.TypeOf(instance)
		methods []string
	)

	for i := 0; i < t.NumMethod(); i++ {
		methods = append(methods, t.Method(i).Name)
	}

	return methods
}

// getFieldNames returns instance's field names.
func getFieldNames(instance interface{}) []string {
	var (
		v      = reflect.Indirect(reflect.ValueOf(instance))
		t      = v.Type()
		fields []string
	)

	if t.Kind() != reflect.Struct {
		return nil
	}

	for i := 0; i < v.NumField(); i++ {
		var (
			vField = v.Field(i)
			tField = v.Type().Field(i)
		)

		if tField.Type.Kind() == reflect.Struct && tField.Anonymous {
			fields = append(fields, getFieldNames(vField.Interface())...)
			continue
		}

		fields = append(fields, tField.Name)
	}

	return fields
}
<commit_msg>Refacto: remove obsolete and useless comments.<commit_after>package deepcopier

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
)

const (
	// TagName is struct field tag name.
	TagName = "deepcopier"
	// FieldOptionName is the from field option name for struct tag.
	FieldOptionName = "field"
	// ContextOptionName is the context option name for struct tag.
	ContextOptionName = "context"
	// SkipOptionName is the skip option name for struct tag.
	SkipOptionName = "skip"
)

// TagOptions are struct tag options.
type TagOptions map[string]string

// Options are copier options.
type Options struct {
	// Context given to WithContext() method.
	Context map[string]interface{}
	// Reversed reverses struct tag checkings.
	Reversed bool
}

// DeepCopier deep copies a struct to/from a struct.
type DeepCopier struct {
	dst interface{}
	src interface{}
	ctx map[string]interface{}
}

// Copy sets source or destination.
func Copy(src interface{}) *DeepCopier {
	return &DeepCopier{src: src}
}

// WithContext injects the given context into the builder instance.
func (dc *DeepCopier) WithContext(ctx map[string]interface{}) *DeepCopier {
	dc.ctx = ctx
	return dc
}

// To sets the given the destination.
func (dc *DeepCopier) To(dst interface{}) error {
	dc.dst = dst
	return cp(dc.dst, dc.src, Options{
		Context: dc.ctx,
	})
}

// From sets the given the source as destination and destination as source.
func (dc *DeepCopier) From(src interface{}) error {
	dc.dst = dc.src
	dc.src = src
	return cp(dc.dst, dc.src, Options{
		Context:  dc.ctx,
		Reversed: true,
	})
}

// cp is the brand new way to process copy.
func cp(dst interface{}, src interface{}, args ...Options) error {
	var (
		options        = Options{}
		srcValue       = reflect.Indirect(reflect.ValueOf(src))
		dstValue       = reflect.Indirect(reflect.ValueOf(dst))
		srcFieldNames  = getFieldNames(src)
		srcMethodNames = getMethodNames(src)
	)

	if len(args) > 0 {
		options = args[0]
	}

	if !dstValue.CanAddr() {
		return errors.New("dst value is unaddressable")
	}

	for _, m := range srcMethodNames {
		name, opts := getRelatedField(dst, m)
		if name == "" {
			continue
		}

		method := reflect.ValueOf(src).MethodByName(m)
		if !method.IsValid() {
			return fmt.Errorf("method %s is invalid", m)
		}

		var (
			dstFieldType, _ = dstValue.Type().FieldByName(name)
			dstFieldValue   = dstValue.FieldByName(name)
			withContext     = false
		)

		if _, ok := opts[ContextOptionName]; ok {
			withContext = true
		}

		var args []reflect.Value
		if withContext {
			args = []reflect.Value{reflect.ValueOf(options.Context)}
		}

		result := method.Call(args)[0]
		if result.Type().AssignableTo(dstFieldType.Type) {
			dstFieldValue.Set(result)
		}
	}

	for _, f := range srcFieldNames {
		var (
			srcFieldValue                = srcValue.FieldByName(f)
			srcFieldType, srcFieldTypeOK = srcValue.Type().FieldByName(f)
			srcFieldName                 = srcFieldType.Name
			dstFieldName                 = srcFieldName
			tagOptions                   TagOptions
		)

		if options.Reversed {
			tagOptions = getTagOptions(srcFieldType.Tag.Get(TagName))
			if v, ok := tagOptions[FieldOptionName]; ok && v != "" {
				dstFieldName = v
			}
		} else {
			if name, opts := getRelatedField(dst, srcFieldName); name != "" {
				dstFieldName, tagOptions = name, opts
			}
		}

		if _, ok := tagOptions[SkipOptionName]; ok {
			continue
		}

		var (
			dstFieldType, dstFieldTypeOK = dstValue.Type().FieldByName(dstFieldName)
			dstFieldValue                = dstValue.FieldByName(dstFieldName)
		)

		// Ptr -> Value
		if srcFieldType.Type.Kind() == reflect.Ptr && !srcFieldValue.IsNil() && dstFieldType.Type.Kind() != reflect.Ptr {
			dstFieldValue.Set(reflect.Indirect(srcFieldValue))
			continue
		}

		if srcFieldTypeOK && dstFieldTypeOK && srcFieldType.Type.AssignableTo(dstFieldType.Type) {
			dstFieldValue.Set(srcFieldValue)
		}
	}

	return nil
}

// getTagOptions parses deepcopier tag field and returns options.
func getTagOptions(value string) TagOptions {
	options := TagOptions{}

	for _, opt := range strings.Split(value, ";") {
		o := strings.Split(opt, ":")

		// deepcopier:"keyword; without; value;"
		if len(o) == 1 {
			k := o[0]
			options[k] = ""
		}

		// deepcopier:"key:value; anotherkey:anothervalue"
		if len(o) == 2 {
			k, v := o[0], o[1]
			k = strings.TrimSpace(k)
			v = strings.TrimSpace(v)
			options[k] = v
		}
	}

	return options
}

// getRelatedField returns first matching field.
func getRelatedField(instance interface{}, name string) (string, TagOptions) {
	var (
		value      = reflect.Indirect(reflect.ValueOf(instance))
		fieldName  string
		tagOptions TagOptions
	)

	for i := 0; i < value.NumField(); i++ {
		var (
			v          = value.Field(i)
			t          = value.Type().Field(i)
			tagOptions = getTagOptions(t.Tag.Get(TagName))
		)

		if t.Type.Kind() == reflect.Struct && t.Anonymous {
			if n, o := getRelatedField(v.Interface(), name); n != "" {
				return n, o
			}
		}

		if v, ok := tagOptions[FieldOptionName]; ok && v == name {
			return t.Name, tagOptions
		}

		if t.Name == name {
			return t.Name, tagOptions
		}
	}

	return fieldName, tagOptions
}

// getMethodNames returns instance's method names.
func getMethodNames(instance interface{}) []string {
	var (
		t       = reflect.TypeOf(instance)
		methods []string
	)

	for i := 0; i < t.NumMethod(); i++ {
		methods = append(methods, t.Method(i).Name)
	}

	return methods
}

// getFieldNames returns instance's field names.
func getFieldNames(instance interface{}) []string {
	var (
		v      = reflect.Indirect(reflect.ValueOf(instance))
		t      = v.Type()
		fields []string
	)

	if t.Kind() != reflect.Struct {
		return nil
	}

	for i := 0; i < v.NumField(); i++ {
		var (
			vField = v.Field(i)
			tField = v.Type().Field(i)
		)

		if tField.Type.Kind() == reflect.Struct && tField.Anonymous {
			fields = append(fields, getFieldNames(vField.Interface())...)
			continue
		}

		fields = append(fields, tField.Name)
	}

	return fields
}
<|endoftext|>