<commit_before>package networkwebsockets

import (
	"encoding/base64"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"net"
	"strings"
	"time"

	"github.com/richtr/bcrypt"
	"github.com/richtr/mdns"
)

const (
	network_ipv4mdns = "224.0.0.251"
	network_ipv6mdns = "ff02::fb"
	network_mdnsPort = 5353
)

var (
	network_ipv4Addr = &net.UDPAddr{
		IP:   net.ParseIP(network_ipv4mdns),
		Port: network_mdnsPort,
	}
	network_ipv6Addr = &net.UDPAddr{
		IP:   net.ParseIP(network_ipv6mdns),
		Port: network_mdnsPort,
	}
)

/** Named Web Socket DNS-SD Discovery Client interface **/

type DiscoveryService struct {
	Name string
	Hash string
	Path string
	Port int

	server *mdns.Server
}

func NewDiscoveryService(name, hash, path string, port int) *DiscoveryService {
	discoveryService := &DiscoveryService{
		Name: name,
		Hash: hash,
		Path: path,
		Port: port,
	}

	return discoveryService
}

func (dc *DiscoveryService) Register(domain string) {
	rand.Seed(time.Now().UTC().UnixNano())
	dnssdServiceId := fmt.Sprintf("%d", rand.Int())

	s := &mdns.MDNSService{
		Instance: dnssdServiceId,
		Service:  "_nws._tcp",
		Domain:   domain,
		Port:     dc.Port,
		Info:     fmt.Sprintf("hash=%s,path=%s", dc.Hash, dc.Path),
	}

	if err := s.Init(); err != nil {
		log.Printf("Could not register service on network. %v", err)
		return
	}

	var mdnsClientConfig *mdns.Config

	// Advertise service to the correct endpoint (local or network)
	mdnsClientConfig = &mdns.Config{
		IPv4Addr: network_ipv4Addr,
		IPv6Addr: network_ipv6Addr,
	}

	// Add the DNS zone record to advertise
	mdnsClientConfig.Zone = s

	serv, err := mdns.NewServer(mdnsClientConfig)

	if err != nil {
		log.Printf("Failed to create new mDNS server. %v", err)
		return
	}

	dc.server = serv

	log.Printf("New '%s' channel advertised as '%s' in %s network", dc.Name, fmt.Sprintf("%s._nws._tcp", dnssdServiceId), domain)
}

func (dc *DiscoveryService) Shutdown() {
	if dc.server != nil {
		dc.server.Shutdown()
	}
}

/** Named Web Socket DNS-SD Discovery Server interface **/

type DiscoveryBrowser struct {
	closed bool
}

func NewDiscoveryBrowser() *DiscoveryBrowser {
	discoveryBrowser := &DiscoveryBrowser{}

	return discoveryBrowser
}

func (ds *DiscoveryBrowser) Browse(service *NamedWebSocket_Service, timeoutSeconds int) {

	entries := make(chan *mdns.ServiceEntry, 255)

	unresolvedServiceRecords := make(map[string]*NamedWebSocket_DNSRecord)

	timeout := time.Duration(timeoutSeconds) * time.Second

	var targetIPv4 *net.UDPAddr
	var targetIPv6 *net.UDPAddr
	var group *NamedWebSocket_Service_Group

	targetIPv4 = network_ipv4Addr
	targetIPv6 = network_ipv6Addr

	group = service.networkSockets

	// Only look for Named Web Socket DNS-SD services
	params := &mdns.QueryParam{
		Service:  "_nws._tcp",
		Domain:   "local",
		Timeout:  timeout,
		Entries:  entries,
		IPv4mdns: targetIPv4,
		IPv6mdns: targetIPv6,
	}

	go func() {
		complete := false
		finish := time.After(timeout)

		// Wait for responses until timeout
		for !complete {
			select {
			case discoveredService, ok := <-entries:

				if !ok {
					continue
				}

				serviceRecord, err := NewNamedWebSocketRecordFromDNSRecord(discoveredService)
				if err != nil {
					log.Printf("err: %v", err)
					continue
				}

				// Ignore our own NetworkWebSocket services
				if isOwned := group.AdvertisedServiceHashes[serviceRecord.Hash_Base64]; isOwned {
					continue
				}

				// Ignore previously discovered NetworkWebSocket services
				if isResolved := group.ResolvedServiceRecords[serviceRecord.Hash_Base64]; isResolved != nil {
					continue
				}

				serviceName := ""
				localServicePath := ""

				// Resolve service hash provided against advertised services
				isKnown := false
				for knownServiceName := range group.knownServiceNames {
					if bcrypt.Match(knownServiceName, serviceRecord.Hash_BCrypt) {
						serviceName = knownServiceName
						localServicePath = fmt.Sprintf("/network/%s", knownServiceName)
						isKnown = true
						break
					}
				}

				if !isKnown {
					// Store as an unresolved DNS record
					unresolvedServiceRecords[serviceRecord.Hash_Base64] = serviceRecord
					continue
				}

				// Resolve websocket connection
				sock := group.Services[localServicePath]
				if sock == nil {
					sock = NewNamedWebSocket(service, serviceName, service.Port, false)
					group.Services[localServicePath] = sock
				}

				// Create proxy websocket connection
				if _, dErr := sock.dialDNSRecord(serviceRecord, serviceName); dErr != nil {
					log.Printf("err: %v", dErr)
					continue
				}

				// Set DNS record as resolved
				group.ResolvedServiceRecords[serviceRecord.Hash_Base64] = serviceRecord

			case <-finish:
				complete = true
			}
		}

		// Replace unresolved DNS records cache
		group.UnresolvedServiceRecords = unresolvedServiceRecords

	}()

	// Run the mDNS/DNS-SD query
	err := mdns.Query(params)

	if err != nil {
		log.Printf("Could not perform mDNS/DNS-SD query. %v", err)
		time.Sleep(time.Second * time.Duration(timeoutSeconds)) // sleep until next loop is scheduled
		return
	}
}

func (ds *DiscoveryBrowser) Shutdown() {
	ds.closed = true
}

/** Named Web Socket DNS Record interface **/

type NamedWebSocket_DNSRecord struct {
	*mdns.ServiceEntry

	Path        string
	Hash_Base64 string
	Hash_BCrypt string
}

func NewNamedWebSocketRecordFromDNSRecord(serviceEntry *mdns.ServiceEntry) (*NamedWebSocket_DNSRecord, error) {
	servicePath := ""
	serviceHash_Base64 := ""
	serviceHash_BCrypt := ""

	if serviceEntry.Info == "" {
		return nil, errors.New("Could not find associated TXT record for advertised Named Web Socket service")
	}

	serviceParts := strings.FieldsFunc(serviceEntry.Info, func(r rune) bool {
		return r == '=' || r == ',' || r == ';' || r == ' '
	})
	if len(serviceParts) > 1 {
		for i := 0; i < len(serviceParts); i += 2 {
			if strings.ToLower(serviceParts[i]) == "path" {
				servicePath = serviceParts[i+1]
			}
			if strings.ToLower(serviceParts[i]) == "hash" {
				serviceHash_Base64 = serviceParts[i+1]

				if res, err := base64.StdEncoding.DecodeString(serviceHash_Base64); err == nil {
					serviceHash_BCrypt = string(res)
				}
			}
		}
	}

	if servicePath == "" || serviceHash_Base64 == "" || serviceHash_BCrypt == "" {
		return nil, errors.New("Could not resolve the provided Named Web Socket DNS Record")
	}

	// Create and return a new Named Web Socket DNS Record with the parsed information
	newNamedWebSocketDNSRecord := &NamedWebSocket_DNSRecord{serviceEntry, servicePath, serviceHash_Base64, serviceHash_BCrypt}

	return newNamedWebSocketDNSRecord, nil
}
<commit_msg>Simplify mdns addr port names<commit_after>package networkwebsockets

import (
	"encoding/base64"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"net"
	"strings"
	"time"

	"github.com/richtr/bcrypt"
	"github.com/richtr/mdns"
)

const (
	ipv4mdns = "224.0.0.251"
	ipv6mdns = "ff02::fb"
	mdnsPort = 5353
)

var (
	network_ipv4Addr = &net.UDPAddr{
		IP:   net.ParseIP(ipv4mdns),
		Port: mdnsPort,
	}
	network_ipv6Addr = &net.UDPAddr{
		IP:   net.ParseIP(ipv6mdns),
		Port: mdnsPort,
	}
)

/** Named Web Socket DNS-SD Discovery Client interface **/

type DiscoveryService struct {
	Name string
	Hash string
	Path string
	Port int

	server *mdns.Server
}

func NewDiscoveryService(name, hash, path string, port int) *DiscoveryService {
	discoveryService := &DiscoveryService{
		Name: name,
		Hash: hash,
		Path: path,
		Port: port,
	}

	return discoveryService
}

func (dc *DiscoveryService) Register(domain string) {
	rand.Seed(time.Now().UTC().UnixNano())
	dnssdServiceId := fmt.Sprintf("%d", rand.Int())

	s := &mdns.MDNSService{
		Instance: dnssdServiceId,
		Service:  "_nws._tcp",
		Domain:   domain,
		Port:     dc.Port,
		Info:     fmt.Sprintf("hash=%s,path=%s", dc.Hash, dc.Path),
	}

	if err := s.Init(); err != nil {
		log.Printf("Could not register service on network. %v", err)
		return
	}

	var mdnsClientConfig *mdns.Config

	// Advertise service to the correct endpoint (local or network)
	mdnsClientConfig = &mdns.Config{
		IPv4Addr: network_ipv4Addr,
		IPv6Addr: network_ipv6Addr,
	}

	// Add the DNS zone record to advertise
	mdnsClientConfig.Zone = s

	serv, err := mdns.NewServer(mdnsClientConfig)

	if err != nil {
		log.Printf("Failed to create new mDNS server. %v", err)
		return
	}

	dc.server = serv

	log.Printf("New '%s' channel advertised as '%s' in %s network", dc.Name, fmt.Sprintf("%s._nws._tcp", dnssdServiceId), domain)
}

func (dc *DiscoveryService) Shutdown() {
	if dc.server != nil {
		dc.server.Shutdown()
	}
}

/** Named Web Socket DNS-SD Discovery Server interface **/

type DiscoveryBrowser struct {
	closed bool
}

func NewDiscoveryBrowser() *DiscoveryBrowser {
	discoveryBrowser := &DiscoveryBrowser{}

	return discoveryBrowser
}

func (ds *DiscoveryBrowser) Browse(service *NamedWebSocket_Service, timeoutSeconds int) {

	entries := make(chan *mdns.ServiceEntry, 255)

	unresolvedServiceRecords := make(map[string]*NamedWebSocket_DNSRecord)

	timeout := time.Duration(timeoutSeconds) * time.Second

	var targetIPv4 *net.UDPAddr
	var targetIPv6 *net.UDPAddr
	var group *NamedWebSocket_Service_Group

	targetIPv4 = network_ipv4Addr
	targetIPv6 = network_ipv6Addr

	group = service.networkSockets

	// Only look for Named Web Socket DNS-SD services
	params := &mdns.QueryParam{
		Service:  "_nws._tcp",
		Domain:   "local",
		Timeout:  timeout,
		Entries:  entries,
		IPv4mdns: targetIPv4,
		IPv6mdns: targetIPv6,
	}

	go func() {
		complete := false
		finish := time.After(timeout)

		// Wait for responses until timeout
		for !complete {
			select {
			case discoveredService, ok := <-entries:

				if !ok {
					continue
				}

				serviceRecord, err := NewNamedWebSocketRecordFromDNSRecord(discoveredService)
				if err != nil {
					log.Printf("err: %v", err)
					continue
				}

				// Ignore our own NetworkWebSocket services
				if isOwned := group.AdvertisedServiceHashes[serviceRecord.Hash_Base64]; isOwned {
					continue
				}

				// Ignore previously discovered NetworkWebSocket services
				if isResolved := group.ResolvedServiceRecords[serviceRecord.Hash_Base64]; isResolved != nil {
					continue
				}

				serviceName := ""
				localServicePath := ""

				// Resolve service hash provided against advertised services
				isKnown := false
				for knownServiceName := range group.knownServiceNames {
					if bcrypt.Match(knownServiceName, serviceRecord.Hash_BCrypt) {
						serviceName = knownServiceName
						localServicePath = fmt.Sprintf("/network/%s", knownServiceName)
						isKnown = true
						break
					}
				}

				if !isKnown {
					// Store as an unresolved DNS record
					unresolvedServiceRecords[serviceRecord.Hash_Base64] = serviceRecord
					continue
				}

				// Resolve websocket connection
				sock := group.Services[localServicePath]
				if sock == nil {
					sock = NewNamedWebSocket(service, serviceName, service.Port, false)
					group.Services[localServicePath] = sock
				}

				// Create proxy websocket connection
				if _, dErr := sock.dialDNSRecord(serviceRecord, serviceName); dErr != nil {
					log.Printf("err: %v", dErr)
					continue
				}

				// Set DNS record as resolved
				group.ResolvedServiceRecords[serviceRecord.Hash_Base64] = serviceRecord

			case <-finish:
				complete = true
			}
		}

		// Replace unresolved DNS records cache
		group.UnresolvedServiceRecords = unresolvedServiceRecords

	}()

	// Run the mDNS/DNS-SD query
	err := mdns.Query(params)

	if err != nil {
		log.Printf("Could not perform mDNS/DNS-SD query. %v", err)
		time.Sleep(time.Second * time.Duration(timeoutSeconds)) // sleep until next loop is scheduled
		return
	}
}

func (ds *DiscoveryBrowser) Shutdown() {
	ds.closed = true
}

/** Named Web Socket DNS Record interface **/

type NamedWebSocket_DNSRecord struct {
	*mdns.ServiceEntry

	Path        string
	Hash_Base64 string
	Hash_BCrypt string
}

func NewNamedWebSocketRecordFromDNSRecord(serviceEntry *mdns.ServiceEntry) (*NamedWebSocket_DNSRecord, error) {
	servicePath := ""
	serviceHash_Base64 := ""
	serviceHash_BCrypt := ""

	if serviceEntry.Info == "" {
		return nil, errors.New("Could not find associated TXT record for advertised Named Web Socket service")
	}

	serviceParts := strings.FieldsFunc(serviceEntry.Info, func(r rune) bool {
		return r == '=' || r == ',' || r == ';' || r == ' '
	})
	if len(serviceParts) > 1 {
		for i := 0; i < len(serviceParts); i += 2 {
			if strings.ToLower(serviceParts[i]) == "path" {
				servicePath = serviceParts[i+1]
			}
			if strings.ToLower(serviceParts[i]) == "hash" {
				serviceHash_Base64 = serviceParts[i+1]

				if res, err := base64.StdEncoding.DecodeString(serviceHash_Base64); err == nil {
					serviceHash_BCrypt = string(res)
				}
			}
		}
	}

	if servicePath == "" || serviceHash_Base64 == "" || serviceHash_BCrypt == "" {
		return nil, errors.New("Could not resolve the provided Named Web Socket DNS Record")
	}

	// Create and return a new Named Web Socket DNS Record with the parsed information
	newNamedWebSocketDNSRecord := &NamedWebSocket_DNSRecord{serviceEntry, servicePath, serviceHash_Base64, serviceHash_BCrypt}

	return newNamedWebSocketDNSRecord, nil
}
<|endoftext|>


<commit_before>//
//  discovery.go
//
//  Created by Martino Facchin
//  Copyright (c) 2015 Arduino LLC
//
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use,
//  copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following
//  conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//  OTHER DEALINGS IN THE SOFTWARE.
//

package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/oleksandr/bonjour"
	"net"
	"strings"
	"time"
)

const timeoutConst = 2

// SavedNetworkPorts contains the ports which we know are already connected
var SavedNetworkPorts []OsSerialPort

// GetNetworkList returns a list of Network Ports
// The research of network ports is articulated in two phases. First we add new ports coming from
// the bonjour module, then we prune the boards who don't respond to a ping
func GetNetworkList() ([]OsSerialPort, error) {
	newPorts, err := getPorts()
	if err != nil {
		return nil, err
	}

	SavedNetworkPorts = Filter(SavedNetworkPorts, func(port OsSerialPort) bool {
		any := true
		for _, p := range newPorts {
			if p.Name == port.Name {
				any = false
				return any
			}
		}
		return any
	})

	SavedNetworkPorts, err = pruneUnreachablePorts(SavedNetworkPorts)
	if err != nil {
		return nil, err
	}

	SavedNetworkPorts = append(SavedNetworkPorts, newPorts...)

	return SavedNetworkPorts, nil
}

func checkAvailability(ip string) bool {
	timeout := time.Duration(1500 * time.Millisecond)
	// Check if the port 80 is open
	conn, err := net.DialTimeout("tcp", ip+":80", timeout)
	if err != nil {
		log.Println(err)
		// Check if the port 22 is open
		conn, err = net.DialTimeout("tcp", ip+":22", timeout)
		if err != nil {
			log.Println(err)
			return false
		}
		conn.Close()
		return true
	}
	conn.Close()
	return true
}

func pruneUnreachablePorts(ports []OsSerialPort) ([]OsSerialPort, error) {
	times := 2

	ports = Filter(ports, func(port OsSerialPort) bool {
		any := false
		for i := 0; i < times; i++ {
			if checkAvailability(port.Name) {
				any = true
			}
		}
		return any
	})

	return ports, nil
}

func getPorts() ([]OsSerialPort, error) {
	resolver, err := bonjour.NewResolver(nil)
	if err != nil {
		log.Println("Failed to initialize resolver:", err.Error())
		return nil, err
	}

	results := make(chan *bonjour.ServiceEntry)

	timeout := make(chan bool, 1)
	go func(exitCh chan<- bool) {
		time.Sleep(timeoutConst * time.Second)
		exitCh <- true
		close(results)
	}(resolver.Exit)

	arrPorts := []OsSerialPort{}
	go func(results chan *bonjour.ServiceEntry, exitCh chan<- bool) {
		for e := range results {
			arrPorts = append(arrPorts, OsSerialPort{Name: e.AddrIPv4.String(), IdVendor: strings.Join(e.Text[:], " "), NetworkPort: true})
		}
		timeout <- true
	}(results, resolver.Exit)

	err = resolver.Browse("_arduino._tcp", "", results)
	if err != nil {
		log.Println("Failed to browse:", err.Error())
		return nil, err
	}
	// wait for some kind of timeout and return arrPorts
	select {
	case <-timeout:
		return arrPorts, nil
	}
}
<commit_msg>add bonjour name as IdProduct<commit_after>//
//  discovery.go
//
//  Created by Martino Facchin
//  Copyright (c) 2015 Arduino LLC
//
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use,
//  copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following
//  conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//  OTHER DEALINGS IN THE SOFTWARE.
//

package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/oleksandr/bonjour"
	"net"
	"strings"
	"time"
)

const timeoutConst = 2

// SavedNetworkPorts contains the ports which we know are already connected
var SavedNetworkPorts []OsSerialPort

// GetNetworkList returns a list of Network Ports
// The research of network ports is articulated in two phases. First we add new ports coming from
// the bonjour module, then we prune the boards who don't respond to a ping
func GetNetworkList() ([]OsSerialPort, error) {
	newPorts, err := getPorts()
	if err != nil {
		return nil, err
	}

	SavedNetworkPorts = Filter(SavedNetworkPorts, func(port OsSerialPort) bool {
		any := true
		for _, p := range newPorts {
			if p.Name == port.Name {
				any = false
				return any
			}
		}
		return any
	})

	SavedNetworkPorts, err = pruneUnreachablePorts(SavedNetworkPorts)
	if err != nil {
		return nil, err
	}

	SavedNetworkPorts = append(SavedNetworkPorts, newPorts...)

	return SavedNetworkPorts, nil
}

func checkAvailability(ip string) bool {
	timeout := time.Duration(1500 * time.Millisecond)
	// Check if the port 80 is open
	conn, err := net.DialTimeout("tcp", ip+":80", timeout)
	if err != nil {
		log.Println(err)
		// Check if the port 22 is open
		conn, err = net.DialTimeout("tcp", ip+":22", timeout)
		if err != nil {
			log.Println(err)
			return false
		}
		conn.Close()
		return true
	}
	conn.Close()
	return true
}

func pruneUnreachablePorts(ports []OsSerialPort) ([]OsSerialPort, error) {
	times := 2

	ports = Filter(ports, func(port OsSerialPort) bool {
		any := false
		for i := 0; i < times; i++ {
			if checkAvailability(port.Name) {
				any = true
			}
		}
		return any
	})

	return ports, nil
}

func getPorts() ([]OsSerialPort, error) {
	resolver, err := bonjour.NewResolver(nil)
	if err != nil {
		log.Println("Failed to initialize resolver:", err.Error())
		return nil, err
	}

	results := make(chan *bonjour.ServiceEntry)

	timeout := make(chan bool, 1)
	go func(exitCh chan<- bool) {
		time.Sleep(timeoutConst * time.Second)
		exitCh <- true
		close(results)
	}(resolver.Exit)

	arrPorts := []OsSerialPort{}
	go func(results chan *bonjour.ServiceEntry, exitCh chan<- bool) {
		for e := range results {
			arrPorts = append(arrPorts, OsSerialPort{Name: e.AddrIPv4.String(), IdProduct: e.Instance, IdVendor: strings.Join(e.Text[:], " "), NetworkPort: true})
		}
		timeout <- true
	}(results, resolver.Exit)

	err = resolver.Browse("_arduino._tcp", "", results)
	if err != nil {
		log.Println("Failed to browse:", err.Error())
		return nil, err
	}
	// wait for some kind of timeout and return arrPorts
	select {
	case <-timeout:
		return arrPorts, nil
	}
}
<|endoftext|>


<commit_before>package glmenu

import (
	"github.com/4ydx/gltext"
	"github.com/go-gl/gl/v3.3-core/gl"
	"github.com/go-gl/glfw/v3.1/glfw"
	"github.com/go-gl/mathgl/mgl32"
	"os"
)

type Point struct {
	X, Y float32
}

var vertexShaderSource string = `
#version 330

uniform mat4 matrix;

in vec4 position;

void main() {
  gl_Position = matrix * p;
}
` + "\x00"

var fragmentShaderSource string = `
#version 330

uniform vec4 background;
out vec4 fragment_color;

void main() {
  fragment_color = background;
}
` + "\x00"

type MouseClick int

const (
	MouseUnclicked MouseClick = iota
	MouseLeft
	MouseRight
	MouseCenter
)

type Menu struct {
	//trigger
	OnShow         func()
	OnEnterRelease func()

	// options
	Visible      bool
	ShowOnKey    glfw.Key
	Height       float32
	Width        float32
	IsAutoCenter bool
	lowerLeft    Point

	backgroundUniform int32
	Background        mgl32.Vec4

	// interactive objects
	Font          *gltext.Font
	Labels        []*Label
	TextBoxes     []*TextBox
	TextScaleRate float32 // increment during a scale operation

	// opengl oriented
	WindowWidth   float32
	WindowHeight  float32
	program       uint32 // shader program
	glMatrix      int32  // ortho matrix
	position      uint32 // index location
	vao           uint32
	vbo           uint32
	ebo           uint32
	vboData       []float32
	vboIndexCount int
	eboData       []int32
	eboIndexCount int
}

func (menu *Menu) AddLabel(label *Label, str string) {
	label.Load(menu, menu.Font)
	label.SetString(str)
	label.Text.SetScale(1)
	label.Text.SetPosition(0, 0)
	label.Text.SetColor(0, 0, 0)
	menu.Labels = append(menu.Labels, label)
}

func (menu *Menu) AddTextBox(textbox *TextBox, str string, width int32, height int32, borderWidth int32) {
	textbox.Load(menu, width, height, borderWidth)
	textbox.SetString(str)
	textbox.Text.SetScale(1)
	textbox.Text.SetPosition(0, 0)
	textbox.Text.SetColor(0, 0, 0)
	menu.TextBoxes = append(menu.TextBoxes, textbox)
}

func (menu *Menu) Show() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = true
	if menu.OnShow != nil {
		menu.OnShow()
	}
}

func (menu *Menu) Hide() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = false
}

func (menu *Menu) Toggle() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = !menu.Visible
}

func (menu *Menu) Load(width float32, height float32, scale int32) (err error) {
	glfloat_size := 4
	glint_size := 4

	menu.Visible = false
	menu.ShowOnKey = glfw.KeyM
	menu.Width = width
	menu.Height = height

	// load font
	fd, err := os.Open("font/luximr.ttf")
	if err != nil {
		return
	}
	defer fd.Close()

	menu.Font, err = gltext.LoadTruetype(fd, scale, 32, 127)
	if err != nil {
		return
	}

	// 2DO: make this time dependent rather than fps dependent
	menu.TextScaleRate = 0.01

	// create shader program and define attributes and uniforms
	menu.program, err = gltext.NewProgram(vertexShaderSource, fragmentShaderSource)
	if err != nil {
		return
	}
	menu.glMatrix = gl.GetUniformLocation(menu.program, gl.Str("matrix\x00"))
	menu.backgroundUniform = gl.GetUniformLocation(menu.program, gl.Str("background\x00"))
	menu.position = uint32(gl.GetAttribLocation(menu.program, gl.Str("position\x00")))

	gl.GenVertexArrays(1, &menu.vao)
	gl.GenBuffers(1, &menu.vbo)
	gl.GenBuffers(1, &menu.ebo)

	// vao
	gl.BindVertexArray(menu.vao)

	// 2DO: Change text depth to get it to render? For now this works.
	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LEQUAL)

	// vbo
	// specify the buffer for which the VertexAttribPointer calls apply
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)

	gl.EnableVertexAttribArray(menu.position)
	gl.VertexAttribPointer(
		menu.position,
		2,
		gl.FLOAT,
		false,
		0, // no stride... yet
		gl.PtrOffset(0),
	)

	// ebo
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)

	// i am guessing that order is important here
	gl.BindVertexArray(0)
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)

	// ebo, vbo data
	menu.vboIndexCount = 4 * 2 // four indices (2 points per index)
	menu.eboIndexCount = 6     // 6 triangle indices for a quad
	menu.vboData = make([]float32, menu.vboIndexCount, menu.vboIndexCount)
	menu.eboData = make([]int32, menu.eboIndexCount, menu.eboIndexCount)
	menu.lowerLeft = menu.findCenter()
	menu.makeBufferData()

	// setup context
	gl.BindVertexArray(menu.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)
	gl.BufferData(
		gl.ARRAY_BUFFER, glfloat_size*menu.vboIndexCount, gl.Ptr(menu.vboData), gl.DYNAMIC_DRAW)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)
	gl.BufferData(
		gl.ELEMENT_ARRAY_BUFFER, glint_size*menu.eboIndexCount, gl.Ptr(menu.eboData), gl.DYNAMIC_DRAW)
	gl.BindVertexArray(0)

	// not necesssary, but i just want to better understand using vertex arrays
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
	return nil
}

func (menu *Menu) ResizeWindow(width float32, height float32) {
	menu.WindowWidth = width
	menu.WindowHeight = height
	menu.Font.ResizeWindow(width, height)
}

func (menu *Menu) makeBufferData() {
	// index (0,0)
	menu.vboData[0] = menu.lowerLeft.X // position
	menu.vboData[1] = menu.lowerLeft.Y

	// index (1,0)
	menu.vboData[2] = menu.lowerLeft.X + menu.Width
	menu.vboData[3] = menu.lowerLeft.Y

	// index (1,1)
	menu.vboData[4] = menu.lowerLeft.X + menu.Width
	menu.vboData[5] = menu.lowerLeft.Y + menu.Height

	// index (0,1)
	menu.vboData[6] = menu.lowerLeft.X
	menu.vboData[7] = menu.lowerLeft.Y + menu.Height

	menu.eboData[0] = 0
	menu.eboData[1] = 1
	menu.eboData[2] = 2
	menu.eboData[3] = 0
	menu.eboData[4] = 2
	menu.eboData[5] = 3
}

func (menu *Menu) Release() {
	gl.DeleteBuffers(1, &menu.vbo)
	gl.DeleteBuffers(1, &menu.ebo)
	gl.DeleteBuffers(1, &menu.vao)
	for i := range menu.Labels {
		menu.Labels[i].Text.Release()
		if menu.Labels[i].Shadow != nil && menu.Labels[i].Shadow.Text != nil {
			menu.Labels[i].Shadow.Text.Release()
		}
	}
	for i := range menu.TextBoxes {
		menu.TextBoxes[i].Text.Release()
	}
}

func (menu *Menu) Draw() bool {
	if !menu.Visible {
		return menu.Visible
	}
	gl.UseProgram(menu.program)

	gl.UniformMatrix4fv(menu.glMatrix, 1, false, &menu.Font.OrthographicMatrix[0])
	gl.Uniform4fv(menu.backgroundUniform, 1, &menu.Background[0])

	gl.Enable(gl.BLEND)
	gl.BlendEquation(gl.FUNC_ADD)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

	gl.BindVertexArray(menu.vao)
	gl.DrawElements(gl.TRIANGLES, int32(menu.eboIndexCount), gl.UNSIGNED_INT, nil)
	gl.BindVertexArray(0)
	gl.Disable(gl.BLEND)

	for i := range menu.Labels {
		if !menu.Labels[i].IsHover {
			if menu.Labels[i].OnNotHover != nil {
				menu.Labels[i].OnNotHover(menu.Labels[i])
				if menu.Labels[i].Shadow != nil {
					menu.Labels[i].OnNotHover(&menu.Labels[i].Shadow.Label)
				}
			}
		}
		menu.Labels[i].Draw()
	}
	for i := range menu.TextBoxes {
		menu.TextBoxes[i].Draw()
	}
	return menu.Visible
}

func (menu *Menu) MouseClick(xPos, yPos float64, button MouseClick) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		menu.Labels[i].IsClicked(xPos, yPos, button)
	}
	for i := range menu.TextBoxes {
		menu.TextBoxes[i].IsClicked(xPos, yPos, button)
	}
}

func (menu *Menu) MouseRelease(xPos, yPos float64, button MouseClick) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		menu.Labels[i].IsReleased(xPos, yPos, button)
	}
	for i := range menu.TextBoxes {
		menu.TextBoxes[i].IsReleased(xPos, yPos, button)
	}
}

func (menu *Menu) MouseHover(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].OnHover != nil {
			menu.Labels[i].IsHovered(xPos, yPos)
		}
	}
}

func (menu *Menu) findCenter() (lowerLeft Point) {
	menuWidthHalf := menu.Width / 2
	menuHeightHalf := menu.Height / 2

	lowerLeft.X = -menuWidthHalf
	lowerLeft.Y = -menuHeightHalf
	return
}

func (menu *Menu) KeyRelease(key glfw.Key, withShift bool) {
	for i := range menu.TextBoxes {
		menu.TextBoxes[i].KeyRelease(key, withShift)
	}
	if menu.OnEnterRelease != nil && key == glfw.KeyEnter {
		menu.OnEnterRelease()
	}
}
<commit_msg>Variable typo.<commit_after>package glmenu

import (
	"github.com/4ydx/gltext"
	"github.com/go-gl/gl/v3.3-core/gl"
	"github.com/go-gl/glfw/v3.1/glfw"
	"github.com/go-gl/mathgl/mgl32"
	"os"
)

type Point struct {
	X, Y float32
}

var vertexShaderSource string = `
#version 330

uniform mat4 matrix;

in vec4 position;

void main() {
  gl_Position = matrix * position;
}
` + "\x00"

var fragmentShaderSource string = `
#version 330

uniform vec4 background;
out vec4 fragment_color;

void main() {
  fragment_color = background;
}
` + "\x00"

type MouseClick int

const (
	MouseUnclicked MouseClick = iota
	MouseLeft
	MouseRight
	MouseCenter
)

type Menu struct {
	//trigger
	OnShow         func()
	OnEnterRelease func()

	// options
	Visible      bool
	ShowOnKey    glfw.Key
	Height       float32
	Width        float32
	IsAutoCenter bool
	lowerLeft    Point

	backgroundUniform int32
	Background        mgl32.Vec4

	// interactive objects
	Font          *gltext.Font
	Labels        []*Label
	TextBoxes     []*TextBox
	TextScaleRate float32 // increment during a scale operation

	// opengl oriented
	WindowWidth   float32
	WindowHeight  float32
	program       uint32 // shader program
	glMatrix      int32  // ortho matrix
	position      uint32 // index location
	vao           uint32
	vbo           uint32
	ebo           uint32
	vboData       []float32
	vboIndexCount int
	eboData       []int32
	eboIndexCount int
}

func (menu *Menu) AddLabel(label *Label, str string) {
	label.Load(menu, menu.Font)
	label.SetString(str)
	label.Text.SetScale(1)
	label.Text.SetPosition(0, 0)
	label.Text.SetColor(0, 0, 0)
	menu.Labels = append(menu.Labels, label)
}

func (menu *Menu) AddTextBox(textbox *TextBox, str string, width int32, height int32, borderWidth int32) {
	textbox.Load(menu, width, height, borderWidth)
	textbox.SetString(str)
	textbox.Text.SetScale(1)
	textbox.Text.SetPosition(0, 0)
	textbox.Text.SetColor(0, 0, 0)
	menu.TextBoxes = append(menu.TextBoxes, textbox)
}

func (menu *Menu) Show() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = true
	if menu.OnShow != nil {
		menu.OnShow()
	}
}

func (menu *Menu) Hide() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = false
}

func (menu *Menu) Toggle() {
	for i := range menu.Labels {
		menu.Labels[i].Reset()
	}
	menu.Visible = !menu.Visible
}

func (menu *Menu) Load(width float32, height float32, scale int32) (err error) {
	glfloat_size := 4
	glint_size := 4

	menu.Visible = false
	menu.ShowOnKey = glfw.KeyM
	menu.Width = width
	menu.Height = height

	// load font
	fd, err := os.Open("font/luximr.ttf")
	if err != nil {
		return
	}
	defer fd.Close()

	menu.Font, err = gltext.LoadTruetype(fd, scale, 32, 127)
	if err != nil {
		return
	}

	// 2DO: make this time dependent rather than fps dependent
	menu.TextScaleRate = 0.01

	// create shader program and define attributes and uniforms
	menu.program, err = gltext.NewProgram(vertexShaderSource, fragmentShaderSource)
	if err != nil {
		return
	}
	menu.glMatrix = gl.GetUniformLocation(menu.program, gl.Str("matrix\x00"))
	menu.backgroundUniform = gl.GetUniformLocation(menu.program, gl.Str("background\x00"))
	menu.position = uint32(gl.GetAttribLocation(menu.program, gl.Str("position\x00")))

	gl.GenVertexArrays(1, &menu.vao)
	gl.GenBuffers(1, &menu.vbo)
	gl.GenBuffers(1, &menu.ebo)

	// vao
	gl.BindVertexArray(menu.vao)

	// 2DO: Change text depth to get it to render? For now this works.
	gl.Enable(gl.DEPTH_TEST)
	gl.DepthFunc(gl.LEQUAL)

	// vbo
	// specify the buffer for which the VertexAttribPointer calls apply
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)

	gl.EnableVertexAttribArray(menu.position)
	gl.VertexAttribPointer(
		menu.position,
		2,
		gl.FLOAT,
		false,
		0, // no stride... yet
		gl.PtrOffset(0),
	)

	// ebo
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)

	// i am guessing that order is important here
	gl.BindVertexArray(0)
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)

	// ebo, vbo data
	menu.vboIndexCount = 4 * 2 // four indices (2 points per index)
	menu.eboIndexCount = 6     // 6 triangle indices for a quad
	menu.vboData = make([]float32, menu.vboIndexCount, menu.vboIndexCount)
	menu.eboData = make([]int32, menu.eboIndexCount, menu.eboIndexCount)
	menu.lowerLeft = menu.findCenter()
	menu.makeBufferData()

	// setup context
	gl.BindVertexArray(menu.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)
	gl.BufferData(
		gl.ARRAY_BUFFER, glfloat_size*menu.vboIndexCount, gl.Ptr(menu.vboData), gl.DYNAMIC_DRAW)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)
	gl.BufferData(
		gl.ELEMENT_ARRAY_BUFFER, glint_size*menu.eboIndexCount, gl.Ptr(menu.eboData), gl.DYNAMIC_DRAW)
	gl.BindVertexArray(0)

	// not necesssary, but i just want to better understand using vertex arrays
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
	return nil
}

func (menu *Menu) ResizeWindow(width float32, height float32) {
	menu.WindowWidth = width
	menu.WindowHeight = height
	menu.Font.ResizeWindow(width, height)
}

func (menu *Menu) makeBufferData() {
	// index (0,0)
	menu.vboData[0] = menu.lowerLeft.X // position
	menu.vboData[1] = menu.lowerLeft.Y

	// index (1,0)
	menu.vboData[2] = menu.lowerLeft.X + menu.Width
	menu.vboData[3] = menu.lowerLeft.Y

	// index (1,1)
	menu.vboData[4] = menu.lowerLeft.X + menu.Width
	menu.vboData[5] = menu.lowerLeft.Y + menu.Height

	// index (0,1)
	menu.vboData[6] = menu.lowerLeft.X
	menu.vboData[7] = menu.lowerLeft.Y + menu.Height

	menu.eboData[0] = 0
	menu.eboData[1] = 1
	menu.eboData[2] = 2
	menu.eboData[3] = 0
	menu.eboData[4] = 2
	menu.eboData[5] = 3
}

func (menu *Menu) Release() {
	gl.DeleteBuffers(1, &menu.vbo)
	gl.DeleteBuffers(1, &menu.ebo)
	gl.DeleteBuffers(1, &menu.vao)
	for i := range menu.Labels {
		menu.Labels[i].Text.Release()
		if menu.Labels[i].Shadow != nil && menu.Labels[i].Shadow.Text != nil {
			menu.Labels[i].Shadow.Text.Release()
		}
	}
	for i := range menu.TextBoxes {
		menu.TextBoxes[i].Text.Release()
	}
}

func (menu *Menu) Draw() bool {
	if !menu.Visible {
		return menu.Visible
	}
	gl.UseProgram(menu.program)

	gl.UniformMatrix4fv(menu.glMatrix, 1, false, &menu.Font.OrthographicMatrix[0])
	gl.Uniform4fv(menu.backgroundUniform, 1, &menu.Background[0])

	gl.Enable(gl.BLEND)
	gl.BlendEquation(gl.FUNC_ADD)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

	gl.BindVertexArray(menu.vao)
	gl.DrawElements(gl.TRIANGLES, int32(menu.eboIndexCount), gl.UNSIGNED_INT, nil)
	gl.BindVertexArray(0)
	gl.Disable(gl.BLEND)

	for i := range menu.Labels {
		if !menu.Labels[i].IsHover {
			if menu.Labels[i].OnNotHover != nil {
				menu.Labels[i].OnNotHover(menu.Labels[i])
				if menu.Labels[i].Shadow != nil {
					menu.Labels[i].OnNotHover(&menu.Labels[i].Shadow.Label)
				}
			}
		}
		menu.Labels[i].Draw()
	}
	for i := range menu.TextBoxes {
		menu.TextBoxes[i].Draw()
	}
	return menu.Visible
}

func (menu *Menu) MouseClick(xPos, yPos float64, button MouseClick) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		menu.Labels[i].IsClicked(xPos, yPos, button)
	}
	for i := range menu.TextBoxes {
		menu.TextBoxes[i].IsClicked(xPos, yPos, button)
	}
}

func (menu *Menu) MouseRelease(xPos, yPos float64, button MouseClick) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		menu.Labels[i].IsReleased(xPos, yPos, button)
	}
	for i := range menu.TextBoxes {
		menu.TextBoxes[i].IsReleased(xPos, yPos, button)
	}
}

func (menu *Menu) MouseHover(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.WindowHeight) - yPos
	for i := range menu.Labels {
		if menu.Labels[i].OnHover != nil {
			menu.Labels[i].IsHovered(xPos, yPos)
		}
	}
}

func (menu *Menu) findCenter() (lowerLeft Point) {
	menuWidthHalf := menu.Width / 2
	menuHeightHalf := menu.Height / 2

	lowerLeft.X = -menuWidthHalf
	lowerLeft.Y = -menuHeightHalf
	return
}

func (menu *Menu) KeyRelease(key glfw.Key, withShift bool) {
	for i := range menu.TextBoxes {
		menu.TextBoxes[i].KeyRelease(key, withShift)
	}
	if menu.OnEnterRelease != nil && key == glfw.KeyEnter {
		menu.OnEnterRelease()
	}
}
<|endoftext|>


<commit_before>package mailyak

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"io"
	"mime/multipart"
	"net/textproto"
)

func (m *MailYak) buildMime() (*bytes.Buffer, error) {
	return m.buildMimeWithBoundaries(randomBoundary(), randomBoundary())
}

func randomBoundary() string {
	var buf [30]byte
	_, err := io.ReadFull(rand.Reader, buf[:])
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%x", buf[:])
}

// buildMimeWithBoundaries creates the MIME message and returns it as a buffer
func (m *MailYak) buildMimeWithBoundaries(mb, ab string) (*bytes.Buffer, error) {
	var buf bytes.Buffer

	m.writeHeaders(&buf)

	// Start our multipart/mixed part
	mixed := multipart.NewWriter(&buf)
	mixed.SetBoundary(mb)
	defer mixed.Close()

	fmt.Fprintf(&buf, "Content-Type: multipart/mixed;\r\n\tboundary=\"%s\"; charset=UTF-8\r\n\r\n", mixed.Boundary())

	ctype := fmt.Sprintf("multipart/alternative;\r\n\tboundary=\"%s\"", ab)

	altPart, err := mixed.CreatePart(textproto.MIMEHeader{"Content-Type": {ctype}})
	if err != nil {
		return nil, err
	}

	if err := m.writeBody(altPart, ab); err != nil {
		return nil, err
	}

	if err := m.writeAttachments(mixed, lineSplitterBuilder{}); err != nil {
		return nil, err
	}

	return &buf, nil
}

// writeHeaders writes the Mime-Version, Reply-To, From, To and Subject headers
func (m MailYak) writeHeaders(buf io.Writer) {

	buf.Write([]byte(m.fromHeader()))
	buf.Write([]byte("Mime-Version: 1.0\r\n"))

	if m.replyTo != "" {
		fmt.Fprintf(buf, "Reply-To: %s\r\n", m.replyTo)
	}

	fmt.Fprintf(buf, "Subject: %s\r\n", m.subject)

	for _, to := range m.toAddrs {
		fmt.Fprintf(buf, "To: %s\r\n", to)
	}
}

// fromHeader returns a correctly formatted From header, optionally with a name
// component
func (m MailYak) fromHeader() string {
	if m.fromName == "" {
		return fmt.Sprintf("From: %s\r\n", m.fromAddr)
	}

	return fmt.Sprintf("From: %s <%s>\r\n", m.fromName, m.fromAddr)
}

// writeBody writes the text/plain and text/html mime parts
func (m MailYak) writeBody(w io.Writer, boundary string) error {
	alt := multipart.NewWriter(w)
	defer alt.Close()

	alt.SetBoundary(boundary)

	var err error
	writePart := func(ctype string, data []byte) {
		if len(data) == 0 || err != nil {
			return
		}

		c := fmt.Sprintf("%s; charset=UTF-8", ctype)

		var part io.Writer
		part, err = alt.CreatePart(textproto.MIMEHeader{"Content-Type": {c}})
		if err != nil {
			return
		}

		part.Write(data)
	}

	writePart("text/plain", m.plain)
	writePart("text/html", m.html)

	return err
}
<commit_msg>Be cool - never panic<commit_after>package mailyak

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"io"
	"mime/multipart"
	"net/textproto"
)

func (m *MailYak) buildMime() (*bytes.Buffer, error) {
	mb, err := randomBoundary()
	if err != nil {
		return nil, err
	}

	ab, err := randomBoundary()
	if err != nil {
		return nil, err
	}

	return m.buildMimeWithBoundaries(mb, ab)
}

func randomBoundary() (string, error) {
	buf := make([]byte, 30)
	_, err := io.ReadFull(rand.Reader, buf)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%x", buf), nil
}

// buildMimeWithBoundaries creates the MIME message and returns it as a buffer
func (m *MailYak) buildMimeWithBoundaries(mb, ab string) (*bytes.Buffer, error) {
	var buf bytes.Buffer

	m.writeHeaders(&buf)

	// Start our multipart/mixed part
	mixed := multipart.NewWriter(&buf)
	mixed.SetBoundary(mb)
	defer mixed.Close()

	fmt.Fprintf(&buf, "Content-Type: multipart/mixed;\r\n\tboundary=\"%s\"; charset=UTF-8\r\n\r\n", mixed.Boundary())

	ctype := fmt.Sprintf("multipart/alternative;\r\n\tboundary=\"%s\"", ab)

	altPart, err := mixed.CreatePart(textproto.MIMEHeader{"Content-Type": {ctype}})
	if err != nil {
		return nil, err
	}

	if err := m.writeBody(altPart, ab); err != nil {
		return nil, err
	}

	if err := m.writeAttachments(mixed, lineSplitterBuilder{}); err != nil {
		return nil, err
	}

	return &buf, nil
}

// writeHeaders writes the Mime-Version, Reply-To, From, To and Subject headers
func (m MailYak) writeHeaders(buf io.Writer) {

	buf.Write([]byte(m.fromHeader()))
	buf.Write([]byte("Mime-Version: 1.0\r\n"))

	if m.replyTo != "" {
		fmt.Fprintf(buf, "Reply-To: %s\r\n", m.replyTo)
	}

	fmt.Fprintf(buf, "Subject: %s\r\n", m.subject)

	for _, to := range m.toAddrs {
		fmt.Fprintf(buf, "To: %s\r\n", to)
	}
}

// fromHeader returns a correctly formatted From header, optionally with a name
// component
func (m MailYak) fromHeader() string {
	if m.fromName == "" {
		return fmt.Sprintf("From: %s\r\n", m.fromAddr)
	}

	return fmt.Sprintf("From: %s <%s>\r\n", m.fromName, m.fromAddr)
}

// writeBody writes the text/plain and text/html mime parts
func (m MailYak) writeBody(w io.Writer, boundary string) error {
	alt := multipart.NewWriter(w)
	defer alt.Close()

	alt.SetBoundary(boundary)

	var err error
	writePart := func(ctype string, data []byte) {
		if len(data) == 0 || err != nil {
			return
		}

		c := fmt.Sprintf("%s; charset=UTF-8", ctype)

		part, err := alt.CreatePart(textproto.MIMEHeader{"Content-Type": {c}})
		if err != nil {
			return
		}

		part.Write(data)
	}

	writePart("text/plain", m.plain)
	writePart("text/html", m.html)

	return err
}
<|endoftext|>


<commit_before>//  Copyright (c) 2014 Couchbase, 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 (
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"time"

	log "github.com/couchbaselabs/clog"
)

// Compares two dotted versioning string and returns true when x >= y.
func VersionGTE(x, y string) bool {
	xa := strings.Split(x, ".")
	ya := strings.Split(y, ".")
	for i := range xa {
		if i >= len(ya) {
			return true
		}
		xv, err := strconv.Atoi(xa[i])
		if err != nil {
			return false
		}
		yv, err := strconv.Atoi(ya[i])
		if err != nil {
			return false
		}
		if xv < yv {
			return false
		}
	}
	return len(xa) >= len(ya)
}

// TODO: replace with proper UUID implementation.
func NewUUID() string {
	val1 := rand.Int63()
	val2 := rand.Int63()
	uuid := fmt.Sprintf("%x%x", val1, val2)
	return uuid[0:16]
}

// Calls f() in a loop, sleeping in an exponential backoff if needed.
// The provided f() function should return < 0 to stop the loop; >= 0
// to continue the loop, where > 0 means there was progress which
// allows an immediate retry of f() with no sleeping.  A return of < 0
// is useful when f() will never make any future progress.
func ExponentialBackoffLoop(name string,
	f func() int,
	startSleepMS int,
	backoffFactor float32,
	maxSleepMS int) {
	nextSleepMS := startSleepMS
	for {
		progress := f()
		if progress < 0 {
			return
		}
		if progress > 0 {
			// When there was some progress, we can reset nextSleepMS.
			log.Printf("backoff: %s, progress: %d", name, progress)
			nextSleepMS = startSleepMS
		} else {
			// If zero progress was made this cycle, then sleep.
			log.Printf("backoff: %s, sleep: %d (ms)", name, nextSleepMS)
			time.Sleep(time.Duration(nextSleepMS) * time.Millisecond)

			// Increase nextSleepMS in case next time also has 0 progress.
			nextSleepMS = int(float32(nextSleepMS) * backoffFactor)
			if nextSleepMS > maxSleepMS {
				nextSleepMS = maxSleepMS
			}
		}
	}
}
<commit_msg>removed old TODO comment<commit_after>//  Copyright (c) 2014 Couchbase, 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 (
	"fmt"
	"math/rand"
	"strconv"
	"strings"
	"time"

	log "github.com/couchbaselabs/clog"
)

// Compares two dotted versioning string and returns true when x >= y.
func VersionGTE(x, y string) bool {
	xa := strings.Split(x, ".")
	ya := strings.Split(y, ".")
	for i := range xa {
		if i >= len(ya) {
			return true
		}
		xv, err := strconv.Atoi(xa[i])
		if err != nil {
			return false
		}
		yv, err := strconv.Atoi(ya[i])
		if err != nil {
			return false
		}
		if xv < yv {
			return false
		}
	}
	return len(xa) >= len(ya)
}

func NewUUID() string {
	val1 := rand.Int63()
	val2 := rand.Int63()
	uuid := fmt.Sprintf("%x%x", val1, val2)
	return uuid[0:16]
}

// Calls f() in a loop, sleeping in an exponential backoff if needed.
// The provided f() function should return < 0 to stop the loop; >= 0
// to continue the loop, where > 0 means there was progress which
// allows an immediate retry of f() with no sleeping.  A return of < 0
// is useful when f() will never make any future progress.
func ExponentialBackoffLoop(name string,
	f func() int,
	startSleepMS int,
	backoffFactor float32,
	maxSleepMS int) {
	nextSleepMS := startSleepMS
	for {
		progress := f()
		if progress < 0 {
			return
		}
		if progress > 0 {
			// When there was some progress, we can reset nextSleepMS.
			log.Printf("backoff: %s, progress: %d", name, progress)
			nextSleepMS = startSleepMS
		} else {
			// If zero progress was made this cycle, then sleep.
			log.Printf("backoff: %s, sleep: %d (ms)", name, nextSleepMS)
			time.Sleep(time.Duration(nextSleepMS) * time.Millisecond)

			// Increase nextSleepMS in case next time also has 0 progress.
			nextSleepMS = int(float32(nextSleepMS) * backoffFactor)
			if nextSleepMS > maxSleepMS {
				nextSleepMS = maxSleepMS
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"os"
)

type PDFormat struct {
	Name             [32]byte //start at byte 0
	Attributes       uint16   //32
	Version          uint16   //34
	CreationDate     uint32   //36
	ModifyDate       uint32   //40
	BackupDate       uint32   //44
	ModifyNumber     uint32   //48
	AppInfoID        uint32   //52
	SortInfoID       uint32   //56
	Type             [4]byte  //60
	Creator          [4]byte  //64
	UniqueIDSeed     uint32   //68
	NextRecordListID uint32   //72
	SectionCount     uint16   //76-78
}

type PDRecordInfoSection struct {
	DataOffset uint32  //starts at byte 0
	Attributes byte    //4
	UniqueID   [3]byte //5-8
}

type PDHeader struct {
	CompressionType uint16 //starts at byte 0
	_               uint16 //2 (always zero?)
	TextLength      uint32 //4
	RecordCount     uint16 //8
	RecordSize      uint16 //10
	CurrentPosition uint32 //12-16
}

type Mobi8Header struct {
	//note that since the values are being read
	//by bytes.Read, we can't use "_" as a name,
	//or have any unexported fields.
	//This is stupid, but whatever.
	//We use the name "SkipX" instead.
	CompressionType     uint16   //start at byte 0
	Skip1               uint16   //2 (always zero?)
	TextLength          uint32   //4
	RecordCount         uint16   //8
	RecordSize          uint16   //10
	CryptoType          uint16   //12
	Skip2               uint16   //14 filler
	Identifier          [4]byte  //16
	HeaderLength        uint32   //20
	Type                uint32   //24
	TextEncoding        uint32   //28
	UniqueID            uint32   //32
	Version             uint32   //36
	OrtographicIndex    uint32   //40
	IncflectionIndex    uint32   //44
	IndexNames          uint32   //48
	IndexKeys           uint32   //52
	Extra               [24]byte //56
	FirstNontext        uint32   //80
	TitleOffset         uint32   //84
	TitleLength         uint32   //88
	Locale              uint32   //92
	InputLanguage       uint32   //96
	OutputLanguage      uint32   //100
	MinVersion          uint32   //104
	FirstImageOffset    uint32   //108
	HuffmanRecordOffset uint32   //112
	HuffmanRecordCount  uint32   //116
	HuffmanTableOffset  uint32   //120
	HuffTableLength     uint32   //124
	ExthFlags           uint32   //128
	Skip3               [32]byte //132
	Unknown0            uint32   //164
	DrmOffset           uint32   //168
	DrmCount            uint32   //172
	DrmSize             uint32   //176
	DrmFlags            uint32   //180
	Skip4               [8]byte  //184
	FirstContentNumber  uint16   //192
	LastContentNumber   uint16   //194
	FdstFlowCount       uint32   //196
	FcisOffset          uint32   //200
	FcisCount           uint32   //204
	FlisOffset          uint32   //208
	FlisCount           uint32   //212
	Skip5               [8]byte  //216
	SrcsOffset          uint32   //224
	SrcsCount           uint32   //228
	Skip6               [8]byte  //232
	TrailDataFlags      uint32   //240
	NcxIndex            uint32   //244-248
}

func GetStruct(file *os.File, hd interface{}, length int, offset int64) (rd int, err error) {
	b := make([]byte, length)
	rd, err = file.ReadAt(b, offset)
	if err != nil {
		return
	}
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.BigEndian, hd)
	return
}

type ExthHeader struct {
	Identifier  [4]byte //starts at byte 0
	Length      uint32  //4
	RecordCount uint32  //8-12
}

type ExthData struct {
	Header  ExthHeader
	Records []ExthRecord
}

type ExthRecordInfo struct {
	Type   uint32 //starts at 0
	Length uint32 //4-8
}

type ExthRecordData []byte

type ExthRecord struct {
	Info ExthRecordInfo
	Data ExthRecordData
}

type FileHeader struct {
	Format            PDFormat
	Sections          []PDRecordInfoSection
	MobiHeader        Mobi8Header
	Fcis              FcisRecord
	Flis              FlisRecord
	Eof               EofRecord
	Exth              ExthData
	RawContents       []ContentRecord
	DecryptedContents []ContentRecord
}

type ContentRecord []byte

func (cr *ContentRecord) Decode(in []byte) (rd int, err error) {
	for p := 0; p < len(in); {
		c := in[p]
		p += 1
		if c >= 1 && c <= 8 {
			for ii := 0; ii < int(c); ii++ {
				if p < len(in) {
					*cr = append(*cr, in[p])
					p += 1
				}
			}
		} else if c < 128 {
			*cr = append(*cr, c)
		} else if c >= 192 {
			*cr = append(*cr, byte(' '))
			*cr = append(*cr, c^128)
		} else {
			if p < len(in) {
				d := uint16(c)<<8 + uint16(in[p])
				p += 1
				m := uint16(d>>3) & 0x7ff
				n := uint16(d&7) + 3
				length := len(*cr)
				for ii := uint16(0); ii < n; ii++ {
					if length-int(m-ii) >= 0 && length-int(m-ii) < len(*cr) {
						one := []byte(*cr)[length-int(m-ii)]
						*cr = append(*cr, one)
					}
				}
			}
		}
	}
	return
}

type FlisRecord struct {
	Identifier [4]byte //starts at 0, "FLIS"
	Skip1      uint32  //4            8
	Skip2      uint16  //8            65
	Skip3      uint16  //10           0
	Skip4      uint32  //12           0
	Skip5      uint32  //16          -1
	Skip6      uint16  //20           1
	Skip7      uint16  //22           3
	Skip8      uint32  //24           3
	Skip9      uint32  //28           1
	Skip10     uint32  //32-36       -1
}

type FcisRecord struct {
	Identifier [4]byte //starts at 0, to 4 "FCIS"
	Skip1      uint32  //4     20
	Skip2      uint32  //8     16
	Skip3      uint32  //12    1
	Skip4      uint32  //16    0
	TextLength uint32  //20    Same as PDHeader
	Skip5      uint32  //24    0
	Skip6      uint32  //28    32
	Skip7      uint32  //32    8
	Skip8      uint16  //36    1
	Skip9      uint16  //38    1
	Skip10     uint32  //40-44 0
}

type EofRecord struct {
	Skip1 uint8 //starts at 0, to 1  233
	Skip2 uint8 //1                  142
	Skip3 uint8 //2                   13
	Skip4 uint8 //3-4                 10
}

func main() {
	hd, err := GetFileHeader("file.mobi")
	check(err)
//	fmt.Printf("%#v\n", hd.Format)
//	fmt.Printf("%#v %#v\n", hd.Sections[0], hd.Sections[181])
//	fmt.Printf("%#v\n", hd.MobiHeader)
//	fmt.Printf("%#v\n", hd.Fcis)
//	fmt.Printf("%#v\n", hd.Flis)
//	fmt.Printf("%#v\n", hd.Eof)
//	fmt.Printf("%#v\n", hd.Exth.Header)
//	fmt.Printf("%#v\n", hd.Exth.Header.Identifier)
//	fmt.Printf("%#v %#v\n", string(hd.Exth.Records[0].Data), string(hd.Exth.Records[16].Data))
//	fmt.Printf("%#v\n", string(hd.Exth.Records[len(hd.Exth.Records)-1].Data))
//	fmt.Printf("%#v\n", hd.MobiHeader.FirstContentNumber)
//	fmt.Printf("%#v\n", hd.MobiHeader.LastContentNumber)
//	fmt.Printf("%#v\n", len(hd.RawContents))
//	fmt.Printf("%#v\n", len(hd.RawContents[0]))
//	fmt.Printf("%#v\n", len(hd.RawContents[len(hd.RawContents)-1]))
	fmt.Printf("%v\n", string(hd.DecryptedContents[125]))
    fmt.Printf("%#v\n", len(hd.DecryptedContents[  125]))
}

//GetPDRecordInfoSectionList reads `count` items from `file`,
//starting at byte `offset` and placing the result in in `ris`.
//Returns the number of records read, and any error.
func GetPDRecordInfoSectionList(file *os.File, ris *[]PDRecordInfoSection, count int, start int) (ii int, err error) {
	for ii = 0; ii < count; ii++ {
		var section PDRecordInfoSection
		_, err = GetStruct(file, &section, 8, int64(start+ii*8))
		if err != nil {
			return
		}
		*ris = append(*ris, section)
	}
	return
}

//GetFileHeader reads the header information from the file at path `path`.
//Returns the FileHeader as read, and any error.
func GetFileHeader(path string) (hd FileHeader, err error) {
	file, err := os.Open(path)
	defer file.Close()
	start := 0
	if err != nil {
		return
	}

	start, err = GetStruct(file, &hd.Format, 78, 0)
	fmt.Println(start)
	if err != nil {
		return
	}

	bytesRead, err := GetPDRecordInfoSectionList(file, &hd.Sections, int(hd.Format.SectionCount), start)
	fmt.Println(bytesRead)
	if err != nil {
		return
	}
	offset := int64(hd.Sections[0].DataOffset)
	bytesRead, err = GetStruct(file, &hd.MobiHeader, 248, offset)
	offset += int64(bytesRead)

	_, err = GetContents(file, &hd.RawContents, hd.Sections, hd.MobiHeader.FirstContentNumber, hd.MobiHeader.LastContentNumber)
	if err != nil {
		return
	}

	if length := len(hd.RawContents); length > 0 {
		for ii := uint16(0); ii < hd.MobiHeader.RecordCount; ii++ {
			var out ContentRecord
			out.Decode(hd.RawContents[ii])
			hd.DecryptedContents = append(hd.DecryptedContents, out)
		}
	}

	if hd.MobiHeader.ExthFlags&64 == 64 {
		_, err = GetExthData(file, &hd.Exth, offset)
	}

	if hd.MobiHeader.FcisCount > 0 {
		offset := int64(hd.Sections[hd.MobiHeader.FcisOffset].DataOffset)
		bytesRead, err = GetStruct(file, &hd.Fcis, 44, offset)
		fmt.Println("FCIS", bytesRead, err)
	}

	if hd.MobiHeader.FlisCount > 0 {
		offset := int64(hd.Sections[hd.MobiHeader.FlisOffset].DataOffset)
		bytesRead, err = GetStruct(file, &hd.Flis, 36, offset)
		fmt.Println("FLIS", bytesRead, err)
	}

	offset = int64(hd.Sections[len(hd.Sections)-1].DataOffset)
	bytesRead, err = GetStruct(file, &hd.Eof, 4, offset)
	fmt.Println("EOF", bytesRead, err)

	return
}

//GetContents reads contents of `file` into `crs`, with record bounds being
//the difference off the DataOffsets in `sections`[first : last + 1]
//Returns the number of content records read, and the error.
func GetContents(file *os.File, crs *[]ContentRecord, sections []PDRecordInfoSection, first, last uint16) (count int, err error) {
	count = 0
	for ii := first; ii <= last; ii++ {
		offset := int64(sections[ii].DataOffset)
		length := int(sections[ii+1].DataOffset - uint32(offset))
		cr := make(ContentRecord, length)
		bytesRead, err := GetStruct(file, &cr, length, offset)
		if bytesRead != length {
			// do something?
		}
		if err != nil {
			// do something?
		}
		*crs = append(*crs, cr)
		count++
	}
	return
}

//GetExthData reads exth data at index `offset` in `file`, and copies
//the contents into into `ex`. Offset is relative to the start of the file,
//not the start of the record.
//Return the total number of bytes read and the error.
func GetExthData(file *os.File, ex *ExthData, offset int64) (total int, err error) {
	read, err := GetStruct(file, &ex.Header, 12, offset)
	total += read
	for ii := uint32(0); ii < ex.Header.RecordCount; ii++ {
		var record ExthRecord
		read, err = GetStruct(file, &record.Info, 8, offset+int64(total))
		total += read
		record.Data = make([]byte, int(record.Info.Length-8))
		read, err = GetStruct(file, &record.Data, len(record.Data), offset+int64(total))
		total += read
		ex.Records = append(ex.Records, record)
	}
	return
}

//check panics when there's an error.
func check(err error) {
	if err != nil {
		panic(err)
	}
}
<commit_msg>added function to write output to file: testing<commit_after>package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"os"
)

type PDFormat struct {
	Name             [32]byte //start at byte 0
	Attributes       uint16   //32
	Version          uint16   //34
	CreationDate     uint32   //36
	ModifyDate       uint32   //40
	BackupDate       uint32   //44
	ModifyNumber     uint32   //48
	AppInfoID        uint32   //52
	SortInfoID       uint32   //56
	Type             [4]byte  //60
	Creator          [4]byte  //64
	UniqueIDSeed     uint32   //68
	NextRecordListID uint32   //72
	SectionCount     uint16   //76-78
}

type PDRecordInfoSection struct {
	DataOffset uint32  //starts at byte 0
	Attributes byte    //4
	UniqueID   [3]byte //5-8
}

type PDHeader struct {
	CompressionType uint16 //starts at byte 0
	_               uint16 //2 (always zero?)
	TextLength      uint32 //4
	RecordCount     uint16 //8
	RecordSize      uint16 //10
	CurrentPosition uint32 //12-16
}

type Mobi8Header struct {
	//note that since the values are being read
	//by bytes.Read, we can't use "_" as a name,
	//or have any unexported fields.
	//This is stupid, but whatever.
	//We use the name "SkipX" instead.
	CompressionType     uint16   //start at byte 0
	Skip1               uint16   //2 (always zero?)
	TextLength          uint32   //4
	RecordCount         uint16   //8
	RecordSize          uint16   //10
	CryptoType          uint16   //12
	Skip2               uint16   //14 filler
	Identifier          [4]byte  //16
	HeaderLength        uint32   //20
	Type                uint32   //24
	TextEncoding        uint32   //28
	UniqueID            uint32   //32
	Version             uint32   //36
	OrtographicIndex    uint32   //40
	IncflectionIndex    uint32   //44
	IndexNames          uint32   //48
	IndexKeys           uint32   //52
	Extra               [24]byte //56
	FirstNontext        uint32   //80
	TitleOffset         uint32   //84
	TitleLength         uint32   //88
	Locale              uint32   //92
	InputLanguage       uint32   //96
	OutputLanguage      uint32   //100
	MinVersion          uint32   //104
	FirstImageOffset    uint32   //108
	HuffmanRecordOffset uint32   //112
	HuffmanRecordCount  uint32   //116
	HuffmanTableOffset  uint32   //120
	HuffTableLength     uint32   //124
	ExthFlags           uint32   //128
	Skip3               [32]byte //132
	Unknown0            uint32   //164
	DrmOffset           uint32   //168
	DrmCount            uint32   //172
	DrmSize             uint32   //176
	DrmFlags            uint32   //180
	Skip4               [8]byte  //184
	FirstContentNumber  uint16   //192
	LastContentNumber   uint16   //194
	FdstFlowCount       uint32   //196
	FcisOffset          uint32   //200
	FcisCount           uint32   //204
	FlisOffset          uint32   //208
	FlisCount           uint32   //212
	Skip5               [8]byte  //216
	SrcsOffset          uint32   //224
	SrcsCount           uint32   //228
	Skip6               [8]byte  //232
	TrailDataFlags      uint32   //240
	NcxIndex            uint32   //244-248
}

func GetStruct(file *os.File, hd interface{}, length int, offset int64) (rd int, err error) {
	b := make([]byte, length)
	rd, err = file.ReadAt(b, offset)
	if err != nil {
		return
	}
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.BigEndian, hd)
	return
}

type ExthHeader struct {
	Identifier  [4]byte //starts at byte 0
	Length      uint32  //4
	RecordCount uint32  //8-12
}

type ExthData struct {
	Header  ExthHeader
	Records []ExthRecord
}

type ExthRecordInfo struct {
	Type   uint32 //starts at 0
	Length uint32 //4-8
}

type ExthRecordData []byte

type ExthRecord struct {
	Info ExthRecordInfo
	Data ExthRecordData
}

type FileHeader struct {
	Format            PDFormat
	Sections          []PDRecordInfoSection
	MobiHeader        Mobi8Header
	Fcis              FcisRecord
	Flis              FlisRecord
	Eof               EofRecord
	Exth              ExthData
	RawContents       []ContentRecord
	DecryptedContents []ContentRecord
}

type ContentRecord []byte

func (cr *ContentRecord) Decode(in []byte) (rd int, err error) {
	for p := 0; p < len(in); {
		c := in[p]
		p += 1
		if c >= 1 && c <= 8 {
			for ii := 0; ii < int(c); ii++ {
				if p < len(in) {
					*cr = append(*cr, in[p])
					p += 1
				}
			}
		} else if c < 128 {
			*cr = append(*cr, c)
		} else if c >= 192 {
			*cr = append(*cr, byte(' '))
			*cr = append(*cr, c^128)
		} else {
			if p < len(in) {
				d := uint16(c)<<8 + uint16(in[p])
				p += 1
				m := uint16(d>>3) & 0x7ff
				n := uint16(d&7) + 3
				length := len(*cr)
				for ii := uint16(0); ii < n; ii++ {
					if length-int(m-ii) >= 0 && length-int(m-ii) < len(*cr) {
						one := []byte(*cr)[length-int(m-ii)]
						*cr = append(*cr, one)
					}
				}
			}
		}
	}
	return
}

type FlisRecord struct {
	Identifier [4]byte //starts at 0, "FLIS"
	Skip1      uint32  //4            8
	Skip2      uint16  //8            65
	Skip3      uint16  //10           0
	Skip4      uint32  //12           0
	Skip5      uint32  //16          -1
	Skip6      uint16  //20           1
	Skip7      uint16  //22           3
	Skip8      uint32  //24           3
	Skip9      uint32  //28           1
	Skip10     uint32  //32-36       -1
}

type FcisRecord struct {
	Identifier [4]byte //starts at 0, to 4 "FCIS"
	Skip1      uint32  //4     20
	Skip2      uint32  //8     16
	Skip3      uint32  //12    1
	Skip4      uint32  //16    0
	TextLength uint32  //20    Same as PDHeader
	Skip5      uint32  //24    0
	Skip6      uint32  //28    32
	Skip7      uint32  //32    8
	Skip8      uint16  //36    1
	Skip9      uint16  //38    1
	Skip10     uint32  //40-44 0
}

type EofRecord struct {
	Skip1 uint8 //starts at 0, to 1  233
	Skip2 uint8 //1                  142
	Skip3 uint8 //2                   13
	Skip4 uint8 //3-4                 10
}

func main() {
	hd, err := GetFileHeader("file.mobi")
	check(err)
	//	fmt.Printf("%#v\n", hd.Format)
	//	fmt.Printf("%#v %#v\n", hd.Sections[0], hd.Sections[181])
	//	fmt.Printf("%#v\n", hd.MobiHeader)
	//	fmt.Printf("%#v\n", hd.Fcis)
	//	fmt.Printf("%#v\n", hd.Flis)
	//	fmt.Printf("%#v\n", hd.Eof)
	//	fmt.Printf("%#v\n", hd.Exth.Header)
	//	fmt.Printf("%#v\n", hd.Exth.Header.Identifier)
	//	fmt.Printf("%#v %#v\n", string(hd.Exth.Records[0].Data), string(hd.Exth.Records[16].Data))
	//	fmt.Printf("%#v\n", string(hd.Exth.Records[len(hd.Exth.Records)-1].Data))
	//	fmt.Printf("%#v\n", hd.MobiHeader.FirstContentNumber)
	//	fmt.Printf("%#v\n", hd.MobiHeader.LastContentNumber)
	//	fmt.Printf("%#v\n", len(hd.RawContents))
	//	fmt.Printf("%#v\n", len(hd.RawContents[0]))
	//	fmt.Printf("%#v\n", len(hd.RawContents[len(hd.RawContents)-1]))
	fmt.Printf("%v\n", string(hd.DecryptedContents[125]))
	fmt.Printf("%#v\n", len(hd.DecryptedContents[125]))
	WriteContentsToFile("./testout.html", hd.DecryptedContents)
}

func WriteContentsToFile(path string, content []ContentRecord) (err error) {
	file, err := os.Create(path)
	check(err)
	for ii := 0; ii < len(content); ii++ {
		file.Write(content[ii])
	}
    return
}

//GetPDRecordInfoSectionList reads `count` items from `file`,
//starting at byte `offset` and placing the result in in `ris`.
//Returns the number of records read, and any error.
func GetPDRecordInfoSectionList(file *os.File, ris *[]PDRecordInfoSection, count int, start int) (ii int, err error) {
	for ii = 0; ii < count; ii++ {
		var section PDRecordInfoSection
		_, err = GetStruct(file, &section, 8, int64(start+ii*8))
		if err != nil {
			return
		}
		*ris = append(*ris, section)
	}
	return
}

//GetFileHeader reads the header information from the file at path `path`.
//Returns the FileHeader as read, and any error.
func GetFileHeader(path string) (hd FileHeader, err error) {
	file, err := os.Open(path)
	defer file.Close()
	start := 0
	if err != nil {
		return
	}

	start, err = GetStruct(file, &hd.Format, 78, 0)
	fmt.Println(start)
	if err != nil {
		return
	}

	bytesRead, err := GetPDRecordInfoSectionList(file, &hd.Sections, int(hd.Format.SectionCount), start)
	fmt.Println(bytesRead)
	if err != nil {
		return
	}
	offset := int64(hd.Sections[0].DataOffset)
	bytesRead, err = GetStruct(file, &hd.MobiHeader, 248, offset)
	offset += int64(bytesRead)

	_, err = GetContents(file, &hd.RawContents, hd.Sections, hd.MobiHeader.FirstContentNumber, hd.MobiHeader.LastContentNumber)
	if err != nil {
		return
	}

	if length := len(hd.RawContents); length > 0 {
		for ii := uint16(0); ii < hd.MobiHeader.RecordCount; ii++ {
			var out ContentRecord
			out.Decode(hd.RawContents[ii])
			hd.DecryptedContents = append(hd.DecryptedContents, out)
		}
	}

	if hd.MobiHeader.ExthFlags&64 == 64 {
		_, err = GetExthData(file, &hd.Exth, offset)
	}

	if hd.MobiHeader.FcisCount > 0 {
		offset := int64(hd.Sections[hd.MobiHeader.FcisOffset].DataOffset)
		bytesRead, err = GetStruct(file, &hd.Fcis, 44, offset)
		fmt.Println("FCIS", bytesRead, err)
	}

	if hd.MobiHeader.FlisCount > 0 {
		offset := int64(hd.Sections[hd.MobiHeader.FlisOffset].DataOffset)
		bytesRead, err = GetStruct(file, &hd.Flis, 36, offset)
		fmt.Println("FLIS", bytesRead, err)
	}

	offset = int64(hd.Sections[len(hd.Sections)-1].DataOffset)
	bytesRead, err = GetStruct(file, &hd.Eof, 4, offset)
	fmt.Println("EOF", bytesRead, err)

	return
}

//GetContents reads contents of `file` into `crs`, with record bounds being
//the difference off the DataOffsets in `sections`[first : last + 1]
//Returns the number of content records read, and the error.
func GetContents(file *os.File, crs *[]ContentRecord, sections []PDRecordInfoSection, first, last uint16) (count int, err error) {
	count = 0
	for ii := first; ii <= last; ii++ {
		offset := int64(sections[ii].DataOffset)
		length := int(sections[ii+1].DataOffset - uint32(offset))
		cr := make(ContentRecord, length)
		bytesRead, err := GetStruct(file, &cr, length, offset)
		if bytesRead != length {
			// do something?
		}
		if err != nil {
			// do something?
		}
		*crs = append(*crs, cr)
		count++
	}
	return
}

//GetExthData reads exth data at index `offset` in `file`, and copies
//the contents into into `ex`. Offset is relative to the start of the file,
//not the start of the record.
//Return the total number of bytes read and the error.
func GetExthData(file *os.File, ex *ExthData, offset int64) (total int, err error) {
	read, err := GetStruct(file, &ex.Header, 12, offset)
	total += read
	for ii := uint32(0); ii < ex.Header.RecordCount; ii++ {
		var record ExthRecord
		read, err = GetStruct(file, &record.Info, 8, offset+int64(total))
		total += read
		record.Data = make([]byte, int(record.Info.Length-8))
		read, err = GetStruct(file, &record.Data, len(record.Data), offset+int64(total))
		total += read
		ex.Records = append(ex.Records, record)
	}
	return
}

//check panics when there's an error.
func check(err error) {
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>package modd

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/cortesi/termlog"
	"github.com/rjeczalik/notify"
)

// Version is the modd release version
const Version = "0.1"

// MaxLullWait is the maximum time to wait for a lull. This only kicks in if
// we've had a constant stream of modifications blocking us.
const MaxLullWait = time.Second * 8

// Logger receives events as "debug", and is silenced by default
var Logger = defaultLogger()

func defaultLogger() termlog.Logger {
	l := termlog.NewLog()
	l.Quiet()
	return l
}

// isUnder takes two absolute paths, and returns true if child is under parent.
func isUnder(parent string, child string) bool {
	parent = filepath.ToSlash(parent)
	child = filepath.ToSlash(child)
	off := strings.Index(child, parent)
	if off == 0 && (len(child) == len(parent) || child[len(parent)] == '/') {
		return true
	}
	return false
}

// Notify events have absolute paths. We want to normalize these so that they
// are relative to the base path.
func normPath(bases []string, abspath string) (string, error) {
	for _, base := range bases {
		absbase, err := filepath.Abs(base)
		if isUnder(absbase, abspath) {
			if err != nil {
				return "", err
			}
			relpath, err := filepath.Rel(absbase, abspath)
			if err != nil {
				return "", err
			}
			return filepath.Join(base, relpath), nil
		}
	}
	return abspath, nil
}

func normPaths(bases []string, abspaths []string) ([]string, error) {
	ret := make([]string, len(abspaths))
	for i, p := range abspaths {
		norm, err := normPath(bases, p)
		if err != nil {
			return nil, err
		}
		ret[i] = norm
	}
	return ret, nil
}

// An existenceChecker checks the existence of a file
type existenceChecker interface {
	Check(p string) bool
}

type statExistenceChecker struct{}

func (sc statExistenceChecker) Check(p string) bool {
	_, err := os.Stat(p)
	if err == nil {
		return true
	}
	return false
}

// Mod encapsulates a set of changes
type Mod struct {
	Changed []string
	Deleted []string
	Added   []string
}

func (mod Mod) String() string {
	return fmt.Sprintf(
		"Added: %v\nDeleted: %v\nChanged: %v",
		mod.Added, mod.Deleted, mod.Changed,
	)
}

// All returns a single list of all changed files
func (mod Mod) All() []string {
	all := make(map[string]bool)
	for _, p := range mod.Changed {
		all[p] = true
	}
	for _, p := range mod.Added {
		all[p] = true
	}
	for _, p := range mod.Deleted {
		all[p] = true
	}
	return _keys(all)
}

// Empty checks if this mod set is empty
func (mod Mod) Empty() bool {
	if len(mod.Changed) > 0 || len(mod.Deleted) > 0 || len(mod.Added) > 0 {
		return false
	}
	return true
}

// Filter applies a filter, returning a new Mod structure
func (mod *Mod) Filter(includes []string, excludes []string) (*Mod, error) {
	changed, err := filterFiles(mod.Changed, includes, excludes)
	if err != nil {
		return nil, err
	}
	deleted, err := filterFiles(mod.Deleted, includes, excludes)
	if err != nil {
		return nil, err
	}
	added, err := filterFiles(mod.Added, includes, excludes)
	if err != nil {
		return nil, err
	}
	return &Mod{Changed: changed, Deleted: deleted, Added: added}, nil
}

func (mod *Mod) normPaths(bases []string) (*Mod, error) {
	changed, err := normPaths(bases, mod.Changed)
	if err != nil {
		return nil, err
	}
	deleted, err := normPaths(bases, mod.Deleted)
	if err != nil {
		return nil, err
	}
	added, err := normPaths(bases, mod.Added)
	if err != nil {
		return nil, err
	}
	return &Mod{Changed: changed, Deleted: deleted, Added: added}, nil
}

func _keys(m map[string]bool) []string {
	if len(m) > 0 {
		keys := make([]string, len(m))
		i := 0
		for k := range m {
			keys[i] = k
			i++
		}
		sort.Strings(keys)
		return keys
	}
	return nil
}

type fset map[string]bool

func mkmod(exists existenceChecker, added fset, removed fset, changed fset, renamed fset) *Mod {
	ret := &Mod{}
	for k := range renamed {
		// If a file is moved from A to B, we'll get separate rename
		// events for both A and B. The only way to know if it was the
		// source or destination is to check if the file exists.
		if exists.Check(k) {
			added[k] = true
		} else {
			removed[k] = true
		}
	}
	for k := range added {
		if exists.Check(k) {
			// If a file exists, and has been both added and
			// changed, we just mark it as added
			delete(changed, k)
			delete(removed, k)
		} else {
			// If a file has been added, and now does not exist, we
			// strike it everywhere. This probably means the file is
			// transient - i.e. has been quickly added and removed, or
			// we've just not recieved a removal notification.
			delete(added, k)
			delete(removed, k)
			delete(changed, k)
		}
	}
	for k := range removed {
		if exists.Check(k) {
			delete(removed, k)
		} else {
			delete(added, k)
			delete(changed, k)
		}
	}
	ret.Added = _keys(added)
	ret.Changed = _keys(changed)
	ret.Deleted = _keys(removed)
	return ret

}

// This function batches events up, and emits just a list of paths for files
// considered changed. It applies some heuristics to deal with short-lived
// temporary files and unreliable filesystem events. There are all sorts of
// challenges here, that mean we can only do a mediocre job as it stands.
//
// - There's no cross-platform way to get the source as well as the destination
// for Rename events.
// - Events can arrive out of order - i.e. we can get a removal event first
// then a creation event for a transient file.
// - Events seem to be unreliable on some platforms - i.e. we might get a
// removal event but never see a creation event.
// - Events appear nonsensical on some platforms - i.e. we sometimes get a
// Create event as well as a Remove event when a pre-existing file is removed.
//
// In the face of all this, all we can do is layer on a set of heuristics to
// try to get intuitive results.
func batch(lullTime time.Duration, maxTime time.Duration, exists existenceChecker, ch chan notify.EventInfo) *Mod {
	added := make(map[string]bool)
	removed := make(map[string]bool)
	changed := make(map[string]bool)
	renamed := make(map[string]bool)
	// Have we had a modification in the last lull
	hadLullMod := false
	for {
		select {
		case evt := <-ch:
			hadLullMod = true
			Logger.SayAs("debug", "%s", evt)
			switch evt.Event() {
			case notify.Create:
				added[evt.Path()] = true
			case notify.Remove:
				removed[evt.Path()] = true
			case notify.Write:
				changed[evt.Path()] = true
			case notify.Rename:
				renamed[evt.Path()] = true
			}
		case <-time.After(lullTime):
			// Have we had a lull?
			if hadLullMod == false {
				return mkmod(exists, added, removed, changed, renamed)
			}
			hadLullMod = false
		case <-time.After(maxTime):
			return mkmod(exists, added, removed, changed, renamed)
		}
	}
}

// Watch watches a set of paths. Mod structs representing a changeset are sent
// on the channel ch.
//
// Watch applies heuristics to cope with transient files and unreliable event
// notifications. Modifications are batched up until there is a a lull in the
// stream of changes of duration lullTime. This lets us represent processes
// that progressively affect multiple files, like rendering, as a single
// changeset.
func Watch(paths []string, lullTime time.Duration, ch chan Mod) error {
	evtch := make(chan notify.EventInfo, 4096)
	for _, p := range paths {
		stat, err := os.Stat(p)
		if err != nil {
			return err
		}
		if stat.IsDir() {
			p = path.Join(p, "...")
		}
		err = notify.Watch(p, evtch, notify.All)
		if err != nil {
			return err
		}
	}
	go func() {
		for {
			ret := batch(lullTime, MaxLullWait, statExistenceChecker{}, evtch)
			if ret != nil && !ret.Empty() {
				ret, err := ret.normPaths(paths)
				if err != nil {
					Logger.Shout("Error normalising paths: %s", err)
				}
				ch <- *ret
			}
		}
	}()
	return nil
}

// CommonExcludes is a list of commonly excluded files suitable for passing in
// the excludes parameter to Watch - includes repo directories, temporary
// files, and so forth.
var CommonExcludes = []string{
	// VCS
	"**/.git/**",
	"**/.hg/**",
	"**/.svn/**",

	// OSX
	"**/.DS_Store/**",

	// Temporary files
	"**.tmp",
	"**~",
	"**#",
	"**.bak",
	"**.swp",

	// Python
	"**.py[cod]",
}
<commit_msg>Add bzr to common excludes<commit_after>package modd

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/cortesi/termlog"
	"github.com/rjeczalik/notify"
)

// Version is the modd release version
const Version = "0.1"

// MaxLullWait is the maximum time to wait for a lull. This only kicks in if
// we've had a constant stream of modifications blocking us.
const MaxLullWait = time.Second * 8

// Logger receives events as "debug", and is silenced by default
var Logger = defaultLogger()

func defaultLogger() termlog.Logger {
	l := termlog.NewLog()
	l.Quiet()
	return l
}

// isUnder takes two absolute paths, and returns true if child is under parent.
func isUnder(parent string, child string) bool {
	parent = filepath.ToSlash(parent)
	child = filepath.ToSlash(child)
	off := strings.Index(child, parent)
	if off == 0 && (len(child) == len(parent) || child[len(parent)] == '/') {
		return true
	}
	return false
}

// Notify events have absolute paths. We want to normalize these so that they
// are relative to the base path.
func normPath(bases []string, abspath string) (string, error) {
	for _, base := range bases {
		absbase, err := filepath.Abs(base)
		if isUnder(absbase, abspath) {
			if err != nil {
				return "", err
			}
			relpath, err := filepath.Rel(absbase, abspath)
			if err != nil {
				return "", err
			}
			return filepath.Join(base, relpath), nil
		}
	}
	return abspath, nil
}

func normPaths(bases []string, abspaths []string) ([]string, error) {
	ret := make([]string, len(abspaths))
	for i, p := range abspaths {
		norm, err := normPath(bases, p)
		if err != nil {
			return nil, err
		}
		ret[i] = norm
	}
	return ret, nil
}

// An existenceChecker checks the existence of a file
type existenceChecker interface {
	Check(p string) bool
}

type statExistenceChecker struct{}

func (sc statExistenceChecker) Check(p string) bool {
	_, err := os.Stat(p)
	if err == nil {
		return true
	}
	return false
}

// Mod encapsulates a set of changes
type Mod struct {
	Changed []string
	Deleted []string
	Added   []string
}

func (mod Mod) String() string {
	return fmt.Sprintf(
		"Added: %v\nDeleted: %v\nChanged: %v",
		mod.Added, mod.Deleted, mod.Changed,
	)
}

// All returns a single list of all changed files
func (mod Mod) All() []string {
	all := make(map[string]bool)
	for _, p := range mod.Changed {
		all[p] = true
	}
	for _, p := range mod.Added {
		all[p] = true
	}
	for _, p := range mod.Deleted {
		all[p] = true
	}
	return _keys(all)
}

// Empty checks if this mod set is empty
func (mod Mod) Empty() bool {
	if len(mod.Changed) > 0 || len(mod.Deleted) > 0 || len(mod.Added) > 0 {
		return false
	}
	return true
}

// Filter applies a filter, returning a new Mod structure
func (mod *Mod) Filter(includes []string, excludes []string) (*Mod, error) {
	changed, err := filterFiles(mod.Changed, includes, excludes)
	if err != nil {
		return nil, err
	}
	deleted, err := filterFiles(mod.Deleted, includes, excludes)
	if err != nil {
		return nil, err
	}
	added, err := filterFiles(mod.Added, includes, excludes)
	if err != nil {
		return nil, err
	}
	return &Mod{Changed: changed, Deleted: deleted, Added: added}, nil
}

func (mod *Mod) normPaths(bases []string) (*Mod, error) {
	changed, err := normPaths(bases, mod.Changed)
	if err != nil {
		return nil, err
	}
	deleted, err := normPaths(bases, mod.Deleted)
	if err != nil {
		return nil, err
	}
	added, err := normPaths(bases, mod.Added)
	if err != nil {
		return nil, err
	}
	return &Mod{Changed: changed, Deleted: deleted, Added: added}, nil
}

func _keys(m map[string]bool) []string {
	if len(m) > 0 {
		keys := make([]string, len(m))
		i := 0
		for k := range m {
			keys[i] = k
			i++
		}
		sort.Strings(keys)
		return keys
	}
	return nil
}

type fset map[string]bool

func mkmod(exists existenceChecker, added fset, removed fset, changed fset, renamed fset) *Mod {
	ret := &Mod{}
	for k := range renamed {
		// If a file is moved from A to B, we'll get separate rename
		// events for both A and B. The only way to know if it was the
		// source or destination is to check if the file exists.
		if exists.Check(k) {
			added[k] = true
		} else {
			removed[k] = true
		}
	}
	for k := range added {
		if exists.Check(k) {
			// If a file exists, and has been both added and
			// changed, we just mark it as added
			delete(changed, k)
			delete(removed, k)
		} else {
			// If a file has been added, and now does not exist, we
			// strike it everywhere. This probably means the file is
			// transient - i.e. has been quickly added and removed, or
			// we've just not recieved a removal notification.
			delete(added, k)
			delete(removed, k)
			delete(changed, k)
		}
	}
	for k := range removed {
		if exists.Check(k) {
			delete(removed, k)
		} else {
			delete(added, k)
			delete(changed, k)
		}
	}
	ret.Added = _keys(added)
	ret.Changed = _keys(changed)
	ret.Deleted = _keys(removed)
	return ret

}

// This function batches events up, and emits just a list of paths for files
// considered changed. It applies some heuristics to deal with short-lived
// temporary files and unreliable filesystem events. There are all sorts of
// challenges here, that mean we can only do a mediocre job as it stands.
//
// - There's no cross-platform way to get the source as well as the destination
// for Rename events.
// - Events can arrive out of order - i.e. we can get a removal event first
// then a creation event for a transient file.
// - Events seem to be unreliable on some platforms - i.e. we might get a
// removal event but never see a creation event.
// - Events appear nonsensical on some platforms - i.e. we sometimes get a
// Create event as well as a Remove event when a pre-existing file is removed.
//
// In the face of all this, all we can do is layer on a set of heuristics to
// try to get intuitive results.
func batch(lullTime time.Duration, maxTime time.Duration, exists existenceChecker, ch chan notify.EventInfo) *Mod {
	added := make(map[string]bool)
	removed := make(map[string]bool)
	changed := make(map[string]bool)
	renamed := make(map[string]bool)
	// Have we had a modification in the last lull
	hadLullMod := false
	for {
		select {
		case evt := <-ch:
			hadLullMod = true
			Logger.SayAs("debug", "%s", evt)
			switch evt.Event() {
			case notify.Create:
				added[evt.Path()] = true
			case notify.Remove:
				removed[evt.Path()] = true
			case notify.Write:
				changed[evt.Path()] = true
			case notify.Rename:
				renamed[evt.Path()] = true
			}
		case <-time.After(lullTime):
			// Have we had a lull?
			if hadLullMod == false {
				return mkmod(exists, added, removed, changed, renamed)
			}
			hadLullMod = false
		case <-time.After(maxTime):
			return mkmod(exists, added, removed, changed, renamed)
		}
	}
}

// Watch watches a set of paths. Mod structs representing a changeset are sent
// on the channel ch.
//
// Watch applies heuristics to cope with transient files and unreliable event
// notifications. Modifications are batched up until there is a a lull in the
// stream of changes of duration lullTime. This lets us represent processes
// that progressively affect multiple files, like rendering, as a single
// changeset.
func Watch(paths []string, lullTime time.Duration, ch chan Mod) error {
	evtch := make(chan notify.EventInfo, 4096)
	for _, p := range paths {
		stat, err := os.Stat(p)
		if err != nil {
			return err
		}
		if stat.IsDir() {
			p = path.Join(p, "...")
		}
		err = notify.Watch(p, evtch, notify.All)
		if err != nil {
			return err
		}
	}
	go func() {
		for {
			ret := batch(lullTime, MaxLullWait, statExistenceChecker{}, evtch)
			if ret != nil && !ret.Empty() {
				ret, err := ret.normPaths(paths)
				if err != nil {
					Logger.Shout("Error normalising paths: %s", err)
				}
				ch <- *ret
			}
		}
	}()
	return nil
}

// CommonExcludes is a list of commonly excluded files suitable for passing in
// the excludes parameter to Watch - includes repo directories, temporary
// files, and so forth.
var CommonExcludes = []string{
	// VCS
	"**/.git/**",
	"**/.hg/**",
	"**/.svn/**",
	"**/.bzr/**",

	// OSX
	"**/.DS_Store/**",

	// Temporary files
	"**.tmp",
	"**~",
	"**#",
	"**.bak",
	"**.swp",

	// Python
	"**.py[cod]",
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
)

var Vector [8][8]int = [8][8]int{
	{0, 0, 0, 0, 0, 0, 0, 0},               // empty
	{0, 0, 0, 0, 0, 0, 0, 0},               // pawn - handled specially.
	{+21, +12, -8, -19, -21, -12, +8, +19}, // N
	{+11, -9, -11, +9, 0, 0, 0, 0},         // B
	{+10, +1, -10, -1, 0, 0, 0, 0},         // R
	{+10, +11, +1, -9, -10, -11, -1, +9},   // Q
	{+10, +11, +1, -9, -10, -11, -1, +9},   // K
	{0, 0, 0, 0, 0, 0, 0, 0},               // ?
}

var Slide [8]bool = [8]bool{false, false, false, true, true, true, false, false}

const (
	MoveQuiet byte = iota
	MoveDoublePush
	MoveCapture
	MoveEnPassant
)

type Move struct {
	From    byte
	To      byte
	Kind    byte
	Promote byte
	Score   int16
}

func pawnmove(b *Board, i byte, retval []Move) []Move {
	var PawnPush, DoublePush byte
	CanDouble := false
	if b.ToMove == BLACK {
		PawnPush = i - 10
		DoublePush = i - 20
		CanDouble = i/10 == 8
	} else {
		PawnPush = i + 10
		DoublePush = i + 20
		CanDouble = i/10 == 3
	}
	if GetPiece(b.Data[PawnPush]) == EMPTY {
		retval = append(retval, Move{i,
			PawnPush, MoveQuiet, EMPTY, 0})
		if CanDouble && GetPiece(b.Data[DoublePush]) ==
			EMPTY {
			retval = append(retval, Move{i,
				DoublePush, MoveDoublePush, EMPTY, 0})
		}
	}
	retval = pawncap(b, i, retval, PawnPush-1)
	retval = pawncap(b, i, retval, PawnPush+1)
	return retval
}

func pawncap(b *Board, i byte, retval []Move, place byte) []Move {
	if OnBoard(place) && GetPiece(b.Data[place]) != EMPTY &&
		GetSide(b.Data[place]) != b.ToMove {
		retval = append(retval, Move{i,
			place, MoveCapture, EMPTY, 0})
	} else if OnBoard(place) && GetPiece(b.Data[place]) == EMPTY && b.EnPassant == place {
		retval = append(retval, Move{i, place, MoveEnPassant, EMPTY, 0})
	}
	return retval
}

func squareattacked(b *Board, i byte, attacking byte) bool {
	var PawnPush byte
	if attacking == BLACK {
		PawnPush = i + 10
	} else {
		PawnPush = i - 10
	}
	if (GetSide(b.Data[PawnPush-1]) == attacking && GetPiece(b.Data[PawnPush-1]) == PAWN) || (GetSide(b.Data[PawnPush+1]) == attacking && GetPiece(b.Data[PawnPush+1]) == PAWN) {
		return true
	}
	for dir := 0; dir < 8; dir++ {
		from := i
		for {
			to := byte(int(from) + Vector[QUEEN][dir])
			piece := GetPiece(b.Data[to])
			if b.Data[to] == OFFBOARD || (piece != EMPTY && GetSide(b.Data[to]) != attacking) {
				break
			} else if piece == QUEEN && GetSide(b.Data[to]) == attacking {
				return true
			} else if piece == ROOK && GetSide(b.Data[to]) == attacking && (Vector[QUEEN][dir] == 10 || Vector[QUEEN][dir] == -10 || Vector[QUEEN][dir] == 1 || Vector[QUEEN][dir] == -1) {
				return true
			} else if piece == BISHOP && GetSide(b.Data[to]) == attacking && (Vector[QUEEN][dir] == 11 || Vector[QUEEN][dir] == -11 || Vector[QUEEN][dir] == 9 || Vector[QUEEN][dir] == -9) {
				return true
			} else if piece == EMPTY {
				from = to
			} else {
				break
			}
		}
		to := byte(int(i) + Vector[KNIGHT][dir])
		if b.Data[to] != OFFBOARD && GetPiece(b.Data[to]) == KNIGHT && GetSide(b.Data[to]) == attacking {
			return true
		}
	}
	return false
}

func quietmove(b *Board, i byte, retval []Move) []Move {
	piece := GetPiece(b.Data[i])
	for dir := 0; dir < 8; dir++ {
		if Vector[piece][dir] == 0 {
			break
		}
		from := i
		for {
			to := byte(int(from) + Vector[piece][dir])
			if b.Data[to] != OFFBOARD {
				if GetPiece(b.Data[to]) == EMPTY {
					retval = append(retval, Move{i,
						to, MoveQuiet, EMPTY, 0})
					if Slide[piece] {
						from = to
					} else {
						break
					}
				} else if GetSide(b.Data[to]) != b.ToMove {
					retval = append(retval, Move{i,
						to, MoveCapture, EMPTY, 0})
					break
				} else {
					break
				}
			} else {
				break
			}
		}
	}
	return retval
}

func MoveGen(b *Board) []Move {
	retval := make([]Move, 0, 32)
	for i := A1; i <= H8; i++ {
		if !OnBoard(i) || GetPiece(b.Data[i]) == EMPTY || GetSide(b.Data[i]) != b.ToMove {
			continue
		}
		if GetPiece(b.Data[i]) == PAWN {
			retval = pawnmove(b, i, retval)
		} else {
			retval = quietmove(b, i, retval)
		}
	}
	return retval
}

func MakeMove(b *Board, m *Move) {
	b.EnPassant = INVALID
	b.Data[m.To] = b.Data[m.From]
	b.Data[m.From] = EMPTY
	switch m.Kind {
	case MoveQuiet:
		/* Do nothing */
	case MoveDoublePush:
		if b.ToMove == BLACK {
			b.EnPassant = m.From - 10
		} else {
			b.EnPassant = m.From + 10
		}
	case MoveEnPassant:
		if b.ToMove == BLACK {
			b.Data[m.To+10] = EMPTY
		} else {
			b.Data[m.To-10] = EMPTY
		}
	}
	b.ToMove ^= BLACK
}

func (m Move) String() string {
	return fmt.Sprint("{From: ", IndexToAlgebraic(m.From), " to: ",
		IndexToAlgebraic(m.To), " type: ", m.Kind, "}")
}

func DoDividePerft(depth int) uint64 {
	board, _ := Parse(START)
	return Perft(depth, board, true)
}

func Perft(depth int, board *Board, divide bool) uint64 {
	if depth == 0 {
		return 1
	}
	var nodes uint64 = 0
	moves := MoveGen(board)
	for _, move := range moves {
		boardc := *board
		MakeMove(&boardc, &move)
		if Illegal(&boardc) {
			continue
		}
		if divide {
			fmt.Printf("%s%s ", IndexToAlgebraic(move.From), IndexToAlgebraic(move.To))
		}
		tmp := Perft(depth-1, &boardc, false)
		nodes += tmp
		if divide {
			fmt.Printf("%d\n", tmp)
		}

	}
	return nodes
}

func ParseMove(m string) (*Move, error) {
	if len(m) != 4 {
		return nil, errors.New("Move is wrong length")
	}
	from := m[:2]
	to := m[2:]
	fromi, err1 := AlgebraicToIndex(from)
	if err1 != nil {
		return nil, err1
	}
	toi, err2 := AlgebraicToIndex(to)
	if err2 != nil {
		return nil, err2
	}
	return &Move{fromi, toi, MoveQuiet, EMPTY, 0}, nil
}
<commit_msg>switch from Copy-Make to Make-Unmake<commit_after>package main

import (
	"errors"
	"fmt"
)

var Vector [8][8]int = [8][8]int{
	{0, 0, 0, 0, 0, 0, 0, 0},               // empty
	{0, 0, 0, 0, 0, 0, 0, 0},               // pawn - handled specially.
	{+21, +12, -8, -19, -21, -12, +8, +19}, // N
	{+11, -9, -11, +9, 0, 0, 0, 0},         // B
	{+10, +1, -10, -1, 0, 0, 0, 0},         // R
	{+10, +11, +1, -9, -10, -11, -1, +9},   // Q
	{+10, +11, +1, -9, -10, -11, -1, +9},   // K
	{0, 0, 0, 0, 0, 0, 0, 0},               // ?
}

var Slide [8]bool = [8]bool{false, false, false, true, true, true, false, false}

const (
	MoveQuiet byte = iota
	MoveDoublePush
	MoveCapture
	MoveEnPassant
)

type Move struct {
	From    byte
	To      byte
	Kind    byte
	Promote byte
	Score   int16
}

type Undo struct {
	ToData    byte
	EnPassant byte
}

func pawnmove(b *Board, i byte, retval []Move) []Move {
	var PawnPush, DoublePush byte
	CanDouble := false
	if b.ToMove == BLACK {
		PawnPush = i - 10
		DoublePush = i - 20
		CanDouble = i/10 == 8
	} else {
		PawnPush = i + 10
		DoublePush = i + 20
		CanDouble = i/10 == 3
	}
	if GetPiece(b.Data[PawnPush]) == EMPTY {
		retval = append(retval, Move{i,
			PawnPush, MoveQuiet, EMPTY, 0})
		if CanDouble && GetPiece(b.Data[DoublePush]) ==
			EMPTY {
			retval = append(retval, Move{i,
				DoublePush, MoveDoublePush, EMPTY, 0})
		}
	}
	retval = pawncap(b, i, retval, PawnPush-1)
	retval = pawncap(b, i, retval, PawnPush+1)
	return retval
}

func pawncap(b *Board, i byte, retval []Move, place byte) []Move {
	if OnBoard(place) && GetPiece(b.Data[place]) != EMPTY &&
		GetSide(b.Data[place]) != b.ToMove {
		retval = append(retval, Move{i,
			place, MoveCapture, EMPTY, 0})
	} else if OnBoard(place) && GetPiece(b.Data[place]) == EMPTY && b.EnPassant == place {
		retval = append(retval, Move{i, place, MoveEnPassant, EMPTY, 0})
	}
	return retval
}

func squareattacked(b *Board, i byte, attacking byte) bool {
	var PawnPush byte
	if attacking == BLACK {
		PawnPush = i + 10
	} else {
		PawnPush = i - 10
	}
	if (GetSide(b.Data[PawnPush-1]) == attacking && GetPiece(b.Data[PawnPush-1]) == PAWN) || (GetSide(b.Data[PawnPush+1]) == attacking && GetPiece(b.Data[PawnPush+1]) == PAWN) {
		return true
	}
	for dir := 0; dir < 8; dir++ {
		from := i
		for {
			to := byte(int(from) + Vector[QUEEN][dir])
			piece := GetPiece(b.Data[to])
			if b.Data[to] == OFFBOARD || (piece != EMPTY && GetSide(b.Data[to]) != attacking) {
				break
			} else if piece == QUEEN && GetSide(b.Data[to]) == attacking {
				return true
			} else if piece == ROOK && GetSide(b.Data[to]) == attacking && (Vector[QUEEN][dir] == 10 || Vector[QUEEN][dir] == -10 || Vector[QUEEN][dir] == 1 || Vector[QUEEN][dir] == -1) {
				return true
			} else if piece == BISHOP && GetSide(b.Data[to]) == attacking && (Vector[QUEEN][dir] == 11 || Vector[QUEEN][dir] == -11 || Vector[QUEEN][dir] == 9 || Vector[QUEEN][dir] == -9) {
				return true
			} else if piece == EMPTY {
				from = to
			} else {
				break
			}
		}
		to := byte(int(i) + Vector[KNIGHT][dir])
		if b.Data[to] != OFFBOARD && GetPiece(b.Data[to]) == KNIGHT && GetSide(b.Data[to]) == attacking {
			return true
		}
	}
	return false
}

func quietmove(b *Board, i byte, retval []Move) []Move {
	piece := GetPiece(b.Data[i])
	for dir := 0; dir < 8; dir++ {
		if Vector[piece][dir] == 0 {
			break
		}
		from := i
		for {
			to := byte(int(from) + Vector[piece][dir])
			if b.Data[to] != OFFBOARD {
				if GetPiece(b.Data[to]) == EMPTY {
					retval = append(retval, Move{i,
						to, MoveQuiet, EMPTY, 0})
					if Slide[piece] {
						from = to
					} else {
						break
					}
				} else if GetSide(b.Data[to]) != b.ToMove {
					retval = append(retval, Move{i,
						to, MoveCapture, EMPTY, 0})
					break
				} else {
					break
				}
			} else {
				break
			}
		}
	}
	return retval
}

func MoveGen(b *Board) []Move {
	retval := make([]Move, 0, 32)
	for i := A1; i <= H8; i++ {
		if !OnBoard(i) || GetPiece(b.Data[i]) == EMPTY || GetSide(b.Data[i]) != b.ToMove {
			continue
		}
		if GetPiece(b.Data[i]) == PAWN {
			retval = pawnmove(b, i, retval)
		} else {
			retval = quietmove(b, i, retval)
		}
	}
	return retval
}

func MakeMove(b *Board, m *Move) *Undo {
	retval := &Undo{b.Data[m.To], b.EnPassant}
	b.EnPassant = INVALID
	b.Data[m.To] = b.Data[m.From]
	b.Data[m.From] = EMPTY
	switch m.Kind {
	case MoveQuiet:
		/* Do nothing */
	case MoveDoublePush:
		if b.ToMove == BLACK {
			b.EnPassant = m.From - 10
		} else {
			b.EnPassant = m.From + 10
		}
	case MoveEnPassant:
		if b.ToMove == BLACK {
			b.Data[m.To+10] = EMPTY
		} else {
			b.Data[m.To-10] = EMPTY
		}
	}
	b.ToMove ^= BLACK
	return retval
}

func UnmakeMove(b *Board, m *Move, u *Undo) {
	b.Data[m.From] = b.Data[m.To]
	b.Data[m.To] = u.ToData
	b.EnPassant = u.EnPassant
	b.ToMove ^= BLACK
	switch m.Kind {
	case MoveEnPassant:
		if b.ToMove == BLACK {
			b.Data[m.To+10] = (b.ToMove ^ BLACK) | PAWN
		} else {
			b.Data[m.To-10] = (b.ToMove ^ BLACK) | PAWN
		}
	}
}

func (m Move) String() string {
	return fmt.Sprint("{From: ", IndexToAlgebraic(m.From), " to: ",
		IndexToAlgebraic(m.To), " type: ", m.Kind, "}")
}

func DoDividePerft(depth int) uint64 {
	board, _ := Parse(START)
	return Perft(depth, board, true)
}

func Perft(depth int, board *Board, divide bool) uint64 {
	if depth == 0 {
		return 1
	}
	var nodes uint64 = 0
	moves := MoveGen(board)
	for _, move := range moves {
		undo := MakeMove(board, &move)
		if Illegal(board) {
			UnmakeMove(board, &move, undo)
			continue
		}
		if divide {
			fmt.Printf("%s%s ", IndexToAlgebraic(move.From), IndexToAlgebraic(move.To))
		}
		tmp := Perft(depth-1, board, false)
		nodes += tmp
		if divide {
			fmt.Printf("%d\n", tmp)
		}
		UnmakeMove(board, &move, undo)

	}
	return nodes
}

func ParseMove(m string) (*Move, error) {
	if len(m) != 4 {
		return nil, errors.New("Move is wrong length")
	}
	from := m[:2]
	to := m[2:]
	fromi, err1 := AlgebraicToIndex(from)
	if err1 != nil {
		return nil, err1
	}
	toi, err2 := AlgebraicToIndex(to)
	if err2 != nil {
		return nil, err2
	}
	return &Move{fromi, toi, MoveQuiet, EMPTY, 0}, nil
}
<|endoftext|>